WebSocket 反爬蟲


WebSocket握手驗證反爬蟲

!

HTTP協議

請求頭

作為一名爬蟲工程師,在工作中常常會遇到爬取實時數據的需求,比如體育賽事實時數據、股市實時數據或幣圈實時變化的數據

Web 領域中,用於實現數據'實時'更新的手段有輪詢和 WebSocket 這兩種。輪詢指的是客戶端按照一定時間間隔(如 1 秒)訪問服務端接口,從而達到 '實時' 的效果,雖然看起來數據像是實時更新的,但實際上它有一定的時間間隔,並不是真正的實時更新。輪詢通常采用 拉 模式,由客戶端主動從服務端拉取數據。

WebSocket 采用的是 推 模式,由服務端主動將數據推送給客戶端,這種方式是真正的實時更新。

什么是 WebSocket:https://www.cnblogs.com/kai-/p/12692187.html#websocker%E4%B8%BB%E6%B5%81%E6%B5%8F%E8%A7%88%E5%99%A8%E9%83%BD%E6%94%AF%E6%8C%81https://www.cnblogs.com/kai-/p/12692187.html#websocker主流瀏覽器都支持

服務器端創建 socket 服務后監聽客戶端,使用 while True 的方式讀取客戶端發送的消息

然后對服務器端發送的握手請求進驗證,如果驗證通過,則返回狀態碼為 101 的響應頭,否則返回狀態碼為 403 的響應頭


客戶端按照 WebSocket 規范生成握手信息並向服務器端發送握手請求,然后讀取服務器端推送的消息,最后驗證握手信息

服務器端和客戶端實際上可以不遵守這些約定

比如服務器可以在校驗握手信息是增加對客戶端 User-Agent 或 Referer (請求頭)的驗證,如果客戶端發送的握手請求中並沒有對應的信息,則拒絕連接



WebSocket 消息校驗反爬蟲

握手成功之后,雙端就可以開始互推消息了

WebSocket 只需要完成 1 次握手,就可以保持長期連接,在后續的消息互發階段是不需要用到 HTTP 協議的

其實消息互發階段也是可以對客戶端身份進行校驗的,這是因為客戶端所獲取的消息是有服務器端主動推動的

如果服務器端不主動推送,那么客戶端就無法獲取信息

可以在服務器端新增一個邏輯:握手結束后客戶端發送特定的消息,服務器端對該消息進行校驗,校驗通過則將服務器端的數據推送給客戶端,否則不做處理

如果我們將客戶端發送的新消息修改為數據倉庫中沒有的鍵,那么服務器端就不會給客戶端推送消息


爬取思路

這里以萊特幣官網 http://www.laiteb.com/ 實時數據為例。WebSocket 的握手只發生一次,所以如果需要通過瀏覽器開發者工具觀察網絡請求,則需要在打開頁面的情況下,打開瀏覽器開發者工具,定位到 NewWork 選項卡,並輸入或刷新當前頁面,才能觀察到 WebSocket 的握手請求和數據傳輸情況。這里以 Chrome 瀏覽器為例

在開發者工具中提供了篩選功能,其中 WS 選項代表只顯示 WebSocket 連接的網絡請求。

這時候可以看到請求記錄列表中有一條名為 realTime 的記錄,鼠標左鍵點擊它后,開發者工具會分為左右兩欄,右側列出本條請求記錄的詳細信息:

與 HTTP 請求不同的是,WebSocket 連接地址以 ws 或 wss 開頭。連接成功的狀態碼不是 200,而是 101。

Headers 標簽頁記錄的是 Request 和 Response 信息,而 Frames 標簽頁中記錄的則是雙方互傳的數據,也是我們需要爬取的數據內容:

Frames 圖中綠色箭頭向上的數據是客戶端發送給服務端的數據,橙色箭頭向下的數據是服務端推送給客戶端的數據。

從數據順序中可以看到,客戶端先發送:

{"action":"subscribe","args":["QuoteBin5m:14"]}

然后服務端才會推送信息(一直推送):

{"group":"QuoteBin5m:14","data":[{"low":"55.42","high":"55.63","open":"55.42","close":"55.59","last_price":"55.59","avg_price":"55.5111587372932781077","volume":"40078","timestamp":1551941701,"rise_fall_rate":"0.0030674846625766871","rise_fall_value":"0.17","base_coin_volume":"400.78","quote_coin_volume":"22247.7621987324"}]}

所以,我們知道了從發起握手到獲得數據的流程

那么,現在問題來了:

  • 握手怎么弄?
  • 連接保持怎么弄?
  • 消息發送和接收怎么弄?
  • 有什么庫可以輕松實現嗎?

aiowebsocket

