企业微信

很多人刚开始用tushare,都会遇到一个尴尬场景:想拉全市场股票或者期货的历史行情,结果跑一次要几十分钟,中途还断线,或者直接报错返回空数据。这不是工具不行,而是下载方式太“老实”。把批量下载拆开看,就是三件事:接口怎么调、任务怎么排、数据怎么落盘。

一次拉太多为什么容易崩

tushare的接口有单次返回条数限制,也有频率限制。你调daily接口拉所有股票某一天的数据,单次最多返回6000条左右,全市场5000多只股票,一天的数据其实勉强能一次拿完。拉多天呢?多品种呢?接口就会开始拒绝或者返回空。

网络也有关系。tushare服务端不可能让你一个请求占用太久,一旦超时,前面拉的数据可能白费。期货数据更明显,主力合约、指数合约、连续合约混在一起,一次全拉,返回结构都变复杂。

真正卡住你的不是tushare慢,而是你没有把大任务拆成小任务。

怎样用tushare一次下载大量数据才不卡顿

股票数据怎么批量拿

股票批量下载的核心思路是按标的切分,按日期循环。全市场股票代码先通过stock_basic拿到列表,再按每500只或者每1000只分组。每组调用daily接口,传入ts_code列表和起止日期。

单次请求的股票数量不要超过接口上限。日历上一天一天循环太慢,可以按周或者按月拉,但要注意接口单次返回条数。如果返回条数接近上限,就继续拆细。

tushare pro的积分不同,频率限制也不同。普通用户每分钟能调的次数有限,那就加一个sleep。别小看这个sleep,它能让你的任务稳定跑完,而不是跑一半被限流。

字段也要显式指定。daily接口默认返回全部字段,但有些字段你根本用不上。用fields参数只取ts_code、trade_date、open、high、low、close、vol、amount,返回体积能小一半以上。字段少,传输快,落盘也快。

期货数据的坑更隐蔽

期货批量下载不能直接照搬股票。期货有主力合约、连续合约、指数合约。你下载某个品种,如果直接拉所有合约,数据量会爆炸。

正确做法是先拿主力合约列表,或者用主力连续合约代码。tushare的期货日线接口fut_daily支持按ts_code拉取。每个品种的主力连续合约像CU.SHF这种,直接拉就行。

换月问题要注意。主力合约在换月时,价格会跳空。如果你做回测,要处理这个跳空,不能直接把连续合约当成真实价格。真实交易中,换月是要移仓的,成本必须算进去。

期货还有夜盘。夜盘数据归属下一个交易日,拉取时日期要对准。拉取全市场期货日线,最好按交易所分开拉,SHFE、DCE、CZCE、CFFEX、INE各拉各的。交易所分开之后,频率限制也分散了。

多线程怎么用才安全

tushare本身不限制你开线程,但服务端有频率限制。多线程不是越多越好,4到8个线程比较稳妥。线程太多,触发限流,反而更慢。

每个线程负责一组标的,线程内部仍然要sleep。用一个队列把股票代码分组,线程从队列里取任务。这样不会重复拉取,也不会漏掉。

写代码时,每个线程单独维护一个tushare pro接口对象。共享一个对象在高并发下可能出问题。线程内拿数据之后,先放到内存列表,等积累到一定数量再统一落盘。频繁写磁盘会拖慢整体速度。

数据存哪里才不白干

很多人拉完数据直接存CSV,文件又多又碎,下次读取慢得想哭。批量下载场景下,Parquet格式比CSV好得多。Parquet压缩率高,读取快,还能按列读取。

按标的或者按日期分目录存储。股票可以按交易日分文件,每天一个Parquet。期货可以按品种分文件。下次要用,直接读对应文件,不用重新拉。

本地缓存要加一层校验。每个文件记录拉取状态,已经拉过的日期跳过。断点续传能力必须有,否则一次网络抖动,前面几小时白跑。

数据库也可以,但SQLite适合小规模,大规模还是Parquet加DuckDB。DuckDB可以直接查Parquet文件,速度接近数据库,还不用额外部署。

定时任务怎么排更省心

批量下载不需要每天手动跑。用crontab或者Windows任务计划,把脚本挂上去。交易日收盘后跑一次,拉当天数据。周末跑一次,补历史缺失。

脚本里加日志。每次拉取记录成功多少条,失败多少条,失败原因是什么。限流失败就等一分钟重试,网络失败就重试三次。重试仍然失败,记录下来下次补。

别在交易时段拉历史数据。交易时段接口响应慢,而且你可能拉到不完整的当日数据。收盘后半小时再跑,数据稳定,速度也快。

代码骨架长什么样

不用写复杂框架,一个简单循环加线程池就够。


import tushare as ts

import pandas as pd

from concurrent.futures import ThreadPoolExecutor

import time

import os

pro = ts.pro_api('你的token')

def fetch_stock_group(codes, start, end):

    for code in codes:

        try:

            df = pro.daily(ts_code=code, start_date=start, end_date=end,

                           fields='ts_code,trade_date,open,high,low,close,vol,amount')

            if df is not None and not df.empty:

                df.to_parquet(f'data/stock/{code}.parquet', index=False)

        except Exception as e:

            print(f'{code} failed: {e}')

        time.sleep(0.15)

def batch_download(all_codes, start, end, workers=4):

    groups = [all_codes[i:i+100] for i in range(0, len(all_codes), 100)]

    with ThreadPoolExecutor(max_workers=workers) as executor:

        for group in groups:

            executor.submit(fetch_stock_group, group, start, end)

if __name__ == '__main__':

    os.makedirs('data/stock', exist_ok=True)

    basic = pro.stock_basic(exchange='', list_status='L', fields='ts_code')

    codes = basic['ts_code'].tolist()

    batch_download(codes, '20200101', '20241231', workers=4)

期货把pro.daily换成pro.fut_daily,代码列表换成主力合约列表。字段按需调整,夜盘数据注意日期归属。

最后提醒几个细节

tushare积分决定频率上限。积分低就多开几个账号轮询,或者降低线程数。不要用免费接口硬扛全市场,时间成本太高。

每次拉取之前先检查本地文件是否存在。存在就跳过,不存在再拉。这个简单判断能省掉大量重复请求。

数据落盘之后做一次基本校验。股票价格不能为负,成交量不能为负,期货结算价要合理。异常数据单独标记,不要直接混入回测。

批量下载这件事,本质是工程问题,不是金融问题。把任务拆细,把频率控制好,把存储做扎实,一次下载大量数据就不会再卡住你。