☰
Python股票数据爬虫+分析+可视化框架:从爬取到图表的全链路拆解
2026/10/25 10:58:02 网站建设 项目流程

简介:一套完整的Python股票数据爬虫、分析与可视化框架,面向计算机相关专业正在做毕业设计、课程设计或期末大作业的学生,也适合需要项目实战练习的学习者。项目共11个文件,压缩包约260KB,包括5个Python脚本、2个pyc编译文件、1个txt依赖清单、1个HTML模板、1个JS图表库和1个MD文档;py脚本承担采集、处理与界面逻辑,HTML与JS构成可视化页面,txt与MD帮助快速复现依赖环境、了解项目结构。核心流程覆盖股票基础信息爬取、历史行情数据采集、工具函数封装与依赖管理,可视化端基于ECharts展示行情浏览页面,形成从数据抓取到界面展示的完整闭环。整体框架定位为98分毕业设计系统,源码经过严格调试,附有项目说明文档,便于快速运行与二次开发,可作为毕业设计或期末大作业的完整参考方案。目前已有282人学习/浏览,适合需要完整参考方案或项目实战练习的学习者。

1. 这不是“又一个爬虫demo”:Python股票数据爬虫+分析+可视化框架到底能帮你交付什么

如果你正处于毕业设计、课程设计或期末大作业的周期里,最怕的不是没思路,而是手里拿到的“参考项目”根本跑不起来。Python股票数据爬虫+分析+可视化框架这套资源,我拆完第一感受是:它不是那种几十行代码的玩具爬虫,而是一套把数据采集、内存缓存、图表渲染、前端模板串成一条完整链路的工整项目。它解决的是“从股票数据源到浏览器里出图表”的全流程问题,适合正在做毕设的计算机相关专业学生,也适合想练完整项目手感、又不想从零攒框架的初学者。这套东西的妙处在于分层很清楚:爬虫归爬虫、视图归视图、入口归入口,哪怕你想换数据源、换图表类型,都能在不动整体结构的前提下改。接下来我把每个文件的实际作用、跑通顺序和真正的坑都拆给你看。

2. 先读懂工程骨架:这个股票分析框架的模块划分与数据流

2.1 目录里每个文件是干什么的

我拿到资源后第一步永远是先列文件清单,而不是急着跑。这个框架的核心文件分布得很典型,从上到下能看出作者的层划分习惯:

文件/目录职责定位一句话说明
data_scrapy/stock_basic_scrapy.py股票基础信息爬虫抓股票代码、名称、行业等基础列表
data_scrapy/history_stock_quotes_scrapy.py历史行情爬虫抓个股日线、成交量、涨跌幅等历史行情
data_scrapy/utils/通用工具模块存放公共方法,比如请求头构造、字段清洗
myDict.py自定义字典/缓存容器内存级数据暂存,避免频繁重爬
requirements.txt依赖清单声明项目运行所需的第三方包
data_viewer/rightview.py图表装配逻辑把爬来的数据组织成图表可用的JSON结构
data_viewer/template.html前端模板浏览器最终渲染的页面骨架
data_viewer/echarts.min.js本地ECharts库图表渲染依赖,放在本地避免外链失效
main.py项目入口串联数据采集、装配、页面服务的启动点

这个分层的好处是:你想只跑爬虫看数据,那就不碰data_viewer;你想只调页面效果,就预置一份JSON数据喂给模板。毕设答辩时老师问“你的项目是怎么组织的”,照这个结构讲,逻辑比“我写了一个爬虫”有说服力得多。

2.2 数据是怎么从网页走到图表的

整个框架的数据流,一句话描述就是:爬虫模块把网页数据解析成Python结构,经过myDict做内存暂存,再交给rightview.py转成ECharts的option格式,最后注入template.html由echarts.min.js渲染成图表。

