☰
Flask股票数据采集分析可视化系统全解析:从爬虫到图表
2026/10/22 3:06:34 网站建设 项目流程

简介:一份基于Python Flask的股票数据采集分析与可视化系统毕业设计资源包,适合计算机专业学生完成毕设或深入学习爬虫与金融数据分析的项目实践。资源共95个文件,主要为jpg图片、js/css前端文件、html页面、xml配置文件、txt说明文档等,还包含演示视频、数据库文件与源码目录,整体打包为zip压缩包,大小约25.14MB。已有136人学习下载。资源内附项目说明与演示视频,源代码按模板、静态资源、应用模块等组织,并包含依赖清单与配置信息,可帮助读者快速理解系统架构、运行环境与数据流程。通过对股票数据爬取、清洗、分析和图表展示的学习,可掌握Flask后端开发、网络爬虫解析以及数据可视化在金融场景中的综合运用。这套资源适合需要完整项目参考、快速搭建同类系统并进行答辩讲解的毕业生。

1. 一份能跑通的毕业设计源码:Flask股票数据采集分析可视化系统的起点

股票数据的采集和分析,是所有金融类毕业设计里最常见的切入点,也是最能拉开工作量差距的一条线。很多同学拿到一份以 Python + 爬虫 + 金融数据分析为主线的系统源码时,第一反应往往是先开个编辑器看看代码能不能跑通,实际上更该先搞清楚它的技术边界:这份资源到底覆盖了从数据抓取、清洗、指标计算到可视化展示的哪几段链路,以及它在演示态和真实生产环境之间隔了什么。适合用它做参考的人群,主要是有毕业设计任务但还没定技术方案的学生,以及想快速搭一个数据可视化 Demo 的开发者。系统采用 Flask 作为后端框架,用爬虫技术从模拟接口采集股票数据,再通过分析模块计算均值、涨跌幅、波动率等基础指标,最终用 matplotlib 生成图表并嵌入网页展示,整条链路完整、可解释、可直接照着改。

2. 系统骨架拆解:Flask 路由、模块划分和数据文件结构

一套看起来“能跑”的 Flask 系统,拆开看无非是几个固定部件的组合:应用入口、路由、模板、静态资源、数据清洗与采集模块。毕业设计答辩时最容易被追问的就是“项目里每个目录干什么用”“请求从 URL 到页面经过了什么”。这一章把骨架拆透,后面所有代码调试、功能扩展、Bug 排查都会轻松很多。

2.1 项目文件结构:code、templates、static、app 这四块各自管什么

拿到压缩包解压后,除了几个说明文档和视频,真正要关心的就两个方向:code 目录下的源码,以及说明文档里的运行指南。先看 code 目录,典型结构如下:

code/ ├── app/ # Flask 核心应用代码 │ ├── __init__.py # 应用工厂,创建 Flask 实例 │ ├── routes.py # 路由与视图函数 │ ├── spider.py # 爬虫采集模块 │ ├── analysis.py # 数据分析模块 │ └── models.py # 数据模型(基于 pyc 缓存可判断 Python 3.7) ├── templates/ # Jinja2 模板,对应各页面的 HTML ├── static/ # 静态资源,存放生成的图表和 CSS ├── requirements.txt # 依赖清单 └── app.py # 本地开发启动脚本

这里有一个细节值得强调:code 根目录下能看到models.cpython-37.pyc和__pycache__,说明原作者的环境是 Python 3.7 或相近版本。Python 3.9 以上环境直接运行这套代码,最常见的翻车点就是第三方依赖库版本不兼容,或者 pyc 缓存文件残留导致 import 报错。我一般拿到资源后会先删除所有__pycache__和.pyc文件,再按 requirements.txt 重新安装依赖,避免环境差异造成的干扰。

从职责划分看,templates 文件夹放页面模板,static 文件夹放可视化生成的图表、CSS 和 JS,app 文件夹是所有业务逻辑所在地。requirements.txt是环境恢复的唯一依据,如果它本身的版本号写得偏旧,运行前就要手动升级几个核心库。

