ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python免费批量下载A股历史数据:从数据源选型到实战部署

Python免费批量下载A股历史数据:从数据源选型到实战部署 做量化的人都知道行情数据就是每天都要吃的饭。可真正到了自己动手去弄A股历史数据的时候很多人第一反应是去开个数据终端、买年费会员或者找某些“整合包”疯狂下载。但说实话免费渠道完全够用关键是你得会配、会下、会处理。这篇文章就围绕A股历史数据免费下载这条主线把我自己用Python批量拉取4000多只个股日线数据的一套流程完整拆开讲从数据源选型、环境配置、批量下载、断点续传到落地存储一步一步说清楚目标是让你照着做几小时内就把整套A股历史数据仓库搭好。1. 数据源选型免费接口不是只有一种答案1.1 主流免费数据源横向对比先把市面上能白嫖的A股历史数据来源捋一遍我按自己的实际体验给它们排了个序。Tushare是老牌选手数据质量高、字段齐全曾经是国内量化圈事实上的免费标准。但现在它的Pro版本走积分制普通注册用户积分不够很多历史数据接口根本调不了尤其是分钟级数据、财务明细这类基本就是把路堵死了。如果你只想拿个日线攒攒积分还能用可如果急着跑通流程它算不上最优解。akshare是我目前用得最多的底层爬取了东方财富、新浪财经、上交所、深交所等多个公开页面和数据接口覆盖范围非常广从股票日线、复权因子、指数行情到龙虎榜、融资融券都能拿。它的最大优势就是零门槛不需要tokenpip装完直接import就能用。缺点也很明显因为依赖网页接口偶尔会遇到页面改版导致接口失效更新不及时的情况就时好时坏。不过社区活跃度高基本隔几天就发一个新版。baostock是另一个免费稳定的选择它的日线数据、复权数据都支持得非常规范接口调用是面向服务式的有专门的login和logout流程适合做严肃的数据批处理。我个人体感是它的速度比akshare爬网页更快毕竟有自己的数据服务器但字段丰富度上不如akshare而且部分数据更新到当日收盘会有延时。网易财经的历史数据接口是很多老玩家的记忆CSV直接下载简单粗暴。但现在网易接口时常抽风字段也不全我基本已经放弃拿它做主数据源了。综合来看我更推荐akshare做主数据源因为免费、免注册、免token、覆盖全拿不到的历史细节再去baostock补两个免费源交叉使用已经能覆盖绝大多数个人量化研究和日常复盘的需求。1.2 我的选型思路与评判标准选数据源这件事最忌讳只看“免费”两个字。我给自己定了几条硬标准供你参考。第一拿到数据的成本要低。注册、审批、token申请流程如果超过10分钟我基本不会选。akshare和baostock都符合这条装上就能用。第二字段必须完整。做量化回测至少要包含开高低收、成交量、成交额、换手率最好还能拿到复权因子和涨跌幅。日线这种最基础的数据如果连涨跌幅都不给后面计算还得自己去merge麻烦不说还容易出错。第三接口要有稳定性保障。baostock是独立服务稳定性好akshare虽然靠爬网页但长期用下来只要及时升级版本问题也不算大。相比之下某些个人维护的爬虫脚本就完全没有稳定性可言代码一变就全盘报废。第四学习成本要可控。数据源是要长期维护的东西如果接口设计反直觉每次用都得翻文档那就很亏。akshare的接口命名基本都是语义化的英文比如stock_zh_a_daily、stock_zh_a_hist一眼就知道是什么意思。最终我选了“akshare为主、baostock为辅”的组合最近两年用下来这套方案应付个人研究、策略回测和教学分享绰绰有余。2. 环境准备半小时搭好下载环境2.1 Python与虚拟环境配置我默认你用的是Python 3.8以上版本如果电脑里还没装直接去官网下载稳定版安装时勾选Add Python to PATH别在这个环节栽跟头。装好Python之后第一件事是建一个独立的虚拟环境不要让项目依赖污染系统Python。我用的是Python自带的venv命令很简单mkdir a-share-data cd a-share-data python -m venv venv激活虚拟环境# Windows venv\Scripts\activate # macOS/Linux source venv/bin/activate看到命令行前面出现(venv)提示符说明环境已经切过来了。用虚拟环境的好处是哪怕你折腾坏了某个库的版本直接删掉这个venv文件夹重来就是不影响系统Python和其他项目。2.2 核心依赖库安装我建议你直接安装这几个库一个都别少pandas数据处理核心装它准没错akshare主数据源baostock备用数据源requests有些需要自己爬接口的时候用tqdm批量下载时看进度条避免干等焦虑安装命令pip install pandas akshare baostock requests tqdm注意akshare版本更新非常勤接口变动也频繁建议安装完后顺手确认一下版本号遇见接口报错时第一时间检查是不是版本太旧pip show akshare如果以后遇到akshare接口调用失败常规操作就是升级pip install --upgrade akshare我在实操中还遇到过pandas和akshare的依赖冲突表现为import akshare直接报AttributeError多半是pandas版本和它不兼容。解决办法也简单升级或降级pandas到akshare要求的版本范围即可不用过度纠结。3. 一键批量下载4000个股数据拉取的完整方案3.1 先拿到全部A股股票列表批量下载的第一步其实是获取一份“待下载清单”也就是所有A股股票的代码和名称列表。akshare里直接有现成接口import akshare as ak # 获取沪深京A股代码名称对照表 df_stock_list ak.stock_info_a_code_name() print(df_stock_list.head()) print(股票数量:, len(df_stock_list))这个接口返回的DataFrame包含code和name两列全市场大约5000只上下因为包含北交所比平时说的4000多只还要多。拿着这份列表我们就可以循环去下载每一只股票的历史日线了。如果你发现这个接口没有返回北交所的股票或者你只想要沪深交易所的标的也可以顺手过滤一下代码前缀比如60开头的是沪市主板00开头的是深市主板30开头的是创业板68开头的是科创板8开头和4开头是北交所。不同的研究需求可以随时按前缀筛选。3.2 核心下载函数与批量循环拿到股票列表之后接下来是写一个核心的下载函数负责单只股票历史日线的获取和落盘。我常用的代码如下import time import os import akshare as ak import pandas as pd def download_daily(code, name, start_date, end_date, data_dirdaily): 下载单只A股日线数据并保存为CSV os.makedirs(data_dir, exist_okTrue) file_path os.path.join(data_dir, f{code}.csv) # 已经下载过的直接跳过 if os.path.exists(file_path) and os.path.getsize(file_path) 0: print(f{code} {name} 已存在跳过) return False try: df ak.stock_zh_a_hist(symbolcode, perioddaily, start_datestart_date, end_dateend_date, adjustqfq) if df is None or df.empty: print(f{code} {name} 无数据) return False # akshare返回的列名是中文统一改成英文方便后面量化使用 df.columns [date, open, close, high, low, volume, amount, amplitude, pct_change, change, turnover] df.to_csv(file_path, indexFalse, encodingutf-8-sig) print(f{code} {name} 下载完成: {len(df)}行) return True except Exception as e: print(f{code} {name} 下载失败: {e}) return False注意几个细节我默认用了前复权qfq。做历史回测时前复权数据能避免分红送股造成的价格跳空是比较合适的默认选择。列名改成英文不是为了装而是为了后面做策略回测、因子计算时少踩编码坑。中文列名虽然看起来直观但在某些绘图库和机器学习库里偶尔会出乱码问题。已有文件且大小不为0的直接跳过这是一个简单的断点续传逻辑防止下次重跑时重复下载。接着写批量循环from tqdm import tqdm def batch_download(df_stock_list, start_date, end_date): success_count 0 for _, row in tqdm(df_stock_list.iterrows(), totallen(df_stock_list)): code row[code] name row[name] ok download_daily(code, name, start_date, end_date) if ok: success_count 1 time.sleep(0.5) # 控制请求频率防止被临时封禁 print(f批量下载完成成功: {success_count} 只)这里我特意在每次请求后加了time.sleep(0.5)。原因后面展开说小睡一下能避开很多接口层面的麻烦。调用方式if __name__ __main__: df_stock ak.stock_info_a_code_name() batch_download(df_stock, start_date19900101, end_date20251231)如果你从头开始完整跑一遍5000多只股票即便每只耗时1秒钟也需要大概一个半小时。我建议本地开着电脑让它慢慢跑就行也可以选择分批次执行比如先下沪深主板再下创业板和科创板分开跑更容易排查问题。3.3 断点续传与容错机制全市场几千只股票一次跑完不出任何问题的概率很低。网络波动、接口限流、临时宕机都可能让某只股票的下载失败。因此我设计的download_daily函数里已经带了一个很实用的特性文件已存在则跳过。这个设计的价值在于你第一次跑中断了或者跑了80%挂了重新执行一遍脚本它会自动跳过已经下载成功的股票只补缺失的那部分。我实际跑下来这种“先跑一遍、缺啥补啥”的策略比写复杂的任务队列系统要省心得多。如果想要更高的容错性可以在批量循环里加上失败重试机制。比如某只股票下载失败先记录到失败列表里等第一轮循环结束后再重试两轮fail_list [] # 实现时在download_daily失败时记录code和name # 循环结束后 for attempt in range(3): if not fail_list: break print(f第{attempt 1}轮重试剩余{len(fail_list)}只) retry_list [] for code, name in fail_list: ok download_daily(code, name, start_date, end_date) if not ok: retry_list.append((code, name)) time.sleep(1) fail_list retry_list这样的三层重试基本能把暂时性失败的问题处理掉剩下的如果还失败多半是个股本身数据异常那就需要单独标记人工看了。用CSV文件作为存储和断点标志是我自己特别喜欢的方式。CSV的好处是不需要额外安装数据库从Excel到pandas都能直接打开查看对后续数据处理非常友好。如果你数据量大了之后需要频繁做时序查询再考虑迁移到Parquet或SQLite也来得及下面会聊到。4. 字段解析与数据质量拿到数据只是第一步4.1 日线字段的专业含义很多人下载完数据把CSV打开一看有日期、开盘、收盘、最高、最低、成交量、成交额就觉得齐活了。但真要拿去做量化这里面还有好几层意思需要搞清楚。先说复权。A股的股票会分红、送股、配股除权除息会直接导致股价在某个交易日出现跳空下跌但这个跳空并不是市场行为而是权益变动造成的。如果不做复权处理你的技术指标、均线、回测收益都会失真。akshare提供的adjust参数可以选qfq前复权、hfq后复权、空字符串不复权。前复权是以当前价格为基准调整历史价格好处是当前价格是真实价格看起来自然。后复权是以上市首日价格为基准好处是所有历史价格都是真实的累计收益体现。如果是算长期收益率后复权更准确如果是做技术分析看图前复权更符合直觉。我默认使用前复权。再说字段。我给列改的英文名对应的含义是date交易日期open开盘价close收盘价high最高价low最低价volume成交量单位是手amount成交金额单位是元amplitude振幅即日内最高与最低价之间的波动幅度pct_change涨跌幅相对于前一交易日的百分比change涨跌额当前收盘价与前一收盘价的差值turnover换手率反映交易活跃度这里有三个字段特别值得注意。volume在akshare返回里默认单位是手而不是股如果你要做成交量加总或均量线需要先明确单位amount是成交金额单位是元有时候你会看到别人代码里除以1e8变成亿元都是口径问题不统一好会算错。turnover换手率单位是百分比比如你看到5.2表示换手率为5.2%不是0.052这个细节很容易坑到人。4.2 数据质量检查清单批量下载完成后我强烈建议先做一轮数据体检不要直接拿去跑回测。检查项目我整理成了固定清单完整性每一只股票的行数是否合理2010年以后上市的股票正常交易日大约每年242个如果一只2015年上市的股票到2025年只有不到1000个交易日那就说明数据大概率缺了一半。重复检查日期有没有重复用DataFrame的date.duplicated()一眼就能查出来。空值检查收盘价有没有NaN成交量是不是为0存在这些问题的数据会影响指标计算。排序检查日期是不是从旧到新升序排列有些接口偶尔会返回乱序数据回测前必须按日期sort一下。举个例子随机挑一只股票检查import pandas as pd df pd.read_csv(daily/600519.csv) df[date] pd.to_datetime(df[date]) df df.sort_values(date).drop_duplicates(subsetdate, keeplast) print(日期范围:, df[date].min(), -, df[date].max()) print(总行数:, len(df)) print(空值数量:\n, df.isnull().sum())如果发现某几个字段空值特别多建议回头重新下载那只股票或者考虑用相邻交易日的数据做合理填充。比如volume为空可以用前后交易日取均值但high、low、open、close这几个核心价格字段不建议自己编宁缺毋滥。停牌的日子在数据里根本不会出现所以你会看到正常交易的日子中间有些日期缺失那是正常现象。做策略复权时要注意pandas里的shift(1)是按行移动的不会自动跳过停牌日如果要对齐交易日序列最好先用交易日历把缺失日期补成NaN再说如果你后续要更新数据也需要在增量更新时只追加新增交易日。4.3 本地存储的两种升级方案当数据量积累到一定程度几千个CSV文件虽然也能用但加载效率确实会下降。我个人的习惯是最新下载的日线数据还是保留CSV因为方便查看和检查同时会做一个全量备份转成Parquet格式给回测框架直接调用。使用Parquet的好处非常明显读取速度快、压缩率高、保留数据类型。转换代码很简单import pandas as pd import pyarrow.parquet as pq import os data_dir daily pq_dir daily_parquet os.makedirs(pq_dir, exist_okTrue) for file in os.listdir(data_dir): if file.endswith(.csv): code file.replace(.csv, ) df pd.read_csv(os.path.join(data_dir, file)) df.to_parquet(os.path.join(pq_dir, f{code}.parquet), indexFalse)如果你的机器内存够大另一种方案是把全市场日线数据统一塞进一个SQLite数据库建一个带索引的日期/代码组合表查询起来非常方便。但对于纯研究场景我还是觉得Parquet文件系统最直观一个代码一个文件跟CSV一一对应迁移成本最低。5. 常见问题与排查技巧实录5.1 请求被限流和临时封禁批量下载全市场股票最大的敌人不是数据源没有数据而是数据源不让你连续请求。akshare虽然免费但底层也是爬各财经网站的公开接口频繁访问同一个数据接口很容易触发对方的IP临时限流。我遇到过两次比较典型的情况第一次是我把sleep时间改成0.1秒6000多只股票大概到了2000多只时突然连续报错几百个请求全部超时另一次是深夜挂着下载只设置了sleep0.2秒结果第二天醒来发现只剩最后1000只没下载但重试一直失败。排查了一番大概率是IP触发了临时限流。我的经验是批量下载时sleep设置在0.3到1秒之间比较稳妥。如果你特别赶时间可以把全市场拆成多个批按交易所维度并行跑但并行多进程并发太快也一样会被封所以我不建议新手一上来就搞并发。另外可以在代码里加一个随机延迟import random time.sleep(random.uniform(0.3, 0.8))随机延迟比固定延迟更不容易被识别成脚本实测下来有效。如果真的被临时封禁通常有两种解法一是等一段时间短则几分钟长则半小时等限流解除后再继续二是切换到备用数据源baostock完成剩余股票的下载。5.2 个股历史数据缺失和异常A股市场几千只股票并不是每一只的历史数据都一样完整。新股上市第一天可能没有涨跌幅限制某些退市股可能中途出现极长的停牌还有些股票因为代码变更、吸收合并历史数据会被分割成几个阶段。这些都可能导致数据缺失。以我自己的下载日志为例偶尔会出现某只股票返回的数据行数少得可怜比如一只老牌股票只有几十行那基本可以断定接口只拿到了最近一小段数据历史部分丢了。遇到这种情况我通常先用baostock再拉一次看看import baostock as bs import pandas as pd lg bs.login() rs bs.query_history_k_data_plus(sh.600519, date,open,high,low,close,volume,amount, start_date1990-01-01, end_date2025-12-31, frequencyd, adjustflag2) # 1后复权2前复权3不复权 data [] while (rs.error_code 0) and rs.next(): data.append(rs.get_row_data()) df_bs pd.DataFrame(data, columnsrs.fields) bs.logout()注意baostock的股票代码格式和akshare不一样上证股票要加sh前缀深证股票加sz前缀比如600519就写成sh.600519、000001就写成sz.000001。另外baostock要求先login用完记得logout。如果两个源都拿不到完整数据那大概率是股票本身有问题比如已经退市了或代码被重新分配过。可以人工去财经网站核对一下实在缺失的部分做好记录在回测时剔除即可不需要为一两只股票卡住整个流程。5.3 增量更新以后每天只需要下载新增数据一次性下载完全量历史数据之后真正的挑战是“后续怎么更新”。如果你只是偶尔复盘手动跑一遍全量下载也无所谓反正每天就多一两条数据。但如果你想持续维护一份数据仓库那建议专门写一个增量更新脚本。我的增量更新逻辑很简单每只股票的CSV文件已经存在了只需要读取最后一行数据的日期从这个日期加一天开始重新下载即可。import pandas as pd import akshare as ak import os def update_daily(code, name, end_date): file_path os.path.join(daily, f{code}.csv) if not os.path.exists(file_path): return download_daily(code, name, 19900101, end_date) df_old pd.read_csv(file_path) last_date pd.to_datetime(df_old[date]).max() next_start (last_date pd.Timedelta(days1)).strftime(%Y%m%d) if next_start end_date.replace(-, ): return False try: df_new ak.stock_zh_a_hist( symbolcode, perioddaily, start_datenext_start, end_dateend_date.replace(-, ), adjustqfq ) if df_new is None or df_new.empty: return False df_new.columns [date, open, close, high, low, volume, amount, amplitude, pct_change, change, turnover] combined pd.concat([df_old, df_new], ignore_indexTrue) combined combined.drop_duplicates(subsetdate, keeplast) combined combined.sort_values(date) combined.to_csv(file_path, indexFalse, encodingutf-8-sig) return True except Exception as e: print(f{code} {name} 增量更新失败: {e}) return False这里最关键的细节是日期比较akshare的start_date和end_date都要求格式为YYYYMMDD所以end_date用datetime对象时要记得转换成纯数字字符串。还有增量更新时注意复权方式要和第一次下载保持一致否则前复权价格会因最新价格变动而产生整体偏移。关于前复权增量更新这里有一个很多新手没意识到的坑前复权价格是随最新价变化的如果一只股票中途分红除权了历史所有前复权价格都会被整体调整。这意味着你的CSV里旧数据可能和新下载的数据价格对不上。如果你跑量化回测更建议要么用后复权要么每天全量重下前复权数据或者直接用不复权数据加复权因子做计算。简单说前复权适合长期静态研究增量更新场景更适合后复权或不复权。我自己在维护长期数据仓库时习惯下载不复权原始价格再单独存一份复权因子表这样不管怎么分红除权原始价格永远不变复权计算可以随时重做。5.4 akshare接口变动引发的报错akshare最大的麻烦是接口跟着网站变。最常见的情况是前两天还能跑的代码今天突然报attribute error或者返回空列表。原因通常是目标网站改版了akshare还没及时适配。遇到这种报错我的处理流程是先看akshare版本pip show akshare如果版本比较旧直接upgrade。升级后如果还是不行去看akshare在GitHub上的issue通常已经有人反馈同类问题作者很快会修复。如果短期内没修复就切到baostock或者其他源顶替保证正常用数据。总之免费方案就要接受它偶尔的不稳定。好在akshare社区足够活跃大部分问题都能在一两天内解决完全不需要过度担心。5.5 数据文件编码导致的乱码你如果直接把CSV用Excel打开发现中文列名和股票名称是乱码多半是编码问题。我的download_daily里保存用的是utf-8-sig这个格式带BOM头Excel能正确识别中文。如果你用gbk保存在macOS或Linux上的Python读取时可能会报编码错误如果你用纯utf-8保存Windows上的Excel可能会乱码。utf-8-sig是当前个人体验下的最佳平衡点。6. 数据初步应用与自动化运维建议数据下载好了接下来能干什么这个话题可以单独再写几篇文章。我在这里简单点几个方向帮你确认这半天的折腾是值得的。最直接的用途是自定义技术指标分析。比如你有自己习惯的均线参数、MACD参数很多行情软件默认给的总是几套固定组合用本地数据你想怎么改都行。我自己就维护了一系列自定义指标脚本每次复盘直接读CSV本地计算速度比打开行情软件还快。其次是量化策略回测。有了全市场的日线数据你就能验证均线突破、动量轮动、双均线交叉之类的基础策略。回测框架可以用backtrader、vectorbt或者自己用pandas向量化实现。数据是本策略是花没有干净的数据回测结果都是噪声。还有一类用处是事件研究比如分析某个宏观事件或行业政策发布后全市场上千只股票在随后几个交易日内的平均反应。这种分析需要的就是全市场数据的批量处理能力用本地CSV就能高效完成。如果你想要更省事的维护方式建议把下载脚本放到服务器或者家里的NAS上设定每天收盘后自动运行增量更新再配合一个定时任务# crontab示例每个交易日18点执行增量更新 0 18 * * 1-5 cd /path/to/a-share-data /path/to/venv/bin/python update_daily.py logs/update.log 21Windows用户可以用任务计划程序配置定时触发器效果一样。增量更新逻辑注意上面说的复权问题建议统一用不复权加复权因子或统一用后复权。最后多说一句关于备份的事。数据仓库重在积累一天两天看不出差别但拉长到三五年这就是一笔很有价值的资产。建议定期把daily目录整体备份一次阿里云OSS、腾讯云COS或者本地移动硬盘都行别等到硬盘坏了才后悔。踩过几次坑之后现在我的整套流程已经非常稳固了akshare主拉全量历史baostock辅助补遗漏增量更新每天自动跑CSV原始数据保留一份Parquet转换一份给回测框架。这套东西对个人研究者来说已经足够扛住绝大多数日常需求。如果你也在搞量化或日常复盘照着这个思路把数据底座搭起来后面会省心很多。
返回列表