news 2026/9/2 11:53:44

基于Django的电影推荐系统实战:从协同过滤到混合策略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Django的电影推荐系统实战:从协同过滤到混合策略

简介:这是一套面向Python Web开发初学者与推荐系统入门者的完整电影推荐系统实战项目,基于Django框架实现协同过滤算法,解决用户个性化观影需求匹配问题,适用于课程设计、毕业设计及全栈开发能力训练。资源共725个文件,包含39个核心Python源码(含数据处理、推荐逻辑与Django视图)、41个Vue组件(实现前端交互与动态渲染)、164个JavaScript脚本(支撑评分、搜索与实时推荐)、53个CSS样式文件及大量SVG/GIF/图片资源,整体压缩包37.65MB,结构清晰,前后端分离特征明显。已有447人学习下载,项目附带可直接运行的批处理脚本(install.bat/run.bat/build.bat)和备份文件,便于快速部署与版本回溯;目录中保留了多个.bak备份文件及完整静态资源链路,有助于理解开发迭代过程与工程化组织方式。

1. 项目缘起:为什么从零构建一个电影推荐系统?

如果你对Python和Web开发感兴趣,想找一个能串联起前后端、数据库、算法和实际业务逻辑的实战项目,那么基于Django的电影推荐系统绝对是一个绝佳的选择。这不仅仅是一个“增删改查”的CRUD应用,它迫使你去思考数据如何流动、算法如何落地、用户体验如何优化,是一个能让你技术栈得到全方位锻炼的“毕业设计级”项目。我之所以选择这个方向,是因为在带新人或者自己技术复盘时,发现很多朋友对Django的理解停留在表单和模板层面,对如何将机器学习模型集成到Web服务中,如何设计一个可扩展的推荐架构感到迷茫。这个项目恰好能填补这块空白。

从技术生态来看,Django在国内Web开发领域,尤其是中后台、内容管理类应用上,拥有非常广泛和稳定的开发者基础。它的“开箱即用”特性对于快速构建原型和稳健的业务系统优势明显。而Python,作为数据科学和机器学习领域的事实标准语言,其丰富的库(如pandas, scikit-learn, numpy)让推荐算法的实现变得异常高效。将两者结合,你就能搭建一个从数据预处理、模型训练,到最终通过Web界面提供个性化服务的完整管道。这个项目源码的价值在于,它展示的是一套“生产就绪”的思考框架和实现路径,而不仅仅是几段孤立的代码。

2. 系统架构全景:从数据到推荐的一站式设计

在动手写第一行代码之前,我们必须把整个系统的骨架搭清楚。一个完整的电影推荐系统远不止一个算法模型,它是一系列组件协同工作的结果。基于Django的特性,我设计了一个典型的分层架构,这能确保代码清晰、易于维护和扩展。

2.1 核心数据模型设计

数据是推荐系统的血液。在models.py中,我们需要精心设计几个核心实体。

用户模型:Django自带的User模型通常不够用,我们需要扩展它。一个常见的做法是使用OneToOneField关联一个Profile模型。这个Profile模型会存储用于推荐的关键元数据。

from django.contrib.auth.models import User from django.db import models class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) # 用于协同过滤的向量(可先留空,由后续计算填充) cf_vector = models.TextField(null=True, blank=True) # 用户注册时选择的兴趣标签(用于冷启动) preferred_genres = models.CharField(max_length=255, blank=True) # 其他统计信息,如平均评分 average_rating = models.FloatField(default=0.0)

电影模型:这是系统的核心。除了基本信息,我们需要存储能被算法利用的特征。

class Movie(models.Model): title = models.CharField(max_length=200) overview = models.TextField(blank=True) # 简介,用于内容分析 release_date = models.DateField(null=True, blank=True) # 关键:将类型存储为逗号分隔的字符串,如“Action,Adventure,Sci-Fi” genres = models.CharField(max_length=500) # 用于内容推荐的向量(如基于简介的TF-IDF向量或嵌入向量) content_vector = models.TextField(null=True, blank=True) # 用于协同过滤的向量(物品向量) cf_vector = models.TextField(null=True, blank=True) poster_url = models.URLField(blank=True) # 海报链接

评分模型:连接用户和电影的桥梁,是协同过滤算法的基石。

class Rating(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) movie = models.ForeignKey(Movie, on_delete=models.CASCADE) rating = models.FloatField() # 评分,如1-5分 created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('user', 'movie') # 防止同一用户对同一电影重复评分

这个设计的关键在于为UserProfileMovie预留了向量字段(cf_vector,content_vector)。这是一种务实的设计:将预先计算好的、可能很长的数值向量(列表)以JSON字符串的形式存入TextField。虽然这不是最优的数据库设计(违背了第一范式),但在项目初期或数据量不大时,它能极大地简化开发复杂度,避免引入专门的向量数据库。当系统规模扩大时,可以平滑地将这些字段迁移到Redis或专业的向量数据库中。

2.2 前后端交互与业务逻辑分层

Django的MTV(Model-Template-View)模式在这里得到了完美体现。我建议采用以下分层:

  1. 数据访问层:即上述的Models,由Django ORM负责。
  2. 业务逻辑层:这是核心。我通常会创建一个独立的Python包,例如recommendation/,在里面放置:
    • algorithms/:存放各种推荐算法类,如CollaborativeFiltering,ContentBased
    • services/:存放推荐服务类,如RecommendationService。它负责调用算法、处理缓存、组装最终给前端的数据。这是协调一切的中枢
    • utils/:存放数据预处理、向量计算等工具函数。
  3. 视图层:Django的Views。它们应该非常“薄”,主要职责是接收HTTP请求,调用RecommendationService获取结果,然后返回响应(JSON或渲染模板)。
  4. 表现层:Templates。用于渲染HTML页面。对于现代开发,你也可以考虑将Django纯粹作为后端API(使用Django REST framework),前端使用Vue.js或React,但这会增加项目的复杂度。本方案采用Django模板以保持项目自包含。

注意:很多初学者会把复杂的算法逻辑直接写在View里,导致View函数臃肿不堪,难以测试和维护。务必坚持“胖模型,瘦视图”或“服务层”的理念,将业务逻辑剥离出去。

3. 推荐算法实战:协同过滤与内容推荐的混合策略

单一的推荐算法往往有局限性。一个健壮的系统通常采用混合策略。这里我实现两种最经典、最有效的算法,并讲解如何将它们结合起来。

3.1 基于用户的协同过滤实现

协同过滤的核心思想是“物以类聚,人以群分”。我们需要计算用户之间的相似度。这里采用余弦相似度。

首先,我们需要一个函数来构建用户-物品评分矩阵。由于数据可能稀疏,我们使用scipy.sparse中的csr_matrix来高效存储。

# recommendation/utils/matrix_builder.py import numpy as np from scipy.sparse import csr_matrix from django.contrib.auth.models import User from .models import Rating, Movie def build_user_item_matrix(): """ 构建用户-物品评分矩阵。 返回: (csr_matrix, user_id_to_index_map, movie_id_to_index_map) """ ratings = Rating.objects.all().select_related('user', 'movie') users = User.objects.all() movies = Movie.objects.all() user_id_to_index = {user.id: idx for idx, user in enumerate(users)} movie_id_to_index = {movie.id: idx for idx, movie in enumerate(movies)} data = [] row_indices = [] col_indices = [] for rating in ratings: row_indices.append(user_id_to_index[rating.user_id]) col_indices.append(movie_id_to_index[rating.movie_id]) data.append(rating.rating) matrix = csr_matrix((data, (row_indices, col_indices)), shape=(len(users), len(movies))) return matrix, user_id_to_index, movie_id_to_index

接下来,实现协同过滤推荐器:

