股票实时行情接口返回字段说明:单次请求的时间字段与数值类型
一、问题:实时快照不是「一根 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 与数值类字段会随请求时刻变化,字段集合与类型是固定的):
json
{
"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 个字段,字段含义按接口文档定义:
| 字段 | 实测类型 | 含义(接口文档定义) | 实测值 | 工程处理说明 |
|---|---|---|---|---|
t |
str |
更新时间 | "2026-11-12 09:19:46" |
带时分秒,用 %Y-%m-%d %H:%M:%S 解析;不能直接套历史分时的 %Y-%m-%d |
p |
float |
当前价格(元) | 11.7 |
浮点数值 |
o |
float |
开盘价(元) | 0.0 |
浮点,盘前可为 0 |
h |
float |
最高价(元) | 0.0 |
浮点,盘前可为 0 |
l |
float |
最低价(元) | 0.0 |
浮点,盘前可为 0 |
yc |
float |
昨日收盘价(元) | 11.71 |
浮点 |
v |
float |
成交量(手) | 0.0 |
浮点;与历史分时同为数值字段,但语义与量级不同 |
cje |
float |
成交额(元) | 0.0 |
大浮点数,保留 float / DECIMAL,不要转单精度 |
lt |
int |
流通市值(元) | 227046514395 |
十二位整数,超出 32 位整数范围,入库需 BIGINT |
sz |
int |
总市值(元) | 227049242917 |
同上,十二位整数 |
ud |
float |
涨跌额(元) | -0.01 |
可为负数,勿用无符号类型 |
pe |
float |
市盈率(动态) | 4.35 |
原值透传 |
sjl |
float |
市净率 | 0.48 |
原值透传 |
lb |
float |
量比(%) | 0.0 |
可为浮点零值 |
fm |
float |
五分钟涨跌幅(%) | -0.09 |
可为负数 |
hs |
float |
换手(%) | 0.0 |
可为浮点零值 |
zf |
float |
振幅(%) | 0.0 |
可为浮点零值 |
zs |
float |
涨速(%) | -0.17 |
可为负数 |
pc |
float |
涨跌幅(%) | -0.09 |
可为负数;与历史分时的 pc(前收盘价)同名但语义不同 |
zdf60 |
float |
60 日涨跌幅(%) | 6.36 |
原值透传 |
zdfnc |
float |
年初至今涨跌幅(%) | 8.33 |
原值透传 |
有两点容易踩坑,单独拎出来:
pc在两类接口里同名不同义。实时接口里是涨跌幅(%),历史分时里是前收盘价(元)。如果两处共用一个结构体或一张宽表,这个字段必须改名区分,否则数值会被静默写错列。lt/sz是十二位整数 。Python 的int没有宽度问题,但落到数据库或转 Parquet 时若被推成INT32就会溢出。
四、接口结构与参数
实时行情地址的结构是:
text
https://api.mairuiapi.com/hsrl/ssjy/{股票代码}/{licence}
hsrl:实时交易路径段;ssjy:该路径下的具体接口段;{股票代码}:六位代码,如000001,不带市场后缀;{licence}:证书串,放在路径最后一段,不是查询参数。
请求方法为 GET,不需要 st / et / lt 之类的时间范围参数------它返回的是单个时点的快照 ,不是时间序列。历史序列要走 /hsstock/history/ 那套路径,且代码段要写成 000001.SZ 这种带市场后缀的形式。
把这两类路径混在一处做封装时,建议用两个不同的函数,而不是靠一个 period 参数分支:一边是「六位代码 + 单对象 + 带秒时间」,另一边是「代码.市场 + 数组 + 纯日期或带秒时间」,返回形态和参数形态都不同,硬合并只会把 as_rows() 这类归一化逻辑越写越复杂。
五、代码实现
下面这段代码只依赖 Python 标准库,完成三件事:取回快照、校验顶层类型与数值字段、按两种格式分别解析时间字段。
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<=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"]))
实测运行输出(原样):
text
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 与数值类字段会随请求时刻变化,字段集合与类型固定。字段含义以当期接口文档定义为准,文中未核实的业务口径一律按原值透传处理,未做业务解释。
本文只讨论接口返回结构与数据处理,不涉及任何行情判断或投资建议。