2.2 Flask 路由与模板:请求是怎么从 URL 走到页面再到图表

Flask 的核心机制是路由绑定视图函数。用户访问某个 URL,Flask 根据装饰器里注册的路径找到对应函数,函数处理完业务数据后,调用render_template把数据传给模板渲染,最终把 HTML 响应给浏览器。这套代码里最典型的链路是用户访问分析页 → 触发爬虫采集 → 调用分析模块计算指标 → 生成图表到 static → 前端模板展示图片。

# app/routes.py 节选 from flask import render_template, request from .spider import fetch_realtime_data from .analysis import calculate_indicators from .visualization import generate_charts @app.route('/analyze', methods=['GET', 'POST']) def analyze(): symbol = request.args.get('symbol', '600000') raw_data = fetch_realtime_data(symbol) # 1. 采集原始数据 result_df = calculate_indicators(raw_data) # 2. 计算指标 chart_path = generate_charts(result_df) # 3. 生成图表 return render_template('analyze.html', tables=result_df.to_html(), chart_url=chart_path)

这段代码把整个请求生命周期串起来了。这里的symbol参数是股票代码,默认值 600000 只是一个示例,不代表真实标的。调用顺序很关键:采集、分析、可视化三个阶段严格串行,任何一个环节抛异常都会导致页面 500。对演示项目来说这样写最简单直观,但生产环境一般会在采集前加缓存判断,避免每次刷新页面都重新抓取数据。

对于初学者,我建议把 routes.py 里的每个视图函数打印上print(f'当前路由: {request.path}'),跑一次服务后打开浏览器分别访问首页、分析页、数据列表页,终端里就能看到完整的路由映射关系,面试或答辩时讲“请求如何处理”会更有底气。模板目录的变量名要和render_template传参保持一致,否则 Jinja2 会渲染成空白或抛UndefinedError。

3. 爬虫数据采集:从网页到 DataFrame 的抓取、清洗与容错

爬虫是整个系统的数据源头,也是最容易翻车的一段。毕业设计里数据源选得好不好,直接决定演示时是真实数据还是空列表。多数演示项目不会直接对接实时行情接口,而是用一个模拟数据源或带延迟的公开接口来逼近真实场景。

3.1 爬虫的技术选型:requests + BeautifulSoup 为什么比 Scrapy 更合适

很多同学一提起爬虫就想到 Scrapy,但在这个系统里,轻量实现才是主线。Scrapy 的优势在于高并发、分布式、中间件体系,适合生产级大规模抓取;而毕业设计场景的数据量通常只有几十条到几百条股票记录,用 requests 发起请求、BeautifulSoup 解析 HTML、正则或 CSS 选择器提取字段,三步就能完成,且调试成本低得多。

# app/spider.py 核心抓取模块(节选,数据源为示例) import requests from bs4 import BeautifulSoup import time def fetch_stock_list(page=1): url = 'https://example.com/api/stock/list' params = {'page': page, 'page_size': 50} headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', 'Accept': 'application/json' } resp = requests.get(url, params=params, headers=headers, timeout=10) if resp.status_code != 200: raise RuntimeError(f'采集失败,状态码: {resp.status_code}') data = resp.json() stock_list = [] for item in data['data']['list']: row = { 'code': item['code'], 'name': item['name'], 'price': float(item['price']), 'volume': int(item['volume']), 'time': item['timestamp'] } stock_list.append(row) delay = min(page * 0.5, 3) # 每页递增延迟,避免请求过快 time.sleep(delay) return stock_list

这段代码里有一个核心参数值得注意:timeout=10,即单次请求最长等待 10 秒。演示环境里经常出现网络抖动,把 timeout 设太长会让页面卡死,设太短又容易误判失败。另外delay参数是模拟限速策略,虽然是演示项目,但写一个礼貌的延时能避免被数据源快速封禁。如果接口返回的 price 字段带逗号分隔符,比如 “12,345.67”,直接 float() 会直接抛 ValueError,常见做法是先字符串替换再转换。

3.2 数据清洗:缺失值、空列表和类型转换的防御式处理

