網易雲音樂評論 可視化分析

       以前已經用python獲取了網易雲音樂的評論數據,下一步的工做就是數據分析了。通常數據分析無非是採用(統計)數字、圖或者表的形式來展示數據之中隱含的信息。其中圖和表顯然是最直觀的了。因此這裏我使用可視化的方法即用圖形來展現從評論中挖掘到的各類信息。javascript

        可視化的工具備不少,好比常見的有excel還有一些專門的繪圖軟件,各個編程語言固然也有不少可視化的包或者庫,好比統計上使用不少的R語言就有不少可視化的庫,我最喜歡的就是ggplot2了,我使用R語言主要用於數據的清洗以及可視化,其豐富的包(package)大大簡化了數據分析的工做量,並且能夠繪製很是複雜、精美的圖表,之後有機會能夠給你們專門介紹一下。python中可視化的庫也不少,最著名的的莫過於matplotlib了,這是一個面向對象的繪圖庫,不少方面的用法和matlab相似(從matlab的繪圖風格借鑑而來),因爲我之前使用過一段時間的matlab,因此上手仍是比較快,其餘的還有seaborn(聽說是對matplotlib的改進和封裝,使用起來更加方便,沒用過,有時間再研究下)、pygraph等。可是使用最普遍的仍是matplotlib。javascript有Echarts(百度的)等,這個我還沒接觸過,是一個能夠網頁上進行可視化的函數庫,聽說很棒。其餘的固然還有特別多,這裏我就不一一列舉了。有興趣的小夥伴能夠自行去查閱資料。這裏我決定使用matplotlib,主要是由於最近主要接觸的就是python,可是數據可視化方面的庫用的很少,恰好能夠拿此次的數據來練練手,其次我接觸過matlab,相信對matplotlib入手會更快一點。html

         此次主要分析的有如下幾個方面:一、一首歌曲評論數目隨時間變化的趨勢,好比天天的評論數變化,每個月的評論數變化等等。二、一首歌曲點贊數目分佈的狀況,好比0-10贊有多少個,佔多少比例,1000贊以上佔多大比例等等。三、熱門評論詞雲的製做,主要想經過詞雲,將文本挖掘的結果可視化,能夠看出哪些是高頻詞彙等。四、一首歌曲評論者的基本信息的狀況展現,好比評論者的地區分佈,年齡分佈、累計聽歌數目分佈、動態分佈、粉絲數分佈等等。經過這些信息,能夠直觀看出一首歌曲被哪些地區、哪些年齡段的人所喜好,以及聽歌的人具備什麼的特色等等。java

     好了,廢話很少說了,直接上圖吧。python

         首先來看一些歌曲評論數隨時間的變化。正則表達式

圖 1編程

圖 2json

圖 3api

圖 4服務器

圖 5網絡

         上面的5張圖我分別選取了5首不一樣的歌曲,有華語歌曲也有英文歌曲,有的起止時間很長(從13年就開始),也有的起止時間很短(從最近幾個月纔開始)。總的來講能夠分爲兩種模式,一種是開始一段時間評論數不多,後來逐漸呈現爆發式地增加,前面三首歌《同桌的你》、《七里香》、《All Too Well》都是這種模式,然後面兩首歌《不要再孤單》、《stay》則是剛好相反,歌曲剛剛出來的那幾天評論數猛增,後面評論數逐漸降低,以後趨於平穩。經過分析,其實也很好理解,第一種模式的歌曲,每每都是早期曲庫中就存在的歌曲(也能夠稱之爲「老歌」),那個時候網易雲音樂纔剛剛出來,用戶數目還不多,因此這些歌曲天天的評論數不多(沒記錯的話網易雲應該是十二、13年左右纔出來的吧),後來網易雲一路走紅,直至如今號稱有2億用戶,因爲用戶基數大,因此這些經典的老歌天然評論數猛增了,能夠想見,這種評論爆發式增加和網易雲音樂用戶的增加趨勢應該是基本一致的。而至於第二種模式,出現這種模式的歌曲每每都是比較新的歌曲,並且每每伴隨着影視劇的火熱忽然火起來,好比《不要再孤單》就是電影的主題曲,電影剛上映的那段時間,歌隨影熱,評論數天然爆發式增加,後來這段熱潮過去了,評論數天然就降下來了(固然這種歌曲應該以網絡歌曲居多,只是某一段時間特別火,不黑,我以爲真正的經典評論數應該不會大起大落,好比《晴天》、《see you again》等)。固然我只是分析了兩種典型的評論隨時間變化的模式,實際確定不止這兩種模式,你們能夠自行去探索。

           

圖 6

圖 7

                前面5張圖都是使用折線圖來展現的,圖6使用的是柱狀圖。咱們來看下圖7,圖7展現的最近一段時間比較火的李玉剛的歌曲《恰好碰見你》的評論數隨時間的分佈,讓人感到奇怪的是,中間從大約1月23日到3月24日的天天的評論量居然是0!這怎麼可能呢?難道真的是這樣麼?固然不是。我解釋一下緣由,這是程序自己的bug,我在抓取評論數過10W的歌曲的過程當中發現,我最終看似抓取了所有的評論,可是實際上在去除重複以後,我只獲得了部分的數據,每次大概只能獲得2W到3W左右的數據,其餘的數據就缺失了。至今我也沒能解決這個問題,我的以爲是服務器作了什麼限制,若是有朋友知道該怎麼解決這個問題,望能不吝賜教!

      除了能夠從宏觀上看一首歌曲天天或者每個月的評論數分佈以外,咱們還能夠將不一樣的歌曲評論隨時間變化放到一塊兒對比,或者將一首歌曲每個月的評論數放在一塊兒進行對比。

圖 8

圖 9

圖 10

                   圖 8 就展現了四首不一樣的歌曲在某一個時間段評論數目隨時間變化,圖9展現了《同桌的你》從16年8月到17年3月這8個月的時間裏每個月評論數的分佈狀況,圖10則是《越長大越孤單》從16年4月到17年3月這12個月的每個月評論數分佈。其實,這種圖形很容易作出,由於我已經將繪圖函數作了封裝,能夠設置自定義參數字典,來生成本身想要的不一樣的圖形,也能夠選擇繪製圖形的種類、顏色以及繪製的時間段、時間間隔等,在文末我會說明這一點。

       接下來,看一下評論點贊數目的分佈狀況。

圖 11

圖 12

    圖10和圖11展現的點贊數目分佈我去除了10贊如下的,緣由是我發現一首歌曲絕大部分的點贊數目(超過99%)都是10贊如下的,這也與咱們的常識相一致,因此爲了方便我就直接去除了。經過上面的兩張圖咱們能夠看出,紅色區域面積最大,即100贊到1000佔據了所有10贊以上評論的絕大部分,其次是10到100贊,而後是1000贊到10000贊,最少的是1W贊以上,我發現大部分歌曲基本都是呈現這個規律,因此只在這裏簡單提一下,就不作詳細分析了。

           接下來分析歌曲熱門評論的詞雲展現,其實python的詞雲,我以前的一篇隨筆也有提到過,使用wordcloud(繪製詞雲)和jieba(中文分詞)便可。這裏就不細說了。直接看圖吧。

圖 13 《不要再孤單》詞雲

圖 14   周杰倫熱門50首歌曲熱門評論詞雲

 

圖 15 Taylor Swift 熱門歌曲熱門評論詞雲

    從以上的詞雲中仍是能夠看出一首歌曲或者一位歌手,評論區中出現頻率最高的是哪些詞的。好比杰倫 的熱門評論中反覆出現的詞就有周杰倫、青春、喜歡、女友、故事等等,一股青春懷舊風撲面而來啊。哈哈,其餘有意思的你們本身去分析吧。

    最後,我想重點來分析一下,一首歌曲的評論者我的信息具備什麼樣的特色。我將這些特色放在一張圖中,經過多張餅圖來展現了,見下。

圖 16

圖 17

圖 18

圖 19

圖 20

圖 21

圖 22

     圖 16 到 圖 22 展現了不一樣的歌手(有中有外,有老有少)以及不一樣的歌曲(老歌和新歌)評論者多方面的信息分佈。經過對比不難發現以下的規律。周杰倫粉絲主要仍是以90後和95後爲主,這兩者之和超過80%。周杰倫、Taylor Swift、Bruno Mars 這三位歌手評論者累計聽歌在1000到10000之間的人數(1000-10000算是累計聽歌較多)佔比要顯著高於其餘幾個歌手,粉絲人數在10-100以及100-1000的比例也是如此,這幾位均可以稱得上時下的歌壇巨星,評論者的聽歌數目以及粉絲人數能夠在必定程度上反映出對音樂的喜好程度以及對音樂的鑑賞力吧(不黑)。TFboys評論者中00後的比例高達25%,爲列舉的全部歌手中最高,其餘歌手00後的比例均不超過10%,不過考慮到tf是美少男組合,這也就能夠說的通了。劉德華歌曲的評論者中80後以及80前的比例之和近20%,而其餘歌手這一數字基本在7%左右,這在必定程度上能夠說明劉天王的粉絲最多的仍是而立以後的中年人啊。再來看地區,一眼望去,不管是歌手仍是歌曲,地區分佈的前五中都出現了一個共同的身影,那就是北京市東城區,看來網易雲上有至關一部分用戶都是來自北京市東城區啊,不過考慮到北京市是我國的文化中心,許多明星、歌手均在北京定居,還有網易雲上推薦的一些音樂人不少都在北京(東城區),這點就不難理解了。屢次出現的地區還有廣州市、成都市等等,這些都是經濟較發達的地區,也是文化產業特別是音樂產業發達的地區(廣州主要是粵語歌,並且離香港也很近,成都民謠應該很豐富(猜想))。這麼一考慮,這些結果就不難理解了。固然,能夠挖掘的其餘信息還有不少,好比還有動態的分佈等等,還能夠按照音樂的類別進行對比等等,若是有興趣,你們能夠本身去完成這個工做。

      到這裏,其實評論數據的可視化就差很少結束了。其實我作得很粗糙,不少分析也純屬我我的的臆測,你們隨便看看就好。最後我還有幾點要補充的。

                 在此次寫代碼的過程當中,我一開始以爲應該寫不了幾行應該就把數據可視化搞定了,沒想到最終還花了我挺長的時間,加到一塊兒代碼有七八百行,固然,不少東西都是能夠精簡的,我懶得去弄了。我將繪圖的幾個函數抽象了出來,能夠經過簡單地配置參數字典(settings)傳入函數來配置本身想要的圖形,好比能夠控制要繪製散點圖仍是柱狀圖,控制顏色、時間間隔等等,只須要更改相應的參數字典就能夠了。主要有兩個類,一個是NetCloudCrawl類,主要用於歌曲評論數的抓取,還有一個是NetCloudProcessor,主要用於生成相關文件以及繪製可視化圖形。幾個主要的函數以下:1 create_all_necessary_files(song_id,song_name) 這個函數能夠自動完成評論數據的抓取(包括熱門評論)、保存到文件,生成必要的統計txt文件,生成詞雲等。只須要傳入歌曲id(song_id)以及歌曲名字(song_name)便可。2 plot_comments(song_name,settings)函數,用於繪製基本的評論或者點贊圖形,settings爲參數字典,用於控制繪圖方式以及呈現結果。3 sub_plot_comments(song_names_list,settings,row,col)用於在一張圖中繪製多個歌曲的評論分佈,song_names_list 爲歌曲名字列表,settings 爲繪圖參數字典,row爲子圖行數,col爲子圖列數。  4 draw_wordcloud 用於繪製詞雲  5 sub_plot_months 用於在一張圖中繪製某一首歌曲在某幾個月(按月繪製)中的評論分佈。6 sub_plot_commenters_info 用於繪製歌曲評論者的各項信息分佈 。 還有三個 測試函數,分別是 sub_plot_months_testsubplot_testplot_comments_test 直接調用相應的繪圖函數,能夠方便地在其中配置參數字典,而後直接調用測試函數便可繪製圖形。因此繪製圖形其實只有簡單的兩步:第一步,肯定歌曲名稱以及id(直接去網易雲音樂上找相應歌曲連接便可,?id= 後面的數字就是歌曲id),而後調用create_all_necessary_files 生成所須要的文件;第二步:調用相應的繪圖函數,通常只須要傳入歌曲名字以及參數字典便可。

           最後仍是附上所有的代碼以下:

NetCloud_spider3.py
  1 #!/usr/bin/env python2.7
  2 # -*- coding: utf-8 -*-
  3 # @Time   : 2017/3/28 8:46
  4 # @Author : Lyrichu
  5 # @Email  : 919987476@qq.com
  6 # @File   : NetCloud_spider3.py
  7 '''
  8 @Description:
  9 網易雲音樂評論爬蟲,能夠完整爬取整個評論
 10 部分參考了@平胸小仙女的文章(地址:https://www.zhihu.com/question/36081767)
 11 post加密部分也給出了,能夠參考原帖:
 12 做者:平胸小仙女
 13 連接:https://www.zhihu.com/question/36081767/answer/140287795
 14 來源:知乎
 15 '''
 16 from Crypto.Cipher import AES
 17 import base64
 18 import requests
 19 import json
 20 import codecs
 21 import time
 22 import os
 23 
 24 # 定義抓取評論類NetCloudCrawl
 25 class NetCloudCrawl(object):
 26     def __init__(self):
 27         # 頭部信息
 28         self.headers = {
 29         'Host':"music.163.com",
 30         'User-Agent':"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0",
 31         'Accept-Language':"zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
 32         'Accept-Encoding':"gzip, deflate",
 33         'Content-Type':"application/x-www-form-urlencoded",
 34         'Cookie':"_ntes_nnid=754361b04b121e078dee797cdb30e0fd,1486026808627; _ntes_nuid=754361b04b121e078dee797cdb30e0fd; JSESSIONID-WYYY=yfqt9ofhY%5CIYNkXW71TqY5OtSZyjE%2FoswGgtl4dMv3Oa7%5CQ50T%2FVaee%2FMSsCifHE0TGtRMYhSPpr20i%5CRO%2BO%2B9pbbJnrUvGzkibhNqw3Tlgn%5Coil%2FrW7zFZZWSA3K9gD77MPSVH6fnv5hIT8ms70MNB3CxK5r3ecj3tFMlWFbFOZmGw%5C%3A1490677541180; _iuqxldmzr_=32; vjuids=c8ca7976.15a029d006a.0.51373751e63af8; vjlast=1486102528.1490172479.21; __gads=ID=a9eed5e3cae4d252:T=1486102537:S=ALNI_Mb5XX2vlkjsiU5cIy91-ToUDoFxIw; vinfo_n_f_l_n3=411a2def7f75a62e.1.1.1486349441669.1486349607905.1490173828142; P_INFO=m15527594439@163.com|1489375076|1|study|00&99|null&null&null#hub&420100#10#0#0|155439&1|study_client|15527594439@163.com; NTES_CMT_USER_INFO=84794134%7Cm155****4439%7Chttps%3A%2F%2Fsimg.ws.126.net%2Fe%2Fimg5.cache.netease.com%2Ftie%2Fimages%2Fyun%2Fphoto_default_62.png.39x39.100.jpg%7Cfalse%7CbTE1NTI3NTk0NDM5QDE2My5jb20%3D; usertrack=c+5+hljHgU0T1FDmA66MAg==; Province=027; City=027; _ga=GA1.2.1549851014.1489469781; __utma=94650624.1549851014.1489469781.1490664577.1490672820.8; __utmc=94650624; __utmz=94650624.1490661822.6.2.utmcsr=baidu|utmccn=(organic)|utmcmd=organic; playerid=81568911; __utmb=94650624.23.10.1490672820",
 35         'Connection':"keep-alive",
 36         'Referer':'http://music.163.com/',
 37         'Upgrade-Insecure-Requests':"1"
 38         }
 39         # 設置代理服務器
 40         self.proxies= {
 41             'http:':'http://180.123.225.51',
 42             'https:':'https://111.72.126.116'
 43         }
 44         # offset的取值爲:(評論頁數-1)*20,total第一頁爲true,其他頁爲false
 45         # first_param = '{rid:"", offset:"0", total:"true", limit:"20", csrf_token:""}' # 第一個參數
 46         self.second_param = "010001" # 第二個參數
 47         # 第三個參數
 48         self.third_param = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
 49         # 第四個參數
 50         self.forth_param = "0CoJUm6Qyw8W8jud"
 51         self.encSecKey = "257348aecb5e556c066de214e531faadd1c55d814f9be95fd06d6bff9f4c7a41f831f6394d5a3fd2e3881736d94a02ca919d952872e7d0a50ebfa1769a7a62d512f5f1ca21aec60bc3819a9c3ffca5eca9a0dba6d6f7249b06f5965ecfff3695b54e1c28f3f624750ed39e7de08fc8493242e26dbc4484a01c76f739e135637c"
 52 
 53         # 獲取參數
 54     def get_params(self,page): # page爲傳入頁數
 55         first_key = self.forth_param
 56         second_key = 16 * 'F'
 57         iv = "0102030405060708"
 58         if(page == 1): # 若是爲第一頁
 59             first_param = '{rid:"", offset:"0", total:"true", limit:"20", csrf_token:""}'
 60             h_encText = self.AES_encrypt(first_param, first_key,iv)
 61         else:
 62             offset = str((page-1)*20)
 63             first_param = '{rid:"", offset:"%s", total:"%s", limit:"20", csrf_token:""}' %(offset,'false')
 64             h_encText = self.AES_encrypt(first_param, first_key, iv)
 65         h_encText = self.AES_encrypt(h_encText, second_key, iv)
 66         return h_encText
 67 
 68     # 解密過程
 69     def AES_encrypt(self,text, key, iv):
 70         pad = 16 - len(text) % 16
 71         text = text + pad * chr(pad)
 72         encryptor = AES.new(key, AES.MODE_CBC, iv)
 73         encrypt_text = encryptor.encrypt(text)
 74         encrypt_text = base64.b64encode(encrypt_text)
 75         return encrypt_text
 76 
 77     # 得到評論json數據
 78     def get_json(self,url, params, encSecKey):
 79         data = {
 80              "params": params,
 81              "encSecKey": encSecKey
 82         }
 83         response = requests.post(url, headers=self.headers, data=data,proxies = self.proxies)
 84         return response.content
 85 
 86     # 抓取熱門評論,返回熱評列表
 87     def get_hot_comments(self,url):
 88         hot_comments_list = []
 89         hot_comments_list.append(u"用戶ID 用戶暱稱 用戶頭像地址 評論時間 點贊總數 評論內容\n")
 90         params = self.get_params(1) # 第一頁
 91         json_text = self.get_json(url,params,self.encSecKey)
 92         json_dict = json.loads(json_text)
 93         hot_comments = json_dict['hotComments'] # 熱門評論
 94         print("共有%d條熱門評論!" % len(hot_comments))
 95         for item in hot_comments:
 96                 comment = item['content'] # 評論內容
 97                 likedCount = item['likedCount'] # 點贊總數
 98                 comment_time = item['time'] # 評論時間(時間戳)
 99                 userID = item['user']['userId'] # 評論者id
