1. 项目概述
这个基于Django框架的个性化电影推荐系统,是我去年指导计算机专业学生完成的毕业设计项目。作为一个在推荐系统领域摸爬滚打多年的开发者,看到这个选题时眼前一亮——它完美结合了Python生态的技术优势和实际应用场景。系统采用经典的协同过滤算法,但针对学生毕设的特殊需求做了大量工程优化,最终实现了一个既具备学术价值又易于二次开发的推荐引擎。
提示:完整项目源码已托管在GitHub,文末会提供获取方式。建议先通读本文了解架构设计再动手实践。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心需求解析
2.1 功能定位
系统需要解决三个核心问题:
- 用户行为数据的高效采集与存储
- 推荐算法的实时计算与更新
- 前后端交互的流畅性保障
2.2 技术选型考量
选择Django而非Flask的原因:
- 自带Admin后台方便数据管理
- ORM简化数据库操作(对SQL不熟练的学生友好)
- 完善的Auth系统开箱即用
python复制# 典型模型定义示例
class Movie(models.Model):
title = models.CharField(max_length=200)
genres = models.CharField(max_length=100)
year = models.IntegerField()
3. 系统架构设计
3.1 整体架构图
采用分层设计:
- 数据层:MySQL + Redis缓存
- 算法层:基于用户的协同过滤(UserCF)
- 表现层:Bootstrap前端 + Django模板引擎
3.2 关键组件交互
mermaid复制graph TD
A[用户请求] --> B[Nginx]
B --> C[Django]
C --> D{缓存检查}
D -->|命中| E[返回结果]
D -->|未命中| F[算法计算]
F --> G[结果缓存]
G --> E
4. 推荐算法实现
4.1 数据预处理
采用TF-IDF处理电影标签:
python复制from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(stop_words='english')
tfidf_matrix = tfidf.fit_transform(movie_data['genres'])
4.2 相似度计算
使用余弦相似度:
python复制from sklearn.metrics.pairwise import linear_kernel
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
4.3 冷启动解决方案
对于新用户采用热度榜兜底策略:
sql复制SELECT movie_id, COUNT(*) as cnt
FROM ratings
GROUP BY movie_id
ORDER BY cnt DESC
LIMIT 50
5. 性能优化实践
5.1 缓存策略
- 使用Django原生缓存框架:
python复制from django.core.cache import cache
def get_recommendations(user_id):
key = f'rec_{user_id}'
result = cache.get(key)
if not result:
result = calculate_rec(user_id)
cache.set(key, result, timeout=3600)
return result
5.2 数据库优化
- 为评分表添加复合索引:
python复制class Meta:
indexes = [
models.Index(fields=['user_id', 'movie_id']),
]
6. 部署注意事项
6.1 生产环境配置
推荐使用Gunicorn+Nginx组合:
bash复制gunicorn --workers 4 --bind 0.0.0.0:8000 project.wsgi:application
6.2 常见问题排查
- 中文乱码问题:
python复制# settings.py
FILE_CHARSET = 'utf-8'
DEFAULT_CHARSET = 'utf-8'
7. 项目扩展方向
- 引入深度学习模型(如NeuMF)
- 增加实时日志分析管道
- 开发移动端适配界面
避坑指南:在Windows开发环境下遇到MySQLclient安装失败时,可改用pymysql:
python复制# __init__.py
import pymysql
pymysql.install_as_MySQLdb()
我在实际部署中发现,当用户量超过1万时,原始的内存缓存会出现性能瓶颈。这时可以考虑两种方案:要么升级到Redis集群,要么实现基于LRU的本地缓存淘汰策略。对于毕业设计级别的项目,后者可能更实际:
python复制from django.core.cache import caches
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = OrderedDict()
def get(self, key):
if key not in self.cache:
return None
value = self.cache.pop(key)
self.cache[key] = value
return value
def set(self, key, value):
if key in self.cache:
self.cache.pop(key)
elif len(self.cache) >= self.capacity:
self.cache.popitem(last=False)
self.cache[key] = value
这个推荐系统最有趣的部分在于算法调参过程。我们发现当KNN的k值设为20时,在MovieLens数据集上能达到最佳效果。但具体数值需要根据你的数据分布调整:
python复制# 最佳近邻数寻找代码示例
for k in range(5, 50, 5):
model = KNNBasic(k=k)
cv_results = cross_validate(model, data, measures=['RMSE'], cv=5)
print(f"k={k}, RMSE={np.mean(cv_results['test_rmse'])}")
对于时间有限的同学,可以直接使用Surprise库内置的算法:
python复制from surprise import KNNBasic
from surprise import Dataset
from surprise.model_selection import cross_validate
data = Dataset.load_builtin('ml-100k')
algo = KNNBasic()
cross_validate(algo, data, measures=['RMSE'], cv=5, verbose=True)
前端展示部分我推荐使用ECharts实现可视化,这个代码片段可以生成用户评分分布图:
javascript复制// 在Django模板中嵌入
function drawChart() {
var chart = echarts.init(document.getElementById('chart'));
option = {
xAxis: {data: ['1星', '2星', '3星', '4星', '5星']},
series: [{data: [12, 34, 89, 120, 205], type: 'bar'}]
};
chart.setOption(option);
}
数据库设计方面有个容易忽略的细节——记得为时间戳字段添加索引,这对分析用户行为模式至关重要:
python复制class Rating(models.Model):
user = models.ForeignKey(User)
movie = models.ForeignKey(Movie)
rating = models.FloatField()
timestamp = models.DateTimeField(db_index=True) # 关键索引
在项目文档编写时,建议采用Sphinx生成专业的技术文档。这个conf.py配置很实用:
python复制extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinx.ext.napoleon'
]
html_theme = 'sphinx_rtd_theme'
测试环节我发现一个典型陷阱:Django的测试数据库不会自动应用索引。解决方法是在TestCase中显式创建:
python复制class RecommendationTests(TestCase):
@classmethod
def setUpTestData(cls):
# 创建测试数据后
with connection.cursor() as cursor:
cursor.execute("CREATE INDEX idx_user_movie ON ratings (user_id, movie_id)")
对于想深入理解推荐系统的同学,我强烈建议阅读《Recommender Systems Handbook》第三章。其中关于相似度计算的讨论对我们优化算法很有启发——比如将用户活跃度作为权重因子:
python复制def weighted_cosine_sim(u1, u2):
base_sim = cosine_sim(u1, u2)
activity_weight = 1 / (1 + math.log10(len(u1.ratings)))
return base_sim * activity_weight
项目展示时,评委常问的一个问题是"如何评估推荐质量"。除了RMSE,还可以计算准确率:
python复制from collections import defaultdict
def precision_at_k(predictions, k=10):
user_est_true = defaultdict(list)
for uid, _, true_r, est, _ in predictions:
user_est_true[uid].append((est, true_r))
precisions = []
for uid, user_ratings in user_est_true.items():
user_ratings.sort(key=lambda x: x[0], reverse=True)
n_rel = sum(1 for (_, true_r) in user_ratings[:k] if true_r >= 4)
precisions.append(n_rel / k)
return sum(precisions) / len(precisions)
最后分享一个部署时的小技巧:使用Supervisor管理进程时,配置自动重启很重要:
ini复制[program:recommendation]
command=/path/to/gunicorn
autostart=true
autorestart=true
stderr_logfile=/var/log/recommendation.err.log
stdout_logfile=/var/log/recommendation.out.log
这个项目最让我自豪的是它的扩展性设计。比如要新增社交推荐功能,只需在models.py中添加:
python复制class UserRelation(models.Model):
from_user = models.ForeignKey(User, related_name='following')
to_user = models.ForeignKey(User, related_name='followers')
created_at = models.DateTimeField(auto_now_add=True)
对于想进一步优化的同学,可以考虑用Celery异步处理计算密集型任务。这个任务定义示例很实用:
python复制@app.task(bind=True)
def train_model_task(self):
try:
data = Dataset.load_from_file('ratings.csv')
algo = KNNBasic()
algo.fit(data.build_full_trainset())
return 'success'
except Exception as e:
self.retry(exc=e, countdown=60)
在安全方面,别忘了对用户输入做严格验证。这个Django表单示例可以防止XSS攻击:
python复制from django import forms
from django.utils.html import escape
class RatingForm(forms.Form):
score = forms.IntegerField(min_value=1, max_value=5)
comment = forms.CharField(widget=forms.Textarea)
def clean_comment(self):
return escape(self.cleaned_data['comment'])
项目中最复杂的部分是实时推荐更新。我们的解决方案是结合信号机制:
python复制from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=Rating)
def update_recommendations(sender, instance, **kwargs):
user_key = f'rec_{instance.user_id}'
cache.delete(user_key)
# 异步触发重新计算
calculate_rec.delay(instance.user_id)
对于数据量大的情况,建议采用批量操作。这个示例代码性能提升显著:
python复制from django.db import transaction
def import_ratings_bulk(csv_path):
with open(csv_path) as f, transaction.atomic():
reader = csv.DictReader(f)
Rating.objects.bulk_create([
Rating(
user_id=row['userId'],
movie_id=row['movieId'],
rating=row['rating'],
timestamp=datetime.fromtimestamp(int(row['timestamp']))
) for row in reader
])
在用户界面优化方面,我们实现了"猜你喜欢"的懒加载效果。关键前端代码:
javascript复制$(window).scroll(function() {
if($(window).scrollTop() + $(window).height() >= $(document).height() - 100) {
loadMoreRecommendations();
}
});
系统还实现了简单的AB测试框架,用于比较不同算法效果:
python复制def get_recommendations(user):
if user.id % 2 == 0:
return algo_a.predict(user)
else:
return algo_b.predict(user)
对于想添加社交功能的同学,这个关注推荐实现很实用:
python复制def get_follow_recommendations(user):
# 获取二度人脉
followees = UserRelation.objects.filter(from_user=user).values_list('to_user', flat=True)
return User.objects.filter(
relations__to_user__in=followees
).exclude(
relations__from_user=user
).annotate(
common_follows=Count('relations__to_user')
).order_by('-common_follows')[:5]
最后强调下文档规范的重要性。我们的API文档采用OpenAPI标准,这个视图集示例很典型:
python复制from drf_yasg import openapi
@swagger_auto_schema(
operation_description="获取个性化推荐",
manual_parameters=[
openapi.Parameter('user_id', openapi.IN_PATH, type=openapi.TYPE_INTEGER)
],
responses={200: openapi.Response('推荐电影列表')}
)
def recommendations(request, user_id):
...
