我们先看一下斗鱼直播的弹幕:
大家可以发现右下角在一直不断变化。
Web 领域中,用于实现数据’实时’更新的手段有轮询和 WebSocket 这两种。
轮询指的是客户端按照一定时间间隔(如 1 秒)访问服务端接口,从而达到 ‘实时’ 的效果,虽然看起来数据像是实时更新的,但实际上它有一定的时间间隔,并不是真正的实时更新。轮询通常采用 拉 模式,由客户端主动从服务端拉取数据。
而 WebSocket 采用的是 推 模式,由服务端主动将数据推送给客户端,这种方式是真正的实时更新。
WebSocket是一种在单个TCP连接上进行全双工通信的协议。
它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
较少的控制开销:只需要进行一次握手,携带一次请求头信息即可,后续只传输数据即可,相比 HTTP 每次请求都携带请求头,WebSocket 非常省资源。
更强的实时性:由于服务器可以主动推送消息,这使得延迟变得可以忽略不计,相比 HTTP 轮询的时间间隔,WebSocket 可以在相同的时间内进行多次传输。
二进制支持:WebSocket 支持二进制帧,这意味着传输更节省。
先以莱特币官网 http://www.laiteb.com/ 实时数据为例
WebSocket 的握手只发生一次,所以如果需要通过浏览器开发者工具观察网络请求,则需要在打开页面的情况下,打开浏览器开发者工具,定位到 NewWork 选项卡,并输入或刷新当前页面,才能观察到 WebSocket 的握手请求和数据传输情况。这里以 Chrome 浏览器为例:
在开发者工具中提供了筛选功能,其中 WS 选项代表只显示 WebSocket 连接的网络请求。
这时候可以看到请求记录列表中有一条名为 realTime 的记录,鼠标左键点击它后,开发者工具会分为左右两栏,右侧列出本条请求记录的详细信息:
与 HTTP 请求不同的是,WebSocket 连接地址以 ws 或 wss 开头。连接成功的状态码不是 200,而是 101。
Headers 标签页记录的是 Request 和 Response 信息,而 Frames 标签页中记录的则是双方互传的数据,也是我们需要爬取的数据内容:
Frames 图中绿色箭头向上的数据是客户端发送给服务端的数据,橙色箭头向下的数据是服务端推送给客户端的数据。
从数据顺序中可以看到,客户端先发送:
{"action":"ping"}
然后服务端才会推送信息(一直推送):
{"action":"subscribe","group":"QuoteBin5m:14","success":true,"request":{"action":"subscribe","args":["QuoteBin5m:14"]}}
所以,从发起握手到获得数据的整个流程为:
使用aiowebsocket库爬取莱特网数据:
Python 库中用于连接 WebSocket 的有很多,但是易用、稳定的有 websocket-client(非异步)、websockets(异步)、aiowebsocket(异步)。
可以根据项目需求选择三者之一,这里介绍的是异步 WebSocket 连接客户端 aiowebsocket。
AioWebSocket是一个遵循 WebSocket 规范的 异步 WebSocket 客户端,相对于其他库它更轻、更快。
下面是代码: (代码为什么这个格式我也不清楚,官方文档里面是这么提供的 =。=)
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.')
运行后: (可以看到数据已经不停的过来了)
我们再可以看下这个网站:(金十数据中心) https://datacenter.jin10.com/price
他的headers中Request Url是wss://开头的 右边正边疯狂的刷新数据,而使用的协议,正是 websocket
对于python下如何连接websocket ,网上有很多的文章,我这里就不用细说了,通常我们拿到这样的接口,都会本能去尝试直连看看,在进一步尝试之后,该端口的api会根据真实的请求变化 ,而且进一步的请求的cookie和key都会变化 ,看来直连的方式是行不通了,那没办法,只能走渲染的路了,selenium可以是可以,不过我们要尝试一下新的路线和方法,那就直接上chrome-headless
Headless Chrome指在headless模式下运行谷歌浏览器(以程序模式运行,没有界面),自从这玩意儿出来之后, phantomjs的作者就宣布不维护了。。。
直接使用docker 来安装chrome headless
docker run -d -p 9222:9222 --cap-add=SYS_ADMIN justinribeiro/chrome-headless
这样我们已经启用了一个chrome headless的服务,那如何使用呢,我们使用websocket 和chrome header less进行交互,不多说了,直接上代码吧
import json
import time
import requests
import websocket
request_id = 0
target_url = 'https://datacenter.jin10.com/price'
def get_websocket_connection():
r = requests.get('http://10.10.2.42:9222/json') #这是开启chrome headless的机器地址
if r.status_code != 200:
raise ValueError("can not get the api ,please check if docker is ready")
conn_api = r.json()[0].get('webSocketDebuggerUrl')
return websocket.create_connection(conn_api)
def run_command(conn, method, **kwargs):
global request_id
request_id += 1
command = {'method': method,
'id': request_id,
'params': kwargs}
conn.send(json.dumps(command))
#while True:
msg = json.loads(conn.recv())
if msg.get('id') == request_id:
return msg
def get_element():
conn = get_websocket_connection()
msg = run_command(conn, 'Page.navigate', url=target_url)
time.sleep(5)
js = "var p = document.querySelector('.jin-pricewall_list-item_b').innerText ; p ;"
for _ in range(20):
time.sleep(1)
msg = run_command(conn, 'Runtime.evaluate', expression=js)
print(msg.get('result')['result']['value'])
if __name__ == '__main__':
get_element()