100                 nickname = item['user']['nickname'] # 暱稱
101                 avatarUrl = item['user']['avatarUrl'] # 頭像地址
102                 comment_info = unicode(userID) + u" " + nickname + u" " + avatarUrl + u" " + unicode(comment_time) + u" " + unicode(likedCount) + u" " + comment + u"\n"
103                 hot_comments_list.append(comment_info)
104         return hot_comments_list
105 
106     # 抓取某一首歌的所有評論
107     def get_all_comments(self,url):
108         all_comments_list = [] # 存放全部評論
109         all_comments_list.append(u"用戶ID 用戶暱稱 用戶頭像地址 評論時間 點贊總數 評論內容\n") # 頭部信息
110         params = self.get_params(1)
111         json_text = self.get_json(url,params,self.encSecKey)
112         json_dict = json.loads(json_text)
113         comments_num = int(json_dict['total'])
114         if(comments_num % 20 == 0):
115             page = comments_num / 20
116         else:
117             page = int(comments_num / 20) + 1
118         print("共有%d頁評論!" % page)
119         for i in range(page):  # 逐頁抓取
120             params = self.get_params(i+1)
121             json_text = self.get_json(url,params,self.encSecKey)
122             json_dict = json.loads(json_text)
123             if i == 0:
124                 print("共有%d條評論!" % comments_num) # 所有評論總數
125             for item in json_dict['comments']:
126                 comment = item['content'] # 評論內容
127                 likedCount = item['likedCount'] # 點贊總數
128                 comment_time = item['time'] # 評論時間(時間戳)
129                 userID = item['user']['userId'] # 評論者id
130                 nickname = item['user']['nickname'] # 暱稱
131                 avatarUrl = item['user']['avatarUrl'] # 頭像地址
132                 comment_info = unicode(userID) + u" " + nickname + u" " + avatarUrl + u" " + unicode(comment_time) + u" " + unicode(likedCount) + u" " + comment + u"\n"
133                 all_comments_list.append(comment_info)
134             print("第%d頁抓取完畢!" % (i+1))
135         return all_comments_list
136 
137 
138     # 將評論寫入文本文件
139     def save_to_file(self,list,filename):
140             with codecs.open(filename,'a',encoding='utf-8') as f:
141                 f.writelines(list)
142             print("寫入文件成功!")
143 
144     # 抓取歌曲評論
145     def save_all_comments_to_file(self,song_id,song_name):
146         start_time = time.time() # 開始時間
147         url = "http://music.163.com/weapi/v1/resource/comments/R_SO_4_%d/?csrf_token=" % song_id
148         if(os.path.exists(song_name)):
149             filename = u"%s/%s.txt" % (song_name,song_name)
150         else:
151             os.mkdir(song_name)
152             filename = u"%s/%s.txt" % (song_name,song_name)
153         all_comments_list = self.get_all_comments(url)
154         self.save_to_file(all_comments_list,filename)
155         end_time = time.time() #結束時間
156         print(u"抓取歌曲《%s》耗時%f秒." % (song_name,end_time - start_time))
NetCloud_comments_plot.py
  1 #!/usr/bin/env python
  2 # -*- coding: utf-8 -*-
  3 # @Time   : 2017/3/29 9:07
  4 # @Author : Lyrichu
  5 # @Email  : 919987476@qq.com
  6 # @File   : NetCloud_comments_plot.py
  7 '''
  8 @Description:
  9 對抓取來的網易雲評論數據進行簡單的可視化分析
 10 '''
 11 from NetCloud_spider3 import NetCloudCrawl
 12 import requests
 13 import matplotlib.dates as mdates
 14 from pylab import *
 15 mpl.rcParams['font.sans-serif'] = ['SimHei']  # 防止沒法顯示中文
 16 import matplotlib.pyplot as plt
 17 from datetime import datetime
 18 import re
 19 import time
 20 import pandas as pd
 21 import codecs
 22 import jieba
 23 from wordcloud import WordCloud
 24 from scipy.misc import imread
 25 from os import path
 26 import os
 27 
 28 
 29 class NetCloudProcessor(NetCloudCrawl):
 30     # 讀取評論文本數據,返回一個列表,列表的每一個元素爲一個字典,字典中包含用戶id,評論內容等
 31     def read_comments_file(self,filename):
 32         list_comments = [] # 評論數據列表
 33         with open(filename,'r') as f:
 34             comments_list = f.readlines() # 讀取文本,按行讀取,返回列表
 35             del comments_list[0] # 刪除首個元素
 36             comments_list = list(set(comments_list)) # 去除重複數據
 37             count_ = -1 # 記錄評論數
 38             for comment in comments_list:
 39                 comment = comment.replace("\n","") # 去除末尾的換行符
 40                 try:
 41                     if (re.search(re.compile(r'^\d+?'),comment)): # 若是以數字開頭
 42                         comment_split = comment.split(' ',5) # 以空格分割(默認)
 43                         comment_dict = {}
 44                         comment_dict['userID'] = comment_split[0] # 用戶ID
 45                         comment_dict['nickname'] = comment_split[1] # 用戶暱稱
 46                         comment_dict['avatarUrl'] = comment_split[2] # 用戶頭像地址
 47                         comment_dict['comment_time'] = int(comment_split[3])# 評論時間
 48                         comment_dict['likedCount'] = int(comment_split[4])# 點贊總數
 49                         comment_dict['comment_content'] = comment_split[5] # 評論內容
 50                         list_comments.append(comment_dict)
 51                         count_ += 1
 52                     else:
 53                         list_comments[count_]['comment_content'] += comment  # 將評論追加到上一個字典
 54                 except Exception,e:
 55                     print(e)
 56         list_comments.sort(key= lambda x:x['comment_time'])
 57         print(u"去除重複以後有%d條評論!" % (count_+1))
 58         return (count_+1,list_comments) # 返回評論總數以及處理完的評論內容
 59 
 60     # 將網易雲的時間戳轉換爲年-月-日的日期函數
 61     # 時間戳須要先除以1000才能獲得真實的時間戳
 62     # format 爲要轉換的日期格式
 63     def from_timestamp_to_date(self,time_stamp,format):
 64         time_stamp = time_stamp*0.001
 65         real_date = time.strftime(format,time.localtime(time_stamp))
 66         return real_date
 67     
 68     
 69     # 統計相關數據寫入文本文件
 70     def count_comments_info(self,comments_list,count_,song_name):
 71         x_date_Ym = [] # 評論數按年月進行統計
 72         x_date_Ymd = [] # 評論數按年月日進行統計
 73         x_likedCount = [] # 點贊總數分佈
 74         for i in range(count_):
 75             time_stamp = comments_list[i]['comment_time'] # 時間戳
 76             real_date_Ym = self.from_timestamp_to_date(time_stamp,'%Y-%m') # 按年月進行統計
 77             real_date_Ymd = self.from_timestamp_to_date(time_stamp,'%Y-%m-%d') # 按年月日統計
 78             likedCount = comments_list[i]['likedCount'] # 點贊總數
 79             x_date_Ym.append(real_date_Ym)
 80             x_date_Ymd.append(real_date_Ymd)
 81             x_likedCount.append(likedCount)
 82         x_date_Ym_no_repeat = []
 83         y_date_Ym_count = []
 84         x_date_Ymd_no_repeat = []
 85         y_date_Ymd_count = []
 86         x_likedCount_no_repeat = []
 87         y_likedCount_count = []
 88         # 年月
 89         for date_ in x_date_Ym:
 90             if date_ not in x_date_Ym_no_repeat:
 91                 x_date_Ym_no_repeat.append(date_)
 92                 y_date_Ym_count.append(x_date_Ym.count(date_))
 93         # 年月日
 94         for date_ in x_date_Ymd:
 95             if date_ not in x_date_Ymd_no_repeat:
 96                 x_date_Ymd_no_repeat.append(date_)
 97                 y_date_Ymd_count.append(x_date_Ymd.count(date_))
 98 
 99         for likedCount in x_likedCount:
