恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于Django与协同过滤算法:从零构建电影推荐系统实战
首页
资讯中心
/
基于Django与协同过滤算法:从零构建电影推荐系统实战
基于Django与协同过滤算法:从零构建电影推荐系统实战
发布时间:2026/9/3 3:14:27
简介这是一套面向Python与Web开发初学者及进阶学习者的电影推荐系统实战项目源码聚焦协同过滤算法在Django框架下的工程化落地适用于课程设计、毕业设计或推荐系统入门实践。资源共725个文件总大小37.65MB涵盖39个核心Python后端逻辑文件含数据处理、推荐算法与Django视图、41个Vue前端组件、164个JavaScript交互脚本、53个CSS样式文件及大量SVG图标与静态图片资源完整呈现前后端分离架构下的推荐系统开发全流程。已有447人学习下载项目包含可直接运行的安装/启动/构建批处理脚本.bat并保留了.bak备份文件与多版本静态资源便于理解迭代过程与调试逻辑目录结构清晰模块划分明确特别适合通过真实业务场景掌握Django ORM、MySQL集成、用户行为建模及前端动态渲染等关键技术点。1. 项目概述与核心价值最近几年无论是刷短视频还是逛电商你肯定没少被“猜你喜欢”骚扰过。这背后推荐系统功不可没。今天我们不聊那些大厂的复杂架构就从一个开发者最务实、最接地气的角度出发聊聊如何亲手搭建一个属于自己的电影推荐系统。这个项目我选择用Django和Python来实现原因很简单Django是Python生态里“开箱即用”的典范它能帮你快速搞定用户认证、后台管理这些繁琐但必需的模块让你能把精力真正集中在推荐算法的核心逻辑上。而Python作为数据科学和机器学习领域的“头号玩家”其丰富的库像pandas, numpy, scikit-learn让实现各种推荐算法变得像搭积木一样简单。这个项目适合谁呢如果你是刚学完Python和Django基础想找个有挑战性的综合项目来练手那它再合适不过了。它能让你把Web开发、数据库设计、算法应用串起来形成一个完整的知识闭环。对于有一定经验、想深入了解推荐系统基本原理的开发者这也是一个绝佳的“麻雀虽小五脏俱全”的实践案例。我们不会上来就搞复杂的深度学习模型而是从最经典、最易理解的协同过滤算法开始一步步构建一个能实际运行、给出推荐结果的Web应用。整个过程你会清晰地看到数据如何流动算法如何工作以及结果如何呈现给用户。2. 系统整体设计与技术选型考量2.1 为什么是Django Python在动手之前我们先聊聊为什么选这个技术栈这比直接写代码更重要。很多新手会纠结于技术选型我的经验是没有最好的技术只有最适合场景和团队的技术。首先看Django。它是一个“大而全”的高级Web框架遵循“约定优于配置”的原则。这意味着它为你预设好了一套最佳实践比如MVTModel-View-Template架构、自带ORM对象关系映射、强大的Admin后台、用户认证系统等。对于电影推荐系统这种需要管理用户、电影、评分等多种数据关系的项目Django的ORM能让你用Python类来定义数据表完全不用写繁琐的SQL语句开发效率极高。它的Admin后台更是“神器”项目初期你几乎不用自己写任何管理页面就能对数据库进行增删改查非常适合快速原型验证。再说Python。在推荐系统领域Python几乎是唯一的选择。其核心优势在于庞大的数据科学生态。实现协同过滤你需要计算用户或物品之间的相似度numpy提供了高效的数组运算你需要处理数据表格pandas能让你像操作Excel一样轻松你需要实现机器学习算法scikit-learn里集成了大量经典算法甚至包括协同过滤的变种。用Python你可以用最简洁的代码表达复杂的数学逻辑。注意虽然Django很强大但它也比较“重”。如果你的项目极端追求性能或需要微服务架构可能会考虑Flask或FastAPI。但对于我们这个以学习和快速实现为核心目标的电影推荐系统Django的综合优势是压倒性的。2.2 系统架构与核心模块拆解一个完整的电影推荐系统远不止一个算法那么简单。我们需要把它拆解成几个松耦合的模块这样代码结构清晰也便于后续维护和扩展。我设计的核心架构分为四层数据层这是系统的基石。负责存储所有数据包括电影信息标题、类型、年份等、用户信息、以及最重要的用户-电影评分数据。我们将使用Django的Model来定义这些数据结构并利用ORM与数据库如SQLite或PostgreSQL交互。算法层这是系统的大脑。核心推荐逻辑在这里实现。我们会先实现基于用户的协同过滤和基于物品的协同过滤。这一层会从数据层读取评分矩阵进行计算并输出推荐结果电影ID列表。业务逻辑层这是系统的中枢。它接收来自用户界面的请求比如“给我推荐电影”调用算法层获取结果然后结合数据层获取电影的详细信息如海报、简介组装成最终呈现给用户的数据。在Django中这部分逻辑通常写在View视图函数或类中。表现层这是系统的脸面。负责将数据渲染成用户看到的网页。我们会使用Django的Template模板语言结合HTML/CSS/JavaScript可以引入Bootstrap等前端框架让页面更美观来构建用户界面包括电影列表页、详情页、评分页面和最重要的推荐结果展示页。各层之间通过清晰的接口调用数据层为算法层和业务层提供数据服务算法层为业务层提供“智力”支持业务层协调各方最终通过表现层交付价值。这种分层设计确保了当你想更换推荐算法比如从协同过滤换成基于内容的推荐时只需要修改算法层其他层几乎不用动。3. 数据模型设计与核心算法原理3.1 数据库模型设计详解好的开始是成功的一半数据库设计就是这“一半”。我们的核心实体有三个User用户、Movie电影、Rating评分。Django已经内置了功能强大的User模型我们通常直接使用或扩展它这里为了简化我们先使用内置用户模型。首先定义Movie模型。一部电影有哪些属性是推荐系统关心的标题、类型流派、上映年份、简介、海报链接是基础。更重要的是为了后续可能实现基于内容的推荐我们需要把电影类型这种多值字段处理好。# models.py from django.db import models class Movie(models.Model): # 基础信息 title models.CharField(max_length200, verbose_name电影标题) year models.IntegerField(verbose_name上映年份, nullTrue, blankTrue) description models.TextField(verbose_name简介, blankTrue) poster_url models.URLField(verbose_name海报链接, max_length500, blankTrue) # 关键电影类型。一部电影可能属于多个类型如“动作科幻”。 # 这里我们存储为逗号分隔的字符串如 Action,Sci-Fi,Adventure # 更规范的做法是建立独立的Genre模型和多对多关系但为简化起步先这样处理。 genres models.CharField(max_length100, verbose_name类型, blankTrue) # 冗余字段用于简化基于内容的相似度计算可选 # 可以将genres字段向量化后存储避免每次实时计算 # genre_vector models.BinaryField(nullTrue, blankTrue) def __str__(self): return f{self.title} ({self.year})接下来是核心的Rating模型。它连接了User和Movie记录了用户对电影的评分比如1-5分。这个模型是协同过滤算法的“燃料”。# models.py from django.contrib.auth.models import User class Rating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name用户) movie models.ForeignKey(Movie, on_deletemodels.CASCADE, verbose_name电影) # 评分通常为1-5的整数或0.5的倍数如3.5 score models.FloatField(verbose_name评分) created_at models.DateTimeField(auto_now_addTrue, verbose_name评分时间) class Meta: # 确保一个用户对同一部电影只能评分一次 unique_together (user, movie) verbose_name 用户评分 def __str__(self): return f{self.user.username} 给 《{self.movie.title}》 评分{self.score}实操心得在项目初期使用CharField存储逗号分隔的类型字符串是快速验证想法的好办法。但当系统复杂后强烈建议拆分成独立的Genre模型并使用ManyToManyField。因为查询“所有科幻电影”时用字符串LIKE查询效率低下且有误判风险比如“科幻喜剧”会被“科幻”匹配到。先跑通流程再优化结构是敏捷开发的关键。3.2 协同过滤算法原理解析与选择推荐算法的核心是“物以类聚人以群分”。协同过滤正是基于这一思想它分为两大类基于用户的协同过滤假设“兴趣相似的用户喜欢的东西也相似”。算法步骤是找到与目标用户A评分行为最相似的K个用户邻居。将这些邻居喜欢高评分、但用户A尚未看过的电影根据邻居的相似度加权推荐给A。基于物品的协同过滤假设“喜欢物品A的用户也喜欢物品B”。这是目前工业界更主流的方法因为物品的相似度相对用户的兴趣更稳定。算法步骤是计算电影之间的相似度基于所有用户对它们的评分。对于目标用户A评分高的电影找出与这些电影最相似的、且用户A未看过的电影加权后推荐。我们如何计算相似度最常用的是余弦相似度和皮尔逊相关系数。想象一个多维空间每个用户或电影都是一个向量向量坐标是他们对各个电影或来自各个用户的评分。余弦相似度计算的是两个向量夹角的余弦值忽略向量的绝对长度即评分尺度只关心评分模式是否一致。皮尔逊相关系数更进一步它去除了用户评分偏置比如有的用户习惯性打高分有的则苛刻衡量的是评分变化的趋势是否一致。对于我们的电影推荐系统我建议优先实现基于物品的协同过滤。原因有三第一电影数量通常相对稳定相似度矩阵可以提前计算并缓存响应速度快第二它更易于解释“因为你喜欢《星际穿越》所以我们推荐《盗梦空间》”第三对用户冷启动问题新用户没有评分稍微友好一些我们可以用热门电影或基于电影属性的推荐作为补充。4. 核心算法实现与Django集成4.1 基于物品的协同过滤代码实现理论说完了我们上代码。首先我们需要一个服务类或工具函数来封装推荐逻辑。我在项目里通常会创建一个recommender的Python包或者在utils目录下建立recommendation.py文件。第一步构建用户-物品评分矩阵。这是一个二维矩阵行是用户列是电影值是评分。由于用户和电影数量很大矩阵会非常稀疏大部分是空值因为用户只看过极少部分电影。我们使用pandas的DataFrame和scipy的稀疏矩阵来处理以节省内存。# utils/recommendation.py import pandas as pd import numpy as np from scipy.sparse import csr_matrix from sklearn.metrics.pairwise import cosine_similarity from django.db.models import Count from .models import Rating, Movie class ItemCFRecommender: def __init__(self): self.similarity_matrix None # 物品相似度矩阵 self.movie_id_to_index {} # 电影ID到矩阵列索引的映射 self.index_to_movie_id {} # 矩阵列索引到电影ID的映射 def fit(self, ratings_dataframe): 训练模型计算电影相似度矩阵。 :param ratings_dataframe: 包含user_id, movie_id, score三列的DataFrame # 创建用户-电影评分矩阵稀疏 user_item_matrix ratings_dataframe.pivot_table( indexuser_id, columnsmovie_id, valuesscore ).fillna(0) # 将NaN填充为0表示未评分 # 转换为稀疏矩阵格式节省内存 sparse_matrix csr_matrix(user_item_matrix.values) # 计算物品电影之间的余弦相似度 # 这里计算的是物品向量列向量之间的相似度 # 转置矩阵使每行代表一个物品电影 item_similarity cosine_similarity(sparse_matrix.T) # 存储相似度矩阵和索引映射 self.similarity_matrix item_similarity self.movie_id_to_index {id: idx for idx, id in enumerate(user_item_matrix.columns)} self.index_to_movie_id {idx: id for idx, id in enumerate(user_item_matrix.columns)} def recommend_for_user(self, user_id, ratings_dataframe, top_n10): 为指定用户生成推荐。 :param user_id: 目标用户ID :param ratings_dataframe: 评分数据 :param top_n: 推荐数量 :return: 推荐的电影ID列表 if self.similarity_matrix is None: raise ValueError(请先调用 fit() 方法训练模型。) # 获取该用户的历史评分记录 user_ratings ratings_dataframe[ratings_dataframe[user_id] user_id] if user_ratings.empty: # 用户无历史评分退回热门推荐或随机推荐 return self._get_fallback_recommendations(top_n) # 初始化一个字典来存储电影的总推荐权重 recommendation_scores {} # 遍历用户评分过的每一部电影 for _, row in user_ratings.iterrows(): movie_id row[movie_id] rating_score row[score] # 获取该电影在相似度矩阵中的索引 if movie_id not in self.movie_id_to_index: continue movie_idx self.movie_id_to_index[movie_id] # 获取与该电影最相似的其他电影相似度向量 similarity_scores list(enumerate(self.similarity_matrix[movie_idx])) # 按相似度降序排序 similarity_scores sorted(similarity_scores, keylambda x: x[1], reverseTrue) # 遍历相似电影累加推荐权重 for other_movie_idx, similarity in similarity_scores: other_movie_id self.index_to_movie_id[other_movie_idx] # 跳过用户已经看过的电影 if other_movie_id in user_ratings[movie_id].values: continue # 推荐权重 用户对该电影的评分 * 两部电影的相似度 # 用户评分越高相似度越高则推荐权重越大 if other_movie_id not in recommendation_scores: recommendation_scores[other_movie_id] 0 recommendation_scores[other_movie_id] rating_score * similarity # 按推荐权重降序排序取前top_n个 sorted_recommendations sorted(recommendation_scores.items(), keylambda x: x[1], reverseTrue) recommended_movie_ids [movie_id for movie_id, _ in sorted_recommendations[:top_n]] return recommended_movie_ids def _get_fallback_recommendations(self, top_n): 冷启动处理返回评分次数最多的热门电影 from django.db.models import Count popular_movies Movie.objects.annotate(rating_countCount(rating)).order_by(-rating_count)[:top_n] return [movie.id for movie in popular_movies]这段代码的核心是fit和recommend_for_user两个方法。fit方法在系统启动或定期任务中执行计算好所有电影两两之间的相似度并存储起来这是一个计算密集型但可以离线的过程。recommend_for_user方法则在用户请求推荐时被调用它利用预计算好的相似度矩阵和用户的历史评分快速计算出推荐列表。4.2 在Django视图中集成推荐服务算法准备好了现在需要把它接入Django的Web流程。我们创建一个视图函数来处理推荐请求。首先我们需要一个方式将数据库中的评分数据加载到pandas DataFrame中并初始化推荐器。为了避免每次请求都重新计算相似度矩阵这非常耗时我们需要在服务启动时计算一次并将其缓存起来。Django的缓存框架非常适合做这个。# views.py from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import login_required from django.core.cache import cache from django.http import JsonResponse import pandas as pd from .models import Rating, Movie from .utils.recommendation import ItemCFRecommender login_required def get_recommendations(request): 为用户获取电影推荐列表 user request.user # 尝试从缓存获取推荐器实例 recommender_key item_cf_recommender recommender cache.get(recommender_key) if recommender is None: # 缓存未命中需要重新训练并缓存推荐器 print(训练推荐模型...) # 从数据库获取所有评分数据 ratings_queryset Rating.objects.select_related(movie, user).all() ratings_list list(ratings_queryset.values(user_id, movie_id, score)) if not ratings_list: # 如果没有评分数据返回空或热门电影 popular_movies Movie.objects.annotate(rating_countCount(rating)).order_by(-rating_count)[:10] return render(request, recommendations.html, {movies: popular_movies}) # 转换为DataFrame ratings_df pd.DataFrame(ratings_list) # 初始化并训练推荐器 recommender ItemCFRecommender() recommender.fit(ratings_df) # 将训练好的推荐器存入缓存设置过期时间例如6小时 # 注意复杂对象缓存可能需要pickle序列化确保ItemCFRecommender类可序列化 cache.set(recommender_key, recommender, timeout60*60*6) print(模型训练完成并已缓存。) # 获取当前用户的评分数据用于推荐计算 user_ratings Rating.objects.filter(useruser).values(movie_id, score) user_ratings_df pd.DataFrame(list(user_ratings)) # 需要所有评分数据来构建用户向量这里简单复用全量数据 # 更优做法是传递全量ratings_df但注意数据一致性 all_ratings Rating.objects.all().values(user_id, movie_id, score) all_ratings_df pd.DataFrame(list(all_ratings)) # 获取推荐电影ID列表 recommended_movie_ids recommender.recommend_for_user(user.id, all_ratings_df, top_n10) # 根据ID列表查询电影详细信息 recommended_movies Movie.objects.filter(id__inrecommended_movie_ids) # 注意上面filter返回的顺序可能与推荐列表顺序不一致需要排序 # 创建一个ID到电影对象的映射然后按推荐顺序排列 movie_dict {movie.id: movie for movie in recommended_movies} ordered_movies [movie_dict[mid] for mid in recommended_movie_ids if mid in movie_dict] # 渲染模板或返回JSON数据 if request.headers.get(X-Requested-With) XMLHttpRequest: # AJAX请求返回JSON data [{id: m.id, title: m.title, year: m.year} for m in ordered_movies] return JsonResponse({recommendations: data}) else: # 普通请求渲染HTML页面 return render(request, movies/recommendations.html, {recommended_movies: ordered_movies})这个视图做了几件关键事情1使用缓存避免重复训练模型2从数据库获取数据并转换为算法需要的格式3调用推荐器获得结果4将结果返回给前端。login_required装饰器确保了只有登录用户才能获得个性化推荐。重要提示在实际生产环境中将整个推荐器对象缓存可能不是最佳选择特别是当数据量巨大时序列化和反序列化开销大且模型更新不灵活。更常见的做法是只将计算好的物品相似度矩阵similarity_matrix和索引映射movie_id_to_index缓存起来或者使用专门的模型存储服务。这里为了演示Django缓存的使用和流程的完整性采用了缓存整个对象的方式。对于学习项目这完全可行。5. 前端展示与用户交互实现5.1 构建电影展示与评分页面推荐结果最终需要呈现给用户。我们需要一个简洁明了的页面来展示电影列表并允许用户进行评分因为用户的评分反馈是驱动推荐系统迭代优化的燃料。首先创建电影列表页展示所有电影或搜索结果的电影。我们使用Django模板语言来动态生成内容。!-- templates/movies/movie_list.html -- {% extends base.html %} {% block content %} h2电影库/h2 div classrow {% for movie in movies %} div classcol-md-3 mb-4 div classcard h-100 {% if movie.poster_url %} img src{{ movie.poster_url }} classcard-img-top alt{{ movie.title }} styleheight: 300px; object-fit: cover; {% else %} div classcard-img-top bg-secondary d-flex align-items-center justify-content-center styleheight: 300px; span classtext-white暂无海报/span /div {% endif %} div classcard-body h5 classcard-title{{ movie.title }} ({{ movie.year }})/h5 p classcard-text text-muted genres {% for genre in movie.genres.split|slice::3 %} span classbadge bg-info me-1{{ genre }}/span {% endfor %} /p p classcard-text small{{ movie.description|truncatechars:100 }}/p /div div classcard-footer !-- 显示用户已有评分 -- {% with user_ratingmovie.user_rating %} {% if user_rating %} p classmb-1我的评分: span classstar-rating text-warning {% for i in 12345 %} {% if forloop.counter user_rating %}★{% else %}☆{% endif %} {% endfor %} /span ({{ user_rating }}) /p {% endif %} {% endwith %} !-- 评分表单 -- form methodpost action{% url rate_movie movie.id %} classd-inline {% csrf_token %} div classbtn-group rolegroup {% for i in 12345 %} button typesubmit namescore value{{ forloop.counter }} classbtn btn-outline-primary btn-sm {{ forloop.counter }}★ /button {% endfor %} /div /form a href{% url movie_detail movie.id %} classbtn btn-link btn-sm float-end详情/a /div /div /div {% empty %} div classcol-12 p classtext-center暂无电影数据。/p /div {% endfor %} /div {% endblock %}这个模板使用了Bootstrap 5的卡片布局使电影展示更美观。关键点在于评分部分我们通过一个简单的表单将评分值1-5提交到后端。这里为了用户体验直接用了按钮提交实际项目中可以考虑使用AJAX实现无刷新评分。对应的视图需要处理评分提交并更新或创建Rating记录。# views.py from django.shortcuts import redirect, get_object_or_404 from django.contrib import messages from django.contrib.auth.decorators import login_required login_required def rate_movie(request, movie_id): 处理用户评分 if request.method POST: movie get_object_or_404(Movie, idmovie_id) score request.POST.get(score) try: score float(score) if 1 score 5: # 使用update_or_create避免重复评分 rating, created Rating.objects.update_or_create( userrequest.user, moviemovie, defaults{score: score} ) if created: messages.success(request, f已为《{movie.title}》评分 {score} 星。) else: messages.info(request, f已更新《{movie.title}》的评分为 {score} 星。) # 清除推荐缓存因为用户行为改变了 cache.delete(item_cf_recommender) else: messages.error(request, 评分必须在1到5之间。) except ValueError: messages.error(request, 无效的评分。) # 重定向回电影列表页或来源页 return redirect(request.META.get(HTTP_REFERER, movie_list))5.2 推荐结果页与个性化展示推荐结果页是系统的价值出口。它的设计应该清晰、有说服力告诉用户“为什么推荐这些电影给你”。!-- templates/movies/recommendations.html -- {% extends base.html %} {% block content %} h2为你推荐/h2 p classtext-muted基于你的观影历史和品味我们发现了这些你可能感兴趣的电影。/p {% if recommended_movies %} div classrow {% for movie in recommended_movies %} div classcol-lg-6 mb-4 div classcard div classrow g-0 div classcol-md-4 {% if movie.poster_url %} img src{{ movie.poster_url }} classimg-fluid rounded-start alt{{ movie.title }} styleheight: 200px; width: 100%; object-fit: cover; {% endif %} /div div classcol-md-8 div classcard-body h5 classcard-title{{ movie.title }} ({{ movie.year }})/h5 p classcard-textsmall classtext-muted{{ movie.genres }}/small/p p classcard-text{{ movie.description|truncatechars:150 }}/p div classd-flex justify-content-between align-items-center div !-- 可以在这里加入推荐理由例如“与你喜欢的《XXX》相似” -- !-- 这需要后端传递更多信息暂时留空 -- span classbadge bg-success推荐/span /div div a href{% url movie_detail movie.id %} classbtn btn-primary btn-sm查看详情/a !-- 快速评分按钮组 -- div classbtn-group ms-2 rolegroup {% for i in 12345|slice::3 %} form methodpost action{% url rate_movie movie.id %} classd-inline {% csrf_token %} button typesubmit namescore value{{ forloop.counter }} classbtn btn-outline-secondary btn-sm {{ forloop.counter }}★ /button /form {% endfor %} /div /div /div /div /div /div /div /div {% endfor %} /div {% else %} div classalert alert-info h4 classalert-heading暂无个性化推荐/h4 p这可能是因为你还没有对任何电影进行评分或者系统正在计算中。你可以先去 a href{% url movie_list %}浏览电影/a 并评分帮助我们更好地了解你的喜好。/p /div {% endif %} !-- 可以添加一个“换一批”或“调整推荐”的按钮通过AJAX获取新的推荐 -- div classtext-center mt-4 button idrefresh-rec classbtn btn-outline-secondary换一批推荐/button /div script // 简单的AJAX示例点击换一批 document.getElementById(refresh-rec).addEventListener(click, function() { fetch({% url get_recommendations %}, { headers: { X-Requested-With: XMLHttpRequest } }) .then(response response.json()) .then(data { console.log(收到新推荐:, data); // 这里需要实现前端更新列表的逻辑可以使用前端框架或手动更新DOM alert(已获取新推荐前端列表更新逻辑需自行实现。); }); }); /script {% endblock %}这个页面不仅展示了推荐结果还提供了快速的评分入口形成了一个“评分 - 更新模型 - 获得新推荐”的闭环。页面底部的“换一批推荐”按钮通过AJAX技术可以在不刷新页面的情况下请求新的推荐列表提升了用户体验。6. 系统优化、部署与常见问题6.1 性能优化与可扩展性考虑当你的电影和用户数据从几百增长到几千、几万时最初的简单实现可能会遇到性能瓶颈。以下是几个关键的优化方向1. 相似度矩阵的计算与存储优化计算所有电影两两之间的相似度时间复杂度是O(n²)当电影数n很大时计算会非常慢。解决方案离线计算与定期更新使用Celery等异步任务队列在服务器空闲时如凌晨计算相似度矩阵并存入数据库或高速缓存如Redis。Django视图直接读取缓存结果。稀疏矩阵与近似计算使用scipy.sparse库存储稀疏矩阵。对于大规模数据可以考虑使用近似最近邻搜索算法如Annoy, Faiss来快速查找相似物品而不是计算全量相似度。分块计算如果数据实在太大可以将电影按类型或其他维度分块只在块内计算相似度。2. 数据库查询优化使用select_related和prefetch_related在Django ORM查询电影及其关联的评分时务必使用这两个方法来避免N1查询问题。例如Movie.objects.prefetch_related(rating_set).all()。建立索引为Rating表的user_id和movie_id字段建立数据库索引能极大提升根据用户或电影查询评分记录的速度。数据聚合对于热门电影、平均分等频繁访问的统计信息可以定期计算并存储在单独的模型字段中用空间换时间。3. 推荐实时性我们的基础实现是“离线训练在线推荐”。用户新产生的评分需要等到下次模型训练时才能影响推荐结果。为了提升实时性可以引入“在线学习”或“混合策略”混合推荐将基于物品的协同过滤离线与基于内容的推荐实时结合。当用户新评分一部电影后立即基于这部电影的内容类型、导演等推荐相似电影作为补充。增量更新当用户新增一条评分时不重新计算整个相似度矩阵而是只更新与该电影相关的相似度行。这需要更复杂的算法实现。4. 缓存策略升级分级缓存不仅缓存模型还可以缓存每个用户的推荐结果。为每个用户生成一个推荐列表并缓存设置较短过期时间如10分钟。这样同一用户短时间内重复访问推荐页响应速度极快。使用更快的缓存后端对于生产环境使用Redis或Memcached替代Django的本地内存缓存。6.2 常见问题排查与调试技巧在开发过程中你肯定会遇到各种问题。这里记录几个我踩过的坑和解决方法问题1推荐结果总是热门电影没有个性化。可能原因评分数据太少或者用户之间、电影之间的评分重叠度太低导致算法无法计算出有效的相似度。在数据稀疏的情况下基于物品的协同过滤会退化成热门推荐。排查与解决检查数据打印出评分矩阵的稀疏度非零元素比例。如果低于1%个性化效果会很差。数据填充尝试使用用户平均分或电影平均分来填充矩阵中的零值但这会引入偏差需谨慎。算法调整尝试使用基于模型的协同过滤如矩阵分解SVD这类方法对稀疏数据的处理能力更强。可以使用surprise库快速尝试。引入冷启动策略对于新用户或评分少的用户直接展示热门电影、最新电影或随机电影是可接受的并强烈引导用户去评分。问题2模型训练fit方法速度太慢请求超时。可能原因直接在Web请求中同步执行fit方法。计算相似度矩阵是CPU密集型任务会阻塞整个请求。解决绝对禁止在视图请求中同步训练必须改为异步任务。使用Celery Redis组合。将fit任务放入Celery队列由后台Worker进程执行。视图函数只触发任务并立即返回“模型正在训练中”的提示。训练完成后将结果写入缓存或数据库。在settings.py中配置Celery。问题3新电影或新用户冷启动无法获得推荐。这是推荐系统的经典难题。我们的_get_fallback_recommendations方法只是简单退回热门电影。更优的混合方案基于内容的推荐对于新电影利用其genres,description等属性计算与其他电影的文本相似度如TF-IDF 余弦相似度作为补充推荐源。探索与利用在推荐列表中混入少量随机的新电影或冷门电影收集用户反馈解决冷启动的同时也能探索用户潜在兴趣。注册信息利用新用户注册时可以让其选择感兴趣的电影类型作为初始推荐依据。问题4Django缓存存储复杂对象报错PickleError。可能原因ItemCFRecommender类或其属性如similarity_matrix是numpy数组不能被默认的pickle序列化。解决实现自定义的序列化方法。为ItemCFRecommender类添加__getstate__和__setstate__方法将numpy数组转换为Python列表再存储读取时再转回numpy数组。更推荐的做法不缓存整个对象而是只缓存核心数据。将similarity_matrix转换为列表或二进制、movie_id_to_index、index_to_movie_id这三个核心数据分别用简单的数据结构如字典、列表缓存。在视图里取出这些数据再重新组装成推荐器。这样更可控也节省缓存空间。# 在ItemCFRecommender类中添加序列化支持 def __getstate__(self): # 将numpy数组转换为列表字典等可序列化对象 state self.__dict__.copy() if isinstance(self.similarity_matrix, np.ndarray): state[similarity_matrix] self.similarity_matrix.tolist() # 确保其他属性都是可序列化的 return state def __setstate__(self, state): # 从状态中恢复将列表转回numpy数组 if similarity_matrix in state and isinstance(state[similarity_matrix], list): state[similarity_matrix] np.array(state[similarity_matrix]) self.__dict__.update(state)问题5前端评分提交后页面刷新体验不好。解决使用AJAX实现异步评分。给评分按钮绑定点击事件通过JavaScript发送POST请求到后端后端处理完成后返回JSON状态前端根据状态更新页面上的评分显示如将按钮高亮而无需刷新整个页面。这能极大提升用户体验鼓励用户进行更多评分互动。本文还有配套的精品资源点击获取