聲明:本篇僅基於興趣以及技術研究而對B站曾經發生過的搶樓事件背後相關技術原理進行研究而寫。請不要將其做爲私利而對B站以及B站用戶體驗形成影響!謝謝合做!若本文對B站及其用戶帶來困擾,請聯繫本人刪除本文。javascript
雖說是技術研究,但實際上並無什麼太深的東西在裏面,你只須要懂一點http協議的請求格式、懂python、會使用python requests package就能完成這個簡單的任務了。html
若是你不懂python,仍是先簡單瞭解一下配置環境以及語法再來繼續下面內容。前端
好了,若是你想要簡單瀏覽並瞭解一點http協議知識,能夠試試閱讀這兩篇:https://blog.csdn.net/a19881029/article/details/1400227三、https://blog.csdn.net/Stream__/article/details/78604937。java
關於本文使用的python庫:見 http://docs.python-requests.org/zh_CN/latest/python
開始吧。ajax
首先你確定得有一個瀏覽器,我推薦Chrome —— 我的喜愛。最好還有一個PyCharm或者其餘python編輯器,若是你只喜歡用python自帶的命令行工具也行。數據庫
而後說一點,如今B站爲了防止搶樓,把番劇下全部視頻的評論區都合併了(一些番劇貌似並無這樣作,例如哆啦A夢),之前每個視頻下都會有對應的評論區,如今全部視頻的評論所有在一塊兒的。。。因此如今就算要搶也只有對新番第一集搶樓可能纔有‘意義’了。json
而後有許多語言工具均可以進行B站的搶樓,好比使用python+phantomjs+selenium、js、Java、C++等,因爲我學習能力與水平有限,沒用過js、Java進行過爬蟲,C++的話本身正在仿照python的requests決定盡力寫一個好用的C++ http庫。api
嘛,這篇仍是至關於用便利的requests來作一個爬蟲小教學以及學習如何使用除get外的http動詞。瀏覽器
工具都準備好了,讓咱們進入主題吧:
這裏隨便選了一部老番《D.C.Ⅱ S.S.》又稱《初音島》做爲測試。
來到番劇劇集頁面,先F12準備監控一下子發送請求服務器返回的數據包:
當咱們要進行這個任務的時候,咱們必需要先知道:咱們該向什麼地方發送的請求?難道就直接對番劇頁面發送就能夠了嗎?若是有作過網站的經驗就會知道,一個網站的前端展現頁面基本上都是經過 js + ajax 等經過後臺的業務邏輯調用數據庫中的數據加載到對應的jsp文件中的html標籤中自動生成的。好比評論區,確定有一個 post 的API接口來接受用戶發送的數據,並將數據存入到數據庫中,而後展現頁面 + js + 數據庫 + 後臺業務邏輯等一套服務,最後咱們用戶才能在前端中看到豐富的內容,才能看到實時更新的數據,說實時更新不太對,但總之你每次刷新頁面,網站後臺就會作這些事情。
這些有什麼用呢?至少我知道了當我在B站評論區編輯好要發送的消息並點擊發送評論的時候,確定是經過一個特意編寫好的接口來post data,而這個post接口的url會在咱們點擊按鈕的動做後顯示在瀏覽器的網絡監控中,因此,咱們要找到這個接口的url就要先發送一個消息試一試:
最好就是在瀏覽器加載完該頁面的數據後按F12打開監控臺,這樣比較乾淨,點擊發表評論後,很快就能夠注意到咱們的動做的回饋,點一下看看內容:
顯然,它是經過http post動詞來提交的,從中我找到了這個add接口的url:
到這裏已經能夠宣告結束了。注意到Request Headers中就是咱們須要自定義添加的請求頭內容,而後,很關鍵的地方就是發送內容,它通常在Chrome監控臺最下面:
oid對應當前視頻的av號,這樣才能肯定是對哪個視頻進行的評論,能夠在視頻頁面這裏獲取它:
這裏type的含義不重要,message顯然是咱們要發送的東西,注意message如何是中文,那麼將會進行url編碼,能夠參考:http://www.w3school.com.cn/tags/html_ref_urlencode.html
後面幾個除csrf外的含義在這裏也並不重要。。。
ps:剛纔我對新番Comic Girls用一樣的csrf試了一下,成功了:
B站評論區效果:
但其實我更以爲比較僥倖的是,B站的發送評論並無作很是複雜的驗證...咱們在python中使用它們:
data = { 'oid': '2458871', 'type': '1', 'message': 'test~', 'plat': '1', 'jsonp': 'jsonp', 'csrf': 'da9c3263c011ee0969ce383e8d799f05' }
而後利用瀏覽器中的Request Headers寫好咱們的請求頭:
headers = { 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Connection': 'keep-alive', 'Content-Length': 'xxx', # 這裏對應的就是咱們的data數據的長度 'Cookie': 'xxx', 'Host': 'api.bilibili.com', 'Referer': 'https://www.bilibili.com/bangumi/play/ep86125', 'User-Agent': 'xxx', }
這裏上面註釋中的長度能夠這樣獲取,來到瀏覽器:
點擊藍色區域變爲:
固然你能夠肉眼一個個字符的數,但我仍是選擇複製一下這段字符串,而後到粘貼到PyCharm中,選中粘貼上去的那段字符串,而後藍色框住的部分就是字符串的長度,也就是這裏咱們的Content-Length的長度:
但這是測試事後獲得了add中的data信息才能知道長度,若是對一部沒有add信息的視頻,仍是本身將data轉換爲瀏覽器中的拼接參數格式的字符串,而後用python len算一下字符串長度:
s = str(data).replace(': ', '=').replace(',', '&')\ .replace('\'', '').replace(' ', '')\ .replace('{', '').replace('}', '') content_length = len(s) print(s) print(content_length)
或者直接len(str(data)),由於Content-Length對於填寫並不嚴格,但雖然Content-Length對於填寫並不嚴格,但就算隨便填寫長度也必需要比實際長度大,由於這樣從推斷上請求內容就應該是損壞的。並且注意一些字符以及中文字符的url編碼格式,好比空格的爲%20,但基本上都是%xx的格式,看url編碼格式中英文字符也是%xx,但實際上也並不須要編碼,因此長度須要注意下。。。
刪除這段字符串,開始使用requests post:
r = request.post(url, data=data, headers=headers, timeout=1, ) print(r.status_code) print(r.json()) # r.text
值得注意的一點是,有些接口的請求內容是data參數,data參數僅僅就是轉換爲字符串,而還有一些接口的請求內容格式是json,這時就只須要將參數data改成使用json便可:
r = request.post(url, json=json)
詳細的能夠對準post方法 Ctrl + 左鍵 看看源碼可能會更有幫助。
ps:擴展一下,這裏以上面的client發送post請求爲例,它的http請求格式是這樣的,其中c表示客戶端,s表示服務端,(:)表示開始,(:!)表示結束:
c >> POST /x/v2/reply/add HTTP1/1\r\n c >>(Request headers:) User-agent: xxx\r\n c >> Accept: application/json, text/javascript, */*; q=0.01\r\n c >> Accept-Encoding': 'gzip, deflate, br\r\n c >> Accept-Language': 'zh-CN,zh;q=0.9\r\n c >> Connection: keep-alive\r\n c >> Content-Length: xxx\r\n c >> Cookie: xxx\r\n c >> Host: api.bilibili.com\r\n c >> Referer: https://www.bilibili.com/bangumi/play/ep86125\r\n c >>(Request headers:!) \r\n c >>(body:) oid=xxx&type=1&message=xxx&plat=1&jsonp=jsonp&csrf=xxx...\r\n c >>(body:!) s >>(:) ... s >>(:!) ...
注意請求格式以及整個過程底層作了哪些事:
1.請求方法與請求資源路徑之間有空格,資源路徑與協議版本之間有空格每一句後面加上\r\n,一個換行符一個換行表示該行結束;
2.請求頭中key與value之間有冒號,冒號後面必定要加上空格,最後一個換行符一個換行表示該行結束;
3.請求頭的結束輸入以一行\r\n表示結束;
4.請求頭結束後的下面纔是請求body部分;
5.請求body結束之後,後面就是服務端返回的響應格式內容;
6.最後再返回請求的資源。
到這裏,這樣就完成了發送評論。
既然咱們能夠發送評論,那麼咱們又如何經過代碼來刪除呢?
咱們先在頁面中刪除一條剛纔對視頻的評論測試一下,找到刪除的接口:
能夠看到B站刪除的接口爲:https://api.bilibili.com/x/v2/reply/del,並且也是經過post的方式進行的。
再看看發送刪除請求須要什麼內容:
注意到 rpid ,這不就是剛纔發表評論時返回的json中出現過的嗎,因而按照以前的發送方法,再經過刪除接口寫一個刪除的方法:
因爲以前發送的已經被刪除了,前面的補充中還測試過《Comic Girls》,因此這裏用她來作刪除測試:
from requests import Session request = Session() url = 'https://api.bilibili.com/x/v2/reply/del' data = { 'oid': '21683499', 'type': '1', 'rpid': '788555414', 'jsonp': 'jsonp', 'csrf': 'da9c3263c011ee0969ce383e8d799f05' } content_length = len(str(data).replace(': ', '=').replace(',', '&').replace('\'', '')) headers = { 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Connection': 'keep-alive', 'Content-Length': str(content_length), 'Cookie': 'xxx', 'Host': 'api.bilibili.com', 'Referer': 'https://www.bilibili.com/bangumi/play/ep86125', 'User-Agent': 'xxx', } r = request.post(url, data=data, headers=headers, timeout=1, ) print(r.status_code) print(r.json()) # r.text
運行結果:
B站頁面上看看,發現沒了:
以前補充中發表的評論是:emmmm...
對比一下樓層數也是正確的。其實我覺得會用到delete動詞的,但沒想到B站的刪除評論方法仍是post。
最後,實際上我寫完以後發現個人方法有問題,首先,新番第一集的oid如何得到?或許能夠想辦法經過新番時間表來獲取,我去新番時間表中找了找,但沒看到給出oid的文件。。。因此仍是須要從其餘角度找找解決辦法,我以爲能夠考慮從主頁上獲取到新番信息的連接,由於記得新番在開播前會有提醒,惋惜我之前貌似歷來沒有點進去看過會是什麼樣子的。。。但假如這時就已經有頁面了只是沒有集數也好辦,就不停的檢測頁面的變化,如何發現有了oid那就同時發送消息,emmm...
總之csrf目前應該能夠固定不變的使用,只要找到oid,對新視頻及時進行(qiang)評論(lou)的問題就解決了。
還有許多細節還得要本身去體會~ 我一直想要下載B站的視頻,但一直沒能完成。。。
2018-05-22 02:01:08 ps:關於如何獲取csrf,能夠參考我這篇中最後提到的方法。
# 額外花絮。。。
以前不太懂的時候,我很sb的試過這樣寫:
from requests import Session request = Session() url = 'https://api.bilibili.com/x/v2/reply/add' data = { 'oid': '2458871', 'type': '1', 'message': 'test~', 'plat': '1', 'jsonp': 'jsonp', 'csrf': 'da9c3263c011ee0969ce383e8d799f05' } r = request.post(url, data=data, auth=('user', 'pw'), timeout=1, ) print(r.status_code) print(r.json()) # r.text
顯然被殘酷的拒絕了:
由於這個接口並非用於登陸的接口,因此確定不可能登陸上的。。。