ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python股票数据可视化与推荐系统完整实践:从数据采集到算法实现

2026/9/28 7:01:18 拓冰建站 浏览量
Python股票数据可视化与推荐系统完整实践:从数据采集到算法实现 手里这套《基于Python的股票数据可视化及推荐系统的设计与实现》是我前阵子帮一个学弟改毕业设计时完整梳理出来的项目。整套东西包含文档、PPT和源码核心就干两件事一是把股票的历史行情数据用图表直观呈现出来二是基于股票的特征给用户推荐风格相似的标的。剥掉课程设计的壳它本质上是一个数据采集 数据清洗 可视化展示 推荐算法的完整闭环非常适合拿来练手也适合作为毕设或课设的基座项目。我用Python从零搭完了整条链路数据用akshare拉取清洗和特征计算用pandas可视化用pyecharts和matplotlib后端用Flask推荐模块用余弦相似度加KMeans聚类最后通过网页交互呈现。这篇文章把我整个设计过程、踩过的坑、以及可以抄作业的代码结构全部整理出来。无论你是刚学Python的小白还是准备做大数据相关课设的学生这套思路都能直接套用。1. 项目背景与核心设计思路1.1 这个项目到底要解决什么问题先说清楚背景。市面上做股票数据展示的工具不少同花顺、东方财富这类客户端功能确实强大但都是面向终端用户的全家桶。作为学习者或开发者你很难从这些商业产品里看到数据是怎么从接口拿下来、怎么清洗、怎么计算指标、怎么画图这一整条技术链路。这个项目的定位很简单用Python实现一个轻量级的、可二次开发的股票数据分析和推荐演示系统。它解决三个具体问题数据获取门槛手动下载Excel或用爬虫抓网页数据效率低且不稳定需要一个可靠的自动拉取方案。可视化表达股票数据是典型的时间序列K线、均线、成交量、MACD这些专业图表需要程序化生成还要考虑交互体验。推荐逻辑演示很多人一听推荐系统就想到电商、短视频但股票的相似标的推荐同样可以用协同过滤和聚类的思路来做这是项目里最有技术含金量的一块。从课程设计的角度说这个项目覆盖了Python数据分析、可视化、Web开发、机器学习入门四个知识点评阅老师想挑毛病都不太容易找到下手点。从技术成长的角度说它逼着你把pandas用熟把图表库用透还要理解推荐算法不是只有皮尔逊相关系数这一条路。1.2 技术选型的底层逻辑技术栈我一开始就定了方向Python全家桶没有引入Java或Scala那套大数据生态。为什么第一Python在数据处理领域生态最完整pandas处理表格数据、numpy做数值计算、matplotlib画图每个环节都有成熟方案学习成本低。第二这个项目的数据量级在几百只股票的日线数据撑死几十万条记录完全不需要Spark、Flink这种分布式框架用了反而是过度设计。第三Python的Flask框架写后端接口极其轻量一个人两天就能把前后端串起来。具体选型如下数据层akshare免费、无需token、覆盖面广下文详细对比数据处理pandas numpy可视化pyecharts交互图表 matplotlib静态报表后端服务Flask Flask-CORS推荐算法scikit-learn余弦相似度、KMeans聚类前端展示HTML JavaScript ECharts通过pyecharts生成的html嵌入这个组合最大的好处是纯Python搞定一切不需要额外部署数据库数据直接存CSV或内存DataFrame不需要单独写前端工程pyecharts生成的就是完整html对毕设场景来说部署和答辩演示都非常友好。提示如果你的项目规模需要更大的数据量或更复杂的推荐逻辑可以把存储层换成MySQL把推荐模块换成DeepFM或Graph Embedding但那是工程化方向不是课程设计方向。作为毕设先跑通闭环比盲目堆技术更重要。2. 数据层采集、清洗与特征工程2.1 数据源怎么选akshare vs tushare这一步是整个项目的根基。数据源选不好后面所有图表和推荐都是空中楼阁。我对比了好几个方案最终锁定在akshare和tushare之间。tushare是业内很知名的开源数据接口但有个痛点积分制度。注册后基础积分只能拉部分数据想要日线行情、财务数据这些核心接口必须攒积分或付费对新手很不友好。akshare则完全免费不需要注册token接口直接调用基于公开网页数据封装数据覆盖A股、ETF、期货、宏观经济等对课程设计来说绰绰有余。我用akshare拉取股票历史行情的核心代码如下import akshare as ak import pandas as pd def fetch_stock_data(stock_code: str, start_date: str 20200101, end_date: str 20241231): 获取单只股票的日线行情数据 stock_code: 股票代码如 600519 代表贵州茅台 df ak.stock_zh_a_hist( symbolstock_code, perioddaily, start_datestart_date, end_dateend_date, adjustqfq # 前复权下文详细解释 ) # akshare返回的列名是中文统一改成英文方便后续处理 df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume, 成交额: amount, 振幅: amplitude, 涨跌幅: pct_change, 涨跌额: change, 换手率: turnover }, inplaceTrue) df[date] pd.to_datetime(df[date]) df.set_index(date, inplaceTrue) return df这里有个细节很多人会忽略列名中文化。akshare返回的DataFrame列名是中文如果你不统一改成英文后续所有特征计算和图表绑定的代码都会变得混乱。我在项目里写了一个统一的列名映射函数所有数据源进来的数据都先过一遍标准化这是数据工程的基本素养。批量拉取多只股票时要注意接口的访问频率。akshare毕竟是爬取公开数据高频请求容易被限流或封IP。我的做法是每只股票之间sleep随机1~3秒并且把拉取结果缓存成本地CSV文件后续所有模块都优先读缓存只有缓存缺失时才走网络请求。2.2 数据清洗与复权处理拿到原始行情数据后清洗是绕不开的一步。真实数据远没有教科书那么干净我实际遇到的主要问题有三个停牌日缺失股票停牌时没有行情数据DataFrame中直接少了行这会导致图表时间轴不连续。处理方案是用df.reindex()按完整交易日序列重采样缺失值用前向填充ffill或者干脆在可视化时用category类型坐标轴。异常值个别日期的涨跌幅异常大通常是数据源解析错误或除权除息导致的。我用的策略是z-score检测超过3倍标准差的数据标记为异常并人工核查。注意别直接删先确认是不是真实事件比如重大利好导致的暴涨。复权问题这是股票数据最核心的坑。除权除息后股价会突然跳空如果不做复权处理K线图和技术指标都会被假缺口误导。akshare的adjust参数有三个选项不复权、qfq前复权、hfq后复权。项目里我统一采用前复权因为前复权保证最新价格是真实价且历史价格被调整适合做技术分析。def clean_stock_data(df: pd.DataFrame) - pd.DataFrame: # 去重防止接口重复返回 df df[~df.index.duplicated(keepfirst)] # 按日期排序 df df.sort_index() # 去除成交量、收盘价为0的异常行 df df[(df[volume] 0) (df[close] 0)] # 补充缺失交易日可选按需开启 # df df.reindex(pd.date_range(df.index[0], df.index[-1], freqB)) return df清洗完的数据要做一个数据质量报告统计每只股票的有效天数、缺失率、异常值数量。这个报告在答辩时非常好用能直观展示你的工程严谨性也可以作为文档中的数据说明章节。2.3 技术指标特征的计算推荐系统和可视化都需要技术指标做支撑。我选了四个最主流、也能讲清楚原理的指标均线MA、MACD、KDJ、RSI。这些都是用pandas的滚动窗口函数直接算的不需要额外库。def compute_indicators(df: pd.DataFrame) - pd.DataFrame: # 简单移动均线 MA5 / MA10 / MA20 df[ma5] df[close].rolling(window5).mean() df[ma10] df[close].rolling(window10).mean() df[ma20] df[close].rolling(window20).mean() # MACD: 计算 DIF (EMA12 - EMA26) 和 DEA (DIF的9日EMA) ema12 df[close].ewm(span12, adjustFalse).mean() ema26 df[close].ewm(span26, adjustFalse).mean() df[macd_dif] ema12 - ema26 df[macd_dea] df[macd_dif].ewm(span9, adjustFalse).mean() df[macd_hist] (df[macd_dif] - df[macd_dea]) * 2 # RSI(14): 用平均涨幅和平均跌幅计算 delta df[close].diff() gain delta.clip(lower0) loss -delta.clip(upper0) avg_gain gain.ewm(alpha1/14, adjustFalse).mean() avg_loss loss.ewm(alpha1/14, adjustFalse).mean() rs avg_gain / avg_loss df[rsi14] 100 - (100 / (1 rs)) # KDJ: K 2/3 * 前K 1/3 * RSV, D同理 low9 df[low].rolling(window9).min() high9 df[high].rolling(window9).max() rsv (df[close] - low9) / (high9 - low9) * 100 df[kdj_k] rsv.ewm(com2, adjustFalse).mean() df[kdj_d] df[kdj_k].ewm(com2, adjustFalse).mean() df[kdj_j] 3 * df[kdj_k] - 2 * df[kdj_d] return df计算技术指标时的第一个注意事项前30行全是NaN。因为MA20、EMA26这些窗口需要足够的历史数据才能算出第一个值。在可视化阶段一定要处理NaNpyecharts画图时遇到NaN会断线或留白我习惯用fillna(methodbfill)或直接丢弃前20行。第二个注意事项是指标参数的固化。MA用5/10/20MACD用12/26/9这些都是行业标准参数答辩时老师问起来要有底气回答这是基于一套被广泛使用的分析体系并非随意取值。懂的都懂参数改一改画出来的图就大不一样。3. 可视化模块从静态图表到交互大屏3.1 可视化工具为什么选了pyecharts可视化是这类项目最直观的门面评审老师第一眼看的往往就是图表效果。Python生态里可视化库很多matplotlib、seaborn、plotly、pyecharts、bokeh各有优劣。我最终主用pyecharts理由非常实际交互性强pyecharts生成的是基于ECharts的HTML图表支持缩放、拖拽、悬浮查看数值答辩演示时观感远超matplotlib的静态png。中文生态友好ECharts对中文标签、中文文档支持极好不会出现matplotlib那种中文乱码问题matplotlib默认字体不含中文字符。生成方式适合Web集成chart.render()直接输出html片段后端渲染时用iframe嵌入或直接返回完整页面前后端衔接非常顺滑。但这不意味着matplotlib就没用了。我在数据探索分析阶段仍然用matplotlib画快速草稿图因为它的API简单直接适合在Jupyter里快速验证数据。到最终交付的可视化大屏才统一切成pyecharts。两种工具的分工是探索用matplotlib展示用pyecharts这也是我在实际项目里形成的习惯。3.2 核心图表的设计项目里我设计了三层可视化内容第一层单只股票的详情图。核心是K线图叠加MA5/MA10/MA20三条均线副图放成交量柱状图再配上MACD指标。K线图在pyecharts里用Kline组件均线用Line组件通过overlap叠加实现同图显示。这里有个关键技巧K线图和均线的x轴数据必须完全一致否则图表错位。from pyecharts import options as opts from pyecharts.charts import Kline, Line, Bar, Grid def render_kline_chart(df, stock_name): dates df.index.strftime(%Y-%m-%d).tolist() # K线数据格式: [open, close, low, high] 注意顺序 kline_data df[[open, close, low, high]].values.tolist() kline ( Kline() .add_xaxis(dates) .add_yaxis( K线, kline_data, itemstyle_optsopts.ItemStyleOpts( color#ef232a, # 阳线红色 color0#14b143, # 阴线绿色 border_color#ef232a, border_color0#14b143, ), ) .set_global_opts( title_optsopts.TitleOpts(titlef{stock_name} K线图), xaxis_optsopts.AxisOpts(type_category, is_scaleTrue), yaxis_optsopts.AxisOpts(is_scaleTrue, splitarea_optsopts.SplitAreaOpts(is_showTrue)), datazoom_opts[opts.DataZoomOpts(range_start60, range_end100)], legend_optsopts.LegendOpts(is_showTrue), ) ) # 均线 line ( Line() .add_xaxis(dates) .add_yaxis(MA5, df[ma5].round(2).tolist(), is_smoothTrue, linestyle_optsopts.LineStyleOpts(width1)) .add_yaxis(MA10, df[ma10].round(2).tolist(), is_smoothTrue, linestyle_optsopts.LineStyleOpts(width1)) .add_yaxis(MA20, df[ma20].round(2).tolist(), is_smoothTrue, linestyle_optsopts.LineStyleOpts(width1)) ) kline.overlap(line) return kline第二层多股票对比图。把同一行业或推荐候选池里的多只股票收盘价做归一化后画在同一张折线图里用于直观对比走势强弱。归一化方法用(close / 首日close - 1) * 100转换成相对涨跌幅百分比否则不同股价区间的股票画在一起毫无可比性。第三层推荐结果仪表盘。用饼图展示推荐股票的行业分布用雷达图展示股票的指标体系收益、波动、换手、市值等维度让推荐结果不只是冷冰冰的股票代码而是有解释性的可视化报告。3.3 可视化层踩过的坑可视化看起来简单实际调试时我遇到几个非常典型的问题时间轴的密度问题。直接把几百个交易日的日期全部放在x轴标签会重叠成一片黑。我妥协的方案是x轴只显示部分刻度用axislabel_optsopts.LabelOpts(interval20)让ECharts自动跳着显示。配合dataZoom滚动条用户想看任意区间都能拖拽缩放。涨跌颜色的争议。A股市场约定俗成是红涨绿跌但ECharts默认风格是绿涨红跌欧美习惯。我在K线图的ItemStyle里手动指定了color#ef232a红color0#14b143绿这个细节如果不处理懂行的人一眼就看出你不够专业。图表刷新与内存。pyecharts渲染大图时如果一次性叠太多seriesHTML会非常臃肿导致浏览器卡顿。我后来规定每张图最多叠加5条线超过的部分拆分成独立子图。这是从实际演示中被卡出阴影后的经验。4. 推荐系统模块给股票找相似之选4.1 推荐系统的核心思路这个模块是整个项目最有区分度的部分。多数毕设做股票系统只会做到展示行情就结束了而加了一个推荐系统项目的技术深度立刻不一样。但这里要想清楚一个问题股票推荐本质上是推荐什么它和电商推荐不同商品推荐是你买了A所以推荐B股票领域没有购买行为用户自选股可以当作隐式反馈。所以我采用了一个更稳妥的可解释方案基于股票特征的相似性推荐——你把某只股票加入自选系统找出与它特征最相似的Top N只股票推荐给你。为什么要用特征相似而不是协同过滤因为协同过滤需要大量用户的交互记录作为输入课程设计场景下根本没有真实用户数据。特征相似则只需要股票本身的数据通过技术指标、波动率、换手率、市值等维度构建特征向量算余弦相似度找邻居。这个思路在金融量化领域叫因子选股 相似度匹配是有真实业务背景的答辩时完全站得住脚。4.2 特征工程把股票变成向量要把股票变成可计算的向量关键是想清楚用哪些特征。我从四个维度构建特征趋势维度区间涨跌幅、MA5相对MA20的偏离度。这个维度体现股票当前处于上升还是下降通道。波动维度日收益率标准差历史波动率、振幅均值。体现股票的风险大小。动量维度近20日累计收益率、RSI均值。反映近期市场情绪。交易活跃度区间日均换手率、日均成交量对数。反映资金关注度。def build_fundamental_features(df: pd.DataFrame) - dict: 把一只股票的历史行情压缩成特征向量 close df[close] daily_return close.pct_change().dropna() features { period_return: (close.iloc[-1] / close.iloc[0] - 1), # 区间涨跌幅 ma_deviation: (close.iloc[-1] / df[ma20].iloc[-1] - 1), # 对MA20的偏离 volatility: daily_return.std() * (252 ** 0.5), # 年化波动率 avg_amplitude: df[amplitude].mean(), # 平均振幅 momentum_20d: (close.iloc[-1] / close.iloc[-21] - 1), # 20日动量 avg_rsi: df[rsi14].mean(), # 平均RSI avg_turnover: df[turnover].mean(), # 平均换手率 avg_volume_log: np.log1p(df[volume].mean()), # 成交量对数 } return features构建特征时最需要注意的是归一化。不同特征的量纲差异巨大涨跌幅是0.1级别的数值换手率是2~5的数值成交量对数值可能是15~20。如果不做归一化余弦相似度会完全被大数值特征主导。我用sklearn.preprocessing.StandardScaler做标准化把所有特征压缩到均值0、方差1的分布中。from sklearn.preprocessing import StandardScaler # 假设 all_features_df 是所有股票的特征矩阵 scaler StandardScaler() feature_scaled scaler.fit_transform(all_features_df)4.3 相似度计算与聚类分组特征向量准备好后推荐的计算就水到渠成了。我用两套方案互为补充方案一余弦相似度找近邻。用户选中股票A计算A的特征向量与全市场其他股票特征向量的余弦相似度排序取TopN。余弦相似度公式就是cos(A, B) A·B / (|A||B|)scikit-learn的cosine_similarity一行搞定。方案二KMeans聚类分组推荐。先把全部股票按特征聚成K个簇我实验后确定K8效果最好同一簇内的股票视为同一风格。用户选中某只股票时先把股票所在的簇拿出来再在簇内按相似度排序推荐。这个方案的优点是可以做风格解释给每个簇贴上标签比如高波动成长型低波动价值型高换手题材型推荐时告诉用户这只股票属于xxx风格的簇以下同风格股票值得关注。from sklearn.cluster import KMeans from sklearn.metrics.pairwise import cosine_similarity def build_recommendations(feature_df, target_stock, top_n5): # 方案一全局余弦相似度 sim_matrix cosine_similarity(feature_df) target_idx feature_df.index.get_loc(target_stock) sim_scores list(enumerate(sim_matrix[target_idx])) sim_scores sorted(sim_scores, keylambda x: x[1], reverseTrue) # 排除自身取前N个 top_similar [feature_df.index[i] for i, score in sim_scores[1:top_n1]] # 方案二聚类 簇内相似度 kmeans KMeans(n_clusters8, random_state42, n_init10) cluster_labels kmeans.fit_predict(feature_df.values) target_cluster cluster_labels[target_idx] cluster_indices [i for i, c in enumerate(cluster_labels) if c target_cluster] cluster_sim [(i, sim_matrix[target_idx][i]) for i in cluster_indices if i ! target_idx] cluster_sim sorted(cluster_sim, keylambda x: x[1], reverseTrue) top_in_cluster [feature_df.index[i] for i, _ in cluster_sim[:top_n]] return top_similar, top_in_clusterKMeans里的n_init10这个参数值得说明。sklearn新版里n_init默认是10但如果你不显式传参之前的版本会有FutureWarning提示。毕设代码里尽量不要出现warning答辩演示时控制台一堆黄色警告很减分。我所有模型参数都显式写出这是代码洁癖也是专业性的体现。4.4 推荐结果的解释与展示推荐系统做出来不难难的是让用户信。我做了两件事增强推荐结果的可信度一是推荐原因可解释。不直接甩出推荐XXXX、YYYY而是展示推荐依据相似度得分、聚类标签、特征对比表。比如贵州茅台与五粮液的特征相似度为0.92两者在动量因子和波动率因子上高度接近这种解释让推荐不再黑盒。二是相关性可视化。把目标股票和推荐股票的走势线画在同一张归一化折线图上用户肉眼就能看出确实走势像这是最有说服力的验证方式。推荐质量的验证我用了组内距离对比计算推荐列表内部的平均距离与全市场随机样本的平均距离如果推荐列表内部距离明显更小说明相似的确实聚在了一起。这个验证结果可以写进项目文档作为推荐效果的量化证据。5. 系统集成与前后端交互5.1 系统的整体架构前面几部分都是独立模块现在要把它们串成一个完整的系统。我的架构是经典的三层结构数据层、业务层、展示层。数据层负责拉取和缓存行情数据业务层负责指标计算和推荐逻辑展示层通过Flask提供HTTP接口并渲染HTML页面。数据在内存中以DataFrame形式流转模块之间用函数调用解耦没有引入消息队列或数据库对整个项目来说既简单又稳定。项目结构 stock_system/ ├── app.py # Flask主入口 ├── requirements.txt # 依赖清单 ├── data/ │ ├── loader.py # 数据采集akshare │ └── cleaner.py # 数据清洗与缓存 ├── analysis/ │ ├── indicators.py # 技术指标计算 │ └── features.py # 特征工程 ├── recommend/ │ ├── similarity.py # 相似度与聚类推荐 │ └── explain.py # 推荐解释生成 ├── visualization/ │ ├── charts.py # pyecharts图表封装 │ └── templates.py # HTML模板组装 ├── static/ │ ├── js/ │ └── css/ └── output/ └── charts/ # 生成的图表HTML文件这个结构看起来可能很普通但它有一个很实际的优点每一层都能单独运行和测试。比如我只想看看某只股票的K线图可以绕过Flask直接跑visualization/charts.py我只想验证推荐效果可以单独跑recommend/similarity.py。模块化设计不只是给老师看的更是给自己调试用的。5.2 后端接口设计后端我只设计了三个核心接口不搞花里胡哨的RESTful规范够用且好懂GET /首页展示推荐候选池和系统功能入口GET /stock/code个股详情页返回K线图、指标图和该股票的基础信息GET /api/recommend?codecodemethodsimilar|cluster推荐接口返回推荐股票列表、相似度得分和推荐原因用Flask实现这些接口非常快核心就几十行代码。这里分享一个我在细节处的处理图表产生的HTML不直接嵌入Flask模板而是先渲染成独立的HTML文件存到output目录Flask通过redirect或iframe引用。为什么这样因为pyecharts生成的HTML包含大量的JS和ECharts库引用直接塞进Jinja2模板会导致模板引擎解析冲突和性能问题。分开渲染互不干扰还能把图表文件作为答辩素材单独展示。from flask import Flask, render_template, jsonify, request from data.loader import get_stock_pool from recommend.similarity import build_recommendations from visualization.charts import render_stock_detail_chart app Flask(__name__) app.route(/) def index(): # 股票池列表用于前端下拉选择 stock_pool get_stock_pool() return render_template(index.html, stock_poolstock_pool) app.route(/stock/code) def stock_detail(code): chart_path render_stock_detail_chart(code) return render_template(stock_detail.html, chart_pathchart_path, stock_codecode) app.route(/api/recommend) def recommend_api(): code request.args.get(code) method request.args.get(method, similar) top_similar, top_in_cluster, explanation build_recommendations(code, methodmethod) return jsonify({ code: code, method: method, recommendations: top_similar if method similar else top_in_cluster, explanation: explanation }) if __name__ __main__: app.run(debugTrue, port5000)5.3 前端页面与交互体验前端我没有采用复杂框架就是原生HTML Bootstrap jQuery加少量ECharts的JavaScript。原因很朴素课程设计没必要引入Vue或React全家桶Bootstrap的栅格系统足够做出一个像样的操作界面。页面分为三个区域左侧是股票筛选栏行业下拉、股票搜索框中间是K线图和均线图的主体展示区右侧是推荐结果卡片区。交互逻辑是这样的用户先选股票中间区域异步加载K线图右侧同时刷新推荐列表点击推荐列表里的任意股票中间区域切换成这只股票的K线图右侧推荐列表也跟着联动更新。这种联动交互是答辩时的加分项。评委老师一般会问系统有没有交互性如果你能现场演示我点一下五粮液图表立刻切换推荐列表也更新效果远比你口头解释一堆架构概念更直观。实现上也不复杂Ajax请求后端接口拿到数据后用ECharts的setOption动态更新图表即可。需要注意的一个体验细节是加载状态。akshare拉取行情需要几秒钟如果不加loading提示用户会以为页面卡死了。我在前端统一加了Loading遮罩后端接口也尽量优先走CSV缓存缓存命中时响应基本在毫秒级。这个快和慢的对比体验直接影响答辩评委对系统好坏的判断。6. 常见问题排查与实操避坑实录6.1 环境配置阶段的坑Python环境配置是这类项目最常见的翻车现场我在帮学弟调试时至少踩过下面这些坑整理成一个速查表问题现象根本原因解决方案ModuleNotFoundError: No module named akshare未安装或安装到错误环境pip install akshare -i https://pypi.tuna.tsinghua.edu.cn/simple确认使用的是conda虚拟环境pyecharts导入报错ImportErrorpyecharts版本过新API变动锁版本pip install pyecharts2.0.0sklearn加载KMeans报AttributeErrorscikit-learn版本兼容问题pip install scikit-learn1.2.2matplotlib中文方块默认字体不含中文字符指定中文字体plt.rcParams[font.sans-serif] [SimHei]pandas中NaN导致图表断线指标计算前30行是NaN绘图前填充df.fillna(methodbfill, inplaceTrue)如果你是零基础新手建议在一开始就用Anaconda创建独立环境不要用系统Python直接装。我见过太多今天装一个包把之前的环境弄坏了的惨剧。独立环境即使烂了删掉重建也就十分钟的事。6.2 数据拉取和缓存的坑akshare虽然免费好用但它有个天然的问题接口返回格式会随上游网页改版而变动。有一天我运行代码突然报KeyError: 日期排查半天发现是akshare更新后列名变成了英文。解决方案有两个一是固定akshare版本在requirements.txt里锁定版本号——项目开发期间不要随意升级二是写代码时做列名兼容处理先检测列名类型再映射。另一个我强烈建议做的事是缓存机制。股票数据是历史数据不会变化根本没必要每次运行都重新拉取。我在data/loader.py里加了CSV缓存目录拉取成功后把DataFrame存成{code}.csv后续所有模块读取时优先读缓存。这样做有三个好处运行速度快、不触发akshare限流、答辩演示时不依赖网络环境。我在现场答辩时遇到过会场WiFi断掉的情况其他同学的系统白屏我的系统因为全部走缓存照常演示那种感觉非常爽。6.3 推荐结果不合理的排查思路第一个常见问题是推荐出高度相关的股票比如你查茅台推荐列表全是一堆银行股。这通常是特征工程的问题——某个特征的方差过大主导了整个相似度计算。我用PCA做了一次特征降维检查发现成交量对数特征的标准差远超其他特征归一化后还是有影响。最后我用的是先标准化再做cosine相似度效果明显改善。第二个问题是聚类结果不稳定。KMeans虽然简单但初始点随机每次运行标签都可能变化。我固定了random_state42保证结果可复现。答辩前一定要验证同样的输入每次推荐结果一致否则老师随机抽查时你前后两次说辞不一致会很尴尬。第三个问题是推荐的股票本身质量差比如推荐了长期停牌或ST的股票。我的解决方案是在推荐前先过滤股票池剔除ST、停牌超过60天、上市不满一年的标的。这个过滤逻辑放在数据清洗阶段统一处理业务代码里就不用每次重复判断。除这些问题外还有一个小提醒推荐结果一定要有数据支撑再下结论。项目里的推荐都是基于历史数据的统计相似不构成任何实际交易建议。我在系统页面底部加了一行本系统仅供学习研究使用不构成投资建议这个声明既是负责任的表现也是项目文档完善度的体现。7. 从毕设到实战的扩展方向如果你的项目做完还有余力或者你希望毕设拿更高的分数我提供几个经过思考的扩展方向引入实时数据。现在系统用的是日线历史数据可以加上akshare的实时行情接口让图表最后一点是今天的最新价格系统瞬间就有了动态感。操作上只需在数据加载时拼一段实时数据工作量很小。增加基本面因子。当前推荐系统的特征偏重价格和交易数据属于技术面。可以引入市盈率、市净率、ROE等基本面因子让推荐维度更丰富。akshare提供财务数据接口用特征拼接的方式融合技术面和基本面推荐的可解释性会大大增强。从Flask迁移到FastAPI。如果后续想做成真正的产品形态FastAPI的性能和自动API文档都更占优势。这个迁移成本非常低因为业务逻辑完全不变只是换个Web框架。推荐算法升级。当前用KMeans加余弦相似度属于机器学习的入门级方案。想提升技术深度可以尝试用深度学习模型如自编码器提取特征后再做相似度计算或者用图神经网络对一个股票关联图建模。这些都是值得写进论文未来展望章节的内容。我个人实际做下来的体会是这类课程设计项目跑通闭环的价值远大于堆砌技术。你用一个周末把数据、可视化、推荐、Web交互全串起来收获的不只是一份能过答辩的代码更是对一个软件系统怎么从零生长出来的完整认知。后续再遇到类似的项目无论数据源换成什么、算法换成什么你都能用这套架构成竹在胸地拆解和重建。这也是我为什么愿意把这套代码和思路整理成文章分享出来的原因——它真正解决了课程作业只能跑demo不知道还能做什么的困惑。