采集到的原始数据不能直接进分析模块。网页 HTML 解析出来的字段经常是字符串,有些还带单位符号,必须在进入 DataFrame 之前做清洗。下面是清洗模块的标准写法:

# app/analysis.py 数据清洗部分 import pandas as pd def clean_stock_data(raw_list): df = pd.DataFrame(raw_list) if df.empty: return df # 统一列名,方便后续计算 df.columns = ['code', 'name', 'price', 'volume', 'timestamp'] # 价格字段去逗号后转浮点;成交量转为 int df['price'] = df['price'].astype(str).str.replace(',', '').astype(float) df['volume'] = pd.to_numeric(df['volume'], errors='coerce').fillna(0).astype(int) # 时间字段统一为字符串并按格式解析 df['timestamp'] = pd.to_datetime(df['timestamp'], errors='coerce') # 删除价格为 0 或 NaN 的脏记录 df = df[df['price'] > 0] return df

这套清洗逻辑是对采集模块的兜底。errors='coerce'的作用是把无法转换的非法值替换成 NaN,然后再用fillna(0)补零,这样脏数据不会直接中断整个链路。清洗数据是分析结果可信度的地基:如果这一步没做干净,后面计算平均价格、波动率时结果全部失真,答辩时一旦被问到“数据质量怎么保证”,很容易露怯。

3.3 采集模块的边界:演示态和真实环境差在哪里

必须明白一点,这套系统的爬虫模块是演示性质的,它用模拟接口来模拟真实行情数据的获取流程。它演示的是 request 请求、解析响应、字段映射、延时控制这套方法论,而非真实交易行情。当你把它改成真实数据源时,反爬策略、登录态、加密参数、请求频率限制都要重新处理。我一般会建议:毕业设计答辩时用模拟数据源讲流程,再在扩展准备里说明“真实场景下替换为公开行情源,并增加代理池和请求重试机制”,这样既保住了演示的稳定性,又体现了技术边界。

4. 金融数据分析:从均值、涨跌幅到波动率的三层拆解

数据采集解决的是“有什么数据”,分析模块解决的是“数据说明什么”。这套系统在分析层面做的不是深度机器学习或时序预测,而是把最常用的行情描述性统计做扎实。对毕业设计而言,够用、可解释、有业务含义,比盲目堆模型重要得多。分析结果通常落到三个层面:集中趋势、变化幅度和波动程度。

4.1 分析维度与指标定义:平均数、涨跌幅、成交量均值怎么算

第一个要明确的是指标口径。平均价格一般指一段时间内收盘价或成交价的算术平均;涨跌幅是当前价格相对前收盘价的变化率,公式是(当前价 - 前收盘价) / 前收盘价 * 100%;成交量均值则反映一段时间内的交易活跃度。这些指标计算都不复杂,但口径一旦错了,后面的每张图表都会跟着错。

# app/analysis.py 指标计算部分 def calculate_indicators(df): result = {} latest_price = df['price'].iloc[-1] prev_close = df['price'].iloc[0] # 示例用首日收盘作为前收盘 result['avg_price'] = round(df['price'].mean(), 2) result['max_price'] = round(df['price'].max(), 2) result['min_price'] = round(df['price'].min(), 2) result['avg_volume'] = int(df['volume'].mean()) result['change_pct'] = round((latest_price - prev_close) / prev_close * 100, 2) # 波动率:以价格序列的标准差衡量 result['volatility'] = round(df['price'].std(), 4) # 按代码分组统计各标的均值和累计成交量 group_stats = df.groupby('code').agg( avg_price=('price', 'mean'), total_volume=('volume', 'sum') ).reset_index() return result, group_stats

这里有一个统计上的坑:df['price'].std()默认是样本标准差,分母是 n-1。如果演示数据只有几条记录,结果会和总体标准差差异很大。在意数值精度的场景,要显式传入ddof=0。不过毕业设计阶段用默认值也行,前提是你在答辩时能说明白为什么用样本标准差。

4.2 时间序列数据的顺序陷阱:为什么排序不对会算出假涨跌

