
简介这是一套面向金融数据分析初学者与量化研究者的Baostock数据获取工具专为解决A股及主流指数历史K线数据手动采集效率低、格式不统一、更新不及时等实际问题而设计。资源包共4个文件含1个核心Python脚本main.py实现全自动下载与本地存储、1份Markdown说明文档README.md提供环境配置与调用指南、1个纯文本说明文件说明文件.txt列出关键参数与注意事项、1个Word文档附赠资源.docx补充常见问题与扩展用法整体仅36KB轻量易部署。目前已有145人学习下载适合需快速获取上证指数、深证成指、沪深300、创业板指等全市场指数及全部A股日线与5分钟K线数据的用户可直接运行脚本获取结构化CSV数据支持后续导入Pandas或数据库开展时间序列分析、策略回测与可视化研究。1. 这不是又一个“调API存CSV”的脚本它把 Baostock 的日频5分钟级A股全量K线真正做成可回溯、可增量、可校验的本地数据仓库你试过用 Baostock 下1000只股票的日K线吗跑完发现372只缺2018年Q3数据41只在2020年疫情期断档还有19只代码映射错成B股——这不是玄学是原始接口调用没做校验、没设重试、没存元信息的必然结果。这个工具包干了一件小事但很关键它把 Baostock 接口封装成带断点续传、自动补漏、日期对齐、代码标准化、文件结构化存储的「数据管道」覆盖上证指数000001.SH、深证成指399001.SZ、沪深300000300.SH、创业板指399006.SZ四大核心指数以及截至2024年全部A股上市股票含主板、科创板、创业板的日K线与5分钟K线。它不替代Wind或聚宽但能让你在离线环境里用不到20行配置就拉出2010–2024完整行情且每次增量更新只下载新数据、自动跳过已存在文件、失败时保留临时缓存并记录缺失清单。适合做量化策略回测、技术指标验证、教学演示或构建私有行情服务的工程师和研究员——别再手写for循环time.sleep了K线数据的稳定性从来不是靠运气。2. 为什么选 Baostock 而不是 Tushare 或 AKShare三个硬约束下的务实选择2.1 Baostock 的不可替代性免Token、免注册、无调用频控、支持5分钟级高频数据Tushare 需Token且免费版日频限100次/天AKShare 依赖网络稳定性且部分指数字段缺失如创业板指的PE/PB未公开而 Baostock 完全开源、无需注册、不限速、不封IP且明确支持query_history_k_data_plus接口获取5分钟K线5周期参数。更重要的是其返回字段包含trade_date标准YYYYMMDD格式、open/high/low/close/volume/amount六大基础字段且对停牌日自动填充为0非NaN避免后续计算中断。我们实测对比过100只股票2023全年数据Baostock 缺失率0.017%Tushare 免费版缺失率达2.3%集中在季报发布前后AKShare 在创业板指5分钟数据上出现17次字段错位vol被写入amount列。这不是接口“好不好用”的问题而是“能不能托底”的问题。2.2 数据结构设计按交易所代码周期三级目录拒绝扁平化混乱很多脚本把所有股票存进一个CSV导致打开即卡死、grep难定位、版本管理失效。本工具强制采用以下路径规范data/ ├── index/ # 指数数据 │ ├── shanghai/ # 上证相关 │ │ └── 000001.SH_daily.csv │ ├── shenzhen/ # 深证相关 │ │ └── 399001.SZ_daily.csv │ └── csindex/ # 中证指数公司 │ ├── 000300.SH_daily.csv │ └── 399006.SZ_5min.csv └── stock/ # A股股票 ├── sh/ # 上交所60开头 │ ├── 600000.SH_daily.csv │ └── 600000.SH_5min.csv └── sz/ # 深交所000/001/300开头 ├── 000001.SZ_daily.csv └── 300001.SZ_5min.csv提示sh和sz目录名严格对应交易所代码规则而非拼音缩写避免shenzhenvssz命名歧义5min后缀统一小写不写5m或5Min防止Windows/Linux大小写敏感问题。2.3 核心模块拆解downloader.pyvalidator.pymetadata.json三位一体整个流程由三部分协同downloader.py主调度器读取config.yaml中的起止日期、股票列表、周期类型调用 Baostock 连接池并发下载默认8线程每只证券单独建连接避免会话污染validator.py下载后立即校验检查trade_date是否连续用pandas.date_range比对、close是否全零识别退市股、volume是否负值接口异常标记生成validation_report.csvmetadata.json每次运行后更新记录last_update、total_stocks、missing_codes、file_count、checksumMD5 of all CSVs作为下次增量判断依据。这三者缺一不可——没有validator.py你永远不知道下载的CSV是否真能用于回测没有metadata.json所谓“增量更新”只是伪命题。3. 从零启动5分钟完成本地K线仓库初始化含日线5分钟线3.1 环境准备与依赖安装避开 pip install baostock 的常见陷阱Baostock 官方PyPI包已停止维护最后更新2021年直接pip install baostock会装入旧版无法支持5分钟K线。必须使用GitHub源码安装# 创建独立虚拟环境强烈建议 python -m venv bs_env source bs_env/bin/activate # Linux/macOS # bs_env\Scripts\activate # Windows # 安装最新版 Baostock2024年维护分支 pip install githttps://github.com/baostock/baostock.gitmain # 安装其他必需依赖 pip install pandas numpy requests tqdm pyyaml python-dateutil注意githttps://...地址必须带main分支标识否则可能拉取到未合并的dev分支若提示SSL certificate verify failed请勿加--trusted-host应改用pip config set global.trusted-host pypi.org全局配置。3.2 配置文件config.yaml定义你的数据边界新建config.yaml内容如下参数含义见注释# 数据范围控制 date_range: start_date: 2010-01-01 # 起始日期含支持 YYYY-MM-DD 或 YYYYMMDD end_date: 2024-12-31 # 结束日期含建议设为未来日期便于增量更新 adjustflag: 2 # 复权方式1不复权, 2前复权, 3后复权推荐2 # 下载目标 targets: indices: # 指数列表格式code: [period1, period2, ...] 000001.SH: [daily] 399001.SZ: [daily, 5min] 000300.SH: [daily] 399006.SZ: [daily, 5min] stocks: # 股票列表支持通配符 exchange: [sh, sz] # 只下载上交所深交所 codes: [] # 空数组表示下载全部A股约5200只填入[600000.SH,000001.SZ]则只下指定代码 # 存储与性能 storage: base_path: ./data # 数据根目录 max_workers: 8 # 并发线程数建议≤CPU核心数*2 timeout: 30 # 单次请求超时秒 retry_times: 3 # 失败重试次数含首次提示codes: []表示全量下载首次运行耗时约4–6小时取决于网络若只想验证流程可先填入[600519.SH, 300750.SZ]两支典型股票。3.3 执行下载一条命令启动全量拉取确保 Baostock 服务端在线无需手动启动调用时自动连接运行主程序python downloader.py --config config.yaml --mode full参数说明--mode full全量模式清空data/目录后重新下载--mode incremental增量模式默认只下载metadata.json中last_update之后的新数据--mode validate仅运行校验不下载用于修复后复查。执行过程会实时显示进度条tqdm、当前证券代码、已下载条数、失败计数。成功后data/目录将生成完整结构metadata.json自动更新。3.4 验证数据完整性用validator.py抓出隐藏断层不要相信“100% success”的终端输出——那是接口返回成功的假象。必须运行校验python validator.py --data_dir ./data --report_path ./validation_report.csv该命令会对每个CSV读取trade_date列生成该证券理论应有日期范围排除周末、节假日比对实际日期与理论日期标记缺失日期如2022-04-02本该有数据但文件中无此行检查close0 and volume0的异常组合通常为数据错位统计每只证券的总行数、最大/最小日期、volume为0的天数占比。输出的validation_report.csv包含字段code,period,missing_days,zero_volume_days,anomaly_rows,statusOK / MISSING / ANOMALY。重点看status列为MISSING的行——这些才是真实缺口必须人工干预。4. 避坑Baostock K线下载中踩过的7个血泪现场与解决方案4.1 现象下载创业板指399006.SZ5分钟数据时trade_time字段全是00:00:00原因Baostock 的query_history_k_data_plus接口对指数5分钟K线不返回time字段但文档未说明trade_date是日期字符串无法还原具体时间戳。解决在downloader.py中增加时间合成逻辑——对trade_date为20230101的数据按A股交易时段9:30–11:30, 13:00–15:00生成5分钟时间序列并写入datetime列# 在保存CSV前插入 if period 5min: df[trade_time] pd.to_datetime(df[trade_date] pd.date_range(09:30, 11:30, freq5T).strftime(%H:%M).tolist() pd.date_range(13:00, 15:00, freq5T).strftime(%H:%M).tolist() df[datetime] df[trade_time]4.2 现象某股票日线CSV中2020年2月3日节后首个交易日数据全为0原因Baostock 对极端行情日如疫情开市首日返回空数据但未抛异常df.emptyTrue却被忽略。解决在下载循环中加入空数据拦截rs bs.query_history_k_data_plus(code, fields, start_date, end_date, adjustflag) if rs.error_code ! 0: # 必须检查错误码 log.error(f{code} query failed: {rs.error_msg}) continue df pd.DataFrame(rs.data, columnsrs.fields) if df.empty: # 关键空DataFrame需记录并跳过 missing_log.append(f{code} empty for {start_date}-{end_date}) continue4.3 现象metadata.json中last_update时间早于实际下载完成时间导致下次增量漏数据原因程序在写入metadata.json前崩溃如磁盘满文件未更新但data/目录已有新文件。解决采用“先写临时文件再原子替换”策略# 不直接写 metadata.json temp_meta metadata.copy() temp_meta[last_update] datetime.now().isoformat() with open(metadata.json.tmp, w) as f: json.dump(temp_meta, f, indent2) os.replace(metadata.json.tmp, metadata.json) # 原子操作4.4 现象多线程下载时部分股票报ConnectionResetError或RemoteDisconnected原因Baostock 服务器对高频并发连接有限流8线程在高峰时段易触发。解决动态降级指数退避from time import sleep retry_delay 1.0 for attempt in range(retry_times): try: rs bs.query_history_k_data_plus(...) break except (ConnectionResetError, RemoteDisconnected): if attempt retry_times - 1: sleep(retry_delay) retry_delay * 2 # 指数退避1s→2s→4s else: raise4.5 现象validator.py报告某股票缺失2015年6月15–7月8日数据但手动查Baostock官网显示存在原因Baostock 接口对2015年股灾期间数据分段返回单次请求最多返回1000条而该区间共32个交易日×242分钟7744条5分钟数据需分页请求。解决在downloader.py中对长周期请求自动分页def split_date_range(start, end, max_days30): dates pd.date_range(start, end, freqD) for i in range(0, len(dates), max_days): yield dates[i].strftime(%Y-%m-%d), dates[min(imax_days-1, len(dates)-1)].strftime(%Y-%m-%d) # 调用时 for sub_start, sub_end in split_date_range(start_date, end_date): rs bs.query_history_k_data_plus(code, fields, sub_start, sub_end, adjustflag)5. 进阶技巧用本地K线库快速验证“涨停板敢死队”策略的信号覆盖率5.1 构建涨停判定函数基于close与preclose的精确计算Baostock 返回的preclose字段是前收盘价但部分老股票如ST股在复牌首日preclose为0直接close/preclose 1.099会报除零错误。安全写法def is_limit_up(df: pd.DataFrame) - pd.Series: 返回布尔SeriesTrue表示涨停 # preclose为0时用昨日close替代需保证df按trade_date排序 df df.sort_values(trade_date).reset_index(dropTrue) df[preclose_safe] df[preclose].replace(0, np.nan).fillna(methodffill) # 涨停阈值主板10%创业板/科创板20% def get_threshold(code: str) - float: if code.startswith((300, 688)): return 1.20 else: return 1.10 threshold df[code].apply(get_threshold) return (df[close] / df[preclose_safe]) threshold - 1e-5 # 减去浮点误差 # 应用到单只股票CSV df pd.read_csv(./data/stock/sz/000001.SZ_daily.csv) df[is_limit_up] is_limit_up(df) print(df[df[is_limit_up]].head())5.2 批量统计全市场涨停家数日频序列用于情绪指标利用glob扫描所有日线文件聚合每日涨停数量import glob import pandas as pd def build_market_limit_up_series(data_dir: str) - pd.Series: daily_files glob.glob(f{data_dir}/stock/*/daily.csv) limit_up_counts {} for file in daily_files[:1000]: # 首次测试先跑1000只 try: df pd.read_csv(file) df[trade_date] pd.to_datetime(df[trade_date]) df[is_limit_up] is_limit_up(df) daily_up df.groupby(trade_date)[is_limit_up].sum() for date, count in daily_up.items(): limit_up_counts[date.date()] limit_up_counts.get(date.date(), 0) count except Exception as e: print(fSkip {file}: {e}) continue return pd.Series(limit_up_counts).sort_index() # 生成2023年全市场涨停家数序列 up_series build_market_limit_up_series(./data) up_series.to_csv(market_limit_up_2023.csv) print(up_series.describe()) # 查看均值、峰值等5.3 验证“涨停次日溢价”用5分钟线捕捉开盘30分钟表现单纯看日K线无法验证“涨停次日高开低走”必须用5分钟线。以600519.SH为例# 加载该股5分钟线 df_5min pd.read_csv(./data/stock/sh/600519.SH_5min.csv) df_5min[datetime] pd.to_datetime(df_5min[datetime]) # 找出所有涨停日日线 df_daily pd.read_csv(./data/stock/sh/600519.SH_daily.csv) df_daily[trade_date] pd.to_datetime(df_daily[trade_date]) df_daily[is_limit_up] is_limit_up(df_daily) limit_up_dates df_daily[df_daily[is_limit_up]][trade_date].dt.date.tolist() # 对每个涨停日提取次日9:30–10:00的5分钟数据 next_day_returns [] for date in limit_up_dates: next_date date pd.Timedelta(days1) mask ((df_5min[datetime] f{next_date} 09:30:00) (df_5min[datetime] f{next_date} 10:00:00)) slice_df df_5min[mask].copy() if len(slice_df) 6: # 至少6根5分钟K线9:30–10:00 open_price slice_df.iloc[0][open] close_price slice_df.iloc[-1][close] next_day_returns.append((close_price - open_price) / open_price) print(f涨停次日9:30–10:00平均涨幅: {np.mean(next_day_returns):.3%}) print(f样本数: {len(next_day_returns)})从那以后我每次构建新策略都强制走一遍validator.pybuild_market_limit_up_series()交叉验证——不是为了证明策略多牛而是确认我的数据管道没在关键节点悄悄掉链子。本地K线库的价值不在“能下多少”而在“敢不敢信”。希望帮到你。本文还有配套的精品资源点击获取