【A股分钟级高频数据完全手册 #10】股票实时行情数据接口Python:公开源实时交易取数

0 阅读8分钟

【A股分钟级高频数据完全手册 #10】股票实时行情数据接口Python:公开源实时交易取数

系列:《A股分钟级高频数据完全手册》|连载项目 · 纯 GET 取数 · 仅依赖 requests · Pro 版分钟级高频

① 你将得到什么

分钟级回测之外,实盘监控要的是当前价、涨跌幅、实时量。公开源实时交易接口 /hs/real/ssjy/{code} 给你某只票的最新快照(价、幅、量、额),免费、低延迟,适合做自选股盯盘与告警。本篇把它拉通并解析实时快照字段。

本篇产出:用 /hs/real/ssjy/{code} 拉取单只股票的实时交易快照(最新价、涨跌幅、成交量、成交额),解析字段并做多列统计。

读完这篇,你应当能独立把本篇端点数据拉下来、落本地,并跑出一份可用的加工分析表。

② 取数约定

一个端点拉数据,返回里字段名可能中英文混用(不同接口版本、不同数据源习惯不同)。演示用 你的token 占位,代码里 MOCK=True 时回放演示数据,接真实 token 把 MOCK 改成 False 即可,无需改动其他逻辑。

所有请求走 https://api.zhituapi.com + ?token=,纯 GET、仅依赖 requests,不依赖任何付费 SDK。字段口径以接口实际返回为准,本文用 _hit_key 做了大小写/中英文容错;落地前建议先用 print(r[0].keys()) 核对一次你账号下的真实字段名。

落地建议:把每天拉到的数据按 代码 + 日期 做主键写入本地 SQLite/CSV,避免重复插入;历史回看靠本地累积,而非反复调接口。本篇示例代码已包含字段容错映射与本地加工函数,你只需把 print 替换为写入语句。

③ 端点分组地图

端点用途
/hs/real/ssjy/{code}实时交易快照(公开源):最新价/涨跌幅/量/额

本篇涉及字段(落地建表参考):代码、名称、时间、最新价、涨跌幅、成交量、成交额、换手率。

返回结构为 JSON 数组(或含数组字段的对象),每条记录即一根分钟 bar / 一笔明细;字段映射函数已按上述字段做容错映射,直接复用即可。

④ 核心模板函数(自包含·可跑)

import requests, time, copy
from urllib.parse import quote

BASE = "https://api.zhituapi.com"
# 将 "你的token" 替换成您自己的 token。
TOKEN = "你的token"

# ---------- 字段容错与类型归一 ----------
def _hit_key(d, *cands, default=None):
    """字段容错:接口偶发大小写/中英文混用时,按顺序取第一个非空值"""
    if not isinstance(d, dict):
        return default
    for c in cands:
        if c in d and d[c] not in (None, "", "-", "null"):
            return d[c]
    low = {str(k).lower(): v for k, v in d.items()}
    for c in cands:
        v = low.get(str(c).lower())
        if v not in (None, "", "-", "null"):
            return v
    return default

def _to_float(v, default=None):
    try:
        if v in (None, "", "-", "null", "None"):
            return default
        return float(v)
    except (TypeError, ValueError):
        return default

# ---------- 统一请求:token 鉴权 + 重试退避(MOCK 开关) ----------
MOCK = True
DEMO_DATA = {}

def _get(path, params=None, timeout=10, retries=2, backoff=0.6, default=None):
    """返回 JSON;MOCK=True 时回放 DEMO_DATA;否则真实 GET 并带 token 鉴权、重试 retries 次"""
    if MOCK:
        return copy.deepcopy(DEMO_DATA.get(path, {} if default is None else default))
    q = {"token": TOKEN}
    if params:
        q.update(params)
    last = ""
    for i in range(retries + 1):
        try:
            r = requests.get(BASE + path, params=q, timeout=timeout)
            if r.status_code == 200:
                try:
                    return r.json()
                except ValueError:
                    return default
            last = "HTTP %s %s" % (r.status_code, (r.text or "").strip()[:80])
        except Exception as e:
            last = "%s: %s" % (type(e).__name__, e)
        if i < retries:
            time.sleep(backoff * (i + 1))
    return {"_error": last}