# recommendation/algorithms/collaborative_filtering.py from sklearn.metrics.pairwise import cosine_similarity import numpy as np from django.core.cache import cache class UserBasedCF: def __init__(self, k=20): """ :param k: 选取最相似的K个邻居 """ self.k = k self.sim_matrix = None self.user_index_map = None def fit(self, user_item_matrix, user_index_map): """训练模型,计算用户相似度矩阵""" # 计算余弦相似度 self.sim_matrix = cosine_similarity(user_item_matrix) self.user_index_map = user_index_map # 可以将相似度矩阵缓存,避免每次请求都重复计算 cache.set('cf_user_sim_matrix', self.sim_matrix, timeout=60*60*24) cache.set('cf_user_index_map', self.user_index_map, timeout=60*60*24) def recommend(self, target_user_id, top_n=10): """为目标用户生成推荐""" if self.sim_matrix is None: # 尝试从缓存加载 self.sim_matrix = cache.get('cf_user_sim_matrix') self.user_index_map = cache.get('cf_user_index_map') if self.sim_matrix is None: raise ValueError("Model not fitted and no cache found.") if target_user_id not in self.user_index_map: # 新用户,无法使用协同过滤,退回热门推荐或基于内容的推荐 return [] target_idx = self.user_index_map[target_user_id] # 获取目标用户对所有电影的评分(原始矩阵中的一行) # 注意:在实际中,我们需要重新加载矩阵或从数据库获取目标用户的评分 # 这里为简化,假设我们能直接拿到目标用户的评分向量 `target_ratings` # 更务实的做法是在服务层处理数据获取 # 计算预测评分:相似度加权平均 user_similarities = self.sim_matrix[target_idx] # 找到最相似的K个用户(排除自己) similar_user_indices = np.argsort(user_similarities)[-self.k-1:-1][::-1] # 这里需要实际的评分矩阵来计算预测分,代码略复杂,核心公式如下: # pred_rating = mean_target_rating + sum(sim * (neighbor_rating - mean_neighbor_rating)) / sum(|sim|) # 具体实现需要仔细处理矩阵运算和缺失值。 # 简化返回:假设我们计算出了每个未评分电影的预测分 `pred_scores` # movie_indices = np.argsort(pred_scores)[-top_n:][::-1] # 再将 movie_indices 转换回 movie_id # recommended_movie_ids = [index_to_movie_id[i] for i in movie_indices] # 由于完整实现较长,此处返回一个示意流程 return self._generate_recommendations_impl(target_idx, similar_user_indices, top_n)

实操心得:纯内存的协同过滤在用户和物品数量上万时,计算相似度矩阵会非常耗时且占用内存。在实际项目中,我通常不会在每次请求时计算,而是将其作为一个离线任务。例如,使用Celery定时(如每天凌晨)计算所有用户的相似度,并将结果(如每个用户最相似的K个用户ID列表)存入缓存或数据库。在线推荐时,直接读取这些预计算的结果进行加权评分预测,速度极快。

3.2 基于内容的推荐实现

当用户行为数据(评分)很少时(冷启动问题),基于内容的推荐就派上用场了。它通过分析用户过去喜欢的物品的属性(如电影类型、简介),来推荐具有相似属性的新物品。

我们利用电影的genresoverview字段。对于类型,我们可以进行多热编码。对于简介,我们使用TF-IDF将其转化为向量。

# recommendation/algorithms/content_based.py from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import linear_kernel import numpy as np from django.core.cache import cache class ContentBasedRecommender: def __init__(self): self.tfidf_matrix = None self.movie_index_map = None self.vectorizer = None def fit(self, movies): """训练TF-IDF模型并计算电影相似度矩阵""" # 准备文本数据:将类型和简介结合 text_data = [] self.movie_index_map = {} for idx, movie in enumerate(movies): self.movie_index_map[movie.id] = idx # 组合文本:类型 + “ ” + 简介 text = f"{movie.genres} {movie.overview}" text_data.append(text) self.vectorizer = TfidfVectorizer(stop_words='english', max_features=5000) self.tfidf_matrix = self.vectorizer.fit_transform(text_data) # 计算电影之间的余弦相似度(基于内容) self.content_sim_matrix = linear_kernel(self.tfidf_matrix, self.tfidf_matrix) cache.set('cb_sim_matrix', self.content_sim_matrix, timeout=60*60*24) cache.set('cb_movie_index_map', self.movie_index_map, timeout=60*60*24) def recommend(self, liked_movie_ids, top_n=10): """根据用户喜欢的电影,推荐相似的电影""" if self.content_sim_matrix is None: self.content_sim_matrix = cache.get('cb_sim_matrix') self.movie_index_map = cache.get('cb_movie_index_map') if not liked_movie_ids: return [] # 计算用户画像:将所有喜欢电影的向量平均 liked_indices = [self.movie_index_map[mid] for mid in liked_movie_ids if mid in self.movie_index_map] if not liked_indices: return [] user_profile = np.mean(self.tfidf_matrix[liked_indices].toarray(), axis=0).reshape(1, -1) # 计算用户画像与所有电影的相似度 # 注意:这里我们直接用用户画像向量去和TF-IDF矩阵算相似度 similarity_scores = linear_kernel(user_profile, self.tfidf_matrix).flatten() # 排除已经喜欢的电影 for idx in liked_indices: similarity_scores[idx] = -1 # 设为负值确保不被选中 # 获取最相似的前N个电影索引 top_indices = np.argsort(similarity_scores)[-top_n:][::-1] # 将索引转换回电影ID index_to_movie_id = {v: k for k, v in self.movie_index_map.items()} recommended_ids = [index_to_movie_id[i] for i in top_indices if i in index_to_movie_id] return recommended_ids

3.3 混合推荐策略与服务层封装

有了两种算法,我们需要一个策略来混合它们。一个简单有效的加权混合方法如下:

# recommendation/services/recommendation_service.py from django.core.cache import cache from ..algorithms.collaborative_filtering import UserBasedCF from ..algorithms.content_based import ContentBasedRecommender from ..models import Rating, Movie class RecommendationService: def __init__(self): self.cf_model = UserBasedCF(k=20) self.cb_model = ContentBasedRecommender() self._load_or_train_models() def _load_or_train_models(self): """加载或训练模型。这是一个简化示例,生产环境应使用离线任务。""" # 尝试从缓存加载协同过滤的相似度矩阵 cf_sim_cache = cache.get('cf_user_sim_matrix') # ... 类似地加载其他缓存 if cf_sim_cache is None: # 如果没有缓存,则进行训练(数据量大时,这应该在后台异步完成) matrix, user_map, _ = build_user_item_matrix() self.cf_model.fit(matrix, user_map) # 内容模型同理 ... def get_hybrid_recommendations(self, user, top_n=20): """ 获取混合推荐。 策略: 1. 如果用户有足够评分(>10),协同过滤权重高(如0.7),内容推荐权重低(0.3)。 2. 如果用户评分少(冷启动),则主要依赖内容推荐,并加入热门电影作为兜底。 """ user_ratings_count = Rating.objects.filter(user=user).count() liked_movie_ids = list(Rating.objects.filter(user=user, rating__gte=4.0).values_list('movie_id', flat=True)) cf_recommendations = [] cb_recommendations = [] # 获取协同过滤推荐 if user_ratings_count >= 5: try: cf_recommendations = self.cf_model.recommend(user.id, top_n=top_n*2) # 多取一些用于混合 except ValueError: # 模型未就绪或用户不在矩阵中 pass # 获取基于内容的推荐 if liked_movie_ids: cb_recommendations = self.cb_model.recommend(liked_movie_ids, top_n=top_n*2) # 混合与去重 all_recs = {} # 给协同过滤推荐赋予基础权重 for idx, movie_id in enumerate(cf_recommendations): all_recs[movie_id] = all_recs.get(movie_id, 0) + 0.7 * (1 - idx/len(cf_recommendations)) # 排名衰减 # 给内容推荐赋予基础权重 for idx, movie_id in enumerate(cb_recommendations): all_recs[movie_id] = all_recs.get(movie_id, 0) + 0.3 * (1 - idx/len(cb_recommendations)) # 如果用户评分很少,调整权重,偏向内容推荐 if user_ratings_count < 5: for movie_id in all_recs: if movie_id in cb_recommendations: all_recs[movie_id] *= 1.5 # 提升内容推荐项的权重 # 按最终权重排序,取前top_n个 sorted_recommendations = sorted(all_recs.items(), key=lambda x: x[1], reverse=True)[:top_n] final_movie_ids = [item[0] for item in sorted_recommendations] # 如果最终结果不足,用热门电影补足(兜底策略) if len(final_movie_ids) < top_n: popular_movies = Movie.objects.annotate(rating_count=Count('rating')).order_by('-rating_count')[:top_n] popular_ids = [m.id for m in popular_movies if m.id not in final_movie_ids] final_movie_ids.extend(popular_ids[:top_n - len(final_movie_ids)]) return Movie.objects.filter(id__in=final_movie_ids)

