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


       之前已經用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_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)或者自行抓取。有任何問題,歡迎大家的指教。

 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM