拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Django+MySQL协同过滤推荐系统(毕设可用)

Django+MySQL协同过滤推荐系统(毕设可用) 简介本资源是一套完整的基于协同过滤算法的电影推荐系统毕业设计实现面向Python Web开发初学者与数据挖掘实践者解决个性化推荐系统从理论到工程落地的学习痛点。项目采用Django 2.2.1框架构建Web界面后端整合Python 3.7、MySQL/SQLite双数据库支持完整实现了基于用户UserCF和基于物品ItemCF的两种协同过滤核心逻辑并内置Movielens公开数据集及豆瓣电影爬虫脚本支持本地部署与在线预览。压缩包共83个文件含28个核心Python模块如recommend_movies.py、populate_user_rate.py、16个HTML模板页、4个CSV数据文件、1个论文PDF及1个SQLite3数据库文件辅以详尽README、requirements依赖清单与技术文档总大小8.41MB。目前已有10607人学习下载读者可直接复现推荐流程、调试算法参数、理解Django推荐应用的典型分层结构models/views/templates/migrations并快速拓展至其他评分型推荐场景。1. 这不是又一个“Hello World”推荐系统它真能跑通冷启动实时评分MySQL事务一致性专为毕业设计卡点而生你手头正赶着毕设 deadline导师刚邮件催问“推荐模块有没有真实交互”而你本地 Django 页面点开就报OperationalError: (2002, Cant connect to local MySQL server through socket /tmp/mysql.sock)——别慌这不是玄学故障是这套基于协同过滤的电影推荐系统在向你喊话它不只是一堆.py文件而是一套完整闭环从用户注册登录、打分行为落库、相似度矩阵在线更新到首页实时召回 Top10全部压在 Python 3.7 Django 2.2.1 MySQL 5.7 的黄金组合上。它没用 Spark 或 Flink 做离线计算所有协同过滤逻辑用户-用户相似度、物品-物品相似度、加权平均预测全在 Django ORM 和原生 SQL 里硬刚它没把 MySQL 当只读缓存而是用transaction.atomic()保证打分写入与相似度重算的强一致性它甚至预留了ratings_last_updated字段为后续增量更新埋了钩子。适合那些需要交源码、能现场演示、答辩时能讲清“为什么选皮尔逊而非余弦”“为什么不用 Redis 缓存相似度矩阵”的同学——不是玩具是能跑进答辩 PPT 里的生产级最小可行体。2. 从零搭起推荐骨架Django 2.2.1 MySQL 5.7 环境落地实操2.1 为什么死磕 Django 2.2.1 而不是最新版三个硬约束必须守这套系统不是为炫技而存在它的版本选择全是被现实卡出来的Django 2.2.1 是最后一个 LTS长期支持版本官方维护到 2022 年 4 月意味着你在毕设答辩后半年内遇到安全补丁还能合法升级它对 Python 3.7 兼容性最稳——Python 3.8 的async/await在 Django 2.2 中尚未深度集成而本系统所有推荐逻辑都是同步阻塞式强行升 Python 版本反而触发django.db.utils.OperationalError: Lost connection to MySQL server during query最关键的是 MySQL 驱动兼容性Django 2.2.1 默认使用mysqlclient1.4.6这个版本能完美解析 MySQL 5.7 的utf8mb4字符集和ON UPDATE CURRENT_TIMESTAMP语法而 Django 3.x 要求mysqlclient2.0后者在 CentOS 7 上编译常因 OpenSSL 版本冲突失败毕设环境经不起折腾。提示别用pip install django直接装最新版执行pip install django2.2.1 mysqlclient1.4.6锁死版本这是血泪经验——我见过三届学生因版本漂移在答辩前夜重装环境。2.2 MySQL 5.7 安装与关键配置绕过/tmp/mysql.sock报错的实操路径error 2002 (hy000): cant connect to local mysql server through socket /tmp/mysql.sock是这套系统最常卡住新手的第一道墙。根本原因不是 MySQL 没装而是 Django 默认找/tmp/mysql.sock而多数 Linux 发行版如 Ubuntu 20.04、CentOS 7的 MySQL 5.7 实际 socket 文件在/var/run/mysqld/mysqld.sock。解决路径如下# 步骤1确认 MySQL 是否运行非 root 用户需 sudo sudo systemctl status mysql # 若未运行启动并设开机自启 sudo systemctl start mysql sudo systemctl enable mysql # 步骤2查真实 socket 路径关键 sudo mysql -u root -p -e SHOW VARIABLES LIKE socket; # 输出类似| socket | /var/run/mysqld/mysqld.sock | # 步骤3修改 Django settings.py 中 DATABASES 配置 # 注意host 必须写 127.0.0.1走 TCP不能写 localhost会触发 socket 查找 DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: movie_recommend, USER: rec_user, PASSWORD: your_strong_password, HOST: 127.0.0.1, # 强制走 TCP避开 socket 路径问题 PORT: 3306, OPTIONS: { init_command: SET sql_modeSTRICT_TRANS_TABLES, charset: utf8mb4, }, } }参数说明HOST: 127.0.0.1是绕过 socket 错误的核心——它让 Django 通过 TCP/IP 连接 MySQL完全忽略 socket 文件路径charset: utf8mb4必须显式声明否则中文电影名如《寄生虫》《小丑》入库会变????init_command设置严格模式防止INSERT INTO ratings VALUES (1,2,NULL)这类空值插入导致后续协同过滤计算崩溃。2.3 初始化数据库结构四张表如何支撑协同过滤闭环系统共定义四张核心表全部由 Django migrations 生成但必须手动校验字段精度——这是后续相似度计算不出结果的根源表名关键字段作用协同过滤依赖点auth_userid,username,emailDjango 内置用户表作为user_id外键关联评分行为movies_movieid(PK),title,genres,year电影元数据item_id来源genres字段用于混合推荐扩展ratings_ratinguser_id(FK),movie_id(FK),rating(DECIMAL(2,1)),timestamp用户打分记录核心数据源协同过滤所有计算基于此表similarity_user_similarityuser1_id,user2_id,similarity_score(DECIMAL(5,4))用户相似度缓存表存储皮尔逊相关系数避免每次请求都重算执行迁移命令前务必检查ratings_rating.rating字段是否为DECIMAL(2,1)范围 0.5~5.0步长 0.5# 进入 MySQL 命令行 mysql -u rec_user -p movie_recommend # 执行校验 DESCRIBE ratings_rating; # 正确输出应含| rating | decimal(2,1) | YES | | NULL | | # 若为 float 或 int立即修正毕设答辩时评委最爱问精度问题 ALTER TABLE ratings_rating MODIFY COLUMN rating DECIMAL(2,1) NOT NULL;3. 协同过滤算法落地皮尔逊相关系数在 Django 中的手动实现与优化3.1 为什么选皮尔逊而非余弦从电影评分数据特性倒推选型电影评分数据天然具备两个特征用户打分尺度差异极大A 用户习惯打 3~4 分B 用户只打 4~5 分直接算余弦相似度会把这种系统性偏移当作“偏好相似”稀疏性极高百万级电影中单个用户通常只评过 50~200 部共同评分项co-rated items往往 10。皮尔逊相关系数Pearson Correlation Coefficient通过中心化处理减去用户平均分消除尺度偏差公式为$$ r_{uv} \frac{\sum_{i \in I_{uv}} (r_{ui} - \bar{r}u)(r{vi} - \bar{r}v)}{\sqrt{\sum{i \in I_{uv}} (r_{ui} - \bar{r}u)^2} \sqrt{\sum{i \in I_{uv}} (r_{vi} - \bar{r}v)^2}} $$其中 $I{uv}$ 是用户 $u$ 和 $v$ 共同评分的电影集合$\bar{r}_u$ 是用户 $u$ 的平均分。Django 中不依赖scipy会引入额外依赖而是用原生 SQL Python 循环实现——这正是本系统可复现的关键。3.2 计算用户相似度SQL 预聚合 Python 后处理双阶段法为避免 N² 复杂度全量计算系统采用“按需计算 缓存”策略。核心函数calculate_user_similarity(u_id, v_id)流程如下# 文件recommend/utils.py from django.db import connection from decimal import Decimal def calculate_user_similarity(user1_id, user2_id): 计算用户 user1_id 与 user2_id 的皮尔逊相似度 返回 Decimal(5,4) 或 None若共同评分项 5 with connection.cursor() as cursor: # Step1SQL 预聚合——一次性取出两用户的全部评分及各自均值 cursor.execute( SELECT r1.movie_id, r1.rating as r1_rating, r2.rating as r2_rating, u1.avg_rating as u1_avg, u2.avg_rating as u2_avg FROM ratings_rating r1 INNER JOIN ratings_rating r2 ON r1.movie_id r2.movie_id INNER JOIN ( SELECT user_id, AVG(rating) as avg_rating FROM ratings_rating WHERE user_id %s GROUP BY user_id ) u1 ON r1.user_id u1.user_id INNER JOIN ( SELECT user_id, AVG(rating) as avg_rating FROM ratings_rating WHERE user_id %s GROUP BY user_id ) u2 ON r2.user_id u2.user_id WHERE r1.user_id %s AND r2.user_id %s , [user1_id, user2_id, user1_id, user2_id]) rows cursor.fetchall() if len(rows) 5: # 共同评分项少于5部相似度无统计意义 return None # Step2Python 端计算分子分母避免浮点误差全程用 Decimal numerator Decimal(0) sum_sq_u1 Decimal(0) sum_sq_u2 Decimal(0) for movie_id, r1_r, r2_r, u1_avg, u2_avg in rows: diff1 Decimal(str(r1_r)) - u1_avg diff2 Decimal(str(r2_r)) - u2_avg numerator diff1 * diff2 sum_sq_u1 diff1 * diff1 sum_sq_u2 diff2 * diff2 if sum_sq_u1 0 or sum_sq_u2 0: return Decimal(0) denominator (sum_sq_u1.sqrt() * sum_sq_u2.sqrt()) if denominator 0: return Decimal(0) similarity numerator / denominator return similarity.quantize(Decimal(0.0001)) # 保留4位小数关键设计点说明SQL 预聚合用INNER JOIN直接筛选出共同评分项比 Python 循环遍历全表快 10 倍以上Decimal 精度控制电影评分是离散值0.5, 1.0, ..., 5.0用float计算会导致0.1 0.2 ! 0.3类似误差Decimal保证数值稳定阈值拦截len(rows) 5直接返回None避免少量共同评分导致相似度虚高——这是答辩时解释“为什么有些用户没推荐”的硬依据。3.3 实时推荐生成基于相似用户的加权平均预测推荐接口/api/recommend/?user_id123的核心逻辑是查出与目标用户最相似的 Top 5 用户按similarity_score降序对每个未评分电影计算加权平均预测分$$ \hat{r}{ui} \bar{r}u \frac{\sum{v \in N^k(u)} sim{uv}(r_{vi} - \bar{r}v)}{\sum{v \in N^k(u)} |sim_{uv}|} $$其中 $N^k(u)$ 是用户 $u$ 的 Top k 相似用户集合。# 文件recommend/views.py from django.http import JsonResponse from .utils import calculate_user_similarity from django.db.models import Avg, Q def get_recommendations(request): user_id int(request.GET.get(user_id)) # Step1获取目标用户已评电影ID集合排除这些只推荐未评的 rated_movie_ids set( Rating.objects.filter(user_iduser_id).values_list(movie_id, flatTrue) ) # Step2查出Top5相似用户从缓存表读非实时计算 similar_users UserSimilarity.objects.filter( user1_iduser_id ).order_by(-similarity_score)[:5] # Step3对每个候选电影计算预测分 recommendations [] all_movies Movie.objects.all()[:100] # 限制候选集防超时 for movie in all_movies: if movie.id in rated_movie_ids: continue # 获取所有相似用户对该电影的评分 ratings_from_similar Rating.objects.filter( user_id__in[su.user2_id for su in similar_users], movie_idmovie.id ).select_related(user) if not ratings_from_similar: continue # 计算加权预测分 weighted_sum Decimal(0) weight_sum Decimal(0) user_avg_cache {} # 缓存相似用户的平均分避免重复查 for rating in ratings_from_similar: sim_score next( (su.similarity_score for su in similar_users if su.user2_id rating.user_id), Decimal(0) ) # 获取该相似用户的平均分缓存 if rating.user_id not in user_avg_cache: avg_rating Rating.objects.filter(user_idrating.user_id).aggregate(Avg(rating))[rating__avg] user_avg_cache[rating.user_id] avg_rating or Decimal(0) weighted_sum sim_score * (Decimal(str(rating.rating)) - user_avg_cache[rating.user_id]) weight_sum abs(sim_score) if weight_sum 0: pred_rating user_avg_cache.get(user_id, Decimal(0)) (weighted_sum / weight_sum) recommendations.append({ movie_id: movie.id, title: movie.title, predicted_rating: float(pred_rating.quantize(Decimal(0.1))) }) # Step4按预测分降序取Top10 recommendations.sort(keylambda x: x[predicted_rating], reverseTrue) return JsonResponse({recommendations: recommendations[:10]})参数说明all_movies[:100]是性能兜底——实际部署时应替换为热门电影池或基于genres的粗筛user_avg_cache避免对同一相似用户多次查平均分减少 DB 查询pred_rating.quantize(Decimal(0.1))强制四舍五入到 0.1符合电影评分惯例不显示 4.33333。4. 避坑指南五个让毕设答辩翻车的隐蔽雷区与解法4.1 现象Django 启动时报django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module原因mysqlclient编译失败常见于 Ubuntu 20.04缺libmysqlclient-dev或 CentOS 7缺mysql-devel。解决# Ubuntu/Debian sudo apt-get install python3-dev default-libmysqlclient-dev build-essential # CentOS/RHEL sudo yum install python3-devel mysql-devel gcc # 再重装 mysqlclient pip uninstall mysqlclient -y pip install mysqlclient1.4.64.2 现象用户打分后推荐列表完全不变similarity_user_similarity表为空原因系统默认不自动更新相似度缓存表需手动触发python manage.py update_similarities命令。解决在management/commands/update_similarities.py中确保handle()方法遍历所有用户对User.objects.all()调用calculate_user_similarity()并存入UserSimilarity答辩演示前必做python manage.py update_similarities --limit100先跑100对验证逻辑生产建议用 Celery 定时任务每日凌晨更新但毕设可简化为手动触发。4.3 现象MySQL 中ratings_rating表有数据但 Django Admin 里Rating模型显示Movie object (1)而非电影名原因Movie模型缺少__str__方法Admin 默认显示主键。解决在movies/models.py的Movie类中添加def __str__(self): return f{self.title} ({self.year})注意这是答辩时评委检查“模型设计规范性”的高频点漏写直接扣分。4.4 现象本地测试正常部署到服务器后推荐接口返回500 Internal Server Error日志显示max_allowed_packet原因MySQL 默认max_allowed_packet4M当相似用户较多时SQL 查询返回结果集超限。解决-- 登录 MySQL执行 SET GLOBAL max_allowed_packet 64*1024*1024; -- 永久生效编辑 /etc/mysql/mysql.conf.d/mysqld.cnf添加 [mysqld] max_allowed_packet 64M4.5 现象用户 A 给电影 X 打 5 分用户 B 给同一电影打 1 分但两人相似度算出来是 0.99原因皮尔逊公式中分母为 0某用户所有评分相同方差为 0代码未拦截。解决在calculate_user_similarity()函数末尾补充if sum_sq_u1 0 or sum_sq_u2 0: return Decimal(0) # 方差为0视为无相似性答辩话术“当用户评分高度一致如全打5分其偏好不具备区分度相似度归零是合理设计。”5. 毕设答辩加分技巧用一条 SQL 揭示协同过滤的本质逻辑5.1 用EXPLAIN分析推荐查询瓶颈现场展示优化过程答辩时当评委问“怎么证明你的推荐是实时的”别只说“用了 Django ORM”直接连上 MySQL执行这条命令EXPLAIN SELECT m.id, m.title, AVG(r.rating) as avg_rating, COUNT(r.id) as rating_count FROM movies_movie m LEFT JOIN ratings_rating r ON m.id r.movie_id GROUP BY m.id, m.title ORDER BY avg_rating DESC, rating_count DESC LIMIT 10;观察Extra列如果出现Using filesort或Using temporary说明排序未走索引——立刻建复合索引-- 为电影热度排序加速 CREATE INDEX idx_movie_rating ON ratings_rating(movie_id, rating); -- 为相似度查询加速 CREATE INDEX idx_similarity_user1 ON similarity_user_similarity(user1_id, similarity_score);价值点这展示了你不仅会调 API更懂数据库底层能把“推荐慢”归因到索引缺失而不是甩锅给算法。5.2 构造对比实验用真实数据验证协同过滤 vs 随机推荐毕设最容易被质疑“推荐真的有用吗”。准备三组数据Group A协同过滤用上述get_recommendations()接口生成Group B热门推荐SELECT movie_id FROM ratings_rating GROUP BY movie_id ORDER BY COUNT(*) DESC LIMIT 10Group C随机推荐SELECT id FROM movies_movie ORDER BY RAND() LIMIT 10。然后人工模拟 5 个用户对每组推荐的 10 部电影打分1~5统计平均分推荐类型用户1均分用户2均分用户3均分用户4均分用户5均分整体均分协同过滤4.23.84.04.13.94.0热门推荐3.53.23.03.33.13.2随机推荐2.11.92.32.02.22.1答辩话术“协同过滤比热门推荐提升 25% 用户满意度比随机推荐提升 90%——这证明算法捕捉到了用户隐含偏好不是简单统计。”5.3 给导师留个“可延展钩子”一句话点明工业级演进路径最后一页 PPT 不要写“感谢聆听”写一行代码加一句解释# 下一步用 Redis 缓存 UserSimilarity 表QPS 从 50→5000 # cache.set(fsimilarity:{u1}:{u2}, str(score), timeout3600)潜台词我知道这套系统当前是单机瓶颈但延展方案清晰——Redis 缓存相似度、Celery 异步更新、MySQL 分库分表都是可落地的工业级路径。导师会觉得你既有落地能力又有架构视野。从那以后我每次重构推荐逻辑都强制走一遍python manage.py update_similarities --dry-run干跑模式看 SQL 执行计划和耗时再决定要不要加索引或改算法。因为毕设不是交完代码就结束而是让每一行代码都能在答辩现场经得起EXPLAIN和SELECT的双重拷问。希望帮到你。本文还有配套的精品资源点击获取
返回列表