📢 产品上线:今天,Infoway 实时财经新闻 API 正式上线。
即日起,开发者、量化团队与交易平台可以通过 WebSocket 订阅全球实时财经新闻推送,我们的新闻接口覆盖 11 种语言,每条新闻自带关联标的(symbols)、紧急度、去重键,并与实时行情共用同一套 API Key。
功能面向高级套餐(199 美金/月)及以上或等额自定义套餐开放。
一、财经新闻 API
市面上的财经新闻接口,大致可以分成两类:
第一类:资讯展示型(轮询查询)
面向内容网站、App 资讯页。你定时发 HTTP 请求拉最新几条,拿到标题、封面图、来源、网页链接,渲染成一个新闻列表给用户看,这种产品的返回字段结构基本大同小异:
id / ctime(发布时间) / title / source(来源) / picUrl(封面图) / url(网页链接) / description(描述)第二类:数据驱动型(实时推送)
面向量化策略、事件驱动交易、舆情风控系统。你需要的不只是简单的新闻卡片,而是一条能被程序消费的结构化事件流,除了新闻内容,返回的字段还需要告诉你:
- 这条新闻和哪些股票有关?
- 重要性如何?
- 是不是刚才那条的重复?
Infoway 的财经新闻 API 属于这一类,字段结构完全不同:
dk(去重键) / title / content(正文) / sd(摘要) / published(时间戳)
/ urgency(紧急度) / provider(来源) / symbols(关联标的) / country / lang / link下一趴我们会做更详细的差异对比。
二、差异一:延迟
轮询模式
资讯展示型接口都是 HTTP 请求-响应,需要你主动发起查询请求。如果需要接近实时,你只能缩短轮询间隔。
如果你每 60 秒拉一次,一条新闻平均要等 30 秒、最坏等 60 秒才被你发现,而且这还没算你为了「更实时」而多打的、90% 都返回重复数据的无效请求。想把延迟压到 5 秒,就得每 5 秒打一次,免费额度(通常是每天 50~100 次)几分钟就烧光了。
推送模型
WebSocket 长连接把逻辑反过来:你订阅一次,之后只要有新闻,服务端就主动把它推给你,中间没有轮询间隔这个天花板。
我们对 Infoway 财经新闻 API 做了真实测量:连接后分别订阅简体中文和英文频道,各持续 10 分钟记录每条推送的到达时刻,并与新闻自身的 published 时间戳对比,得到端到端时延(从新闻源标注的发布时间到推送落到客户端)。
英文源的数据尤其能说明问题,它的吞吐更高、时延更低,实时性一目了然:
实测对比(同为 10 分钟窗口,真实生产环境):
| 指标 | 简体中文 zh-Hans | 英文 en |
|---|---|---|
| 推送条数 | 8 条 | 13 条 |
| 吞吐 | 0.8 条/分钟 | 1.3 条/分钟 |
| 端到端时延・最快 | 约 30 秒 | 约 22 秒 |
| 端到端时延・中位数 | 约 3~4 分钟 | 约 54 秒 |
携带 symbols 占比 | 约 50% | 约 77% |
| 覆盖新闻源 | reuters / gelonghui / panews / fx168 | reuters / dow-jones / business_wire / moneycontrol / trading-economics |
从上面表格可见,英文频道在 10 分钟内推了 13 条新闻、中位时延只有约 54 秒,而中文频道中位数在 3~4 分钟。造成这种差异的原因是英文接入了更多快讯类源(dow-jones、business_wire、reuters 等),量大且快;而中文频道里聚合/深度稿件占比更高,它们的源发布时间本身就偏早。
为什么中英文时延差这么多?
因为 published 是新闻源自己标注的发布时间,快讯类源几乎是即时的,服务器收到就马上推给你。
而聚合/深度稿件进入内部管道时,源发布时间可能已经是几分钟前,这部分是源侧的固有滞后,任何下游接口都消不掉。如果想要最低时延,建议直接订阅那条最活跃、快讯源最多的语言频道(通常是 en)。
而无论源采集时延是多少,有一件事是确定的:
推送模型不会在它之上再叠加你自己的轮询间隔,但在轮询模式下就会。
这就是推送相对轮询的结构性优势,它消掉的是延迟里唯一由你的接入方式决定、且完全可以为零的那一段。
下面是英文频道实测到的几条真实推送(含关联标的),可以直观感受它的覆盖广度:
| 时延 | 来源 | 标题 | symbols |
|---|---|---|---|
| 22s | reuters | Shein seeks $30-$40 billion valuation for August Hong Kong IPO | 截至笔者发稿当日,希音仍未上市,所以无标的关联 |
| 30s | reuters | Resilient earnings soothe India’s oil shock | NSE:RELIANCE1!、NSE:NIFTY、IOC.IN 等 9 个 |
| 41s | business_wire | Procore 宣布定价 8.25 亿美元可转债发行 | PCOR.US |
| 55s | dow-jones | Global Server Shipments to Hit Record 5 Million Units in 3Q | DELL.US、SMCI.US、HPE.US |
| 65s | dow-jones | HSBC 拟完成至多 10 亿美元股票回购 | LSE:HSBA |
一条印度炼油业新闻,接口直接关联了 RELIANCE、NIFTY 指数和多家石油股共 9 个标的。
附:如何自测任意财经新闻 API 的真实延迟
如果你想自行对比不同新闻接口的延迟,可以直接用下面的代码:
import time
def measure_latency(news_item: dict) -> float:
"""端到端时延 = 本地接收时刻 - 新闻发布时间戳(均为 UTC 秒)"""
return time.time() - news_item["published"]
# 对轮询接口:记录你"发起请求"到"看到这条新闻"的时间差,
# 并叠加你的轮询间隔,才是真实的发现延迟。
# 对 WebSocket 推送:直接用上式,收到即测。同一条新闻,用轮询和用推送分别测一次,差距会非常直观。
三、差异二:更适合机器阅读的字段
这是普通的新闻接口和我们产品的最大区别。看一眼字段对照:
| 能力 | 普通接口 | Infoway 财经新闻 API |
|---|---|---|
| 标题 / 正文 | title / description(多为摘要) | title / content(完整正文)+ sd(摘要) |
| 关联股票代码 | ❌ (需自己从文本里抽) | ✅ symbols,如 ["000660.KS","005930.KS"] |
| 紧急度分级 | ❌ | ✅ urgency,数值越小越紧急 |
| 跨源去重键 | ⚠️ 只有 id(同源唯一) | ✅ dk,标题+正文的 md5,跨源判重 |
| 时间戳 | ctime 字符串 | published Unix 时间戳,直接可运算 |
| 来源 | source | provider |
symbols
普通接口只给你一段文本。想知道这条新闻和我持仓的哪只票有关,你得自己搭一套命名实体识别(NER):分词、识别公司名、映射到股票代码、处理简称/别名/一词多义……
这不仅是几百行代码,还是一个需要持续维护、准确率永远达不到 100% 的工程负担。
Infoway 直接在推送里给出 symbols 结构化代码。比如在一条关于SK 海力士与工会谈判的推送,symbols 直接给出:
"symbols": ["000660.KS", "005930.KS", "NVDA.US"]000660.KS(SK 海力士)、005930.KS(三星电子)、NVDA.US(英伟达),新闻文本里未必同时出现这三个代码,但语义上强相关,接口已经替你关联好了。你可以据此把新闻精准路由到关注这些标的的用户,或直接触发对应持仓的风控逻辑:
def route_news_to_holdings(news: dict, my_holdings: set[str]) -> bool:
"""新闻关联标的与持仓有交集 → 触发提醒/风控"""
hit = set(news.get("symbols") or []) & my_holdings
if hit:
print(f"⚠️ 持仓相关新闻 [{news['urgency']}] {news['title']} 命中: {hit}")
return True
return False
route_news_to_holdings(news, {"005930.KS", "AAPL.US"})同样的逻辑,如果用普通接口,你得先跑一遍 NER 才能拿到这个 symbols 集合。
urgency
事件驱动系统里,不是所有新闻都同等重要。urgency 数值越小越紧急,你可以据此做优先级调度,突发快讯插队处理,常规资讯批量入库:
import heapq
# 用 urgency 做小顶堆,突发消息优先出队
queue = []
heapq.heappush(queue, (news["urgency"], news["published"], news))四、差异三:dk 去重键
财经新闻天然多源:路透、格隆汇、各大媒体可能同时报道同一件事。资讯展示型接口给的 id 是按源生成的,同一条新闻在不同源有不同 id,你根本判不出重复。
Infoway 的 dk 是标题+正文内容的 md5,只要内容实质相同就是同一个 dk,天然跨源判重。接入时始终以 dk 做幂等,就能保证入库/推送干净:
seen = set()
def dedup(news: dict) -> bool:
dk = news.get("dk")
if not dk or dk in seen:
return False # 重复,丢弃
seen.add(dk)
return True # 新新闻,处理在我们的 10 分钟实测窗口内,每一条推送都带有 dk(覆盖率 100%),拿它做幂等即可。
五、差异四:多语言
市面上常见的新闻接口大多聚焦国内中文资讯。而Infoway 财经新闻 API 覆盖 11 种语言、全球主流财经源:
| 语言代码 | 语言 | 语言代码 | 语言 |
|---|---|---|---|
en | 英语(全球) | pt | 葡萄牙语 |
zh-Hans | 简体中文 | ru | 俄语 |
zh-Hant | 繁体中文 | de | 德语 |
ja | 日语 | fr | 法语 |
ko | 韩语 | es | 西班牙语 |
tr | 土耳其语 |
做跨市场、多语言资讯聚合时,按语言分别订阅即可,无需为每个国家单独找数据源。
六、横向对比总表
| 维度 | 普通接口 | Infoway 财经新闻 API(推送) |
|---|---|---|
| 传输模型 | HTTP 轮询查询 | WebSocket 主动推送 |
| 延迟下限 | ≈ 轮询间隔(叠加在源时延之上) | 无轮询天花板;实测中位时延英文频道约 54 秒、中文约数分钟(因源而异) |
| 关联股票代码 | ❌ (需自建 NER) | ✅ symbols 结构化返回 |
| 紧急度分级 | ❌ | ✅ urgency |
| 跨源去重 | ⚠️ 仅同源 id | ✅ dk(内容 md5) |
| 正文 | 多为摘要 description | 完整 content + 摘要 sd |
| 语言覆盖 | 多为国内中文 | 11 种语言 / 全球源 |
| 终端展示授权 | 常见「仅限内部分析」限制 | 面向开发者/机构商用授权 |
| 工程成本 | 低(一个 GET) | 需维护长连接(心跳/重连),本文附完整代码 |
结论很直接:做资讯展示,轮询接口够用且更省事;做事件驱动、量化、风控,你需要的是推送 + 结构化字段,这正是本接口的定位。
七、接口概览与协议
这一趴我们讲解如何接入Infoway 新闻接口。
| 项目 | 说明 |
|---|---|
| 连接地址 | wss://data.infoway.io/news?apikey=YOUR_API_KEY |
| 鉴权方式 | API Key 通过 URL 查询参数传入 |
| 连接限制 | 同一 API Key 仅允许一条连接 |
| 订阅维度 | 按语言订阅(每条连接一个语言) |
| 心跳要求 | 每约 30 秒发一次心跳,超 60 秒无心跳自动断开 |
| 权限要求 | 高级套餐(199 美金/月)及以上,或等额自定义套餐 |
协议号(code)一览:
| 协议号 | 方向 | 含义 |
|---|---|---|
10020 | 客户端 → 服务端 | 订阅新闻 |
10021 | 服务端 → 客户端 | 订阅确认 |
10022 | 服务端 → 客户端 | 新闻推送 |
10010 | 客户端 → 服务端 | 心跳保活 |
200 | 服务端 → 客户端 | 握手成功 |
本接口没有独立的「取消订阅」指令:每条连接只维护一个订阅,重发
10020会用新语言覆盖旧订阅。
八、请求与返回示例(真实数据)
8.1 订阅请求(10020)
{
"code": 10020,
"trace": "5dd7e89cdcc247d78e817b67269167e0",
"data": { "lang": "zh-Hans" }
}code:协议号,固定10020trace:随机追踪 ID,用于把请求与返回对应data.lang:订阅语言,不区分大小写(服务端归一化为小写)
8.2 握手与订阅确认
{ "code": 200, "msg": "ws connect success" }{
"code": 10021,
"trace": "5dd7e89cdcc247d78e817b67269167e0",
"msg": "ok",
"data": { "lang": "zh-hans" }
}8.3 新闻推送(10022,真实推送,正文已截断展示)
{
"code": 10022,
"data": {
"dk": "e9ecbfbc6799c39d5ec09c8c771ac0fe",
"country": "CN",
"lang": "zh-Hans",
"route": "lang",
"title": "中国债市动态:资金转松支撑中短券走强,科技股大涨压制长债",
"published": 1785815048,
"urgency": 2,
"provider": "reuters",
"symbols": ["399006.SZ", "000688.SH"],
"link": "",
"content": "中国债市周二走势分化,中短券在资金面转松带动下表现较好,收益率小幅下行;长端和超长端则延续弱势,30年期国债收益率上行……(正文略)",
"sd": "中国债市周二走势分化,中短券在资金面转松带动下表现较好,收益率小幅下行;长端和超长端则延续弱势……"
}
}注意这条真实推送里的 symbols:399006.SZ(创业板指)、000688.SH(科创板 50),一条债市新闻被自动关联到了受科技股情绪影响的两个指数,这正是给机器和AI用的字段的价值。
8.4 推送字段说明
| 字段 | 类型 | 是否必有 | 说明 |
|---|---|---|---|
dk | String | 是 | 去重键,标题+正文的 md5,跨源判重 |
title | String | 是 | 新闻标题 |
content | String | 否 | 新闻正文(纯文本) |
sd | String | 否 | 摘要 / 简介 |
published | Long | 是 | 发布时间,Unix 秒级时间戳(UTC) |
urgency | Integer | 是 | 紧急度,数值越小越紧急 |
provider | String | 是 | 来源,如 reuters、gelonghui |
symbols | Array | 否 | 关联标的代码,如 ["399006.SZ"] |
country | String | 否 | 国家 / 地区,全球性新闻可能为空 |
lang | String | 是 | 语言 |
route | String | 是 | 采集路由:country 或 lang |
link | String | 否 | 新闻原文链接 |
九、完整 Python 客户端
以下代码已包含心跳+断线重连+去重:
import os
import asyncio
import json
import uuid
import logging
from typing import Optional
import websockets
from websockets.asyncio.client import ClientConnection
from websockets.exceptions import ConnectionClosed
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger("infoway-news")
REQ_SUBSCRIBE = 10020
REQ_HEARTBEAT = 10010
ACK_SUBSCRIBE = 10021
PUSH_NEWS = 10022
HANDSHAKE_OK = 200
SUBSCRIBE_LANG = "zh-Hans" # 可改为 en / ja / ko / zh-Hant 等
class NewsWSClient:
"""Infoway 财经新闻 WebSocket 客户端(心跳 + 指数退避重连 + 去重)"""
def __init__(self, api_key: str, lang: str = SUBSCRIBE_LANG):
self.ws_url = f"wss://data.infoway.io/news?apikey={api_key}"
self.lang = lang
self.ws: Optional[ClientConnection] = None
self.running = True
self.reconnect_base, self.reconnect_max = 5, 60
self.heartbeat_interval = 30
self.heartbeat_task: Optional[asyncio.Task] = None
self.seen_dk: set[str] = set()
async def _send(self, msg: dict) -> None:
await self.ws.send(json.dumps(msg))
async def _subscribe(self) -> None:
await self._send({"code": REQ_SUBSCRIBE, "trace": uuid.uuid4().hex,
"data": {"lang": self.lang}})
logger.info("已发送订阅请求,语言=%s", self.lang)
def _start_heartbeat(self) -> None:
self._cancel_heartbeat()
async def _loop():
try:
while True:
await asyncio.sleep(self.heartbeat_interval)
if self.ws is None or self.ws.close_code is not None:
break
await self._send({"code": REQ_HEARTBEAT, "trace": uuid.uuid4().hex})
except (ConnectionClosed, asyncio.CancelledError):
pass
self.heartbeat_task = asyncio.create_task(_loop())
def _cancel_heartbeat(self) -> None:
if self.heartbeat_task and not self.heartbeat_task.done():
self.heartbeat_task.cancel()
self.heartbeat_task = None
def _on_news(self, data: dict) -> None:
dk = data.get("dk")
if dk and dk in self.seen_dk:
return # 去重
if dk:
self.seen_dk.add(dk)
symbols = ", ".join(data.get("symbols") or []) or "-"
logger.info("[新闻] 紧急度=%s 来源=%s 标的=%s\n 标题: %s",
data.get("urgency"), data.get("provider"), symbols, data.get("title"))
# TODO: 写入数据库 / 推送消息队列 / 触发策略信号
def _on_message(self, raw: str) -> None:
try:
msg = json.loads(raw)
except json.JSONDecodeError:
return
code = msg.get("code")
if code == PUSH_NEWS:
self._on_news(msg.get("data", {}))
elif code == ACK_SUBSCRIBE:
logger.info("订阅确认成功,当前语言=%s", msg.get("data", {}).get("lang"))
elif code == HANDSHAKE_OK:
logger.info("握手成功:%s", msg.get("msg"))
async def _connect_once(self) -> None:
async with websockets.connect(self.ws_url) as ws:
self.ws = ws
logger.info("WebSocket 连接成功")
await self._subscribe() # 重连后必须重新订阅
self._start_heartbeat()
try:
async for message in ws:
self._on_message(message)
finally:
self._cancel_heartbeat()
self.ws = None
async def start(self) -> None:
backoff = self.reconnect_base
while self.running:
try:
await self._connect_once()
backoff = self.reconnect_base
except ConnectionClosed as e:
logger.warning("连接关闭: %s", e)
except Exception as e:
logger.error("连接异常: %s", e)
if not self.running:
break
logger.info("%.0f 秒后重连...", backoff)
await asyncio.sleep(backoff)
backoff = min(backoff * 2, self.reconnect_max)
async def main():
api_key = os.environ.get("INFOWAY_API_KEY", "YOUR_API_KEY")
await NewsWSClient(api_key, lang="zh-Hans").start()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("退出")十、更适合交易所的新闻接口
如果你正在为你的交易所寻找可靠的新闻源,Infoway 新闻接口天然是更适合的选项:
1. symbols 字段帮您「按品种」对新闻进行分类,零 NER 成本
交易所的核心界面通常是一个个品种交易页面(AAPL、HSBC、原油……)。用户希望在看盘的同时,看到这只票现在有什么最新消息。
正如前文所言,市面上其他的新闻接口无法有效实现这个需求,这类接口只给一段文本,交易所得自己搭 NER 把新闻匹配到几千个上市品种,工程量巨大且永远不准。而 Infoway 每条新闻自带 symbols,直接就是新闻 → 品种的映射。上一节实测里那条 dow-jones 服务器出货新闻带着 DELL.US、SMCI.US、HPE.US,接口已经替交易所把它分发到这三只票的页面上了。
2. 与行情共用一套接入,边际成本极低
Infoway API除了新闻,还提供实时行情数据,覆盖A股、港股、美股、日本、韩国、印度市场,另外还有外汇、期货、CFD、指数、贵金属、能源等。全部通过WebSocket高频推送。如果您正在使用我们的实时行情接口,新闻只是多订阅一条 WebSocket,鉴权、运维、SDK 全部复用。用 urgency 还能在终端上把突发快讯置顶飘红。
下面是一段交易所可直接复用的按品种建新闻索引逻辑,把推送流实时分发到每个品种的新闻栏:
from collections import defaultdict, deque
# symbol -> 最近 N 条新闻(供各品种页面直接读取)
news_by_symbol: dict[str, deque] = defaultdict(lambda: deque(maxlen=50))
def index_news(news: dict) -> None:
"""每条推送按关联标的分发到对应品种的新闻流"""
item = {
"title": news["title"],
"sd": news.get("sd"),
"published": news["published"],
"urgency": news["urgency"], # 越小越紧急,前端可据此置顶飘红
"provider": news["provider"],
"link": news.get("link"),
}
for sym in news.get("symbols") or []:
news_by_symbol[sym].appendleft(item)
def instrument_news(symbol: str) -> list:
"""交易终端渲染某品种页面时,直接取它的相关新闻"""
return list(news_by_symbol.get(symbol, []))
# 例:用户打开 DELL.US 页面
for n in instrument_news("DELL.US"):
print(f"[{n['urgency']}] {n['title']} — {n['provider']}")交易所前端要做的,只是在品种页调用一次 instrument_news(symbol)。整条链路里,最难的新闻关联股票已经由接口的 symbols 字段解决了。
十一、其他落地场景
1. 持仓/自选股新闻雷达
用 symbols 与用户持仓求交集,命中即推送;用 urgency 决定是弹窗强提醒还是静默入库。整套逻辑不需要你写一行 NER。
2. 事件驱动信号引擎
监听关联到特定标的的突发新闻(urgency 小),第一时间调用 Infoway 行情接口拉该标的的最新成交与盘口,把「消息面」和「价格面」拼成完整决策链路,两者共用同一套 API Key。
3. 多语言舆情看板
按语言开多条连接,聚合全球资讯;用 dk 跨源去重、provider 标注来源,快速搭一个干净的多语言财经资讯监控台。优先订阅 en 频道拿到最快、最全的流。
十二、接入注意事项
- 心跳不能停:每约 30 秒发一次
10010;超 60 秒无心跳(业务心跳或 WebSocket Ping)服务端会断开。标准 Ping/Pong 同样有效。 - 一 Key 一连接:同一 API Key 只能一条连接。要订阅多语言,用多个 Key,或在一条连接上切换
lang。 - 重连必重订阅:断线重连后要重新发送
10020,否则收不到推送(上面的客户端已处理)。 - 始终以
dk去重:多源可能推同一条新闻,用dk做幂等。 - 时间戳是 UTC 秒:
published展示前按用户时区转换。 - 保管好 API Key:Key 走 URL 传入,勿在前端或公开仓库暴露,放服务端环境变量。
常见错误码:517 缺少 apikey、518 apikey 不存在、519 无新闻权限(套餐未开通)、520 该 apikey 已有连接、514 WebSocket 路径错误。
十三、如何开通
Infoway 的实时财经新闻 API 接口并不需要单独购买套餐,而是面向 高级套餐及以上,或 等额自定义套餐 用户开放。开通后即可用同一 API Key 订阅新闻推送,与实时行情共享同一套鉴权体系。
完整字段定义、语言代码与错误码见新闻接口文档