这个RecommendationService类就是系统的大脑。它在__init__中初始化模型,提供了get_hybrid_recommendations这个核心接口。View层只需要调用这个接口,传入当前用户对象,就能拿到混合了多种策略的、排好序的电影推荐列表。

4. Django视图与前端展示:将推荐结果送达用户

有了强大的后端服务,我们需要通过Django的视图和模板将其呈现给用户。这里设计两个核心页面:主页(显示个性化推荐)和电影详情页(提供评分入口和相似电影推荐)。

4.1 核心视图实现

首先,在views.py中创建主页视图。这个视图需要处理登录和未登录两种状态。

# movie_rec/views.py from django.contrib.auth.decorators import login_required from django.shortcuts import render from recommendation.services.recommendation_service import RecommendationService def home(request): """ 系统主页。为登录用户展示个性化推荐,为未登录用户展示热门电影。 """ context = {} if request.user.is_authenticated: # 调用推荐服务 rec_service = RecommendationService() recommended_movies = rec_service.get_hybrid_recommendations(request.user, top_n=12) context['recommended_movies'] = recommended_movies context['section'] = 'personalized' else: # 未登录用户,展示热门电影或最新电影 from django.db.models import Count from .models import Movie popular_movies = Movie.objects.annotate(num_ratings=Count('rating')).order_by('-num_ratings')[:12] context['recommended_movies'] = popular_movies context['section'] = 'popular' return render(request, 'movie_rec/home.html', context)

电影详情页视图,除了展示电影信息,还应该显示基于内容的相似电影推荐。

def movie_detail(request, movie_id): """ 电影详情页。展示电影信息、用户评分,并提供相似电影推荐。 """ from .models import Movie, Rating from recommendation.services.recommendation_service import RecommendationService movie = get_object_or_404(Movie, id=movie_id) user_rating = None if request.user.is_authenticated: try: user_rating = Rating.objects.get(user=request.user, movie=movie) except Rating.DoesNotExist: pass # 获取基于内容的相似电影推荐(不依赖用户行为) rec_service = RecommendationService() # 这里我们直接使用内容推荐模型,传入当前电影ID作为“喜欢”的电影 similar_movie_ids = rec_service.cb_model.recommend([movie_id], top_n=6) similar_movies = Movie.objects.filter(id__in=similar_movie_ids) context = { 'movie': movie, 'user_rating': user_rating.rating if user_rating else None, 'similar_movies': similar_movies, } return render(request, 'movie_rec/movie_detail.html', context)

处理用户评分的视图。这是一个POST请求,需要更新数据库并可能触发用户推荐向量的更新(异步任务)。

from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST @require_POST @csrf_exempt @login_required def rate_movie(request): """ 处理用户评分。AJAX调用。 """ movie_id = request.POST.get('movie_id') rating_value = float(request.POST.get('rating')) # 验证评分范围,如1-5 if not (1 <= rating_value <= 5): return JsonResponse({'success': False, 'error': 'Invalid rating value.'}) movie = get_object_or_404(Movie, id=movie_id) rating_obj, created = Rating.objects.update_or_create( user=request.user, movie=movie, defaults={'rating': rating_value} ) # 评分后,可以触发一个异步任务来更新该用户的协同过滤向量(如果采用增量更新策略) # 例如:celery_task_update_user_vector.delay(request.user.id) # 对于简单项目,也可以选择在下次离线计算时统一更新。 return JsonResponse({'success': True, 'created': created})

4.2 前端模板与交互

主页模板home.html需要优雅地展示电影网格。这里使用Bootstrap进行快速布局。

<!-- templates/movie_rec/home.html --> {% extends 'base.html' %} {% block content %} <div class="container mt-4"> <h2> {% if section == 'personalized' %} 为您推荐的电影 {% else %} 热门电影 {% endif %} </h2> <div class="row"> {% for movie in recommended_movies %} <div class="col-lg-3 col-md-4 col-sm-6 mb-4"> <div class="card h-100"> <img src="{{ movie.poster_url|default:'https://via.placeholder.com/300x450' }}" class="card-img-top" alt="{{ movie.title }}" style="height: 300px; object-fit: cover;"> <div class="card-body"> <h5 class="card-title">{{ movie.title }}</h5> <p class="card-text text-muted"> <small>{{ movie.release_date|date:"Y" }} | {{ movie.genres|truncatewords:3 }}</small> </p> <p class="card-text">{{ movie.overview|truncatewords:20 }}</p> </div> <div class="card-footer"> <a href="{% url 'movie_detail' movie.id %}" class="btn btn-outline-primary btn-sm">查看详情</a> </div> </div> </div> {% empty %} <div class="col-12"> <p class="text-center">暂无推荐。请先给一些电影评分吧!</p> </div> {% endfor %} </div> </div> {% endblock %}

