Python如何爬取实时变化的WebSocket数据的方法


Posted in Python onMarch 09, 2019

一、前言

作为一名爬虫工程师,在工作中常常会遇到爬取实时数据的需求,比如体育赛事实时数据、股市实时数据或币圈实时变化的数据。如下图:

Python如何爬取实时变化的WebSocket数据的方法

Python如何爬取实时变化的WebSocket数据的方法

Python如何爬取实时变化的WebSocket数据的方法

Web 领域中,用于实现数据'实时'更新的手段有轮询和 WebSocket 这两种。轮询指的是客户端按照一定时间间隔(如 1 秒)访问服务端接口,从而达到 '实时' 的效果,虽然看起来数据像是实时更新的,但实际上它有一定的时间间隔,并不是真正的实时更新。轮询通常采用 拉 模式,由客户端主动从服务端拉取数据。

WebSocket 采用的是 推 模式,由服务端主动将数据推送给客户端,这种方式是真正的实时更新。

二、什么是 WebSocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议。它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket 优点

  • 较少的控制开销:只需要进行一次握手,携带一次请求头信息即可,后续只传输数据即可,相比 HTTP 每次请求都携带请求头,WebSocket 非常省资源。
  • 更强的实时性:由于服务器可以主动推送消息,这使得延迟变得可以忽略不计,相比 HTTP 轮询的时间间隔,WebSocket 可以在相同的时间内进行多次传输。
  • 二进制支持:WebSocket 支持二进制帧,这意味着传输更节省。
  • ……

爬虫面对 HTTP 和 WebSocket

Python 中的网络请求库非常多,Requests 是最常用的请求库之一,它可以模拟发送网络请求。但是这些请求都是基于 HTTP 协议的。在面对 WebSocket 的时候 Requests 就发挥不料作用了,必须使用能够连接 WebSocket 的库。

三、爬取思路

这里以莱特币官网 http://www.laiteb.com/ 实时数据为例。WebSocket 的握手只发生一次,所以如果需要通过浏览器开发者工具观察网络请求,则需要在打开页面的情况下,打开浏览器开发者工具,定位到 NewWork 选项卡,并输入或刷新当前页面,才能观察到 WebSocket 的握手请求和数据传输情况。这里以 Chrome 浏览器为例:

Python如何爬取实时变化的WebSocket数据的方法

在开发者工具中提供了筛选功能,其中 WS 选项代表只显示 WebSocket 连接的网络请求。

这时候可以看到请求记录列表中有一条名为 realTime 的记录,鼠标左键点击它后,开发者工具会分为左右两栏,右侧列出本条请求记录的详细信息:

Python如何爬取实时变化的WebSocket数据的方法

与 HTTP 请求不同的是,WebSocket 连接地址以 ws 或 wss 开头。连接成功的状态码不是 200,而是 101。

Headers 标签页记录的是 Request 和 Response 信息,而 Frames 标签页中记录的则是双方互传的数据,也是我们需要爬取的数据内容:

Python如何爬取实时变化的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"}]}

所以,从发起握手到获得数据的整个流程为:

Python如何爬取实时变化的WebSocket数据的方法

那么,现在问题来了:

  • 握手怎么弄?
  • 连接保持怎么弄?
  • 消息发送和接收怎么弄?
  • 有什么库可以轻松实现吗?

四、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 表示服务端向客户端推送的消息

五、编码获取数据

回到这一次的爬取需求,目标网站是莱特币官网:

Python如何爬取实时变化的WebSocket数据的方法

从刚才的网络请求记录中,我们得知目标网站的 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.')

运行代码后观察输出,你会发现什么都没有发生。既没有内容输出,也没有断开连接,程序一直在运行,但是什么都没有:

Python如何爬取实时变化的WebSocket数据的方法

这是为什么呢?

是对方不接受我方的请求吗?

还是有什么反爬虫限制呢?

实际上,刚才的流程图可以解释这个问题:

Python如何爬取实时变化的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.')

保存后运行,就会看到数据源源不断的推送过来:

Python如何爬取实时变化的WebSocket数据的方法

到这里,爬虫就能够获取到想要的数据了。

aiowebsocket 做了什么

代码不长,使用的时候只需要将目标网站 WebSocket 地址填入,然后按照流程发送数据即可,那么 aiowebsocket 在这个过程中做了什么呢?

  • 首先,aiowebsocket 根据 WebSocket 地址,向指定的服务端发送握手请求,并校验握手结果。
  • 然后,在确认握手成功后,将数据发送给服务端。
  • 整个过程中为了保持连接不断开,aiowebsocket 会自动与服务端响应 ping pong。
  • 最后,aiowebsocket 读取服务端推送的消息

