简介这套Python股票数据爬虫、分析与可视化框架是一份适合计算机相关专业学生用于毕业设计、课程设计或期末大作业的完整项目整体获得98分评价。项目经过严格调试可直接运行主要面向需要项目实战练习的初学者及正在准备毕设的高校生。资源共包含11个文件压缩包大小约260KB文件类型涵盖5个Python脚本、2个编译缓存文件、1个依赖说明、1个HTML模板、1个JavaScript库和1个Markdown说明文档其中Python脚本覆盖股票基础信息爬取、历史行情采集、数据查看与主程序入口HTML与JS构成可视化前端README提供项目说明结构清晰便于二次开发。目前已有282人学习下载。通过该资源读者可以快速理解从数据抓取、清洗存储到图表展示的完整流程掌握ECharts可视化组件的应用方式并直接基于源码扩展功能或完成课程答辩省去从零搭建环境与调试的时间。整体来看这是一套能够直接用于毕设答辩的高分参考项目。1. 这个框架到底在解决什么一条完整数据链路而不是三个零散脚本git 上挂着「高分完整项目代码」的股票项目一抓一大把但真正拿到手能跑、能改、能写进简历的其实都有一个共同结构爬虫拿数据、pandas 做分析、绘图库出结论三层串成一条流水线。绝大多数人栽在半路不是爬虫写不出来而是数据格式不统一、复权没处理、日期索引错位最后图表画出来自己都不信。这套框架的价值就在「串」字上。它的目标不是给你一个预测涨停的黑匣子而是让任何一个交易日的数据从网络请求开始经过清洗、指标计算、信号标注最终落到一张可以复盘和展示的图上全程可追溯、可调参。适合的人群很明确课程设计要交完整项目的学生、想系统入门量化分析的数据开发者以及需要把零散脚本工程化的从业者——你要的是骨架和边界不是玄学。先把这句话放前面这个方向值得做但值得的是「链路整合」这件事本身而不是某个神奇策略。下文按框架的三层结构拆开讲每层给可抄作业的代码再补参数和踩坑记录。2. 股票数据爬虫层先读懂请求再动手别一上来就上框架2.1 两条数据获取路线怎么选常见做法有两条路一条是直接构造 HTTP 请求去拿公开接口的数据另一条是用封装好的第三方数据源库调用函数返回 DataFrame。这两条路没有绝对好坏取舍点在于你要什么。直接用 requests 走公开接口优点是不依赖第三方库的版本变化缺点是接口格式、字段名、限频规则都要自己摸遇到网站改版就崩。用封装库则相反——数据字段统一、自带缓存和重试但库一旦停止维护整个爬虫层就得重写。我一般会建议项目初期直接用封装库把时间省给分析和可视化但代码里把数据获取的函数单独隔离成一个模块这样将来换数据源只改一个文件。这个决策本质上是把「会变的和不变的分离」。数据源会变分析逻辑和图表配置相对稳定爬虫层只负责一件事把一个股票代码 日期区间变成一份干净的 DataFrame字段固定为 date、open、high、low、close、volume 六列即可。2.2 最小可运行的抓取脚本下面是整个框架的入口函数示意的数据接口做了一次标准封装实际使用时把 URL 换成你对接的数据源地址即可字段名保持不变import requests import pandas as pd import time def fetch_daily(stock_code: str, start_date: str 20200101, end_date: str 20241231, retries: int 3, timeout: int 10) - pd.DataFrame: 抓取日K数据并返回标准化DataFrame。 stock_code: 股票代码如 600519 start_date / end_date: 起止日期格式 yyyyMMdd retries: 请求失败时的重试次数 timeout: 单次请求超时秒数 url https://example-data-endpoint/api/daily params { code: stock_code, start: start_date, end: end_date, adjust: qfq, # 前复权保证历史价格连续 } for attempt in range(retries): try: r requests.get(url, paramsparams, timeouttimeout) r.raise_for_status() data r.json() df pd.DataFrame(data[items]) df.columns [date, open, high, low, close, volume] df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) return df except Exception as e: if attempt retries - 1: raise RuntimeError(f抓取失败: {stock_code}, 原因: {e}) from e time.sleep(2 * (attempt 1)) # 递增等待2s、4s、6s return pd.DataFrame() # 不会走到这里仅为类型提示这段代码的核心思路是把「请求、解析、标准化」拆成三步。requests 负责拿原始内容pandas 把 JSON 转成表结构最后统一字段名和日期类型。参数里值得注意的有两个adjustqfq是前复权它影响的是后续所有技术指标计算这点在后面第 3 章展开说retries和timeout是给数据源的「礼貌性防御」数据源返回偶发超时太常见了没有重试机制等于把整条分析链路交给运气。2.3 增量更新与落盘把数据存成 SQLite股票数据没必要每次全量抓日期越积越多全量请求既慢又容易触发限频。正确做法是首次抓全量之后只抓本地缺失的日期段增量入库。import sqlite3 DB_PATH stock_data.db def ensure_database(): 初始化数据库建 daily_kline 表记录每个股票的已更新最新日期。 with sqlite3.connect(DB_PATH) as conn: conn.execute( CREATE TABLE IF NOT EXISTS daily_kline ( stock_code TEXT, date TEXT, open REAL, high REAL, low REAL, close REAL, volume INTEGER, PRIMARY KEY (stock_code, date) ) ) def update_stock(stock_code: str): 先查库里最新日期再增量抓取并写入。 with sqlite3.connect(DB_PATH) as conn: row conn.execute( SELECT MAX(date) FROM daily_kline WHERE stock_code ?, (stock_code,) ).fetchone() start row[0] if row[0] else 20200101 df fetch_daily(stock_code, start_datestart) if df.empty: return 0 with sqlite3.connect(DB_PATH) as conn: df[stock_code] stock_code df.to_sql(daily_kline, conn, if_existsappend, indexFalse) return len(df)数据库用(stock_code, date)做联合主键是防重复写入的关键。to_sql默认不会自动去重所以主键设计和增量取MAX(date)形成了双重保险即使重复执行主键冲突也会让新数据无法覆盖旧记录。这里有个容易忽略的点日期写入数据库前统一转成YYYY-MM-DD字符串比存datetime对象更稳妥也是为了让MAX(date)的字符串比较天然按字典序成立。3. 技术指标与信号分析把裸数据变成可执行的判断3.1 复权这件事不处理等于分析的是假数据拿到日K数据之后第一件要做的事不是算指标而是确认数据是不是复权后的。股票分红送转会导致除权跳空不复权的K线会出现一根大阴线MACD、RSI 这些指标都会在这根K线上给出错误信号。前复权是把历史价格按照最新价格倒推调整优点是当前价格和历史价格连续适合看近期走势后复权则相反更适合做长期收益率的计算。这个框架的爬虫层已经默认抓前复权数据但如果你用了别的数据源一定要在入库前检查某一天收盘价突然下跌超过 10% 且成交量异常放大但消息面没有利空大概率就是除权缺口此时应改为复权口径或剔除这段数据。动手检查的代码很简单# 检查是否含明显的除权跳空 df[daily_ret] df[close].pct_change() gap_threshold 0.15 suspect_days df.loc[abs(df[daily_ret]) gap_threshold, [date, close, daily_ret]]通常复权数据处理不当的常见结果是计算出来的均线系统在除权日附近突然转折使用者误以为出现了金叉或死叉信号。所以这步是分析层的第一个检查点宁可没有数据也不要拿错数据。3.2 指标计算向量化是性能关键别写循环技术指标在 pandas 里的正确写法是向量化运算即对整列做rolling、ewm等窗口操作而不是用 for 循环逐行算。同样的数据量循环写法慢几十倍代码也丑。下面是一个具体的现代化计算写法def add_indicators(df: pd.DataFrame) - pd.DataFrame: 为日K数据追加常用技术指标返回新DataFrame不修改原表。 df df.copy() # 均线rolling(窗口).mean() df[ma5] df[close].rolling(5).mean() df[ma20] df[close].rolling(20).mean() df[ma60] df[close].rolling(60).mean() # MACD: 12日与26日EMA差值DIF EMA12 - EMA26 df[ema12] df[close].ewm(span12, adjustFalse).mean() df[ema26] df[close].ewm(span26, adjustFalse).mean() df[dif] df[ema12] - df[ema26] df[dea] df[dif].ewm(span9, adjustFalse).mean() df[macd_bar] (df[dif] - df[dea]) * 2 # RSI(14): 用平均涨幅和平均跌幅的比值换算 delta df[close].diff() gain delta.clip(lower0) loss -delta.clip(upper0) avg_gain gain.rolling(14).mean() avg_loss loss.rolling(14).mean() rs avg_gain / avg_loss df[rsi14] 100 - (100 / (1 rs)) # 剔除指标计算期的 NaN 空值 df df.iloc[60:].reset_index(dropTrue) return dfewm(adjustFalse)是MACD标准算法要求adjustTrue默认值会改变权重导致指标曲线明显失真这是最容易翻车的一行。rolling(14).mean()的窗口大小决定RSI的灵敏度和噪音的取舍短线看盘常用的周期是 6 和 24中线就用 14。每个指标的可调窗口其实是很主观的经验值框架里把它们集中放在一个指标参数配置的位置避免改参数时到处挖代码。最后iloc[60:]丢弃前 60 个无效值是有意为之——前 20 日均线还没走完前 26 日 EMA 还没稳定与其留着 NaN 不如直接截断但如果你只做单股展示也可以保留后自行填充。3.3 让信号可验证趋势、超买超卖与金叉死叉有了指标下一步就是定义信号并把这些信号写成一个可复用的判定函数。常见做法是给每一行打上「趋势标签 交易信号」两个字段def add_signals(df: pd.DataFrame) - pd.DataFrame: 基于均线与RSI生成信号标签。 df df.copy() # 趋势判定收盘价在MA20上方为多头趋势 df[trend] df[close] df[ma20] # RSI超买卖区 df[overbought] df[rsi14] 80 df[oversold] df[rsi14] 20 # 金叉死叉今日MA5上穿MA20 golden_cross (df[ma5] df[ma20]) (df[ma5].shift(1) df[ma20].shift(1)) dead_cross (df[ma5] df[ma20]) (df[ma5].shift(1) df[ma20].shift(1)) df[golden_cross] golden_cross df[dead_cross] dead_cross return df信号逻辑的定义方式直接决定了后续可视化和回测怎么写。这里的shift(1)是拿昨日数据做比较搞清楚「今日上穿」是golden_cross内一行代码所依赖的写回测和实盘模拟时必须保持一致否则会出现用当日收盘后信号在当日开盘价成交的「未来函数」类问题影响很大但极隐蔽。4. 可视化呈现把计算结果画成能交付的图4.1 K线 均线 MACD 三联图分析结果最终要落到图上标准画法是上方主图画K线实体和均线下方副图画成交量或MACD柱。用 matplotlib 手动画K线会让新手抓狂——蜡烛实体、上下影线、红涨绿跌都要自己摆矩形。实际做项目时建议借现成的 K 线绘制封装库或者自己写一个极简的画蜡烛函数重点放在布局和配色上。这里给出一段可以用在框架里的主图代码自行补齐坐标轴刻度import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False def plot_kline_with_ma(df: pd.DataFrame, stock_code: str): 绘制收盘价与均线直观展示趋势与金叉死叉位置。 fig, ax plt.subplots(figsize(14, 7)) ax.plot(df[date], df[close], labelClose, color#333333, linewidth1.5, alpha0.9) ax.plot(df[date], df[ma5], labelMA5, linewidth1.0, color#e67e22) ax.plot(df[date], df[ma20], labelMA20, linewidth1.0, color#2980b9) ax.plot(df[date], df[ma60], labelMA60, linewidth1.0, color#8e44ad) # 用散点标出金叉位置 cross_dates df.loc[df[golden_cross], date] cross_prices df.loc[df[golden_cross], close] ax.scatter(cross_dates, cross_prices, marker^, colorred, s80, zorder5, labelGolden Cross) ax.set_title(f{stock_code} 日K与均线系统) ax.legend(locupper left) ax.grid(True, alpha0.3) ax.set_xlabel(日期) ax.set_ylabel(价格) plt.tight_layout() plt.show()这版本用折线替代了传统K线实体好处是绘图代码量小均线和信号点的叠加关系一目了然适合放在项目文档里。如果你想拿真蜡烛图画封面图可以再封装一个plot_candlestick函数用ax.bar画实体、用ax.vlines画影线逻辑不复杂。4.2 多标的对比用交互式图表拉近「横向对比」的距离单只股票的图只能回答「这只个股怎么样」数据分析项目里更常问的问题是「这几只票哪个更值得关注」。多标的对比用静态图会显得拥挤常见的做法是换用 Plotly 这类交互式绘图库让浏览者自行缩放、悬停查看数值。import plotly.graph_objects as go def plot_normalized_price(stocks: dict): 把多只股票收盘价归一化到同一起点后绘制对比曲线。 fig go.Figure() for name, df in stocks.items(): df df.sort_values(date).reset_index(dropTrue) norm df[close] / df[close].iloc[0] * 100 # 起点统一为100 fig.add_trace(go.Scatter( xdf[date], ynorm, modelines, namename, linedict(width1.5) )) fig.update_layout( title多标的走势归一化对比起点100, xaxis_title日期, yaxis_title归一化价格, templateplotly_white, hovermodex unified, height500 ) fig.show()归一化是横向对比的关键一步让不同价格量级的股票放在同一张图上可读。hovermodex unified解决多线悬停时数据匹配问题——鼠标在一处同一天的几只股票数值全部显示这是高频使用需要搭配的选项。另外Plotly 的图默认是独立 HTML 渲染课程设计答辩时可以直接演示交接而不必截静态图这是加分项。4.3 图表「专业性」靠的是什么不少项目死磕配色和排版其实专业性往往由几个最容易被忽略的点撑起来一是轴范围是否出错pandas 默认索引和日期混用会导致 x 轴间距失真二是是否标注了数据截止日期有一行fig.text(...)在图的角落标明「数据更新至2024-12-01」会让图表立刻变得可信三是红涨绿跌还是红跌绿涨国内行情软件习惯红涨绿跌如果你的图反着画懂行的人一眼就看穿。口径统一的意思就是全网和A股习惯的颜色方向保持一致除非你明确标注了反向。做可视化不是追求艺术是追求在最短时间内让看图者获取信息且不被误导。5. 常见问题与避坑跑通之后一定踩过的五个坑5.1 数据源限频报错 403程序中断现象连续抓了几十只股票后某一次请求直接返回 403之后所有请求都失败整个框架卡死。原因公开数据源对单 IP 有频率限制但你前期请求太快没触发到达阈值后直接把会话踢下线而代码里只做了单次请求的重试没有在全局排队。解决在爬虫层增加一个全局请求间隔最小限定值比如每两次请求之间time.sleep(0.3)并且遇到 403 时先停 60 秒再做后续请求。另外最好同时记录本次会话成功与失败的次数失败超 5 次就整体熔断避免在限频解除前疯狂重试。5.2 日期字符串排序看似是日期其实是文本现象表格看起来日期是乱的2020-10-09 排在 2020-9-30 前面均线图出现锯齿回退。原因数据源返回的日期是20201009这种字符串你用sort_values(date)排序时它按字符串逐字符比较前缀缺0时就会乱序。这类问题隐蔽因为多数时候你只看头部几行看不出异常。解决在标准化层统一先pd.to_datetime再排序并把排序列重新设为索引。如果你是从多个数据源合并数据更要多加一步统一日期到YYYY-MM-DD格式否则一旦混入时间戳类型后续merge就极易出问题。相关排查建议在每次入库前跑一次df[date].is_monotonic_increasing断言。5.3 均线在除权日假死叉数据口径不一致现象某只股票的 MA5 在除权日突然下穿 MA20形成了明显的死叉但查看消息面并无利空后续趋势也没走坏。原因股票除权产生了价格跳口而你的数据源返回的是不复权数据均线是基于「断崖式下跌」的价格线计算的假信号由此产生前文爬虫层若没有指定复权参数就会出现该问题。解决回到爬虫层把参数调整为前复权同时删除本地未复权的历史文件重新抓取。这个坑的教训是指标的可靠性上限不取决于指标算法多精确而取决于数据前处理多干净。5.4 信号标签前后不一致shift 的方向用反现象金叉信号标在死叉之后或者信号点与图上可见的交叉位置明显错位。原因shift(1)方向写反了。df[ma5] df[ma20] (df[ma5].shift(1) df[ma20].shift(1))会把「连续上方」标记为金叉而真正交叉点应该是昨日均线未交叉、今日才交叉方向反了结果完全相反。解决写信号逻辑时先在测试集上人工抽查最近 10 个信号点对照df[[date, close, ma5, ma20]].tail(15)肉眼核对。这套「抽查最近N个信号」的习惯我在所有项目里都保留它是信号正确性最廉价的守门员。5.5 中文字体方块绘图正常但没有中文现象图表标题、图例全部是空方块口字形其他一切正常答辩时非常尴尬。原因matplotlib 默认字体不含中文字符需要在每次绘图前指定系统已安装的中文字体SimHei或Noto Sans CJK SC都可用但不同系统默认字体集合差别很大。解决在单独的 config 文件里统一设置字体再配合axes.unicode_minusFalse让负号正常显示。然后写一个冒烟测试函数生成一张含中文标题的空图若抛字体警告则提前暴露环境问题不要等到生成正式图表才发现。6. 让框架每天自动跑一遍定时任务、增量更新与结果自检框架的价值发挥依赖持续运行。把抓取、指标计算和出图过程串联成一个定时任务每天收盘后自动跑一遍是检查框架健壮性的最简单路径也是让它成为「活数据」的关键一步。# 保存为 run_daily.py作为每日入口 import sqlite3 import pandas as pd import logging logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) WATCH_LIST [600519, 000001, 300750] # 自行替换为目标代码列表 def run_daily(): for code in WATCH_LIST: try: added update_stock(code) logging.info(f{code}: 新增 {added} 条) except Exception as e: logging.exception(f{code}: 抓取失败) continue for code in WATCH_LIST: with sqlite3.connect(DB_PATH) as conn: df pd.read_sql_query( SELECT * FROM daily_kline WHERE stock_code ? ORDER BY date, conn, params(code,) ) df add_indicators(df) df add_signals(df) # 校验最后一行的趋势与信号是否存在 latest df.iloc[-1] assert latest[trend] is not None, 信号缺失 if latest[golden_cross]: logging.warning(f{code}: 出现金叉信号) if __name__ __main__: run_daily()定时任务在 Linux 下写成 cron 表达式周一到周五收盘后一小时执行0 18 * * 1-5 cd /path/to/project \ /usr/bin/python3 run_daily.py logs/daily.log 21日志归档是运行稳定性观察的抓手。每天翻daily.log的三行信息——新增条数、是否异常、是否触发信号——简直是这套框架运行健康的仪表盘。写日志时注意保留时间戳因为数据源偶发延迟会导致增量区间重叠日志能帮你回推某一天的数据缺口到底是网络还是逻辑问题。这套框架改造到现在我觉得最大的收获不是「预测得准」而是形成了一种纪律所有结论都有数据来源、有计算过程、有图可查。所谓的高分项目代码本质上是这套纪律的产物。每次拿图答辩之前我都会反复确认数据截止日期标注清晰、指标口径说明完整细节比想象中更能说服人。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?