简介:面向计算机相关专业毕业设计、课程设计与期末大作业场景,这套Python股票数据爬虫+分析+可视化框架提供完整可运行的项目代码,聚焦股票行情数据采集、清洗分析与前端可视化展示,适合正在做毕设或需要实战练手的学习者快速上手。压缩包共11个文件,以5个Python脚本为核心,覆盖爬虫采集、数据处理与可视化主程序,另含HTML模板、JavaScript图表库、文本说明和Markdown文档,分别支撑页面展示、图表渲染、依赖说明与项目介绍,整包仅260KB。目前已有282人学习下载,代码经过严格调试,确保可运行。框架内各模块职责清晰,从行情接口抓取数据、清洗整理到生成交互式可视化图表,形成完整技术链路;附带项目说明与目录结构整理,便于直接作为毕业设计系统使用,也能帮助读者理解股票数据爬取、分析到可视化展示的完整流程,具有较高的参考与复用价值。
1. 从爬虫到图表:这套股票数据框架解决了什么
先说结论:这套项目不是那种只有爬虫、抓完就扔的半成品,而是一条「数据采集 → 指标分析 → 页面可视化」的完整链路。你运行main.py之后,本地会拉起 Web 服务,能直接看到股票行情对应的 K 线、均线和成交量的交互图表。对于做 Python 毕业设计、期末大作业的人来说,它最值钱的地方在于——每个环节都有落地的 Python 代码,不是 PPT 式的功能罗列。
框架里data_scrapy目录负责抓取与清洗,data_viewer目录负责读取处理后数据并交给模板渲染,utils/myDict.py这类模块则承担路径、请求头、字段映射这类横向逻辑。底层用 requests 做网络请求,pandas 处理行情表,前端通过本地引入的echarts.min.js画图。对五年以上经验的工程师,这套结构也有可拆的地方:爬虫层与展示层解耦、配置外置、依赖收敛在requirements.txt,稍作扩展就能改成多标的轮询或数据库落盘。这篇文章我会把抓包逻辑、字段清洗、指标计算和视图中枢串起来讲,最后给出答辩和调试相关的实测经验。
2. 行情爬虫模块:从证券列表到历史K线的抓取设计
2.1 项目里爬虫模块的分工
打开压缩包后能看到两个核心爬虫文件:stock_basic_scrapy.py和history_stock_quotes_scrapy.py。前者负责拉股票基础信息(代码、名称、所属板块、总市值、流通市值、上市日期),后者负责按股票代码抓历史行情(开高低收、成交量、成交额)。这种拆分在真实业务里也很常见——基础数据更新频率低,适合全量刷新;行情数据更新频率高,适合按代码增量抓取。
两个爬虫共享utils/myDict.py里的配置。点开这个文件,你会看到请求头、超时时间、需要抓取的股票代码列表、本地缓存路径。很多初学者会把这些值硬编码在爬虫文件里,这套项目把它们独立出来,职责是清晰的。实际改的时候,只需要把股票池替换成自己关注的代码,就能跑通一个新场景。
2.1.1 基础信息爬虫的抓包思路
stock_basic_scrapy.py走的是公开行情接口,不需要登录。它先向证券列表接口发起 GET 请求,拿到 JSON 后解析里面的总记录数和分页信息,再按页数循环拼接 URL。代码结构大致如下:
import requests import json import pandas as pd def fetch_stock_list(page_size=100): base_url = "https://你的目标行情源/api/stock_list" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", } all_data = [] for page in range(1, 6): params = { "page": page, "size": page_size, "fields": "code,name,industry,total_mv,float_mv,list_date" } resp = requests.get(base_url, headers=headers, params=params, timeout=10) data = resp.json() records = data.get("data", {}).get("list", []) all_data.extend(records) return pd.DataFrame(all_data) if __name__ == "__main__": df = fetch_stock_list() df.to_csv("data_scrapy/stock_basic.csv", index=False, encoding="utf-8-sig")逻辑说明:这里用params而不是手动拼接字符串,会让 URL 编码更安全;utf-8-sig是为了让 Excel 打开 CSV 时不出现中文乱码。page循环次数是写死的,实际你可以根据返回的total字段动态计算总页数后再循环,这个项目里用固定值是为了降低答辩时的解释成本。
2.2 历史K线接口的参数设计与断点续抓
history_stock_quotes_scrapy.py是整条链路里最值得细看的文件。它接收股票代码、开始日期、结束日期三个参数,向行情数据接口请求日 K 线,然后把返回的列表解析成 DataFrame。请求参数里必须带股票代码和周期字段,部分接口还会校验adjust参数——qfq表示前复权,hfq表示后复权,""表示不复权。做技术指标计算时建议用前复权数据,否则除权除息会让均线出现假跳变。
抓取过程中还做了容错处理。网络超时或接口熔断时会重试,失败达到上限就跳过当前代码继续跑,最后把成功抓取的部分写入本地文件:
import time import random import pandas as pd import requests def fetch_history(code, start_date="20200101", end_date="20241231"): url = "https://你的目标行情源/api/kline" headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"} all_rows = [] for retry in range(3): try: params = { "code": code, "period": "daily", "start": start_date, "end": end_date, "adjust": "qfq", } resp = requests.get(url, params=params, headers=headers, timeout=15) if resp.status_code != 200: raise ValueError(f"HTTP {resp.status_code}") quotes = resp.json().get("data", {}).get("klines", []) for row in quotes: all_rows.append(row.split(",")) break except Exception as e: print(f"[{code}] 第{retry+1}次请求失败: {e}") time.sleep(2 * (retry + 1)) if all_rows: df = pd.DataFrame(all_rows, columns=["date", "open", "close", "high", "low", "volume", "amount"]) df.to_csv(f"data_scrapy/history_{code}.csv", index=False) return len(all_rows)参数说明:start和end的格式是YYYYMMDD,实际接口也可能接受YYYY-MM-DD,具体要看接口文档;retry循环里sleep(2 * (retry + 1))是常见的指数退避写法,第一次失败等 2 秒、第二次等 4 秒,比固定 sleep 更不容易被对方限流。抓完一个代码后,代码里还会加一句time.sleep(random.uniform(1, 3)),这个随机延时是爬虫工程里的常规操作,作用是防止请求频率太规律而触发反爬规则。
2.3 反爬应对与数据完整性校验
这套项目的爬虫定位是「面向学习环境的轻量抓取」,没有上代理池和浏览器指纹伪装。但它的请求头、延时、重试逻辑对入门级反爬已经够用。你如果要在真实环境复用,有几个地方需要按你的目标站点调整:
- 请求头要带完整的
User-Agent、Referer、Accept-Language,有的接口还会校验Cookie; - 股票池大时建议分批次跑,每批之间
sleep(30)以上; - 抓回来的数据要做空值检查,重点看成交量
volume和收盘价close是否有None、''、NaN这三种情况。
我一般会加一条完整性统计:对比接口返回的 K 线数量与交易日历天数,如果差太多,说明中间有部分数据被限流丢弃,需要重新跑这一段日期。这个思路放在答辩里,讲出来就是「数据质量校验模块」,比单纯说「我抓了数据」要专业得多。
3. 数据清洗与指标分析:Pandas 计算均线与回撤
3.1 为什么抓完不能直接画图
很多人拿到历史行情就急着渲染到前端,结果画出来的 K 线图缺口很多。原因是接口返回的数据不一定按时间正序排列,偶尔还会重复推送当天数据,而且部分日期的成交量可能是0(停牌或数据源缺失)。所以项目里对原始 CSV 做了二次处理,这个逻辑分散在utils和data_viewer的读取函数里。
清洗步骤可以归纳为三件事:第一,把date列转成datetime类型并设为索引;第二,按日期去重并排序;第三,把字符串型的数字用pd.to_numeric转成浮点数,必要时填充停牌日的空值。整理后得到一张标准的 DataFrame,列名统一为:date, open, close, high, low, volume, amount。
3.2 技术指标计算:均线、收益率与最大回撤
data_viewer/rightview.py里的主要工作是把清洗后的行情数据加工成前端需要的指标序列。最基础的是MA均线,通常做MA5、MA20、MA60三条。用 pandas 计算非常直接:
import pandas as pd # df 是已清洗的日线数据,按日期升序 df["ma5"] = df["close"].rolling(window=5).mean() df["ma20"] = df["close"].rolling(window=20).mean() df["ma60"] = df["close"].rolling(window=60).mean() # 日收益率 df["daily_return"] = df["close"].pct_change() # 累计收益率 df["cum_return"] = (1 + df["daily_return"]).cumprod() - 1 # 最大回撤 = 当日累计净值 / 历史累计净值峰值 - 1 df["cum_max"] = df["cum_return"].cummax() df["drawdown"] = df["cum_return"] / df["cum_max"] - 1 max_drawdown = df["drawdown"].min()逻辑说明:rolling(window=5).mean()表示最近 5 个交易日收盘价的滚动均值,常用于短线趋势判断;pct_change()是当日对比前一交易日的涨跌幅,注意这里的分母是前一日收盘价;cummax()用来追踪累计收益的历史最高点,最大回撤就是当前收益距离最高点跌了多少,这是衡量风险的核心指标。
指标参数这里,不同策略场景可以调整:短线交易看MA5和MA20的金叉死叉;中长线看MA60与MA120;最大回撤一般控制在-20%以内算稳健。答辩时如果被问「为什么用这些指标」,你可以说:均线是动量类指标的基础,回撤能反映策略在极端行情下的抗风险能力,比单纯看收益率更全面。
3.3 分析结果的输出形式
项目里没有把分析结果落数据库,而是直接生成一个大的 JSON,传给前端模板。这个设计对课程设计和毕业设计很友好:不需要 MySQL 环境,部署时省去建表操作。rightview.py里会构造一个类似下面的结构:
result = { "code": stock_code, "name": stock_name, "dates": df["date"].astype(str).tolist(), "kline": df[["open", "close", "low", "high"]].values.tolist(), "volumes": df["volume"].tolist(), "ma5": df["ma5"].round(2).tolist(), "ma20": df["ma20"].round(2).tolist(), "ma60": df["ma60"].round(2).tolist(), "max_drawdown": round(float(max_drawdown), 4), }这样前端拿到的dates、kline、volumes长度一致,方便直接映射到 ECharts 的xAxis和series上。有一点要注意:df["date"].astype(str)会把日期格式化为2024-03-15,ECharts 的type: 'candlestick'对这种字符串日期解析没有问题;如果直接用datetime对象,JSON 序列化会报错。
4. Web 可视化层:Flask 路由与 ECharts 数据联动
4.1 main.py 作为聚合入口
整个项目启动后先跑main.py。它做的事情是:初始化 Flask 应用,注册蓝图,将data_viewer里的视图函数挂载到路由上,最后app.run()监听本地端口。这样做的好处是,爬虫和分析逻辑可以独立测试,Web 服务只是作为展示出口,不要和爬虫代码耦合在一起。
看代码时会发现main.py里没有特别多复杂的逻辑,核心是把rightview模块导入并暴露一个/stock/<code>动态路由。这个设计在真实工程里对应的是「应用入口只做装配,不做业务」,评审老师问到项目分层时你也能讲得出依据。
4.1.1 视图函数的响应流程
rightview.py里的核心视图函数接收股票代码后,先读取本地清洗好的行情数据,然后计算指标,最后把 JSON 数据交给模板渲染:
from flask import render_template, jsonify import pandas as pd def get_stock_data(code): df = pd.read_csv(f"data_scrapy/history_{code}.csv", parse_dates=["date"]) df = df.sort_values("date").drop_duplicates(subset="date") df["ma5"] = df["close"].rolling(5).mean() df["ma20"] = df["close"].rolling(20).mean() df["ma60"] = df["close"].rolling(60).mean() return df @app.route("/stock/<code>") def stock_view(code): df = get_stock_data(code) chart_data = build_chart_json(df) # 组装 ECharts 需要的 dict return render_template("template.html", data=chart_data)逻辑说明:parse_dates=["date"]让 pandas 在读取时直接完成日期转换,省掉后面一步to_datetime;drop_duplicates(subset="date")保留第一次出现的行,重复推送的数据行会被移除。build_chart_json的作用是把已经计算好的均线序列放进返回字典,避免在模板里写复杂的 Python 逻辑。
4.2 template.html 与 echarts.min.js 的配合
这个项目的可视化完全依赖template.html,它本地引用了echarts.min.js,所以你断网也能跑。模板里接收后端传进来的data,然后在前端初始化图表容器。常见写法是在<script>里用JSON.parse接收后端变量,但更稳妥的做法是让 Flask 用| tojson过滤器直接把 Python 字典转换为 JSON 字符串。
K 线图和成交量图通常放在同一个grid容器里,用dataZoom实现联动缩放:
var chart = echarts.init(document.getElementById("chart")); var raw = {{ data | tojson }}; var option = { tooltip: { trigger: "axis" }, legend: { data: ["MA5", "MA20", "MA60"] }, grid: [ { left: 60, right: 20, top: 30, height: "55%" }, { left: 60, right: 20, top: "72%", height: "20%" } ], xAxis: [ { type: "category", data: raw.dates, gridIndex: 0 }, { type: "category", data: raw.dates, gridIndex: 1 } ], yAxis: [ { scale: true, gridIndex: 0 }, { gridIndex: 1 } ], dataZoom: [ { type: "inside", xAxisIndex: [0, 1], start: 50, end: 100 }, { type: "slider", xAxisIndex: [0, 1], top: "95%" } ], series: [ { name: "K线", type: "candlestick", data: raw.kline, itemStyle: { color: "#ef232a", color0: "#14b143", borderColor: "#ef232a", borderColor0: "#14b143" } }, { name: "MA5", type: "line", data: raw.ma5, smooth: true, lineStyle: { width: 1 } }, { name: "MA20", type: "line", data: raw.ma20, smooth: true, lineStyle: { width: 1 } }, { name: "成交量", type: "bar", xAxisIndex: 1, yAxisIndex: 1, data: raw.volumes } ] }; chart.setOption(option);参数说明:candlestick系列的data每一项必须是[open, close, low, high]的顺序,这正是第 3 节里df[["open", "close", "low", "high"]]的列顺序,顺序反了图形会乱;color对应阳线颜色,color0对应阴线颜色,红涨绿跌是国内行情软件的习惯;dataZoom里xAxisIndex: [0, 1]表示上下两个图共享缩放,start: 50表示默认只显示后 50% 的数据,避免刚打开页面时图形被压缩得看不清。
4.3 数据量较大时的渲染策略
如果股票超过 3 年日线,K 线数据大约有 700 到 800 条,ECharts 一次性渲染没有压力。但如果你是抓分钟线,数据量会到几万条,前端会卡顿。这个项目在模板中已经预设了缩放窗口,你可以把start: 50, end: 100改为start: 80, end: 100,让默认视角只看最近 20% 区间。另外,ECharts 的sampling: 'lttb'可以对折线图数据进行降采样,但对 K 线图不适用,所以不建议对candlestick做采样,保持原始数据才是正确的展示方式。
5. 项目落地:运行排错、参数调优与毕设文档要点
5.1 依赖安装与目录结构核对
拿到压缩包解压后,先看根目录的requirements.txt,里面锁定了项目运行所需的 Python 库。一般包含requests、pandas、flask。安装命令是:
pip install -r requirements.txt如果你用的是 Anaconda 环境,建议先建一个独立环境再装,避免和已有包版本冲突。装完后核对目录结构是否能对上:
├── data_scrapy/ │ ├── stock_basic_scrapy.py │ ├── history_stock_quotes_scrapy.py │ └── utils/myDict.py ├── data_viewer/ │ ├── rightview.py │ └── template.html ├── main.py └── requirements.txtmain.py必须放在项目根目录,因为它里面会用相对路径引用data_viewer和data_scrapy。如果你把main.py单独移出来运行,会报ModuleNotFoundError,这不是代码问题,是启动路径问题。
5.2 常见运行报错与排查方式
第一个高频报错是ModuleNotFoundError: No module named 'utils'。原因是直接运行了data_scrapy目录下的爬虫文件,而utils是根目录下的包,需要先cd到项目根目录再执行python -m data_scrapy.stock_basic_scrapy。第二个高频问题是 CSV 文件中文乱码,这个项目写入时用了utf-8-sig,所以一般不会出现;如果自己改代码时需要统一编码,读取时也要带上encoding="utf-8"。
| 错误现象 | 排查方向 |
|---|---|
| 爬虫请求无响应 | 检查目标接口是否要求Cookie,请求头的User-Agent是否被识别为 Python 默认值 |
| 图表不显示 | 打开浏览器控制台看echarts.min.js是否能加载,确认data变量已经注入模板 |
| 日期排序错乱 | 确认读取 CSV 时用了parse_dates,另外检查是否有重复行影响排序 |
main.py启动失败 | 看端口是否被占用,app.run(port=5000)可以改用5001等端口 |
5.3 答辩与课程设计文档的写作建议
这套项目拿高分的关键不只是能跑,而是文档里能「把技术点讲清楚」。建议围绕三条线组织说明:
第一,爬虫设计写清楚采集对象、接口参数、反爬策略、数据校验方式;不要写「我用了爬虫」,而是写「本项目通过轮询分页接口,结合随机延时与指数退避重试机制,实现单标的每日 800 条历史 K 线的高可用抓取」。第二,指标分析部分说明均线、收益率、最大回撤这三个指标的计算公式和业务含义,最好加一份本地导出的结果对比截图。第三,可视化部分解释为什么选 ECharts 而不是 Matplotlib——交互式缩放、K 线组件成熟、支持数据集联动,这三点是 ECharts 的优势。
如果你想把项目扩展成更好的毕设版本,最简单有效的改动是把 CSV 落盘换成 SQLite 存储,加一个scheduler定时任务,让爬虫每天收盘后自动更新数据。这不会破坏原有框架,反而能把程序从「人工触发」升级为「自动运维」,在答辩时是很好的加分项。
最后提供一个调试技巧:Flask 默认的debug=True能输出完整异常堆栈,开发时在你改动过的路由函数上方加@app.after_request打印响应耗时,能快速定位是读取 CSV 慢还是模板渲染慢。单次 K 线请求响应时间应该控制在 100 毫秒内,如果明显偏慢,优先检查是不是每次视图调用都重新读全量 CSV,考虑设置一个全局缓存字典来控制过期时间。
本文还有配套的精品资源,点击获取