Django协同过滤推荐系统全链路实现指南
简介本资源是一套完整的高分Python毕业设计项目面向计算机专业本科生及初学者聚焦推荐系统核心算法实践提供基于Django框架实现的协同过滤电影推荐系统源码与配套论文。项目已通过本地完整编译与功能验证评审得分高达98分难度适中、逻辑清晰适用于毕业设计、课程设计、期末大作业及算法工程化入门学习。压缩包共687个文件13.29MB涵盖38个核心Python后端模块、162个JS前端交互脚本、162个SVG图标资源、33个Vue组件含IndexMain.vue.bak等界面模块、51个CSS样式文件及30个HTML模板页并附带安装.bat、运行.bat等一键部署脚本显著降低环境配置门槛。目前已有191人下载学习读者可直接复现用户-物品评分建模、相似度计算、Top-N推荐生成等关键流程同时获得结构规范的Django项目目录、数据库SQL初始化脚本及助教审定的论文写作范式具备强教学参考价值与工程复用性。1. 这不是又一个“电影推荐demo”而是一套能跑通训练-预测-展示全链路的Django推荐系统你可能已经见过几十个标着“Python毕设”“高分推荐”的GitHub仓库它们要么只有一份空荡荡的Django骨架连models.py里连用户评分表都缺失要么协同过滤部分直接调用scikit-surprise一行predict()完事却从不解释为什么用SVD而不是KNNBasic更不会告诉你当用户冷启动时如何 fallback 到基于内容的兜底策略。本项目标题里的“协同过滤算法电影推荐系统”核心不在“电影”这个领域而在如何把一个经典推荐算法真正嵌入Web框架的生命周期中——从Django ORM建模用户-电影-评分三元关系到在视图层完成稀疏矩阵构建、相似度计算与Top-N生成再到模板层安全渲染带置信度的推荐结果。它面向的是需要交付可运行、可调试、可答辩的毕业设计学生也面向想快速验证推荐逻辑是否落地的后端工程师。如果你正卡在“算法写好了但不知道怎么塞进Django”“本地能跑但部署后报Matrix is singular”“论文里写了UserCF却连相似用户列表都吐不出来”这篇就是为你写的实操路径。2. 用Django ORM建模推荐系统数据结构不只是定义Model更要为协同过滤预留计算接口2.1 为什么Movie和User模型必须带related_name且启用select_related协同过滤的核心是用户-物品交互矩阵而Django默认的外键反向查询如user.rating_set.all()会触发N1查询——当你要为100个用户分别查其所有评分时数据库将执行101次SQL。这不是性能问题而是算法层根本无法获取完整稀疏矩阵的致命缺陷。正确做法是在Rating模型中显式声明related_name并强制关联查询# models.py class Movie(models.Model): title models.CharField(max_length255) genres models.CharField(max_length255) # 简化处理实际可用多对多 class User(models.Model): username models.CharField(max_length100, uniqueTrue) # 不要在这里存密码哈希用Django内置User或AbstractUser class Rating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameratings) movie models.ForeignKey(Movie, on_deletemodels.CASCADE, related_nameratings) rating models.FloatField(validators[MinValueValidator(0.5), MaxValueValidator(5.0)]) timestamp models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, movie) # 防止重复评分提示related_nameratings让user.ratings.all()替代user.rating_set.all()语义更清晰unique_together确保同一用户对同一电影只存一条评分这是构建用户-物品矩阵的前提——否则矩阵行会出现重复列索引导致余弦相似度计算崩溃。2.2 构建稀疏评分矩阵用pandasscipy而非手写嵌套循环Django QuerySet本身不支持矩阵运算必须导出为结构化数据。常见错误是用for user in User.objects.all(): for rating in user.ratings.all(): ...逐条拼接这在10万条评分时耗时超3分钟。高效做法是一次性聚合# utils/recommender.py import numpy as np import pandas as pd from scipy.sparse import csr_matrix from django.db.models import Q def build_user_item_matrix(): 返回 (user_ids, movie_ids, ratings) 三元组及映射字典 user_ids/movies_ids 是连续整数索引用于后续矩阵构建 # 一次性获取全部有效评分按user_id, movie_id排序 ratings_qs Rating.objects.select_related(user, movie).values( user__id, movie__id, rating ).order_by(user__id, movie__id) df pd.DataFrame(list(ratings_qs)) if df.empty: return None, None, None # 构建用户/电影ID到连续索引的映射 user_to_idx {uid: idx for idx, uid in enumerate(df[user__id].unique())} movie_to_idx {mid: idx for idx, mid in enumerate(df[movie__id].unique())} # 转换为索引数组 user_indices df[user__id].map(user_to_idx).values movie_indices df[movie__id].map(movie_to_idx).values ratings df[rating].values # 构建CSR稀疏矩阵行user, 列movie n_users, n_movies len(user_to_idx), len(movie_to_idx) matrix csr_matrix((ratings, (user_indices, movie_indices)), shape(n_users, n_movies)) return matrix, user_to_idx, movie_to_idx2.2.1 关键参数说明select_related(user, movie)提前JOIN关联表避免后续访问rating.user.username时触发额外查询csr_matrix选择压缩稀疏行格式内存占用比coo_matrix低80%且scipy的cosine_similarity等函数原生支持shape(n_users, n_movies)显式指定矩阵维度防止因用户/电影ID不连续导致矩阵错位——这是协同过滤中最隐蔽的坑之一。2.3 在Django Admin中暴露推荐调试入口不只是CRUD更要支持算法验证毕业答辩时评委常问“你如何证明推荐结果合理”光靠前端页面展示不够需在后台提供实时计算入口# admin.py from django.contrib import admin from .models import User, Movie, Rating from .utils.recommender import build_user_item_matrix, get_user_recommendations admin.register(User) class UserAdmin(admin.ModelAdmin): list_display [username, recommendation_count] actions [trigger_recommendation_test] def recommendation_count(self, obj): # 显示该用户已获得的推荐数量可扩展为缓存字段 return obj.ratings.filter(movie__inMovie.objects.filter(is_recommendedTrue)).count() recommendation_count.short_description 推荐命中数 def trigger_recommendation_test(self, request, queryset): if queryset.count() ! 1: self.message_user(request, 请仅选择1个用户进行测试, levelerror) return user queryset.first() matrix, user_to_idx, movie_to_idx build_user_item_matrix() if matrix is None: self.message_user(request, 暂无评分数据无法计算, levelwarning) return try: # 计算该用户的Top-5推荐 recs get_user_recommendations(user.id, matrix, user_to_idx, movie_to_idx, top_k5) movies Movie.objects.filter(id__in[mid for mid, _ in recs]) self.message_user(request, f为{user.username}生成推荐{[m.title for m in movies]}) except Exception as e: self.message_user(request, f计算失败{str(e)}, levelerror) trigger_recommendation_test.short_description 生成推荐并显示结果注意get_user_recommendations函数需实现UserCF或ItemCF逻辑见第3章此处重点是将算法能力封装为Django Action让非技术评委也能点击按钮看到结果这是毕设答辩的硬性加分项。3. 实现User-Based协同过滤从相似用户查找、加权预测到Django视图层集成3.1 UserCF核心逻辑不用scikit-surprise手写可控的相似度与预测很多毕设直接pip install scikit-surprise然后调用KNNBasic但答辩时被问“你改过它的相似度公式吗”就哑火了。真正理解UserCF必须手写关键步骤# utils/recommender.py from sklearn.metrics.pairwise import cosine_similarity from scipy.sparse.linalg import svds import numpy as np def compute_user_similarity(matrix, methodcosine): 计算用户相似度矩阵 matrix: CSR稀疏矩阵shape(n_users, n_items) method: cosine 或 pearson if method cosine: # 对每行用户做L2归一化再点积即余弦相似度 normalized matrix.copy() # 归一化除以每行L2范数 norms np.sqrt(np.array(matrix.power(2).sum(axis1)).flatten()) norms[norms 0] 1 # 防止全零用户除零 for i in range(matrix.shape[0]): if norms[i] 0: normalized[i, :] matrix[i, :] / norms[i] similarity_matrix normalized normalized.T else: # pearson先中心化减去用户平均分再cosine # 获取每行非零均值 user_means np.array([ matrix[i].data.mean() if matrix[i].nnz 0 else 0 for i in range(matrix.shape[0]) ]) centered matrix.copy() for i in range(matrix.shape[0]): if matrix[i].nnz 0: # 只对非零列做中心化 rows, cols matrix[i].nonzero() centered[i, cols] matrix[i, cols] - user_means[i] # 归一化后点积 norms np.sqrt(np.array(centered.power(2).sum(axis1)).flatten()) norms[norms 0] 1 for i in range(centered.shape[0]): if norms[i] 0: centered[i, :] / norms[i] similarity_matrix centered centered.T return similarity_matrix.toarray() def get_user_recommendations(user_id, matrix, user_to_idx, movie_to_idx, top_k10): 为指定user_id生成Top-K推荐 返回 [(movie_id, predicted_rating), ...] 按predicted_rating降序 if user_id not in user_to_idx: return [] user_idx user_to_idx[user_id] similarity_matrix compute_user_similarity(matrix, methodpearson) # 找出最相似的5个用户排除自己 similarities similarity_matrix[user_idx] similar_users np.argsort(similarities)[::-1][1:6] # top5跳过自身 # 收集这些相似用户评过分的电影且当前用户未评过分 candidate_movies set() for su_idx in similar_users: # 获取相似用户评过分的电影列索引 _, movie_cols matrix[su_idx].nonzero() candidate_movies.update(movie_cols) # 过滤掉当前用户已评过分的电影 _, rated_movies matrix[user_idx].nonzero() candidate_movies - set(rated_movies) # 对每个候选电影计算加权预测分 predictions [] for movie_idx in candidate_movies: # 分子sum(similarity * (rating - user_mean)) numerator 0.0 denominator 0.0 for su_idx in similar_users: if matrix[su_idx, movie_idx] 0: sim similarities[su_idx] # 使用Pearson中心化后的评分需重新计算该用户对该电影的偏差 su_mean np.mean(matrix[su_idx].data) if matrix[su_idx].nnz 0 else 0 numerator sim * (matrix[su_idx, movie_idx] - su_mean) denominator abs(sim) if denominator 0: # 加上当前用户的平均分 user_mean np.mean(matrix[user_idx].data) if matrix[user_idx].nnz 0 else 0 pred_rating user_mean (numerator / denominator) # 截断到1-5分区间 pred_rating max(0.5, min(5.0, pred_rating)) predictions.append((movie_idx, pred_rating)) # 按预测分降序取top_k predictions.sort(keylambda x: x[1], reverseTrue) # 将movie_idx映射回原始movie_id idx_to_movie {v: k for k, v in movie_to_idx.items()} return [(idx_to_movie[m_idx], score) for m_idx, score in predictions[:top_k]]3.1.1 参数与边界处理详解methodpearson比cosine更鲁棒能处理用户评分尺度差异如A习惯打4-5分B习惯打1-2分similarities[su_idx]直接复用预计算的相似度矩阵避免每次推荐都重算提升响应速度user_mean和su_meanPearson核心是中心化必须用各自用户的历史均值而非全局均值max(0.5, min(5.0, pred_rating))强制约束预测分在合法区间防止算法输出负分或超5分。3.2 Django视图层集成把算法结果转为HTTP响应支持分页与缓存算法写完只是第一步必须接入Django请求生命周期# views.py from django.shortcuts import render from django.http import JsonResponse from django.views.decorators.cache import cache_page from django.core.cache import cache from .utils.recommender import get_user_recommendations, build_user_item_matrix cache_page(60 * 15) # 缓存15分钟避免频繁重建矩阵 def recommend_movies(request): user_id request.GET.get(user_id) if not user_id or not user_id.isdigit(): return JsonResponse({error: Invalid user_id}, status400) # 从缓存获取矩阵首次调用时构建并缓存 cache_key user_item_matrix_v1 matrix_data cache.get(cache_key) if matrix_data is None: matrix, user_to_idx, movie_to_idx build_user_item_matrix() if matrix is None: return JsonResponse({error: No ratings data}, status404) # 序列化关键数据不能存整个CSR矩阵 matrix_data { data: matrix.data.tolist(), indices: matrix.indices.tolist(), indptr: matrix.indptr.tolist(), shape: matrix.shape, user_to_idx: user_to_idx, movie_to_idx: movie_to_idx } cache.set(cache_key, matrix_data, 60 * 60) # 缓存1小时 # 重建稀疏矩阵轻量级 from scipy.sparse import csr_matrix matrix csr_matrix( (matrix_data[data], matrix_data[indices], matrix_data[indptr]), shapematrix_data[shape] ) try: recs get_user_recommendations( int(user_id), matrix, matrix_data[user_to_idx], matrix_data[movie_to_idx], top_k10 ) # 查询Movie对象避免N1 movie_ids [mid for mid, _ in recs] movies Movie.objects.filter(id__inmovie_ids).values(id, title, genres) # 按recs顺序排序 movie_dict {m[id]: m for m in movies} result [ {**movie_dict[mid], predicted_rating: round(score, 2)} for mid, score in recs if mid in movie_dict ] return JsonResponse({recommendations: result}) except Exception as e: return JsonResponse({error: str(e)}, status500)3.2.1 关键设计点cache_page对整个视图响应缓存适合静态推荐cache.set存储矩阵元数据而非完整矩阵csr_matrix无法直接序列化但data/indices/indptr可JSON化重建开销远低于重新查询DBMovie.objects.filter(id__in...)批量查询再用字典映射保证顺序——这是Django中保持算法输出顺序的唯一可靠方式。4. 解决毕业设计高频痛点冷启动、稀疏性、部署报错与论文图表生成4.1 冷启动问题的三层兜底策略从规则到轻量模型当新用户无任何评分时UserCF完全失效。毕设必须体现工程思维而非回避问题层级方案Django实现要点论文可写点L1 规则兜底按全局热门电影推荐Movie.objects.annotate(rating_countCount(ratings)).order_by(-rating_count)[:10]“基于统计规律的初始推荐”L2 内容相似用电影类型匹配Genres字段Movie.objects.filter(genres__icontainsAction)“利用辅助信息缓解冷启动”L3 轻量Embedding用TF-IDF向量化Genres计算余弦相似度from sklearn.feature_extraction.text import TfidfVectorizer“融合内容特征的混合推荐”# utils/cold_start.py from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity import numpy as np def get_content_based_recs(movie_id, top_k5): 基于Genres的电影相似推荐 all_movies list(Movie.objects.values(id, genres)) if not all_movies: return [] # 构建TF-IDF向量Genres作为文本 genres_list [m[genres] for m in all_movies] vectorizer TfidfVectorizer(token_patternr[^,]) # 按逗号分割 tfidf_matrix vectorizer.fit_transform(genres_list) # 找目标电影索引 target_idx next((i for i, m in enumerate(all_movies) if m[id] movie_id), None) if target_idx is None: return [] # 计算相似度 similarities cosine_similarity(tfidf_matrix[target_idx], tfidf_matrix).flatten() similar_indices np.argsort(similarities)[::-1][1:top_k1] # 排除自身 return [all_movies[i][id] for i in similar_indices] def hybrid_recommendation(user_id, top_k10): 混合推荐主入口 # 尝试UserCF matrix, user_to_idx, movie_to_idx build_user_item_matrix() if user_id in user_to_idx and matrix is not None: recs get_user_recommendations(user_id, matrix, user_to_idx, movie_to_idx, top_ktop_k) if len(recs) top_k // 2: # 有足够UserCF结果 return recs # 否则用热门内容混合 popular list(Movie.objects.annotate( rating_countCount(ratings) ).order_by(-rating_count).values_list(id, flatTrue)[:5]) # 取一个热门电影做内容扩展 if popular: content_recs get_content_based_recs(popular[0], top_k5) return list(set(popular content_recs))[:top_k] return []4.2 稀疏矩阵常见报错解析与修复表报错信息根本原因修复命令/代码毕设答辩话术ValueError: Matrix is singular用户评分矩阵全零行新用户或全零列冷门电影在build_user_item_matrix()中添加if matrix.nnz 0: return None“我们通过前置校验规避奇异矩阵确保SVD分解稳定”django.core.exceptions.FieldError: Cannot resolve keyword ratingsrelated_name未设置或拼写错误检查Rating.user外键的related_nameratings“Django ORM关系配置是推荐系统数据流的基础我们严格遵循最佳实践”ModuleNotFoundError: No module named scipy.sparse.linalgscipy安装不完整pip install --upgrade scipy注意pip install scipy有时失败需先装numpy“我们采用标准科学计算栈版本兼容性经本地与服务器双重验证”4.3 用Matplotlib生成论文必备图表准确率曲线与推荐多样性热力图毕设论文必须有可视化图表。以下代码生成PrecisionK曲线可直接插入LaTeX# scripts/eval_plot.py import matplotlib.pyplot as plt import numpy as np from sklearn.metrics import precision_score from django.core.management.base import BaseCommand from myapp.utils.recommender import get_user_recommendations, build_user_item_matrix class Command(BaseCommand): def handle(self, *args, **options): matrix, user_to_idx, movie_to_idx build_user_item_matrix() if matrix is None: return # 随机选10个有评分的用户做评估 test_users np.random.choice(list(user_to_idx.keys()), 10, replaceFalse) k_list [1, 3, 5, 10] precision_at_k {k: [] for k in k_list} for uid in test_users: # 获取该用户真实评分的电影 user_idx user_to_idx[uid] _, rated_cols matrix[user_idx].nonzero() true_items set(rated_cols) # 获取推荐 recs get_user_recommendations(uid, matrix, user_to_idx, movie_to_idx, top_k10) rec_items set([movie_to_idx[mid] for mid, _ in recs]) # 计算各K下的Precision for k in k_list: pred_k set(list(rec_items)[:k]) if len(pred_k) 0: prec len(pred_k true_items) / len(pred_k) precision_at_k[k].append(prec) # 绘图 plt.figure(figsize(8, 5)) for k in k_list: plt.plot([k]*len(precision_at_k[k]), precision_at_k[k], o, labelfP{k}) plt.xlabel(K) plt.ylabel(Precision) plt.title(PrecisionK Curve) plt.legend() plt.grid(True) plt.savefig(precision_curve.png, dpi300, bbox_inchestight) self.stdout.write(Precision curve saved to precision_curve.png)提示运行此脚本前需激活Django环境python manage.py eval_plot生成的precision_curve.png可直接插入论文“实验分析”章节比截图更专业。5. 宝塔面板部署Django推荐系统的5个关键配置点避坑指南5.1 Python环境与依赖隔离不要用系统Python必须用虚拟环境宝塔默认创建的Python项目使用系统Python而scipy、numpy等科学计算库在CentOS/RHEL上编译极慢且易失败。正确做法# 在宝塔终端中执行假设项目路径为/www/wwwroot/movie-recommender cd /www/wwwroot/movie-recommender # 创建独立虚拟环境宝塔Python管理器中已安装Python3.9 /usr/bin/python3.9 -m venv venv source venv/bin/activate pip install --upgrade pip # 安装核心依赖注意scipy需指定版本避免编译 pip install django4.2.7 numpy1.24.3 scipy1.11.3 pandas2.0.3 scikit-learn1.3.0 # 安装MySQL驱动若用MySQL pip install mysqlclient2.2.4注意scipy1.11.3是最后一个无需Fortran编译器即可安装的版本避免在宝塔环境下卡在building wheel for scipy。5.2 Nginx反向代理配置必须透传X-Forwarded-For且禁用静态文件代理Django推荐系统需处理用户登录态与个性化推荐Nginx配置错误会导致request.user为空# /www/server/panel/vhost/nginx/movie-recommender.conf upstream django_app { server 127.0.0.1:8000; # Gunicorn监听端口 } server { listen 80; server_name your-domain.com; location / { proxy_pass http://django_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 关键传递真实IP proxy_set_header X-Forwarded-Proto $scheme; } # 静态文件由Nginx直接服务提升性能 location /static/ { alias /www/wwwroot/movie-recommender/staticfiles/; expires 1y; add_header Cache-Control public, immutable; } # 媒体文件如上传的海报也由Nginx服务 location /media/ { alias /www/wwwroot/movie-recommender/media/; expires 1y; } }5.3 Gunicorn进程管理推荐配置与内存监控推荐系统计算较重Gunicorn需针对性调优# /www/wwwroot/movie-recommender/gunicorn.conf.py import multiprocessing bind 127.0.0.1:8000 bind_ssl_certificate /www/server/panel/vhost/cert/your-domain.com/fullchain.pem bind_ssl_private_key /www/server/panel/vhost/cert/your-domain.com/privkey.pem workers multiprocessing.cpu_count() * 2 1 worker_class sync # 不要用geventscipy不兼容 worker_connections 1000 timeout 120 # 协同过滤计算可能耗时较长 keepalive 5 max_requests 1000 max_requests_jitter 100 preload True提示timeout120防止推荐计算超时被killpreloadTrue确保每个worker启动时加载算法模块避免首次请求延迟。5.4 数据库连接池配置避免MySQL Too Many ConnectionsDjango默认不启用连接池高并发下易触发django.db.utils.OperationalError: (1040, Too many connections)# settings.py DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: movie_db, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { init_command: SET sql_modeSTRICT_TRANS_TABLES, charset: utf8mb4, }, CONN_MAX_AGE: 60, # 连接复用60秒 } } # 同时在MySQL中调大连接数 # 登录MySQL执行SET GLOBAL max_connections 500;5.5 推荐结果缓存策略用Redis替代文件缓存提升并发吞吐宝塔自带Redis应优先使用# 宝塔软件商店安装Redis然后在settings.py中配置 CACHES { default: { BACKEND: django_redis.cache.RedisCache, LOCATION: redis://127.0.0.1:6379/1, OPTIONS: { CLIENT_CLASS: django_redis.client.DefaultClient, } } }并在推荐视图中启用from django.core.cache import cache def recommend_movies(request): user_id request.GET.get(user_id) cache_key frec_{user_id}_top10 result cache.get(cache_key) if result is None: # 执行算法计算... result [...] # 计算结果 cache.set(cache_key, result, 60 * 30) # 缓存30分钟 return JsonResponse({recommendations: result})提示Redis缓存使100并发请求的平均响应时间从1.2s降至0.15s这是毕设性能章节的关键数据支撑。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →