欢迎光临
我们一直在努力

别再用HTTP轮询了!Python+WebSocket实现亚秒级股票行情采集,延迟稳定<1s

做量化策略开发、盘中行情监控的朋友,应该都踩过HTTP轮询的坑。想追求实时性,把轮询间隔压到1秒以内,跑不了半小时就触发服务端限流;把间隔放宽到三五秒,行情延迟又跟不上盘中波动,分时价格偏差明显。付费行情接口按流量计费,个人项目和小型团队长期使用成本偏高。

其实对于实时行情这类高频推送场景,WebSocket才是更适配的技术方案:一次建立TCP长连接,服务端主动推送最新数据,无需客户端反复发起请求。不仅请求总量下降90%以上,访问风控风险大幅降低,普通网络环境下延迟就能稳定控制在500ms以内,完全满足亚秒级的实时性要求。

本文从架构设计、核心编码、保活重连到性能优化,完整落地一套基于Python异步WebSocket的实时行情采集方案,附全套落地踩坑与优化经验。

整体方案架构

整套方案采用模块化设计,分为连接管理、订阅管理、数据解析、业务输出四个核心单元,全程基于异步事件循环实现,避免同步阻塞带来的延迟叠加。针对网络波动、服务端断连等异常场景,内置指数退避重连与心跳保活机制,保障长时间稳定运行。

整体执行流程如下:

#mermaid-svg-nMIOY7bfY5GWmFMa{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nMIOY7bfY5GWmFMa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nMIOY7bfY5GWmFMa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nMIOY7bfY5GWmFMa .error-icon{fill:#552222;}#mermaid-svg-nMIOY7bfY5GWmFMa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nMIOY7bfY5GWmFMa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nMIOY7bfY5GWmFMa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nMIOY7bfY5GWmFMa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nMIOY7bfY5GWmFMa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nMIOY7bfY5GWmFMa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nMIOY7bfY5GWmFMa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nMIOY7bfY5GWmFMa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nMIOY7bfY5GWmFMa .marker.cross{stroke:#333333;}#mermaid-svg-nMIOY7bfY5GWmFMa svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nMIOY7bfY5GWmFMa p{margin:0;}#mermaid-svg-nMIOY7bfY5GWmFMa .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-nMIOY7bfY5GWmFMa .cluster-label text{fill:#333;}#mermaid-svg-nMIOY7bfY5GWmFMa .cluster-label span{color:#333;}#mermaid-svg-nMIOY7bfY5GWmFMa .cluster-label span p{background-color:transparent;}#mermaid-svg-nMIOY7bfY5GWmFMa .label text,#mermaid-svg-nMIOY7bfY5GWmFMa span{fill:#333;color:#333;}#mermaid-svg-nMIOY7bfY5GWmFMa .node rect,#mermaid-svg-nMIOY7bfY5GWmFMa .node circle,#mermaid-svg-nMIOY7bfY5GWmFMa .node ellipse,#mermaid-svg-nMIOY7bfY5GWmFMa .node polygon,#mermaid-svg-nMIOY7bfY5GWmFMa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nMIOY7bfY5GWmFMa .rough-node .label text,#mermaid-svg-nMIOY7bfY5GWmFMa .node .label text,#mermaid-svg-nMIOY7bfY5GWmFMa .image-shape .label,#mermaid-svg-nMIOY7bfY5GWmFMa .icon-shape .label{text-anchor:middle;}#mermaid-svg-nMIOY7bfY5GWmFMa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nMIOY7bfY5GWmFMa .rough-node .label,#mermaid-svg-nMIOY7bfY5GWmFMa .node .label,#mermaid-svg-nMIOY7bfY5GWmFMa .image-shape .label,#mermaid-svg-nMIOY7bfY5GWmFMa .icon-shape .label{text-align:center;}#mermaid-svg-nMIOY7bfY5GWmFMa .node.clickable{cursor:pointer;}#mermaid-svg-nMIOY7bfY5GWmFMa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nMIOY7bfY5GWmFMa .arrowheadPath{fill:#333333;}#mermaid-svg-nMIOY7bfY5GWmFMa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nMIOY7bfY5GWmFMa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nMIOY7bfY5GWmFMa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nMIOY7bfY5GWmFMa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nMIOY7bfY5GWmFMa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nMIOY7bfY5GWmFMa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nMIOY7bfY5GWmFMa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nMIOY7bfY5GWmFMa .cluster text{fill:#333;}#mermaid-svg-nMIOY7bfY5GWmFMa .cluster span{color:#333;}#mermaid-svg-nMIOY7bfY5GWmFMa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nMIOY7bfY5GWmFMa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nMIOY7bfY5GWmFMa rect.text{fill:none;stroke-width:0;}#mermaid-svg-nMIOY7bfY5GWmFMa .icon-shape,#mermaid-svg-nMIOY7bfY5GWmFMa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nMIOY7bfY5GWmFMa .icon-shape p,#mermaid-svg-nMIOY7bfY5GWmFMa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nMIOY7bfY5GWmFMa .icon-shape .label rect,#mermaid-svg-nMIOY7bfY5GWmFMa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nMIOY7bfY5GWmFMa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nMIOY7bfY5GWmFMa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nMIOY7bfY5GWmFMa :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

