QVeris

用 Python 调用免费财经新闻 API

选择可测试的财经新闻接口,用 Python 按股票代码请求新闻,并在生产接入前核对额度、历史、情绪、来源和 JSON 字段。

Financial news API free Python whiteboard workflow for validating ticker news, limits, history, sentiment and JSON

Python 财经新闻采集器的实现目标

开发者在确定要接入财经新闻后,需要的并不是一次性请求,而是能够应对分页、限流、重复报道、结构变化、部分失败和进程重启的 Python 采集器。本页不以供应商覆盖比较为重点,核心目标是构建一个规模适中但具备生产形态的采集循环,并确保日后更换 API 时不必改写所有下游流程。

可恢复采集

只在批次成功落库后保存游标和检查点,确保进程重启时既不漏文章,也不会越过失败任务。

类型化归一

把不同响应转成统一内部文章模型,在进入 NLP、告警或存储前验证必需字段。

幂等重试

结合退避、稳定标识、URL 规范化和数据库唯一约束,确保重复请求不会生成重复结果。

选择能支撑可靠 Python 采集循环的财经新闻 API

优先选择游标分页,而不是脆弱的页码分页

Python 采集器应能在中断后继续运行,同时避免漏掉或重复读取不断变化的结果集。测试接口是否返回不透明的下一页游标、确定的排序规则和明确的结束条件;只有对应批次成功落库后,才保存游标。

先统一文章结构,再交给下游 NLP

把供应商字段映射为类型明确的记录,包括供应商文章 ID、规范 URL、标题、发布时间、来源、语言、实体、股票代码和抓取时间。原始响应可留作排错,但不要让每个模型或 Notebook 都直接依赖供应商特有 JSON。

把重试、去重与检查点作为一个整体设计

对暂时性错误进行退避重试,遵守 Retry-After,并结合供应商 ID 与规范 URL 去重。重试必须幂等:重复运行同一页不能生成重复提醒或重复数据行。

使用带重叠窗口的发布时间水位

同时保存供应商游标和发布时间水位。进程恢复时,不要恰好从最后时间戳开始,而应重新请求一小段重叠窗口并去重,否则延迟到达或被更正的文章可能落在检查点之前。发布时间、供应商收录时间和本地抓取时间应分别保存。

隔离结构与内容异常

不能因为一篇文章格式异常就丢弃整页,也不能静默接受破坏兼容性的字段变化。将异常记录连同供应商、请求 ID、原始响应、验证错误和重试状态放入隔离区;只有确认分页游标仍可靠时才继续采集,并安排后续复核。

把采集与内容增强分开

先获取并提交来源记录,再执行摘要、翻译、情绪、向量化或提醒排序。下游增强可以独立重试和版本化,采集器则始终确定性地记录供应商在什么时间返回了什么内容。

检查项要回答的问题证据
Python 易用性是否兼容 requests,是否有 SDK、类型与异步支持?最小同步与异步原型
分页游标是否稳定,顺序、页大小和断点续传如何?两页重放与恢复测试
限流429 响应、Retry-After、重置头与突发规则?受控负载下的实际响应头
幂等性重复读取一页是否会产生重复结果?供应商 ID、URL 哈希与数据库约束
可观察性能否测量延迟、失败、空字段和额度?结构化日志与采集指标
测试能否离线记录并重放响应?测试样本、结构测试与失败用例

免费财经新闻 API 的 Python 请求示例

不要把 API Key 写进源码

从环境变量读取密钥,设置合理的超时时间,不要在日志或 Notebook 中输出密钥。

验证状态码、内容类型和字段

遇到 HTTP 错误时立即停止处理,确认返回内容为 JSON,并拒绝缺少应用必需字段的新闻记录。

明确处理分页和限流

遵循文档中的游标,处理 429 与 Retry-After,按固定 URL 或文章 ID 去重,并保留来源时间戳。

封装一个小型供应商客户端

把基础 URL、鉴权、超时、连接复用、错误映射、游标提取和响应验证集中到同一接口中,返回规范化批次与检查点元数据,不要让原始供应商 JSON 扩散到整个应用。这样更换 API 时修改范围小且容易测试。

测试重启与部分失败

记录代表性响应并在离线环境重放,覆盖两页正常结果、提交前超时、带 Retry-After 的 429、异常 JSON、单条无效文章、重复重放、空结果、更正到达和字段删除。既要断言最终落库记录,也要断言最终检查点。

暴露采集健康状态

测量请求延迟、从发布到收录的延迟、成功率、重试、剩余额度、每页文章数、重复率、隔离记录和检查点年龄。即使请求仍返回 HTTP 200,只要进度过期或重要来源突然消失,也应告警。

import os
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from urllib.parse import urldefrag

import requests


@dataclass(frozen=True)
class Article:
    provider_id: str
    canonical_url: str
    title: str
    published_at: datetime
    source: str
    language: str
    retrieved_at: datetime

    @property
    def dedupe_key(self) -> tuple[str, str]:
        return self.provider_id, self.canonical_url


