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中使用poplib模块收取邮件的教程
Apr 29 Python
Saltstack快速入门简单汇总
Mar 01 Python
浅谈Python 中整型对象的存储问题
May 16 Python
浅谈Series和DataFrame中的sort_index方法
Jun 07 Python
tensorflow 恢复指定层与不同层指定不同学习率的方法
Jul 26 Python
Python实现的各种常见分布算法示例
Dec 13 Python
python实现公司年会抽奖程序
Jan 22 Python
把pandas转换int型为str型的方法
Jan 29 Python
Python如何处理大数据?3个技巧效率提升攻略(推荐)
Apr 15 Python
django-filter和普通查询的例子
Aug 12 Python
wxPython实现列表增删改查功能
Nov 19 Python
python图像处理 PIL Image操作实例
Apr 09 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中用数组的方法设置cookies
2011/04/21 PHP
PHP中全面阻止SQL注入式攻击分析小结
2012/01/30 PHP
2014过年倒计时示例
2014/01/31 PHP
护卫神php套件 php版本升级方法(php5.5.24)
2015/05/10 PHP
在Javascript中为String对象添加trim,ltrim,rtrim方法
2006/09/22 Javascript
JQuery 确定css方框模型(盒模型Box Model)
2010/01/22 Javascript
当前页禁止复制粘贴截屏代码小集
2013/07/24 Javascript
原生javascript模仿win8等待提示圆圈进度条
2014/04/24 Javascript
轻松创建nodejs服务器(10):处理POST请求
2014/12/18 NodeJs
jQuery制作简单柱状图实例
2015/01/28 Javascript
基于jQuery实现照片墙自动播放特效
2017/01/12 Javascript
使用vue-cli脚手架工具搭建vue-webpack项目
2019/01/14 Javascript
如何从零开始手写Koa2框架
2019/03/22 Javascript
jQuery实现带3D切割效果的轮播图功能示例【附源码下载】
2019/04/04 jQuery
基于Vue SEO的四种方案(小结)
2019/07/01 Javascript
jQuery实现鼠标移入显示蒙版效果
2020/01/11 jQuery
JS实现网页端猜数字小游戏
2020/03/06 Javascript
js实现飞机大战游戏
2020/08/26 Javascript
python通过ElementTree操作XML获取结点读取属性美化XML
2013/12/02 Python
浅谈python为什么不需要三目运算符和switch
2016/06/17 Python
Python中正则表达式详解
2017/05/17 Python
python 换位密码算法的实例详解
2017/07/19 Python
Flask web开发处理POST请求实现(登录案例)
2018/07/26 Python
Django页面数据的缓存与使用的具体方法
2019/04/23 Python
详解Matplotlib绘图之属性设置
2019/08/23 Python
python django中8000端口被占用的解决
2019/12/17 Python
Python文本处理简单易懂方法解析
2019/12/19 Python
爱尔兰家电数码商城:Currys PC World爱尔兰
2016/07/23 全球购物
俄罗斯建筑和装饰材料在线商店:Stroilandia
2020/07/25 全球购物
Linux管理员面试经常问道的相关命令
2014/12/12 面试题
重大事项社会稳定风险评估方案
2014/06/15 职场文书
2014年小学教师工作自我评价
2014/09/22 职场文书
2014年学校法制宣传日活动总结
2014/11/01 职场文书
研究生简历自我评
2015/03/11 职场文书
2016年“我们的节日·中秋节”活动总结
2016/04/05 职场文书
html原生table实现合并单元格以及合并表头的示例代码
2023/05/07 HTML / CSS