📢 产品上线:今天,Infoway 实时财经新闻 API 正式上线

即日起,开发者、量化团队与交易平台可以通过 WebSocket 订阅全球实时财经新闻推送,我们的新闻接口覆盖 11 种语言,每条新闻自带关联标的(symbols)、紧急度、去重键,并与实时行情共用同一套 API Key

功能面向高级套餐(199 美金/月)及以上或等额自定义套餐开放。

一、财经新闻 API

市面上的财经新闻接口,大致可以分成两类:

第一类:资讯展示型(轮询查询)
面向内容网站、App 资讯页。你定时发 HTTP 请求拉最新几条,拿到标题、封面图、来源、网页链接,渲染成一个新闻列表给用户看,这种产品的返回字段结构基本大同小异:

Python
id / ctime(发布时间) / title / source(来源) / picUrl(封面图) / url(网页链接) / description(描述)

第二类:数据驱动型(实时推送)
面向量化策略、事件驱动交易、舆情风控系统。你需要的不只是简单的新闻卡片,而是一条能被程序消费的结构化事件流,除了新闻内容,返回的字段还需要告诉你:

  • 这条新闻和哪些股票有关?
  • 重要性如何?
  • 是不是刚才那条的重复?

Infoway 的财经新闻 API 属于这一类,字段结构完全不同:

Python
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 / fx168reuters / dow-jones / business_wire / moneycontrol / trading-economics

从上面表格可见,英文频道在 10 分钟内推了 13 条新闻、中位时延只有约 54 秒,而中文频道中位数在 3~4 分钟。造成这种差异的原因是英文接入了更多快讯类源(dow-jones、business_wire、reuters 等),量大且快;而中文频道里聚合/深度稿件占比更高,它们的源发布时间本身就偏早。

为什么中英文时延差这么多?

因为 published新闻源自己标注的发布时间,快讯类源几乎是即时的,服务器收到就马上推给你。

而聚合/深度稿件进入内部管道时,源发布时间可能已经是几分钟前,这部分是源侧的固有滞后,任何下游接口都消不掉。如果想要最低时延,建议直接订阅那条最活跃、快讯源最多的语言频道(通常是 en)。

而无论源采集时延是多少,有一件事是确定的:

推送模型不会在它之上再叠加你自己的轮询间隔,但在轮询模式下就会。

这就是推送相对轮询的结构性优势,它消掉的是延迟里唯一由你的接入方式决定、且完全可以为零的那一段。

下面是英文频道实测到的几条真实推送(含关联标的),可以直观感受它的覆盖广度:

时延来源标题symbols
22sreutersShein seeks $30-$40 billion valuation for August Hong Kong IPO截至笔者发稿当日,希音仍未上市,所以无标的关联
30sreutersResilient earnings soothe India’s oil shockNSE:RELIANCE1!NSE:NIFTYIOC.IN 等 9 个
41sbusiness_wireProcore 宣布定价 8.25 亿美元可转债发行PCOR.US
55sdow-jonesGlobal Server Shipments to Hit Record 5 Million Units in 3QDELL.USSMCI.USHPE.US
65sdow-jonesHSBC 拟完成至多 10 亿美元股票回购LSE:HSBA

一条印度炼油业新闻,接口直接关联了 RELIANCENIFTY 指数和多家石油股共 9 个标的

附:如何自测任意财经新闻 API 的真实延迟

如果你想自行对比不同新闻接口的延迟,可以直接用下面的代码:

Python
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 时间戳,直接可运算
来源sourceprovider

symbols

普通接口只给你一段文本。想知道这条新闻和我持仓的哪只票有关,你得自己搭一套命名实体识别(NER):分词、识别公司名、映射到股票代码、处理简称/别名/一词多义……

这不仅是几百行代码,还是一个需要持续维护、准确率永远达不到 100% 的工程负担。

Infoway 直接在推送里给出 symbols 结构化代码。比如在一条关于SK 海力士与工会谈判的推送,symbols 直接给出:

Python
"symbols": ["000660.KS", "005930.KS", "NVDA.US"]

000660.KS(SK 海力士)、005930.KS(三星电子)、NVDA.US(英伟达),新闻文本里未必同时出现这三个代码,但语义上强相关,接口已经替你关联好了。你可以据此把新闻精准路由到关注这些标的的用户,或直接触发对应持仓的风控逻辑:

Python
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 数值越小越紧急,你可以据此做优先级调度,突发快讯插队处理,常规资讯批量入库:

Python
import heapq

# 用 urgency 做小顶堆,突发消息优先出队
queue = []
heapq.heappush(queue, (news["urgency"], news["published"], news))

四、差异三:dk 去重键

财经新闻天然多源:路透、格隆汇、各大媒体可能同时报道同一件事。资讯展示型接口给的 id按源生成的,同一条新闻在不同源有不同 id,你根本判不出重复。

Infoway 的 dk标题+正文内容的 md5,只要内容实质相同就是同一个 dk,天然跨源判重。接入时始终以 dk 做幂等,就能保证入库/推送干净:

Python
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
跨源去重⚠️ 仅同源 iddk(内容 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)

Python
{
    "code": 10020,
    "trace": "5dd7e89cdcc247d78e817b67269167e0",
    "data": { "lang": "zh-Hans" }
}
  • code:协议号,固定 10020
  • trace:随机追踪 ID,用于把请求与返回对应
  • data.lang:订阅语言,不区分大小写(服务端归一化为小写)

8.2 握手与订阅确认

Python
{ "code": 200, "msg": "ws connect success" }
Python
{
    "code": 10021,
    "trace": "5dd7e89cdcc247d78e817b67269167e0",
    "msg": "ok",
    "data": { "lang": "zh-hans" }
}

8.3 新闻推送(10022,真实推送,正文已截断展示)

Python
{
    "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": "中国债市周二走势分化,中短券在资金面转松带动下表现较好,收益率小幅下行;长端和超长端则延续弱势……"
    }
}

注意这条真实推送里的 symbols399006.SZ(创业板指)、000688.SH(科创板 50),一条债市新闻被自动关联到了受科技股情绪影响的两个指数,这正是给机器和AI用的字段的价值。

8.4 推送字段说明

字段类型是否必有说明
dkString去重键,标题+正文的 md5,跨源判重
titleString新闻标题
contentString新闻正文(纯文本)
sdString摘要 / 简介
publishedLong发布时间,Unix 秒级时间戳(UTC)
urgencyInteger紧急度,数值越小越紧急
providerString来源,如 reutersgelonghui
symbolsArray关联标的代码,如 ["399006.SZ"]
countryString国家 / 地区,全球性新闻可能为空
langString语言
routeString采集路由:countrylang
linkString新闻原文链接

九、完整 Python 客户端

以下代码已包含心跳+断线重连+去重:

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.USSMCI.USHPE.US,接口已经替交易所把它分发到这三只票的页面上了。

2. 与行情共用一套接入,边际成本极低
Infoway API除了新闻,还提供实时行情数据,覆盖A股港股美股日本韩国印度市场,另外还有外汇期货CFD、指数、贵金属、能源等。全部通过WebSocket高频推送。如果您正在使用我们的实时行情接口,新闻只是多订阅一条 WebSocket,鉴权、运维、SDK 全部复用。用 urgency 还能在终端上把突发快讯置顶飘红。

下面是一段交易所可直接复用的按品种建新闻索引逻辑,把推送流实时分发到每个品种的新闻栏:

Python
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 订阅新闻推送,与实时行情共享同一套鉴权体系。

完整字段定义、语言代码与错误码见新闻接口文档