计算涨跌幅时最怕的就是时间乱序。采集的数据如果按股票代码分组后没有按时间排序,iloc[-1]取到的就不是最近价格,算出的涨跌幅毫无意义。

# 排序修正:按代码和时间双维度排序 def prepare_df(df): df = df.sort_values(['code', 'timestamp'], ascending=[True, True]) df['price_pct_change'] = df.groupby('code')['price'].pct_change() * 100 return df

pct_change()是 pandas 内置方法,天然按行号相邻计算百分比变化。排序正确之后,每个分组内当前行与上一行的差值才是真正的时间序列变化率。很多同学在这步跳过排序直接调用 pct_change,结果第一行会出现 NaN,后续聚合时又把 NaN 当 0,最终涨跌幅均值被拉低。清洗阶段排序这个动作,一句话就能在答辩时讲清楚价值。

4.3 分析结果的输出曲线:直接 print 还是落盘还是入库

分析结果有三种承载方式:控制台打印、写入 CSV 文件、写入 SQLite。这套演示系统的常见做法是分析模块返回 DataFrame,由路由决定是嵌入网页表格还是导出。做导出功能时,最省事的方案是 pandas 直接写 CSV:

df.to_csv(f'output/{code}_analysis.csv', index=False, encoding='utf-8-sig')

这里有个编码关键点:普通utf-8编码的 CSV 用 Excel 打开会乱码,utf-8-sig带有 BOM 头,Excel 识别正确。如果只做网页展示,不需要落盘;但如果老师要求提交分析报告,这份 CSV 就是最直观的成果物。

5. 可视化呈现:matplotlib 静态图与 Flask 的配合方案

数据算出来后,最终要变成人眼能看的图表。这套系统以 matplotlib 和 seaborn 为主,生成静态图片后放入 static 目录,由 Flask 通过 URL 路径提供给前端。这套方案的好处是代码简单、不依赖前端图表库、离线演示稳定。

5.1 后端绘图为什么选静态图片而不是交互图表

交互图表如 ECharts、Plotly 的交互性更强,但需要前端引入相应 JS 库并在模板里写初始化逻辑。matplotlib 生成静态 PNG 的逻辑更线性:画图 → 保存 → 显示,整个链路容易排查。以下代码是实现价格走势图和成交量柱状图的标准模板:

# app/visualization.py 图表生成模块(节选) import matplotlib matplotlib.use('Agg') # 必须使用非交互式后端 import matplotlib.pyplot as plt import os def generate_charts(df, output_dir='static/charts'): os.makedirs(output_dir, exist_ok=True) chart_path = os.path.join(output_dir, 'price_trend.png') fig, ax1 = plt.subplots(figsize=(10, 5)) ax1.plot(df['timestamp'], df['price'], color='#d62728', linewidth=1.5) ax1.set_xlabel('时间') ax1.set_ylabel('价格', color='#d62728') ax1.tick_params(axis='y', labelcolor='#d62728') ax2 = ax1.twinx() ax2.bar(df['timestamp'], df['volume'], alpha=0.3, color='#1f77b4') ax2.set_ylabel('成交量', color='#1f77b4') plt.title('股票价格与成交量趋势') fig.autofmt_xdate(rotation=45) plt.tight_layout() plt.savefig(chart_path, dpi=100) plt.close(fig) return 'charts/price_trend.png'

这段代码有两个关键点。第一,matplotlib.use('Agg')必须在任何 pyplot 操作之前执行,否则在 Flask 这种非 GUI 环境里会弹出交互窗口或直接报错,这是后端绘图最常见的一个环境坑。第二,plt.close(fig)必须写,否则每次请求都会多占一份内存,跑上几十次后服务变得异常缓慢。

5.2 Flask 静态目录资源与模板引用的映射机制

图表保存到static/charts/price_trend.png后,前端模板里怎么引用,取决于 Flask 的静态目录映射约定。默认情况下,Flask 将项目根目录下的static文件夹映射为/static路径,所以模板中直接写:

<img src="{{ url_for('static', filename='charts/price_trend.png') }}" alt="价格趋势图">

