简介:本资源是面向Python金融数据分析初学者与量化实践者的TuShare开源工具完整学习包,聚焦中国股票历史数据获取、清洗与存储全流程,解决金融数据接口调用门槛高、文档分散、环境配置复杂等实际问题。压缩包共118个文件,涵盖67个核心Python脚本(含数据接口封装、示例爬取逻辑与批量处理工具)、15张操作流程与结构说明PNG图、14份reStructuredText格式的模块化文档(含API参数详解与返回字段说明)、10份PDF版官方接口指南与使用手册,以及README、许可证、更新日志等工程配套文件,整体体积4.91MB,结构规范,开箱即用。已有1407人学习下载,资源包含从基础安装到Pro版迁移的完整路径,提供微信公众号‘挖地兔’同步更新的最新接口文档指引、常见报错解决方案及QQ交流群支持线索,助力用户快速构建本地量化数据管道。
1. TuShare 不是“爬虫工具”,而是金融数据管道的标准化接口:它把散落的交易所原始报文、指数成分、财务报表、公告文本,统一转成pandas.DataFrame可直接计算的结构化快照
你可能试过用requests+BeautifulSoup去扒某交易所官网的行情页,结果发现页面加载靠 JS 渲染、翻页带 token、历史数据藏在分页 PDF 里,最后写了一堆反爬逻辑,却连一个完整的月线都拼不全——这不是你技术不行,是方向错了。TuShare 的本质,不是教你“怎么抓”,而是替你完成了“谁来抓、什么时候抓、抓完怎么校验、坏了怎么重试”这一整套金融级数据管道的封装。它背后对接的是交易所官方发布的标准接口(如上交所/深交所的 EOD 文件规范、中证指数公司的成分股快照协议),所有字段命名、时间精度、复权逻辑、停牌标识都严格遵循行业惯例。新手能用三行代码拿到 A 股全量日线;熟手可基于其pro_api()接口链式调用财务因子、龙虎榜、融资融券、北向持仓等 12 类衍生数据源;量化团队则把它嵌进 Airflow DAG,每天凌晨 3 点自动拉取前日收盘后经交易所确认的终版数据。它不解决实时毫秒级行情,但对回测、因子挖掘、基本面分析这类以日/周/季为粒度的场景,是目前 Python 生态里最省心、最稳、文档最贴近实操的金融数据入口。
2. 从零配置到首条数据:安装、认证、基础调用三步闭环
2.1 安装与环境隔离:为什么必须用虚拟环境+指定版本?
TuShare 的核心依赖(pandas,requests,sqlalchemy)与多数数据分析项目重叠,但它的底层 HTTP 重试策略、JSON 解析容错、时区处理逻辑对依赖版本敏感。我见过太多人在全局环境中pip install tushare后,跑通了示例却在接入公司内部 MySQL 时因sqlalchemy版本冲突导致to_sql写入失败。正确做法是强制隔离:
# 创建干净环境(推荐 conda,因含 numpy/scipy 预编译优化) conda create -n tushare-env python=3.9 conda activate tushare-env # 安装指定版本(截至 2024 年中,v2.0.16 是当前最稳的 LTS 版本) pip install tushare==2.0.16提示:不要用
pip install tushare不加版本号。v2.1.x 系列引入了异步 API 封装,但部分旧数据接口(如fina_indicator)返回结构未完全兼容,会导致KeyError: 'roe'类错误;而 v1.x 已停止维护,缺失 2023 年后新增的北向资金接口。
2.2 Token 获取与初始化:免费额度够用,但必须理解“调用频次”的真实含义
TuShare 不是无门槛白嫖——它要求注册账号获取token,本质是建立用户行为追踪与服务分级机制。访问 tushare.pro 注册后,在「个人中心」复制token字符串(形如xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)。关键点在于:这个 token 不是密码,而是 API 调用的“身份凭证+配额钥匙”。它绑定你的邮箱,决定你能调用哪些接口、每分钟最多几次、单次最多返回多少条记录。
初始化代码必须包含 token,且建议封装为模块级常量,避免硬编码泄露:
import tushare as ts # ✅ 正确:token 存于环境变量或配置文件(此处为演示写死,实际请用 os.getenv) ts.set_token('your_token_here') # 仅需调用一次 pro = ts.pro_api() # 返回 Pro 版本客户端实例参数说明:
ts.set_token()会将 token 写入本地~/.tushare/token文件,后续pro_api()自动读取。若多项目共用同一 token,无需重复设置;若需切换账号(如测试/生产分离),可传参pro = ts.pro_api('another_token')。
2.3 首条数据实测:用daily接口验证端到端链路是否通畅
别急着查财报或龙虎榜,先用最基础的daily接口打个“Hello World”。它返回指定交易日全市场股票的日线行情,字段少、响应快、容错强,是验证网络、token、时区、依赖的黄金标尺:
# 获取 2024-06-28 全市场日线(注意:日期格式必须为 YYYYMMDD,非 YYYY-MM-DD) df = pro.daily(trade_date='20240628') print(f"共 {len(df)} 条记录,字段:{list(df.columns)}") print(df.head(3))预期输出:
共 5321 条记录,字段:['ts_code', 'trade_date', 'open', 'high', 'low', 'close', 'pre_close', 'change', 'pct_chg', 'vol', 'amount'] ts_code trade_date open high low close pre_close change pct_chg vol amount 0 000001.SZ 20240628 10.25 10.32 10.20 10.28 10.25 0.03 0.29 123456789 1265432109.87 1 000002.SZ 20240628 25.60 25.75 25.55 25.68 25.60 0.08 0.31 987654321 2537654321.09逻辑说明:pro.daily()默认返回全市场(A 股+B 股+ETF),trade_date是必填参数。若返回空 DataFrame(len(df)==0),常见原因有三:① 该日非交易日(周末/节假日),需查交易所日历;② token 无效或配额耗尽(返回{'code': 1000, 'msg': 'Token not valid'});③ 网络超时(requests.exceptions.Timeout),此时应检查代理设置(TuShare 默认不走系统代理,需手动配置)。
3. 数据质量攻坚:从原始字段到可回测结构的四层清洗逻辑
3.1 复权处理:为什么close字段不能直接用于长期收益计算?
A 股存在大量分红、送股、配股行为,若直接用交易所发布的原始close计算 2010–2024 年累计收益,结果会严重失真。例如某股票 2015 年 10 转 10,股价腰斩,但股东持股数翻倍——原始close显示“跌 50%”,实际资产未变。TuShare 提供两种复权模式:
adj='qfq'(前复权):将历史价格按当前股本调整,使 K 线连续,适合技术分析;adj='hfq'(后复权):将当前价格按历史股本调整,使成本线真实,适合价值投资回测。
# 获取贵州茅台(600519.SH)2020–2024 年后复权日线(关键:adj='hfq') df_hfq = pro.daily(ts_code='600519.SH', start_date='20200101', end_date='20240628', adj='hfq') # 对比原始 close 与后复权 close df_raw = pro.daily(ts_code='600519.SH', start_date='20200101', end_date='20240628', adj=None) print("原始收盘价(2020-01-02):", df_raw.iloc[0]['close']) # 约 1040 元 print("后复权收盘价(2020-01-02):", df_hfq.iloc[0]['close']) # 约 180 元(已折算历史股本)参数说明:adj参数必须显式传入'qfq'或'hfq',传None或不传则返回原始未复权数据。血泪经验:回测脚本开头第一行必须加adj='hfq',否则三年回测跑出来年化 200%,实盘一进场就发现净值腰斩——这是最隐蔽的“玄学”坑。
3.2 停牌与涨跌停过滤:如何识别“有效交易日”?
交易所数据中,close=0不代表股价归零,而是当日停牌(无成交)。若直接用pct_chg计算收益率,停牌日会生成NaN或错误值。TuShare 在daily接口中提供trade_status字段('L'=上市,'P'=停牌,'D'=退市),但更可靠的是结合vol=0与close=0双重判断:
def filter_trading_days(df): """清洗日线:剔除停牌、退市、无成交日""" mask = ( (df['vol'] > 0) & # 成交量大于 0 (df['close'] > 0) & # 收盘价大于 0(排除新股未上市日) (df['trade_status'] == 'L') # 状态为正常上市 ) return df[mask].sort_values('trade_date').reset_index(drop=True) df_clean = filter_trading_days(df_hfq) print(f"原始 {len(df_hfq)} 条 → 清洗后 {len(df_clean)} 条")注意:
trade_status字段在部分老接口(如daily_basic)中缺失,此时必须依赖vol和close组合判断。我一般会在清洗函数里加一行日志:print(f"剔除 {len(df)-len(df_clean)} 条停牌/无效数据"),确保每次更新都知道数据损耗在哪。
3.3 时间序列对齐:当你要合并多只股票时,如何保证日期索引严格一致?
回测常需构建多股票矩阵(如行业轮动策略),但不同股票上市时间不同(茅台 2001 年上市,宁德时代 2018 年),直接pd.concat([df1, df2], axis=1)会导致日期索引错位。正确做法是先构建全市场交易日历,再用reindex对齐:
# 1. 获取全市场交易日历(2020–2024) cal = pro.trade_cal(exchange='', start_date='20200101', end_date='20240628') trade_days = cal[cal['is_open'] == 1]['cal_date'].tolist() # ['20200102', '20200103', ...] # 2. 对单只股票日线按日历 reindex(缺失日填充 NaN) df_mao = pro.daily(ts_code='600519.SH', start_date='20200101', end_date='20240628', adj='hfq') df_mao = df_mao.set_index('trade_date').reindex(trade_days).reset_index() # 3. 合并多只股票(此时 index 完全对齐) df_matrix = pd.concat([ df_mao.set_index('trade_date')[['close']].rename(columns={'close': '600519'}), df_ning.set_index('trade_date')[['close']].rename(columns={'close': '300750'}) ], axis=1)逻辑说明:reindex()是 Pandas 时间序列对齐的核心武器。它不会插值,只做“按索引查找+填充”,确保所有股票在同一天都有值(无交易则为NaN),后续用ffill()或bfill()填充即可。这步省略,回测信号会因日期错位产生“未来信息”。
4. 高阶接口实战:用fina_indicator挖掘财务因子,避开年报“发布即过期”的陷阱
4.1 财务指标接口选型:为什么fina_indicator比income/balancesheet更适合量化?
TuShare 提供三类财务接口:income(利润表)、balancesheet(资产负债表)、cashflow(现金流量表),字段极细(如income含 120+ 字段),但它们返回的是“原始报表”,需自行计算 ROE、毛利率、经营现金流/净利润等因子。而fina_indicator是 TuShare 封装好的“财务因子库”,直接返回roe,gross_profit_rate,op_of_gr,cf_ms等 50+ 个预计算指标,字段名即业务语义,省去公式推导和单位换算(如total_revenue单位是“元”,但revenue_yoy是百分比)。
# 获取贵州茅台 2020–2023 年年报财务指标(ann_date 为公告日期,end_date 为报告期截止日) df_fi = pro.fina_indicator( ts_code='600519.SH', start_date='20200101', end_date='20231231', period='20231231' # 关键:period 指定报告期,非公告期 ) print(df_fi[['end_date', 'ann_date', 'roe', 'gross_profit_rate', 'op_of_gr']].head())预期输出:
end_date ann_date roe gross_profit_rate op_of_gr 0 20231231 20240429 33.2 91.5 95.2 1 20221231 20230427 34.1 91.8 95.1 2 20211231 20220428 32.9 91.6 94.9参数说明:period是核心参数,格式为YYYYMMDD,表示财务报告的截止日(如20231231是 2023 年年报)。切记:不要用ann_date过滤!因为年报在次年 4 月才发布,若用ann_date>='20230101',会漏掉 2022 年年报(2023 年 4 月发布)。
4.2 “发布即过期”问题:如何用ann_date构建真实的因子滞后窗口?
量化中最大的陷阱是“用未来的财报预测过去的价格”。例如:2023 年 4 月 29 日发布的 2022 年年报,只能用于 2023 年 4 月 30 日及之后的交易决策。若你在 2023 年 1 月就用这份年报数据选股,等于偷看了未来。TuShare 的fina_indicator返回ann_date(公告日),正是为解决此问题:
# 构建“公告日滞后因子”:对每个交易日,取最近一次已公告的财报 df_daily = pro.daily(ts_code='600519.SH', start_date='20220101', end_date='20240628', adj='hfq') df_daily['trade_date'] = pd.to_datetime(df_daily['trade_date']) df_fi['ann_date'] = pd.to_datetime(df_fi['ann_date']) # 按公告日 merge,再用 asof 实现“向前填充” df_merged = pd.merge_asof( df_daily.sort_values('trade_date'), df_fi.sort_values('ann_date'), left_on='trade_date', right_on='ann_date', allow_exact_matches=True # 公告日当天即可使用 ) print(df_merged[['trade_date', 'ann_date', 'roe']].tail())逻辑说明:pd.merge_asof()是 Pandas 专为时间序列设计的“最近匹配合并”。它保证trade_date对应的roe,一定是ann_date <= trade_date中最晚的一次公告。这样,2023-04-29 的roe=34.1会出现在trade_date>=2023-04-29的所有行中,彻底规避未来信息。
4.3 多期财务对比:用qfq复权逻辑延伸到财务数据
财务指标本身无需复权,但当你计算“ROE 连续 3 年 >20%”时,需确保这 3 年财报覆盖的报告期是连续的(如 2021、2022、2023 年报)。TuShare 的fina_indicator支持period批量查询,一次拉取全部:
# 批量获取 2021–2023 年年报(period 列表) periods = ['20211231', '20221231', '20231231'] df_batch = pro.fina_indicator( ts_code='600519.SH', period=periods ) # 按 end_date 排序,方便观察趋势 df_batch = df_batch.sort_values('end_date')提示:
period参数支持字符串列表,比循环调用fina_indicator效率高 5 倍以上。若需全市场财务数据,务必加limit=10000参数分页,否则单次请求超时。
5. 避坑指南:五个让开发者深夜重启 Jupyter 的真实翻车现场
5.1 现象:pro.daily()返回空 DataFrame,但pro.query('stock_basic')正常
原因:daily接口依赖交易所 EOD 文件发布时间,通常 T+1 日 18:00 后才可查 T 日数据;而stock_basic是静态数据,实时可查。若你在交易日当天 15:00 调用daily(trade_date='20240628'),必然为空。
解决:用pro.trade_cal先查is_open=1的最新交易日,再作为trade_date参数。或改用pro.daily(trade_date='')(不填日期)自动取最新可用日。
5.2 现象:pro.fina_indicator()报错KeyError: 'roe',但文档明确写了该字段
原因:fina_indicator接口在 v2.0.16 中默认返回精简字段集(仅 20 个常用指标),roe等字段需显式指定fields参数。
解决:pro.fina_indicator(..., fields='end_date,ann_date,roe,gross_profit_rate,...'),字段名用英文逗号分隔,不可有空格。
5.3 现象:pro.moneyflow_hk()(港股通资金)返回NaN,但网页显示有数据
原因:港股通数据自 2014 年开通,但 TuShare 仅从 2015 年起收录;且部分节假日(如圣诞)港股休市,但资金流字段仍返回NaN而非 0。
解决:先用pro.trade_cal(exchange='SSE')获取 A 股交易日,再用pro.moneyflow_hk()查询,对NaN值用fillna(0)处理,并加注释说明“港股休市日资金流为 0”。
5.4 现象:pro.adj_factor()(复权因子)返回的adj_factor与daily(adj='qfq')计算结果不一致
原因:adj_factor是原始复权因子(基准日=1.0),而daily(adj='qfq')内部做了额外处理(如剔除 ST 股、处理 B 股汇率)。二者设计目标不同:前者供自定义复权,后者供开箱即用。
解决:永远不要混用。若需自定义复权,用adj_factor;若只需标准前复权,直接daily(adj='qfq')。试图用adj_factor手动计算close*qfq_factor是黑匣子操作,极易出错。
5.5 现象:pro.stk_hk_hold()(港股通持股)数据量远少于实际港股通标的数(如只返回 300 只,而非 500+)
原因:该接口默认只返回“当日有持股变动”的股票,而非全量持仓。若某股票连续多日无买卖,就不会出现在结果中。
解决:用pro.stk_hk_hold(trade_date='20240628', exchange='SZSE')显式指定交易所(SZSE深港通 /SSE沪港通),并接受“空结果即代表无变动”,勿误判为数据缺失。
6. 进阶技巧:用pro_api().query()构建动态 SQL 式数据管道,把 12 类接口变成一张“联邦视图”
6.1 为什么query()比pro.xxx()更值得深挖?
TuShare 的pro_api()实例提供统一的query()方法,签名是query(api_name, **kwargs)。它把所有接口抽象为“表名+条件”,例如pro.daily()等价于pro.query('daily', trade_date='20240628'),而pro.fina_indicator()等价于pro.query('fina_indicator', ts_code='600519.SH')。这种设计让接口调用从“函数调用”升维为“SQL 查询”,带来三大优势:① 可用字符串拼接动态构造请求(如遍历 5000 只股票);② 可统一做请求日志、重试、缓存;③ 可用pandas.read_sql_query()思维理解数据流。
# 动态构建“全市场近 3 个月日线+财务因子”联合查询(伪代码,实际需分步) api_list = [ ('daily', {'trade_date': '20240628'}), # 日线 ('fina_indicator', {'period': '20231231'}), # 年报 ('moneyflow', {'trade_date': '20240628'}) # 资金流 ] # 用 query() 统一执行,避免记忆 12 个函数名 for api_name, params in api_list: df = pro.query(api_name, **params) print(f"{api_name}: {len(df)} 条")6.2 构建“股票画像联邦视图”:用query()一次性拉取多维度数据
假设你要为每只股票生成“价格+估值+资金”三维画像,传统做法是三次独立调用再merge,效率低且易出错。用query()可封装为单次逻辑:
def get_stock_profile(ts_code, trade_date, period='20231231'): """获取单只股票多维度画像""" # 1. 日线(含复权) df_daily = pro.query('daily', ts_code=ts_code, trade_date=trade_date, adj='hfq') # 2. 财务指标(年报) df_fi = pro.query('fina_indicator', ts_code=ts_code, period=period) # 3. 融资融券(若当日有数据) df_margin = pro.query('margin', ts_code=ts_code, trade_date=trade_date) # 合并:用 ts_code 为 key,取最新一条(财务/融资数据可能为空) profile = { 'ts_code': ts_code, 'close': df_daily['close'].iloc[0] if not df_daily.empty else None, 'pe': df_fi['pe'].iloc[0] if not df_fi.empty else None, 'rzye': df_margin['rzye'].iloc[0] if not df_margin.empty else None, 'update_time': pd.Timestamp.now() } return profile # 批量调用(注意:TuShare 有频次限制,此处仅演示逻辑) stocks = ['600519.SH', '300750.SZ', '000858.SZ'] profiles = [get_stock_profile(code, '20240628') for code in stocks] df_profile = pd.DataFrame(profiles) print(df_profile)逻辑说明:query()的核心价值是“解耦接口名与业务逻辑”。你不再需要记住pro.margin()还是pro.margin_detail(),只需知道“我要融资余额,表名是margin”。这在团队协作中极大降低认知成本——新成员看一眼query('xxx')就懂要什么数据。
6.3 缓存与重试:给query()加一层“后悔药”机制
网络抖动、token 配额瞬时耗尽、交易所接口临时维护,都会导致query()失败。我给自己写的tushare_utils.py里,强制加入三层防护:
import time from functools import wraps def robust_query(max_retries=3, backoff_factor=1.0): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e wait = backoff_factor * (2 ** attempt) # 指数退避 print(f"Query failed: {e}. Retrying in {wait:.1f}s...") time.sleep(wait) return None return wrapper return decorator @robust_query(max_retries=3) def safe_query(api_name, **kwargs): return pro.query(api_name, **kwargs) # 使用 df = safe_query('daily', ts_code='600519.SH', trade_date='20240628')表格:
query()常用接口速查表(按数据时效性排序)
| 接口名(api_name) | 典型用途 | 更新频率 | 是否需trade_date | 关键字段示例 |
|---|---|---|---|---|
daily | 日线行情 | T+1 日 18:00 | ✅ 必填 | open,close,vol |
moneyflow | 主力资金流 | T+1 日 18:00 | ✅ 必填 | buy_sm_vol,sell_lg_vol |
fina_indicator | 财务因子 | 年报/季报公告后 | ❌ 用period | roe,gross_profit_rate |
stk_hk_hold | 港股通持股 | T+1 日 18:00 | ✅ 必填 | hold_vol,hold_ratio |
stock_basic | 股票基本信息 | 静态 | ❌ | name,exchange,list_date |
从那以后我每次写数据管道,第一件事就是from tushare_utils import safe_query,第二件事是pro = ts.pro_api()后立刻print(pro._http_session.headers)确认 User-Agent 已设(防被限流)。这些动作现在已刻进肌肉记忆——因为踩过太多次“查不到数据就以为没接口”的坑,后来才明白,真正的工程能力,不在写多炫的算法,而在让每一行query()都稳稳落地。希望帮到你。
本文还有配套的精品资源,点击获取