基于Python和Django的双协同过滤电影推荐系统设计与实现
毕业设计做到推荐系统这一挂我个人的看法是选题选得好不好直接决定后面几个月是轻松还是折磨。我当时选的就是“基于Python和Django的双协同过滤电影推荐系统”说白了就是把爬虫抓数据、协同过滤算相似、Django做web展示这一整条链路跑通最后既有能演示的界面又有能写进论文里的算法分析。这个题目能覆盖机器学习、大数据处理、Web开发多个模块导师一看就觉得工作量够答辩时也有的聊。如果你正好在纠结毕设方向或者已经选了类似题目这篇就把我当时踩过的坑、总结出来的经验全部拆开揉碎了讲。这篇内容适合几类人一是准备选推荐系统方向做毕业设计的同学二是想快速上手Django框架并整合机器学习算法的初学者三是想搞清楚协同过滤里那些细节到底怎么落地的人。我会从整体架构、爬虫实现、算法细节、Django集成、常见问题这几个方面把我当时怎么做的一步步还原出来包括核心代码逻辑和参数选择的原因。没有废话直接开始。1. 项目背景与整体方案设计1.1 为什么选择协同过滤推荐系统推荐系统是机器学习里应用最广、最容易讲清楚效果的方向之一尤其适合毕业设计。对比其他的比如图像识别、自然语言处理推荐系统不需要特别强的算力数据量可控评价指标比如准确率、召回率也直观。更关键的是它能把“数据采集→数据处理→算法建模→系统实现”这一整条工程链路串起来正符合毕设要体现“完整项目”的要求。当时我选基于协同过滤而不是基于内容推荐主要考虑两点。第一协同过滤是推荐算法里最经典的一类理论成熟论文里好写相关工作和原理分析。第二它不需要复杂的特征工程只需要用户对物品的评分或者行为数据数据源容易获取。基于内容的推荐虽然也可以做但需要额外提取电影的类型、导演、演员等特征数据搜集和存储成本更高对新手不太友好。关键是“双协同过滤”这个点。单纯只做基于用户的协同过滤UserCF或者只做基于物品的协同过滤ItemCF会显得工作单薄。如果把两者都实现再做一个加权融合既有算法对比又有混合策略这就成了答辩时的加分项。我当时就是先分别实现UserCF和ItemCF再通过一个权重参数把它们的结果结合起来然后调权重看效果变化这部分内容直接写成了论文里的一章“融合策略实验”。1.2 技术选型考量Python、Django、爬虫、大数据技术栈当时经过反复比较最终确定是Python Django 爬虫 SQLite/MySQL。Python不用多说机器学习算法生态最好写爬虫也快。Django作为Web框架自带Admin后台、ORM、模板系统开发效率高适合快速做一个展示页面。相比FlaskDjango集成了更多东西比如用户认证、表单处理写毕设可以少折腾很多。如果用Flask很多功能要自己拼反而浪费时间。爬虫部分我用的Requests BeautifulSoup或者Scrapy因为目标网站结构相对稳定不需要分布式爬虫单机多线程就够了。数据量不需要上千万条我当时抓了大约两万条评分数据、三千部电影、两百个用户已经足够做协同过滤演示了。大数据这个关键词在毕设里怎么体现其实不用铺真的大数据集群。可以用Pandas做数据处理把数据量调整到十万级通过优化算法和数据结构比如用稀疏矩阵存储在论文里讨论一下大数据场景下的性能瓶颈和优化思路这样就够了。真让一个本科毕设去搭Hadoop集群既复杂又没必要。1.3 系统整体架构整个系统分三层数据层、算法层、应用层。数据层负责爬取数据、清洗、存储。我设计了三个核心表用户表(user_id, 用户名等)、电影表(movie_id, 电影名, 类型, 地区等)、评分表(user_id, movie_id, rating, timestamp)。评分表是协同过滤的输入也是整个系统的核心。算法层负责读取评分数据构建用户-物品评分矩阵计算相似度矩阵生成推荐结果。我单独写了一个算法模块不跟Django的视图函数耦合这样方便在Jupyter Notebook里单独调试算法跑通了再接进Web系统。应用层就是Django项目负责接收请求、调用算法、渲染页面。用户在网页上选择“基于用户的推荐”或者“基于物品的推荐”就可以看到推荐结果。另外我还做了一个热门榜作为冷启动时的兜底推荐。2. 数据采集爬虫设计与实现2.1 确定数据来源与字段数据来源是某知名电影评分网站。这里不直接说具体名字因为我之前吃过亏网站改版后爬虫就废了。我在毕设里用的是公开的模拟数据包结构跟真实网站一致但避免了实时爬虫的不稳定性。你如果也想用它可以找一些开源的数据集比如某开源社区提供的“ml-100k”或者“ml-1m”电影评分数据这比爬虫更省事。但毕设题目里写了“爬虫”我就自己抓了一部分再结合公开数据凑够了完整度。我抓的字段包括电影ID、电影名、类型、导演、上映年份、评分人数、平均评分。用户ID我伪造了100个模拟用户每个用户对随机的一部分电影打分分数在1到5之间。这样虽然数据是模拟的但算法验证不受影响因为协同过滤只关心用户-物品的评分关系。2.2 请求与解析注意事项如果你一定要写实时的爬虫需要注意几个点。第一请求头要伪装成浏览器不然很容易被拒绝。我当时用的是Requests库设置了User-Agent和Referer还做了随机的延时1到3秒来避免被封。第二解析网页时优先选择CSS选择器或者XPath别只用正则。因为网页结构会有微调正则写死了很容易失效。BeautifulSoup配合lxml解析器用soup.select()定位目标元素容错率更高。第三分页和反爬。很多网站会限制单页条数并且可能有动态加载。我当时直接找了一个JSON接口用requests.get()拿到JSON数据再解析比解析HTML省力得多。这一步算是个小技巧建议你在做爬虫之前先看看目标网站有没有暴露内部的接口。清理数据的时候注意字段的缺失值和重复值。比如有些电影没有导演信息评分量为0这些记录我直接删掉否则后面计算相似度时会出现NaN或者除零错误。2.3 数据清洗与存储清洗工作我放在Pandas里做。首先把爬下来的原始数据转换成DataFrame然后去重、去空白、转换类型。评分列的数值型强制转换日期列统一格式。对于评分记录我只保留分数在1到5之间的数据超出范围的丢弃。存储方面Django默认用的是SQLite小数据量完全够用。但我为了后面算法处理更高效把评分数据导出成CSV文件用Pandas直接读取避免每次都要查数据库。在Django的ORM里也同步保存一份用于在网页上展示统计信息。两边通过批量导入脚本同步避免数据不一致。我当时犯过一个错爬虫抓到的评分数据量一大直接用ORM一条条插入数据库慢得离谱。后来改成批量bulk_create()速度快了好多倍。如果你的数据量还在十万级这个优化效果很明显。3. 协同过滤算法从原理到实现3.1 基于用户的协同过滤(UserCF)基于用户的协同过滤核心思想是给你推荐那些和你兴趣相似的其他用户喜欢的电影。算法分三步。第一步建立用户-物品评分矩阵。行是用户列是物品值是评分。第二步计算用户之间的相似度。我用的皮尔逊相关系数公式是sim(u, v) Σ(r_ui - mean_u)(r_vi - mean_v) / sqrt(Σ(r_ui - mean_u)^2 * Σ(r_vi - mean_v)^2)皮尔逊相关系数能消除用户评分尺度的影响比如有人习惯打3分有人习惯打5分直接算欧氏距离会被这个偏差带偏。实际操作中我用Pandas的corrwith()方法直接算出来了效率很高。第三步为目标用户找到K个最相似的用户然后把这些用户喜欢的物品中目标用户没有交互过的物品按“加权评分”排序推荐。加权评分公式是我用python自己实现的def recommend_by_user(user_id, top_n10): target_ratings user_item_matrix.loc[user_id] similar_users user_sim_matrix[user_id].drop(user_id).sort_values(ascendingFalse)[:K] scores {} for sim_user, sim_score in similar_users.items(): user_rated user_item_matrix.loc[sim_user].dropna() for movie, rating in user_rated.items(): if pd.isna(target_ratings[movie]): # 目标用户没看过 scores[movie] scores.get(movie, 0) sim_score * rating # 按权重排序返回TopN这里要注意如果K取太小推荐结果可能太单一K取太大又会引入不相似的用户。我试过K10到K50最后选了K20效果比较平衡。3.2 基于物品的协同过滤(ItemCF)基于物品的协同过滤核心思想是推荐给你那些和你喜欢过的物品相似的其他物品。算法逻辑跟UserCF对称。第一步还是建立评分矩阵但相似度计算换成了物品之间的相似度。我用余弦相似度因为物品的属性向量一般是评分向量余弦相似度可以高效处理高维稀疏向量。公式sim(i, j) (r_ui * r_uj)的和 / (sqrt(∑(r_ui)^2) * sqrt(∑(r_uj)^2))Python示例item_sim user_item_matrix.T.corr(methodpearson) # 快速算物品相关性当然物品之间的相关性直接用皮尔逊也可以但要注意评分矩阵转置之后列的均值计算方式会变。第二步也是计算加权推荐。当用户对电影A打了高分系统就找到和电影A最相似的电影B把B推荐给用户。推荐分 用户对A的评分 × A和B的相似度多个相近电影加权求和。ItemCF有一个好处物品的相似度可以离线算好存起来。用户来请求推荐时只用查表不用实时计算响应速度很快。我实际测了一下3000部电影算一次物品相似度矩阵在我当时的笔记本上花了不到10秒完全能接受。3.3 双协同过滤的融合策略“双协同过滤”名字听起来很唬人其实做法不复杂。就是把UserCF和ItemCF两个算法的推荐列表做一个融合。我用的方法是设定一个权重参数α最终推荐分 α * UserCF评分 (1-α) * ItemCF评分。这里的关键是两者评分的量纲要一致。UserCF给的是“用户相似度 × 评分”的加权和ItemCF也是类似的量级但数值范围不一定完全对得上。所以我先对两个评分做归一化处理把分数压到0到1之间再按α融合。Python伪代码def hybrid_recommend(user_id, alpha0.6): user_scores recommend_by_user(user_id) item_scores recommend_by_item(user_id) all_movies set(user_scores.keys()) | set(item_scores.keys()) final_scores {} for movie in all_movies: s_user user_scores.get(movie, 0) s_item item_scores.get(movie, 0) final_scores[movie] alpha * s_user (1 - alpha) * s_item return sorted(final_scores.items(), keylambda x: x[1], reverseTrue)[:top_n]α怎么选我做了几组对照实验分别在α0.4、0.5、0.6、0.8时算了推荐结果的命中率看用户实际会给多高的分最终α0.6效果最好。这一部分实验数据直接成了论文里“融合策略效果对比”那节。答辩时导师对这里很感兴趣问了几个问题因为这是体现“自己思考过”的地方而不只是照搬课本。3.4 相似度计算与性能优化相似度计算是协同过滤最耗时的地方。如果是十万级评分、几千部电影直接双重循环计算复杂度高我毕业设计的demo级别数据还能承受。但如果数据真的到了“大数据”的量级比如几百万评分就必须做优化。我当时用了几个优化手段一是使用稀疏矩阵用scipy.sparse的csr_matrix存储评分矩阵避免大量0维浪费内存。二是用向量化操作配合numpy不用Python原生循环算相似度。三是相似度计算做预计算存到数据库或者缓存文件里用户请求时直接读取。实际测试用稀疏矩阵后内存占用降低了约80%。我还在论文里简单讨论了既然是基于大数据就要把可扩展性考虑进去提了一下可以使用分布式计算框架比如Spark但毕设主体不涉及平台部署点到为止即可。4. Django集成与系统实现4.1 Django项目结构设计Django项目我用了一个标准的MVT结构。app命名为recommender里面分几个子模块models.py定义用户、电影、评分表views.py处理推荐请求algorithm.py放协同过滤算法utils.py放数据处理辅助函数。目录大致是这样的manage.py recommender/ settings.py urls.py movie_rec/ models.py views.py algorithm.py urls.py templates/ index.html recommend.html movie_detail.html为什么要把算法单独拆成一个文件因为Django的视图函数不适合放复杂逻辑不然调试不方便。我当时就是先在这个文件里用命令行直接运行验证算法输出没问题再丢给视图调用。在视图中我只做请求接收、调用算法、准备上下文数据这三个动作。4.2 核心功能模块实现核心的视图函数逻辑是用户输入要推荐的用户ID选择推荐方式UserCF、ItemCF还是混合然后算法模块返回推荐电影列表包括电影ID、名字、预测分数。视图示例def recommend_view(request): if request.method POST: user_id request.POST.get(user_id) method request.POST.get(method) # user, item, hybrid rec_movies get_recommendations(user_id, method) return render(request, recommend.html, {movies: rec_movies}) return render(request, recommend.html, {})这里的get_recommendations函数我封装在algorithm.py里对三种模式做了分支判断。返回的每个电影我还从电影表里查出类型、导演这些字段方便页面展示。为了演示推荐效果我特意在首页放了一个“用户历史评分记录”表格让用户可以选一个已有用户ID查看他看过的电影然后点击“推荐”看结果。这样现场演示的时候能明显看出推荐结果跟他历史的关联也方便写进演示视频里。4.3 前端页面展示前端我没有用特别复杂的框架就是Bootstrap Django模板。首页有用户选择器、推荐方式选择按钮、推荐结果表格。表格里列出电影名、类型、推荐评分排序。再搞一个折叠区域显示算法的计算结果或者处理耗时直观展示“从提交请求到生成结果”的过程。模板示例略缩form methodpost action{% url recommend %} {% csrf_token %} select nameuser_id {% for user in users %} option value{{ user.id }}{{ user.id }}/option {% endfor %} /select button namemethod valueuserUserCF推荐/button button namemethod valueitemItemCF推荐/button button namemethod valuehybrid混合推荐/button /form table thead tr th排名/th th电影名/th th类型/th th预测评分/th /tr /thead tbody {% for movie in movies %} tr td{{ forloop.counter }}/td td{{ movie.title }}/td td{{ movie.genre }}/td td{{ movie.score }}/td /tr {% endfor %} /tbody /table另外我还做了一个“用户相似度可视化”页面用柱状图显示指定用户最相近的10个用户以及相似度分数。这部分用ECharts画图不大需要后端处理把JSON传给前端就行。答辩演示时一眼就能看出算法的内在逻辑。5. 常见问题与bug排查实录5.1 数据量过大导致内存溢出第一次把全量数据放进算法跑直接内存飙升到我怀疑人生。原因是rating DataFrame里的用户ID和电影ID是字符串占内存大而且我一开始用密集矩阵存储。后来我把ID全部转成整数索引再用scipy稀疏矩阵内存问题一下子解决了。具体操作是创建两个字典分别把用户ID和电影ID映射到从0开始的整数编号然后构造稀疏评分矩阵from scipy.sparse import csr_matrix row ratings[user_idx] col ratings[movie_idx] data ratings[rating] sparse_matrix csr_matrix((data, (row, col)))这个矩阵参与向量化运算比用Pandas DataFrame的密集形式快了不止一个量级。5.2 冷启动问题处理协同过滤最怕冷启动也就是老用户或者老物品没有行为数据。新用户刚注册没有历史评分算法没办法给你推荐。我当时做了个“热门兜底”推荐全站评分人数最多的前10部电影。这个方案虽然简单但在实际演示时确实有用。论文里可以写“针对冷启动场景采用基于流行度的推荐策略作为协同过滤的补充”。新电影也一样没有评分没有交互数据协同过滤推荐不出来。我同样做了时间衰减逻辑只要新电影一上线就把它加入到“最新上架”榜单里保证它至少能被看到。5.3 爬虫被封IP的对策如果你真的爬实时数据最怕遇到封IP。我当时的应对策略有三个一是降低请求频率用random sleep二是更换User-Agent每次请求随机选一个三是用代理池但那个成本比较高而且毕设规模用不上。我建议你优先用公开数据集把爬虫部分简单做一个demo不要依赖实时数据免得答辩前几天突然挂了心态直接崩。5.4 其他常见坑相似度矩阵对角线不为1我一开始直接用Pandas的corr()发现有的物品跟自己的相似度不是1因为有些列全为NaN。解决办法是在计算完后强制把对角线设为1。推荐结果重复个别电影同时从多个渠道生成推荐在混合推荐时会出现重复。我用了字典最后根据电影ID去重。Django的CSRF问题如果你直接用表单POST忘记加{% csrf_token %}会报403错误。这个坑我看了半天才明白。数据库编码问题中文电影名存入SQLite后显示乱码后来统一在settings里设置UTF-8编码并在连接后加了Django的OPTIONS charset: utf8mb4。6. 写在最后的一些经验说实话做完这个毕设我对推荐系统的理解比之前上课听几百遍都深。原理书上看觉得简单自己动手才会发现就算是最基础的UserCF也有很多当时没想到的细节。要特别强调一点别让算法一直“独守空房”死在后台一定要接上Web界面或者做一个API让人能交互。这样答辩的时候你一边操作一边讲比单纯放PPT有说服力得多。我个人的体会是双协同过滤这个方案不是越复杂越好其实两项基础算法的融合已经足够体现你对问题的思考。在答辩现场我讲了融合权重的调参过程和效果对比导师明确表示这点是加分项。如果你有余力可以在融合算法的基础上再加一个简单的SVD矩阵分解做对比那论文就会更饱满但也会多不少工作量量力而行。最后给几个终极建议数据一定要提前准备不要等到写论文了才去爬数据算法模块一定要独立出来测试好再接Django文档里的图表要画得清楚推荐流程图、系统架构图这种东西特别加分。希望这篇能帮你少走一些我走过的弯路毕设一次过。