python學習 —— post請求方法的應用


 聲明:本篇僅基於興趣以及技術研究而對B站曾經發生過的搶樓事件背后相關技術原理進行研究而寫。請不要將其作為私利而對B站以及B站用戶體驗造成影響!謝謝合作!若本文對B站及其用戶帶來困擾,請聯系本人刪除本文。

  雖然說是技術研究,但實際上並沒有什么太深的東西在里面,你只需要懂一點http協議的請求格式、懂python、會使用python requests package就能完成這個簡單的任務了。

  如果你不懂python,還是先簡單了解一下配置環境以及語法再來繼續下面內容。

  好了,如果你想要簡單瀏覽並了解一點http協議知識,可以試試閱讀這兩篇:https://blog.csdn.net/a19881029/article/details/14002273、https://blog.csdn.net/Stream__/article/details/78604937。

  關於本文使用的python庫:見 http://docs.python-requests.org/zh_CN/latest/

  開始吧。

  首先你肯定得有一個瀏覽器,我推薦Chrome —— 個人喜好。最好還有一個PyCharm或者其他python編輯器,如果你只喜歡用python自帶的命令行工具也行。

  然后說一點,現在B站為了防止搶樓,把番劇下所有視頻的評論區都合並了(一些番劇貌似並沒有這樣做,例如哆啦A夢),以前每一個視頻下都會有對應的評論區,現在所有視頻的評論全部在一起的。。。所以現在就算要搶也只有對新番第一集搶樓可能才有‘意義’了。

  然后有許多語言工具都可以進行B站的搶樓,比如使用python+phantomjs+selenium、js、Java、C++等,由於我學習能力與水平有限,沒用過js、Java進行過爬蟲,C++的話自己正在仿照python的requests決定盡力寫一個好用的C++ http庫。

  嘛,這篇還是相當於用便利的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

  顯然被殘酷的拒絕了:

  因為這個接口並不是用於登錄的接口,所以肯定不可能登錄上的。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM