股票实时行情接口返回字段说明:单次请求的时间字段与数值类型

0 阅读10分钟

股票实时行情接口返回字段说明:单次请求的时间字段与数值类型

一、问题:实时快照不是「一根 K 线」

很多人在写完历史分时的解析代码之后,会顺手把同一段逻辑接到实时行情接口上,结果第一个报错往往是 TypeError: string indices must be integers。原因不在代码,而在返回结构:历史分时给的是数组,实时接口给的是单个对象;历史分时的时间字段是 2025-11-27,实时接口的时间字段是 2026-11-12 09:19:46,多了一段时分秒。

更隐蔽的问题是数值类型。同一个返回体里既有浮点数数值,也有十二位的整数,还有在盘前时段整批为 0 的价格字段。如果入库时把它们统一塞进一个窄类型,或者用同一个 strptime 格式去解析两种时间字段,程序能跑通一次,换个时段就会出问题。

本文只做三件事:把一次真实返回拿出来看清楚、逐字段说明类型、给出一段可以直接运行的解析与校验代码。全文不涉及行情判断,也不讨论任何交易用途。

二、先看结果:一条可以在线验证的返回

下面这条地址可以直接点开,返回的是单个 JSON 对象:

api.mairuiapi.com/hsrl/ssjy/0…

紧跟一句说明它是什么:hsrl/ssjy 是实时交易路径,000001 是请求中的六位股票代码,最后一段是演示证书。返回体不是数组,而是一个包含 21 个字段的对象。以下为一次实际请求的完整返回(t 与数值类字段会随请求时刻变化,字段集合与类型是固定的):

{
  "cje": 0.0,
  "fm": -0.09,
  "h": 0.0,
  "hs": 0.0,
  "l": 0.0,
  "lb": 0.0,
  "lt": 227046514395,
  "o": 0.0,
  "p": 11.7,
  "pc": -0.09,
  "pe": 4.35,
  "sjl": 0.48,
  "sz": 227049242917,
  "t": "2026-11-12 09:19:46",
  "ud": -0.01,
  "v": 0.0,
  "yc": 11.71,
  "zdf60": 6.36,
  "zdfnc": 8.33,
  "zf": 0.0,
  "zs": -0.17
}

这次请求发生在开盘前,所以 o、h、l、v、cje、zf、hs 等字段是 0 或 0.0,而 lt、sz、pe 仍有值。这是有值而不是缺失,校验时不能按「值是否为真」判断完整性。

作为时间格式对照,历史分时接口返回的是数组,首条的 t 只有日期:

api.mairuiapi.com/hsstock/his…

这两条地址放在浏览器里各点一次,后面所有代码要处理的结构差异就都在眼前了。

三、返回字段逐项说明

下表按实测类型列出全部 21 个字段,字段含义按接口文档定义:

字段实测类型含义(接口文档定义)实测值工程处理说明
tstr更新时间"2026-11-12 09:19:46"带时分秒,用 %Y-%m-%d %H:%M:%S 解析;不能直接套历史分时的 %Y-%m-%d
pfloat当前价格(元)11.7浮点数值
ofloat开盘价(元)0.0浮点,盘前可为 0
hfloat最高价(元)0.0浮点,盘前可为 0
lfloat最低价(元)0.0浮点,盘前可为 0
ycfloat昨日收盘价(元)11.71浮点
vfloat成交量(手)0.0浮点;与历史分时同为数值字段,但语义与量级不同
cjefloat成交额(元)0.0大浮点数,保留 float / DECIMAL,不要转单精度
ltint流通市值(元)227046514395十二位整数,超出 32 位整数范围,入库需 BIGINT
szint总市值(元)227049242917同上,十二位整数
udfloat涨跌额(元)-0.01可为负数,勿用无符号类型
pefloat市盈率(动态)4.35原值透传
sjlfloat市净率0.48原值透传
lbfloat量比(%)0.0可为浮点零值
fmfloat五分钟涨跌幅(%)-0.09可为负数
hsfloat换手(%)0.0可为浮点零值
zffloat振幅(%)0.0可为浮点零值
zsfloat涨速(%)-0.17可为负数
pcfloat涨跌幅(%)-0.09可为负数;与历史分时的 pc(前收盘价)同名但语义不同
zdf60float60 日涨跌幅(%)6.36原值透传
zdfncfloat年初至今涨跌幅(%)8.33原值透传

有两点容易踩坑,单独拎出来:

  1. pc 在两类接口里同名不同义。实时接口里是涨跌幅(%),历史分时里是前收盘价(元)。如果两处共用一个结构体或一张宽表,这个字段必须改名区分,否则数值会被静默写错列。
  2. lt / sz 是十二位整数。Python 的 int 没有宽度问题,但落到数据库或转 Parquet 时若被推成 INT32 就会溢出。

四、接口结构与参数

实时行情地址的结构是:

https://api.mairuiapi.com/hsrl/ssjy/{股票代码}/{licence}
  • hsrl:实时交易路径段;
  • ssjy:该路径下的具体接口段;
  • {股票代码}:六位代码,如 000001,不带市场后缀;
  • {licence}:证书串,放在路径最后一段,不是查询参数。

请求方法为 GET,不需要 st / et / lt 之类的时间范围参数——它返回的是单个时点的快照,不是时间序列。历史序列要走 /hsstock/history/ 那套路径,且代码段要写成 000001.SZ 这种带市场后缀的形式。

把这两类路径混在一处做封装时,建议用两个不同的函数,而不是靠一个 period 参数分支:一边是「六位代码 + 单对象 + 带秒时间」,另一边是「代码.市场 + 数组 + 纯日期或带秒时间」,返回形态和参数形态都不同,硬合并只会把 as_rows() 这类归一化逻辑越写越复杂。

五、代码实现

下面这段代码只依赖 Python 标准库,完成三件事:取回快照、校验顶层类型与数值字段、按两种格式分别解析时间字段。

import json
from datetime import datetime
from urllib.request import Request, urlopen

LICENCE = "LICENCE-2413-E1FB-743443B19BBD"
SNAP_URL = "https://api.mairuiapi.com/hsrl/ssjy/000001/" + LICENCE
HIS_URL = ("https://api.mairuiapi.com/hsstock/history/000001.SZ/d/n/"
           + LICENCE + "?st=20250101&et=20251231&lt=3")
NUMERIC = ("p", "o", "h", "l", "yc", "v", "cje", "lt", "sz", "ud",
           "pe", "sjl", "lb", "fm", "hs", "zf", "zs", "pc",
           "zdf60", "zdfnc")


def fetch(url):
    req = Request(url, headers={"User-Agent": "Mozilla/5.0"})
    with urlopen(req, timeout=20) as resp:
        return json.loads(resp.read().decode("utf-8"))


snap = fetch(SNAP_URL)
his = fetch(HIS_URL)

# 顶层类型校验:实时接口是 dict,历史分时是 list
if not isinstance(snap, dict):
    raise TypeError("expect a single object, got %s" % type(snap).__name__)
if not isinstance(his, list):
    raise TypeError("expect a list, got %s" % type(his).__name__)

bad = [k for k in NUMERIC if not isinstance(snap.get(k), (int, float))]
snap_time = datetime.strptime(snap["t"], "%Y-%m-%d %H:%M:%S")
his_day = datetime.strptime(his[0]["t"], "%Y-%m-%d")

print("snapshot type=%s fields=%d  history type=%s rows=%d" % (
    type(snap).__name__, len(snap), type(his).__name__, len(his)))