这条链路里最容易断的不是爬虫,而是中间的“数据装配”环节。很多学生自己写毕设时,爬虫跑完数据就往终端一打印,前端拿不到结构化的东西。这个框架把rightview.py单独拎出来,就是为了让“原始数据”和“图表数据”解耦。

爬虫侧输出的常见结构是这样的:每只股票一个字典,包含代码、名称、日期序列、收盘价序列、成交量序列。rightview.py要做的就是把这种纵向的“多股票多维字段”,拆成ECharts里series需要的横向格式。如果这里不做拆分,前端拿到的数据会显得很笨重。

我一般会建议拿到资源后先打印rightview.py的输入和输出各一条,确认数据字段名是否对得上。这个动作能帮你避免后面白屏时一头雾水。

2.3 最小闭环:怎么先让它跑起来

在改任何代码之前,先按我给的顺序跑通一次。打开终端,在项目根目录执行:

# 安装依赖,建议用 Python 3.8+ 的虚拟环境 pip install -r requirements.txt # 第一步:单独跑基础信息爬虫,验证网络和数据解析是否正常 python data_scrapy/stock_basic_scrapy.py # 第二步:单独跑历史行情爬虫,验证个股K线数据能否落地 python data_scrapy/history_stock_quotes_scrapy.py # 第三步:启动入口,打开可视化页面 python main.py

这里的依赖清单是这类爬虫项目的标配:requests负责发起HTTP请求,lxml配合xpath做页面解析,flask用来提供本地页面服务(main.py常见做法是用Flask起一个本地端口)。如果main.py运行后提示端口被占用,把代码里的端口参数改掉就行,不影响全局。

我拆项目时有个习惯:先看跑通的最小闭环占多少步。这个框架三步能见效果,属于负担比较小的那种。跑通后再看数据准不准、图表全不全,那时候才有资格谈改造。

3. 爬虫层拆解:股票基础信息与历史行情的抓取逻辑

3.1 两个爬虫脚本怎么分工

stock_basic_scrapy.py和history_stock_quotes_scrapy.py虽然名字里都带scrapy,但它们实际是标准的requests脚本,只是按模块命名习惯挂了个scrapy后缀。这种命名在毕设里很常见,不用纠结。

两个脚本的分工很明确:

脚本抓取粒度数据用途更新频率
stock_basic_scrapy.py股票维度(代码、名称、行业)生成基础列表,供用户选择低频,一天一次足够
history_stock_quotes_scrapy.py时间维度(每日开高低收、成交量)绘制K线、走势图,做分析高频,需要控制节流

分工清晰的最大好处是出了问题能定位:列表没抓到看第一个脚本,K线不对看第二个脚本,不用在两个文件里来回翻。

3.2 基础信息爬虫的核心逻辑

stock_basic_scrapy.py要解决的事情是:把全市场股票的基础信息拉下来,并稳定解析。常见的实现方式是用requests加xpath,先定位表格行节点,再从每行里提取代码和名称。我会这样组织核心逻辑:

import requests from lxml import html # 目标地址以公开股票列表页为例,具体以项目内配置为准 LIST_URL = "https://example.com/stock_list" # 占位:项目里是实际数据源地址 def fetch_stock_basic(): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Referer": "https://example.com/", # 部分站点会校验来源 } resp = requests.get(LIST_URL, headers=headers, timeout=10) resp.encoding = resp.apparent_encoding # 优先按页面实际编码解析 tree = html.fromstring(resp.text) stocks = [] # 常见页面的行节点定位,具体xpath以项目实际页面为准 for row in tree.xpath("//table/tbody/tr"): code = row.xpath("./td[1]/text()") name = row.xpath("./td[2]/text()") if code and name: stocks.append({"code": code[0].strip(), "name": name[0].strip()}) return stocks

这段代码的重点是两个地方:resp.encoding = resp.apparent_encoding和xpath取文本后要strip()。前者解决中文乱码,后者解决文本里的换行空格污染数据。

headers里的User-Agent不是可有可无的装饰,很多公开数据源对空UA的请求直接返回403或跳验证码页面。项目里如果stock_basic_scrapy.py被注释掉了headers,你补上UA和Referer,大概率就能从“抓不到”变成“能抓到”。

3.3 历史行情脚本里的翻页与节流

history_stock_quotes_scrapy.py比基础信息复杂,因为它要面对大量个股的逐日数据。常见做法是按股票代码遍历,每只股票按日期参数请求K线接口,拿到JSON后直接解析。核心逻辑长这样:

import time import requests from myDict import MyDict # 项目自带的内存缓存容器 QUOTE_API = "https://example.com/api/quote" # 占位:项目里是行情接口地址 def fetch_history_quotes(stock_code, page_size=100): cache_key = f"history:{stock_code}" cache_data = MyDict.get(cache_key) if cache_data: return cache_data # 命中缓存直接返回,避免重复请求 quotes = [] for page in range(1, 5): # 控制总页数,避免一次性请求过多 params = { "code": stock_code, "page": page, "pageSize": page_size, "fields": "date,open,close,high,low,volume", } try: resp = requests.get(QUOTE_API, params=params, timeout=10) data = resp.json() quotes.extend(data.get("data", [])) except Exception as e: print(f"page {page} 请求失败: {e}") break # 单页失败先停,不要连环重试 time.sleep(0.3) # 关键:每页之间做节流 MyDict.set(cache_key, quotes, expire=3600) # 缓存一小时 return quotes

这里的两个参数值得细看。page_size决定单次请求的数据量,我习惯用100,因为多数接口单页上限就在这个量级,太大反而容易被服务端拒绝。time.sleep(0.3)是硬节流,控制在每秒约3次请求,既能跑完数据,又不至于触发接口的风控。

try...except里要注意:失败后我只break当前股票的翻页,而不是整只股票重试。这是血泪经验——行情接口偶尔超时很正常,为了一页数据把整只股票重抓一遍,代价太高。

3.4 请求头与限流:能不能跑全量数据的关键

这几个脚本单独跑一两页数据没问题,但毕设演示要的是“跑全量数据”。全量意味着请求数量成倍上升,这时候决定成败的不是解析逻辑,而是请求头完整度和限流策略。

请求头至少要有User-Agent、Referer、Accept三项。Referer尤其容易被忽略,有些数据源会校验请求是从哪个页面跳过来的,缺了它直接返回空数据。我见过最玄学的场景:爬虫偶尔成功偶尔失败,最后发现是Referer时有时无。

限流方面,两个脚本之间也要错峰。基础信息爬虫跑完再跑行情爬虫,不要并行。如果行情脚本加了time.sleep还是被限制,优先看是不是IP层面的问题,解决办法是把每页间隔从0.3秒调到1.5秒,牺牲速度换稳定性。

4. 可视化层:main.py、rightview.py与template.html的联动机制

4.1 main.py:入口脚本到底做了什么

main.py不是爬虫,也不是图表代码,而是整个框架的“装配车间”。它做的事情一般有三件:调用爬虫模块拿数据、把数据传给rightview.py生成图表配置、启动本地服务让浏览器能访问template.html。

常见的main.py实现基于Flask,写法大概是这样:

from flask import Flask, render_template from data_scrapy.stock_basic_scrapy import fetch_stock_basic from data_viewer.rightview import build_chart_option app = Flask(__name__, template_folder="data_viewer") @app.route("/") def index(): stocks = fetch_stock_basic() # 1. 拿数据 option = build_chart_option(stocks) # 2. 装配图表配置 return render_template("template.html", chart_option=option) if __name__ == "__main__": app.run(host="127.0.0.1", port=8080, debug=False)

这里第一个要理解的点是template_folder参数。template.html在data_viewer目录下,如果不指定模板文件夹,Flask默认去templates目录找,就会报TemplateNotFound。项目里如果main.py已经配置好,你不需要动;如果你自己拆开重写入口,这是一个高频翻车点。

debug=False也建议保持,调试时用debug=True虽然能热重载,但爬虫类项目里热重载可能引发重复请求,把数据源搞封了就得不偿失。

4.2 rightview.py:把“数据”翻译成“图表语言”

rightview.py存在的意义,是把Python字典数据结构翻译成ECharts的option对象。ECharts能渲染什么,完全取决于你给它什么样的series和xAxis。这个脚本的核心逻辑一般是构造时间轴和对应的行情序列:

def build_chart_option(stock_data): dates = [item["date"] for item in stock_data] close_prices = [item["close"] for item in stock_data] volumes = [item["volume"] for item in stock_data] option = { "title": {"text": "收盘价走势"}, "tooltip": {"trigger": "axis"}, "xAxis": {"type": "category", "data": dates}, "yAxis": {"type": "value"}, "series": [ {"name": "收盘价", "type": "line", "data": close_prices}, {"name": "成交量", "type": "bar", "data": volumes}, ], } return option

这里值得注意的细节是tooltip的trigger设为axis。对于时间序列数据,按轴触发提示比按点触发好用得多,鼠标划过去能看到整条竖线上的所有指标,答辩演示时观感更好。

rightview.py里如果还想加K线图,只需要把type从line换成candlestick,并把data结构改成“开高低收”的四元组数组。这个脚本是扩展图表类型的理想位置,因为它已经和爬虫解耦了。

4.3 template.html与echarts.min.js的配合写法和路径坑

template.html承担的是最终渲染。它内部必须做三件事:引入echarts.min.js、准备一个图表容器div、在脚本里读取后端传入的chart_option并初始化图表。我用最小代码说明它和本地JS库的配合方式:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>股票分析可视化</title> <!-- 注意:这里引用的是本地文件,不是CDN --> <script src="{{ url_for('static', filename='echarts.min.js') }}"></script> </head> <body> <div id="chart" style="width: 900px; height: 520px;"></div> <script> const chart = echarts.init(document.getElementById('chart')); // chart_option 由后端通过模板变量注入 chart.setOption({{ chart_option | tojson }}); </script> </body> </html>

template.html有几个隐含约定。第一,echarts.min.js必须放在Flask能访问到的static目录下,如果项目里它和template.html同目录,那main.py要么配了static_folder,要么直接把JS路径写死为相对路径。第二,{{ chart_option | tojson }}是后端对象到前端JS对象的关键转换,少了tojson过滤器,Python字典会被渲染成{ 'date': ... }这种带引号的格式,ECharts直接报错。

如果你改了main.py的端口或路由,记得同步检查template.html里的资源引用路径。白屏问题里有一半是JS文件没加载成功,打开浏览器控制台看到404就说明路径写错了。另一个常见问题是图表容器div没有被渲染出来,检查是不是模板里漏掉了闭合标签。

5. 避坑与排查:把这套框架跑顺的五条实战记录

5.1 现象:小数据量正常,一跑全量就被拦截

第一次跑完基础信息爬虫,我只请求了两页,一切正常。换成全量列表后,第三页开始返回空数据,再过几页直接提示“访问频繁”。原因是代码里对全量场景没有节流,请求频率远超数据源容忍阈值。

解决:给每页请求加上time.sleep(random.uniform(0.5, 1.5)),让请求间隔有随机抖动,比固定时长的节流更难被识别。另一个兜底是给requests.get加retries逻辑,遇到单次失败休息5秒再重试,不要连环请求。

5.2 现象:历史行情返回的日期字段乱码

跑history_stock_quotes_scrapy.py时,日期和名称字段打印出来是乱码,但数字字段正常。这是典型的编码问题,JSON接口通常返回UTF-8,但如果脚本在请求后手动指定了错误的编码,中文就会碎掉。

