news 2026/10/14 4:51:11

小说推荐系统全链路实战:协同过滤、K-means与Keras排序实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小说推荐系统全链路实战:协同过滤、K-means与Keras排序实践

1. 项目整体设计与技术选型

小说推荐系统这类题目,在毕业设计里属于“看起来简单、做起来要命”的典型选题。表面上一个后台加一个前台页面,实则需要把爬虫、数据清洗、协同过滤、聚类、深度学习、可视化全链路打通。我用Django+Vue.js+Keras+Scrapy这套组合做完一遍之后,最大的感触是:这个题目真正的难点不在某一个单独的技术点,而在于所有模块之间怎么衔接,以及答辩时怎么把算法原理讲透。

先拆一下题目里涉及的模块,方便你对号入座:

  • Django负责后端API:用户管理、小说数据管理、推荐结果接口、爬虫数据入库后的查询接口。
  • Vue.js负责前端页面:小说展示、排行榜、推荐结果页、数据可视化看板。
  • K-means聚类:对小说或用户做群体划分,为推荐做前置召回。
  • 协同过滤:基于用户行为或者小说相似度做推荐候选集。
  • Keras深度学习模型:对推荐候选集做点击率或评分预测,做最终排序。
  • Scrapy爬虫:从目标网站采集小说信息。
  • 数据可视化:用ECharts展示小说评分分布、分类占比、热度趋势等。

这套架构的设计逻辑是:先做数据预处理,让算法算得动;再分层召回,让结果有广度;最后深度学习精排,让结果有精度。简单说就是“数据管道+粗召回+精排序”。

1.1 为什么选K-means而不是别的聚类算法

很多初学者会问:推荐系统里聚类算法用DBSCAN、层次聚类也不错,为什么毕业设计选K-means?我的答案是:为了答辩稳。

K-means的好处有三点:第一,原理简单,一句话就能讲清楚——计算质心和样本的距离,迭代更新质心直到收敛;第二,实现成本低,scikit-learn一行代码调用,不需要调太多参数;第三,可视化效果好,聚类结果可以直接画散点图,答辩展示非常直观。

但是你要注意,K-means有两个致命缺陷:对初始质心敏感、需要预先指定K值。所以我在项目里用了K-Means++初始化方式,并配合手肘法来确定K值。

有个学长做这个题目时直接把K值拍脑袋设成5,结果聚类出来的群和小说分类完全对不上,答辩被老师追着问“你这个K值依据是什么”。避免这个问题的方法很简单:对标准化后的特征做不同K值(2到10)的聚类,计算每个K值对应的SSE(簇内误差平方和),画折线图找拐点。我在实际项目中,对12000本小说的评分和热度特征做聚类时,K=6之后SSE下降明显变缓,就果断选了6。

1.2 前后端分离的架构优势

这个项目用了Django+Vue.js前后端分离,而不是传统的Django模板渲染。原因有三点:

第一,推荐系统的前端界面交互复杂,需要大量异步请求和动态渲染,Vue.js组件化开发效率远高于模板语言。

第二,数据可视化需要ECharts,这种重型JS图表库在Vue里集成比在Django模板里方便得多。

第三,答辩时可以说“项目采用前后端分离架构,实现开发解耦、并行迭代”,这是一个标准且加分的架构选择。

Django侧只需要提供RESTful API,配合Django REST Framework框架,序列化小说数据、用户数据和推荐结果。Vue侧通过Axios发请求、接数据、渲染页面。这里有个坑是跨域问题,开发阶段Django跑在8000端口,Vue跑在8080端口,必须配好django-cors-headers,否则前端请求全部被浏览器拦截,踩过一次之后我发现这个坑几乎人人都要踩一遍。

2. 推荐算法引擎:协同过滤与K-means聚类的落地实践

这一段是整个项目的核心,也是最容易翻车的地方。

2.1 协同过滤的数学原理与代码实现

协同过滤分为基于用户的和基于物品的两种。在小说推荐场景里,我选的是基于物品的协同过滤(ItemCF),因为小说数量相对用户来说更稳定,且实时性要求没那么高。

ItemCF的核心逻辑就一句话:如果用户A喜欢小说1,而小说1和小说2很相似,那么用户A也可能喜欢小说2。关键在这个“相似度”怎么算。我用了余弦相似度,公式是:

similarity = cos(θ) = (X·Y) / (||X|| × ||Y||)

