websocket與爬蟲

kimg1234發表於2019-02-16

原文來自 websocket與爬蟲

背景

寫爬蟲的目的應該就是為了拿到資料,或者說模擬某種操作
如果他使用的是http(s) 協議來傳輸資料的,那麼我們就模擬http協議來傳送資料
如果它使用的是websocket協議來傳輸資料的,
那麼我們理所當然的就模擬websocket來傳送資料~

首先,我們需要了解什麼是websocket

websocket的介紹

WebSocket是一種在單個TCP連線上進行全雙工通訊的協議。WebSocket通訊協議於2011年被IETF定為標準RFC 6455,並由RFC7936補充規範。WebSocket API也被W3C定為標準。

WebSocket使得客戶端和伺服器之間的資料交換變得更加簡單,允許服務端主動向客戶端推送資料。在WebSocket API中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以建立永續性的連線,並進行雙向資料傳輸。

上面是維基百科的介紹.
簡單的將,websocket 和http一樣,都是一種網路傳輸協議

他比http協議好的地址有哪些呢?

  • 較少的控制開銷。在連線建立後,伺服器和客戶端之間交換資料時,用於協議控制的資料包頭部相對較小。在不包含擴充套件的情況下,對於伺服器到客戶端的內容,此頭部大小隻有2至10位元組(和資料包長度有關);對於客戶端到伺服器的內容,此頭部還需要加上額外的4位元組的掩碼。相對於HTTP請求每次都要攜帶完整的頭部,此項開銷顯著減少了。
  • 更強的實時性。由於協議是全雙工的,所以伺服器可以隨時主動給客戶端下發資料。相對於HTTP請求需要等待客戶端發起請求服務端才能響應,延遲明顯更少;即使是和Comet等類似的長輪詢比較,其也能在短時間內更多次地傳遞資料。
  • 保持連線狀態。於HTTP不同的是,Websocket需要先建立連線,這就使得其成為一種有狀態的協議,之後通訊時可以省略部分狀態資訊。而HTTP請求可能需要在每個請求都攜帶狀態資訊(如身份認證等)。
  • 更好的二進位制支援。Websocket定義了二進位制幀,相對HTTP,可以更輕鬆地處理二進位制內容。
  • 可以支援擴充套件。Websocket定義了擴充套件,使用者可以擴充套件協議、實現部分自定義的子協議。如部分瀏覽器支援壓縮等。
  • 更好的壓縮效果。相對於HTTP壓縮,Websocket在適當的擴充套件支援下,可以沿用之前內容的上下文,在傳遞類似的資料時,可以顯著地提高壓縮率

websocket的應用場景

  • 直播平臺的彈幕
  • 實時聊天
  • 等等

websocket 協議

WebSocket 是獨立的、建立在 TCP 上的協議。

Websocket 通過 HTTP/1.1 協議的101狀態碼進行握手。

為了建立Websocket連線,需要通過瀏覽器發出請求,之後伺服器進行回應,這個過程通常稱為“握手”

那麼websocket協議是如何握手的呢?

websocket握手

下面是websocket一次握手的過程
客戶端請求

GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

伺服器響應

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/

和http欄位不一樣的地方

  • Connection必須設定Upgrade,表示客戶端希望連線升級。
  • Upgrade欄位必須設定Websocket,表示希望升級到Websocket協議。
  • Sec-WebSocket-Key是隨機的字串,伺服器端會用這些資料來構造出一個SHA-1的資訊摘要。把“Sec-WebSocket-Key”加上一個特殊字串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然後計算SHA-1摘要,之後進行BASE-64編碼,將結果做為“Sec-WebSocket-Accept”頭的值,返回給客戶端。如此操作,可以儘量避免普通HTTP請求被誤認為Websocket協議。
  • Sec-WebSocket-Version 表示支援的Websocket版本。RFC6455要求使用的版本是13,之前草案的版本均應當棄用。
  • Origin欄位是可選的,通常用來表示在瀏覽器中發起此Websocket連線所在的頁面,類似於Referer。但是,與Referer不同的是,Origin只包含了協議和主機名稱。
  • 其他一些定義在HTTP協議中的欄位,如Cookie等,也可以在Websocket中使用。

可以看到只是在http協議上增加了幾個硬性規定,http協議的user-agent,cookie都可以在websocket握手過程中使用

抓包時候的注意事項:因為websocket只有一次握手,握手成功後就可以雙方傳送訊息了,假如你開啟網頁後沒有找到你要抓的資料,那麼你就需要重新重新整理網頁,讓他重新握手一次

websocket的事件

on_open

表示剛剛連線的時候

onmessage

表示收到訊息怎麼做

send

表示給伺服器傳送訊息

on_close

表示關閉連線

那麼知道了這些對我們有什麼好處麼?
找js的時候會很好找,這幾個關鍵詞基本上都是固定的
你可以直接全域性搜搜,然後很容易能找到傳送的js程式碼

模擬傳送的時候也是一樣的.

實際案例

前面介紹了一堆websocket協議相關的東西,估計很多人已經暈了.
沒關係,先看例項,有問題再回到上面看

抓包可以使用fiddle,chrome也是可以的

我們先使用chrome

本次要抓的網站的一個投票網站
大家可以先隨便投一個票,抓抓包看看
會發現怎麼沒有找到他是如何提交資料的…

選擇ws,然後重新整理下網頁,再點選下投票,會發現有一個請求

可以看到是在握手階段,請求頭裡面的引數和我們上面講的是一樣的.

請求地址是ws://v5.10brandchina.com:8008/
這邊順帶說一下,有時候這邊會看到 wss://v5.10brandchina.com:8008/
那麼這兩個有啥區別的,簡單的講就是http與https協議的區別一樣…

