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 生成不重复的随机数的代码
May 15 Python
基于循环神经网络(RNN)实现影评情感分类
Mar 26 Python
python实现简单flappy bird
Dec 24 Python
python 检查是否为中文字符串的方法
Dec 28 Python
wxpython实现按钮切换界面的方法
Nov 19 Python
python实现简单日志记录库glog的使用
Dec 13 Python
python爬虫模块URL管理器模块用法解析
Feb 03 Python
TensorFlow加载模型时出错的解决方式
Feb 06 Python
pycharm激活码快速激活及使用步骤
Mar 12 Python
TensorFlow使用Graph的基本操作的实现
Apr 22 Python
TensorFlow keras卷积神经网络 添加L2正则化方式
May 22 Python
Python基于wordcloud及jieba实现中国地图词云图
Jun 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
咖啡店都有些什么常规豆子呢?有什么风味在里面
2021/03/04 咖啡文化
Session的工作方式
2006/10/09 PHP
php SQL之where语句生成器
2009/03/24 PHP
计算一段日期内的周末天数的php代码(星期六,星期日总和)
2009/11/12 PHP
『PHP』PHP截断函数mb_substr()使用介绍
2013/04/22 PHP
php获取当前时间的毫秒数的方法
2014/01/26 PHP
php实现简单爬虫的开发
2016/03/28 PHP
php使用正则表达式获取字符串中的URL
2016/12/29 PHP
获取任意Html元素与body之间的偏移距离 offsetTop、offsetLeft (For:IE5+ FF1 )[
2006/12/22 Javascript
json 入门基础教程 推荐
2009/10/31 Javascript
初识javascript 文档碎片
2010/07/13 Javascript
用Javascript评估用户输入密码的强度实现代码
2011/11/30 Javascript
基于jquery的放大镜效果
2012/05/30 Javascript
jQuery 属性选择器element[herf*='value']使用示例
2013/10/20 Javascript
jquery删除提示框弹出是否删除对话框
2014/01/07 Javascript
动态加载js、css等文件跨iframe实现
2014/02/24 Javascript
搭建pomelo 开发环境
2014/06/24 Javascript
javascript封装的sqlite操作类实例
2015/07/17 Javascript
Javascript基础教程之比较null和undefined值
2016/05/16 Javascript
Bootstrap实现提示框和弹出框效果
2017/01/11 Javascript
BOM之navigator对象和用户代理检测
2017/02/10 Javascript
vue实现后台管理权限系统及顶栏三级菜单显示功能
2019/06/19 Javascript
微信小程序解析富文本过程详解
2019/07/13 Javascript
vue使用代理解决请求跨域问题详解
2019/07/24 Javascript
js正则表达式简单校验方法
2021/01/03 Javascript
Python判断字符串与大小写转换
2015/06/08 Python
Python合并同一个文件夹下所有PDF文件的方法
2019/03/11 Python
Python:Numpy 求平均向量的实例
2019/06/29 Python
Python编程快速上手——Excel到CSV的转换程序案例分析
2020/02/28 Python
Python tcp传输代码实例解析
2020/03/18 Python
英国马匹装备和马术用品购物网站:Equine Superstore
2019/03/03 全球购物
日本航空官方网站:JAL
2019/06/19 全球购物
德国2018年度最佳在线药房:Bodfeld Apotheke
2019/11/04 全球购物
松材线虫病防治方案
2014/06/15 职场文书
Redis RDB技术底层原理详解
2021/09/04 Redis
Go gorilla/sessions库安装使用
2022/08/14 Golang