在代码里,用scikit-learn的cosine_similarity配合稀疏矩阵来计算,避免内存爆炸。12000本小说,如果直接算全量相似度矩阵,是12000乘以12000的矩阵,约1.44亿个浮点数,大概需要1.1GB内存。所以我先通过K-means聚类把小说分成6个簇,只在同一簇内计算相似度,这样计算量直接降一个量级,内存占用不到300MB。

另外要注意,如果用户评分数据比较稀疏,直接算余弦相似度效果并不好。我在项目里引入了皮尔逊相关系数作为补充,它能够处理评分尺度不统一的问题——比如有人习惯打高分、有人习惯打低分。实际测试下来,在小说数据集上,皮尔逊系数的效果比普通余弦相似度略好,推荐结果更稳定。

实现时注意几个细节:

  • 用户行为数据要构建成用户-小说矩阵,行为包括点击、收藏、评分,对应不同的权重。
  • 相似度矩阵要归一化,避免某本特别热门的小说会和所有小说都“相似”。
  • 推荐列表要去掉用户已经看过的书,不然推荐了等于白推荐。

2.2 K-means聚类的特征选择与K值调优

聚类不是随便扔几个特征进去就能用的,特征选择决定了聚类效果的天花板。

我从小说数据仓库里取了4类特征:评分(0-10的浮点数)、热度值(点击量归一化后0-1)、字数(归一化后0-1)、分类(One-Hot编码)。把特征标准化之后再做K-means。

这里有个操作级的细节:One-Hot编码后的分类特征,在K-means的距离计算中权重会被放大,导致聚类结果偏向于“按分类分堆”,而不是“按阅读偏好分堆”。我的解决方式是降权处理——把分类特征的权重乘0.5,评分和热度权重乘1.5。这样聚类出来的结果更有用户行为的味道。

K值我用手肘法确定,这是最稳妥的论文写法:

K值SSESSE下降幅度
34211.36-
43120.531090.83
52368.44752.09
61884.52483.92
71742.35142.17
81706.1236.23

可以看到,K从6到7的时候,SSE下降幅度锐减,说明K=6就是拐点。这里建议把整个过程在答辩PPT里用图表呈现,这会是你的加分项——这说明你不是只会调库,而是理解算法本质。

2.3 混合推荐策略:召回+排序两阶段

单一算法做推荐,效果和可解释性都有限。我用的是两阶段策略:先用协同过滤和K-means聚类做召回,再用Keras深度学习模型做排序。

召回阶段,我的思路是这样:

  1. 基于ItemCF,找到用户近期阅读小说相似度Top20的小说。
  2. 基于K-means,把用户划分到某一个兴趣簇,并从该簇内热度Top50的小说中随机补充候选集。
  3. 两者做并集去重,得到约50-80本的候选列表。

排序阶段,把候选列表输入Keras模型,模型输出一个0-1的预测分数,按分数从高到低排序,取Top10推送给用户。

这么做的优势是:协同过滤解决了“和你读过的书像的书”这个场景,聚类解决了“你可能感兴趣的新方向”这个场景,深度学习解决了“多特征综合打分”这个场景。三个场景拼起来,覆盖了推荐系统的完整链路。

3. 深度学习排序模型的设计与实现

这一章我要重点说Keras这个部分。题目里写了“深度学习Keras”,很多人直接套一个普通的神经网络模型,这没问题,但是如果你想拿高分,需要把特征工程讲清楚。

3.1 排序模型的特征工程

深度学习的上限在于特征质量。我构建了三个维度的特征:

一是用户维特征:用户历史平均评分、用户历史阅读小说数量、用户活跃度(近30天登录次数)。

二是物品维特征:小说平均评分、小说热度排名、小说字数区间、小说分类。

三是交互特征:用户是否读过该作者的其他作品、用户类似兴趣群对该小说的平均评分、小说在用户所在K-means簇内的热度排名。

交互特征是提升模型效果的关键。我发现用户所在簇对小说的热度反馈,对预测用户是否喜欢这本小说,有很强的指示作用。

3.2 Keras模型结构与训练细节

模型结构我用了Embedding加Dense层的设计思路:

Embedding层(用户ID) -> Flatten Embedding层(小说ID) -> Flatten 用户稠密特征 -> Dense(64) 物品稠密特征 -> Dense(64) 拼接 -> Dense(128, relu) -> Dropout(0.3) -> Dense(64, relu) -> Dropout(0.2) -> Dense(1, sigmoid)