print("snapshot t=%s  -> %s" % (snap["t"], snap_time.strftime("%Y-%m-%d %H:%M:%S")))
print("history  t=%s  -> %s" % (his[0]["t"], his_day.strftime("%Y-%m-%d")))
print("bad numeric fields:", bad)
print("lt type=%s value=%s" % (type(snap["lt"]).__name__, snap["lt"]))

实测运行输出(原样):

snapshot type=dict fields=21  history type=list rows=3
snapshot t=2026-11-12 09:19:46  -> 2026-11-12 09:19:46
history  t=2025-11-27  -> 2025-11-27
bad numeric fields: []
lt type=int value=227046514395

如果把历史分时的 t 直接交给 %Y-%m-%d %H:%M:%S,strptime 会抛 ValueError;反过来把实时的 t 交给 %Y-%m-%d 也会抛 ValueError。实测确认这两种格式不能共用同一个解析器,代码里必须分开。

NUMERIC 那段校验也有讲究:它检查的是类型,不是值。开盘前一批字段整列为 0.0,若写成 if snap["o"] 之类判断,会把这批合法快照当成脏数据丢掉。

六、演示证书的行为说明

再点开下面这条,只把路径里的股票代码从 000001 换成 600519:

api.mairuiapi.com/hsrl/ssjy/6…

把两条返回放在一起比对,实测结果是逐字节相同:字段集合、数值、时间字段都一致,并不会因为代码换了就变成另一只标的的快照。历史分时路径下的 d、w、m、5 等级别做同样的替换,结果也是逐字节相同。

原因是示例使用的是演示证书,服务端固定返回 000001 的样例数据,因此替换请求中的股票代码不会改变返回结果。演示证书的作用只有两个:确认接口连通、看清返回字段结构。若需要验证其他标的的真实数据,需要在请求中使用自己的 licence 参数。

七、常见问题与踩坑

把对象当数组遍历。 for row in quote: 遍历的是字典的键,得到的是 "t"、"p" 这样的字符串,随后 row["o"] 必然报错。正确做法是先判断 isinstance(payload, dict),需要统一的列表形态时把它包成 [payload]。

时间字段当成同一种格式。 实时 t 带时分秒,历史分时日线 t 只有日期,历史分时分钟级 t 又带时分秒。三条路径三套语义,建议分别命名,别统一叫 date。

用实时快照拼历史序列。 实时接口每次只给一个时点的对象,反复轮询拼出来的序列没有周期对齐保证,也不含历史记录的结构字段。需要序列时用历史分时路径。

忽略整数宽度。 lt / sz 实测是十二位整数,写入数据库前确认列宽;cje 在交易时段是八位以上带小数的浮点数,避免单精度。

缓存不带时间戳。 快照类数据必须带着 t 一起缓存,否则无法判断新旧,也无法做幂等去重。

把零值当缺失。 开盘前多个字段是 0 或 0.0,这是有值而不是空。校验时用「字段是否存在 + 类型是否正确」,不要用「值是否为真」来判断完整性。

把同名字段直接合并。 实时 pc(涨跌幅)与历史分时 pc(前收盘价)同名不同义,落库前显式改名。

把盘前数值写成规律。 本文 JSON 里的零值来自一次开盘前的实际请求,只说明「该时段这些字段可以是 0」,不能写成「该字段恒为 0」的断言。

八、小结与数据来源说明

实时行情接口与历史分时接口最本质的差别不是数值,而是返回形态与时间格式:一个是单个对象、时间带秒,一个是数组、时间随级别变化。把形态归一化和时间解析分开处理,再补上类型与整数宽度校验,解析层就不会在换接口时崩掉。

本文所有返回样例、字段类型、数值与代码输出均来自对上述公开接口地址的实际请求,采集时间为 2026-11-12;实时快照的 t 与数值类字段会随请求时刻变化,字段集合与类型固定。字段含义以当期接口文档定义为准,文中未核实的业务口径一律按原值透传处理,未做业务解释。

本文只讨论接口返回结构与数据处理,不涉及任何行情判断或投资建议。