"股小仙"系列第四篇,也是最短的一篇——讲一段只有 20 行的代码。但这段代码浓缩了三个值得展开的点:一个不稳定官方接口的替代品怎么选、一个"反直觉编码"的坑、一个用魔法下标从密文里取数的黑科技。行情数据是持仓系统的最后一块拼图:没有现价,可卖判定和浮盈都是死的。
一、行情源选型:为什么是腾讯
个人系统接行情,候选无非几个:
| 方案 | 门槛 | 稳定性 |
|---|---|---|
| 券商/同花顺官方接口 | 要登录态/签名 | 随时变(随手记系列就是血泪) |
| Tushare 等聚合库 | 注册积分制 | 接口配额变动频繁 |
| 新浪/腾讯公开行情 | 无需任何 key | 多年稳定,非官方但事实标准 |
选了腾讯qt.gtimg.cn。理由很功利:无需注册、无配额、一次 HTTP GET、字段里直接带 52 周高低价——最后一点是决定性的,因为波段计划(A3 篇)需要 52 周分位,其他免费源大多只给现价。
风险也明说:这是非官方接口,没有 SLA,字段位置可能变。对策放在代码里——后面讲容错降级。
二、市场前缀:一行代码的市场判断
prefix='sh'ifstock_code.startswith(('6','5'))else'sz'url=f"https://qt.gtimg.cn/q={prefix}{stock_code}&_={int(datetime.now().timestamp())}000"A股代码首位数字决定交易所归属:6 开头沪市主板、5 开头沪市 ETF/基金,其余(0、3 开头)默认深市。这个映射不完美(1 开头的深市基金会被误判),但对"主板股票 + 场内 ETF"的个人持仓场景,一行startswith(('6','5'))足够——代码的精度应该匹配场景的精度,而不是匹配通用性。
URL 末尾的时间戳参数_=是经典防缓存手法:行情接口的响应会被中间层缓存,加个每次都变的参数强制回源。
三、GBK:一个以为灭绝了的编码
resp=urlopen(url,timeout=5)raw=resp.read().decode('gbk')2026 年了,还有接口用 GBK?有,而且是大厂的老接口——历史包袱让它没法改编码。坑点在于:不指定编码时 Python 默认按 UTF-8 解,报错还算幸运;更阴的是部分字节恰好合法,解出来中文名成乱码但数字字段正常——股价对、股票名是"鎷熷皬鍏?",你以为只是显示问题,其实是整段解析已经不可信。
所以decode('gbk')不能省,而且要显式写。顺带一提:券商导出的 xls 也是 GBK(A1 篇会再遇到它),国内金融数据的 GBK 渗透率远超想象。
四、波浪号密文:68 个字段与魔法下标
响应长这样(截断):
v_sh600900="1~长江电力~600900~27.50~27.32~27.35~...(共 68 个字段)~33.10~24.80";按~切开取数:
parts=raw.split('~')iflen(parts)>68:price=float(parts[3])high_52w=float(parts[67])ifparts[67]else0.0low_52w=float(parts[68])ifparts[68]else0.0return(price,high_52w,low_52w)eliflen(parts)>3:return(float(parts[3]),0.0,0.0)# 降级:只有现价值得说的三件事:
1. 字段位置是"考古"出来的。这个接口没有官方文档,全网流传的字段表是无数人抓包对照试出来的:parts[3]现价、parts[67]52 周高、parts[68]52 周低。用魔法下标前必须做长度校验(len(parts) > 68)——接口一改版,数组变短,没有校验就是 IndexError 裸奔。
2. 两级降级设计。68 字段齐 → 返回完整三元组;只有基本字段 → 返回现价 + 52 周置零;解析彻底失败 → 返回(0.0, 0.0, 0.0)。下游(A3 篇的分位计算)对 52 周为零的情况已经写了回退逻辑(退回用自有成交价算分位)——每一层都假设上游可能残缺,残缺时给出诚实的零值而不是抛异常。
3.if parts[67] else 0.0的空串防御。某些字段在特定状态下是空字符串(停牌、新股上市不足 52 周),float('')会炸。这个三元表达式是数据清洗的最小防御工事。
五、超时与告警:免费接口的正确姿势
try:resp=urlopen(url,timeout=5)...exceptExceptionase:print(f"警告: 获取股价失败 -{e}",file=sys.stderr)return(0.0,0.0,0.0)三个细节:超时必须设(免费接口偶发挂起,不设超时整个分析管线会卡死在行情上);异常打到stderr(stdout 留给结构化结果,警告走错误流,重定向时互不污染);失败返回零值元组而不是 None(调用方不需要判空,三元组解构永远成立)。
免费接口的契约是"尽力而为"——代码结构要承认这一点:超时、降级、零值返回,三件套缺一不可。
六、收尾:20 行代码的三条原则
- 精度匹配场景:市场前缀一行判断,不追求通用正确;
- 假设上游残缺:长度校验、空串防御、两级降级、零值返回;
- 失败不传染:行情挂了不该挂掉整个分析,stderr 告警 + 零值继续跑。
这个小模块是整个系统里最不起眼的,但把它写稳之后,两年里没为它改过一行——稳定性是给"不起眼的代码"的最高勋章。
系列导航
- A1 数据底座:券商流水入库与表结构(排期中)
- A2 买卖匹配引擎:两轮匹配、贪心排序与幂等键的坑
- A3 网格层健康检测与波段加仓计划
- A4(本篇):免费行情接入