看一下互動的內容(點選Frames)
可以看到已經有四條訊息了,但是訊息內容是二進位制的,chrome這邊無法預覽…
那麼我們使用fiddle試一下

抓包與分析

開啟fiddle,重新整理一下網頁
不重新整理的話是看不到的,然後隨便投一下票.

怎麼找到請求呢,很簡單,看狀態碼為101的就行,然後雙擊這一行

然後這邊還是看到四條訊息,我們點選第一條,然後用TextView展示,可以看到訊息是這些
為啥用TextView呢?其實是一個一個的試過來的,假如你發現都試過了,還是亂碼,那應該是他使用了其他的壓縮或者加密方法,需要檢視js看看他是如何加密的

這個網站的資料是沒有加密過的.
帶向上的箭頭的是我們向伺服器傳送的,向下的箭頭是伺服器返回的(下面的資料,前面帶黑點?,是我們傳送的)

  • {"action":"auth","val":5}

{"action":"auth","msg":"eval("\115\141\164\150\56\163\151\156\50\61\65\61\67\67\66\62\63\61\63\51")"}

  • {"action":"auth","val":-0.3241458910493796}

{"action":"wait","msg":95420}

  • {"action":"vote","val":"{"itemid":126067,"catid":41867,"captcha":"%u7EC7%u65E7%u5F88%u9C7C","auth":5,"rnd":"4186712606754595"}"}

{"action":"vote","msg":"ok,231812,2018-02-04 22:32:55"}

可以看出來
首先我們傳送{"action":"auth","val":5}
然後伺服器返回一串資訊給我們,
然後我們根據伺服器返回的算出一個值,也就是
{"action":"auth","val":-0.3241458910493796}
再傳送給伺服器.
伺服器返回{"action":"wait","msg":95420},表示驗證通過
然後我們投票,傳送了投票的一些資訊給服務
伺服器告訴我們投票成功.

以上就是整個通訊過程.

那如果我們要模擬傳送的話,需要知道哪些資訊呢

  1. {"action":"auth","val":5}裡面的val:5,這個5是固定的麼?如果不是,是如何生成的
  2. 伺服器返回的是什麼,如何解析
  3. 如何根據伺服器返回的生成一個新的val
  4. 傳送投票資訊裡面{"action":"vote","val":"{"itemid":126067,"catid":41867,"captcha":"%u7EC7%u65E7%u5F88%u9C7C","auth":5,"rnd":"4186712606754595"}"}

itemid,catid,capthc,auth,rnd如何生成

找引數

還是使用chrome,直接用ctrl + shift +f,然後輸入websocket(或者on_open,on_message,等等上面提到的事件去搜尋)

運氣很好,輸入websocket直接就搜到了js,還是沒有混淆的

首先發現 websocket 地址是根據catId變的,如果catId能被2整除則地址為xxx,否則為xxx
那麼catId是什麼呢,除錯發現就是url中的id,我們當前url為http://www.10brandchina.com/vote/startin.php?id=41867則 catId為41867

然後onmessage也看到了,大概意思是收到資訊後,用json解析,如果action是auth的話,則呼叫sendData這個方法,如果action是vote的話,則使用vote_resule方法.

在看到onopen方法,是呼叫sendData,併傳送(`auth`,authType),在這邊是不是聯想到前面,我們第一次傳送的資料?{"action":"auth","val":5},是不是感覺一模一樣

close方法就不說了,反正我們也用不上

再看看sendData這個方法,

用python實現的話是這樣

再看vote_result方法,大概作用是判斷投票結果

所有的方法我們都找到了,那麼我們再和之前要找的引數走一遍.

  1. {"action":"auth","val":5}裡面的val:5,這個5是固定的麼?如果不是,是如何生成的

    這個5也就是onopen裡面的authType,至於authType是不是固定的,搜尋一下就知道了.

  2. 伺服器返回的是什麼,如何解析
  3. 如何根據伺服器返回的生成一個新的val

    可以通過onmessage方法知道他返回的json資料,json解析一下就行,
    裡面的val是通過執行 eval(val)得到的
    所以你也可以直接執行這個.或者用python實現

  4. 傳送投票資訊裡面{"action":"vote","val":"{"itemid":126067,"catid":41867,"captcha":"%u7EC7%u65E7%u5F88%u9C7C","auth":5,"rnd":"4186712606754595"}"}

itemid,catid,capthc,auth,rnd如何生成

itemid 就是你投票的公司的id,catid之前講過,captcha就是驗證碼,
auth和上面的authtype一樣
rnd是通過搜尋js發現了.

再看看驗證碼是如何生成的呢

檢查驗證碼是否正確

我們已經拿到所有需要的東西了,只要用程式模擬傳送就行了.

模擬傳送

使用的包是websocket

官方demo

import websocket
try:
    import thread
except ImportError:
    import _thread as thread
import time

def on_message(ws, message):
    print(message)

def on_error(ws, error):
    print(error)

def on_close(ws):
    print("### closed ###")

def on_open(ws):
    def run(*args):
        for i in range(3):
            time.sleep(1)
            ws.send("Hello %d" % i)
        time.sleep(1)
        ws.close()
        print("thread terminating...")
    thread.start_new_thread(run, ())


if __name__ == "__main__":
    websocket.enableTrace(True)
    ws = websocket.WebSocketApp("ws://echo.websocket.org/",
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    ws.on_open = on_open
    ws.run_forever()

可以看到使用還是很簡單的,也是onopen,onmessage,send

所以我們只要用我們上面得到的資訊就行模擬傳送就可以了

因為是投票網站,所以不提供程式碼…有啥問題,請留言~

相關文章