url_for('static', filename='...')的作用是动态生成静态资源绝对路径。这里有一个容易踩的坑:不要直接在模板里写/static/charts/price_trend.png。如果项目部署到子路径,硬编码路径会全部失效,而url_for会自动处理根路径前缀。

5.3 图表风格定制:中文字体和颜色怎么一次配好

matplotlib 默认字体不支持中文,如果不做配置,图表上的“时间”“价格”“成交量”全部变成方框。配字体的标准方式是:

plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS'] plt.rcParams['axes.unicode_minus'] = False

axes.unicode_minus=False解决负号渲染成方块的问题。字体选用要看你运行环境:Windows 上用 SimHei,Linux 服务器上如果没有中文字体,可以装fonts-wqy-microhei,或者直接在代码里指定字体文件路径。图表配色我一般用 matplotlib 默认的 Tableau 色板,红蓝搭配最省事,不会出现看不清线型的情况。

6. 部署运行与常见问题排查:五个高频翻车点怎么定位

每次拿到一份新资源,环境搭建阶段总是最消耗耐心的一段。从 Python 版本到依赖库版本,从端口到缓存,踩过的坑各不相同,但排错思路有迹可循。下面这五条是我实际跑这类 Flask 项目时反复遇到的坑位,每条都按现象、原因、解决三段处理。

6.1 Python 版本与依赖库冲突:pyc 文件引发玄学报错

现象是模型相关代码在启动时报ModuleNotFoundError或ValueError: source code string cannot contain null bytes,检查源文件却完全正常。原因多半是__pycache__里的 pyc 文件是 Python 3.7 编译的,与你当前解释器不兼容。解决方法是删除所有__pycache__目录和.pyc文件,然后按 requirements.txt 重装依赖。这个操作用一条命令就能做完:

find code -type d -name __pycache__ -exec rm -rf {} + \; rm -f code/*.pyc

6.2 爬虫数据源返回空数据:页面表格空白但服务不报错

现象是启动正常、页面能打开,但数据表格是空的。原因大多是采集接口返回了空列表,而视图函数没有做空数据拦截。解决思路是在路由里加异常兜底并打印日志:

raw_data = fetch_stock_list() if not raw_data: flash('数据源暂不可用,请稍后重试') return render_template('error.html'), 503

6.3 图表不显示:404 或旧缓存图

现象是页面打开后图片位置是破图,查看浏览器开发者工具发现/static/charts/...返回 404。原因多为static目录路径不对,或图表文件名已被覆盖但浏览器缓存了旧文件。解决方法是检查路由所在文件的相对路径:如果你把 app 包放在二级目录,static文件夹必须在 Flask 实例所在目录下。另一个强制刷新技巧是在图片路径后手动拼一个时间戳参数:

<img src="{{ url_for('static', filename='charts/price_trend.png') }}?v={{ generation_time }}">

6.4 价格或成交量浮点异常:NAN 就像瘟疫

现象是分析结果出现大量 NaN 或 0。原因大概率是原始数据里某些字段没有清洗干净,比如空字符串转 float 失败后被coerce成了 NaN,之后含 NaN 的运算全部返回 NaN。解决方向是在清洗阶段记录异常行数,并且打印一条警告:

invalid_count = df['price'].isna().sum() if invalid_count > 0: print(f'警告: 发现 {invalid_count} 条价格缺失记录,已过滤')

6.5 端口被占用:Address already in use

现象是启动时提示OSError: [Errno 98] Address already in use,或者项目启动后浏览器仍旧访问到旧页面。原因往往是有另一个 Python 进程占了同一个端口。解决方式是换端口或杀掉旧进程再启动:

sudo lsof -i :5000 # 找出占用 PID kill <PID> # 杀掉旧进程 python app.py --port 5001 # 或直接换端口

第二条路径更轻量:Flask 的app.run(port=5001)会直接绑定到新端口,不影响浏览访问。从那以后我每次启动这种 Flask 演示项目,都会强制走一遍“删 pyc → 查空数据 → 开端口检测 → 刷新缓存”的流程,五分钟就能确定问题全部在环境、数据还是前端路径上。这套流程对这套毕业设计源码同样适用,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询