Strasmore Research
学习 Matt Connor作者: Matt Connor · 更新于 2026-10-06 · data as of October 6, 2026 · refreshed weekly

Python 免费股票市场数据 API 调用教程

学习在 Ubuntu 中用 Python、requests 和 pandas 调用免费股票市场数据 API,将 JSON 加载为 DataFrame,并输出单个 ticker 的 20 日平均成交量。

通过 Python 调用免费的股票市场数据 API,只需发送一次 HTTP 请求,并使用大多数人已经安装的两个库:requests 用于获取 JSON,pandas 用于将其转换为 DataFrame。整个流程无需 API key。本教程可在全新的 Ubuntu 容器中从头运行到尾,最后输出单个 ticker 的 20 个交易日平均成交量。相关端点提供哪些数据,请参阅 免费股票市场数据 API 指南;本页介绍的是同一 API 的 Python 用法。

在全新 Ubuntu 容器中安装 Python、requests 和 pandas

标准 Ubuntu 镜像未预装 Python 的 venv 模块,而近期版本也禁止将软件包安装到系统解释器中。虚拟环境可以同时解决这两个问题,并且在当前各个 Ubuntu 版本中的行为一致。请以 root 身份运行以下命令,或在 apt-get 行前加上 sudo。

export DEBIAN_FRONTEND=noninteractive
apt-get update && apt-get install -y python3 python3-venv
python3 -m venv .venv
. .venv/bin/activate
pip install "requests>=2.31,<3" "pandas>=2.0,<4"

这里采用的版本范围是有意设置的。每个范围都接受该库的所有受支持版本,只排除行为未知的未来主版本。这样,随着 Ubuntu 默认 Python 版本升级,同一行命令仍可顺利安装。没有任何依赖被固定到仅在今天存在的某个构建版本。以下内容均假设虚拟环境已激活。

如何在 Python 中调用免费的股票市场数据 API?

这些演示端点可直接响应普通 GET 请求,无需密钥,也无需注册。第一个脚本会请求一条精选查询,并打印返回数据的结构。

import json
import requests

BASE = "https://ai.strasmore.com/api/demo"

resp = requests.get(BASE, params={"q": "dividend_yield_leaders"}, timeout=30)
resp.raise_for_status()
payload = resp.json()

print(list(payload.keys()))
print(payload["columns"])
print(json.dumps(payload["rows"][0], indent=2))

requests.get 根据 params 构建查询字符串,raise_for_status() 将任何 4xx 或 5xx 状态转换为异常,.json() 则将响应正文解析为字典。简化其结构后,该字典如下:

{
  "key":     "dividend_yield_leaders",
  "label":   "...",
  "nl":      "Which large-cap US stocks currently have the highest dividend yields?",
  "sql":     "SELECT ...",
  "columns": ["as_of", "ticker", "dividend_yield_pct", "price", "market_cap_bn", "price_to_earnings"],
  "rows":    [{"as_of": "<date>", "ticker": "<symbol>", "dividend_yield_pct": <number>, ...}, ...],
  "elapsed": "...",
  "source":  "Strasmore Research",
  "more":    "..."
}

pandas 需要关注两个键。columns 按顺序列出字段,rows 则包含每条记录对应的一个 JSON 对象,并以这些字段名作为键。sql 键记录生成这些数据的确切查询。每次响应都会返回该查询,因此数据可审计,而不是一个黑箱。向 /api/demo/catalog 发送 GET 请求,可列出所有精选键,以及后续使用的 SQL 端点。

如何将 JSON 加载到 pandas DataFrame?

这些精选查询用于回答固定问题。您选择的 ticker 的日线数据来自无需注册的 SQL 端点 /api/demo/sql。该端点通过 sql 参数接收只读查询,并返回相同的 columns 和 rows 数据对。截至 2026 年 9 月,其限制会显示在每次响应中:500 行、20 秒、最多一年历史数据,且无需密钥。使用密钥的免费 SQL API可将上限提高至每天 100 次查询,并支持更长的历史数据;请求代码相同。

SQL 中设置了两道保护措施,以确保平均值准确。交易时段内更新的表可能包含当前日期的多行数据,而且当天的成交量在市场开市期间仍是不完整的。查询通过 date < today() 截止到昨天,并使用 GROUP BY date 和 max(volume) 合并同一日期的重复行。如果您还不了解日线数据如何从逐笔成交数据中生成,了解 OHLCV 日线数据的构建方式可以说明这些数据行的来源。

import time
import requests
import pandas as pd

SQL_URL = "https://ai.strasmore.com/api/demo/sql"
TICKER = "AAPL"

SQL = f"""
SELECT date, max(volume) AS volume
FROM stocks_daily_aggs
WHERE ticker = '{TICKER}'
  AND date < today()
GROUP BY date
ORDER BY date DESC
LIMIT 20
"""


def fetch(sql, attempts=4):
    """GET the SQL endpoint. Back off on 429 and 5xx; stop on any other 4xx."""
    delay = 2.0
    for attempt in range(1, attempts + 1):
        resp = requests.get(SQL_URL, params={"sql": " ".join(sql.split())}, timeout=30)
        if resp.status_code == 200:
            return resp.json()
        if resp.status_code == 429 or resp.status_code >= 500:
            try:
                wait = max(1.0, float(resp.headers.get("Retry-After")))
            except (TypeError, ValueError):
                wait = delay
            print(f"HTTP {resp.status_code}; waiting {wait:.0f}s (attempt {attempt} of {attempts})")
            time.sleep(wait)
            delay *= 2
            continue
        raise SystemExit(f"HTTP {resp.status_code}: {resp.text[:400]}")
    raise SystemExit("gave up after repeated 429 or 5xx responses")


payload = fetch(SQL)
df = pd.DataFrame(payload["rows"], columns=payload["columns"])
df["date"] = pd.to_datetime(df["date"])
df["volume"] = pd.to_numeric(df["volume"])
df = df.sort_values("date").reset_index(drop=True)

print(df.to_string(index=False))

avg_20 = df["volume"].mean()
first, last = df["date"].iloc[0], df["date"].iloc[-1]
print(f"{TICKER} 20-session average volume: {avg_20 / 1e6:.1f}M shares "
      f"({first:%Y-%m-%d} to {last:%Y-%m-%d}, {len(df)} sessions)")

pd.DataFrame(rows, columns=columns) 直接根据对象列表构建表格,传入 columns 则可保留 API 返回的字段顺序。随后进行两项转换:日期以字符串形式返回,需转换为真正的时间戳;volume 会被强制转换为数值,以防响应将其序列化为文本。按从旧到新的顺序排序后,DataFrame 的排列方式符合图表要求。对恰好 20 行数据执行 df["volume"].mean(),即可得到 20 个交易日的平均值。最后一行以百万为单位输出该平均值,并列出其涵盖的日期范围。

20日平均成交量实际衡量什么?

平均成交量可以平滑嘈杂的日度序列。单个交易日的成交股数会受到指数再平衡、期权到期日、财报发布日期和新闻标题影响而大幅波动。20个交易日约为一个日历月,既足以削弱这些尖峰,又足够短,能够跟踪一只股票交易活跃度的变化。下方图表使用同一张日度数据表,计算脚本输出的同一指标,覆盖最近几个月,并将原始交易日数据与滚动平均值并列展示。

查询AAPL每日成交量及其20个交易日移动平均值,百万股
34 rows (showing 20)
交易日成交量(百万)20日均值(百万)
2026-08-1738.251.9
2026-08-1853.452.5
2026-08-1950.553
2026-08-204153.1
2026-08-2146.953
2026-08-2434.752.3
2026-08-2525.951
2026-08-263449.9
2026-08-2732.447.8
2026-08-2838.643.1
2026-08-3141.241.4
2026-09-0153.240.6
2026-09-0233.839.8
2026-09-0337.239.4
2026-09-0439.639.7
2026-09-0835.539.2
2026-09-0965.640.6
2026-09-107042
2026-09-1150.742.5
2026-09-1439.343.1
每个数字背后的完整 SQL
SELECT
    session,
    volume_millions,
    avg_20d_millions
FROM
(
    SELECT
        toString(d)                                                                            AS session,
        round(vol / 1e6, 1)                                                                    AS volume_millions,
        round(avg(vol) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) / 1e6, 1)  AS avg_20d_millions,
        count() OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW)                    AS sessions_in_window
    FROM
    (
        SELECT
            date                    AS d,
            toFloat64(max(volume))  AS vol
        FROM global_markets.stocks_daily_aggs
        WHERE ticker = 'AAPL'
          AND date >= today() - 75
          AND date <  today()
        GROUP BY date
    )
)
WHERE sessions_in_window = 20
ORDER BY session
自己运行这个查询

日度序列每个交易日对应一个数据点;平滑后的序列取该交易日及此前19个交易日的平均值。图表覆盖2026-08-17至2026-10-02,其中有34个交易日具备完整的20日窗口。在这些交易日中的最后一个交易日,AAPL成交33.3百万股,20日平均成交量为42.2百万股。这个平均值就是本页面生成当天脚本输出的数字。若今天运行脚本,纳入计算的20个交易日已经发生变化,数字也会随之变化。

更换 ticker

将 TICKER 替换为其他代码后,脚本即可适用于表格中的任何一只在美国上市的证券。下面的比较对四个家喻户晓的名称执行相同的20个交易日计算,用于快速了解“平均成交量”在一只指数 ETF 和三只超大盘股之间的规模差异。

查询四只热门股票的20个交易日移动平均成交量,百万股
ticker20日均值(百万)首个交易日最后交易日
NVDA1102026-09-042026-10-02
SPY462026-09-042026-10-02
AAPL42.22026-09-042026-10-02
MSFT21.32026-09-042026-10-02
每个数字背后的完整 SQL
SELECT
    ticker,
    round(avg(vol) / 1e6, 1)   AS avg_20d_millions,
    toString(min(d))           AS first_session,
    toString(max(d))           AS last_session
FROM
(
    SELECT
        ticker,
        d,
        vol,
        row_number() OVER (PARTITION BY ticker ORDER BY d DESC) AS rn
    FROM
    (
        SELECT
            ticker,
            date                    AS d,
            toFloat64(max(volume))  AS vol
        FROM global_markets.stocks_daily_aggs
        WHERE ticker IN ('SPY', 'NVDA', 'AAPL', 'MSFT')
          AND date >= today() - 45
          AND date <  today()
        GROUP BY ticker, date
    )
)
WHERE rn <= 20
GROUP BY ticker
HAVING count() = 20
ORDER BY avg_20d_millions DESC
自己运行这个查询

四者中,NVDA的20个交易日平均成交量最高,为每个交易日110百万股;MSFT最低,为21.3百万股。所有数据均根据2026-09-04至2026-10-02期间的交易日计算。这里的 AAPL 数值与上文追踪结果的终值相同:定义一致,只计算一次,无论出现在哪里。

429 响应是什么样的?如何礼貌地重试?

限流以 HTTP 状态码的形式返回,而不是作为数据返回。响应会携带状态码 429 Too Many Requests,通常还会带有 Retry-After 请求头,其中说明需要等待的秒数;响应正文并不是您请求的数据。因此,fetch 辅助函数会先检查 status_code,再进行解析。处理规则依次如下:

  1. 200:解析并返回 JSON。
  2. 429 或任何 5xx:如果存在 Retry-After,则等待相应时间;否则采用退避策略(等待 2、4、8 秒,随后等待 16 秒),然后重试,总计最多四次。
  3. 其他任何 4xx:停止重试。响应正文会明确说明查询被拒绝的原因,例如请求的表不存在,或只读限制拒绝了相关语句。继续重试不会改变结果。例如,请求未知表时,返回的会是 400。

养成两个习惯,可以避免触发限流。精选结果最多每十分钟刷新一次;如果循环轮询速度更快,获取到的只是相同字节,而将响应内容缓存在本地无需成本。此外,只请求所需的数据:计算 20 个交易日的平均值时使用 LIMIT 20,不要请求一整年的数据后再丢弃大部分结果。网络故障(例如连接中断或 DNS 短暂异常)会以 requests.RequestException 的形式出现,辅助函数不会捕获这类错误;如果脚本需要无人值守运行,请将调用放在 try/except 中。

常见问题

Python 是否有免费的股票市场数据 API?

有。本指南中的演示端点可接收来自 requests 的免认证 GET 请求,并返回可直接加载到 pandas 的 JSON:/api/demo?q=<key> 提供整理好的查询,/api/demo/sql 提供手写的只读 SQL。每次最多返回 500 行数据,历史数据最长覆盖一年。无需密钥,也无需注册。

在 Python 中获取股票数据是否需要 API 密钥?

本指南使用的演示层级不需要。免费密钥可将上限提高到每天 100 次查询,并提供更长的历史数据;Python 代码无需修改。将相同端点封装为 LLM 工具的开发者,可以从 面向 AI 智能体的市场数据技能 开始。

如何将 JSON API 响应转换为 pandas DataFrame?

使用 resp.json() 解析响应内容,然后将行对象列表传递给 pd.DataFrame(rows, columns=columns)。在进行算术运算前,使用 pd.to_datetime 转换日期字符串,使用 pd.to_numeric 转换数字字符串;JSON 没有日期类型,一些 API 会将小数序列化为文本。

调用股票 API 时,429 错误意味着什么?

HTTP 429 表示请求过多,服务器正在对调用方实施限流。如果响应包含 Retry-After 标头,请等待其中给出的秒数;如果没有,则采用指数退避,并缓存响应,不要反复请求每十分钟才更新一次的结果。

如何在 pandas 中计算平均成交量?

按每个交易日加载一行数据,并包含 volume 列。保留最近 twenty 个完整交易日,然后调用 df["volume"].mean()。如需基于更长历史数据计算滚动平均值,请使用 df["volume"].rolling(20).mean();上方的跟踪面板就是这样绘制的。


上方每个面板下方都附有完全相同的 SQL,脚本的原理也相同,只是在前面加入 requests 调用。免费 API 密钥。每天 100 次查询,22 年历史数据,可对原始成交记录执行 SQL。无需银行卡。获取 API 密钥

#python#stock market api#free market data#requests#pandas#tutorial