class FinancialNewsClient:
    def __init__(self, base_url: str, api_key: str) -> None:
        self.base_url = base_url
        self.session = requests.Session()
        self.session.headers.update({"Authorization": f"Bearer {api_key}"})

    def fetch_page(self, cursor: str | None) -> tuple[list[Article], list[dict], str | None]:
        params = {"symbols": "AAPL", "language": "en", "limit": 100}
        if cursor:
            params["cursor"] = cursor

        for attempt in range(4):
            response = self.session.get(self.base_url, params=params, timeout=(5, 20))
            if response.status_code == 429:
                retry_after = response.headers.get("Retry-After", "1")
                delay = int(retry_after) if retry_after.isdigit() else 1
                time.sleep(min(delay, 30))
                continue
            if 500 <= response.status_code < 600 and attempt < 3:
                time.sleep(2 ** attempt)
                continue

            response.raise_for_status()
            if "application/json" not in response.headers.get("Content-Type", ""):
                raise ValueError("Expected a JSON response")

            payload = response.json()
            raw_items = payload.get("results")
            if not isinstance(raw_items, list):
                raise ValueError("Expected results to be a list")

            retrieved_at = datetime.now(timezone.utc)
            valid, quarantine = [], []
            for raw in raw_items:
                try:
                    required = {"id", "title", "published_at", "source_url", "source"}
                    if not required.issubset(raw):
                        raise ValueError("Missing a required article field")
                    published = datetime.fromisoformat(raw["published_at"].replace("Z", "+00:00"))
                    if published.tzinfo is None:
                        raise ValueError("published_at must include a timezone")
                    valid.append(Article(
                        provider_id=str(raw["id"]),
                        canonical_url=urldefrag(raw["source_url"])[0],
                        title=raw["title"].strip(),
                        published_at=published.astimezone(timezone.utc),
                        source=raw["source"].strip(),
                        language=raw.get("language", "und"),
                        retrieved_at=retrieved_at,
                    ))
                except (KeyError, TypeError, ValueError) as error:
                    quarantine.append({"raw": raw, "error": str(error)})

            return valid, quarantine, payload.get("next_cursor")

        raise RuntimeError("The provider remained unavailable after bounded retries")


def collect(client, start_cursor, commit_batch):
    cursor = start_cursor
    while True:
        articles, quarantine, next_cursor = client.fetch_page(cursor)
        unique = {article.dedupe_key: article for article in articles}

        # This function must commit articles, quarantine records, and the
        # next cursor in one database transaction. Advance no checkpoint first.
        commit_batch(list(unique.values()), quarantine, next_cursor)

        if not next_cursor:
            break
        cursor = next_cursor


client = FinancialNewsClient(
    "https://provider.example/v1/financial-news",
    os.environ["FINANCIAL_NEWS_API_KEY"],
)
# Wire in application-owned transactional functions before running:
# collect(client, start_cursor=load_checkpoint(), commit_batch=commit_batch)

检查点约束:commit_batch 必须使用数据库唯一约束,并在同一个事务中保存有效文章、隔离记录和 next_cursor。进程在提交前终止时,可以安全重放同一页;提交后终止时,则从已保存游标对应的下一页继续。绝不能先保存游标、后保存文章。

域名和字段名仅用于说明通用请求结构,避免虚构 QVeris 或服务商端点;请按所选服务商的当前文档替换。

QVeris 如何帮助发现财经新闻 API 能力

QVeris 帮助 Agent 发现可调用能力并检查输入输出,但不保证服务商免费、实时、完整或适用于所有用途;这些事实必须以服务商当前文档为准。

  • 在 QVeris 工具详情中搜索财经新闻或股票新闻能力。
  • 接入工作流前检查鉴权、股票代码参数、返回字段和错误行为。
  • 参考 QVeris Python SDK 文档了解受支持的接入方式。

FAQ

有免费的财经新闻 API 吗?

有些服务提供免费套餐或试用。选择前应比较配额、延迟、历史、字段、市场覆盖、署名和商用规则。

Python 如何获取财经新闻?

使用 requests 发送带鉴权的请求,传入股票代码或查询词,检查状态码,验证 JSON,并处理分页与限流。

股票新闻 API 能否按代码筛选?

很多金融专用接口支持股票代码,但代码格式和交易所映射不同,应查阅当前参数文档并用已知代码测试。

能免费获取历史财经新闻吗?

有时可以,但免费历史通常限制日期范围、结果数量或访问延迟,不能假设当前新闻接口自动包含完整档案。

免费财经新闻 API 包含情绪吗?

部分接口返回情绪、实体或相关性,另一些只返回原始文章。应检查字段定义、置信度、方法和语言支持。

免费财经新闻 API 是否需要密钥?

多数需要 API Key,也有少数公开或样例端点。无论是否需要密钥,都应核对限额、来源许可和生产使用条件。

权威参考资料

服务商套餐会变化,应以当前文档为证据;本页提供评估流程,不承诺某个具体免费额度长期有效。

用 Python 调用免费财经新闻 API | QVeris Guides