用Python秒搶B站一樓

用Python秒搶B站一樓

來自專欄 Python爬蟲實戰

B站的一樓自古都是戰火紛飛,尤其是搶熱門UP主的一樓更是困難,所以寫了一個Python的小程序用來自動搶一樓。以下是教程,想直接用的跳到最後即可。

思路:記錄當前UP主的視頻數,每秒檢測一次視頻數是否增加;獲取最新視頻的av號,利用POST請求發送評論

獲取視頻數:

開始直接想爬取UP主空間的視頻數,發現B站整個網頁都是用JS渲染的,因為要想每秒獲取一次視頻數肯定爬取速度不能太慢,爬完用JS渲染頁面勢必會影響速度,而且會爬取很多無關內容,所以只能抓包分析。

打開 chrome -> 進入UP主空間(投稿頁面) -> 按F12 -> 切換到network -> 刷新頁面

截獲的數據

一個一個往下看找到了一個 navnum 開頭的請求,點 Preview 發現返回了一個類似JSON 的格式

裡面的 video 正是UP主的視頻投稿數,但是多了個_jp1

觀察鏈接的後半部分

navnum?mid=221648&jsonp=jsonp&callback=__jp1

mid就是UP主的id號,把後面的callback去掉,粘貼到地址欄訪問,終於出來了純的JSON數據,後來我試了下把jsonp去掉也可以。

Note:直接從這裡獲取視頻數會有個BUG,後面在解釋。

獲取最新視頻

繼續往下查找發現了一個 getSubmitVideos 開頭的請求,點開後是一個標準的JSON,對比一下發現裡面 vlist 就是視頻列表,其中列表的第一個的aid就是最新視頻的av號

但是載入了很多其他視頻的信息,我們只需要最新的視頻,觀察一下鏈接

getSubmitVideos?mid=221648&pagesize=30&tid=0&page=1&keyword=&order=pubdate

發現 pagesize 就是整個列表的個數,改成1再訪問,果然這次只返回了1個最新的視頻,

但是突然看到了一個 count 的數據竟然是視頻數 -_-||。。。。,不過前一個請求數據量更小,所以應該會快一些(也只能這樣安慰自己了)。

Note:BUG就在這裡,當前一個獲得了視頻數與這裡的不一致,原因是這個視頻數的更新會慢一點,也就是有一些延遲,當檢測到更新時直接獲取最新的視頻,得到的不是最新視頻而是上一次的視頻。解決方法就是統一換成從這裡獲取視頻數。

提交評論

進入視頻的評論區,F12,輸入評論並提交,截獲了一個叫add的POST請求,查看下錶單的數據:

oid是視頻av號,message是我的評論,csrf是 跨站請求偽造 (簡單來說就是把表單做個唯一標記,保證是你提交的),然後看到了一大堆的cookie,用戶驗證應該只用到了幾個,應該是B站的程序猿為了省事一起提交了。。。如果想要篩選cookie可以先清除B站的cookie,然後從 快速登錄入口 調試登錄,看看設置了哪些cookie再從裡面刪除一些看似不重要的,看看是否能評論。這裡我直接寫出來我篩選的結果,一共需要4個cookie:

  • "DedeUserID": 用戶ID
  • "DedeUserID__ckMd5": 用戶ID_MD5值
  • "SESSDATA": 會話
  • "bili_jct": crsf

bili_jct 經過比對就是表單中的 csrf 的值,這兩個必須一樣,否則會提交失敗。cookie的有效期是1個月,一個月後需要重新獲取。提交成功後會返回一個json,如果提交成功code==0。

震驚了...印尼學生光著腳踢火焰足球_運動_生活_bilibili_嗶哩嗶哩?

www.bilibili.com圖標

運行截圖

代碼

因為之前的那個BUG在運行的時候才發現,為了省事就簡單改了下,獲取視頻數和av號可以合併成一個函數減少冗餘;headers直接放在函數里比較好;cookie應該放在外面方便更改;還有些小細節沒遵守PEP8;代碼僅供參考,最好自己寫寫。

# -*- coding: utf-8 -*-import requestsimport timeheaders = { User-Agent: Mozilla/5.0}def get_videos_nums(mid): # 獲取視頻數 videos_url = "https://space.bilibili.com/ajax/member/" "getSubmitVideos?mid={}&pagesize=1&page=1&order=pubdate".format(mid) resp = requests.get(videos_url, headers=headers) try: resp_json = resp.json() except ValueError: print("json 解析失敗:{}".format(resp.text)) else: return resp_json[data][count] return Nonedef get_video_aid_title(mid): # 獲取av號和標題 videos_url = "https://space.bilibili.com/ajax/member/" "getSubmitVideos?mid={}&pagesize=1&page=1&order=pubdate".format(mid) resp = requests.get(videos_url, headers=headers) resp.raise_for_status() resp.encoding = utf-8 try: resp_json = resp.json() except ValueError: print("json 解析失敗:{}".format(resp.text)) else: aid = resp_json[data][vlist][0][aid] title = resp_json[data][vlist][0][title] return aid, title return Nonedef post_comment(aid, message): # 提交評論 reply_url = "https://api.bilibili.com/x/v2/reply/add" cookie = { "DedeUserID": "", # 用戶ID "DedeUserID__ckMd5": "", # 用戶ID_MD5值 "SESSDATA": "", # 會話cookie "bili_jct": "" # crsf cookie } request_headers = { "Cookie": "DedeUserID={DedeUserID}; " "DedeUserID__ckMd5={DedeUserID__ckMd5}; " "SESSDATA={SESSDATA}; " "bili_jct={bili_jct}; ".format(DedeUserID=cookie["DedeUserID"], DedeUserID__ckMd5=cookie["DedeUserID__ckMd5"], SESSDATA=cookie["SESSDATA"], bili_jct=cookie[bili_jct]), "User-Agent": "Mozilla/5.0", } form_data = { "oid": aid, "type": 1, "message": message, "plat": 1, "jsonp": "jsonp", "csrf": cookie["bili_jct"] } try: resp = requests.post(reply_url, headers=request_headers, data=form_data) resp.raise_for_status() resp_json = resp.json() except requests.HTTPError: print("網路錯誤") except ValueError: print("json 解析失敗") else: if resp_json.get(code, None) is not None: if resp_json[code] == 0: print("評論成功:{}".format(message)) return True else: print("評論失敗:{}".format(message)) else: print("json 格式錯誤") return Falseif __name__ == __main__: mid = 221648 # UP主ID號 message = "怒搶第一" # 留言內容(必須在3-1000個字元以內) print("執行中") try: current_videos_num = get_videos_nums(mid) target_num = current_videos_num + 1 while target_num != get_videos_nums(mid): time.sleep(1) # 訪問時間間隔(秒) aid, title = get_video_aid_title(mid) if post_comment(aid, message): print("視頻{}評論成功".format(title)) else: print("視頻{}評論失敗".format(title)) except requests.HTTPError: print("網路錯誤")

總結

對於不能直接爬取的頁面,抓包分析就顯得尤為重要了,目前這個程序只能搶一個UP主的樓,利用多線程可以實現同時搶多個UP主的樓,grequests是一個多線程的requests庫,還可以設定搶固定樓層,例如:2333樓,如果各位有興趣後面再出個多線程版本。第一次寫教程,歡迎各位提出意見或建議。

GitHub源碼


推薦閱讀:

TAG:Python入門 | python爬蟲 | 嗶哩嗶哩 |