Python如何爬取實時變化的WebSocket數據

1、前言

做爲一名爬蟲工程師,在工做中經常會遇到爬取實時數據的需求,好比體育賽事實時數據、股市實時數據或幣圈實時變化的數據。以下圖:git

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

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

2、什麼是 WebSocket

WebSocket是一種在單個TCP鏈接上進行全雙工通訊的協議。它使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。api

WebSocket 優勢

  • 較少的控制開銷:只須要進行一次握手,攜帶一次請求頭信息便可,後續只傳輸數據便可,相比 HTTP 每次請求都攜帶請求頭,WebSocket 很是省資源。
  • 更強的實時性:因爲服務器能夠主動推送消息,這使得延遲變得能夠忽略不計,相比 HTTP 輪詢的時間間隔,WebSocket 能夠在相同的時間內進行屢次傳輸。
  • 二進制支持:WebSocket 支持二進制幀,這意味着傳輸更節省。
  • ……

爬蟲面對 HTTP 和 WebSocket

Python 中的網絡請求庫很是多,Requests 是最經常使用的請求庫之一,它能夠模擬發送網絡請求。可是這些請求都是基於 HTTP 協議的。在面對 WebSocket 的時候 Requests 就發揮不料做用了,必須使用可以鏈接 WebSocket 的庫。瀏覽器

3、爬取思路

這裏以萊特幣官網 http://www.laiteb.com/ 實時數據爲例。WebSocket 的握手只發生一次,因此若是須要經過瀏覽器開發者工具觀察網絡請求,則須要在打開頁面的狀況下,打開瀏覽器開發者工具,定位到 NewWork 選項卡,並輸入或刷新當前頁面,才能觀察到 WebSocket 的握手請求和數據傳輸狀況。這裏以 Chrome 瀏覽器爲例: 安全

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

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

與 HTTP 請求不一樣的是,WebSocket 鏈接地址以 ws 或 wss 開頭。鏈接成功的狀態碼不是 200,而是 101。服務器

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

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"}]}

複製代碼

因此,從發起握手到得到數據的整個流程爲:

那麼,如今問題來了:

  • 握手怎麼弄?
  • 鏈接保持怎麼弄?
  • 消息發送和接收怎麼弄?
  • 有什麼庫能夠輕鬆實現嗎?

4、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 = 'ws://echo.websocket.org'
    try:
        asyncio.get_event_loop().run_until_complete(startup(remote))
    except KeyboardInterrupt as exc:
        logging.info('Quit.')
複製代碼

運行後的結果輸出爲:

2019-03-07 15:43:55-Client send: b'AioWebSocket - Async WebSocket Client'
2019-03-07 15:43:55-Client receive: b'AioWebSocket - Async WebSocket Client'
2019-03-07 15:43:55-Client send: b'AioWebSocket - Async WebSocket Client'
2019-03-07 15:43:56-Client receive: b'AioWebSocket - Async WebSocket Client'
2019-03-07 15:43:56-Client send: b'AioWebSocket - Async WebSocket Client'
……
複製代碼

send 表示客戶端向服務端發送的消息

recive 表示服務端向客戶端推送的消息

5、編碼獲取數據

回到這一次的爬取需求,目標網站是萊特幣官網:

從剛纔的網絡請求記錄中,咱們得知目標網站的 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
        while True:
            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'
    try:
        asyncio.get_event_loop().run_until_complete(startup(remote))
    except KeyboardInterrupt as exc:
        logging.info('Quit.')
複製代碼

運行代碼後觀察輸出,你會發現什麼都沒有發生。既沒有內容輸出,也沒有斷開鏈接,程序一直在運行,可是什麼都沒有:

這是爲何呢?

是對方不接受我方的請求嗎?

仍是有什麼反爬蟲限制呢?

實際上,剛纔的流程圖能夠解釋這個問題:

整個流程中有一步是須要客戶端給服務端發送指定的消息,服務端驗證後纔會不停推送數據。因此,應該在消息讀取前、握手鍊接後加上消息發送的代碼:

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
        # 客戶端給服務端發送消息
        await converse.send('{"action":"subscribe","args":["QuoteBin5m:14"]}')
        while True:
            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'
    try:
        asyncio.get_event_loop().run_until_complete(startup(remote))
    except KeyboardInterrupt as exc:
        logging.info('Quit.')

複製代碼

保存後運行,就會看到數據源源不斷的推送過來:

到這裏,爬蟲就可以獲取到想要的數據了。

aiowebsocket 作了什麼

代碼不長,使用的時候只須要將目標網站 WebSocket 地址填入,而後按照流程發送數據便可,那麼 aiowebsocket 在這個過程當中作了什麼呢?

  • 首先,aiowebsocket 根據 WebSocket 地址,向指定的服務端發送握手請求,並校驗握手結果。
  • 而後,在確認握手成功後,將數據發送給服務端。
  • 整個過程當中爲了保持鏈接不斷開,aiowebsocket 會自動與服務端響應 ping pong。
  • 最後,aiowebsocket 讀取服務端推送的消息

【奎因:】若是你認爲 aiowebsocket 幫助了你,那麼請你到 Github https://github.com/asyncins/aiowebsocket 上給一個 Star。若是在使用當中發現問題或者但願給 aiowebsocket 提建議,那麼也能夠到 Github 上提出。只要你提出建議,就必定可以幫助 aiowebsocket 變的更好,而 aiowebsocket 也可以繼續爲你服務。

相關文章
相關標籤/搜索