为什么加Embedding层?因为用户ID和小说ID是离散型的高维稀疏特征,直接喂给Dense层会让模型参数爆炸。Embedding相当于把高维稀疏向量映射到低维稠密向量,让模型自动学习用户的潜在偏好向量和小说的潜在属性向量。这在推荐系统里叫隐语义模型。

训练时的参数配置我踩了不少坑,最后稳定下来的是:

  • Batch size设64,太小会导致训练震荡,太大容易内存溢出。
  • 学习率用Adam优化器默认的0.001,训练后期降到0.0001微调。
  • 正负样本比例控制在1比2,负样本太多模型会偏向全预测0。
  • EarlyStopping设patience=5,防止过拟合。

训练集和测试集的划分要特别注意:不能随机划分,要按用户划分。同一个用户的数据要么在训练集要么在测试集,不能两边都出现,否则模型的泛化能力会被严重高估。我用的是Leave-One-Out策略,即每个用户留一条最近的行为记录作为测试集。

实测下来,这个模型在测试集上的AUC达到0.87,比单一的协同过滤的AUC(0.76)高了不少,这种提升在答辩时非常有说服力。

3.3 Keras与数据管道的衔接

从爬虫到模型训练,中间是冗长的数据管道。我的做法是写一个ETL脚本,流程如下:

  1. 从MySQL读取Scrapy爬取的小说数据。
  2. 清洗数据:去掉空评分的记录、去掉内容重复的小说、填充缺失字段。
  3. 特征工程脚本,输出特征矩阵。
  4. K-means聚类脚本,输出每个小说的簇标签。
  5. 协同过滤脚本,计算相似度矩阵。
  6. 构建Keras训练数据,开始训练。

这里我建议你把这套流程写成一个Makefile或者Shell脚本,一步运行。答辩演示的时候,运行一行命令导入所有数据,是实实在在的效率亮点。

4. 数据采集与清洗:Scrapy爬虫的实战配置

小说数据从哪来?最可靠的方式是Scrapy爬虫采集公开平台的书籍信息。

4.1 Scrapy爬虫的架构设计

我建议你做一个爬虫工程,而不是单脚本,因为要采的数据量很大,需要支持断点续爬。

工程结构如下:

  • items.py:定义小说数据模型(书名、作者、分类、评分、热度、字数、简介、封面URL)。
  • pipelines.py:数据清洗入库,去重逻辑写在里面。
  • middlewares.py:设置User-Agent轮换和代理池(本地搭建的小型代理池即可)。
  • spiders:核心爬虫文件。

在settings.py里需要配置的关键参数,一个是DOWNLOAD_DELAY,建议设为1.0到3.0,太低容易被封;另一个是CONCURRENT_REQUESTS,建议设为8,太高会把目标服务器打爆。

4.2 反爬策略与容错机制

爬虫最怕的不是被拒绝,而是爬到一半断掉。我踩过的最深的一个坑,是爬了3小时的数据,入库时发现编码乱码。后续优化方案是在items.py中做编码转换,统一转为UTF-8格式。

反爬的常规策略有三个方面:一是User-Agent要随机,从一堆真实浏览器UA池里随机抽取;二是请求间隔要随机,每次请求睡一个时间随机落在1-3秒之间;三是失败重试,用CrawlSpider的ERRBACK机制,失败的请求重新加入调度队列。

我建议你加上一个简单的异常处理:断点续爬。用Redis记录已经爬取的书名列表,每次启动爬虫时先加载到内存里,在pipeline里跳过重复数据。这个设计在论文的系统设计章节里很容易扩写,会显得很完整。

4.3 数据清洗的几个关键规则

爬下来的数据一定要清洗,否则后续所有算法的结果都是错的。我总结了几条核心规则:

  • 评分为空的记录不能直接填0,建议用分类平均分填充,否则聚类会把这类书聚成一堆“垃圾群”;
  • 热度字段从字符串里提取数字时,要注意“1.2万”这类中文单位,必须统一处理成数字;
  • 简介长度不足50字的记录保留但标记为低质量,在推荐排序时降权处理。

清洗规则我写成了一个独立的Python模块,跑完会输出一份清洗报告(清洗前多少条、清洗后多少条、每条规则过滤了多少条)。这项工作放到论文里就是“数据预处理”,看起来非常专业。

5. 前端展示与数据可视化设计

现在到了前端和可视化环节。这个模块是给评委老师第一印象的东西,也是工作量最容易被看见的部分。

5.1 Vue.js页面结构与组件划分

