避开这三个坑:新手用Tushare获取股票数据时最常犯的错误(及解决方法)
第一次用Tushare抓取股票数据时,我盯着屏幕上那个刺眼的Invalid API token报错整整半小时——明明复制了官网的密钥,为什么还是不行?后来才发现,原来免费注册的Token需要手动激活才能使用。这种看似简单却容易踩坑的细节,正是新手最需要警惕的。
本文将分享三个最典型的Tushare使用陷阱,它们会以各种形式出现在你的代码中:可能是突然失效的Token,可能是怎么也查不到数据的股票代码,或是永远返回空值的接口调用。理解这些问题的根源,能让你少走80%的弯路。
1. Token失效:为什么我的API密钥突然不能用了?
很多新手第一次接触Tushare时,会直接复制示例代码中的ts.set_token('你的token'),然后发现无论如何调整都无法获取数据。这通常涉及三个隐藏规则:
Token激活机制
免费注册获得的Token需要完成手机验证和实名认证后才能激活。未激活的Token虽然能通过set_token设置,但实际调用接口时会返回403错误。验证流程如下:
- 登录Tushare Pro官网
- 进入"个人中心"-"账号管理"
- 完成手机号绑定(需接收短信验证码)
- 提交身份证信息进行实名认证
注意:2023年后注册的用户还需额外完成风险测评问卷才能激活数据权限
积分耗尽问题
即使是已激活的Token,也可能因为积分不足导致请求失败。免费用户每日有100次基础调用限额,常见接口扣分规则:
| 接口类型 | 单次调用扣分 | 典型场景 |
|---|---|---|
| 日线行情 | 2分 | pro.daily() |
| 财务指标 | 10分 | pro.fina_indicator() |
| 上市公司基本信息 | 5分 | pro.stock_basic() |
检查积分余额的Python代码:
import tushare as ts ts.set_token('你的token') pro = ts.pro_api() print(pro.query('user')) # 查看剩余积分和权限环境变量配置建议
为避免Token硬编码在脚本中,推荐使用环境变量管理:
# Linux/Mac export TUSHARE_TOKEN='你的token' # Windows setx TUSHARE_TOKEN "你的token"然后在Python中通过os.environ读取:
import os ts.set_token(os.getenv('TUSHARE_TOKEN'))2. 股票代码的隐藏规则:为什么600036查不到数据?
当我第一次尝试查询招商银行数据时,直接输入600036却返回空值——原来Tushare要求所有股票代码必须带交易所后缀。这是新手最容易忽略的格式要求:
完整代码格式
A股股票必须使用代码.交易所格式,其中:
.SH表示上海证券交易所(如600036.SH).SZ表示深圳证券交易所(如000001.SZ)
常见错误转换案例:
# 错误示例 code = '600036' # 缺少交易所后缀 # 正确转换方式 def format_stock_code(code): if code.startswith(('6', '9')): return f"{code}.SH" elif code.startswith(('0', '3')): return f"{code}.SZ" else: raise ValueError("未知的股票代码格式") # 使用示例 print(format_stock_code('600036')) # 输出: 600036.SH特殊品种标识
除常规股票外,其他品种有独立标识规则:
- 科创板股票:
688XXX.SH - 创业板股票:
300XXX.SZ - 指数:
000300.SH(如沪深300指数)
3. 空数据陷阱:为什么我的请求没有返回结果?
即使Token和代码格式都正确,新手仍可能遇到接口返回空DataFrame的情况。这通常与三个因素有关:
日期范围问题
Tushare的日线数据最早可追溯到1990年,但不同接口有不同限制:
pro.daily()支持任意日期范围pro.weekly()仅返回完整周的数据pro.monthly()仅返回完整月的数据
交易日历差异
这个查询2023年10月1日数据的代码会返回空值——因为当天是国庆节休市:
# 错误示例:查询非交易日 df = pro.daily(ts_code='600036.SH', start_date='20231001', end_date='20231001') # 正确做法:先获取交易日历 cal = pro.trade_cal(exchange='', start_date='20230101', end_date='20231231') trading_days = cal[cal['is_open'] == 1]['cal_date'].tolist()权限层级限制
免费用户无法访问某些高级数据字段,比如:
- 不复权价格(需5000积分以上权限)
- 港股实时行情(需机构账号)
- 龙虎榜明细(需20000积分)
检查字段权限的方法:
# 查看daily接口可用字段 fields = pro.query('daily').columns print(fields) # 过滤掉无权限字段 available_fields = [f for f in fields if not f.startswith('_')]4. 高效使用Tushare的五个专业技巧
批量请求优化
避免在循环中频繁调用接口,改用ts_code参数批量查询:
# 低效做法 codes = ['600036.SH', '000001.SZ'] dfs = [] for code in codes: dfs.append(pro.daily(ts_code=code, start_date='20230101')) # 高效做法(减少API调用次数) multi_df = pro.daily(ts_code=','.join(codes), start_date='20230101')本地缓存策略
使用sqlite3建立本地数据缓存,避免重复请求:
import sqlite3 def get_cached_data(code, date): conn = sqlite3.connect('tushare_cache.db') query = f"SELECT * FROM stocks WHERE code='{code}' AND date='{date}'" return pd.read_sql(query, conn) # 首次请求后存储数据 df.to_sql('stocks', conn, if_exists='append', index=False)异常重试机制
网络不稳定时自动重试的装饰器实现:
import time from functools import wraps def retry(max_attempts=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): attempts = 0 while attempts < max_attempts: try: return func(*args, **kwargs) except Exception as e: print(f"Attempt {attempts+1} failed: {str(e)}") time.sleep(delay) attempts += 1 raise Exception("Max retry attempts exceeded") return wrapper return decorator @retry() def safe_api_call(): return pro.daily(ts_code='600036.SH')时区转换处理
Tushare返回的时间戳是UTC+8时区,与其他系统集成时需特别注意:
df['trade_date'] = pd.to_datetime(df['trade_date']) df['trade_date_utc'] = df['trade_date'].dt.tz_localize('Asia/Shanghai').dt.tz_convert('UTC')内存优化技巧
处理大规模历史数据时,使用分类数据类型减少内存占用:
dtypes = { 'ts_code': 'category', 'trade_date': 'category', 'vol': 'float32' } df = df.astype(dtypes) print(df.memory_usage(deep=True)) # 查看内存使用量