
简介这份资源面向计算机专业毕业生与推荐系统入门者提供一套可直接运行的电影推荐系统完整实现同时覆盖基于用户与基于物品两种协同过滤算法适合用于毕业设计参考或课程项目实践。压缩包共83个文件、约8.41MB以Python源码与编译缓存为主辅以HTML模板、CSV数据集、数据库文件及图片资源另含论文PDF、说明文档与爬虫脚本技术栈为Python3.7、Django2.2.1搭配MySQL或SQLite前端使用Bootstrap3数据集来自MovieLens。已有10613人学习下载说明其参考价值得到较多验证。读者可获得完整项目源码、数据库文件、论文与技术文档以及豆瓣爬虫和用户评分填充脚本便于理解推荐流程、复现实验并在此基础上二次开发。1. 从一份“跑不起来”的毕设说起电影推荐系统到底该交付什么每年到了毕设季总有人拿着一个压缩包来找我说代码能跑但答辩讲不清、老师一问就露馅。问题往往不在算法多难而在于整份方案没有“骨架”需求没立住模块划分含糊数据库表随手一画协同过滤只贴了一段调库代码验收时连自己都说不清推荐结果为什么长这样。这套基于 Python 3.7 Django 2.2.1 MySQL 的电影推荐系统解决的正是这件事——它把“用户看什么、系统推什么、数据怎么存、结果怎么验”串成一条完整链路适合要交毕设/课设的学生也适合想拿一套可改可交付方案的从业者。协同过滤不是玄学把数据流和模块边界画清楚它就是一个能讲透、能复现的工程问题。2. 需求与技术选型为什么是 Django 2.2.1 加协同过滤2.1 先把需求拆成三类角色做推荐系统最容易犯的错是一上来就写算法。我一般会先把角色和动作列清楚再决定技术栈。这个项目里其实只有三类角色普通用户、后台管理员、系统本身。普通用户要能注册登录、浏览电影、打分评论、看到“猜你喜欢”管理员要能维护电影库、用户和评论系统本身要在用户行为积累到一定程度后算出推荐列表并缓存下来。把这三类角色的动作写成用例需求就立住了后面每个模块都能对上一个用例答辩时也不至于被问“这个功能是干嘛的”。具体到功能边界用户侧的核心是“行为采集”——评分、收藏、浏览都算信号管理侧的核心是“数据维护”——电影元数据、海报、分类、演员系统侧的核心是“离线计算”——用评分矩阵算相似度生成每个用户的推荐结果。这三块职责不能混混在一起代码就会变成一坨改一个地方崩三个地方。通常的做法是用户行为写库、离线任务读库算、结果表单独存前端只读结果表互不干扰。2.2 技术选型背后的取舍Python 3.7 Django 2.2.1 这个组合在今天看不算新但对毕设场景反而是优点Django 2.2 是 LTS 版本文档全、报错信息友好ORM 把 MySQL 的增删改查包得很干净学生不用手写大量 SQL 就能把数据层跑通。MySQL 选 5.7 或 8.0 都行InnoDB 引擎保证事务评分表并发写入不会丢数据。协同过滤用基于用户的 UserCF 或基于物品的 ItemCF数据量在几千条评分这个量级纯 Python NumPy 算余弦相似度完全够用不需要上 Spark上了反而是给自己挖坑。这里有个反直觉的点很多人觉得推荐系统必须用深度学习才“高级”但毕设答辩老师看的是你有没有把问题想清楚。UserCF 的“找相似用户、加权打分”逻辑你能用一张图讲明白比堆一个调不通的神经网络强得多。选型的原则是“每一层都能解释”Django 负责 Web 和 ORMMySQL 负责持久化NumPy 负责矩阵运算职责清晰出问题也好定位。2.3 环境与依赖的落地配置环境这一步最容易翻车版本对不上后面全是玄学报错。我一般会把依赖钉死写成 requirements.txt避免“在我电脑上能跑”的尴尬。# 建议用虚拟环境隔离Python 3.7 对应虚拟环境创建 python3.7 -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate # 核心依赖版本钉死避免解析冲突 pip install django2.2.1 pip install mysqlclient1.4.6 # Django 连 MySQL 的驱动 pip install numpy1.18.5 # 协同过滤矩阵运算 pip install pillow7.1.2 # 电影海报上传这段配置的关键在mysqlclient和numpy两个包。mysqlclient是 Django 官方推荐的 MySQL 驱动比纯 Python 的pymysql性能好但编译时依赖系统里的 MySQL 开发库Linux 下要先装default-libmysqlclient-devWindows 下直接装预编译 wheel 即可。numpy版本不要贪新1.18 系列和 Python 3.7 兼容性最稳。装完之后用python -c import django; print(django.get_version())验证一下输出 2.2.1 才算过关。这一步看着琐碎但把版本钉死能省掉后面一半的排查时间。3. 总体架构与数据库设计四层结构与五张核心表3.1 分层架构与请求链路整个系统按 Django 的 MTV 模式切成四层表现层模板 静态资源、视图层views 处理请求、业务层推荐算法、评分逻辑、数据层ORM MySQL。一次“查看推荐”的请求链路是这样的用户访问/recommend/视图先查当前登录用户再从推荐结果表里读该用户的 TopN 列表如果结果为空就触发一次离线计算或返回热门兜底最后渲染模板。这条链路里视图只做编排算法只做计算数据只做存取任何一层出问题都能单独测。我一般会把推荐计算做成独立的 service 模块而不是塞进 views 里。原因是 views 会被 HTTP 请求频繁调用而推荐计算是重活塞进去会导致页面卡顿。独立成recommend/service.py之后既可以由管理后台手动触发也可以挂定时任务还能单独写单元测试这是把“能跑”变成“可维护”的关键一步。3.2 五张核心表的设计数据库是整个系统的地基表设计错了后面算法再漂亮也白搭。这个项目最少需要五张表用户表、电影表、评分表、推荐结果表、评论表。用户表可以直接复用 Django 自带的auth_user扩展一个UserProfile存头像和简介电影表存标题、导演、分类、海报、简介评分表是协同过滤的燃料必须对(user_id, movie_id)建唯一索引防止同一用户重复评分推荐结果表存user_id、movie_id、预测分、生成时间评论表挂在电影下面。-- 评分表协同过滤的核心数据源 CREATE TABLE rating ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, movie_id INT NOT NULL, score DECIMAL(3,1) NOT NULL, -- 0.5 ~ 5.0 分支持半星 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_movie (user_id, movie_id), -- 防重复评分 KEY idx_movie (movie_id) -- 加速按电影聚合 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这张表有两个设计点值得说。第一UNIQUE KEY保证一个用户对一部电影只有一条评分业务上用update_or_create写入重复打分就是更新而不是插入。第二score用DECIMAL(3,1)而不是整数是为了支持半星评分推荐算法里半星的区分度比整数更细。idx_movie索引则是为 ItemCF 准备的按电影聚合评分时能少扫很多行。推荐结果表同理对user_id建索引前端查 TopN 时直接走索引不用全表扫。3.3 推荐结果表与冷启动兜底推荐结果表不是可有可无的缓存它是把“离线计算”和“在线展示”解耦的关键。表结构大概是user_id、movie_id、predicted_score、rank、generated_at五个字段每次离线任务跑完先删该用户的旧结果再批量插入新结果保证前端读到的永远是最新一批。冷启动用户没有评分记录算不出相似用户这时候不能返回空页面通常的做法是查“全站高分电影”或“最新上架”做兜底等用户积累了几条评分再切到个性化推荐。提示推荐结果表要加generated_at字段答辩时老师问“这个推荐是什么时候算的”你能直接答上来也能据此判断结果是否过期。4. 协同过滤模块实现从评分矩阵到推荐列表4.1 评分矩阵的构建与稀疏处理协同过滤的第一步是把评分表转成矩阵。行是用户列是电影值是评分没评过的位置是 0。这个矩阵天然稀疏几千用户乘几千电影非零元素可能不到 1%。构建时用 pandas 的pivot_table最省事但要注意内存数据量大时先按活跃用户过滤。我一般会设一个阈值比如评分少于 5 条的用户不参与相似度计算因为样本太少算出来的相似度不可信反而污染推荐结果。import numpy as np import pandas as pd from django.db import connection def build_rating_matrix(): # 从评分表读出全部评分字段顺序固定 sql SELECT user_id, movie_id, score FROM rating df pd.read_sql(sql, connection) # 透视成 用户 x 电影 矩阵缺失值填 0 matrix df.pivot_table( indexuser_id, columnsmovie_id, valuesscore, fill_value0 ) # 过滤评分过少的用户降低噪声 active (matrix 0).sum(axis1) 5 return matrix[active]这段代码的核心是pivot_table和活跃用户过滤。fill_value0把缺失评分当 0 处理后面算余弦相似度时 0 不参与分子正好符合“没评分不算相似”的直觉。active那行是血泪经验早期我没过滤结果一个只评了 1 部电影的用户和谁都“相似度很高”推荐出来的东西完全没法看。阈值 5 不是死的数据量小可以降到 3数据量大可以提到 10按实际情况调。4.2 UserCF 相似度计算与推荐生成有了矩阵UserCF 的流程就三步算用户两两之间的余弦相似度、取 TopK 相似用户、用他们的评分加权预测目标用户对未看电影的分数。余弦相似度用 NumPy 向量化算比双重循环快一个数量级。预测公式是sum(sim * score) / sum(sim)只对相似用户评过、目标用户没评过的电影算分最后按预测分排序取 TopN。def user_cf_recommend(matrix, target_user, top_k20, top_n10): # 目标用户向量 target matrix.loc[target_user].values # 余弦相似度矩阵每行与目标向量点积 / 模长乘积 norms np.linalg.norm(matrix.values, axis1) target_norm np.linalg.norm(target) sims matrix.values.dot(target) / (norms * target_norm 1e-9) sim_series pd.Series(sims, indexmatrix.index).drop(target_user) # 取最相似的 K 个用户 top_users sim_series.nlargest(top_k) # 加权预测只算目标用户没评过的电影 scores {} for uid, sim in top_users.items(): for mid, score in matrix.loc[uid].items(): if score 0 and matrix.loc[target_user, mid] 0: scores[mid] scores.get(mid, 0) sim * score # 归一化并按预测分排序 result sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n] return result这段代码有几个参数要讲清楚。top_k20是参与预测的相似用户数太小推荐不稳定太大引入噪声20 是常见起点。top_n10是最终返回的推荐条数对应前端一页展示。分母里的1e-9是防除零新用户向量全 0 时不会崩。matrix.loc[target_user, mid] 0这个判断保证只推没看过的电影这是推荐系统的基本礼貌。实际跑的时候这段计算放在离线任务里结果写进推荐结果表前端不直接调避免每次请求都重算。4.3 ItemCF 作为补充与结果融合UserCF 有个天然短板用户数一多用户相似度矩阵就是平方级膨胀而且新用户冷启动难。所以我会再补一个 ItemCF算电影之间的相似度用“用户看过的电影”去推相似电影。ItemCF 的相似度矩阵是电影乘电影电影数量通常比用户稳定更新频率也低适合做补充。两路结果可以简单加权融合比如 UserCF 占 0.6、ItemCF 占 0.4去重后取 TopN。融合这一步不用太复杂答辩时能说清“为什么两路互补”就够了。注意ItemCF 的相似度矩阵要定期重算电影库新增后旧矩阵会失效通常的做法是每天凌晨跑一次全量重算或者新增电影达到一定数量后触发。5. 避坑与常见问题排查那些让答辩翻车的细节5.1 推荐结果全是热门电影现象是每个用户的推荐列表几乎一样都是评分最高的那几部。原因通常是相似度计算被热门电影主导或者冷启动兜底逻辑覆盖了个性化结果。解决方法是给评分做均值中心化减去用户平均分再算相似度削弱“高分用户和谁都像”的偏差同时检查推荐结果表是否真的按用户写入而不是所有用户共用一份。5.2 页面加载慢到超时现象是打开推荐页要等好几秒甚至 502。原因多半是把协同过滤计算塞进了视图每次请求都重算矩阵。解决办法是把计算挪到离线任务视图只读结果表如果结果表为空返回热门兜底而不是现场算。另外检查 MySQL 的user_id索引有没有建没索引的查询在几千条数据时就会明显变慢。5.3 中文乱码与海报 404现象是电影标题显示成问号或者海报加载不出来。乱码一般是数据库字符集没设成utf8mb4建库建表时都要显式指定海报 404 多半是MEDIA_URL和MEDIA_ROOT配置不一致开发环境下还要在urls.py里加静态文件路由。这两个问题不涉及算法但答辩演示时最扎眼提前配好能省很多尴尬。5.4 评分重复导致矩阵错乱现象是同一用户对同一电影有多条评分透视矩阵时报重复索引。根因是写入时用了create而不是update_or_create或者唯一索引没建。解决方法是评分表加UNIQUE(user_id, movie_id)业务层统一走update_or_create历史脏数据用一条 SQL 去重保留最新一条。5.5 离线任务跑完结果没更新现象是手动触发计算后前端还是旧推荐。原因通常是事务没提交或者前端有缓存。检查任务里是否用了transaction.atomic()且正常退出推荐结果表是否先删后插前端模板如果开了缓存加个时间戳参数强制刷新。这类问题排查起来不玄学按“写库—提交—读库”的顺序逐段验证就行。6. 验收与答辩用一张对照表把方案讲透验收这件事我一般建议学生自己先当一次“挑刺的老师”。准备一张功能对照表左边写需求用例右边写实现位置和验证方式逐条打勾。比如“用户能对电影评分”对应评分视图和rating表验证方式是提交一条评分后查库确认“能生成个性化推荐”对应离线任务和推荐结果表验证方式是给两个评分历史不同的用户各跑一次看结果是否有差异。这张表既是自测清单也是答辩时的讲解提纲老师问哪个功能你直接指到表和代码位置比空口说“实现了”有说服力得多。验收项实现位置验证方法合格标准用户注册登录auth 模块 模板注册新号并登录能进入个人页电影浏览与搜索电影视图 分类过滤按分类筛选结果与分类一致评分写入评分视图 rating 表提交评分后查库无重复记录个性化推荐离线任务 结果表两个用户对比列表存在差异冷启动兜底推荐视图分支新用户访问推荐页返回热门而非空白后台数据维护Django admin新增一部电影前台可见答辩时还有一个技巧主动讲一个“翻车点”。比如你可以说“早期推荐结果全是热门片后来做了均值中心化才解决”这比全程念功能列表更能体现你真的动过手。老师追问算法细节时把评分矩阵、相似度公式、TopK 三个关键词讲清楚再补一句“数据量在这个量级用 NumPy 足够没上分布式”既诚实又显得有判断。演示环节提前把数据灌好别现场注册新号冷启动页面容易让场面尴尬。从那以后我每次带人做这类项目都强制先写验收对照表再写代码因为答辩场上真正救命的不是算法多花哨而是你能指着每一行说清它为什么在那。希望帮到你。本文还有配套的精品资源点击获取