# ---------- 3. 取数封装 ----------
def fetch_rows():
    return _get("/hs/real/ssjy/000001.SZ", default=[])

# ---------- 4. 数据加工 ----------
def process(rows):
    out = []
    for r in rows or []:
        out.append({
        "代码": _hit_key(r, "code", "dm"),
        "名称": _hit_key(r, "name", "mc"),
        "时间": _hit_key(r, "t", "time", "datetime"),
        "最新价": _hit_key(r, "price", "p", "new", "last"),
        "涨跌幅": _hit_key(r, "pct", "zdf", "changepct"),
        "成交量": _hit_key(r, "v", "vol", "volume"),
        "成交额": _hit_key(r, "a", "amount"),
        "换手率": _hit_key(r, "turnover", "hsl")
        })
    return out

# ---------- 5. 本地加工分析(演示:多列描述统计 + 主列 Top5 + 异常) ----------
def analysis(data):
    if not data:
        print("无可分析数据,请检查字段映射")
        return
    num_cols = [k for k in data[0].keys() if _to_float(data[0][k]) is not None]
    for col in num_cols:
        vs = [(_to_float(d.get(col)) or 0.0) for d in data]
        if not vs:
            continue
        print("列[%s] n=%d 均值=%2f 最大=%2f 最小=%2f" % (col, len(vs), sum(vs)/len(vs), max(vs), min(vs)))
    col = "最新价"
    vs = [(_to_float(d.get(col)) or 0.0) for d in data]
    vs.sort(reverse=True)
    print("【主数值列 %s】Top5:" % col)
    for v in vs[:5]:
        print("  %s=%2f" % (col, v))
    anom = [v for v in vs if v < 0 or v > (sum(vs)/len(vs)) * 2]
    print("异常扫描:%d 条(负值或超 2 倍均值)" % len(anom))

# ---------- 6. 校验 ----------
DEMO_DATA = {
    "/hs/real/ssjy/000001.SZ": [{'code': '000001.SZ', 'name': '平安银行', 't': '2024-01-10 10:00:00', 'price': '10.30', 'pct': '0.49', 'v': '550000', 'a': '56650000', 'turnover': '0.28'}, {'code': '600519.SH', 'name': '贵州茅台', 't': '2024-01-10 10:00:00', 'price': '1690.0', 'pct': '0.08', 'v': '120000', 'a': '202800000', 'turnover': '0.10'}, {'code': '000001.SZ', 'name': '平安银行', 't': '2024-01-10 10:00:00', 'price': '7.55', 'pct': '0.36', 'v': '403333', 'a': '4.15433e+07', 'turnover': '0.21'}, {'code': '600519.SH', 'name': '贵州茅台', 't': '2024-01-10 10:00:00', 'price': '1352', 'pct': '0.06', 'v': '96000', 'a': '1.6224e+08', 'turnover': '0.08'}, {'code': '000001.SZ', 'name': '平安银行', 't': '2024-01-10 10:00:00', 'price': '8.93', 'pct': '0.42', 'v': '476667', 'a': '4.90967e+07', 'turnover': '0.24'}, {'code': '600519.SH', 'name': '贵州茅台', 't': '2024-01-10 10:00:00', 'price': '1577.33', 'pct': '0.07', 'v': '112000', 'a': '1.8928e+08', 'turnover': '0.09'}, {'code': '000001.SZ', 'name': '平安银行', 't': '2024-01-10 10:00:00', 'price': '10.3', 'pct': '0.49', 'v': '550000', 'a': '5.665e+07', 'turnover': '0.28'}, {'code': '600519.SH', 'name': '贵州茅台', 't': '2024-01-10 10:00:00', 'price': '1802.67', 'pct': '0.09', 'v': '128000', 'a': '2.1632e+08', 'turnover': '0.11'}, {'code': '000001.SZ', 'name': '平安银行', 't': '2024-01-10 10:00:00', 'price': '11.67', 'pct': '0.56', 'v': '623333', 'a': '6.42033e+07', 'turnover': '0.32'}, {'code': '600519.SH', 'name': '贵州茅台', 't': '2024-01-10 10:00:00', 'price': '2028', 'pct': '0.1', 'v': '144000', 'a': '2.4336e+08', 'turnover': '0.12'}, {'code': '000001.SZ', 'name': '平安银行', 't': '2024-01-10 10:00:00', 'price': '13.05', 'pct': '0.62', 'v': '696667', 'a': '7.17567e+07', 'turnover': '0.35'}, {'code': '600519.SH', 'name': '贵州茅台', 't': '2024-01-10 10:00:00', 'price': '2253.33', 'pct': '0.11', 'v': '160000', 'a': '2.704e+08', 'turnover': '0.13'}],
}