Python 庫中用於連接 WebSocket 的有很多,但是易用、穩定的有 websocket-client(非異步)、websockets(異步)、aiowebsocket(異步)。

可以根據項目需求選擇三者之一,今天介紹的是異步 WebSocket 連接客戶端 aiowebsocket

github 地址:https://github.com/asyncins/aiowebsocket

ReadMe中介紹到:

AioWebSocket是一個遵循 WebSocket 規范的 異步 WebSocket 客戶端,相對於其他庫它更輕、更快。

它的安裝和其他庫一樣簡單,使用 pip install aiowebsocket 即可。安裝好后,我們可以根據 ReadMe 中提供的示例代碼來測試

import asyncio
import logging
from datetime import datetime
from aiowebsocket.converses import AioWebSocket


async def startup(uri):
    async with AioWebSocket(uri) as aws:
        converse = aws.manipulator
        message = b'AioWebSocket - Async WebSocket Client'
        while True:
            await converse.send(message)
            print('{time}-Client send: {message}'
                  .format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), message=message))
            mes = await converse.receive()
            print('{time}-Client receive: {rec}'
                  .format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), rec=mes))


if __name__ == '__main__':
    remote = 'wss://echo.websocket.org'
    # remote = 'ws://echo.websocket.org'
    try:
        asyncio.get_event_loop().run_until_complete(startup(remote))
    except KeyboardInterrupt as exc:
        logging.info('Quit.')

可以打印出 send :客戶端向服務端發送的消息 recive 服務端向客戶端推送的消息


從剛才的網絡請求記錄中,我們得知目標網站的 WebSocket 地址為:

wss://api.bbxapp.vip/v1/ifcontract/realTime,從地址中可以看出目標網站使用的是 wss,也就是 ws 的安全版,它們的關系跟 HTTP/HTTPS 一樣。

aiowebsocket 會自動處理並識別 ssl,所以我們並不需要作額外的操作,只需要將目標地址賦值給連接 uri 即可

import asyncio
import logging
from datetime import datetime
from aiowebsocket.converses import AioWebSocket


async def startup(uri):
    async with AioWebSocket(uri) as aws:
        converse = aws.manipulator
        message = b'{"action":"subscribe","args":["QuoteBin5m:14"]}'

        while True:
            await converse.send(message)
            # print('{time}-Client send: {message}'
            #       .format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), message=message))
            mes = await converse.receive()
            print('{time}-Client receive: {rec}'
                  .format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), rec=mes))


if __name__ == '__main__':
    remote = 'wss://api.bbxapp.vip/v1/ifcontract/realTime'
    # remote = 'ws://echo.websocket.org'
    try:
        asyncio.get_event_loop().run_until_complete(startup(remote))
    except KeyboardInterrupt as exc:
        logging.info('Quit.')

代碼不長,使用的時候只需要將目標網站 WebSocket 地址填入,然后按照流程發送數據即可,那么 aiowebsocket 在這個過程中做了什么呢?

  • 首先,aiowebsocket 根據 WebSocket 地址,向指定的服務端發送握手請求,並校驗握手結果。

  • 然后,在確認握手成功后,將數據發送給服務端。

  • 整個過程中為了保持連接不斷開,aiowebsocket 會自動與服務端響應 ping pong。

  • 最后,aiowebsocket 讀取服務端推送的消息

    參考鏈接:https://blog.csdn.net/sinat_38682860/article/details/88534192


WebSocket Ping 反爬蟲

通過剛才我們知道,WebSocket 是可以保持長期連接的,但是服務器端不可能保持所有客戶端永久連接這太耗費資源了,

有沒有一種方法可以檢查客戶端的狀態呢?

WebSocket 協議規范中約定,服務器端可以向客戶端發送 Ping 幀,當客戶端收到 Ping 幀時應當回復 Pong 幀

如果客戶端不回復或者回復的並不是 Pong 幀,那么服務器端就可以人為客戶端異常,主動關閉該連接

  通常,Ping 幀和 Pong 幀的 Plyload Data 中是沒有內容的,所以只要目標服務器發送 Ping 幀時,客戶端回復沒有任何內容的 Pong 幀即可


總結

信息校驗主要解決了客戶端身份鑒別、數據來源判斷和請求的合法性判斷等問題,避免數據接收者使用被篡改過得數據,保證數據的有效性

無論是 HTTP 協議還是 WebSocket 協議,都需要對客戶端身份進行鑒別,信息校驗無疑是最合適的方法

WebSocket 反爬蟲的產生跟協議規范有很大的關聯,由於協議中的一些規范並不是強制實現的,所以開發者可以在服務器端與客戶端握手和消息互傳的過程叫做驗證


免責聲明!

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



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