100             if likedCount not in x_likedCount_no_repeat:
101                 x_likedCount_no_repeat.append(likedCount)
102                 y_likedCount_count.append(x_likedCount.count(likedCount))
103         # 將統計的數據存入txt文件
104         with open(u"%s/comments_num_by_Ym.txt" % song_name,"w") as f:
105             f.write("date_Ym comments_num\n")
106             for index,date_Ym in enumerate(x_date_Ym_no_repeat):
107                 f.write(x_date_Ym_no_repeat[index] + " " + str(y_date_Ym_count[index]) + "\n")
108             print(u"成功寫入comments_num_by_Ym.txt!")
109         with open(u"%s/comments_num_by_Ymd.txt" % song_name,"w") as f:
110             f.write("date_Ymd comments_num\n")
111             for index,date_Ymd in enumerate(x_date_Ymd_no_repeat):
112                 f.write(x_date_Ymd_no_repeat[index] + " " + str(y_date_Ymd_count[index]) + "\n")
113             print(u"成功寫入comments_num_by_Ymd.txt!")
114         with open(u"%s/likedCount.txt" % song_name,"w") as f:
115             f.write("likedCount count_num\n")
116             for index,likedCount in enumerate(x_likedCount_no_repeat):
117                 f.write(str(x_likedCount_no_repeat[index]) + " " + str(y_likedCount_count[index]) + "\n")
118             print(u"成功寫入likedCount.txt!")
119     # 獲得處理過的x_date 和 count 統計信息
120     def get_xdate_ycount(self,count_file_name,date_type,min_date_Ym,max_date_Ym,min_date_Ymd,max_date_Ymd):
121         with open(count_file_name,'r') as f:
122             list_count = f.readlines()
123             # comment_or_like = list_count[0].replace("\n","").split(" ")[1] # 判斷是評論數仍是點贊數
124             # song_name = count_file_name.split("/")[0] # 歌曲名字
125             del list_count[0]
126             x_date = []
127             y_count = []
128             for content in list_count:
129                 content.replace("\n","")
130                 res = content.split(' ')
131                 if(date_type == '%Y-%m-%d'):
132                     if(int("".join(res[0].split("-"))) >= int("".join(min_date_Ymd.split("-"))) and int("".join(res[0].split("-"))) <=  int("".join(max_date_Ymd.split("-")))):
133                         x_date.append(res[0])
134                         y_count.append(int(res[1]))
135                 else:
136                     if(int("".join(res[0].split("-"))) >= int("".join(min_date_Ym.split("-"))) and int("".join(res[0].split("-"))) <=  int("".join(max_date_Ym.split("-")))):
137                         x_date.append(res[0])
138                         y_count.append(int(res[1]))
139         return (x_date,y_count)
140 
141 
142     # 繪製圖形展現歌曲評論以及點贊分佈
143     # plot_type:爲 'plot' 繪製散點圖   爲 'bar' 繪製條形圖
144     # date_type 爲日期類型
145     # time_distance 爲時間間隔(必填)例如:5D 表示5天,1M 表示一個月
146     # min_liked_num 爲繪圖時的最小點贊數
147     # max_liked_num 爲繪圖時的最大點贊數
148     # min_date_Ym 爲最小日期(年-月形式)
149     # max_date_Ym 爲最大日期(年-月形式)
150     # min_date_Ymd 爲最小日期(年-月-日形式)
151     # max_date_Ymd 爲最大日期(年-月-日形式)
152     def plot_comments(self,song_name,settings):
153         comment_type = settings['comment_type']
154         date_type = settings['date_type']
155         plot_type = settings['plot_type']
156         bar_width = settings['bar_width']
157         rotation = settings['rotation']
158         time_distance = settings['time_distance']
159         min_date_Ymd = settings['min_date_Ymd']
160         max_date_Ymd = settings['max_date_Ymd']
161         min_date_Ym = settings['min_date_Ym']
162         max_date_Ym = settings['max_date_Ym']
163         if(comment_type):  # 評論
164             if(date_type == '%Y-%m-%d'):
165                 count_file_name = u"%s/comments_num_by_Ymd.txt" % song_name
166             else:
167                 count_file_name = u"%s/comments_num_by_Ym.txt" % song_name
168         else:
169             count_file_name = u"%s/likedCount.txt" % song_name
170         with open(count_file_name,'r') as f:
171             list_count = f.readlines()
172             del list_count[0]
173             if(comment_type): # 若是是評論
174                 x_date = []
175                 y_count = []
176                 for content in list_count:
177                     content.replace("\n","")
178                     res = content.split(' ')
179                     if(date_type == '%Y-%m-%d'):
180                         if(int("".join(res[0].split("-"))) >= int("".join(min_date_Ymd.split("-"))) and int("".join(res[0].split("-"))) <=  int("".join(max_date_Ymd.split("-")))):
181                              x_date.append(res[0])
182                              y_count.append(int(res[1]))
183                     else:
184                         if(int("".join(res[0].split("-"))) >= int("".join(min_date_Ym.split("-"))) and int("".join(res[0].split("-"))) <=  int("".join(max_date_Ym.split("-")))):
185                              x_date.append(res[0])
186                              y_count.append(int(res[1]))
187             else: # 若是是點贊
188                 # 分爲10-100,100-1000,1000-10000,10000以上這5個區間,因爲絕大多數歌曲評論點贊數都在10贊一下
189                 # 超過99%,因此10贊如下暫時忽略
190                 x_labels = [u'10-100',u'100-1000',u'1000-10000',u'10000以上']
191                 y_count = [0,0,0,0]
192                 for content in list_count:
193                     content.replace("\n","")
194                     res = content.split(' ')
195                     if(int(res[0]) <= 100 and int(res[0]) >= 10):
196                         y_count[0] += int(res[1])
197                     elif(int(res[0]) <= 1000):
198                         y_count[1] += int(res[1])
199                     elif(int(res[0]) <= 10000):
200                         y_count[2] += int(res[1])
201                     else:
202                         y_count[3] += int(res[1])
203         # 若是是評論
204         if(comment_type):
205             type_text = u"評論"
206             x = [datetime.strptime(d, date_type).date() for d in x_date]
207             # 配置橫座標爲日期類型
208             plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%s' % date_type))
209             if(date_type == '%Y-%m-%d'):
210                 plt.gca().xaxis.set_major_locator(mdates.DayLocator())
211             else:
212                 plt.gca().xaxis.set_major_locator(mdates.MonthLocator())
213             if(plot_type == 'plot'):
214                 plt.plot(x,y_count,color = settings['color'])
215             elif(plot_type == 'bar'):
216                 plt.bar(x,y_count,width=bar_width,color = settings['color'])
217             else:
218                 plt.scatter(x,y_count,color = settings['color'])
219             plt.gcf().autofmt_xdate(rotation=rotation)  # 自動旋轉日期標記
220             plt.title(u"網易雲音樂歌曲《" + song_name + u"" + type_text + u"數目分佈")
221             plt.xlabel(u"日期")
222             plt.ylabel(u"數目")
223             plt.xticks(pd.date_range(x[0],x[-1],freq="%s" % time_distance)) # 設置日期間隔
224             plt.show()
225         else:  # 若是是點贊
226             x = y_count
227             type_text = u"點贊"
228             pie_colors = settings['pie_colors']
229             auto_pct = settings['auto_pct'] # 百分比保留幾位小數
230             expl = settings['expl'] # 每塊距離圓心的距離
231             plt.pie(x,labels = x_labels,explode=expl,colors = pie_colors,autopct = auto_pct)
232             plt.title(u"網易雲音樂歌曲《" + song_name + u"" + type_text + u"數目分佈")
233             plt.legend(x_labels)
234             plt.show()
235         plt.close()
236 
237 
238     # 生成某個歌曲的統計信息文件
239     def generate_count_info_files(self,song_name):
240         filename = "%s/%s.txt" %(song_name,song_name)
241         count_,list_comments = self.read_comments_file(filename)
242         print(u"%s有%d條評論!" % (song_name,count_))
243         self.count_comments_info(list_comments,count_,song_name)
244 
245     # 一步完成數據抓取,生成統計信息文件的工做
246     def create_all_necessary_files(self,song_id,song_name):
247         start_time = time.time()
248         # 數據抓取並寫入文件
249         self.save_all_comments_to_file(song_id,song_name)
250         # 生成熱門評論文件
251         url = "http://music.163.com/weapi/v1/resource/comments/R_SO_4_%d/?csrf_token=" % song_id
252         hot_comments_list = self.get_hot_comments(url)
253         self.save_to_file(hot_comments_list,u"%s/hotcomments.txt" % song_name)
254         # 生成統計信息文件(3個)
255         self.generate_count_info_files(song_name)
256         # 生成全部評論者信息文件
257         self.save_commenters_info_to_file(song_name)
258         # 生成 評論詞雲(所有評論)
259         self.draw_wordcloud(song_name,singer_name=False)
260         end_time = time.time()
261         print(u"任務完成!程序耗時%f秒!" %(end_time - start_time))
262     # 獲得某首歌曲下全部評論者(須要去除重複)的主頁信息
263     def get_commenters_info(self,filename):
264         commenters_info_list = []  # 存放評論用戶信息
265         with codecs.open(filename,"r",encoding= 'utf-8') as f:
266             lists = f.readlines()
267             del lists[0]  # 刪除第一行
268             commenters_urls_list = [] # 評論者列表
269             for info in lists:
270                 if(re.match(r'^\d.*?',info)):
271                     commenters_urls_list.append(u"http://music.163.com/user/home?id=" + info.split(" ")[0]) # 評論者主頁地址
272             commenters_urls_list = list(set(commenters_urls_list)) # 去除重複的人
273             print("共有%d個不一樣評論者!" % len(commenters_urls_list))
274         for index,url in enumerate(commenters_urls_list):
275             try:
276                 info_dict = {}  # 評論用戶我的信息字典
277                 user_id_compile = re.compile(r'.*id=(\d+)')
278                 user_id = re.search(user_id_compile,url).group(1)
279                 html = requests.get(url,headers = self.headers).text
280                 event_count_compile = re.compile(r'<strong id="event_count">(\d+?)</strong>')
281                 event_count = re.search(event_count_compile,html).group(1) # 我的動態數目
282                 follow_count_compile = re.compile(r'<strong id="follow_count">(\d+?)</strong>')
283                 follow_count = re.search(follow_count_compile,html).group(1) # 關注人數
284                 fan_count_compile = re.compile(r'<strong id="fan_count">(\d+?)</strong>')
285                 fan_count = re.search(fan_count_compile,html).group(1)
286                 location_compile = re.compile(u'<span>所在地區:(.+?)</span>') # 注意須要使用unicode編碼,正則表達式才能匹配
287                 location_res = re.search(location_compile,html)
288                 if(location_res):
289                     location = location_res.group(1)
290                 else:
291                     location = u"未知地區"
292                 self_description_compile = re.compile(u'<div class="inf s-fc3 f-brk">我的介紹:(.*?)</div>')
293                 if(re.search(self_description_compile,html)):  # 若是能夠匹配到
294                     self_description = re.search(self_description_compile,html).group(1)
295                 else:
296                     self_description = u"未知我的介紹"
297                 age_compile = re.compile(r'<span.*?data-age="(\d+)">')
298                 if(re.search(age_compile,html)):
299                     age_time = re.search(age_compile,html).group(1) # 這個獲得的是出生日期距離unix時間戳起點的距離
300                     # 須要將其轉換爲年齡
301                     age = (2017-1970) - (int(age_time)/(1000*365*24*3600)) # 真實的年齡
302                 else:
303                     age = u"未知年齡"
304                 listening_songs_num_compile = re.compile(u'<h4>累積聽歌(\d+?)首</h4>')
305                 if(re.search(listening_songs_num_compile,html)):
306                     listening_songs_num = re.search(listening_songs_num_compile,html).group(1) # 聽歌總數
307                 else:
308                     listening_songs_num = u'未知聽歌總數'
309                 info_dict['user_id'] = user_id
310                 info_dict['event_count'] = event_count # 動態總數
311                 info_dict['follow_count'] = follow_count # 關注總數
312                 info_dict['fan_count'] = fan_count  # 粉絲總數
313                 info_dict['location'] = location  # 所在地區
314                 info_dict['self_description'] = self_description # 我的介紹
315                 info_dict['age'] = age # 年齡
316                 info_dict['listening_songs_num'] = listening_songs_num # 累計聽歌總數
317                 commenters_info_list.append(info_dict)
318                 print("成功添加%d個用戶信息!" % (index+1))
319             except Exception,e:
320                 print e
321         return commenters_info_list  # 返回評論者用戶信息列表
322 
323     # 保存評論者的信息
324     def save_commenters_info_to_file(self,song_or_singer_name):
325         if(os.path.exists(u"%s/%s.txt" % (song_or_singer_name,song_or_singer_name))):
326             filename = u"%s/%s.txt" % (song_or_singer_name,song_or_singer_name)
327         else:
328             filename = u"%s/hotcomments.txt" % song_or_singer_name
329         commenters_info_lists = self.get_commenters_info(filename) # 獲得用戶信息列表
330         with codecs.open(u"%s/commenters_info.txt" % song_or_singer_name,"w",encoding='utf-8') as f:
331             f.write(u"用戶ID 動態總數 關注總數 粉絲總數 所在地區 我的介紹 年齡 累計聽歌總數\n")
332             for info in commenters_info_lists:
333                 user_id = info['user_id'] # 用戶id
334                 event_count = info['event_count'] # 動態數目
335                 follow_count = info['follow_count'] # 關注的人數
336                 fan_count = info['fan_count'] # 粉絲數
337                 location = info['location'] # 所在地區
338                 self_description = info['self_description'] # 我的介紹
339                 age = unicode(info['age']) # 年齡
340                 listening_songs_num = info['listening_songs_num'] # 累計聽歌總數
341                 full_info = unicode(user_id) + u" " + event_count + u" " + follow_count + u" " + fan_count + u" " + location + u" " + self_description + u" " + age + u" " + listening_songs_num + u"\n"
342                 f.write(full_info)
343         print(u"成功寫入文件%s/commenters_info.txt" % song_or_singer_name)
344 
345     # 獲得某個歌手所有熱門歌曲id列表
346     def get_songs_ids(self,singer_url):
347         ids_list = []
348         html = requests.get(singer_url,headers = self.headers,proxies = self.proxies).text
349         re_pattern = re.compile(r'<a href="/song\?id=(\d+?)">.*?</a>')
350         ids = re.findall(re_pattern,html)
351         for id in ids:
352             ids_list.append(id)
353         return ids_list
354     # 獲得某個歌手全部歌曲的熱門評論
355     def get_singer_all_hot_comments(self,singer_name,singer_id):
356         singer_url = 'http://music.163.com/artist?id=%d' % singer_id
357         song_ids = self.get_songs_ids(singer_url) # 獲得歌手全部熱門歌曲id列表
358         for song_id in song_ids:
359             url = "http://music.163.com/weapi/v1/resource/comments/R_SO_4_%d/?csrf_token=" % int(song_id)
360             hot_comments_list = self.get_hot_comments(url)
361             if(os.path.exists(singer_name)):
362                 self.save_to_file(hot_comments_list,u"%s/hotcomments.txt" % singer_name)
363             else:
364                 os.mkdir(singer_name)
365                 self.save_to_file(hot_comments_list,u"%s/hotcomments.txt" % singer_name)
366         print(u"成功寫入%s的%d首歌曲!" %(singer_name,len(song_ids)))
367 
368     # 在一張圖中繪製多個歌曲的評論分佈
369     # song_names_list 爲多個歌曲名字的列表
370     # settings 爲含有字典元素的列表,每一個字典含有每一個子圖的配置項
371     def sub_plot_comments(self,song_names_list,settings,row,col):
372         n = len(song_names_list) # 歌曲總數
373         row = row
374         col = col
375         for i in range(n):
376             plt.subplot(row,col,i+1)
377             if(settings[i]['date_type'] == '%Y-%m-%d'):
378                 count_file_name = u"%s/comments_num_by_Ymd.txt" % song_names_list[i]
379             else:
380                 count_file_name = u"%s/comments_num_by_Ym.txt" % song_names_list[i]
381             date_type = settings[i]['date_type']
382             min_date_Ym = settings[i]['min_date_Ym']
383             max_date_Ym = settings[i]['max_date_Ym']
384             min_date_Ymd = settings[i]['min_date_Ymd']
385             max_date_Ymd = settings[i]['max_date_Ymd']
386             x_date,y_count = self.get_xdate_ycount(count_file_name,min_date_Ym = min_date_Ym,max_date_Ym = max_date_Ym,
387                                                                    min_date_Ymd = min_date_Ymd,max_date_Ymd = max_date_Ymd,date_type = date_type)
388 
389             x = [datetime.strptime(d, date_type).date() for d in x_date]
390             # 配置橫座標爲日期類型
391             plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%s' % date_type))
392             if(date_type == '%Y-%m-%d'):
393                 plt.gca().xaxis.set_major_locator(mdates.DayLocator())
394             else:
395                 plt.gca().xaxis.set_major_locator(mdates.MonthLocator())
396             plot_type = settings[i]['plot_type']
397             if(plot_type == 'plot'):
398                 plt.plot(x,y_count,color = settings[i]['color'])
399             elif(plot_type == 'bar'):
400                 plt.bar(x,y_count,width=settings[i]['bar_width'],color = settings[i]['color'])
401             else:
402                 plt.scatter(x,y_count,color = settings[i]['color'])
403             plt.gcf().autofmt_xdate(rotation=settings[i]['rotation'])  # 自動旋轉日期標記
404             plt.title(u"網易雲音樂歌曲《"  + song_names_list[i] + u"" + u"評論數目分佈(%s到%s)" %(x[0],x[-1]),fontsize = settings[i]['fontsize'])
405             plt.xlabel(u"日期")
406             plt.ylabel(u"數目")
407             plt.xticks(pd.date_range(x[0],x[-1],freq="%s" % settings[i]['time_distance'])) # 設置日期間隔
408         plt.subplots_adjust(left=0.2, bottom=0.2, right=0.8, top=0.8,hspace=1.2,wspace=0.3)
409         plt.show()
410     # 獲得評論列表
411     def get_comments_list(self,filename):
412         with codecs.open(filename,"r",encoding='utf-8') as f:
413             lists = f.readlines()
414             comments_list = []
415             for comment in lists:
416                 if(re.match(r"^\d.*",comment)):
417                     try:
418                         comments_list.append(comment.split(" ",5)[5].replace("\n",""))
419                     except Exception,e:
420                         print(e)
421                 else:
422                     comments_list.append(comment)
423         return comments_list
424 
425     # 繪製詞雲
426     # pic_path 爲詞雲背景圖片地址
427     # singer_name 爲 False 時,則讀取歌曲評論文件,不然讀取歌手熱評文件
428     # isFullComments = True 時,讀取所有評論,不然只讀取熱評
429     def draw_wordcloud(self,song_name,singer_name,pic_path = "JayChou.jpg",isFullComments = True):
430         if singer_name == False:
431             if isFullComments == True:
432                 filename = u"%s/%s.txt" % (song_name,song_name) # 所有評論
433             else:
434                 filename = u"%s/hotcomments.txt" % song_name # 一首歌的熱評
435         else:
436             filename = u"%s/hotcomments.txt" % singer_name
437         comments_list = self.get_comments_list(filename)
438         comments_text = "".join(comments_list)
439         cut_text = " ".join(jieba.cut(comments_text)) # 將jieba分詞獲得的關鍵詞用空格鏈接成爲字符串
440         d = path.dirname(__file__) # 當前文件文件夾所在目錄
441         color_mask = imread(pic_path) # 讀取背景圖片
442         cloud = WordCloud(font_path=path.join(d,'simsun.ttc'),background_color='white',mask=color_mask,max_words=2000,max_font_size=40)
443         word_cloud = cloud.generate(cut_text) # 產生詞雲
444         if singer_name == False:
445             name = song_name
446         else:
447             name = singer_name
448         word_cloud.to_file(u"%s/%s.jpg" % (name,name))
449         print(u"成功生成%s.jpg" % name)
450 
451     # 對一首歌曲繪製其某一年某幾個月的評論分佈
452     # date_lists 爲要繪製的月份
453     def sub_plot_months(self,song_name,DateLists,settings,row,col):
454         n = len(DateLists)
455         row = row #
456         col = col #
457         filename = u"%s/comments_num_by_Ymd.txt" % song_name
458         date_lists = []
459         y_count = []
460         with codecs.open(filename,"r",encoding = 'utf-8') as f:
461             lists = f.readlines()
462             del lists[0] # 刪除頭部信息
463             for content in lists:
464                 date_lists.append(content.split(" ")[0]) # 添加日期信息
465                 y_count.append(int(content.split(" ")[1])) # 添加數量信息
466         for i in range(n):
467             plt.subplot(row,col,i+1)
468             x_date = [date for date in date_lists if re.match(r"%s" % DateLists[i],date)]
469             y = [y_count[j] for j in range(len(y_count)) if re.match(r"%s" % DateLists[i],date_lists[j])]
470             x = [datetime.strptime(d, "%Y-%m-%d").date() for d in x_date]
471             plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))
472             plot_type = settings[i]['plot_type']
473             if(plot_type == 'plot'):
474                 plt.plot(x,y,color = settings[i]['color'])
475             elif(plot_type == 'bar'):
476                 plt.bar(x,y,width=settings[i]['bar_width'],color = settings[i]['color'])
477             else:
478                 plt.scatter(x,y,color = settings[i]['color'])
479             plt.gcf().autofmt_xdate(rotation=settings[i]['rotation'])  # 自動旋轉日期標記
480             plt.title(u"《%s》%s到%s" % (song_name,x[0],x[-1]),fontsize = settings[i]['fontsize'])
481             plt.xlabel(u"日期")
482             plt.ylabel(u"評論數目")
483             plt.xticks(pd.date_range(x[0],x[-1],freq="%s" % settings[i]['time_distance'])) # 設置日期間隔
484         plt.subplots_adjust(left=0.09, bottom=0.27, right=0.89, top=0.83,hspace=0.35,wspace=0.35)
485         plt.show()
486 
487 # 繪製一首歌曲評論者相關信息的分佈
488     def sub_plot_commenters_info(self,song_or_singer_name):
489         file_name = u"%s/commenters_info.txt" % song_or_singer_name
490         with codecs.open(file_name,'r',encoding='utf-8') as f:
491             info_lists = f.readlines()
492             del info_lists[0] # 刪除頭部信息
493             event_count_list = []  # 動態總數
494             follow_count_list = [] # 關注總數
495             fan_count_list = []  # 粉絲總數
496             area_list = [] # 所在地區
497             age_list = [] # 年齡
498             listen_songs_num_list = [] # 累計聽歌數目
499             for info in info_lists:
500                 info.replace("\n","")
501                 event_count_list.append(int(info.split(" ")[1]))
502                 follow_count_list.append(int(info.split(" ")[2]))
503                 fan_count_list.append(int(info.split(" ")[3]))
504                 area_res= re.search(re.compile(u'.*\d (.+?-.+?) .*?|.*(未知地區).*'),info)
505                 if(area_res):
506                     if(area_res.group(1)):
507                         area_list.append(area_res.group(1))
508                 age_list.append(info.split(" ")[-2])
509                 listen_songs_num_list.append(int(info.split(" ")[-1]))
510         event_count = [0,0,0,0]
511         follow_count = [0,0,0,0,0]
512         fan_count = [0,0,0,0,0]
513         listen_songs_num = [0,0,0,0]
514         area_count = [0,0,0,0,0,0]
515         age_count = [0,0,0,0,0]
516         for content in event_count_list:
517             if(content <= 10):
518                 event_count[0] += 1
519             elif(content <= 50):
520                 event_count[1] += 1
521             elif(content <= 100):
522                 event_count[2] += 1
523             else:
524                 event_count[3] += 1
525         for content in follow_count_list:
526             if(content < 10):
527                 follow_count[0] += 1
528             elif(content < 30):
529                 follow_count[1] += 1
530             elif(content < 50):
531                 follow_count[2] += 1
532             elif(content < 100):
533                 follow_count[3] += 1
534             else:
535                 follow_count[4] += 1
536         for content in fan_count_list:
537             if(content < 10):
538                 fan_count[0] += 1
539             elif(content < 100):
540                 fan_count[1] += 1
541             elif(content < 1000):
542                 fan_count[2] += 1
543             elif(content < 10000):
544                 fan_count[3] += 1
545             else:
546                 follow_count[4] += 1
547         area_no_repeat_list = list(set(area_list)) # 去除重複
548         area_tuple = [(area,area_list.count(area)) for area in area_no_repeat_list]
549         area_tuple.sort(key= lambda x:x[1],reverse=True) # 從高到低排列
550         for i in range(5):  # 取出排名前4的地區
551             area_count[i] = area_tuple[i][1]
552         area_count[5] = sum([x[1] for x in area_tuple[5:]]) # 前5名以外的所有地區數量
553         area_labels = [x[0] for x in area_tuple[0:5]] # 前5個地區的名字
554         area_labels.append(u"其餘地區")
555         age_no_repeat_list = list(set(age_list)) # 去除重複
556         age_info = [age_list.count(age) for age in age_no_repeat_list]
557         for index,age_ in enumerate(age_no_repeat_list):
558             if(age_ != u"未知年齡"): # 排除未知年齡
559                 if(int(age_) <= 17):
560                     age_count[0] += age_info[index] # 00後
561                 elif(int(age_)<=22):  # 95後
562                     age_count[1] += age_info[index]
563                 elif(int(age_)<=27): # 90後
564                     age_count[2] += age_info[index]
565                 elif(int(age_)<=37): # 80後
566                     age_count[3] += age_info[index]
567                 else:
568                     age_count[4] += age_info[index]  # 80前
569         age_labels = [u"00後",u"95後",u"90後",u"80後",u"80前"]
570 
571         for content in listen_songs_num_list:
572             if(content < 100):
573                 listen_songs_num[0] += 1
574             elif(content < 1000):
575                 listen_songs_num[1] += 1
576             elif(content < 10000):
577                 listen_songs_num[2] += 1
578             else:
579                 listen_songs_num[3] += 1
580         for i in range(6):
581             if(i == 0):
582                 title = u"%s:評論者<動態數目>分佈" % song_or_singer_name
583                 labels = [u"0-10",u"10-50",u"50-100",u"100以上"]
584                 colors = ["red","blue","yellow","green"]
585                 x = event_count
586                 plt.subplot(2,3,i+1)
587                 plt.pie(x,colors=colors,labels=labels,autopct="%1.1f%%")
588                 plt.title(title)
589                 # plt.legend(labels)
590             elif(i == 1):
591                 title = u"%s:評論者<關注人數>分佈" % song_or_singer_name
592                 labels = [u"0-10",u"10-30",u"30-50",u"50-100",u"100以上"]
593                 colors = ["red","blue","yellow","green","white"]
594                 x = follow_count
595                 plt.subplot(2,3,i+1)
596                 plt.pie(x,colors=colors,labels=labels,autopct="%1.1f%%")
597                 plt.title(title)
598                 # plt.legend(labels)
599             elif(i == 2):
600                 title = u"%s:評論者<粉絲人數>分佈" % song_or_singer_name
601                 labels = [u"0-10",u"10-100",u"100-1000",u"1000-10000",u"10000以上"]
602                 colors = ["red","blue","yellow","green","white"]
603                 x = fan_count
604                 plt.subplot(2,3,i+1)
605                 plt.pie(x,colors=colors,labels=labels,autopct="%1.1f%%")
606                 plt.title(title)
607                 # plt.legend(labels)
608             elif(i == 3):
609                 title = u"%s:評論者<地區>分佈" % song_or_singer_name
610                 colors = ["red","blue","yellow","green","white","purple"]
611                 x = area_count
612                 plt.subplot(2,3,i+1)
613                 plt.pie(x,colors=colors,labels=area_labels,autopct="%1.1f%%")
614                 plt.title(title)
615                 # plt.legend(area_labels,loc='upper center', bbox_to_anchor=(0.1,0.9),ncol=1,fancybox=True,shadow=True)
616             elif(i == 4):
617                 title = u"%s:評論者<年齡>分佈" % song_or_singer_name
618                 colors = ["red","blue","yellow","green","white"]
619                 x = age_count
620                 plt.subplot(2,3,i+1)
621                 plt.pie(x,colors=colors,labels=age_labels,autopct="%1.1f%%")
622                 plt.title(title)
623                 # plt.legend(age_labels)
624             else:
625                 title = u"%s:評論者<累計聽歌>分佈" % song_or_singer_name
626                 labels = [u"0-100",u"100-1000",u"1000-10000",u"10000以上"]
627                 colors = ["red","blue","yellow","green"]
628                 x = listen_songs_num
629                 plt.subplot(2,3,i+1)
630                 plt.pie(x,colors=colors,labels=labels,autopct="%1.1f%%")
631                 plt.title(title)
632                 # plt.legend(labels)
633         plt.tight_layout()
634         plt.show()
635 
636     # sub_plot_months 測試
637     def sub_plot_months_test(self):
638         song_name = u"越長大越孤單"
639         row = 3
640         col = 4
641         settings_dict = {
642             "plot_type":"plot",
643             "color":"g",
644             "bar_width":0.8,
645             "fontsize":10,
646             "rotation":50,
647             "time_distance":"5D"
648         }
649         settings = []
650         DateLists = ['2016-04','2016-05','2016-06','2016-07','2016-08','2016-09','2016-10','2016-11','2016-12','2017-01','2017-02','2017-03']
651         for i in range(len(DateLists)):
652             settings.append(settings_dict)
653         self.sub_plot_months(song_name,DateLists,settings,row=row,col=col)
654 
655     # 繪製subplot 測試
656     def subplot_test(self):
657         song_names_list = [u"七里香",u"不要再孤單",u"All Too Well",u"恰好碰見你"]
658         settings_dict = {"date_type":"%Y-%m-%d",
659                          "plot_type":"bar",
660                          "fontsize":12,
661                          "color":"r",
662                          "bar_width":0.4,
663                          "rotation":50,
664                          "time_distance":"3D",
665                          "min_date_Ymd":"2017-03-01",
666                          "max_date_Ymd":"2017-12-31",
667                          "min_date_Ym":"2013-01",
668                          "max_date_Ym":"2017-12"
669                          }
670         settings = []
671         for i in range(len(song_names_list)):
672             settings.append(settings_dict)
673         row = 2
674         col = 2
675         self.sub_plot_comments(song_names_list,settings,row=row,col = col)
676 
677     # plot_comments 函數測試
678     def plot_comments_test(self):
679         song_name = u"我從崖邊跌落"
680         settings = {
681             "comment_type":False,
682             "date_type":"%Y-%m-%d",
683             "plot_type":"plot",
684             "bar_width":0.8,
685             "rotation":20,
686             "color":"purple",
687             "pie_colors":["blue","red","coral","green","yellow"],
688             "auto_pct":'%1.1f%%',
689             "expl" :[0,0,0.1,0.3],   # 離開圓心的距離
690             "time_distance":"3D",
691             "min_date_Ymd":"2013-12-01",
692             "max_date_Ymd":"2017-12-31",
693             "min_date_Ym":"2013-01",
694             "max_date_Ym":"2017-12"
695         }
696         self.plot_comments(song_name,settings)
697 
698 
699 if __name__ == '__main__':
700     Processor = NetCloudProcessor()
701     Processor.plot_comments_test()
 

注:上面的代碼沒法直接運行,由於繪圖時缺乏必要的文件(即函數create_all_necessary_files產生的與歌曲名字或者歌手同名的文件夾),你們能夠去百度雲下載我已經抓取過的數據(地址:http://pan.baidu.com/s/1slS55gx)或者自行抓取。有任何問題,歡迎你們的指教。

相關文章
相關標籤/搜索