股票大数据分析Python实战:爬虫抓取、数据清洗与可视化 简介面向金融数据分析与Python爬虫学习者的实战资料包围绕股票大数据全流程展开从爬虫采集历史行情与新闻公告到Pandas清洗缺失值与异常值再到Matplotlib、Seaborn绘制K线图和波动性图表并延伸至Scikit-learn线性回归、随机森林等模型的股价预测以及Tushare/Wind接口实时数据获取与Flask/Django可视化部署。资源包为7z压缩格式大小约4.47MB当前文件总数及类型明细未单独标注适合已掌握Python基础、希望切入量化分析或金融数据挖掘场景的读者。已有3645人学习下载。通过随包资料可以系统梳理爬虫反爬处理、数据预处理、统计分析和机器学习建模的关键代码思路为独立开展股票数据分析项目提供可直接参考的实践框架与排错经验。1. 基于股票大数据分析Python实战爬虫只是入口别在第一步就耗尽耐心接手一个基于股票大数据分析的Python实战项目头一件事不是选模型而是把行情数据从公开接口搬回本地。很多朋友以为python爬虫是重头戏真动手才发现爬虫只占半小时后面的清洗、对齐、落盘才是无底洞。这套实战资源就是围绕“行情抓取—清洗—存储—指标计算—可视化”这条主线组织的适合刚开始做量化分析、又不想从零摸索代码的人。说句实话股票行情抓取本身不难难在数据可信、字段对齐、图表可读。下文讲的都是我在真实复现中踩过又填平的细节你可以照着跑也可以在避坑清单里找后悔药。2. 数据抓取层Python爬虫选型与股票行情接口实战2.1 三种数据获取方式为什么选爬虫股票日线数据至少有三种拿法财经网站手动导出CSV、免费公开接口、付费数据终端。手动导出适合一次性小样本但批量拉几十只股票时鼠标点几百次不现实。付费终端数据全但很多数据在客户端里想交给python处理还得再导一道。爬虫方式最灵活直接请求接口拿JSON在内存里转成DataFrame后面写Excel还是入库都顺。选型之后要明确一个边界爬虫抓的是“公开接口能返回的数据”不是绕过登录权限拿非公开数据。做量化分析用日线级别的开高低收、成交量已经够用。请求频率也要克制我一般会设置0.5秒以上的间隔减少对数据源的打扰。方式优点缺点适合场景手动导出CSV简单直接无法批量、耗时一次性分析公开接口爬虫可批量、可定时字段不稳定、需清洗日线数据、量化初阶付费终端数据质量高环境隔离、成本高生产级策略相比Java或C的工程化路线Python做数据探查的优势是生态pandas和matplotlib这类库把结构化数据操作和绘图都收进内存几行代码就能完成。有人主张直接上Selenium渲染页面但日线行情是标准接口数据requests足够。Selenium慢且容易被检测适合做页面型数据源不适合高频率更新。选型时先看数据源有没有JSON接口有就优先requests。2.2 日K线抓取的最小可跑脚本确定用爬虫后第一步是把“一个请求”跑通。我一般先把接口返回打印出来确认字段结构再写解析。下面是抓日K的最小脚本框架具体接口地址放在配置里换数据源时只改一处。import requests def fetch_daily_kline(symbol, start_date, end_date, api_conf): 抓取日K线 symbol: 股票代码如 600000 api_conf: 接口配置字典包含 url / headers / params params { symbol: symbol, start: start_date, end: end_date, freq: daily } params.update(api_conf.get(extra_params, {})) resp requests.get( api_conf[url], paramsparams, headersapi_conf[headers], timeout10 ) resp.raise_for_status() # 有的接口把数据放在 list 字段里有的放在 data 里这里做兼容 payload resp.json() rows payload.get(list, payload.get(data, [])) return rows逻辑说明params负责查询条件symbol传股票代码start和end控制区间freq表示K线频率。requests.get发出请求timeout10限制最长等待时间避免某次请求卡死整个循环。resp.raise_for_status()在状态码非2xx时直接抛异常防止后端返回错误页时还被当数据解析。最后用payload.get(list, payload.get(data, []))做两层兼容很多行情接口数据节点名不一样这一行能省掉不少调试时间。参数说明api_conf[headers] 至少要包含User-Agent和Referer建议直接从浏览器开发者工具复制完整请求头而不是手写精简版。start_date和end_date用YYYY-MM-DD格式多数接口是闭区间多传一两天问题不大少传会缺数据。extra_params是留给接口特殊参数的兜底字典比如有些接口需要传market或source字段。强调一点不要在这个脚本里做循环批量请求先单独跑一只股票确认返回结构没问题再套循环。批量抓取的坑往往不是接口不通而是字段取值和频率限制。2.3 把接口返回整理成DataFrame字段映射与类型转换接口返回一般是JSON数组直接转成DataFrame后列名、类型、顺序都是乱的。我的习惯是三步走重命名列、固定列顺序、统一类型。import pandas as pd def to_frame(rows, field_map): # field_map 示例{trade_date: date, open_price: open} df pd.DataFrame(rows) df df.rename(columnsfield_map) # 固定列顺序防止接口字段顺序调整导致后续计算错位 need [date, open, high, low, close, volume] df df[need].copy() for col in [open, high, low, close, volume]: df[col] pd.to_numeric(df[col], errorscoerce) df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) return df逻辑说明rename把接口原始字段名映射为统一命名之后代码里只认date、open、high、low、close、volume。df[need]这一步本质是数组切片把列顺序固定下来同时丢掉多余字段。pd.to_numeric配合errorscoerce会把空字符串、非数字字符转成NaN类型统一为float。最后to_datetime把日期统一成时间戳类型sort_values按日期升序reset_index让行号从0开始连续避免画图时索引断层。参数说明field_map必须按实际接口字段填写如果字段没映射到df[need]里会直接报KeyError这个错误比后续数据错位好排查。errorscoerce会把脏值变成NaN后面清洗时统一处理不要改成errorsraise否则一条脏数据会让整个抓取任务崩溃。reset_index(dropTrue)里的dropTrue很关键不加的话旧索引会变成新列后续绘图和计算都可能出现错行。这份资源里fetch_daily_kline和to_frame是配套使用的先抓后转后面所有清洗和画图都基于这个统一结构。下载后建议先跑通这两步再进入指标计算。如果接口返回的是CSV格式而不是JSON解析逻辑会简单一些直接用pd.read_csv读取字符串即可但字段重命名的步骤不能省。3. 数据清洗与落盘DataFrame到Excel的五个关键点3.1 复权处理为什么不能直接把原始价格拿去做指标股市里有分红送股价格会在除权日出现跳空。比如一只股票10元每10股派5元现金除权后价格会下调但这个下调不代表真实亏损。如果直接用原始价格算移动平均或涨跌幅指标会在除权日出现虚假信号。最常见做法是前复权以最新价格为基准调整历史价格。def adjust_price(df, factor_coladj_factor): if factor_col not in df.columns: # 没有复权因子时至少提醒一次避免静默用原始价 print(warning: no adj_factor found) return df base df[factor_col].iloc[-1] df[adj_ratio] df[factor_col] / base for col in [open, high, low, close]: df[col _adj] df[col] * df[adj_ratio] return df逻辑说明base取最后一个交易日的复权因子作为前复权基准。adj_ratio是当日因子和基准因子的比值历史价格乘以这个比值后最新交易日价格保持不变历史价格按比例缩放这样除权跳空就被抹平了。打印warning是为了避免你忘了带复权因子列数据看起来没问题但实际上没有复权。参数说明factor_col默认叫adj_factor如果数据源叫factor或adj_factor_1要显式传入。复权后的列名加了_adj后缀不覆盖原始列。好处是可以并排对比坏处是后续计算前要记得统一用哪个收盘价列否则容易混。如果数据源直接给adj_close就不需要这个函数但要做一次校验adj_close和原始close在最新一行应该相等。另一个细节前复权后历史价格理论上不会变成负数但遇到极端除权会导致历史价格很低。如果出现奇怪的值优先检查因子序列是否倒序而不是怀疑算法。3.2 去重与时间线检查停牌和缺失是两回事清洗的第二步是处理重复行和缺失日期。接口偶尔会返回重复数据尤其做增量更新时旧数据和新数据衔接容易产生重复行。去重很简单但日期连续性问题要多说一句股票天然有休市日不能把“非交易日”当缺失。def clean_ts(df): df df.drop_duplicates(subset[date], keeplast) df df.sort_values(date) df df.reset_index(dropTrue) diff df[date].diff().dropna() gaps diff[diff pd.Timedelta(days2)] if len(gaps) 0: print(candidate gap rows:, len(gaps)) return df逻辑说明drop_duplicates按日期去重keeplast表示保留最后一条适合增量更新里新数据覆盖旧数据。diff计算相邻交易日的间隔间隔超过两天意味着这里可能有停牌或数据缺失。打印数量而不是直接抛错是因为停牌本身合法需要人工判断是补数据还是保留停牌。参数说明keeplast在回测场景里要慎用如果你拼接的是“旧数据在前、新数据在后”重复行保留后者是对的如果顺序反了结果就反了。pd.Timedelta(days2)是判断阈值正常周五到下周一间隔是3天自然日会在gaps里出现这不算异常。所以只看数量、不做自动补值避免误判断。对于真实缺口画图时用日期索引pandas会自动跳过缺失交易日不要手动插值。手动插值等于给回测数据加了不存在的交易信息属于常见误用。3.3 写入Excel多sheet输出和日期格式处理清洗完的数据最终要落盘Excel是最轻量的交付格式。使用to_excel时三个细节值得注意需要多sheet时用ExcelWriter日期列转成date后再写避免Excel里看到的是一串时间戳写完顺手检查行数和原始行数是否一致。def save_excel(df_dict, filepath): from pandas import ExcelWriter with ExcelWriter(filepath, engineopenpyxl) as writer: for sheet_name, df in df_dict.items(): df_out df.copy() if date in df_out.columns: df_out[date] df_out[date].dt.date df_out.to_excel(writer, sheet_namesheet_name, indexFalse) print(saved:, filepath)逻辑说明ExcelWriter是上下文管理器with块结束时会把所有sheet一次性写入文件。df_dict是字典key是sheet名value是DataFrame。日期转成dt.date是为了让Excel识别成日期格式而不是一串毫秒数。indexFalse表示不写DataFrame的行号否则Excel里会多出一列0、1、2。参数说明engineopenpyxl专门处理xlsx文件如果环境里没装需要先pip install openpyxl。sheet_name不能重复重复会覆盖中文sheet名在部分老版本Excel里兼容性一般建议用英文或拼音。数据量超过几十万行时to_excel会明显变慢此时应该转用to_sql写库。资源里附带了一个sqlite落地模块接口和这个函数保持一致的调用方式换行连接串就能切到MySQL或Oracle。用python连接oracle查询数据本质上也是这个套路先建连接再执行SQL或to_sql区别只在驱动配置上。清洗和落盘做到这里数据已经能交付给Excel或数据库了。再往后是计算指标和画图也是这套资源里最容易看到效果的部分。4. 指标计算与可视化定义函数、数组切片和坐标轴密集的解法4.1 把指标封装成def函数调参只改一处量化分析里最常用的就是移动平均、涨跌幅、交叉信号。如果不封装函数每个脚本都重复写rolling和shift一旦需求变化到处改。封装后参数暴露在函数签名里调参只改一处。def sma(series, window): # 简单移动平均窗口为 window 个交易日 return series.rolling(windowwindow, min_periodswindow).mean() def calc_change_pct(df): df[change_pct] df[close].pct_change() * 100 return df逻辑说明sma用rolling滑窗计算均值min_periodswindow保证前window-1行结果是NaN避免用不完整窗口算出虚值。calc_change_pct用pct_change计算涨跌幅再乘100转成百分比。参数说明window在两条均线配合时通常给5、10、20、60这类常见周期。不同搭配会产生完全不同的交叉信号。pct_change默认是用当前值除以上一行再减1也就是相对前一交易日的变化不是相对开盘价。对涨跌幅列做聚合时算术平均没有意义应该用乘积或log收益这点在回测里容易被忽略。计算指标时不要用for循环逐行算均线DataFrame的rolling和shift都是矢量化实现速度差一个量级代码也短。我第一次写交叉信号时用了循环跑几百行数据没问题换成几千只股票时直接卡死改成矢量化操作后才顺畅。资源里的指标模块全部按矢量化写法组织不建议为了“看得懂”退回循环版本。4.2 数组切片与数据对齐信号算错通常是索引没对齐DataFrame里做运算时pandas会自动按索引对齐。一旦之前做过筛选、拼接或遗漏reset_index两个Series的索引就会错位轻则NaN重则整个交叉信号偏移一根K线。解决思路是提前固定索引顺序关键运算后用数组切片做验证。def add_ma_cross(df, short5, long20): df df.sort_values(date).reset_index(dropTrue) df[ma_short] sma(df[close], short) df[ma_long] sma(df[close], long) # 上穿信号短均线今天大于长均线且昨天不大于 df[cross_up] (df[ma_short] df[ma_long]) \ (df[ma_short].shift(1) df[ma_long].shift(1)) return df逻辑说明先做reset_index(dropTrue)强制索引从0开始连续这是对齐的前提。cross_up列由两个布尔条件做与运算今天短均线在长均线上方且昨天不在严格捕获上穿的那一行。shift(1)取的是上一交易日的值如果索引错位shift结果就不对应了。参数说明short和long是窗口参数5和20是短中期常用组合修改这两个数字会直接改变信号数量。布尔与运算里的括号不能省优先级比比较运算符低不写括号会报错或结果全False。如果df做过按股票代码分组务必先groupby再对组内排序否则不同股票的数据混在一起算出的均线是错的。4.3 画图横坐标太密集matplotlib三个参数解决一直有朋友问“python画图横坐标太密集”。其实不是数据问题是刻度数量问题。当数据跨度几百个交易日绘图库默认每个日期都标刻度横坐标自然挤成一团。解法是用定位器控制刻度间隔。import matplotlib.pyplot as plt import matplotlib.dates as mdates def plot_close(df, figsize(12, 6), interval30): fig, ax plt.subplots(figsizefigsize) ax.plot(df[date], df[close], linewidth1.2, labelclose) ax.xaxis.set_major_locator(mdates.DayLocator(intervalinterval)) ax.xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m-%d)) plt.xticks(rotation45, haright) plt.ylabel(price) plt.legend() plt.tight_layout() plt.show()逻辑说明set_major_locator控制主刻度落在哪些日期上DayLocator(interval30)表示每30天标一个刻度。set_major_formatter把刻度标签格式化为“年-月-日”。rotation45旋转标签haright让标签右对齐减少重叠。tight_layout自动调整边距防止底部标签被截断。参数说明interval要结合数据跨度调整。数据只有30天interval5更合适数据跨度一年以上interval60甚至90都可以。如果横坐标不是日期而是字符串需要先转成datetime再plot否则DateLocator不生效。另一种常见做法是只显示部分数据df.iloc[::step]做数组切片降采样。但要清楚切片后的趋势线形状不变但真实数据点稀疏了可以和定位器组合使用。均线指标和交叉信号画出来后基本能判断一段趋势是否可信。这套资源里还有MACD的扩展脚本原理和上面一样核心是先保证close序列干净且对齐。任何指标算出来全NaN不要怀疑公式先回头检查rolling的min_periods和索引顺序。5. 避坑清单爬虫、环境、画图三类高频翻车现场下面把前面几章分散的踩坑经验汇总成清单。每条记录都按“现象、原因、解决”三行说明方便直接对号入座。5.1 环境与安装的坑现象python环境刚装好pip install pandas时出现下载超时、编译报错或者提示找不到匹配版本。原因默认包源访问慢网络波动大另外Python版本过新或过旧导致某些包没有对应wheel包。最常见的是直接在系统全局环境装包把环境搞脏后面项目依赖互相冲突。解决先给这个项目建虚拟环境在虚拟环境内安装。pip下载时显式指定国内镜像源不要用默认源反复重试。如果某个包本地编译失败去找对应Python版本的whl文件安装安装顺序要注意先装numpy再装pandas最后装matplotlib依赖顺序反了会重复编译。用vscode配置python环境时记得先激活虚拟环境再打开编辑器否则解释器选不对代码里import pandas会直接报ModuleNotFoundError。5.2 爬虫请求与数据字段的坑现象同样的接口地址在浏览器里能打开在python requests里返回403或者返回的不是JSON而是验证页面。原因requests默认的User-Agent没有浏览器特征接口识别出脚本访问后拒绝服务。还有一类情况是缺少Referer接口按来源校验请求。这种反爬不算复杂但第一次遇到时完全没有头绪属于典型的黑匣子。解决用requests.Session保持会话把浏览器开发者工具里的User-Agent、Referer、Accept完整复制到headers里。如果拿到的还是HTML先打印前200个字符确认响应格式不要直接调json()否则会在解析阶段抛错。频率控制也是关键循环请求时每次sleep 0.5到1秒批量从一只股票扩展到几十只时间隔不够很容易触发临时限制。别等到被限再补救一开始就把延时节流写进采集函数。现象接口昨天还能返回字段今天就少了某列脚本直接KeyError中断。原因行情接口字段属于公共数据接口方调整字段名或返回结构时一般不会提前通知脚本使用者。遇到这种问题只能靠代码兜底。解决解析时不要直接df[date]用df.get(date)或先判断列是否在df.columns中。关键字段缺失时跳过该条数据并打印日志而不是当场崩溃。这样至少能把问题隔离在一行数据上让整体抓取任务跑完。下载数据后也要定期抽查字段结构接口变更时第一反应不是改下游计算而是检查解析层有没有拦住。5.3 时间序列与指标计算的坑现象计算移动平均后序列前段出现一大段NaN和预期不符。原因rolling默认min_periods等于window窗口没攒够前全是NaN这是正常的。但如果你在回测里直接拿这段NaN去算信号策略会在前window天完全无信号影响对早期数据的评估。解决明确需求再决定。如果画图展示保留NaN没问题如果回测建议给足窗口计算期不要用0填充。用0填充会制造出均线突然从0开始回升的假象属于自我欺骗。正确的做法是把样本起点往前推给指标留出预热窗口或者用min_periods1让窗口内有多少数据就按多少数据算。但min_periods1在早期数据少时均值波动会异常大要清楚这个代价。现象复权后的价格曲线和行情软件对不上或者指标曲线有明显断裂。原因原始价格序列混入了未复权数据或者前复权基准选错。有的接口返回的是“复权因子”而不是“复权价”如果你直接把复权因子当成价格去算结果全是错的。解决先确认数据源的字段含义。factor是乘数不是价格。使用前复权时基准应选最新交易日的因子。做完复权后抽一个除权日和行情软件对比收盘价误差在0.01以内才算过。这里没有玄学逐行核对最可靠。6. 进阶玩法定时增量抓取与数据校验的一线习惯6.1 增量更新只抓新增区间不重放全量项目跑通之后最实际的需求是每天更新数据。全量重抓虽然省脑子但接口压力大而且随着历史数据变长每次全量重写Excel会越来越慢。增量更新的原则是本地有的不重抓只补本地最后日期之后的数据。def incremental_update(exist_df, new_df): if len(exist_df) 0: return new_df last_date exist_df[date].max() append_part new_df[new_df[date] last_date] merged pd.concat([exist_df, append_part], ignore_indexTrue) merged merged.drop_duplicates(subset[date], keeplast) merged merged.sort_values(date).reset_index(dropTrue) return merged逻辑说明last_date取本地数据的最大日期注意不是当天日期避免在盘中拿到未收盘的残缺数据。new_df里只保留大于last_date的行再和旧数据拼接。concat之后用drop_duplicates守一道防止边界日期重复。最后按日期排序并重置索引。参数说明keeplast在这很关键如果新数据和旧数据在last_date这一天都有一行应该保留新的。append_part如果为空说明行情还没更新不要强行更新。这个函数适合日线不适合分钟线。分钟线要考虑复权因子变化会导致历史价格重算不能只做增量拼接。6.2 用三个样本点验证抓取结果抓完数据后不要直接拿去算指标先验证。我的习惯是抽三个交易日最近一天、一个月前、除权日和行情软件对比收盘价。再检查涨跌幅列的最大值和最小值是否在正常范围比如个股日涨跌幅一般不超过20%。最后看最后一行的日期是否为最近交易日。整个验证过程最好封装成一个函数每次增量更新后自动执行。6.3 参数化批量抓取加股票不改代码最后一个习惯是把股票代码列表和日期范围放到配置里爬虫只负责按配置执行。常见做法是维护一个tickers.json写一个循环逐只抓取。批量抓取的关键是控制节奏单线程加sleep比多线程好调试至少先把可靠跑通再提速度。从那以后我每次写完抓取脚本都会强制走一遍字段检查和样本验证先跑通一只股再放量跑几十只。这个顺序帮我挡掉了至少三回批量翻车。希望帮到你。本文还有配套的精品资源点击获取