用Python雪球接口pysnowball搭建个人投资数据监控:一份含踩坑记录的新手完整实战指南
【免费下载链接】pysnowball雪球股票数据接口 python edition项目地址: https://gitcode.com/gh_mirrors/py/pysnowball
先问个扎心的问题:你每天花在行情软件上来回翻页、挨个查自选股、再打开基金APP记净值的时间,加起来够不够看两集电视剧?
我过去就是这样。收盘后掏出手机,从自选股列表第一只翻到最后一只,截图、手抄到Excel里,隔几天还要去翻财报数据算指标。坚持了两个月,Excel开了七八个Sheet,真正用上的信息不到两成,还经常因为漏记某天的净值把曲线画断。直到我发现了 pysnowball——一个把雪球股票数据接口封装成几十个Python函数的开源库,从此我的"盘后流程"从手动两小时变成了脚本十秒。
这篇文章不是教程搬运,而是我用它搭完一套真实在跑的"投资体检机器人"之后,把从零到能用的完整过程、踩过的坑、以及几个值得深挖的原理一次性讲清楚。你读完可以直接照着搭一套属于自己的版本。
pysnowball 是什么:一句话说完它的价值
pysnowball 是雪球股票数据接口的 Python 版本,项目描述就一句话——"雪球股票数据接口 python edition",但干起活来毫不含糊。它把雪球、蛋卷基金等多个数据源里零散的 HTTP 接口,整理成了按业务划分的 Python 函数,你不需要研究 cookie、签名、分页参数,一个ball.xxx()就把数据拿回来了。
它的能力清单大致长这样:
| 模块 | 代表函数 | 能拿到什么 |
|---|---|---|
| 实时行情 | quotec/quote_detail/pankou/kline | 批量报价、个股详情、十档盘口、多周期K线 |
| 财务报表 | indicator/income/balance/cash_flow | ROE、营收利润、资产负债、现金流 |
| 资金动向 | capital_flow/capital_history/margin | 资金流向、融资融券、大宗交易 |
| F10 资料 | top_holders/bonus/skholderchg | 十大股东、分红送配、股东变动 |
| 基金数据 | fund_info/fund_nav_history/fund_growth | 基金基本信息、净值历史、业绩走势 |
| 其他扩展 | convertible_bond/index_*/northbound_shareholding_* | 可转债、指数、北向资金 |
一句话总结:股票、基金、指数、可转债、港股通,你能想到的常见数据面,这里基本都有口子。所有函数名都定义在项目的 pysnowball/init.py 里,导入即用。
完整实战:给我自己写一个"盘后投资体检机器人"
下面进入正题。我的目标很朴素:每天收盘后自动完成三件事——扫一遍自选股行情、给持仓股票做一轮财务体检、记录基金的净值变化,最后输出一份 Markdown 报告。这套流程跑通后,我再也没手动翻过行情软件。
第一步:装库配 token,先踩第一个坑
安装没什么好说的:
pip install pysnowball真正的坑在 token。pysnowball 调用雪球接口需要在请求头里带上你的登录凭证,代码里通过set_token注入:
import pysnowball as ball # token 形如 "xq_a_token=一串字符;u=一串数字" # 获取方式:登录雪球网页版,用浏览器开发者工具在任意接口请求的 Cookie 里找到这两项 ball.set_token("xq_a_token=xxxxxxxx;u=xxxxxxxx")这里有个小坑值得提醒:set_token本质是把 token 写进进程环境变量XUEQIUTOKEN(源码见 pysnowball/token.py),所以它只在当前进程内有效。你今天设了 token 关掉终端,明天再跑脚本如果忘了设置,就会直接抛异常:
Exception: 未设置TOKEN别问我怎么知道的。所以我的建议是别把 token 硬编码在脚本里,用环境变量或独立配置文件管理,写个小工具函数统一处理:
import os import pysnowball as ball def init_ball(): """优先读环境变量,其次读本地 token 文件,保证每次运行都能正确初始化""" token = os.environ.get("XUEQIUTOKEN") if not token: # 自己维护一个 .xq_token 文件,不提交到代码仓库 with open(os.path.expanduser("~/.xq_token")) as f: token = f.read().strip() ball.set_token(token) # 顺手验证一下:用无需 token 的批量行情接口做连通性检查 data = ball.quotec("SH000001") assert data["error_code"] == 0, "token 无效或网络异常" print("[OK] 初始化完成")为什么这么设计?因为"未设置TOKEN"这个错出现得极其高频,把初始化收敛成一个函数,脚本每个入口先调它,能省掉大量排查时间。
第二步:一行代码扫完整个自选股
这是 pysnowball 最让我惊喜的地方。quotec支持逗号分隔批量传入,一次请求就能拿回一组股票的实时报价:
import pysnowball as ball init_ball() # 自选股:上证 + 深证,注意代码前缀 SH/SZ 不能省 watchlist = ["SH600519", "SZ000858", "SH600036", "SZ300750", "SH601318"] quotes = ball.quotec(",".join(watchlist)) for q in quotes["data"]: print(f"{q['symbol']:<10} 现价 {q['current']:<8} 涨跌 {q['percent']:>6.2f}% " f"成交额 {q['amount']/1e8:.2f}亿 总市值 {q['market_capital']/1e8:.0f}亿")输出类似:
SH600519 现价 1458.0 涨跌 0.85% 成交额 42.13亿 总市值 18319亿 SZ000858 现价 168.0 涨跌 -1.23% 成交额 35.67亿 总市值 2108亿为什么要这样写?因为逐个调用的话,5 只股票要发 5 次请求,30 只就是 30 次,既慢又容易触发限流。批量接口一次搞定,脚本执行时间从"秒"级直接压到"毫秒"级。字段含义也很直白:current现价、percent涨跌幅、amount成交额、market_capital总市值,配合pankou(十档盘口)和quote_detail(含市盈率、市净率、换手率的完整详情)可以拼出很丰富的看板。
第三步:给持仓做一次"财务体检"
光看价格没用,我每季度还会用财务报表数据给持仓打个分。pysnowball 的finance模块把雪球的财务接口封装得特别顺手,indicator(业绩指标)、income(利润表)、balance(资产负债表)、cash_flow(现金流量表)各管一摊,还支持is_annals=1只看年报、count控制返回条数:
import pysnowball as ball def financial_check(symbol, name): """对单只股票做财务体检,返回评分明细""" # 业绩指标:重点看 ROE、毛利率(返回结构里每个指标是 [数值, 同比变动]) ind = ball.indicator(symbol, is_annals=1, count=5) rows = ind["data"]["list"] latest = rows[0] roe = latest["avg_roe"][0] # 加权ROE gross = latest["gross_selling_rate"][0] # 毛利率 np_ps = latest["np_per_share"][0] # 每股净利润 # 资产负债表:看资产负债率 bal = ball.balance(symbol, is_annals=1, count=5) debt_ratio = bal["data"]["list"][0]["asset_liab_ratio"][0] * 100 # 现金流量表:看经营现金流是否为正 cf = ball.cash_flow(symbol, is_annals=1, count=5) ocf = cf["data"]["list"][0]["ncf_from_oa"][0] score = 0 score += 2 if roe > 15 else 1 if roe > 8 else 0 score += 2 if gross > 30 else 1 score += 2 if debt_ratio < 50 else 1 if debt_ratio < 70 else 0 score += 2 if ocf > 0 else 0 print(f"[{name}] ROE={roe:.1f}% 毛利率={gross:.1f}% " f"负债率={debt_ratio:.1f}% 经营现金流={ocf/1e8:.2f}亿 → 评分 {score}/8") return score这里有个特别贴心的细节:indicator返回的每个指标都是[当前值, 同比变化率]的二元组结构,等于数据和同比变动一次拿齐,我连二次计算都省了。财务体检的价值在于把"感觉这公司不错"变成"ROE 连续三年高于 15%、负债率低于 50%、经营现金流为正"这种可比较的数字。
第四步:把基金净值也纳入监控
我的组合里还有几只基金。pysnowball 的基金接口走的是蛋卷基金的通道(相关 URL 定义见 pysnowball/api_ref.py 的fund_*部分),fund_info拿基本信息,fund_nav_history拉历史净值,组合起来就能画收益曲线:
import pysnowball as ball from datetime import datetime def track_fund(code): """抓取基金净值历史,返回最近 N 期的 (日期, 单位净值) 列表""" info = ball.fund_info(code) name = info["data"]["fd_name"] nav = ball.fund_nav_history(code, page=1, size=30) items = nav["data"]["items"] records = [] for it in items: # 蛋卷接口返回的净值日期是毫秒时间戳 day = datetime.fromtimestamp(it["nav_date"] / 1000).strftime("%Y-%m-%d") records.append((day, it["unit_nav"])) latest, prev = records[0], records[-1] ret = (latest[1] / prev[1] - 1) * 100 print(f"[{name}] 最新净值 {latest[1]:.4f} 近{len(records)}期收益 {ret:+.2f}%") return records track_fund("008975")这一步的坑在于单位:净值日期是毫秒时间戳,直接打印会得到一串天书数字,必须除以 1000 再转换。我第一版脚本就是忘了这茬,报告里全是1714396800000,排查了半天。
第五步:组装成定时任务,让脚本替你打工
把上面几段拼成一个daily_report.py,输出 Markdown 报告,再用 cron 在每周五收盘后自动跑:
# daily_report.py 主流程 def main(): init_ball() lines = ["# 投资体检报告", f"生成时间:{datetime.now():%Y-%m-%d %H:%M}", ""] lines.append("## 行情速览") for q in ball.quotec(",".join(WATCHLIST))["data"]: lines.append(f"- {q['symbol']} 现价 {q['current']} 涨跌 {q['percent']}%") lines.append("## 财务体检") for code, name in HOLDINGS.items(): financial_check(code, name) lines.append("## 基金跟踪") for code in FUNDS: track_fund(code) with open("report.md", "w") as f: f.write("\n".join(lines)) print("报告已生成:report.md") if __name__ == "__main__": main()# crontab -e,每周五 15:30 自动执行 30 15 * * 5 cd /path/to/your/project && python daily_report.py >> cron.log 2>&1至此,那个"每天手动翻两小时"的习惯彻底退役了。脚本跑完后我只需要花五分钟读报告,剩下的时间去看书。
进阶专题一:搞懂 token 机制,从此不再被"未设置TOKEN"折磨
前面说了 token 会写进环境变量,这里再往深挖一层,因为理解它对你排查问题帮助极大。
看 pysnowball/utls.py 的源码会发现,所有请求都经过一个fetch函数,它做三件事:拼上伪装成雪球 iPhone 客户端的请求头、从token.get_token()读 cookie、发请求并校验状态码。关键就在这行:
response = requests.get(url, headers=HEADERS)HEADERS 里长这样:
HEADERS = {'Host': host, 'Accept': 'application/json', 'Cookie': token.get_token(), 'User-Agent': 'Xueqiu iPhone 14.15.1', ...}也就是说,pysnowball 的本质是一个把"模拟雪球客户端请求"这件事封装好的 HTTP 客户端。它没有偷偷替你登录,而是把登录凭证的注入责任交给你。两个细节值得记住:
- 部分接口不需要 token。比如
quotec走的是fetch_without_token,不带头部 cookie 也能返回数据。这意味着你哪怕 token 配错了,行情接口也可能正常,但quote_detail、pankou这类接口会失败——排查时要先确认问题出在哪个接口上。 - token 有有效期。雪球的 cookie 隔一段时间会失效,表现就是脚本昨天还好好的,今天突然报错或者返回异常数据。解法是写个连通性自检(像我在
init_ball里做的那样),一旦失败就提示重新抓 token,而不是让脚本带病运行。
进阶专题二:用多周期 K 线做一次最简单的均线策略
kline是另一个值得玩透的接口。它的第二个参数period支持day(日K)、week(周K)、month(月K)、60m(60分钟)、30m(30分钟)、1m(1分钟),第三个参数count控制返回条数,接口定义在 pysnowball/realtime.py:
def kline(symbol, period='day', count=284): return utls.fetch(api_ref.kline.format(symbol, int(time.time()*1000), period, count))默认返回 284 条,而且注意 URL 里 count 是负号——雪球接口用负数表示"向前取 N 根"。返回的item是一个二维数组,每行依次是[时间戳, 开盘, 收盘, 最高, 最低, 成交量, 成交额, ...]。利用它,30 行代码就能算出一条均线信号:
import pysnowball as ball from datetime import datetime def ma_signal(symbol, short=5, long=20): """基于日K收盘价判断 5 日/20 日均线的金叉死叉""" data = ball.kline(symbol, period="day", count=long + 5) closes = [row[2] for row in data["data"]["item"]] # 每行第3列是收盘价 ma_short = sum(closes[-short:]) / short ma_long = sum(closes[-long:]) / long prev_short = sum(closes[-short-1:-1]) / short prev_long = sum(closes[-long-1:-1]) / long if prev_short <= prev_long and ma_short > ma_long: return f"{symbol} 今日金叉信号" if prev_short >= prev_long and ma_short < ma_long: return f"{symbol} 今日死叉信号" return f"{symbol} 无信号 (MA{short}={ma_short:.2f}, MA{long}={ma_long:.2f})" for code in ["SH600519", "SZ000858", "SH600036"]: print(ma_signal(code))这段代码的价值在于展示了一条完整链路:接口封装帮你把"发请求、带 token、解析 JSON"全干掉,你只需要关心数据处理本身。从"想要数据"到"拿到可计算的收盘价序列",中间只有一行调用。
五个高频问题与我的解法
Q1:报错"未设置TOKEN"但明明设过了?多半是换了终端或重启了进程,环境变量丢了。回到进阶专题一:token 只在当前进程生效,写个init_ball()统一初始化,每次运行先调它。
Q2:返回结果里error_code不是 0 怎么办?说明请求本身没通(比如参数格式错了、股票代码前缀写反了)。先检查代码格式:A股必须带SH/SZ前缀,基金用纯数字代码。再检查 token 是否过期。
Q3:quotec和quote_detail有什么区别,用哪个?quotec是轻量批量接口,支持多只股票逗号拼接,适合快速刷屏;quote_detail返回单只股票的完整信息(市盈率、市净率、52周高低点、分红率等),字段更全但一次只能查一只。场景不同选不同的工具。
Q4:请求发多了会不会被封?会。我在测批量循环时把脚本写岔过,短时间连发几百次请求直接被限流。建议在循环里加time.sleep()控制频率,批量数据能合并成一次请求的(比如quotec)就绝不拆开。
Q5:基金接口拿到的数据结构和股票不一样?对,基金的fund_*系列走的是蛋卷基金通道,返回结构和股票接口完全不同,而且单位也各不同(净值日期是毫秒时间戳)。写解析代码前先print()一次原始返回,看清楚结构再动手,别凭猜。
收尾:从两小时到十秒,这笔账很划算
最后算一笔直观的账。改造前,我每个交易日的"盘后流程"是:打开雪球挨个看 5 只自选股(约 15 分钟)→ 翻持仓的财务数据(约 30 分钟)→ 查 3 只基金净值并更新 Excel(约 15 分钟)→ 汇总整理(约 30 分钟)。加起来 90 分钟起步,还经常忘。
改造后:
| 项目 | 改造前 | 改造后 |
|---|---|---|
| 单日耗时 | 约 90 分钟手动操作 | 脚本 10 秒 + 读报告 5 分钟 |
| 数据完整度 | 常漏记、靠记忆 | 行情+财务+基金全量落盘 |
| 出错概率 | 手抄错行、漏更净值 | 脚本稳定,token 自检兜底 |
| 沉淀价值 | 数据散落在 Excel | Markdown 报告可追溯、可对比 |
如果你也在做个人投资记录、量化策略研究,或者只是懒得天天翻软件,pysnowball 值得你花一个晚上搭一套自己的监控脚本。安装、初始化、调通第一个接口,总共不超过 30 分钟;剩下的,就是让数据开始替你说话。
克隆项目仓库后照着跑起来:
git clone https://gitcode.com/gh_mirrors/py/pysnowball cd pysnowball pip install -r requirements.txt配合 README.md 里的接口示例(实时行情、K线、财务、资金流向都有现成代码),很快你也能拥有一套属于自己的数据自动化流水线。
【免费下载链接】pysnowball雪球股票数据接口 python edition项目地址: https://gitcode.com/gh_mirrors/py/pysnowball
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考