1.前言
由於前段時間在B站看到我關注的一個程序員UP主爬取了自己所有視頻下的所有評論並錄入到數據庫里,進行了一波分析。
我就覺得挺有意思的,而且那時候我還不太會爬蟲。正巧,趕上這機會,學習學習爬蟲。
參考資源:https://www.cnblogs.com/awesometang/p/11991755.html
2.分析
樣例視頻:https://www.bilibili.com/video/BV1V44y1T7mY?spm_id_from=444.41.0.0
首先要先看看B站的評論是用哪種方式顯示出來的。
用F12是正常能看到網頁中的各個元素的,但是打開網頁源代碼卻沒有任何有關評論的信息。所以猜測大概率是通過Ajax動態加載的。
那就需要去抓包得到評論數據了。
3.抓包
我嘗試過直接在網頁中使用F12來查看請求,確實可以正常找到視頻的 評論
內容,但是無法抓到完整的 評論的評論
內容。
所以,F12的功能似乎並不夠滿足我們的需求,需要用一個專業的抓包工具——Fiddler(自行下載)
這里不做詳細使用教程
3.1 抓評論的包
-
先打開Fiddler
-
清空已攔截到的所有請求,然后掛在旁邊
-
刷新B站視頻頁面,把頁面拉到
評論區
位置,然后就別動了(為了保證獲取評論的請求能被Fiddler攔截到) -
這時候再看Fiddler的頁面,能看到已經攔截到了非常多的請求。
-
經過我們在F12里查看到的,我們可以知道評論的數據是
Json數據
。所以我們在Fiddler攔截到的請求里找Json圖標的就行。 -
隨便雙擊一個請求,將右側的視圖欄選擇到
Json
-
開始找評論。只看Json圖標的請求。直到看到以下頁面(可能是壓縮狀態,需要自己展開看)
這里我就明說了,評論的請求是帶 main
的,所以可以直接在Fiddler中按 CTRL + F
搜索 main
找到一個 main?callback=
。這個就是評論的請求
然后我們復制這個請求的 url
並在瀏覽器中對其進行訪問。
出現了一個問題,我們需要的那個請求的url無法訪問,后來嘗試才知道,需要刪除這一小段(我也不知道為什么)
再次嘗試訪問:
那么現在,我們就已經能夠爬取到一段的評論了。可是。。。
搞了這么久,只能拿到20個評論???
因為B站的評論不是一次性全部獲取到的,你拉到底,它才會幫你請求后一段的評論數據。整這樣👿
沒關系,和前面的步驟一樣。
清空已攔截的請求-->拉到底-->尋找評論請求
那每一段的評論都不一樣,那么每一個請求指定是有某些參數是不一樣的。我們可以多抓幾個,找到規律。
這里面可以看出來,只有 next
的值是不一樣的。
但為什么是 0、2、3
呢,不夠規律啊。那我們在看看 next=1
時是什么數據。
發現 next=1
和 next=0
的數據是一樣的。那 0 可能是默認請求的,實際請求的就是 1 。
這樣看的話,規律就找到了。
不斷遞增 next
的值,就可以把所有的評論都拿到手。
3.2 抓評論的評論的包
由於在網頁中使用F12是拿不到 評論的評論
的包,所以我們使用 Fiddler 再試一次。
原理和前面一樣。
清空已攔截的請求-->點擊“點擊查看”-->尋找評論的評論請求
這時再看 Fiddler ,已經攔截到一些請求了
其中有一個和前面很相似的一個 url
!!! reply?callback=
? main?callback=
那就一定是它了。
再用剛才的分析法,找到每一頁 評論的評論
的請求規律。
這里面可以看出 pn
應該就是我們要找的那個規律。
可是,最后面那串數字怎么也不一樣。
我們嘗試刪除那一段奇怪的數字
居然也能訪問,而且數據都正常,那么后面那一小串奇怪的文字咱就不要了。(加上也無妨,不影響)
3.3 評論和評論的評論是怎么關聯的
學過數據庫肯定會知道,要渲染某條評論和這條評論的評論,那么肯定有通過某個東西將他們關聯到一起。
這個東西肯定是在保存評論到數據庫中時用某個字段聯系到一起的。
我們需要找到這個聯系,才能知道每個評論和它的子評論。
不弄清關聯的話,就無法弄清楚每條評論和它的子評論了。也就無法爬取所有的 評論的評論
了。
我們就找 第一個評論和它的子評論
來研究。
現在,我們已經知道了所有的請求方式和他們之間的聯系了。開爬!
4.爬評論
代碼寫的有點糙,可以自行優化。
# 發送請求
import requests
# 將數據存入數據庫
import MySQLdb
# 每次請求停1s,太快會被B站攔截。
import time
# 連接數據庫
conn = MySQLdb.connect(host="localhost", user='root', password='admin', db='scholldatabase', charset='utf8')
cursor = conn.cursor()
# 預編譯語句
sql = "insert into bilibili(rpid,root,name,avatar,content) values (%s,%s,%s,%s,%s)"
# 爬蟲類(面向對象)
class JsonProcess:
def __init__(self):
self.Json_data = ''
# 請求頭
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39'
}
# 發送爬取請求
def spider(self, URL):
url = URL
response = requests.get(url, headers=self.headers, verify=False)
response.encoding = 'utf-8'
self.Json_data = response.json()['data']['replies']
# 爬取子評論
def getSecondReplies(root):
reply = JsonProcess()
# 頁數
pn = 1
# 不知道具體有多少頁的評論,所以使用死循環一直爬
while True:
url = f'https://api.bilibili.com/x/v2/reply/reply?jsonp=jsonp&pn={pn}&type=1&oid=979849123&ps=10&root={root}&_=1647581648753'
# 沒爬一次就睡1秒
time.sleep(1)
reply.spider(url)
# 如果當前頁為空(爬到頭了),跳出子評論
if reply.Json_data is None:
break
# 組裝數據,存入數據庫
for node in reply.Json_data:
rpid = node['rpid']
name = node['member']['uname']
avatar = node['member']['avatar']
content = node['content']['message']
data = (rpid, root, name, avatar, content)
try:
cursor.execute(sql, data)
conn.commit()
except:
pass
print(rpid, ' ', name, ' ', content, ' ', avatar, ' ', root)
# 每爬完一次,頁數加1
pn += 1
# 爬取根評論
def getReplies(jp, i):
# 不知道具體有多少頁的評論,所以使用死循環一直爬
while True:
url = f'https://api.bilibili.com/x/v2/reply/main?jsonp=jsonp&next={i}&type=1&oid=979849123&mode=3&plat=1&_=1647577851745'
jp.spider(url)
# 如果當前頁為空(爬到頭了),跳出循環,程序結束。
if jp.Json_data is None:
break
# 組裝數據,存入數據庫。
for node in jp.Json_data:
print('===================')
rpid = node['rpid']
name = node['member']['uname']
avatar = node['member']['avatar']
content = node['content']['message']
data = (rpid, '0', name, avatar, content)
try:
cursor.execute(sql, data)
conn.commit()
except:
pass
print(rpid, ' ', name, ' ', content, ' ', avatar)
# 如果有子評論,爬取子評論
if node['replies'] is not None:
print('>>>>>>>>>')
getSecondReplies(rpid)
# 每爬完一頁,頁數加1
i += 1
if __name__ == '__main__':
JP = JsonProcess()
getReplies(JP, 1)
print('\n================存儲完成================\n')
conn.close()