做投资研究的人十有八九绕不开雪球。社区讨论是一层真正让技术派上头的是雪球 App 和网页版背后那套行情接口。我自己最开始也是从新浪、腾讯的免费股票数据接口入手后面发现字段不够细、要拼的数据太多就开始研究雪球的数据采集方案。这篇文章就记录我从拆包到落地脚本的完整过程包括协议分析、代码实现、常见坑以及使用边界。适合对接口有兴趣、想自己搭自选股监控或做个人量化分析的朋友参考。1. 为什么我最后选了雪球而不是继续用新浪和腾讯1.1 免费接口里雪球的字段确实更“懂”投资者老牌的免费接口我也用了挺久。新浪的hq.sinajs.cn/listsz000001一行文本能拿到开盘、最高、最低、收盘、成交量简单直接腾讯的qt.gtimg.cn/qsh600519信息稍微多一点但本质上还是以行情报价为核心的轻量数据。问题在于当我想看“PE、PB、市值、换手率、量比、52周最高最低”这些偏估值和情绪面的指标时新浪和腾讯并不直接给全需要自己从其他页面去拼。而雪球的quote.json返回体里这些字段基本一次到位对于做自选股筛选和个股体检来说非常方便。1.2 三种接口的横向对比对比项新浪腾讯雪球symbol 风格sz000001sh600519SH600519实时性还不错还不错接近行情商体验字段丰富度基础报价基础报价估值、市值、盘口、52周区间等比较全批量查询支持逗号拼接支持逗号拼接支持批量 quote 接口历史K线有但分散有字段简单kline 接口直接返回结构化数据官方文档无无无需要抓包分析风控敏感度中中高频率控制要求更严格这张表不是想说明雪球一定最好而是说如果你的需求只是偶尔看一眼价格新浪腾讯更快更省事如果你要做筛选、回测、字段加工雪球的综合体验更好。1.3 什么样的人适合研究雪球接口我个人总结下来适合研究这套接口的人主要有三类自选股监控派想把自选股的实时价、涨跌、异动推到本地或者做提醒。量化学习派需要历史K线跑均线策略、写简单的回测框架。数据整理派想把不同平台的数据统一到一张本地表里做分析。反过来如果你准备做高频交易或者大规模商业分发那我劝你直接放弃这个思路原因后面第 6 节详细说。2. 雪球接口协议拆解浏览器做了什么脚本就做什么2.1 打开开发者工具看一眼 XHR研究任何网页接口第一步永远是 F12。打开雪球网页版随便搜一只股票在 Network 面板里筛quote相关的 XHR 请求能看到类似这样的地址实时行情https://stock.xueqiu.com/v5/stock/quote.json?symbolSH600519extenddetail批量行情https://stock.xueqiu.com/v5/stock/batch/quote.json?symbolSH600519,SZ000001extenddetail历史K线https://stock.xueqiu.com/v5/stock/chart/kline.json?symbolSH600519begin1696982400000perioddaytypebeforecount-5indicatorkline注意这几个接口都挂在stock.xueqiu.com这个域名下面相对比较稳定但不要理所当然以为它永远是公开的。接口路径和参数随时可能因为网页改版而调整。2.2 三个请求头决定访问成败参数其实不算复杂真正的门槛在请求头。我踩过最开始的几次 403基本都是请求头没给全。关键就三个User-Agent最好伪装成正常浏览器的 UA不要用默认的python-requests。Referer雪球的服务端会校验来源通常设置成https://xueqiu.com/就能过。Cookie核心是xq_a_token、u、xq_r_token这类字段。第一次访问雪球首页时服务端会下发 Cookie所以脚本里要先用 Session 访问一次首页把 Cookie 保持在会话里再请求接口。不要自己去拼 Cookie因为 token 时效性很强拼出来的可能会很快失效。2.3 返回 JSON 的核心结构quote.json返回的 JSON 大致长这样我用示例结构说明数字不是实时值{ error_code: 0, data: { quote: { symbol: SH600519, code: 600519, name: 贵州茅台, current: 1686.0, percent: 1.23, change: 20.5, high: 1690.0, low: 1650.0, volume: 2600000, amount: 4387500000, market_capital: 2117000000000, pe_ttm: 30.5, pb: 8.1, turnover_rate: 0.21, high52week: 1700.0, low52week: 1200.0 } } }error_code等于 0 表示请求成功。核心字段都在data.quote里很多是数字类型解析成本很低。3. 从零到一一个可运行的雪球行情脚本3.1 环境准备我用的是 Python 3.9 以上的版本依赖只需要requests如果你想顺手做数据处理可以再加个pandas。python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install requests pandas3.2 初始化会话和请求头先建立一个 Session 对象把请求头设置成浏览器风格并访问一次首页拿到 Cookie。import time import json import requests UA Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 session requests.Session() session.headers.update({ User-Agent: UA, Accept: application/json, text/plain, */*, Referer: https://xueqiu.com/ }) # 先访问首页让服务端下发匿名 Cookie session.get(https://xueqiu.com/, timeout10)这一步很多人会偷懒省略结果后面每次请求都会报错。首次访问首页不是没用的它本质是“登录态初始化”。3.3 获取单只股票实时行情拿到带 Cookie 的 Session 之后实时行情其实就是一个 GET 请求的事。def get_quote(symbol): url https://stock.xueqiu.com/v5/stock/quote.json params { symbol: symbol, extend: detail } resp session.get(url, paramsparams, timeout10) resp.raise_for_status() data resp.json() if data.get(error_code) ! 0: print(请求异常, data) return None return data[data][quote]调用一下if __name__ __main__: q get_quote(SH600519) if q: print(q)这里有几个细节值得注意symbol的格式是大写市场前缀加股票代码沪市是SH深市是SZ港股通常是HK美股是US前缀。不要和腾讯、新浪的混合格式搞混。extenddetail参数会返回更全的字段建议带上。不要在一个循环里对几百只股票逐个调quote.json后面会单独讲批量接口。3.4 解析关键字段输出可读结果接口返回的是 JSON直接 print 会特别乱。我习惯封装一个格式化函数把关心的字段输出成一眼能看懂的内容。def format_quote(q): lines [ f{q.get(name)}{q.get(symbol)}, f现价{q.get(current)} 涨跌幅{q.get(percent)}%, f今高{q.get(high)} 今低{q.get(low)}, f成交量{q.get(volume)} 成交额{q.get(amount)}, f市值{q.get(market_capital)} PE(TTM){q.get(pe_ttm)} PB{q.get(pb)}, f52周高{q.get(high52week)} 52周低{q.get(low52week)} ] return \n.join(lines)有一点要提醒不同品种的字段不是完全一致的。股票、基金、债券、指数返回的公共字段可能相同但估值类字段不一定都有。所以格式化的时候尽量用.get()不要直接q[pe_ttm]否则遇到基金或指数可能 KeyError。4. 进阶用法批量行情、历史K线和日常筛选4.1 批量行情一次请求能省不少事如果自选股有几十只用单个quote.json逐个去查显然不现实。雪球提供了一个批量接口batch/quote.json。def get_batch_quotes(symbols): url https://stock.xueqiu.com/v5/stock/batch/quote.json params { symbol: ,.join(symbols), extend: detail } resp session.get(url, paramsparams, timeout10) resp.raise_for_status() data resp.json() if data.get(error_code) ! 0: return [] items data[data][items] return [item[quote] for item in items if quote in item]批量接口返回的结构和单票接口有点区别最外层是data.items每一个 item 里再套quote。写解析逻辑的时候要按这个层级走。你可以一次传几十个 symbol我实际用下来查询效率比循环快很多而且对服务端的压力也小一些。4.2 历史K线数据怎么拿历史K线是回测和趋势分析的基础。雪球的 kline 接口一次能返回多根K线参数稍微复杂一点symbol股票标识和前面一致。periodK线周期常见day、week、month分钟级别也有但我用得少。count-30负数表示从begin时间点往前取 30 根。typebefore表示只取 begin 之前的K线避免取到未来数据。indicatorkline指定返回 OHLC 数据。示例代码import time def get_kline(symbol, periodday, count30): url https://stock.xueqiu.com/v5/stock/chart/kline.json begin int(time.time() * 1000) params { symbol: symbol, begin: begin, period: period, type: before, count: -count, indicator: kline } resp session.get(url, paramsparams, timeout10) resp.raise_for_status() data resp.json() if data.get(error_code) ! 0: return [] columns data[data][column] rows data[data][item] return [dict(zip(columns, row)) for row in rows]返回结果里column是字段名列表item是二维数组。我把它们拼成一堆 dict用起来会顺手很多。字段一般包括timestamp、volume、open、high、low、close、chg、percent、turnoverrate等。注意begin用的是毫秒时间戳不是秒。别在这上面踩坑。4.3 把接口组合起来做一个小筛选器当你能批量拿行情、又能拿K线之后能做的事情就很多了。举个我自己的例子每天收盘后我想把“PE 低于 20、当日涨幅超过 3%、最近 20 天没有大涨过”的股票筛出来放到备选池里。quotes get_batch_quotes(symbols) for q in quotes: pe q.get(pe_ttm) percent q.get(percent, 0) if pe is None: continue if percent 3 and pe 20: print(q[name], q[current], percent, pe)再配合 kline 数据算一下 20 日涨幅过滤条件可以写得更细。整个过程不复杂但对数据处理能力是有要求的。我不建议把这个工具直接当投资决策依据它更适合做研究阶段的初筛。筛选条件、数据源、阈值设置都需要自己反复验证。5. 高频请求与小白容易踩的坑5.1 请求频率过快直接被限制这是我第一次大规模拉数据时遇到的最狠的教训。当时我写了一个 for 循环想拉全市场几千只股票的实时行情结果拉到一半左右开始出现 403再往后直接返回验证页。最后我不得不停了几分钟重新拿 Cookie 才恢复。雪球对接口频率的限制比其他免费数据源更严尤其是针对数据中心类的接口。我的建议是批量接口一次能传几十个 symbol就不要循环请求单票接口。每次请求之间至少time.sleep(1)如果量很大建议间隔 1 到 3 秒。不要开多线程去并发打接口雪球对这种短时间高并发请求非常敏感。5.2 Cookie 过期返回一堆看不懂的错误码匿名 Cookie 不是永久有效的。脚本跑一段时间后可能突然发现所有请求都返回error_code非 0或者直接报错。遇到这种情况最直接的恢复方式就是重新执行一次首页访问刷新会话里的 Cookie。def refresh_session(): session.get(https://xueqiu.com/, timeout10)我把这个逻辑封装成了一个refresh_session()函数在程序启动和请求连续失败时都会调用。5.3 盘前、盘后和停牌数据不要想当然这个坑特别隐蔽。在非交易时段请求接口时current往往还是上一个交易日的收盘价percent可能显示为 0但这并不意味着这只股票今天没有波动。同样遇到停牌股成交量、成交额可能就是 0字段值也会保持不变。所以被监控工具拿到数据后最好根据接口返回的时间戳和本地时间做一次判断区分“实时盘口”和“收盘快照”。否则你的告警逻辑很容易在盘前盘后误报。5.4 非官方接口的变更不可控雪球网页版和 App 每次改版接口都有可能调整。我遇到过参数从extenddetail改成别的、字段名从high52week变成别的、接口路径加版本号的变更。应对办法就是给脚本加一层容错JSON 解析失败或者 Key 不存在时不要直接 crash先打日志。把接口返回的原始 JSON 存一份到本地方便出问题时排查。定期跑一个最小请求确认接口还是通的。这套接口本质上不是“文档齐全的开放平台”而是网页功能的一部分随时可能变。所以做个人研究没问题但别把它当生产基础设施。6. 接口研究的合规边界与长期使用建议6.1 自己能用的接口不等于可以随便分发这是做数据采集最容易忽略的问题。从技术上说只要浏览器能访问脚本也能访问。但从使用边界上说接口是雪球网页端产品的一部分不是官方对外开放的数据服务。个人为了学习、研究在自己本地低频调用通常问题不大但如果把它做成公共接口、付费工具、企业数据服务就很容易踩到平台规则和合规风险。我的原则是只在本地跑不上公网服务。不把原始数据打包对外分发。不绕过平台已有的登录、验证机制。控制频率明显低于正常用户操作节奏。6.2 真正做产品时优先考虑有授权的数据源如果你不是研究而是想做一个长期可用的产品比起研究雪球接口我更建议优先考虑有明确授权的数据服务商比如量化平台的数据模块、专业行情商、或者有正式数据授权的服务商。这类服务的优势在于数据合法、接口稳定、字段有文档出了问题有人处理。如果你只是自用那雪球这套接口可以玩得很开心。自选股提醒、估值异动监控、K线回测辅助都是非常合适的场景。把频率控制好、把数据校验做好它完全可以成为个人投资研究工作台的一部分。写到这里顺便分享一个小习惯我每次跑完数据都会把原始 JSON 留一份本地缓存再往下做清洗和转换。这样即使接口某天改了返回结构手里的历史数据也不会突然“断档”。这套方法不完美但对个人研究者来说可能是最实用的落地方式。