1. 为什么选这个组合:Django+Vue.js+爬虫+推荐,一条龙毕设的架构拆解
每年到毕设选题季,都有大量同学在"技术栈怎么选"上纠结一两个月。前端要好看、后端要能扛事、还得有亮点,最好还能塞进一点大数据元素——结果往往是贪多嚼不烂,最后做出来个四不像。我这个小说推荐系统项目选择 Django+Vue.js 的组合,不是头脑发热,而是把毕设的评分点拆开之后,一项项对上的。
先看后端。Django 自带 Admin 后台、ORM、认证体系和模板引擎,一个小说系统里最常见的用户注册登录、书籍管理、评论收藏这些功能,Django 几乎是开箱即用。它的 ORM 对新手极其友好,你不需要写原生 SQL 就能完成大部分增删改查,这在毕设时间紧张的情况下非常关键。而且 Django 的 ecosystem 成熟,用django-rest-framework写 API 接口,比 Flask 手撸序列化省一半功夫。
再看前端。Vue.js 的核心优势是响应式数据绑定和组件化开发。做小说推荐系统这种页面交互多的项目,左侧分类、中间推荐列表、右侧热榜,每个模块拆成独立组件,数据一变页面自动刷新,开发体验比 jQuery 时代舒服太多。搭配 Element UI 组件库,表格、表单、分页器、标签这些现成的样式直接拿来用,半小时就能把后台管理页面的框架搭出来。
至于小说爬虫和数据可视化,这俩是这个项目的"加分项"。很多同学的毕设就是简单的增删改查,做得再熟练也拿不到高分。爬虫模块证明了你有数据采集和清洗的能力,可视化大屏则直接展示了你的数据呈现能力,推荐算法更是给"大数据"这个关键词一个落地支撑。一套组合下来,开题报告、中期检查、答辩PPT都有内容可写。
1.1 前后端分离的项目骨架如何搭建
项目代码仓库建议直接分成三个目录:backend/、frontend/、spider/。这样在论文里写模块划分时也清楚,答辩时老师一眼就能看出你的工程化思维。
后端我用 Django 3.2 + Django REST Framework 搭建,Python 3.8 环境。创建虚拟环境后装依赖,然后新建一个名为novel_api的独立 App,专门负责提供 RESTful API,另一个userApp 管用户认证,novelApp 管小说数据,recommendApp 管推荐逻辑。这样划分的好处是后续写推荐算法时,不用在视图函数里堆一堆业务代码,直接调recommend.services里的函数就行。
前端用 Vue CLI 创建一个frontend项目,配好 Vue Router 和 Vuex(状态管理)。开发阶段前后端通过代理连接:在vue.config.js里配置devServer.proxy,把/api开头的请求转发到 Django 起的127.0.0.1:8000,这样就不会有跨域问题,本地联调非常顺。
// vue.config.js module.exports = { devServer: { proxy: { '/api': { target: 'http://127.0.0.1:8000', changeOrigin: true } } } }生产部署时再让 Nginx 统一代理前后端,不过毕设阶段跑通开发模式就够了,没必要折腾部署。
1.2 数据模型的合理设计
小说系统的核心数据表并不复杂,关键在于表之间的关系设计。我的数据模型分四块:用户表(Django 自带的User扩展)、小说表Book、分类表Category、用户行为表(收藏、阅读记录、评分)。推荐算法需要用到用户行为数据,所以Rate表是重中之重。
Book表的字段我建议这样设计:
title:书名,索引字段,爬虫下载后写入author:作者category:外键指向Category表intro:内容简介,推荐算法做文本相似度时会用word_count:字数,用于可视化统计status:连载状态(连载中/已完结)cover_url:封面图链接score:爬虫带回来的初始评分,后面会被用户评分修正
Rate表记录用户对小说的评分和互动,这是协同过滤算法的输入数据。字段包括user外键、book外键、rating整型(1-5分)、timestamp时间戳。光是这个表在后面扩展"基于贝叶斯平均的评分修正"时也很有用,捡了芝麻后面还能开西瓜。
不要小看这个数据库设计,推荐算法的效果好不好,七成取决于你有没有把用户行为数据存全、存干净。很多人做毕设推荐系统最后跑出来的结果离谱,根源就是行为数据表太简陋。
2. 小说数据怎么来:爬虫模块从设计到落地的完整链路
爬虫是这个项目最有意思的部分,也是最容易翻车的地方。我的目标非常明确:采集公开的小说元数据(书名、作者、简介、分类、字数、评分),用于填充数据库和做可视化。不碰VIP章节内容,不去破解任何付费/会员限制,只抓免费公开展示的信息。
2.1 目标站点分析与爬取策略
先确定目标站点,找那种结构清晰、反爬策略比较温和的网站。打开目标站点的分类页,按 F12 看网络请求,确认页面是服务端渲染还是 AJAX 动态加载。这一步直接决定你是用requests + BeautifulSoup解析 HTML,还是用requests直接请求 JSON 接口。
我当时的做法是:优先找 JSON 接口。原因很现实——解析 JSON 比解析 HTML 稳定得多,HTML 结构一变你的解析代码就废了,而 JSON 接口通常字段固定,维护成本低。比如很多小说站点的分类页,数据其实是通过一个/api/category/list接口返回的,直接伪造一个 User-Agent 请求就能拿到完整的书籍列表。
爬取策略分三层:
- 分类页列表数据:拿到每本书的详情页 URL 和基础信息。
- 详情页数据:逐个请求详情页,解析出简介、字数、状态等元数据。
- 增量更新:定期重新爬取,发现新书就 INSERT,发现字数变化就 UPDATE。
import requests from bs4 import BeautifulSoup HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://example.com/' } def get_book_detail(book_url): resp = requests.get(book_url, headers=HEADERS, timeout=10) resp.encoding = 'utf-8' soup = BeautifulSoup(resp.text, 'html.parser') # 以具体网站结构调整选择器 title = soup.select_one('h1').text.strip() intro = soup.select_one('.intro').text.strip() word_count = soup.select_one('.word_count').text.strip() return { 'title': title, 'intro': intro, 'word_count': word_count, }2.2 数据清洗与入库的细节
爬虫拿到的数据是脏的。数值字段经常是"123.45万字"这种带单位的中文字符串,评分可能是"8.7分",连载状态可能是"连载中(1234章)"。这些都要在入库前统一格式化。
我写了一个clean_data.py模块,专门干三件事:
- 类型转换:把"123.45万字"解析成纯数字
int(12345),单位统一为"千字"存库,方便后面可视化时做数值统计。 - 去重:按
title + author作为唯一键,重复的书跳过不插入。 - 空值处理:简介为空的书直接丢弃,没有评分的用网站的默认评分兜底。
清洗逻辑直接决定后面的统计分析能不能做。如果你想做"各分类小说平均字数对比"这种可视化,word_count字段是字符串根本没法计算,还得回头清洗,浪费时间。
2.3 爬虫的合规和反爬处理
这里必须说一句不太中听但很重要的话:爬虫项目的核心能力不在于"多能爬",而在于"知道什么不该爬"。毕设阶段,你的目的是展示数据采集和处理的完整链路,不是去做一个商业爬虫平台。所以我在代码里刻意控制爬取频率,每次请求间隔 3-5 秒,单日采集量控制在几千条以内,绝不并发轰炸目标站点。
反爬方面,我只做了最基础的伪装:随机 User-Agent、Referer 补齐、偶尔用代理池。对于需要登录才能看的内容一律不碰。这里要提醒一下,如果目标网站有robots.txt文件,建议先看一眼,尊重网站的爬取约定,这既是法律意识也是职业习惯。
2.4 爬下来的数据怎么验证
数据入库后不能直接开跑,必须有验证环节。我的做法是随机抽 50 条数据和源站人工比对,核对书名、作者、简介字段是否有偏差。另外用一个SELECT COUNT(*)查询检查每个分类的书籍数量分布,如果某个分类数量异常(比如玄幻类突然有 10 万本,其他分类只有 100 本),基本可以断定解析时分类字段匹配出了 bug。
这一步虽然繁琐,但能让你的项目"经得起答辩追问"。答辩老师经常会问:"你爬了多少数据?数据质量怎么保证?"你如果只回答"爬了 5000 本",那是送命题;如果你能补充一句"我抽了 50 条人工核对正确率 100%,并且每本书都有唯一键去重",基本的专业性就立住了。
3. 推荐系统实现:让"猜你喜欢"真正动起来的核心逻辑
推荐算法是整个项目里最值得写进论文的核心章节,也是拉开分数差距的地方。很多毕设项目写推荐系统,就是简单的"按评分排个序",那不叫推荐系统,叫排行榜。真正的推荐系统必须基于用户行为数据做个性化计算。
3.1 三种常见推荐方案的对比选型
我梳理了三种在毕设里最常见的方案:基于内容的推荐、协同过滤推荐、混合推荐。
基于内容推荐的关键是根据小说本身特征(分类、标签、简介关键词)计算相似度。优点是冷启动友好,新书也能被推荐;缺点是推荐结果同质化严重,翻来覆去都是同一类。
协同过滤推荐又分两种:基于用户的(UserCF)和基于物品的(ItemCF)。UserCF 是"和你兴趣相似的人也在看这本书",ItemCF 是"看了这本书的人还看了那些书"。毕设场景里我推荐优先做 ItemCF,原因是用户量通常不大,基于用户的相似度计算容易遇到"矩阵稀疏"问题,而 ItemCF 在物品数量固定的情况下计算更稳定。
考虑毕设的演示效果,我最终选择了混合推荐:基础排序用 ItemCF,冷启动用户用基于内容的推荐 + 热门榜兜底。这样无论老师在演示时创建新用户还是老用户登录,页面都不会空。
3.2 基于物品协同过滤的完整实现
ItemCF 的核心就两步:先建立"用户-物品"评分矩阵,再计算物品间的相似度。评分矩阵我从Rate表里查出来,存成一个二维结构,然后计算物品相似度矩阵。
物品相似度公式有很多种,我用的是余弦相似度的变体——皮尔逊相关系数。核心思路是:如果两本书被同一批用户喜欢过,那么它们之间的相似度就高。
import numpy as np from sklearn.metrics.pairwise import cosine_similarity def build_item_similarity(rate_matrix): # rate_matrix: 用户-物品评分矩阵,行为用户,列为物品 # 填充未评分项为 0,计算物品间余弦相似度 item_sim = cosine_similarity(rate_matrix.T) return item_sim def recommend_by_itemcf(user_id, top_n=10): # 1. 取出该用户的评分记录,找到已评分物品列表 # 2. 对每个已评分物品,取相似度最高的 K 个未读物品 # 3. 用相似度加权评分得到预测分,排序取 TopN pass这个算法的关键是我在注释里写的三步。实际实现时,加权预测分的计算公式长这样:
预测分 = SUM(物品i的相似物品j的相似度 * 用户对j的评分) / SUM(相似度)如果你用纯 Python for 循环遍历所有物品算相似度,数据量到 5000 本书时就会明显卡顿。这里的性能优化我用的是 NumPy 矩阵运算一次性算完,比循环快几百倍。这也是一个非常好的论文素材点——"基于矩阵分解的推荐性能优化"。
3.3 冷启动问题的处理策略
冷启动分两种情况:新用户和新书。新用户没有任何评分记录,协同过滤直接失效。我这里的做法是让前端在注册时勾选感兴趣的分类标签,后端根据标签从新书/高分书中抽取推荐列表,给用户"第一次点击"的机会。新书没有评分数据,就用爬虫携带的初始评分和历史热门数据兜底。
这个逻辑不复杂但很加分。答辩时老师问"如果数据库里只有 100 条评分记录怎么办",你能清晰说出冷启动处理方案,说明你真的思考过系统鲁棒性,这在毕设评分里是亮点。
3.4 推荐结果的评分与排序
推荐结果不能是随机堆给你的,必须有一个可解释的排序规则。我最终的排序策略是:
- 先按预测分数降序排列。
- 分数相同时,优先推荐评分人数多的书(说明是大众认可的)。
- 已经看过的书从结果集里排除。
预测分数我做了基于贝叶斯平均的修正处理,这是从热词里联想到的一个亮点。现实场景中,一本只有 1 个人打 10 分的书,不应该排在 100 个人打了 8.5 分的书前面。用贝叶斯平均公式把评分往平均分方向收缩:
修正评分 = (平均分 * 总评次数阈值 + 该书评分 * 该书评次数) / (总评次数阈值 + 该书评次数)这个公式在论文里写出来,导师一看就知道你做过功课,不是随便拿个算法凑数。而且在答辩现场你可以直接现场演示:把一本"1 人打分 10 分"的书和一本"300 人打分 8.9 分"的书放进推荐池,看排序结果的变化,非常有说服力。
4. 小说可视化大屏:从数据表到一看就懂的可视化面板
可视化是本项目的门面,也是"大数据"这个关键词最直观的体现。小说推荐系统本身是一个功能闭环,但你要在毕设答辩的几分钟里让评委看懂"你的数据有什么价值",可视化大屏是最省力气的表达方式。
4.1 可视化选型:ECharts 还是其他方案
可视化方案我对比了几种:直接写原生 Canvas/SVG(开发量大)、Highcharts(商用需授权)、D3.js(学习曲线陡峭)、ECharts(开源免费、中文文档全)。最终的结论是 ECharts,没有悬念。
ECharts 对毕设项目的优势非常明显:它内置了十几种图表类型,折线图、柱状图、饼图、雷达图、词云都有现成模板;响应式适配做得好,电脑和投影仪上展示都不变形;社区资源多,你搜任何图表类型基本都能找到完整 Demo。
4.2 核心图表的实现细节
我的可视化大屏设计了六个图表模块,每个模块对应一种分析维度:
| 图表类型 | 分析维度 | 数据口径 |
|---|---|---|
| 柱状图 | 各分类小说数量分布 | GROUP BY category |
| 折线图 | 近30天新书入库趋势 | 按发布时间统计 |
| 饼图 | 连载状态占比 | 连载中/已完结 |
| 词云 | 小说标签高频词 | 从标签表提取 |
| 雷达图 | 各分类平均字数/评分对比 | 多维统计 |
| 排行榜 | 综合评分 Top10 小说 | 贝叶斯修正后排名 |
具体实现时不需要写一堆 ECharts 配置项,把它封装成一个visualize.js工具函数,统一从后端 API 拉数据,再配置不同的option对象。
// 饼图:连载状态占比 const pieOption = { tooltip: { trigger: 'item' }, series: [{ type: 'pie', radius: ['40%', '70%'], data: pieData // 从 /api/stats/status 获取 }] };数据对接方面,我在 Django 后端写了一个统计类视图,直接用 ORM 的annotate和aggregate做聚合查询,返回 JSON 给前端。这里的核心技巧是:任何可视化图表的数据都应该由后端聚合好再返回,而不是把全表数据丢给前端去算。你见过那种打开大屏页面加载 5 秒以上的毕设项目吗?基本都是因为后端把所有数据返回了,前端用 JS 挨个算,慢得离谱。
4.3 大屏数据对接 API 的优化技巧
这里有一个实际优化过的细节:ECharts 的词云图(wordCloud)组件对数据格式有苛刻要求,标签字段必须是name和value。但我后端返回的字段叫label和count,前端如果不做映射,图表直接空白。我统一封装了一个transformToWordCloud(data)函数做字段转换,并把加载失败的空状态提示也做好了,演示时不至于翻车。
另外一个经验:所有图表的数据请求用Promise.all并发发出去,然后统一渲染。如果你一个个请求串行执行,四个图表的初始化时延加起来可能超过 2 秒,现场演示非常尴尬。
5. 大数据视角:这个毕设里的"大数据"体现在哪
说实话,一个小说推荐系统动辄几万条数据,离真正的大数据量级还很远。但毕设题目里写了"大数据",你就必须向这个方向靠拢,至少要有"大数据思维"的体现。我的理解是:不在于数据量多大,而在于你是否用了处理大数据的思路去设计系统。
5.1 数据量级的真实情况与架构应对
我当时爬了大约 8000 本小说元数据,用户行为表有 2 万多条评分记录。这个量级 MySQL 轻轻松松就能扛住,但你写论文时不能只写"用 MySQL 存储",要描述你的架构能支持多少量级的数据增长,以及当前设计的瓶颈在哪里。
我论文里的表述方式是:"系统当前基于单机 MySQL 和 Django ORM 实现,在万级数据量下性能良好;当数据规模突破百万条时,推荐计算模块可无缝切换到 Spark 进行批处理,ORM 层替换为 Hive/Spark SQL 查询。"这种表述的高明之处在于:你坦然承认当前实现是单体架构,但已有的抽象接口(推荐算法独立成 service 模块、数据访问层全部走 ORM)具备替换为大数据组件的可能性。
5.2 推荐计算中的性能优化
前面提到 ItemCF 的相似度计算用 NumPy 矩阵运算,这个优化思路就是大数据领域的核心理念——把计算从循环迭代转为矩阵并行操作。我实测在 8000 本书的数据规模下,原生 Python 循环算相似度需要 40 多秒,改成 NumPy 后 2 秒内出结果,这个性能对比数据建议写进论文的实验章节,非常有说服力。
另一个性能优化是热门推荐结果缓存。每天的推荐榜用户请求量大,但计算结果变化缓慢,我用 Django Cache 框架把 Top100 推荐结果缓存 30 分钟,把推荐接口的响应时间从 700ms 降到了 40ms 左右。这也是大数据处理中典型的"计算向存储转移"策略。
5.3 如果想让大数据成分更浓,可以从这些方向扩展
如果你的导师对"大数据"的要求更严格,可以考虑这几个扩展方向:
- 数据采集层升级:用 Scrapy 分布式爬虫替代简单 requests 脚本,支持多节点协同采集。论文里写"基于 Scrapy-Redis 的分布式爬虫设计"。
- 存储层升级:把用户行为数据从 MySQL 迁移到 HBase 或 ClickHouse,用列式存储承载高并发写入。
- 计算层升级:用 Spark MLlib 里的 ALS 交替最小二乘算法替代自写的 ItemCF,用 PySpark 处理推荐模型的训练。
- 调度层升级:用 Airflow 编排每日爬虫任务、数据清洗任务和推荐模型更新任务。
这些扩展不一定要全做,哪怕只做一个"实验性演示",比如用 PySpark 在本地跑通一个 5000 条数据的 ALS 训练过程,论文里就可以理直气壮地写"系统支持 Spark 计算引擎,本文实现了 ItemCF 与 ALS 双路推荐"。
6. 毕设避坑清单:从开发到答辩经常翻车的点
这部分是血泪经验总结。我在整个项目开发过程中踩了不少坑,有些坑真的是写代码前根本想不到的,这里一个个列出来,给你省点时间。
6.1 前后端联调中最常见的5个坑
第一个坑是Django 的 CORS 跨域。本地开发时前端在 localhost:8080,后端在 localhost:8000,直接 AJAX 请求必然跨域。解决办法是在后端安装django-cors-headers,在settings.py里配置CORS_ALLOW_ALL_ORIGINS = True(仅限开发环境)。如果用了代理就不需要这个,但保险起见全配上。
第二个坑是Vue 的history模式刷新后 404。Vue Router 默认使用hash模式,如果你改成history模式,开发服务器刷新页面时可能 404。简单做法是用默认的 hash 模式,虽然 URL 上有#符号,但毕设不要求优雅 URL,别在这上面浪费时间。
第三个坑是Django 的 STATICFILES。把封面图、前端静态资源部署到 Django 时,static文件路径经常配错导致图片不显示。我踩过很深的一次坑就是,vscode里写的 img 标签引用的图片在浏览器里死活加载不出来,排查半天发现是 STATIC_URL 配置不对。这个问题的解决办法是:写模板时使用{% static 'images/xxx.jpg' %}标签,不要手写硬编码路径。
第四个坑是时间格式序列化。Django DateTimeField 返回的是datetime对象,直接 JSON 序列化会报错。Django REST Framework 默认能处理,但如果你手写 JsonResponse 就会翻车。我统一用 DRF 的 Serializer 处理所有 API 输出,规避了这个坑。
第五个坑是Vue 组件里的this指向。在methods里写setTimeout回调或者axios.then回调时,this指向会丢失,拿不到this.books。解决办法是回调函数用箭头函数写,或者在外部缓存const that = this。这个问题新手必踩,答辩现场调试时会很尴尬,提前写好就行。
6.2 论文和文档的写作重点
很多同学代码写完了,论文憋不出来,其实是没掌握结构。我的论文目录大致是:
- 第一章 绪论(背景、意义、国内外研究现状)
- 第二章 相关技术介绍(Django、Vue.js、ECharts、推荐算法原理)
- 第三章 需求分析与系统设计(用例图、架构图、数据库设计)
- 第四章 系统实现(爬虫模块、推荐模块、可视化模块、前后端实现)
- 第五章 系统测试(功能测试、性能测试、推荐效果评估)
- 第六章 总结与展望
重点发力在第四章,每实现一个模块都配一张截图和技术说明。推荐算法的公式推导更是重中之重,皮尔逊相关系数公式和贝叶斯平均公式必须完整呈现,别只写"用了协同过滤算法"一句话。LW 文档(也就是毕设配套的说明文档)内容就是从论文精简来的设计说明书,重点写数据库表结构和接口设计。
6.3 答辩演示的注意事项
答辩演示时切记三件事:
第一,准备一个干净的数据集。不要用爬虫原始数据做演示,里面可能有乱码、错别字、封面缺失等意外情况。我专门造了一个类别分布均衡、评分数据量充足的演示数据集,保证推荐结果和可视化图表都是"好看"的状态。
第二,提前录好演示视频备用。现场演示的最大风险是:投影仪分辨率不兼容、网络不通、数据库连不上。我提前用 OBS 把完整的系统演示录制成 10 分钟视频,现场如果环境出问题就说"我准备了演示视频,先看下实际效果",反而显得准备充分。
第三,准备好三个深度问题的答案。老师最可能问的问题我总结成了三个:推荐算法为什么选 ItemCF 不选 UserCF?(答数据稀疏性和计算稳定性);爬虫采集的合规性怎么保证?(答只采集公开数据、控制频率、尊重 robots.txt);系统如何扩展支持更大数据量?(答接口抽象 + Spark 替换方案)。把这三个问题的答案写熟,答辩环节基本就稳了。
7. 项目目录结构与源码组织建议
最后分享一个非常实用但大多数同学会忽视的点:源码的目录组织方式。很多人的毕设代码打开一团乱麻,models.py 里写 500 行,views.py 里塞了一整页逻辑,答辩老师打开 GitHub 仓库第一眼就印象分暴跌。
我的目录结构是这样的:
novel-recommend-system/ ├── backend/ # Django 后端 │ ├── novel_api/ # 主工程配置 │ ├── user/ # 用户模块 │ ├── novel/ # 小说模块 │ ├── recommend/ # 推荐模块 │ ├── stats/ # 可视化统计模块 │ ├── spider/ # 爬虫模块 │ └── requirements.txt ├── frontend/ # Vue 前端 │ ├── src/ │ │ ├── views/ # 页面组件 │ │ ├── components/ # 通用组件 │ │ ├── api/ # 接口封装 │ │ └── router/ # 路由配置 ├── docs/ # 论文、LW文档、PPT ├── data/ # 爬虫数据备份 └── README.mdREADME.md 不是摆设,我用 20 分钟写了一个完整的项目启动说明:环境版本、安装步骤、初始化命令、测试账号。这个文件在答辩时直接展示给导师看,能极大提升"项目完整性"的印象。很多同学觉得 README 是开源项目才需要的,毕设不用写,但实际体验下来,写一个清晰的 README 会让导师对你的工程素养刮目相看,这比答辩时多吹两句代码质量有效得多。
启动步骤记得写清顺序:
# 1. 创建并激活 Python 虚拟环境 python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate # 2. 安装后端依赖 pip install -r requirements.txt # 3. 数据库迁移与初始化 python manage.py makemigrations python manage.py migrate python manage.py init_data # 导入爬虫数据 # 4. 启动后端服务 python manage.py runserver # 5. 启动前端(另开终端) cd frontend npm install npm run serve依赖版本锁定是另一个容易忽略的点。Django 3.2 和 Django 4.2 的某些 API 有区别,Vue 2 和 Vue 3 的语法差异更大。如果requirements.txt不锁版本,你换台电脑跑项目时可能装到最新版,然后各种报错,在上交代码前两天心态直接崩掉。我当时锁的是Django==3.2.18、djangorestframework==3.14.0、vue@2.6.14,这套组合现在依然能完美运行。
个人实际体会是:小说推荐系统这个毕设选题,难度不低但天花板也不低。爬虫、推荐、可视化三个模块单独拎出来任何一个都可以写一篇小论文,组合在一起就是一个"技术栈完整、有算法深度、有可视化呈现"的三位一体项目。最关键的是,这套技术栈和对问题的处理思路(冷启动、数据清洗、性能优化、贝叶斯修正)放到真实工作中也是通用的底层能力。你做完这个项目,不只是完成了一项毕业任务,实际上是走通了一条"原始数据—加工处理—算法建模—可视化呈现"的完整数据流水线,这种思维方式在后续的工作或考研项目中都会持续复用。