Python+Django协同过滤电影推荐网站毕业设计实战指南

Python+Django协同过滤电影推荐网站毕业设计实战指南 简介推荐系统作为信息过滤的重要手段在视频、电商等场景中广泛应用。协同过滤算法通过分析用户历史行为计算相似度并预测偏好是构建个性化推荐的核心方法之一。Python凭借丰富的数据处理库和成熟的Web框架Django成为快速实现推荐系统原型的理想技术栈。本文从协同过滤原理出发讲解如何基于PythonDjango搭建一个完整的电影推荐视频网站涵盖评分矩阵构建、用户相似度计算、预测评分生成、Django工程落地以及数据库脚本设计等关键环节并总结了开发过程中的常见问题与答辩要点适合毕业设计及推荐系统入门实践参考。 毕业设计如果用自己熟悉的领域当切入口确实比凭空选方向靠谱得多。我第一次接触这个基于PythonDjango的协同过滤电影推荐视频网站时最大的感受是这题再适合毕设不过但前提是你没有把它做成一坨拼凑的Demo。多数同学在选这个题目的时候心里想的是推荐算法听起来有深度Django又能出网站刚好还能配数据库脚本真正动手才发现算法讲得浅了显得没工作量网站做得糙了又容易被抽检到。这篇文章我会把从数据准备、协同过滤算法实现、Django工程落地到答辩演示的完整链路拆开来讲顺便把我在实际开发中踩过的坑一并交代清楚。内容适合正在准备毕设、或者想快速搭出一个有点东西的推荐系统原型的同学参考不需要你有多深的算法基础但希望你愿意自己动手敲代码。1. 选题动机与系统全景这个毕设题目到底在考核什么1.1 为什么电影推荐是简历和答辩两头通吃的选题选毕设题目有个隐蔽的评分逻辑导师和评阅老师看的不是你的功能多花哨而是你在这套系统里做了什么有门槛的事。普通的增删改查网站早就没有区分度了但加入推荐算法之后整个系统就从一个管理系统升级成了有智能决策能力的应用平台这在毕业设计的评分维度里属于算法设计、应用创新和工程实现都有涉及的类型。电影推荐又比商品推荐、新闻推荐更适合学生做原因有三个。第一电影数据容易获得MovieLens数据集提供了大量匿名的用户评分记录字段清晰、格式统一不用自己去清洗那些乱七八糟的电商数据。第二电影内容本身好解释推荐结果的评估可以直接看是不是同一类型、同一年代、同一个导演的作品答辩时展示效果直观。第三视频网站是大家日常都有使用经验的场景界面做出来像一个能用的产品的门槛很低不用额外解释业务规则。一个容易被忽视的点是推荐算法在整个系统里的占比不需要太高但必须能被清楚地讲出来。我见过太多同学把协同过滤写成了一个相似用户看过的电影取交集的玩具代码这样答辩被追问到公式推导时很容易露馅。正确的做法是算法真正跑起来并且你能够说清楚相似度怎么算、预测评分怎么算、为什么要这样做。这也是我这篇文章想重点解决的事。1.2 技术选型Python、Django、协同过滤分别扮演什么角色整个系统的分工可以用一句话概括Python负责数据清洗和算法实现Django负责把算法结果变成一个可视化的网站协同过滤算法负责生成推荐列表这个核心卖点数据库脚本负责让系统在任何一台机器上都能快速复现。选Python不选Java的原因很简单Python的数据处理生态太成熟了。pandas能快速搞定评分矩阵的构建numpy能直接做向量化的相似度计算scikit-learn虽然自带了一些推荐相关的工具但毕设场景下我建议你自己写协同过滤的核心逻辑因为这个过程本身就是工作量的一部分。Django则是Python Web框架里开箱即用程度最高的选择自带Admin后台、ORM数据库映射、模板引擎和用户认证这几个组件刚好覆盖电影网站所需要的用户管理、影片管理和页面渲染省去了大量重复造轮子的时间。关于数据库我推荐的组合是MySQL或SQLite二选一具体看你们实验室的习惯。既然题目明确要求提供数据库脚本那就说明你最终要能导出一份结构完整、可复现的建表和初始化数据脚本。我的建议是开发阶段用SQLite跑通功能因为Django对SQLite的支持是零配置的不用安装数据库服务但是毕业设计提交的时候最好把数据迁到MySQL然后导出一份完整的.sql文件。这个迁移过程本身不复杂但能体现你对数据库操作的基本功。1.3 项目交付物的完整清单当你说内含Python完整源代码和数据库脚本的时候交付物要有明确的边界。不要只在论文里提一句系统包含推荐功能交上去的东西至少要包含以下几类完整的Django项目源码包括settings配置、app目录、模板文件、静态文件和依赖清单requirements.txt数据库脚本既要包含建库建表的DDL语句也要包含可直接导入的初始数据INSERT语句推荐算法的核心模块最好独立成一个Python文件或者一个Django app中的service模块让评阅老师能快速找到算法位置一个简短的部署说明文档写清楚Python版本、依赖安装命令、数据导入命令和启动命令这部分经验是我吃了亏才总结出来的。第一次交中期材料的时候我只交了一个项目压缩包里面没有requirements.txt也没有数据导入脚本老师在自己的电脑上根本跑不起来当场就被打回整改。后来我把环境依赖、数据库脚本、README全部补齐再提交就顺利多了。毕业设计本质上是一次工程交付能不能在你的环境之外复现是很重要的一条线。2. 协同过滤算法的最小可用实现从原理到能跑的代码2.1 基于用户的协同过滤为什么更适合毕设场景协同过滤算法主要有两个流派基于用户的(User-based)和基于物品的(Item-based)。基于用户的思路是找到和你口味相似的人把他们喜欢的而你没看过的东西推荐给你基于物品的思路是找到和你看过的电影相似的电影推荐给你。在毕设场景里我强烈建议优先实现基于用户的协同过滤原因有三个。第一原理更容易讲清楚。人以群分这个概念不需要任何数学基础也能理解答辩时你从使用场景切入导师很容易跟上你的思路而基于物品的推荐虽然实际应用更广但在讲物品相似度的时候容易被追问相似度到底怎么定义解释成本更高。第二代码实现直接对应评分矩阵的运算逻辑方便在论文里画出清晰的流程图和数据流图。第三基于用户的协同过滤天然需要一个当前用户作为输入这和网站的登录体系、用户行为记录能够自然结合不至于让算法部分和Web部分像两个孤立模块。当然基于用户的协同过滤也有一个众所周知的短板冷启动。新用户没有任何评分行为时系统无法算相似度。解决方法是给它一个默认的热门榜兜底让冷启动用户看到的是全网评分最高的电影之后随着用户评分行为增多再逐步切换成个性化推荐。这个冷启动个性化的组合策略是答辩时的一个加分项因为它说明你考虑到了实际工程问题。2.2 评分矩阵、相似度计算与预测打分的核心代码这一节我给出一个可以直接跑的最小实现不依赖任何大型框架只用pandas和numpy就能完成。整个流程分三步构建用户-电影评分矩阵计算用户之间的相似度为目标用户生成推荐。先看数据形态。假设我们有三张表用户表、电影表、评分表。评分表里至少要有user_id、movie_id、rating三个字段。构建评分矩阵的代码如下import pandas as pd import numpy as np # ratings是评分表DataFrame列名为user_id, movie_id, rating # 构建用户-电影评分矩阵行是用户列是电影空值填0 rating_matrix ratings.pivot_table( indexuser_id, columnsmovie_id, valuesrating, fill_value0 ) # 转成numpy数组方便计算 matrix rating_matrix.values接下来是用户相似度计算。常用的相似度指标有余弦相似度和皮尔逊相关系数。在评分数据场景下皮尔逊相关系数比余弦相似度更合适因为它对每个用户的评分习惯做了归一化——有人习惯打4分以上有人习惯把好片和烂片拉开差距皮尔逊系数能消除这些尺度差异。from sklearn.metrics.pairwise import cosine_similarity # 计算用户间的皮尔逊相关系数矩阵 # 为了处理0值和少量评分的情况这里用numpy手写实现 def pearson_similarity(matrix): n_users matrix.shape[0] sim_matrix np.zeros((n_users, n_users)) # 每一行是一个用户在这个矩阵中的索引 for i in range(n_users): for j in range(n_users): if i j: sim_matrix[i][j] 1.0 continue # 取出两个用户都评过分的电影 mask (matrix[i] 0) (matrix[j] 0) if np.sum(mask) 0: sim_matrix[i][j] 0 continue vec_i matrix[i][mask] vec_j matrix[j][mask] if np.std(vec_i) 0 or np.std(vec_j) 0: sim_matrix[i][j] 0 continue sim_matrix[i][j] np.corrcoef(vec_i, vec_j)[0][1] return sim_matrix最后一步是生成推荐。对目标用户尚未看过的每一部电影把所有对该电影有评分的用户按相似度加权求和得到预测评分然后排序取TopN即可。def recommend(user_id, rating_matrix, sim_matrix, top_n10): user_idx list(rating_matrix.index).index(user_id) # 找到当前用户没有评分的电影 unseen rating_matrix.columns[rating_matrix.loc[user_id] 0] scores [] for movie in unseen: movie_idx list(rating_matrix.columns).index(movie) # 取出所有对该电影有评分的用户的评分向量 rated_users rating_matrix[movie] 0 # 只保留相似度大于0的用户 valid rated_users (sim_matrix[user_idx] 0) if np.sum(valid) 0: scores.append((movie, 0)) continue # 加权平均相似度 * 评分 / 相似度之和 sim_values sim_matrix[user_idx][valid] movie_ratings rating_matrix[movie][valid].values pred np.dot(sim_values, movie_ratings) / np.sum(sim_values) scores.append((movie, pred)) scores.sort(keylambda x: x[1], reverseTrue) return scores[:top_n]这套代码在数据量几千条时性能还不错但如果评分记录到了几十万条双层循环计算用户相似度就会很慢。我在毕设里用的解法是把相似度计算封装成一个后台任务每天凌晨跑一次结果存到Redis或者数据库的推荐结果表里用户访问推荐页时直接读结果而不是实时计算。这个优化在毕设里是可选项但如果你能写进论文里进度感和工程深度都会提升一个档次。2.3 冷启动和“新电影无人评分”问题的毕业设计版处理推荐系统最怕的是新用户和新物品。新用户的问题上面已经说了用热门榜兜底。新电影的问题更隐蔽一部电影刚入库评分人数为0协同过滤永远不会推荐它但如果它是刚上线的高热度电影不推荐反而显得系统不智能。我在这个项目里采取了一个很朴素的混合策略评分人数大于等于5的电影才纳入协同过滤推荐候选池评分人数不足5的新电影靠标签相似度做补充——从电影表里取出类型、导演、演员等属性和用户最近看过的电影比较给出基于内容的简单推荐最终推荐页面上协同过滤结果放前10个内容推荐结果放前3个两版合并这种混合推荐方案算法含量不高但胜在靠谱而且答辩的时候你可以明确说出来这是为了解决冷启动问题做的工程妥协。导师会认为你想过实际应用而不是只会调包。3. Django工程落地的关键模块设计你不能只写一个算法Demo3.1 数据模型设计用户、电影、评分、推荐结果表算法再好最终要落到数据库里。我设计的Django模型有四个核心部分这里直接给出模型结构思路。用户模型直接复用Django内置的auth.User在此基础上通过OneToOne扩展一个Profile存放用户的头像、个性签名等信息方便后续扩展。电影模型Movie是另一张核心表字段包括title、poster_url、description、release_date、director、actors、genres、rating_average、rating_count。其中genres我推荐用逗号分隔的字符串存虽然不那么符合关系数据库范式但在毕设场景里查询起来最省事不用为了多对多关系引入额外的关联表。评分模型Rating是算法输入的核心字段为user、movie、score、comment、created_at。这是一个多对多的中间表需要设置unique_together约束确保同一个用户对同一部电影只能有一条评分记录。推荐结果表Recommendation则是一个缓存表字段包括user、movie、score、rank、created_at。算法离线计算产生的推荐结果直接写进这张表Django的推荐视图只查它这是性能和逻辑解耦的关键。3.2 推荐服务的接口封装与后台任务设计推荐逻辑不能散落在视图函数里单独放在一个services/recommend.py模块中是更清晰的工程组织方式。我习惯的做法是views.py只负责HTTP请求和响应推荐相关的计算全部封装成一个服务类例如# services/recommend.py class Recommender: def __init__(self): self.rating_matrix self._build_matrix() self.sim_matrix self._calc_sim() def _build_matrix(self): ratings Rating.objects.all().values(user_id, movie_id, score) # 转DataFrame后生成矩阵 return matrix def recommend_for_user(self, user_id, top_n10): cached Recommendation.objects.filter(user_iduser_id).order_by(rank) if cached: return cached results compute_online(user_id) for rank, (movie, score) in enumerate(results): Recommendation.objects.update_or_create(...) return results在线推荐和离线预计算的取舍上面已经提到实际开发时我建议走混合路线普通用户直接读缓存推荐表评分行为刚发生变化后的首次刷新触发一次在线计算但这个在线计算只对当前用户跑避免全矩阵重新算一遍。3.3 视频播放页与前端模板的组织视频网站不能只有列表页播放页才是用户停留的核心。这个项目的播放页需要处理三件事引入视频播放器、展示电影详情和推荐理由、商机推荐列表和评论区。前端我用的是Django模板少量JavaScript没有引入复杂的前端框架。视频播放器推荐用video.js或者Django默认支持的HTML5 video标签本地开发阶段放几个准备好的MP4测试片段就行不需要真正的流媒体服务器。播放页有一个容易被忽略的细节推荐理由的展示。推荐系统不能只告诉用户我们推荐你看《霸王别姬》还要在推荐理由里写因为你看过《活着》和你品味相似的用户也喜欢《霸王别姬》。我实现了一个简单的方法在生成推荐结果时把相似用户的信息一并存储。展示推荐理由不仅让页面看起来更专业也方便你在系统演示时解释推荐逻辑一举两得。4. 数据库脚本与初始数据准备做不好这一步很容易翻车4.1 数据库脚本里该放什么、不该放什么题目强调内含数据库脚本说明这不仅是项目的一个附属品还是一份重要的交付物。很多同学的数据库脚本只写了建表语句没有初始数据老师拿过来一运行页面全是空的体验非常差。我的建议是脚本至少包含三层DDL层建库、建表、字段注释、索引、唯一约束DML层初始数据包括电影、分类、模拟用户、模拟评分可选存储过程或视图层如果你在系统里用了复杂的统计查询可以写成视图或存储过程也能体现数据库功底不需要放进去的是大段的测试数据和无意义的垃圾数据。数据库脚本的长度不是重点重点是导入之后系统能正常运行而且数据量和数据形态能支撑算法演示。我最终提交的脚本里包含了大约500部电影和1000条用户评分记录这个量级不算大但足够演示协同过滤的效果也不会让老师在导入时等太久。4.2 数据来源的三条路爬虫、公开数据集和手工造数电影数据哪里来这个问题看似简单实际是很多同学停滞不前的地方。我总结出一条经验不要想着去爬那些大型视频网站一是反爬策略复杂二是版权和数据安全问题麻烦三是爬下来的数据质量参差不齐。毕设场景下三条路是可靠的。第一条路是直接用公开数据集MovieLens它有不同规模的版本最小的是100K评分数据足够用了。这个数据集包含用户ID、电影ID、评分、时间戳还有一些电影标题和类型的文件格式非常干净。第二条路是找一个电影信息API比如一些公开的开放接口获取电影名称、导演、演员、海报地址等信息存进自己的数据库。第三条路是手工造小规模数据适合你只需要演示算法效果的场景造几十个用户、几百条评分就够但要保证评分分布合理不能太稀疏。我用的是第一条路和第二条路的结合从MovieLens拿评分数据再通过脚本批量更新电影详情字段。这样评分数据真实、电影信息也完整演示效果会好很多。4.3 从MovieLens到本地库完整导入流程导入流程我写成了一个独立的Python脚本import_data.py放在项目根目录下。核心步骤是读CSV文件、清洗字段、写入Django模型对应的表。import csv from movies.models import Movie, Rating from django.contrib.auth.models import User def import_movies(csv_path): with open(csv_path, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: Movie.objects.update_or_create( movie_idrow[movieId], defaults{ title: row[title], genres: row[genres], } ) def import_ratings(csv_path): # 从评分文件读入写入Rating表 ...有一个特殊的坑要提醒你MovieLens里的用户ID是自己的一套编号而Django自带的User表主键是自增的两者不能直接对应。我当时的处理方式是单独建一张映射表或者在MovieLens的用户ID基础上加一个偏移量保证不冲突。这块在答辩的时候也很容易被问到因为评阅老师会好奇你的用户数据是怎么产生的。5. 开发过程中踩坑的记录帮你省下至少一周的无头排查5.1 Django版本与Python版本的匹配陷阱这个坑几乎每个用Django的毕设党都会遇到。Django版本和Python版本是强绑定的Django 2.x支持Python 3.5以上Django 3.x支持Python 3.6以上Django 4.x要求Python 3.8以上。很多同学的电脑上装的是Python 3.10甚至3.12如果直接按网上的旧教程装一个Django 2.2运行时会报各种不兼容错误而且报错信息还不那么直观。我建议直接用Python 3.10或者3.11搭配Django 4.2这是一个长期维护版本文档多、示例多、坑也被人踩得差不多了。requirements.txt一定要锁定版本号不要写那种没有版本号的裸依赖否则换一台机器运行时所有依赖都会被装成最新版系统行为可能变化。5.2 协同过滤计算慢数据量不大也一样卡按理说几百条评分数据算相似度矩阵应该瞬间完成但我在第一次实现时还是卡了好几秒原因是我在每次请求推荐页面时都重新算了一遍全量相似度矩阵。问题不在算法复杂度而在重复计算。解决方法是上面提到的推荐结果缓存表同时把相似度计算改成了异步任务。Django里可以用Celery来实现异步如果不想引入额外依赖也可以用一个简单方案把相似度计算的结果存成pickle文件每天定时重新生成用户请求直接读文件。这个方法虽然不如Celery优雅但胜在简单而且对于毕设的数据量完全够用。答辩时你可以说使用定时任务预计算推荐结果并采用缓存策略提升系统响应速度没有任何问题。5.3 视频文件的存储与播放兼容性视频文件是视频网站的灵魂但也是坑最密集的地方。开发时要注意两点。第一本地开发时视频文件放在Django的media目录下通过Django配置的MEDIA_URL进行访问。但如果视频文件很大Django自带的开发服务器在处理并发请求时会非常吃力所以演示时最好用几个小体积的MP4文件不要放几个GB的1080p原片。第二视频编码格式要注意浏览器兼容性。MP4的H.264编码是兼容性最好的别的编码格式经常出现页面有播放器但画面打不开的情况。我吃过一次亏下载了一个高品质的MKV测试视频用video.js播放时一直黑屏查了半天才发现浏览器根本不支持MKV格式。后来统一转成H.264编码的MP4文件一切正常。做毕设一定要记住不要在产品形态上追求极致的格式支持稳定、能演示、不翻车才是第一位。5.4 编码问题数据库里的中文乱码数据库脚本导入时中文乱码是很常见的问题。要避开这个坑有几个固定操作MySQL建库时指定utf8mb4字符集Django的数据库配置里加上OPTIONS脚本文件保存为UTF-8无BOM格式。如果你在Windows下开发用记事本或者某些默认编码的工具保存文件很容易存入带BOM的UTF-8导入MySQL时就会乱码。我的经验是涉及中文数据的脚本文件一律用VS Code重新保存为UTF-8格式。另外数据库脚本里的INSERT语句如果包含大量中文文本建议在脚本开头加一句SET NAMES utf8mb4;这样命令行导入时就不会出现乱码。6. 答辩演示与材料提交让系统在你的电脑之外也能复现6.1 演示时的“预置数据”设计什么场景最加分答辩演示是最容易紧张、也最容易出问题的环节。我的一条核心经验是在演示系统前先在数据库里预置好一组“表演数据”让推荐结果呈现出明显的个性化差异。什么意思呢你可以准备两个测试账号账号A看过的电影集中在悬疑片和犯罪片账号B看过的电影集中在爱情片和喜剧片这样两个账号登录后的推荐页就有截然不同的风格。演示时一登录推荐列表明显不同评委一眼就能看出推荐算法确实在工作。如果演示时所有账号看到的都是同样的热门电影评委就会怀疑你的算法是不是摆设。所以预置数据的核心目标就是让推荐结果有区分度。我的做法是建立一个seed 数据脚本专门负责生成这些演示账号及其评分行为且不会污染正式数据。6.2 讲算法时最容易被追问的三个问题答辩时只要你的题目里带“推荐”二字评委提问的重点基本集中在算法部分。我梳理了三个高频问题提前准备好答案就能稳定发挥。第一个问题是“你的相似度是怎么算的为什么选皮尔逊相关系数而不是余弦相似度”这个问题我们前面已经解释过重点在于评分偏差的归一化。你要能用自己的话说清楚不要背教材定义。第二个问题是“冷启动问题怎么解决”一定要回答用户冷启动和物品冷启动两个方面如果只回答用户冷启动会被追问“新电影怎么办”。第三个问题是“你的系统是实时推荐还是离线推荐”你要讲清楚自己用的混合策略普通场景读缓存评分行为变化后触发单用户在线重算。这个回答同时展示了算法能力和工程意识是很加分的。6.3 代码目录整洁度、注释规范与论文素材的对应关系在最终提交材料时我强烈建议把代码目录按照“数据导入脚本、算法模块、Web应用、文档”四个维度组织而不是让所有文件堆在根目录里。评阅老师可能不会逐行看代码但一定会先看目录结构一个清晰的项目结构本身就是第一印象。注释方面我建议在核心算法代码里添加关键注释讲清楚每一步的数学意义这样论文里写算法章节时可以直接对照着写。不要追求满屏注释而是要在相似度计算、预测评分、TopN排序这几个核心逻辑处写清注释。这些注释放在论文里、“实验过程”里都会成为你的素材。另外一个小技巧在Git仓库里维护一个CHANGELOG或者README记录每一步开发过程和决策理由。答辩准备PPT时你会发现这份文档比你的记忆可靠得多。最后分享一个我个人的做法如果你现在还在纠结“代码从哪里开始”我建议第一步不要写Django而是先把数据准备和算法跑通。用MovieLens的数据集在Jupyter Notebook里先做出一个能生成推荐列表的脚本然后再迁移到Django工程里。这个顺序能帮你把“算法”和“Web”两个相对独立的部分分别调通避免在Django里同时排算法和框架的错排查难度直接减半。在我实际做这个项目的过程中最花时间的其实不是算法本身而是数据导入和工程组织。你只要把数据脚本、Django模型、推荐服务这三块理清楚整个系统就已经成型了。剩下那些花哨的页面效果和额外的功能都是锦上添花。把核心链路搞稳、搞透比堆砌功能有用得多。本文还有配套的精品资源点击获取