电影详情页movie_detail.html需要集成评分功能。

<!-- templates/movie_rec/movie_detail.html --> {% extends 'base.html' %} {% block content %} <div class="container mt-4"> <div class="row"> <div class="col-md-4"> <img src="{{ movie.poster_url|default:'https://via.placeholder.com/300x450' }}" class="img-fluid rounded" alt="{{ movie.title }}"> </div> <div class="col-md-8"> <h1>{{ movie.title }}</h1> <p><strong>上映日期:</strong> {{ movie.release_date|date:"Y-m-d" }}</p> <p><strong>类型:</strong> {{ movie.genres }}</p> <hr> <h4>简介</h4> <p>{{ movie.overview }}</p> <hr> {% if user.is_authenticated %} <h4>我的评分</h4> <div class="rating-section"> <p>当前评分: <span id="current-rating">{{ user_rating|default:"尚未评分" }}</span></p> <div class="star-rating"> {% for i in "12345"|make_list %} <span class="star"># movie_rec/management/commands/import_movielens.py import pandas as pd from django.core.management.base import BaseCommand from django.contrib.auth.models import User from movie_rec.models import Movie, Rating, UserProfile class Command(BaseCommand): help = 'Import MovieLens dataset (movies.csv, ratings.csv)' def add_arguments(self, parser): parser.add_argument('movies_file', type=str, help='Path to movies.csv') parser.add_argument('ratings_file', type=str, help='Path to ratings.csv') def handle(self, *args, **options): # 1. 导入电影 movies_df = pd.read_csv(options['movies_file']) self.stdout.write(f'Importing {len(movies_df)} movies...') movie_map = {} # 存储MovieLens ID到我们数据库ID的映射 for _, row in movies_df.iterrows(): # MovieLens数据中,类型是`Action|Adventure|Sci-Fi`格式 genres = row['genres'].replace('|', ', ') movie, created = Movie.objects.get_or_create( title=row['title'], defaults={ 'overview': '', # MovieLens没有简介,可以留空或后续从TMDB补充 'genres': genres, } ) movie_map[row['movieId']] = movie.id if created: self.stdout.write(f' Created: {movie.title}') # 2. 导入用户和评分 ratings_df = pd.read_csv(options['ratings_file']) self.stdout.write(f'Importing {len(ratings_df)} ratings...') user_map = {} for _, row in ratings_df.iterrows(): user_id = row['userId'] if user_id not in user_map: # 创建Django用户(使用虚拟用户名和邮箱) user, _ = User.objects.get_or_create( username=f'user_{user_id}', defaults={'email': f'user_{user_id}@example.com'} ) UserProfile.objects.get_or_create(user=user) user_map[user_id] = user movie_id = movie_map.get(row['movieId']) if movie_id: Rating.objects.update_or_create( user=user_map[user_id], movie_id=movie_id, defaults={'rating': row['rating']} ) self.stdout.write(self.style.SUCCESS('Data import completed successfully!'))

运行命令:python manage.py import_movielens /path/to/movies.csv /path/to/ratings.csv。导入后,你就有了一个充满评分数据的数据库,可以立即用于训练协同过滤模型。

5.2 模型预热与缓存策略

在首次启动服务或数据更新后,我们需要“预热”推荐模型,即预先计算好相似度矩阵并存入缓存。我们可以创建另一个管理命令或使用Django的ready()信号在启动时执行(注意:对于大数据集,这会阻塞启动,最好用异步任务)。