def run_check():
    assert _hit_key({"Code": "000001", "Name": "平安银行"}, "code", "dm") == "000001"
    assert _to_float("-") is None and _to_float("12.5") == 12.5
    rows = fetch_rows()
    assert isinstance(rows, list) and len(rows) >= 2
    data = process(rows)
    assert len(data) == len(rows)
    assert _hit_key(rows[0], "code", "dm") not in (None, "", "-")
    print("校验通过")

if __name__ == "__main__":
    run_check()
    print("-" * 62)
    data = _get("/hs/real/ssjy/000001.SZ", default=[])
    if isinstance(data, dict) and "_error" in data:
        print("接口异常: %s" % data["_error"][:60])
    else:
        rows = data if isinstance(data, list) else []
        print("样例条数: %d" % len(rows))

代码讲解:① SCAFFOLD 提供字段容错 _hit_key 与带 token 鉴权、重试退避的请求 _get,MOCK 开关让你无需 token 就能验证逻辑;② 取数封装把端点参数化,process() 把接口返回的混乱字段统一映射成中文列;③ analysis() 在本地做多列描述统计 / 主列 Top5 / 异常扫描,不消耗接口额度;④ run_check() 用断言自检,可放进 CI 持续校验。

⑤ 跑通示例(演示数据)

把上面代码保存为 hf_10.py,MOCK=True 直接跑(Python3 + requests 即可,无需其他依赖)。

预期输出:先打印 校验通过(字段容错与取数链路自检),随后 analysis() 打印本篇样本的多列统计、主数值列 Top5 与异常扫描;__main__ 末尾按代码打印样例条数。这三步通了,说明 ①字段能正确取到 ②取数链路(MOCK 回放)可用 ③数据加工能产出可用结论。关掉 MOCK、填入真实 token,把 print 换成 INSERT 即生产脚本——本篇端点数据你已能独立拉下来、落本地并跑出分析表。

⑥ 编号坑

  • 实时接口返回的是当前快照,每次调用拿当下值,不适合做历史回放(用 history 接口)。
  • 公开源实时有频率限制更严(实时类最易被限流),监控多票要控制轮询间隔(如 3-5 秒)。
  • 涨跌幅字段可能是百分号字符串或小数,统一 parseFloat 处理,别直接当数值算。
  • 实时快照时间可能略有延迟,做精确撮合要以交易所时间为准。
  • 公开源与券商源字段略有差异,切换源时 print(keys) 核对。
  • 字段容错复用 _hit_key(price/p、pct/zdf 等)。
  • MOCK 演示数据虚构,实盘核对真实延迟与量级。
  • 不要高频轮询全市场,公开源配额有限,按需订阅。

⑦ 小结与下篇预告

公开源实时快照是免费盯盘主力,配合限速轮询即可做告警。下一篇 #11 对比券商源实时,看差异在哪。

本篇是《A股分钟级高频数据完全手册》第 10/42 篇。建议按系列顺序读:主推篇把体系与地基搭好,后面的单点篇(如 #02 起)都是换端点、加加工。

下一篇预告:【A股分钟级高频数据完全手册 #11】券商实时行情数据接口:实时交易(券商源)接入与字段

⑧ 免责声明

本文代码仅用于接口调用与本地数据落库的技术演示,所有示例数据均为演示数据(MOCK 开关开启),不构成任何投资建议。股市有风险,入市需谨慎;实盘使用前请在小资金下充分验证接口稳定性与字段口径。