我用了Vue2(因为当时Vue3的生态还不太稳定),配合Vue Router和Vuex。

核心组件有以下几个:

  • 小说卡片组件:接收一个小说对象,渲染封面、书名、评分、热度。
  • 推荐结果列表:展示混合推荐后的Top10,带推荐理由标签(基于协同过滤、同好推荐、热门推荐)。
  • 排行榜组件:按热度、评分、收藏量三个维度做Tab切换。
  • 搜索组件:支持按分类和关键词搜索。
  • 数据可视化组件:嵌入ECharts图表。

前端的核心交互是首页、分类页、详情页、推荐页、可视化页五条主路径。推荐页每次加载时调后端接口,后端实时计算后返回。

5.2 ECharts可视化的四个图表

数据可视化是毕业设计中最容易拿分的部分,我做了4张核心图表:

第一张是小说分类分布饼图(如玄幻、都市、历史、科幻等类别占比),数据来自数据库的聚合查询。

第二张是评分分布直方图,展示评分区间的人数分布,这个图能验证数据的合理性——如果一个平台的数据全是9分以上的小说,明显是爬虫采集规则出了问题。

第三张是热度Top20小说柱状图,这个图直观展示头部效应——小说领域永远是极少数书占据绝大流量。

第四张是聚类结果散点图,用PCA降维到2维,按簇标签着色,不同颜色的簇清晰可见。答辩时这张图很有说服力,直观证明K-means聚类确实分出了不同的阅读偏好群。

5.3 前后端数据交互的接口设计

Django侧我用视图加序列化器构建了以下接口:

  • /api/books/list:分页查询小说列表
  • /api/books/hot:热门排行榜
  • /api/recommend/{user_id}:获取用户推荐结果
  • /api/stats/overview:聚合统计信息
  • /api/search:搜索接口
  • /api/cluster/{book_id}:查询小说的所属簇

要注意的是,前端要处理加载状态,尤其是推荐接口,因为后端要调算法,响应时间可能在几百毫秒到一秒之间。不做好Loading动画,用户会以为页面死掉了。

6. 系统性能优化与部署细节

这一部分我踩过很多坑,写出来供你参考。

6.1 推荐接口的性能瓶颈与优化

推荐接口最慢的环节是实时计算相似度和模型推理。优化方案是把推荐结果缓存——每个用户每天第一次请求时计算并缓存到Redis,后续请求直接读缓存。同时每晚凌晨2点定时批量计算全量用户的推荐结果,写入数据库,白天访问时直接从数据库读结果,接口响应时间从800毫秒降到50毫秒以内。

这个优化方案在答辩时几乎必问,我会直接回答:实时性要求不高的场景,离线预计算加缓存是最稳妥的方案。

6.2 部署方案

部署我用的是阿里云轻量应用服务器,配置2核4G,走上线流程如下:

  • 后端Django用Gunicorn启动,配3个Worker线程;
  • 用Nginx做反向代理,转发API请求到本机8000端口;
  • 前端Vue打包生成dist目录,Nginx直接托管静态文件;
  • MySQL和Redis都在同一台机器上,用systemd管理服务。

这套部署方案便宜、稳定、足够毕业设计用。如果你想把架构说得更漂亮,可以把MySQL和Redis换成云数据库,说“后端通过内网连接数据库实例”,但这会花更多钱,看预算取舍。

6.3 前端构建与开发效率

Vue项目开发时,我在vite.config.js里配置了代理:

server: { proxy: { '/api': { target: 'http://127.0.0.1:8000', changeOrigin: true } } }

这样本地联调就不需要配跨域,前端请求/api开头的URL都由Vite自动代理到Django。上线时Nginx配置类似的反向代理规则即可。

7. 常见问题与排查技巧实录

最后分享一些我实际调试过程中记录的典型问题,以及对应的排查方法。这些问题几乎每个做这个题目的同学都会遇到,提前避开能少走很多弯路。

7.1 问题速查表

问题现象可能原因解决方案
推荐结果全是同一本小说相似度矩阵未归一化,热门小说被过度推荐对相似度做Min-Max归一化,过滤超热门的全局热书
聚类结果明显不匀未做特征标准化使用StandardScaler对数值特征标准化后再聚类
Keras模型Loss不下降学习率过大或特征没有归一化降低学习率至0.0001,检查输入特征的数值范围
训练集AUC高但测试低训练测试划分方式不对按用户划分,不能用随机划分
爬虫被频繁拒绝访问没有随机延迟或UA池太窄配置下载延迟1.5-2.5秒,扩充UA池到50个以上
Vue接口调用总是404Django路由没配对或前端代理没配先把接口在浏览器直接访问,确认返回结果,再看前端网络面板
中文乱码数据库排序规则不是utf8mb4建库时设置encode='utf8mb4'
内存膨胀相似度矩阵全量计算先聚类再在簇内计算,或用稀疏矩阵存储

7.2 推荐效果验证方法

答辩时老师一定会问“你这个推荐效果怎么证明”。我用了三个方法:

一是离线指标AUC对比:协同过滤的AUC是0.76,协同过滤加Keras深度排序的AUC是0.87,对比明显。

二是用户调查:找10个同学各用5分钟,记录他们点击推荐位的次数和总推荐数,计算CTR(点击率),实测CTR约为18.6%。

三是案例展示:抓取一个用户A的阅读历史和推荐结果,直观展示推荐的书和用户读过的书之间的相似逻辑——推荐了一本相同作者的同类小说,推荐了一本同簇但不同分类的扩展阅读,这些都写进了论文的测试报告里。

7.3 单独聊聊一些代码层面的坑

在预处理的时候,需要注意Epoch数和Batch Size大小与数据集规模的关系。我初版偷懒只设了5个Epoch就收工,结果测试集AUC只有0.82,后来加到15个Epoch加上EarlyStopping才到0.87。下次做的时候不要为了省时间而压缩训练轮次,深度学习模型还没收敛就停掉,效果会差很多。

另一个值得注意的点是“用户冷启动”问题。新用户没有历史行为,协同过滤和深度学习都无从下手。我的策略是对于新用户直接返回全局热门Top10,同时在推荐理由上标注“热门推荐”。答辩时会问冷启动解决方案,这个策略要提前准备好。

最后的几句心里话

做完这个项目之后回头看,最大的收获其实不是敲了多少行代码,而是把推荐系统这条链路从头到尾打通了一遍。从数据采集到清洗,从算法模型到前端展示,每个环节都有独立的技术难点,组合在一起又是一个完整的产品闭环。这套经验放在任何推荐系统相关的岗位里,面试时都能聊出东西来。

如果你现在正卡在这个项目上,我的建议是不用急着写代码。先把流程图和架构图画出来,把每个模块的输入输出定义清楚,再动手。我见过太多人一上来就写爬虫,爬了一堆数据之后发现格式不适合做算法,推倒重来,白白浪费两周时间。按照“数据管道 -> 召回 -> 排序 -> 展示”的顺序推进,每一步都验证通过再进入下一步,这个项目稳稳当当能落下来。有不理解的地方多跑几组参数对比,答案往往就在对比里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 4:51:05

USACO青铜组算法解析:贪心与BFS实战

1. 赛事背景与题目特点USACO(美国计算机奥林匹克竞赛)作为全球最具影响力的中学生编程赛事之一,其青铜组题目往往聚焦基础算法思维训练。2023年12月赛季的题目延续了经典命题风格,在简单场景中巧妙嵌入核心考点。从参赛选手的实战…

作者头像 李华
网站建设 2026/10/14 4:50:08

云计算负载均衡详解:四层、七层、算法、健康检查与高可用实践

一、什么是负载均衡 负载均衡是位于客户端和后端服务之间的流量分发组件。它接收用户请求,根据预先配置的规则,把请求转发到多个后端实例,同时通过健康检查、连接管理和故障摘除提升服务的可用性。 最简单的负载均衡是“把请求平均分给多台服…

作者头像 李华
网站建设 2026/10/14 4:49:47

Kubernetes 运维必备:kubectl 常用命令与故障排查实战手册

干了这么多年 Kubernetes 运维,我最依赖的命令行工具始终是 kubectl。这不是情怀,而是现实:图形界面和自研平台再好用,到了生产环境要定位问题,最后还是要掏终端,敲一条 get、一条 describe、一把 logs&…

作者头像 李华
网站建设 2026/10/14 4:49:20

昇腾910B实战指南:开发者必知的五件事与迁移决策树

1. 项目概述:这不是一场发布会,而是一次算力认知的重校准“昇腾反超英伟达”——这句话最近在开发者群、技术论坛和AI项目组晨会上高频出现,但它不是一句情绪化口号,更不是营销话术的简单复读。它背后是一组被反复交叉验证的实测数…

作者头像 李华