這個作業的要求來自於:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/3075 。
B站有很多號稱“鎮站之寶”的視頻,擁有着數量極其恐怖的評論和彈幕。這次我的目的就是爬取B站視頻的評論數據,分析某番劇為何會深受大家喜愛。
首先我通過B站大神的數據統計了解到,B站評論數量最多的番劇是《全職高手》。如下圖所示:
通過數據可知《全職高手》這部番的第一集和最后一集分別占據了評論數量排行榜的第二名和第一名,遠超越其他很多很火的番。於是我先去B站https://www.bilibili.com/bangumi/play/ep107656 ,發現這部番需要開通大會員才能觀看。如下圖所示:
因為我不是B站大會員,看不了視頻,我便百度百科了《全職高手》https://baike.so.com/doc/289439-27501474.html 。《全職高手》是一部游戲競技類的番,故事講述網游榮耀中被譽為教科書級別的頂尖高手葉修,因為種種原因遭到俱樂部的驅逐,離開職業圈的他寄身於一家網吧成了一個小小的網管。但是擁有十年游戲經驗的他,在榮耀新開的第十區重新投入了游戲,帶着對往昔的回憶和一把未完成的自制武器,開始了重返巔峰之路。下圖是這部番劇中的人物關系圖:
雖然視頻看不了,評論和相關回復卻是可以看的。如下圖所示:
63w6條!9k多頁的評論!由於數據量實在龐大,每條數據一一查看顯然不現實,我就開始編寫爬蟲爬取所需數據。
使用Python爬取網頁一般分為五個階段,接下來我將按步驟分析並爬取評論數據,分析番劇《全職高手》大火的原因。
1. 分析目標網頁
首先我觀察評論區結構,發現評論區為鼠標點擊翻頁形式,共9399頁數據,每一頁有20條評論,每條評論包含用戶名、評論內容、評論樓層、時間日期、點贊數等用戶個人信息。如下圖所示:
接着我打開開發者工具,用鼠標點擊評論翻頁,觀察這個過程變化。
我發現整個過程中URL不變,說明評論區翻頁不是通過URL控制,而在每翻一頁時,網頁會向服務器發出Request URL的請求。如下圖所示:
點擊Preview欄,切換到預覽頁面,可以看到這個請求返回的最終結果。下面是該請求返回的json文件,包含了本頁在replies里的評論數據。這個json文件里包含了很多信息,除了網頁上展示的信息,還有很多未展示的信息。如下圖所示:
2.獲取網頁內容
現在正式寫代碼來爬取數據------訪問目標url的網頁,獲取目標網頁的html內容並返回。代碼如下:
# -*- coding: utf-8 -*-
"""
Spyder Editor
This is a temporary script file.
"""
import requests
def fetchURL(url):
'''
功能:訪問 url 的網頁,獲取網頁內容並返回
參數:
url :目標網頁的 url
返回:目標網頁的 html 內容
'''
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
}
try:
r = requests.get(url,headers=headers)
r.raise_for_status()
print(r.url)
return r.text
except requests.HTTPError as e:
print(e)
print("HTTPError")
except requests.RequestException as e:
print(e)
except:
print("Unknown Error !")
if __name__ == '__main__':
url = 'https://api.bilibili.com/x/v2/reply?callback=jQuery172020326544171595695_1541502273311&jsonp=jsonp&pn=2&type=1&oid=11357166&sort=0&_=1541502312050'
html = fetchURL(url)
print(html)
運行結果如下圖所示:
運行后發現網頁出錯,服務器拒絕了我們的訪問。
同理,這個請求放瀏覽器地址欄里面直接打開,網頁出錯,服務器拒絕訪問,什么內容都看不到。如下圖所示:
查閱許多相關資料https://baike.so.com/doc/5438832-5677151.html之后,我找到了解決方法。原請求的URL有callback 、jsonp、pn、type、oid和
_ = 共六個參數。其中,真正有用的參數只有三個:pn(頁數)、type(=1)和oid(視頻id)。刪除其余不必要的參數后,用新整理出的URL訪問服務器,成功獲取到評論數據。如下圖所示:
在主函數中,通過寫一個for循環來改變pn(頁數)的值,獲取每一頁的評論數據。
if __name__ == '__main__': for page in range(0,9400): url = 'https://api.bilibili.com/x/v2/reply?type=1&oid=11357166&pn=' + str(page) html = fetchURL(url)
3.提取關鍵信息
通過json庫對獲取的響應內容進行解析,接着提取所需的信息,即樓層、用戶名、性別、時間、評價、點贊數和回復數。代碼如下:
# -*- coding: utf-8 -*- """ Spyder Editor This is a temporary script file. """ import requests import json import time def fetchURL(url): ''' 功能:訪問 url 的網頁,獲取網頁內容並返回 參數: url :目標網頁的 url 返回:目標網頁的 html 內容 ''' headers = { 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36', } try: r = requests.get(url,headers=headers) r.raise_for_status() print(r.url) return r.text except requests.HTTPError as e: print(e) print("HTTPError") except requests.RequestException as e: print(e) except: print("Unknown Error !") def parserHtml(html): ''' 功能:根據參數 html 給定的內存型 HTML 文件,嘗試解析其結構,獲取所需內容 參數: html:類似文件的內存 HTML 文本對象 ''' s = json.loads(html) for i in range(20): comment = s['data']['replies'][i] # 樓層,用戶名,性別,時間,評價,點贊數,回復數 floor = comment['floor'] username = comment['member']['uname'] sex = comment['member']['sex'] ctime = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(comment['ctime'])) content = comment['content']['message'] likes = comment['like'] rcounts = comment['rcount'] print('--'+str(floor) + ':' + username + '('+sex+')' + ':'+ctime) print(content) print('like : '+ str(likes) + ' ' + 'replies : ' + str(rcounts)) print(' ') url = 'https://api.bilibili.com/x/v2/reply?type=1&oid=11357166&pn=3' html = fetchURL(url) parserHtml(html)
運行結果如下圖所示:
4.輸出保存
爬取到想要的評論數據后,除了將其顯示輸出,還要將數據以csv格式保存於本地並存儲到數據庫中。項目部分代碼如下:
# -*- coding: utf-8 -*-
"""
Spyder Editor
This is a temporary script file.
"""
import requests
import json
import time
import random
import pymysql
import pandas as pd
from sqlalchemy import create_engine
def fetchURL(url):
'''
功能:訪問 url 的網頁,獲取網頁內容並返回
參數:
url :目標網頁的 url
返回:目標網頁的 html 內容
'''
# headers是為了把爬蟲程序偽裝成瀏覽器
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
}
# 請求標頭中的所有信息都是headers內容,添加到requests請求中
try:
r = requests.get(url,headers=headers) # 發送請求,獲取某個網頁
r.raise_for_status() # 如果發送了一個錯誤請求(客戶端錯誤或者服務器錯誤響應),可以通過r.raise_for_status()來拋出異常
print(r.url)
return r.text
except requests.HTTPError as e: # 如果HTTP請求返回了不成功的狀態碼,r.raise_for_status()會拋出一個 HTTPError異常
print(e)
print("HTTPError")
except requests.RequestException as e:
print(e)
except:
print("Unknown Error !")
# 拋異常處理
def parserHtml(html):
'''
功能:根據參數 html 給定的內存型 HTML 文件,嘗試解析其結構,獲取所需內容
參數:
html:類似文件的內存 HTML 文本對象
'''
try:
s = json.loads(html) # json.load()主要用來讀寫json文件函數:json.loads函數的使用,將字符串轉化為字典
except:
print('error')
commentlist = []
hlist = []
# 列表append()方法用於將傳入的對象附加(添加)到現有列表中
hlist.append("序號")
hlist.append("名字")
hlist.append("性別")
hlist.append("時間")
hlist.append("評論")
hlist.append("點贊數")
hlist.append("回復數")
#commentlist.append(hlist)
# 樓層,用戶名,性別,時間,評價,點贊數,回復數
for i in range(20): # 每頁只有20條評論,循環20次
comment = s['data']['replies'][i]
blist = []
floor = comment['floor']
username = comment['member']['uname']
sex = comment['member']['sex']
ctime = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(comment['ctime']))
content = comment['content']['message']
likes = comment['like']
rcounts = comment['rcount']
blist.append(floor)
blist.append(username)
blist.append(sex)
blist.append(ctime)
blist.append(content)
blist.append(likes)
blist.append(rcounts)
commentlist.append(blist)
writePage(commentlist)
print('---'*20)
def writePage(urating):
'''
Function : To write the content of html into a local file
html : The response content
filename : the local filename to be used stored the response
'''
# Pandas讀取本地CSV文件並設置Dataframe(數據格式),將爬取數據保存到本地CSV文件。加上mode='a' 追加寫入數據;sep=','表示數據間使用逗號作為分隔符;不保存列名和行索引
dataframe = pd.DataFrame(urating)
dataframe.to_csv('B_comments.csv', mode='w', index=False, sep=',', header=False)
# 將爬取數據保存到數據庫中
conInfo = "mysql+pymysql://root:@localhost:3306/comments?charset=utf8"
engine = create_engine(conInfo,encoding='utf-8')
dataframe.to_sql(name='comment',con=engine,if_exists='append',index=False,index_label='id')
pymysql.connect(host='localhost',port=3306,user='root',passwd='',db='comments',charset='utf8')
# 循環輸出9399頁,共188454條評論數據內容
if __name__ == '__main__':
for page in range(0,9400):
url = 'https://api.bilibili.com/x/v2/reply?type=1&oid=11357166&pn=' + str(page)
html = fetchURL(url)
parserHtml(html)
# 為了降低被封ip的風險,設置合理的爬取間隔,每爬20頁便隨機歇1~5秒。
if page%20 == 0:
time.sleep(random.random()*5)
運行結果如下圖所示:
數據保存如下圖所示(相關文件保存在文件夾):
5.數據分析
數據的可視化分析:(Pyecharts)
注意:得到數據之后要先對數據進行清洗和預處理再進行數據的統計分析。代碼如下:
from pyecharts import Pie attr = ['男','女','保密'] value= [31129,63123,93748] pie = Pie('《全職高手》評論性別比例', title_pos='center', width=900) pie.add("7-17", attr, value, center=[75, 50], is_random=True, radius=[30, 75], rosetype='area', is_legend_show=False, is_label_show=True) pie.render('./網友評論性別.html')
運行結果如下圖所示:
可以看出,這類講網游的動漫,不僅深受大眾喜愛,而且女生尤其喜歡,這部番劇觀看人數女生還遠超男生呢。
詞雲分析:(jieba)
代碼如下:
import jieba # 導入matplotlib,用於生成2D圖形 import matplotlib.pyplot as plt # 導入wordcount,用於制作詞雲圖 from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator # 獲取所有評論 comments = [] with open('B_comments.csv', mode='r', encoding='utf-8') as f: rows = f.readlines() for row in rows: comment = row.split(',')[3] if comment != '': comments.append(comment) # 設置分詞 comment_after_split = jieba.cut(str(comments), cut_all=False) # 非全模式分詞,cut_all=false words = " ".join(comment_after_split) # 以空格進行拼接 # print(words) # 設置屏蔽詞 stopwords = STOPWORDS.copy() stopwords.add("電影") stopwords.add("一部") stopwords.add("一個") stopwords.add("沒有") stopwords.add("什么") stopwords.add("有點") stopwords.add("這部") stopwords.add("這個") stopwords.add("不是") stopwords.add("真的") stopwords.add("感覺") stopwords.add("覺得") stopwords.add("還是") stopwords.add("但是") stopwords.add("就是") # 導入背景圖 bg_image = plt.imread('bg.jpg') # 設置詞雲參數,參數分別表示:畫布寬高、背景顏色、背景圖形狀、字體、屏蔽詞、最大詞的字體大小 wc = WordCloud(width=1024, height=768, background_color='white', mask=bg_image, font_path='STKAITI.TTF', stopwords=stopwords, max_font_size=400, random_state=50) # 將分詞后數據傳入雲圖 wc.generate_from_text(words) plt.imshow(wc) plt.axis('off') # 不顯示坐標軸 plt.show() # 保存結果到本地 wc.to_file('詞雲圖.jpg')
運行結果如下圖所示:
可以看出,全職、榮耀、君莫笑、葉修、葉神等劇情里面的重要事件和人物都是評論里常提及的詞語,“啊啊啊”也出現非常多,是因為大部分人在上線觀劇后都會發出巨多的感嘆詞表示激動之情。
除此之外,分析用戶個人昵稱,還會發現這里面有許多死忠粉,在昵稱里面會采用和《全職高手》相關信息。比如,“葉修、陳果、唐柔、蘇沐橙......”是劇中人物名字,“君莫笑、毀人不倦、寒煙柔......”是人物游戲角色昵稱。而其余角色如黃少天、藍河、魏琛等都是主角葉修的好友,也是網絡上主角的同性cp對象。此外,像“BOSS、團隊、治療、戰術、節奏、屬性、武器、神之領域......”則是游戲本身或游戲過程中的一些重要名詞。
總結:
爬取用戶評論數據后,分析可知《全職高手》這部番劇如此受青睞的原因有:首先,它是根據蝴蝶藍編寫的同名小說《全職高手》改編,吸引一大批書迷去觀看。其次,故事所揭露的正是當今社會的一些現象和人文理念,引發觀眾共鳴。再加上制作精良且有國內老牌動畫制作公司視美精典保駕護航,許多觀眾都給予很高的評價,堪稱“國漫神作”。
在本次作業爬取B站最受喜愛番劇20w評論數據過程中,我遇到了不少困難。譬如:
(1)請求的網頁url不能直接用,需要對參數進行篩選整理后才能訪問網頁。
(2)爬取過程並不順利,因為如果爬取期間有用戶發表評論,則請求返回的響應會為空,導致程序出錯。所以在實際爬取過程中,需記錄爬取的位置,以便出錯之后從該位置繼續爬取。此外,挑選發帖人數少的時間段爬取數據,可以極大程度降低程序出錯的機率。
(3)爬取到的數據有多處不一致,主要原因有------評論區樓層只到了20w,但是評論數量卻有63w6條,數目不一致是由於B站的評論是可以回復的,回復的評論也會計算到總評論數里。我只爬取了樓層的評論,而相應的回復內容則忽略,只需統計回復數量。另外,評論區樓層有20w條,但是最后爬取的數據只有18w條,這是因為有刪評論的情況。評論刪除后,后面的樓層並不會重新排序,而是把刪掉的那層空下了,導致樓層數和評論數不一致。