聲明:全過程沒有任何違法操作
彈幕部分
本部分有兩個方法介紹
直接在視頻頁加載的文件中爬取
首先打開《ELOG》S11世界賽特別篇:歡迎回家,然后F12打開資源管理器,在網絡->Fetch/XHR中尋找文件(不要問我為什么,因為一般情況下數據文件可能會在這一欄出現),然后一個一個文件點,會發現這樣一類文件的預覽是一堆亂碼
點開一看要么是不允許訪問或者一個小視頻框一直處於加載,所以判定這一類文件不是我們要尋找的。
然后我們再繼續尋找,會發現一個seg.so開頭的文件
然后這又是一堆亂碼,但是這次這個不一樣了,因為你會發現后面的數字長度都差不多(可能是巧合),而且格式都差不多,多半這就是我們要尋找的文件,我們放在python中看看
好家伙,果然是,但是中間除了彈幕中文內容,還有一堆看不懂的亂碼,而且點開這個文件會閃退,說明不能用一般的方式處理,所以我們可以使用正則表達式提取其中的彈幕部分。
一下是代碼設計(文件為:爬取B站視頻彈幕.py)
import requests
import re
url = "https://api.bilibili.com/x/v2/dm/web/seg.so?type=1&oid=452176196&pid=337021716&segment_index=1"
#需要加申請頭,注意字符串中出現有一些特殊符號需要保留需要加轉義符號\
#cookie是用戶信息
#user-agent是服務器申請
headers = {
"cookie":"buvid3=0DFEE112-51F6-F856-76B1-FB921343F2E495271infoc; _uuid=D8AC11EE-1D17-81065-DCED-D7DE5553873894300infoc; buvid_fp=0DFEE112-51F6-F856-76B1-FB921343F2E495271infoc; CURRENT_FNVAL=2000; blackside_state=1; rpdid=|(k|~umkuRR)0J'uYJ))lkm~m; PVID=1; b_lsid=D4C984EF_17D947274C1; sid=aox54cdj",
"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
}
response = requests.get(url=url, headers=headers).text
#利用正則表達式提取彈幕(Ctrl+F尋找匹配字段),.*?是通配符,可以匹配任意字符,從:開始到@前的內容都是需要的,()精確匹配
# #正則表達式提取的是一個列表
content_list = re.findall(':(.*?)@', response)
f = open('《ELOG》S11世界賽特別篇:歡迎回家視頻彈幕', mode='w', encoding="utf-8") #模式a表示追加寫入,而w只表示可寫入,二者區別極大
for index in content_list:
content = index[1: ]
f.write(content)
f.write("\n")
f.close()
print("彈幕寫入完成")
我們運行一下看看效果
這就是在視頻頁加載的文件中直接爬到彈幕了
通過B站的api端口爬取彈幕並整理
為什么需要整理,請接着往下學習
在網址bilibili前加一個i,進入視頻的api網址,如Flappy Bird-我把Flappy Bird改編成了分手模擬器
接下來可以進入彈幕地址,打開如下圖
這就是彈幕內容了,然后我們可以先下載下來
# 下載文件(注意,要看清這個文件是什么格式,如果保存用.txt格式或是其他格式就會出現亂碼)
def downloadfiles(url,count):
f = requests.get(url)
filename = str(count)+".so"
with open(filename,"wb") as code:
code.write(f.content)
print('文件下載成功!')
print('-----------')
當然,我們下載的.so文件是可以使用utf-8格式打開的,打開后里面的彈幕內容仍然是中文的,就不會出現亂碼,如何正確的處理文件里的內容才是重要的。代碼設計如下。
def calculatenum():
global endnum
f = open('E://.py/list.so', "r", encoding='utf-8')
lines = f.readlines()
for item in lines:
d = re.findall('>(.*?)<', item)
emptycontain = []
i = 0
for index in d:
emptycontain.append('{}'.format(i + 1) + index)
i += 1
print('列表中元素總數為:{}'.format(i))
print('------------------')
endnum = i - 2
def convert():
global endnum
ff = open('./彈幕文件.txt', "w", encoding='utf-8')
f = open('./彈幕源文件.so', "r", encoding='utf-8')
lines = f.readlines()
content = []
calculatenum()
for item in lines:
d = re.findall('>(.*?)<', item)
i = 16
while i <= endnum:
ff.write(d[i])
ff.write('\n')
content.append(d[i])
i += 2
else:
break
print('彈幕整理完成!')
解釋一下這里為什么有一個calculatenum函數,這個函數是用來計算d列表中元素個數,因為正則表達式返回的內容類型為list類型,所以這一步必不可少,這也是重要的一個設計思路。
完整的代碼如下
import re
import requests
def downloadfiles(url,count):
f = requests.get(url)
filename = str(count)+".so"
with open(filename,"wb") as code:
code.write(f.content)
print('文件下載成功!')
print('-----------')
def calculatenum():
global endnum
f = open('E://.py/list.so', "r", encoding='utf-8')
lines = f.readlines()
for item in lines:
d = re.findall('>(.*?)<', item)
emptycontain = []
i = 0
for index in d:
emptycontain.append('{}'.format(i + 1) + index)
i += 1
print('列表中元素總數為:{}'.format(i))
print('------------------')
endnum = i - 2
def convert():
global endnum
ff = open('./彈幕文件.txt', "w", encoding='utf-8')
f = open('./彈幕源文件.so', "r", encoding='utf-8')
lines = f.readlines()
content = []
calculatenum()
for item in lines:
d = re.findall('>(.*?)<', item)
i = 16
while i <= endnum:
ff.write(d[i])
ff.write('\n')
content.append(d[i])
i += 2
else:
break
print('彈幕整理完成!')
if __name__ == '__main__':
downloadfiles('https://api.bilibili.com/x/v1/dm/list.so?oid=455243599', '彈幕源文件')
convert()
我們運行一下看看
這樣就從api端口中爬取到彈幕了
評論部分
評論部分就是一個比較難的,雖然以前有大佬爬過,但是他們的方法在現在就沒有太大作用了,但是對我也有參考價值,廢話不多說,看我如何處理。
當然,視頻還是我把Flappy Bird改編成了分手模擬器,首先F12找文件,在網絡->Fetch/XHR找啊找啊找,我發現一個問題,怎么找也沒有對應的評論文件,說明存儲評論的文件並不在這一欄,我們點開全部。這里我發現一個隨評論刷新的一類文件(因為現在的B站評論不是以頁顯示的了,意思也就是說B站的評論是刷新的,動態的,說明一定有隨評論刷新而增加的文件),也就是以main?開頭的文件
好巧不巧,我一打開數據,一路展開,找到了對應內容,說明評論就是裝在這類文件里面的
那么我們就需要設計程序爬取到里面的內容。
但是這個文件是用get方式就能得到的json文件(還記得我在爬取佰騰網中說過的吧,json文件返回的內容是python中的字典類型),因此一個get申請獲取就ok了,至於如何去處理文件內的數據,就看自己的想法,這里我只想要爬取評論者和評論內容,所以設計就比較簡單了。設計代碼如下
import requests
f = open('我把Flappy Bird改編成了分手模擬器評論.txt', "w", encoding='utf-8')
f.write("我把Flappy Bird改編成了分手模擬器視頻評論" + "\n")
f.write("\n")
def get():
url = "https://api.bilibili.com/x/v2/reply/main?jsonp=jsonp&next=0&type=1&oid=379680114&mode=3&plat=1"
#需要加申請頭,注意字符串中出現有一些特殊符號需要保留需要加轉義符號\
#cookie是用戶信息
#user-agent是服務器申請
headers = {
'accept-language': 'zh-CN,zh;q=0.9',
'cookie': 'buvid3=0DFEE112-51F6-F856-76B1-FB921343F2E495271infoc; _uuid=D8AC11EE-1D17-81065-DCED-D7DE5553873894300infoc; buvid_fp=0DFEE112-51F6-F856-76B1-FB921343F2E495271infoc; CURRENT_FNVAL=2000; blackside_state=1; rpdid=|(k|~umkuRR)0J\'uYJ))lkm~m; sid=aox54cdj; fingerprint=5260c3f503ebc356d558c28d1a3972d9; buvid_fp_plain=3E450DFB-AFB9-62E8-E610-D184933E113454738infoc; DedeUserID=1509085458; DedeUserID__ckMd5=83f2d34217a80d8e; SESSDATA=4385026c,1654425909,7fca3*c1; bili_jct=74d249db78dc581cca66406e0f4a54a3; CURRENT_QUALITY=80; b_lsid=2C67E6D5_17D956A459F; PVID=3',
'referer': 'https://www.bilibili.com/video/BV1h3411b76L?spm_id_from=333.999.0.0',
'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'script',
'sec-fetch-mode': 'no-cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36'
}
response = requests.get(url=url, headers=headers).json()
result = response['data']['replies']
try:
for item in result:
user = item['member']['uname']
comment = item['content']['message']
f.write(user)
f.write(":")
f.write(comment)
f.write("\n")
try:
for index in item['replies']:
f.write(" "+index['member']['uname'])
f.write(":")
f.write(index['content']['message'])
f.write("\n")
except(TypeError):
f.write("\n")
i = 1
while response['data']['cursor']['is_end'] == False:
i += 1
url1 = "https://api.bilibili.com/x/v2/reply/main?jsonp=jsonp&next=" + str(i) + "&type=1&oid=379680114&mode=3&plat=1"
response1 = requests.get(url=url1, headers=headers).json()
result1 = response1['data']['replies']
for item2 in result1:
user = item2['member']['uname']
comment = item2['content']['message']
f.write(user)
f.write(":")
f.write(comment)
f.write("\n")
try:
for index1 in item2['replies']:
f.write(" " + index1['member']['uname'])
f.write(":")
f.write(index1['content']['message'])
f.write("\n")
except(TypeError):
f.write("\n")
else:
print('程序停止')
except(TypeError):
print("程序停止")
get()
我們運行一下看看效果
這就爬取到了B站視頻的評論了(但是這里有個小缺陷,一個評論者一般會有5,6個回復者,這種方式也只能爬到最多3個評論者,原因我也不清楚)
總結
通過爬取B站的彈幕和視頻,我們在這個過程用到了正則表達式,但是從這個過程中收獲最大的還是程序設計的邏輯以及如何更好地處理問題。其實上面的代碼通過一定的修改可以批量爬取彈幕和評論,這里讀者們可以自己嘗試設計一個批量爬取彈幕和視頻的程序(一個小提示:獲取某個up主的主頁,通過爬取這個up主主頁中視頻(oid)號,然后進行批量生產url就可以達到批量爬取的目的)