# recommendation/management/commands/warmup_recommendations.py from django.core.management.base import BaseCommand from django.core.cache import cache from recommendation.utils.matrix_builder import build_user_item_matrix from recommendation.algorithms.collaborative_filtering import UserBasedCF from recommendation.algorithms.content_based import ContentBasedRecommender from movie_rec.models import Movie class Command(BaseCommand): help = 'Warm up recommendation models and cache' def handle(self, *args, **options): self.stdout.write('Warming up Collaborative Filtering model...') matrix, user_map, movie_map = build_user_item_matrix() cf_model = UserBasedCF(k=20) cf_model.fit(matrix, user_map) self.stdout.write(self.style.SUCCESS('CF model warmed up.')) self.stdout.write('Warming up Content-Based model...') movies = Movie.objects.all() cb_model = ContentBasedRecommender() cb_model.fit(movies) self.stdout.write(self.style.SUCCESS('CB model warmed up.')) # 也可以预计算一些热门推荐,存入缓存 from django.db.models import Count popular_movies = list(Movie.objects.annotate(rating_count=Count('rating')).order_by('-rating_count')[:50].values_list('id', flat=True)) cache.set('global_popular_movies', popular_movies, timeout=None) # 永不过期,除非手动更新 self.stdout.write(self.style.SUCCESS('Popular movies cached.'))

将这个命令添加到服务器的启动脚本中,或者设置为一个定时的Celery任务(例如每小时运行一次,增量更新)。

5.3 部署注意事项与性能优化

将Django项目部署到生产环境(如Nginx + Gunicorn)时,针对推荐系统需要特别关注以下几点:

  1. 缓存无处不在:除了Django自带的缓存框架(可配置为Redis),在推荐服务内部也要积极缓存。例如,RecommendationService.get_hybrid_recommendations方法可以为每个用户生成一个缓存键(如f”recs:{user.id}”),并将推荐结果缓存10-30分钟。这能极大减轻数据库和计算压力。

  2. 异步任务处理:用户评分、模型重新训练、向量更新等耗时操作,务必使用Celery等异步任务队列。不要让HTTP请求线程等待这些操作完成。

  3. 数据库优化

    • Rating表的(user, movie)复合字段建立唯一索引(我们已经用unique_together做了)。
    • Rating表的user_idmovie_id分别建立外键索引。
    • 如果电影数量巨大,考虑对Movie表的titlegenres字段建立索引以加速搜索和过滤。
  4. 向量存储分离:当用户和物品数量达到十万、百万级别时,将cf_vectorcontent_vector这类大字段从MySQL/PostgreSQL中移出,存入Redis或专门的向量数据库(如Milvus, Weaviate)。这能大幅提升相似度计算和检索的效率。

  5. API响应优化:在返回推荐列表时,使用select_relatedprefetch_related来避免N+1查询问题,一次性获取电影的所有关联信息。

# 在视图中优化查询 recommended_movies = Movie.objects.filter(id__in=movie_ids).select_related('...')

这个项目从设计到实现的每一步,都贯穿着对性能、可维护性和用户体验的考量。它不仅仅是一个Demo,更是一个展示了如何将机器学习算法与成熟的Web框架进行工程化整合的范本。当你按照这个流程走下来,你会对Django的项目结构、业务分层、缓存策略、异步处理以及推荐系统的核心逻辑有一个非常扎实和深入的理解。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 11:52:16

STM32时钟树原理与CubeMX配置实战:从入门到精通

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/2 11:50:00

2026年AI论文写作免费工具怎么选?实测对比来了

免费AI论文写作工具到底能不能用&#xff1f;这篇实测直接给出答案。本文选取市面上常见的几款AI论文写作工具&#xff0c;从生成能力、降重效果、图表处理等维度逐一测试&#xff0c;帮读者看清免费版与付费版的真实差距。 免费与付费的差距在哪里 先厘清一个概念&#xff1…

作者头像 李华
网站建设 2026/9/2 11:46:52

AionUi 性能优化指南:6 个方法让 AI 助手对话更快更顺滑

AionUi 性能优化指南&#xff1a;6 个方法让 AI 助手对话更快更顺滑 【免费下载链接】AionUi Open-source 24/7 Cowork app for OpenClaw, Hermes, Claude Code, Codex, OpenCode and 20 more CLI Agent | Customize your assistants | Team them up&#xff5c;Star if you li…

作者头像 李华
网站建设 2026/9/2 11:43:29

让Git支持S3远程仓库:轻量级CLI扩展实现指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华