解决:不要手动设置resp.encoding,让requests按响应头自动判断;如果自动判断还乱码,再强制resp.encoding = "utf-8"。这个坑的隐蔽之处在于它只在特定接口出现,基础信息脚本正常不代表行情脚本也正常。

5.3 现象:main.py启动后页面白屏,控制台报ECharts未定义

页面能打开HTML,但图表区域一片空白,检查控制台发现echarts is not defined。原因基本指向echarts.min.js没有加载成功,要么是路径写成CDN但当前环境不能访问外网,要么是本地路径层级不对。

解决:确认echarts.min.js的实际位置,把script标签的src改成相对路径或Flask的url_for正确写法。改完强制刷新浏览器(Ctrl+F5),避免缓存干扰。从那以后我再没因为JS库路径白屏超过五分钟。

5.4 现象:第二次运行main.py,图表数据还是上一次的

这是myDict.py的缓存特性导致的典型误用。第一次跑完,整个市场数据被写进内存字典,第二次运行入口脚本时命中了缓存,直接跳过重新爬取。数据源没变还好,但如果过了几天再跑,缓存里的旧数据会误导分析。

解决:查myDict.py里缓存的过期机制。如果有expire参数,在main.py或爬虫脚本里把它设成合理值;如果没有过期机制,在入口脚本加一个--fresh参数,运行时强制清空缓存。毕设演示前一天跑数据时,一定先清缓存再拉新数据,否则图上日期停留在昨天。

5.5 现象:xpath取不到数据,解析结果为空列表

stock_basic_scrapy.py之前好好的,过段时间再跑,xpath提取结果变成空。爬虫脚本最怕的就是页面结构调整,表格列顺序一变,./td[1]/text()就指向了错误内容。

解决:先用浏览器开发者工具重新检查当前页面结构,确认列顺序和class名。如果数据是通过JavaScript异步加载的,那requests拿到的静态源码里根本没有表格节点,需要用无头浏览器或直接改找数据接口。这个判断很重要:先分清是页面变了还是数据是动态渲染的,再决定改xpath还是换数据源。

6. 改造与验证:让这套“高分毕业设计”真正变成你的项目

拿到资源后,最忌讳的是原封不动交上去。毕设答辩和课程作业都会查重、看工作量,所以至少要完成三件事:验证它能跑、改动其中一层、补充自己的说明。先说我验证项目的固定流程:

# 1. 验证基础信息爬虫,输出条数要大于100 python data_scrapy/stock_basic_scrapy.py | wc -l # 2. 验证历史行情爬虫,重点看单只股票的日期范围 python data_scrapy/history_stock_quotes_scrapy.py --code 600000 # 3. 验证可视化,启动后打开页面,确认图表真实渲染 python main.py

验证完成后,我建议改造方向有三个:第一,把数据落成SQLite,加一个简单的存储层,这样就不用每次启动都重爬;第二,把数据源从现有接口换成Tushare或akshare,替换stock_basic_scrapy.py里的请求逻辑即可,rightview.py和template.html完全不用动;第三,把模板扩展成可视化大屏风格,利用ECharts的多图表网格能力,把走势、成交量、涨跌分布放在同一屏。

这部分是这个项目最有价值的地方:分层设计决定了你改一层不影响其他层。爬虫换成新数据源,输出结构保持代码、名称、日期、收盘价、成交量这些字段,可视化层就能原样工作。我当时为了验证这个说法,把两个爬虫脚本都换了数据源,只改了请求地址和字段映射,图表一次渲染成功。

最后说一个我自己的习惯:每次接到这类毕设资源,我都会先建一个干净的Python虚拟环境,从零按requirements.txt装依赖,跑通后再动代码。这个动作能看清这套源码真正的运行条件,也方便我写自己的毕设记录。这几步走完,这套Python股票数据爬虫+分析+可视化框架才算真的属于你了,而不是仅仅存在于下载文件夹里。希望这份拆解帮到你。

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

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

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

立即咨询