ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python Django MySQL电影推荐系统毕设:协同过滤算法完整落地

Python Django MySQL电影推荐系统毕设:协同过滤算法完整落地 简介这是一套基于Python、Django与MySQL实现的电影推荐系统毕业设计源码包主要面向计算机相关专业本科生、研究生及需要完成课程设计或毕业设计的开发者。系统采用Django作为Web框架借助MySQL持久化存储可覆盖用户登录注册、电影信息展示、评分与推荐等核心业务模块适合用于理解推荐算法落地、ORM操作及前后端交互流程。压缩包共含2000个文件以py源码及pyc编译文件为主辅以po/mo多语言文件、html模板、js/css前端资源及jpg图片素材结构完整包含Django项目运行所需的配置、静态资源与依赖组件包体大小约28.03MB。资源已有7816人学习下载热度较高。结合源码目录与预览信息读者可快速还原运行环境复用其中的数据库表设计、推荐逻辑与页面模板便于在此基础上进行功能扩展与毕业设计论文写作。1. 把电影推荐系统做成毕设Python、Django 与 MySQL 的一次完整落地选电影推荐系统当毕业设计的人不少但真正动手会发现协同过滤算法单独能跑通Django 能把网页渲染出来MySQL 能存数据三件事捏合到一起却是另一道坎。这套源码的价值就在这里——它把用户注册登录、电影展示、评分提交、基于用户的协同过滤推荐全部打通是「Python Django MySQL」三件套的一次完整落地。你拿到的不是零散的算法片段而是一个能直接启动、能改代码、能写进论文的毕设框架。适合正在做期末项目或毕业设计的学生也适合想快速搞懂 Django MTV 架构和推荐算法如何协作的 Python 开发者。与其从零搭三个月不如先把这个闭环跑起来再往深挖。2. 数据模型先行用三张核心表把 MySQL 和 Django ORM 对齐2.1 先看清楚 MTV 架构里每一层在干什么这套项目按 Django 标准的 MTV 模式组织Model 负责和 MySQL 表结构映射Template 负责页面渲染View 负责业务逻辑处理。相比传统 MVCDjango 的控制器职责被拆到 urls.py 和 view 两层——路由分发在 urls.py 里做具体业务处理在 view 函数里做。理解这一点是后续改代码的前提因为所有推荐算法的调用入口都在 view 层而你真正要调的算法代码放在独立的 recommend 应用里。解压源码后先看目录结构而不是急着执行 migrate。项目根目录下大概是这样的组织方式movie_recommend/ ├── manage.py ├── requirements.txt ├── movie_sys/ │ ├── __init__.py # 项目初始化pymysql 在这里注册 │ ├── settings.py # 数据库、APP、缓存等所有配置 │ ├── urls.py # 全项目路由入口 │ └── wsgi.py ├── apps/ │ ├── user/ # 用户注册登录模块 │ ├── movie/ # 电影列表、搜索、评分模块 │ └── recommend/ # 协同过滤算法模块 ├── static/ # CSS、JS、图片等静态资源 └── templates/ # Django 模板文件apps 目录把业务拆成了三个独立的应用这是 Django 项目后期扩展性的关键。user、movie、recommend 三个应用各自包含 models.py、views.py、urls.py、admin.py互不嵌套。新手容易犯的错是把所有 models 都堆在一个文件里导致后期维护时改一张表要牵连一堆视图函数。这套源码的拆分方式值得直接照抄算法相关的代码全部收拢在 recommend 应用里你替换推荐策略时不动其他任何模块。2.2 用户、电影、评分模型的字段设计为什么这么定推荐系统的数据基础就三张表用户表、电影表、评分表。这套源码里用户表直接继承 Django 自带的 AbstractUser而不是重新建一张孤立表这是很关键的设计决策。继承的好处是省去了自己实现密码哈希、权限分组、session 关联这些底层逻辑同时可以在子类上自由扩展字段。电影表保存的是元数据评分表是推荐算法唯一的数据来源三者关系如下from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): 继承 Django 内置用户模型追加业务字段 nickname models.CharField(max_length32, blankTrue, verbose_name昵称) avatar models.ImageField(upload_toavatar/, blankTrue, verbose_name头像) class Meta: verbose_name 用户 verbose_name_plural verbose_name def __str__(self): return self.username class Movie(models.Model): 电影信息表 title models.CharField(max_length128, verbose_name电影名) genres models.CharField(max_length255, verbose_name类型) release_year models.IntegerField(nullTrue, blankTrue, verbose_name上映年份) average_rating models.FloatField(default0.0, verbose_name平均评分) rating_count models.IntegerField(default0, verbose_name评分人数) poster_url models.URLField(blankTrue, verbose_name海报地址) description models.TextField(blankTrue, verbose_name剧情简介) class Meta: db_table movie verbose_name 电影 verbose_name_plural verbose_name def __str__(self): return self.title class Rating(models.Model): 用户-电影评分记录推荐算法的唯一输入 user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name用户) movie models.ForeignKey(Movie, on_deletemodels.CASCADE, verbose_name电影) score models.IntegerField(choices[(i, i) for i in range(1, 6)], verbose_name评分) create_time models.DateTimeField(auto_now_addTrue, verbose_name评分时间) class Meta: db_table rating unique_together (user, movie) verbose_name 评分 verbose_name_plural verbose_name def __str__(self): return f{self.user}-{self.movie}-{self.score}逐字段说明设计意图。score 限定 1 到 5 的分值而不是 10 分制是因为 5 分制配合皮尔逊相关系数计算时评分尺度更集中稀疏矩阵下的均值偏差更小。average_rating 和 rating_count 冗余存储在 Movie 表里虽然违背第三范式但换来了列表页无需实时聚合的高性能——这两个字段在每次评分提交后用 update 语句同步这个取舍在实际项目中非常实用。unique_together 确保了同一用户对同一电影只有一条评分记录这是后续算法不产生脏数据的基石。2.3 MySQL 连接配置字符集、连接池与驱动选择的细节Django 默认数据库是 SQLite毕设项目要求 MySQL那就要在 settings.py 里替换 DATABASES 配置同时解决驱动问题。Windows 环境下直接 pip install mysqlclient 大概率会编译失败这套源码的做法是用 pymysql 做兼容层让 Django 的 mysql 后端引擎照常工作。先看配置代码DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: movie_db, USER: root, PASSWORD: 你的数据库密码, HOST: 127.0.0.1, PORT: 3306, CONN_MAX_AGE: 60, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES, }, } }需要在项目包的init.py 里提前注册 pymysqlimport pymysql pymysql.install_as_MySQLdb()CONN_MAX_AGE 设置为 60 秒表示连接复用避免每个请求都重新握手这个参数在推荐接口被频繁调用时能明显降低延迟。OPTIONS 里的 charset 指定 utf8mb4是因为电影名里可能有特殊字符和 emojiMySQL 的 utf8 字符集存不下四个字节的字符必须用 utf8mb4。init_command 设置 STRICT_TRANS_TABLES 是为了让插入超长字符串时抛异常而不是静默截断方便尽早发现问题。3. 协同过滤落地UserCF 算法在 Django 里的完整实现3.1 为什么这个项目选择 UserCF 而不是 ItemCF基于用户的协同过滤UserCF和基于物品的协同过滤ItemCF是两种最经典的做法。ItemCF 先找「看过这部电影的人还看过什么」UserCF 先找「和我品味相似的人喜欢什么」。毕设场景里用户量一般只有几十到几百电影条目可能有几千ItemCF 需要预先计算两两电影之间的相似度矩阵维度是电影数的平方数据稀疏时矩阵极度稀疏冷启动更严重。UserCF 的计算量随用户数增长用户少时响应快而且推荐结果带有社交解释空间——「与你品味相似的人也喜欢」这个解释在论文里展示时更直观。这套源码选 UserCF 是合理取舍如果你后续想换 ItemCF只需要替换 recommend 应用里的核心函数业务层不用动。3.2 评分矩阵构建与皮尔逊相似度计算UserCF 的第一步是从 Rating 表读出全量数据构建「用户 - 电影 - 评分」的嵌套字典结构。这里有个性能关键点直接用 ORM 遍历所有 Rating 对象会产生大量 Python 对象内存开销大。正确做法是用 values() 只取需要的列把每条记录转成普通字典代码实现如下import math from collections import defaultdict from apps.movie.models import Rating def build_user_item_matrix(): 构建用户-物品评分矩阵返回 {uid: {mid: score}} matrix defaultdict(dict) rows Rating.objects.all().values(user_id, movie_id, score) for row in rows: matrix[row[user_id]][row[movie_id]] row[score] return matrix def pearson_similarity(user_a_ratings, user_b_ratings): 计算两个用户评分向量的皮尔逊相关系数 common set(user_a_ratings.keys()) set(user_b_ratings.keys()) if len(common) 2: return 0.0 avg_a sum(user_a_ratings[m] for m in common) / len(common) avg_b sum(user_b_ratings[m] for m in common) / len(common) numerator sum( (user_a_ratings[m] - avg_a) * (user_b_ratings[m] - avg_b) for m in common ) denom_a math.sqrt(sum((user_a_ratings[m] - avg_a) ** 2 for m in common)) denom_b math.sqrt(sum((user_b_ratings[m] - avg_b) ** 2 for m in common)) if denom_a 0 or denom_b 0: return 0.0 return numerator / (denom_a * denom_b)看到 common 集合少于两个时直接返回 0.0 这个细节了吗如果两个用户只共同看过一部电影皮尔逊公式的分子和分母都会退化算出的相关系数要么是 1 要么是 -1完全没有统计意义。这个保护判断是很多算法实现里容易漏掉的坑。计算均值时只统计共同评分过的电影而不是各自评分的全集这也是协同过滤的标准做法。矩阵构建函数里用 values() 而不是 all()能省掉 ORM 实例化对象的时间数据量到十万条评分时差别会非常明显。3.3 生成 Top-N 推荐并保持排序稳定有了相似度计算推荐生成就是三步找到相似用户、加权汇总他们看过的电影、排序截断。推荐结果要过滤掉当前用户已经看过的电影否则推荐页出现自己打过分的老电影会很奇怪。实现如下def recommend_for_user(user_id, top_k10): 基于 UserCF 生成 Top-N 推荐返回电影 ID 列表 matrix build_user_item_matrix() if user_id not in matrix: return [] current_user_ratings matrix[user_id] # 1. 计算当前用户与所有其他用户的相似度 similarity_list [] for other_uid, other_ratings in matrix.items(): if other_uid user_id: continue sim_score pearson_similarity(current_user_ratings, other_ratings) if sim_score 0: similarity_list.append((other_uid, sim_score)) # 2. 按相似度降序取前 20 个邻居 similarity_list.sort(keylambda x: x[1], reverseTrue) neighbors similarity_list[:20] # 3. 邻居的评分按相似度加权累加到候选电影上 recommend_scores defaultdict(float) for neighbor_uid, sim_value in neighbors: for movie_id, score in matrix[neighbor_uid].items(): if movie_id not in current_user_ratings: recommend_scores[movie_id] sim_value * score # 4. 按加权得分降序截取前 top_k ranked sorted(recommend_scores.items(), keylambda x: x[1], reverseTrue) return [movie_id for movie_id, _ in ranked[:top_k]]第 3 步的加权公式有两种可选一种是像上面这样直接用相似度乘以评分实现最简单另一种是用相似度乘上「评分减该用户平均分的差值」能消除不同用户打分尺度不一致的问题。如果你的评分数据里有人习惯全打 5 分有人习惯打 3 分第二种中心化方式更准确。源码保留了简单版本方便你理解主逻辑想改中心化版本只需把 score 替换成 score - avg_b。neighbors 取 20 是个经验值毕设数据量小取 10 到 30 差别不大但必须限制数量否则所有用户都参与加权计算推荐画面上会出现大量低分噪音电影。4. 视图层闭环从注册登录到推荐结果页面的完整请求链4.1 用户认证Session、CSRF 与登录装饰器推荐系统依赖个性化的用户评分数据所以认证模块必不可少。这套源码没有自己写密码比对逻辑直接用 Django 内置的 authenticate 和 login 函数CSRF 校验也交给模板里的 {% csrf_token %} 标签处理。登录视图的代码是标准写法但有几个细节值得注意from django.contrib.auth import authenticate, login from django.contrib.auth.decorators import login_required from django.shortcuts import render, redirect def user_login(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) user authenticate(request, usernameusername, passwordpassword) if user is not None: login(request, user) return redirect(movie:list) return render(request, user/login.html, {error: 用户名或密码错误}) return render(request, user/login.html) login_required def recommend_page(request): user request.user recommend_movie_ids recommend_for_user(user.id) movies Movie.objects.filter(id__inrecommend_movie_ids) # 保持推荐顺序不能用 ORM 默认的主键排序 movies sorted(movies, keylambda m: recommend_movie_ids.index(m.id)) return render(request, movie/recommend.html, {movies: movies})login_required 装饰器会在用户未登录时自动跳转到登录页比在每个视图里手动判断 request.user.is_authenticated 干净得多。urls.py 里需要配置 LOGIN_URL否则装饰器找不到跳转目标。利用 request.user 拿到当前登录用户的 id这是 UserCF 算法的输入起点。最后一个排序操作容易被忽略ORM 的 filter(id__in...) 返回的结果是按数据库主键排序的filter 列表打乱而推荐算法输出的列表是按得分排序的不重新排序页面上看到的 Top-N 就是乱的。4.2 电影列表与搜索ORM 条件查询和分页电影列表页用 Django 内置的 ListView 加上 Paginator 实现分页搜索则通过 request.GET 获取关键字后做模糊匹配。这里有一个隐含的性能坑搜索字段虽然是 genres但 genres 存的是「科幻/动作」这种组合字符串SQL 的 LIKE 查询无法命中索引。毕设数据量小可以接受但你要知道这是妥协。分页参数 per_page 设为 12正好凑成 3 列乘以 4 行的网格布局模板端直接每页展示一个数组。from django.core.paginator import Paginator from django.shortcuts import render from apps.movie.models import Movie def movie_list(request): keyword request.GET.get(keyword, ).strip() movies Movie.objects.all().order_by(-rating_count) if keyword: movies movies.filter(title__icontainskeyword) paginator Paginator(movies, 12) page_number request.GET.get(page, 1) page_obj paginator.get_page(page_number) return render(request, movie/list.html, {page_obj: page_obj, keyword: keyword})order_by(-rating_count) 让高分高热电影排在前面保证了没有登录时首页也有内容可看。get_page 方法能容错页码越界时返回最后一页不会抛 404。分页逻辑和关键字保持不变是通过 URL 传参实现的模板里需要手动拼接 keywordxxx 参数这是 Django 分页最常见的联调失误点。4.3 评分提交与推荐页拼接算法结果如何回到模板评分提交是唯一写操作直接影响推荐算法产出的数据。处理逻辑拆成三步查重、更新或创建、同步电影表的聚合字段。代码不长但顺序很关键from django.contrib.auth.decorators import login_required from django.views.decorators.http import require_POST from django.shortcuts import get_object_or_404, redirect from apps.movie.models import Movie, Rating from django.db.models import F, Avg login_required require_POST def submit_rating(request, movie_id): movie get_object_or_404(Movie, pkmovie_id) score int(request.POST.get(score, 0)) if score 1 or score 5: return redirect(movie:detail, movie_idmovie_id) rating, created Rating.objects.update_or_create( userrequest.user, moviemovie, defaults{score: score}, ) # 同步更新电影的平均分和评分人数 agg Rating.objects.filter(moviemovie).aggregate( avg_scoreAvg(score), count_scoreCount(id) ) movie.average_rating round(agg[avg_score], 1) movie.rating_count agg[count_score] movie.save() return redirect(movie:detail, movie_idmovie_id)update_or_create 把查重和写入合并成一次操作靠的就是模型里 unique_together 的约束。如果用先 filter 再 create 的写法在高并发下依然会触发完整性冲突update_or_create 则靠数据库唯一索引兜底。score 入参校验放在构造 ORM 操作之前防止伪造请求把非法值写进表里。Rating 表的每一条记录都是后续协同过滤的输入所以这个视图是算法数据质量的守门员任何脏数据入库都会直接污染相似度计算结果。5. 避坑指南这套电影推荐源码最容易翻车的五个地方5.1 环境层的三个高频坑驱动编译、认证插件和中文乱码坑一mysqlclient 在 Windows 下安装总是失败。现象执行 pip install mysqlclient 时报错提示 error: command gcc failed 或者找不到 mysql.h。原因是 mysqlclient 是 C 扩展包需要系统里有 MySQL C 客户端库和编译工具链不是 Python 层面的依赖问题。解决方法是放弃 mysqlclient改用 pymysql并在项目包init.py 里执行 install_as_MySQLdb()Django 配置保持不变底层自动走纯 Python 驱动。这是 Windows 开发环境最省事的方案。坑二连接 MySQL 8.0 时提示 Authentication plugin caching_sha2_password 无法加载。现象manage.py migrate 时报 django.db.utils.OperationalError错误信息指向 caching_sha2_password。原因是 MySQL 8.0 默认的认证插件较新而 pymysql 在部分版本下对这个插件的兼容有坑。解决方法是登录 MySQL 后执行 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;把用户认证方式改回传统模式。如果你用的 MySQL 版本是 5.7不会遇到这个问题但 8.0 及以上基本必踩。坑三电影表数据写入后MySQL 客户端里显示成问号。现象页面正常显示中文但用 Navicat 打开 movie 表看到的是三个问号。原因是建库时没指定字符集MySQL 默认走了 latin1。解决方法是先删库再重建执行 CREATE DATABASE movie_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;同时保证 settings.py 里 OPTIONS 配置了 charset: utf8mb4。这两个地方缺一不可只改一个都会出问题。数据已经污染的话用 ALTER TABLE 改字符集也无法还原已坏的中文。5.2 数据与算法层的两个隐藏坑唯一约束和全量计算坑四同一用户给同一部电影重复打分提交时报 IntegrityError。现象第二次点击评分按钮后页面直接 500控制台出现 duplicate entry 错误。原因是 Rating 表里定义了 unique_together (user, movie)但视图里没有处理已存在记录的情况。解决方法是使用 update_or_create像 4.3 节那样第一次执行时创建记录第二次及以后自动走更新路径。如果你是沿用网上的旧代码很多教程用 get_or_create它只能避免重复创建无法更新分数换汤不换药。坑五评分数据到 5 万条以后推荐页每次打开都要卡十几秒。现象用户点击「为我推荐」按钮后页面长时间白屏只有 admin 后台正常。原因是 recommend_for_user 每次请求都去数据库全表加载评分记录重新构建矩阵并且计算所有用户的两两相似度时间复杂度是 O(N^2)。解决方法是给矩阵加缓存首次计算后写入 Django cache设置 30 分钟过期用户发生新的评分操作时删除对应缓存键。更彻底的做法是把相似度计算做成离线任务每天定时重算一次但这个改造量对毕设来说过大加个缓存就够用了。6. 部署与验证别让推荐效果只停留在 localhost6.1 用留出法验证推荐质量别只靠肉眼判断推荐系统的好坏不能靠「推荐得挺准」这种感觉来评估。我建议你写个简单的脚本把评分数据按 8:2 拆成训练集和测试集训练集只用来构建用户评分矩阵和计算推荐列表测试集里用户真正看过的电影用来做验证。核心指标有三个准确率推荐列表里用户真正看过的占比、召回率用户看过的电影里被推荐出来的占比、覆盖率推荐出来的电影占电影总数的比例。采样代码片段如下from sklearn.model_selection import train_test_split from apps.movie.models import Rating from apps.recommend.core import recommend_for_user rows list(Rating.objects.values(user_id, movie_id, score)) train_rows, test_rows train_test_split(rows, test_size0.2, random_state42) def offline_evaluate(): 用训练集生成推荐用测试集打分 hits 0 total_recommend 0 total_actual 0 for uid in set(r[user_id] for r in train_rows): recommended recommend_for_user(uid, top_k10) watched_test {r[movie_id] for r in test_rows if r[user_id] uid} hits len(set(recommended) watched_test) total_recommend len(recommended) total_actual len(watched_test) precision hits / total_recommend recall hits / total_actual print(fprecision{precision:.3f}, recall{recall:.3f})注意这个离线脚本只评估算法本身不经过 Django 请求流程所以你必须在纯 Python 环境下能调用 recommend 模块这要求脚本从项目根目录启动且 DJANGO_SETTINGS_MODULE 环境变量已指向 movie_sys.settings。random_state 固定住任何改动后重跑的结果才可对比。数据量小的时候 precision 可能在 0.1 以下这很正常别慌重点是看算法调整后指标是否变好。6.2 生产部署的四个必改项本地能跑不代表部署后能跑。settings.py 里下面四项不改就直接上线翻车概率极高。以表格方式列出配置项本地开发值部署推荐值原因DEBUGTrueFalseDEBUG 开启会暴露完整 traceback任何人都能看到源码路径和数据库结构ALLOWED_HOSTS空列表[your_domain.com, 服务器IP]不设这个Django 直接拒绝所有请求数据库密码明文写在 settings.py用环境变量 os.environ.get 读取防止项目被传到 GitHub 后密码泄露静态文件开发环境自动处理python3 manage.py collectstatic不收集静态文件CSS 全部加载不出来最后提醒一个部署后最容易被忽略的行为git 初始化前先创建 .gitignore 文件把 db.sqlite3、settings_priv.py 这类含敏感信息的文件排除掉。这是一个很小的习惯能在你项目上线后避免数据库结构泄露和服务被扫描的麻烦。从那以后我每次拿到 Django 毕设源码都会强制自己走一遍「空库初始化 - 灌种子数据 - 单用户评分 - 看推荐结果」的四步冒烟流程确保核心链路在下行环境中同样可用。希望帮到你。本文还有配套的精品资源点击获取
返回列表