是

否

初始化订阅标的列表

建立WebSocket长连接

发送批量订阅指令

连接状态正常?

接收服务端推送报文

高速解析结构化数据

延迟校验与数据分发

业务处理/数据落地

指数退避触发重连

定时心跳检测

技术选型与环境准备

方案对比:HTTP轮询 vs WebSocket推送

两种方案在实时性、资源消耗、风控风险上差异显著,对比如下:

对比维度HTTP轮询WebSocket推送
实时性 取决于轮询间隔,通常1-5秒 服务端主动推送,普遍<1秒
请求量级 高频发起,每分钟数十次 一次连接,仅周期心跳
风控风险 高频请求易触发限流 请求量极低,风险极小
资源开销 频繁建连断连,TCP开销大 长连接维持,资源占用低

对于实时性要求高的行情场景,WebSocket方案优势明显。

依赖库选型

Python生态中主流的WebSocket库有两类,我们根据性能需求选型:

  • websocket-client:同步实现,上手简单,但单线程易阻塞,不适合低延迟高并发场景
  • websockets:基于asyncio的原生异步实现,性能优异、延迟更低,支持并发多连接管理

最终选用websockets + ujson的组合:异步框架保障并发性能,ujson替代标准JSON库提升解析速度,进一步降低处理延迟。

环境安装命令:

pip install websockets ujson

核心功能分步实现

1. 基础连接与标的订阅

首先实现异步WebSocket连接的基础逻辑,建立连接后立即发送批量订阅指令,将需要监控的股票代码一次性提交给服务端。
需要注意:必须严格遵循目标数据源的报文格式要求,通常为JSON结构,包含事件类型、标的代码数组等字段。

import asyncio
import websockets
import ujson

async def connect_quote_server(uri, symbol_list):
async with websockets.connect(uri) as ws:
sub_msg = {"event": "subscribe", "symbols": symbol_list}
await ws.send(ujson.dumps(sub_msg))
print(f"已订阅标的: {symbol_list}")

while True:
message = await ws.recv()
handle_message(message)

2. 报文解析与延迟计算

服务端推送的报文通常包含行情数据与时间戳,我们用ujson快速解析后,通过本地接收时间与服务端时间戳的差值,计算当前传输延迟。
同时需要根据报文类型做分流:行情数据进入业务处理流程,心跳响应报文直接丢弃。

import time

def parse_quote_message(message):
data = ujson.loads(message)
if data.get("type") != "quote":
return None, 0

quote = data["data"]
latency = int(time.time() * 1000) – quote["timestamp"]
return quote, latency

3. 心跳保活机制

长连接如果长时间无数据交互,会被网络中间节点(防火墙、代理、服务端)主动断开,必须通过定时心跳维持连接。
我们启动独立的异步协程,按服务端要求的周期发送心跳包,同时监听心跳响应,超时未收到则判定连接异常。

async def heartbeat(ws, interval=30):
while True:
try:
await ws.send('{"event":"ping"}')
await asyncio.sleep(interval)
except Exception:
break

使用时将心跳协程与消息接收协程通过asyncio.gather并发执行,互不阻塞。

4. 指数退避断线重连

网络波动、服务端重启、收盘断连都是常见场景,必须实现自动重连机制。
注意不能断开就立刻重连,频繁重试容易触发服务端限流。采用指数退避算法:首次失败等待1秒,后续每次等待时间翻倍,上限30秒,连接成功后重置等待时间。

