B站觀衆姥爺的評論及回覆爬取及數據可視化
1. 前言
在昨天RNG和EDG的比賽中,RNG以2比1戰勝了EDG,這是兩隊在今年第一次相遇,小狗和廠長都沒在,所以被大家稱爲:新豬狗大戰,所以吸引了許多粉絲的關注。
作爲一個也有過LOL青春的老年人來說,只能說那種感覺又回來了。要說三場比賽中,最精彩的還是第三場,EDG領先1W經濟,最後由於偷家和後期決策問題,導致被RNG翻盤,由於這局比賽打的過於激烈,在網上引起了劇烈的討論。
這裏就選取B站中官方上傳的比賽視頻RNG vs ENG下面的評論和回覆,看一下觀衆姥爺們都在說什麼,有什麼經典的騷段子或者吐槽的問題
2. 評論和回覆的數據爬取
2.1 視頻基本情況查看
這裏直接調用api接口進行查看,只需要輸入視頻的標識號即可,封裝函數如下
import json,requests,time
import pandas as pd
def get_base_info(oid):
base_info_url = f'https://api.bilibili.com/x/web-interface/archive/stat?aid={oid}'
base_info = requests.get(base_info_url,headers = dic_header).json()['data']
#print(base_info) #可以輸出轉化爲json形式的數據
print('EDG vs RNG大戰視頻基本信息:\n')
print('播放數量:{}\n彈幕數量:{}\n收藏數量:{}\n硬幣數量:{}\n分享數量:{}\n點贊數量:{}\n------\n評論數量:{}'.format(
base_info['view'],base_info['danmaku'],base_info['favorite'],
base_info['coin'],base_info['share'],base_info['like'],base_info['reply']
))
if __name__ == '__main__':
dic_header = {'User-Agent': 'Mozilla/5.0'}
oid = 370124445
get_base_info(oid)
→ 輸出的結果爲:(這裏把評論信息突出,可以看出截止到目前已經差不多100w播放,超過1w條評論了,足見各位觀衆姥爺們的熱情)
2.2 數據資源地址解析
2.2.1 評論數據資源url解析
打開官網視頻後,首先是對網頁進行解析,找到存放評論數據資源的接口(url
)。操作步驟如下:【右鍵檢查】 → 【Network
】 → 【刷新】 → 【點擊任意文件】 → 【Preview
】 → 【找到數據所在的文件】 → 【Headers
】 → 【資源url
】
圖示如下:(這裏解析發現返回的數據都在reply?..等文件裏面,所以直接就篩選這類的文件即可)
在上面的界面點擊Preview
旁邊的Headers
,就可以找到資源對應的url
了,但是這個url
並不是直接就可以用的(可以嘗試一下直接複製這個url使用瀏覽器打開),需要進行簡化一下,方便我們找到其中的規律,順利進行數據的爬取,如下
選取如上圖的url,進行url‘瘦身’(有效成分進行提取),再經過幾次測試之後,發現Query String Parameters
中的參數只有部分是有效的,也就是pn
,type
,和oid
有效。那麼直接查看一下前五頁的評論的有效url
,如下,因此可以發現評論的數據資源url
是有規律的。
conment_1 = https://api.bilibili.com/x/v2/reply?&pn=1&type=1&oid=370124445
conment_2 = https://api.bilibili.com/x/v2/reply?&pn=2&type=1&oid=370124445
conment_3 = https://api.bilibili.com/x/v2/reply?&pn=3&type=1&oid=370124445
conment_4 = https://api.bilibili.com/x/v2/reply?&pn=4&type=1&oid=370124445
conment_5 = https://api.bilibili.com/x/v2/reply?&pn=5&type=1&oid=370124445
2.2.2 回覆數據資源url解析
這裏以第一頁的界面進行演示,點開下面的第一條評論,然後點擊‘點擊查看’,就會發現右側多出來一個文件,點開後如下所示,第一頁的回覆數據(每頁是10條)就出現在右側了
和上面的操作一樣,找到這個數據資源的url
進行‘瘦身’,經過測試發現其中的有效成分是:pn
,type
,oid
和root
,對比上面評論數據資源的url
,發現這裏多了一個root
的參數,可以初步猜測應該是屬於每個評論的識別的標識,那麼嘗試再評論數據上面找一下這個參數,可以發現上圖中,評論數據就是在root
這個參數下,具體的數值對應的就是這個評論的rpid
,如下:
首先確定單條回覆數據資源url
的規律,那麼後面的多條評論下回複數據的url規律就只需要把後面root
對應的數值修改即可,這裏還是以第一條評論下的多頁回覆數據的url
爲例,如下
reply_1 = https://api.bilibili.com/x/v2/reply/reply?&pn=1&type=1&oid=370124445&ps=10&root=2616308350
reply_2 = https://api.bilibili.com/x/v2/reply/reply?&pn=2&type=1&oid=370124445&ps=10&root=2616308350
reply_3 = https://api.bilibili.com/x/v2/reply/reply?&pn=3&type=1&oid=370124445&ps=10&root=2616308350
reply_4 = https://api.bilibili.com/x/v2/reply/reply?&pn=4&type=1&oid=370124445&ps=10&root=2616308350
reply_5 = https://api.bilibili.com/x/v2/reply/reply?&pn=5&type=1&oid=370124445&ps=10&root=2616308350
2.3 構造函數返回要爬取數據的url列表
2.3.1 生成評論數據資源url列表和對應的rpid
根據上面的分析可知,每頁評論數據的url是有規律的,爲了下一步獲取評論對應的回覆數據,因此除了返回評論數據的url列表,還要返回其對應的rpid數據(這裏處理的方式是在函數內部直接調用爬取回複數據的函數,而沒有麻煩的再返回rpid數據進行遍歷循環輸出),第一個函數封裝如下,
def get_comment_datas(oid):
comment_url = 'https://api.bilibili.com/x/v2/reply'
comment_page = 1
comment_data_lst = []
while True:
try:
param = { 'callback': 'jQuery1720028589320105517402_' + str(now_time),
'jsonp': 'jsonp',
'pn': comment_page,
'type': '1',
'oid': oid,
'sort': '2',
'_': now_time }
html = requests.get(url=comment_url, headers=dic_header, params=param)
start = html.text.index('{')
end = html.text.index('})')+1
comment_data = json.loads(html.text[start:end])['data']['replies']
#print(comment_data) #成功的轉換爲json數據
print(f'當前正在爬取第{comment_page}頁評論數據...')
for data in comment_data:
dic_coment = {}
dic_coment['member'] = data['member']['uname']
dic_coment['like'] = data['like']
dic_coment['comment'] = data['content']['message']
dic_coment['time'] = datetime.fromtimestamp(data['ctime'])
dic_coment['rpid'] = data['rpid_str']
comment_data_lst.append(dic_coment)
print('暱稱: {}\n點贊數:{}\n'.format(dic_coment['member'],
dic_coment['like'] ))
#comment_data_lst.extend(get_reply_data(comment_page,dic_coment['rpid']))
#這個是下一步封裝完爬取回複數據的函數後才添加的
time.sleep(1)
# if comment_page > 1:
# break
comment_page += 1
except Exception as Comment_Page_Error:
break
return comment_data_lst
提醒:
1)這裏第一個函數獲取評論數據資源的url使用了全部的參數(param
),也可以使用簡化後的url,下面爬取回複數據的爬取使用的就是簡化的,這裏提出兩種方式進行數據的爬取
2)在進行代碼完整性,測試能否正常輸出的時候,建議將註釋的內容(if
判斷結構)打開,這樣看是否可以獲取第一頁的內容,如果可以的話再進行下一步函數的封裝
3)最後全部函數封裝完畢後,再將其註釋掉,這樣就可以爬取全部的數據了
2.3.2 生成回覆數據資源url列表
1) 首先,大部分內容和上面的一樣,但是這裏使用的是簡化版的url,結果是一樣的,就沒有必要再使用全部參數的url了
2) 其次也是相同的步驟,也拿第一頁的回覆數據進行試錯,保證程序可以正常輸出結果後再獲取全部的數據
3)最後將函數插入到上方的註釋處,由於返回的是列表數據,所以要使用列表的extend
的方法
def get_reply_data(comment_page,rpid):
reply_page = 1
reply_data_lst =[]
while True:
print('正在爬取第{}頁評論數據中的第{}頁的回覆數據......'.format(comment_page,reply_page))
reply_url = 'https://api.bilibili.com/x/v2/reply/reply?&pn={}&type=1&oid=370124445&ps=10&root={}'.format(reply_page,rpid)
html = requests.get(url=reply_url, headers=dic_header)
reply_data = html.json()['data']['replies']
try:
for data in reply_data:
dic_reply = {}
dic_reply['comment'] = data['content']['message']
dic_reply['member'] = data['member']['uname']
dic_reply['like'] = data['like']
dic_reply['time'] = datetime.fromtimestamp(data['ctime'])
reply_data_lst.append(dic_reply)
print('暱稱: {}\n點贊數:{}\n'.format(dic_reply['member'],
dic_reply['like'] ))
# if reply_page > 1:
# break
reply_page += 1
except Exception as Reply_Page_Error:
break
return reply_data_lst
提醒:
1)爲了可視化輸出結果,判斷程序的進行情況,設置了暱稱和其對應評論點贊數對應的變量數據的輸出
2)還有更直觀的顯示當前程序正在爬取多少頁的評論數據中的多少頁的回覆數據(有點拗口,哈哈哈),如下圖就懂了
2.4 輸出結果
2.4.1 爬取第100頁數據的截圖
2.4.2 爬取第200頁數據的截圖
2.4.3 爬取所有的數據的截圖(總共10086條數據,這就很‘移動’了)
3. 數據可視化
3.1 點贊量最高top20的‘經典’評論
data = pd.read_excel('b站.xlsx')
#加載數據
df = data.copy()
#爲了防止原數據被破壞,操作之前備份
df = df.sort_values(by = 'like',ascending = False)
#按照點贊數進行排序,然後篩選前20條數據
df_top20 = df.iloc[:20]
x = df_top20['comment']
#作爲x軸
y = df_top20['like']
#作爲y軸
import pyecharts as pe
#使用的是0.5.11版本
bar = pe.Bar('豬狗大戰B站評論及回覆數據分析')
bar.add('騷話Top20', x, y, is_datazoom_show = True,
datazoom_range = [0,100], mark_line=[ "average"],
tooltip_axispointer_type = 'cross')
bar.render('1.html')
→ 輸出的結果爲:(點贊最多的話就是:rng贏了我去泰國變性娶了棗子哥,當時評論的時候還是兩隊沒有結束戰鬥的時刻,這位大兄弟是真的有點騷呢)
3.2 數據詞雲展示
先採用jiaba
分詞,將數據進行拆分,然後剔除字符長度爲1的數據,然後作爲展示的數據,然後進行數據的清洗,和過濾詞的設置,最後生成詞雲
import jieba
#導入jieba庫
comment_str_all = ''
for comment in df['comment']:
comment_str_all += comment
comment_str_all = comment_str_all.replace('edg','EDG').replace('rng','RNG').replace('ig','IG').replace('一萬','1w')
#把comment中的數據全部拼接成爲字符串,然後在替換重複的數據
seg_list = jieba.lcut(comment_str_all)
#中文分詞
keyword_count = pd.Series(seg_list)
#keyword_count.str.len()
#這裏是查看切割數據後不同長度的情況
keyword_count = keyword_count[keyword_count.str.len()>1]
#剔除數據長度爲1的數據
keyword_count.value_counts()
#進行數據排序,這一步就是爲了下一步設置filter_words做的準備
filter_words = ['回覆','不是','什麼','真的','就是','這麼','那麼','怎麼','現在','是的','這個','那個','這種','時候',
'什麼','這部','沒有','還有','覺得','什麼','就是','沒有','一個','不是','還是','最後','我們','但是',
'因爲','真的','還是','現在 ','可能','可以','只是','其實','所以','這樣','也許','一直','第一','爲了','它們',
'看到','看過','自己','不會','一下','然後','真有','他們','已經']
keyword_count = keyword_count[~keyword_count.str.contains('|'.join(filter_words))]
#排除filters_word裏面的數據
keyword_count = keyword_count.value_counts()[:100]
#選擇前100個重要的詞彙進行詞雲展示
wd = pe.WordCloud("關鍵詞彙挖掘-詞雲圖")
# 提取每個詞
words = keyword_count.index.tolist()
# 提取每個詞的詞頻
words_counts = keyword_count.values.tolist()
# 繪製圖表
wd.add("詞頻", words, words_counts, shape = 'star',
word_size_range=[20, 100], rotate_step=10)
#生成圖表
wd.render('2.html')
→ 輸出的結果爲:
1) 戰隊方面:可以發現,除了豬狗大戰中兩個關鍵詞之外,竟然還有IG戰隊的出現,回想一下上一次2019年3月30號IG20滴血翻盤EDG,也剛好是一週年的時間,所以網友們都在說致敬當初的一週年
2)英雄人物方面:卡薩丁(小虎)、維魯斯(Hope)、沙皇(小學弟),烏茲(uzi,永遠的神),都在關鍵詞中多次出現,主要是第三局中,小虎的後期卡薩丁給了隊伍翻盤的希望,然後Hope的維魯斯在賽場上也有很亮眼的表現,最後是uzi的解說以及網友的調侃,都使得只要有RNG的比賽,他(神一樣的男人)都會出現在關鍵詞的位置
3) 賽場因素:經濟,1w,火龍(魂),遠古+大龍(雙龍會),指揮+運營(偷家失誤)等,可以看出這些詞彙大部分指向的都是EDG戰隊的戰況,也把‘領先1W經濟被翻盤’賽場情況體現的淋漓盡致
4) 觀衆方面:棺材,澱粉,下飯,失望,經典,大哭,doge等詞彙,可以發現觀衆面對這場比賽有着兩個方向的情感,一種是偏向的對過去‘領先1W經濟被翻盤’賽事的致敬,比如下飯,經典;還有是作爲雙方粉絲之間的看比賽心情的反覆橫跳,比如棺材(我又起來了,我又躺下了…),澱粉(皇雜)等
3.3 回覆消息也能上熱門嗎?
這裏抽空研究一下,有些人很喜歡評論(回覆)已經上熱門的評論,這樣有可能自己的這條言論也會火起來,事實上是這樣嗎?下面就來以數據說話。
提醒: 在爬取回複數據的時候,是沒有rpid
的,因此很容易分辨出哪些是評論的數據,哪些是回覆的消息
df_top100 = df.iloc[:100]
#選取前100條數據作爲熱門的評判標準
df_top100['is_reply'] =''
df_top100.loc[df_top100.rpid.notnull(),'is_reply'] = False
df_top100.loc[~df_top100.rpid.notnull(),'is_reply'] = True
#添加新字段,方便統計個數
df_top100 = df_top100['is_reply'].value_counts()
attr = ["評論熱門佔比", "回覆熱門佔比"]
#因爲只有兩個數據,所以這裏就直接命名了
v1 = df_top100.values.tolist()
#也可以只用這種方式直接生成數據
pie = pe.Pie("熱門數據 圓環圖示例", title_pos='center')
pie.add(
"",
attr,
v1,
radius=[40, 75],
label_text_color=None,
is_label_show=True,
legend_orient="vertical",
legend_pos="left",
)
pie.render('3.html')
→ 輸出的結果爲:(可以發現,即使沒有能夠及時搶到評論的熱門,通過回覆,也能有30%左右的機會上熱門呢,這也就解釋了爲什麼有很多人傾向於回覆熱門的評論數據了)
3.4 有水軍嗎?
這裏可以看看是不是有評論的人故意刷評論或者回復呢?如果存在大量的同一個id發出的消息,那麼極有可能屬於水軍(當然還有一種可能就是熱門評論數據的觀衆姥爺們之間的互動,還有可能是‘鍵盤俠’)
df_shuijun = df[['member','comment']].groupby(by = 'member').count().sort_values(by = 'comment',ascending = False)
#進行分組計數,然後按照從大到小的順序進行排列
#df[df['member'].str.contains('華洛麗桑卓')]
#這個是用來查找包含某個內容的原始數據
len(df_shuijun[df_shuijun['comment'] >= 10])
#查看一下動態超過10條的數據量,也就是下面61的依據
shuijun_data_over_10 = df_shuijun.iloc[:61]
x = shuijun_data_over_10.index.tolist()
y = shuijun_data_over_10['comment'].tolist()
#設置x,y數據
bar = pe.Bar('水軍數據排行榜')
bar.add('動態數據大於10條的排名信息', x, y, is_datazoom_show = True,
datazoom_range = [0,100], mark_line=[ "average"],
tooltip_axispointer_type = 'cross')
bar.render('4.html')
→ 輸出的結果爲:(可以看到竟然有一個人動態數量達到了108條,這個明顯屬於異常數據)
查看一下這個異常數據對應的原始數據(使用同樣的方法,就可以查看所有動態量大於10條所對應的的原始數據)
data_lsz_50 = df[df['member'].str.contains('華洛麗桑卓')].sort_values(by = 'rpid',ascending =False).head(20).set_index(np.arange(1,21))
→ 輸出的結果爲:(經過前20條數據中的content內容基本可以看出來,這個姥爺應該屬於‘鍵盤俠’類的高手,至於其他的人員的數據也可以按照此方式進行數據的查看,最終可以確定是否真正的存在水軍在水評論的現象)
4. 溜了
本想寫一個爬蟲的,結果發現到手了1w多條數據,忍不住就手賤了,非要分析一下,這麼一整就凌晨兩點半了,挺禿然的…