【奎因:】如果你认为 aiowebsocket 帮助了你,那么请你到 Github https://github.com/asyncins/aiowebsocket 上给一个 Star。如果在使用当中发现问题或者希望给 aiowebsocket 提建议,那么也可以到 Github 上提出。只要你提出建议,就一定能够帮助 aiowebsocket 变的更好,而 aiowebsocket 也能够继续为你服务。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python fileinput模块使用实例
May 28 Python
python学习必备知识汇总
Sep 08 Python
Python学习pygal绘制线图代码分享
Dec 09 Python
Python将DataFrame的某一列作为index的方法
Apr 08 Python
Python实现的质因式分解算法示例
May 03 Python
Python线程同步的实现代码
Oct 03 Python
python分批定量读取文件内容,输出到不同文件中的方法
Dec 08 Python
对python中list的拷贝与numpy的array的拷贝详解
Jan 29 Python
Python3利用print输出带颜色的彩色字体示例代码
Apr 08 Python
Django admin禁用编辑链接和添加删除操作详解
Nov 15 Python
tensorflow -gpu安装方法(不用自己装cuda,cdnn)
Jan 20 Python
基于Python绘制个人足迹地图
Jun 01 Python
浅谈python的深浅拷贝以及fromkeys的用法
Mar 08 #Python
Python高级特性与几种函数的讲解
Mar 08 #Python
Python I/O与进程的详细讲解
Mar 08 #Python
举例讲解Python常用模块
Mar 08 #Python
python re库的正则表达式入门学习教程
Mar 08 #Python
opencv与numpy的图像基本操作
Mar 08 #Python
Python脚本修改阿里云的访问控制列表的方法
Mar 08 #Python
You might like
php cout<<的一点看法
2010/01/24 PHP
PHP之APC缓存详细介绍 apc模块安装
2014/01/13 PHP
PHP中使用Session配合Javascript实现文件上传进度条功能
2014/10/15 PHP
一个非常实用的php文件上传类
2017/07/04 PHP
JQuery与Ajax调用新浪API获取短网址的代码
2014/02/07 Javascript
怎么判断js脚本加载完成
2014/02/28 Javascript
浅谈JavaScript中的作用域和闭包问题
2015/07/07 Javascript
JavaScript事件学习小结(五)js中事件类型之鼠标事件
2016/06/09 Javascript
JavaScript数据结构之二叉树的遍历算法示例
2017/04/13 Javascript
原生JavaScript来实现对dom元素class的操作方法(推荐)
2017/08/16 Javascript
详解extract-text-webpack-plugin 的使用及安装
2018/06/12 Javascript
详解vue项目中实现图片裁剪功能
2019/06/07 Javascript
javascript面向对象三大特征之封装实例详解
2019/07/24 Javascript
javascript实现点亮灯泡特效示例
2019/10/15 Javascript
Openlayers测量距离与面积的实现方法
2020/09/25 Javascript
[06:14]《辉夜杯》外卡赛附加赛 4支战队巡礼
2015/10/23 DOTA
[38:27]完美世界DOTA2联赛PWL S2 Forest vs FTD.C 第二场 11.26
2020/11/30 DOTA
python模拟登陆阿里妈妈生成商品推广链接
2014/04/03 Python
玩转python爬虫之爬取糗事百科段子
2016/02/17 Python
在Python程序和Flask框架中使用SQLAlchemy的教程
2016/06/06 Python
谈谈如何手动释放Python的内存
2016/12/17 Python
tensorflow 打印内存中的变量方法
2018/07/30 Python
在python中pandas读文件,有中文字符的方法
2018/12/12 Python
python将list转为matrix的方法
2018/12/12 Python
djano一对一、多对多、分页实例代码
2019/08/16 Python
布鲁明戴尔百货店:Bloomingdale’s
2016/12/21 全球购物
西班牙著名的珠宝首饰品牌:P D PAOLA
2018/09/15 全球购物
德国W家官网,可直邮中国的母婴商城:Windeln.de
2021/03/03 全球购物
递归实现回文判断(如:abcdedbca就是回文,判断一个面试者对递归理解的简单程序)
2013/04/28 面试题
火锅店营销方案
2014/02/26 职场文书
交通事故一次性赔偿协议书范本
2014/11/02 职场文书
法定代表人资格证明书
2015/06/18 职场文书
读书笔记格式
2015/07/02 职场文书
2016年优秀共产党员先进事迹材料
2016/02/29 职场文书
MySQL RC事务隔离的实现
2022/03/31 MySQL
动作冒险《Hell Is Us》将采用虚幻5 消灭怪物探索王国
2022/04/13 其他游戏