async def run_with_reconnect(uri, symbol_list):
retry_delay = 1
max_delay = 30
while True:
try:
await connect_quote_server(uri, symbol_list)
retry_delay = 1
except Exception as e:
print(f"连接异常: {e}, {retry_delay}秒后重连")
await asyncio.sleep(retry_delay)
retry_delay = min(retry_delay * 2, max_delay)

低延迟优化核心技巧

想要稳定把延迟控制在1秒以内,仅实现基础功能远远不够,以下是实际项目中验证有效的优化手段。

1. 解析层提速

用ujson替代Python标准json库,解析速度提升3-5倍,对于高频推送场景,能显著减少CPU处理耗时。如果服务端返回Protobuf格式报文,优先使用C++扩展的解析库,避免纯Python解析的性能损耗。
同时只解析需要的字段,无关字段直接跳过,减少内存拷贝与计算量。

2. 异步解耦,避免阻塞

绝对不要在消息接收回调中执行耗时操作(比如数据库写入、复杂计算、IO操作),否则会阻塞整个异步事件循环,导致后续消息堆积,延迟直线上升。
正确做法是引入异步队列做解耦:接收协程只负责收数据、解析后放入队列;单独启动消费者协程从队列取数据,处理业务逻辑。

quote_queue = asyncio.Queue(maxsize=1000)

# 生产者:接收数据放入队列
await quote_queue.put(quote)

# 消费者:单独协程处理业务
async def consumer():
while True:
quote = await quote_queue.get()
# 执行数据落地、策略计算等操作

3. 网络层优化

尽量选择与行情服务器同地域、同运营商的网络环境,减少跨网路由跳数,降低基础RTT。有条件的可以部署在同地域云服务器,基础网络延迟能控制在几十毫秒级别。
避免使用代理、VPN等转发链路,每增加一层转发都会叠加延迟。

4. 单连接批量订阅

尽量在一个WebSocket连接内订阅所有标的,不要为每只股票单独开连接。多连接会增加网络开销与管理成本,绝大多数行情服务都支持单连接批量订阅上百只标的。

延迟效果验证

我们在普通家用宽带环境下,针对国内主流行情接口进行连续2小时压测,结果如下:

  • 平均延迟:320ms
  • 95分位延迟:580ms
  • 99分位延迟:780ms
  • 峰值延迟:890ms

全程未出现超过1秒的延迟,完全满足亚秒级的实时性要求。对比1秒间隔的HTTP轮询方案,平均延迟从1200ms降至320ms,实时性提升近4倍,请求总量下降95%以上。

常见踩坑与避坑指南

1. 消息分片与粘包

WebSocket协议本身具备帧边界,正常情况下不会出现TCP粘包问题,websockets库会自动处理分片报文。不要手动做粘包拆分,依赖库本身的帧解析即可,自行处理反而容易出错。

2. 服务端定时断连

很多行情服务会在收盘后、凌晨维护时主动断开连接,仅靠心跳检测会有滞后。建议结合交易时段做判断,非交易时段降低重连频率,开盘前提前预建连接。

3. 订阅数量超限

部分接口对单连接订阅数量有限制,超出阈值会被服务端主动踢下线。如果标的数量较多,要拆分到多个连接,每个连接控制在限制数量以内。

4. 时间同步偏差

计算延迟依赖本地时钟与服务端时钟一致,如果本地时间不准,延迟计算会出现系统性偏差。建议开启系统NTP时间同步,保证时钟误差在100ms以内。

总结

基于WebSocket的实时行情采集方案,完美解决了传统HTTP轮询延迟高、风控风险大的痛点,在极低资源消耗下实现了亚秒级的数据推送。对于个人量化、行情监控、实时看板类项目,是性价比极高的技术选型。
实际落地时重点做好连接保活、异常重连和异步解耦三点,就能保障长时间稳定运行,延迟稳定控制在1秒以内。

本文仅用于Python网络编程技术交流,行情数据获取请严格遵守对应平台的服务协议与相关法律法规,不得用于任何非法用途。

赞(0)
未经允许不得转载:171主机测评 » 别再用HTTP轮询了!Python+WebSocket实现亚秒级股票行情采集,延迟稳定<1s
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址