之前已經用python獲取了網易雲音樂的評論數據,下一步的工作就是數據分析了。一般數據分析無非是采用(統計)數字、圖或者表的形式來展現數據之中隱含的信息。其中圖和表顯然是最直觀的了。所以這里我使用可視化的方法即用圖形來展示從評論中挖掘到的各種信息。
可視化的工具有很多,比如常見的有excel還有一些專門的繪圖軟件,各個編程語言當然也有很多可視化的包或者庫,比如統計上使用很多的R語言就有很多可視化的庫,我最喜歡的就是ggplot2了,我使用R語言主要用於數據的清洗以及可視化,其豐富的包(package)大大簡化了數據分析的工作量,而且可以繪制非常復雜、精美的圖表,以后有機會可以給大家專門介紹一下。python中可視化的庫也很多,最著名的的莫過於matplotlib了,這是一個面向對象的繪圖庫,很多方面的用法和matlab類似(從matlab的繪圖風格借鑒而來),由於我以前使用過一段時間的matlab,所以上手還是比較快,其他的還有seaborn(據說是對matplotlib的改進和封裝,使用起來更加方便,沒用過,有時間再研究下)、pygraph等。但是使用最廣泛的還是matplotlib。javascript有Echarts(百度的)等,這個我還沒接觸過,是一個可以網頁上進行可視化的函數庫,據說很棒。其他的當然還有特別多,這里我就不一一列舉了。有興趣的小伙伴可以自行去查閱資料。這里我決定使用matplotlib,主要是因為最近主要接觸的就是python,但是數據可視化方面的庫用的不多,剛好可以拿這次的數據來練練手,其次我接觸過matlab,相信對matplotlib入手會更快一點。
這次主要分析的有以下幾個方面:1、一首歌曲評論數目隨時間變化的趨勢,比如每天的評論數變化,每月的評論數變化等等。2、一首歌曲點贊數目分布的情況,比如0-10贊有多少個,占多少比例,1000贊以上占多大比例等等。3、熱門評論詞雲的制作,主要想通過詞雲,將文本挖掘的結果可視化,可以看出哪些是高頻詞匯等。4、一首歌曲評論者的基本信息的情況展示,比如評論者的地區分布,年齡分布、累計聽歌數目分布、動態分布、粉絲數分布等等。通過這些信息,可以直觀看出一首歌曲被哪些地區、哪些年齡段的人所喜愛,以及聽歌的人具有什么的特點等等。
好了,廢話不多說了,直接上圖吧。
首先來看一些歌曲評論數隨時間的變化。
圖 1
圖 2
圖 3
圖 4
圖 5
上面的5張圖我分別選取了5首不同的歌曲,有華語歌曲也有英文歌曲,有的起止時間很長(從13年就開始),也有的起止時間很短(從最近幾個月才開始)。總的來說可以分為兩種模式,一種是開始一段時間評論數很少,后來逐漸呈現爆發式地增長,前面三首歌《同桌的你》、《七里香》、《All Too Well》都是這種模式,而后面兩首歌《不要再孤單》、《stay》則是恰好相反,歌曲剛剛出來的那幾天評論數猛增,后面評論數逐漸下降,之后趨於平穩。通過分析,其實也很好理解,第一種模式的歌曲,往往都是早期曲庫中就存在的歌曲(也可以稱之為“老歌”),那個時候網易雲音樂才剛剛出來,用戶數目還很少,所以這些歌曲每天的評論數很少(沒記錯的話網易雲應該是12、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_test、subplot_test、plot_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)或者自行抓取。有任何問題,歡迎大家的指教。