【零依赖量化数据实战 #17】A 股公司基本面:5 个 URL 做个股画像
摘要:【零依赖量化数据实战 #17】A 股公司基本面:5 个 URL 做个股画像 系列:《零依赖量化数据实战》|零依赖 · 纯 GET · 不 import 任何 SDK 适用:想做 A 股个股基本面分析、股东结构追踪
系列:《零依赖量化数据实战》|零依赖 · 纯 GET · 不 import 任何 SDK
适用:想做 A 股个股基本面分析、股东结构追踪、基金持仓监控,不想装库或对接多个数据源的开发者
你将得到什么
- 5 个「公司基本面」端点的路径、返回字段和语义(公司简介 / 财务指标 / 十大股东 / 流通股东 / 基金持仓),照公开文档核对过的,不是猜的
- 一段
company_profile(code)把 5 个端点按股票代码一次性拉齐,做一张「个股基本面画像」 - 字段名容错写法(候选键命中),对字段名不敏感、对形态(list / dict)不敏感
- 完整可复制运行代码,把
你的智兔token换成真实智兔证书即可直接跑
一、五个端点,一张语义表
「个股基本面」拆成 5 个端点,路径前缀统一走 /hs/gs/,后面跟子类型 + 股票代码。
GET https://api.zhituapi.com/hs/gs/gsjj/{code} 公司简介(基本信息/行业/上市日)
GET https://api.zhituapi.com/hs/gs/cwzb/{code} 财务指标(ROE/PE/PB/营收等)
GET https://api.zhituapi.com/hs/gs/sdgd/{code} 十大股东(前十大持股明细)
GET https://api.zhituapi.com/hs/gs/ltgd/{code} 流通股东(流通股持股明细)
GET https://api.zhituapi.com/hs/gs/jjcg/{code} 基金持仓(公募基金持有该股的情况)
鉴权统一 ?token=<你的智兔token>。{code} 是路径参数(不是查询参数),换成股票代码(文档示例 600519)。gsjj/cwzb 返回的可能是聚合对象(dict),sdgd/ltgd/jjcg 返回的是列表(每条一个股东/一只基金)。
一个实际用法:输入一只股票代码 →
gsjj看公司是干什么的 →cwzb看财务是否健康 →sdgd/ltgd看谁在持股、有没有机构进/出 →jjcg看公募基金有没有在买。五步串起来就是一张「个股基本面画像」。
二、字段名不固定?用候选键命中
公司基本面接口的返回字段名可能因数据源更新而变化。与其硬编码字段名(改了就全崩),不如用候选键列表命中抽取:
def _hit_key(d, cands):
"""从字典中按候选键名列表命中第一个存在的键。"""
if not isinstance(d, dict):
return None
for k in cands:
if k in d:
return k
return None
def _to_float(v):
"""把可能是 '5.2%' / '5.2' / None 的值归一成 float。"""
if v is None:
return None
if isinstance(v, (int, float)):
return float(v)
s = str(v).replace("%", "").replace(",", "").strip()
try:
return float(s)
except ValueError:
return None
def summarize_gs(name, data):
"""从公司基本面返回中抽取关键字段做汇总,对字段名不敏感、对形态不敏感。"""
if data is None:
return f" {name}: 无数据"
if isinstance(data, list):
if not data:
return f" {name}: 0 条"
first = data[0] if isinstance(data[0], dict) else {}
code_key = _hit_key(first, ["code", "dm", "symbol", "gpdm"])
name_key = _hit_key(first, ["name", "mc", "gpmc", "shortname", "gdmc"])
amount_key = _hit_key(first, ["amount", "je", "cze", "cgs", "ltg", "hold"])
parts = [f" {name}: {len(data)} 条"]
if code_key:
parts.append(f"首条代码={first.get(code_key)}")
if name_key:
parts.append(f"名称={first.get(name_key)}")
if amount_key:
parts.append(f"金额/持股键={first.get(amount_key)}")
return " | ".join(parts)
if isinstance(data, dict):
keys = list(data.keys())[:6]
return f" {name}: 聚合对象,键={keys}"
return f" {name}: {type(data).__name__}"
这样就算数据源把 dm 改成 gpdm,代码不用动。命不中也不报错——只是那行汇总少一个字段。
三、个股画像:把 5 个端点按代码拉齐
核心不是「把五个接口都调通」,而是把同一只股票的 5 个维度数据归一后做基本面画像。我们关心两件事:
- 每个端点返回什么形态、首条/键长什么样(确认数据到位)
- 股东列表和基金持仓的条数对比(机构关注度信号)
import sys, time
import requests
TOKEN = "你的智兔token" # ← 换成你的真实 token
BASE = "https://api.zhituapi.com"
GS_ENDPOINTS = {
"gsjj": ("/hs/gs/gsjj/{code}", "公司简介"),
"cwzb": ("/hs/gs/cwzb/{code}", "财务指标"),
"sdgd": ("/hs/gs/sdgd/{code}", "十大股东"),
"ltgd": ("/hs/gs/ltgd/{code}", "流通股东"),
"jjcg": ("/hs/gs/jjcg/{code}", "基金持仓"),
}
def fetch(name, code, token=TOKEN, timeout=15):
"""拉一个公司基本面端点,返回 (data, err)。"""
path_tmpl, _ = GS_ENDPOINTS[name]
url = f"{BASE}{path_tmpl.format(code=code)}"
try:
r = requests.get(url, params={"token": token}, timeout=timeout)
except requests.RequestException as e:
return None, f"网络异常:{e}"
if r.status_code != 200:
return None, f"{r.status_code} {r.text.strip()[:140]}"
try:
payload = r.json()
except ValueError:
return None, f"非 JSON 返回:{r.text[:140]}"
if isinstance(payload, dict):
detail = payload.get("detail") or payload.get("error")
return None, f"业务错误:{detail or list(payload)[:6]}"
return payload, None
def company_profile(code, token=TOKEN, sleep=0.2):
"""拉 5 个公司基本面端点做一张「个股画像」快照。"""
if token == "你的智兔token":
print("[演示] TOKEN 为占位符,下面请求会返回错误码契约;请换成真实 token 后再跑。")
snap = {}
for name in GS_ENDPOINTS:
data, err = fetch(name, code, token=token)
if err:
print(f" {name} ({GS_ENDPOINTS[name][1]}): 暂不可用:{err}")
snap[name] = None
else:
print(summarize_gs(name, data))
snap[name] = data
time.sleep(sleep)
return snap
sleep=0.2 是限频保护——5 个端点 + 0.2 秒间隔 ≈ 1 秒,远在限频内。要扫描多只股票,把 company_profile 套一层代码循环,外面再撑一个更大的间隔即可。
四、代码自验结果
离线自测 6 项全 PASS(真实输出,不联网):
selftest PASS: 端点注册完整(5/5) / _hit_key 命中 / _to_float 去百分号 / 列表汇总 / 空数据兜底 / 聚合对象处理 共 6 项
联网跑(占位 token,以 600519 为例)真实输出:
[演示] TOKEN 为占位符,下面请求会返回错误码契约;请换成真实 token 后再跑。
gsjj (公司简介): 暂不可用:404 102:Licence证书(你的智兔token)不存在
cwzb (财务指标): 暂不可用:404 102:Licence证书(你的智兔token)不存在
sdgd (十大股东): 暂不可用:404 102:Licence证书(你的智兔token)不存在
ltgd (流通股东): 暂不可用:404 102:Licence证书(你的智兔token)不存在
jjcg (基金持仓): 暂不可用:404 102:Licence证书(你的智兔token)不存在
5 个端点契约一致,单端点失败不影响整体流程、程序友好退出。本文未编造任何公司简介/财务/股东数据——换成覆盖该接口的正式证书后重跑,即可打印真实个股基本面画像。
五、坑与注意事项
坑 #1:102 不代表路径写对了。 鉴权发生在路由匹配之前。把路径故意写错配无效 token,同样返回 102。路径合法性只能靠客户端按白名单自查——本文 5 个路径是照公开文档核对的。
坑 #2:{code} 是路径参数,不是查询参数。 5 个端点的股票代码都拼在 URL 路径里(/hs/gs/gsjj/600519),不是 ?code=600519。写成查询参数会拿到 404 路径不存在,不是 102 证书错误——看到 404 但没有 102: 前缀,先查路径格式。
坑 #3:gsjj/cwzb 可能返回聚合对象(dict),sdgd/ltgd/jjcg 返回列表。 公司简介和财务指标通常是一条聚合数据(一个 dict),股东和基金持仓是多条列表。summarize_gs 对两种形态都做了兜底——dict 时打印键列表,list 时打印条数 + 首条字段。别假设所有端点返回同一种形态。
坑 #4:十大股东和流通股东不是一回事。 sdgd(十大股东)是按总股本排序的前十大股东,ltgd(流通股东)是按流通股排序的。同一只股票两者的名单可能不同——大股东持有的可能不全是流通股(限售股不在流通股东里),流通股东里排前的可能是散户或机构但不是总股本前十。
小结与下篇预告
这篇你拿到了 5 个公司基本面端点(简介/财务/十大股东/流通股东/基金持仓)、股票代码路径参数处理写法、以及一个把 5 维拉齐做个股基本面画像的模板,顺带避开了 102 误判、路径参数误写查询参数、返回形态不统一、十大股东与流通股东混淆四个坑。
至此,#14–#17 形成了一条从市场异动到公司面的分析链:日内异动池(#14) → 可转债全貌(#15) → 港股通资金面(#16) → 公司基本面画像(#17)。
下一篇(#18)讲北交所实时行情与清单全貌——用 /bj/list/all(北交所全市场清单)+ /bj/stock/real/ssjy/{code}(单只北交所股票实时交易)+ /bj/index/real/ssjy/{code}(北交所指数实时交易),把视角从「单只股票基本面」升级到「北交所全市场盯盘」,把分析链延伸到交易所层面。
免费领取证书
数据来自 智兔数服(www.zhituapi.com):零 SDK、纯 GET、免费版即可起步。
领取路径:进入 www.zhituapi.com → 点击「请求证书」→「证书获取」→「免费版」(邮箱验证 3 步即可拿到 token)。
把代码里的 你的智兔token 换成你拿到的真实 token,上面的脚本就能直接打印公司基本面数据。
免责声明
本文仅演示公开数据接口的用法,所有代码示例均以占位 token 自验,未含任何真实数据;文中合成数据仅为逻辑自验用途,不构成投资建议,亦不承诺收益。