1. 项目概述与设计思路
去年接手了一个动漫推荐系统的开发需求,客户希望打造一个能根据用户喜好智能推荐动漫作品的平台。经过多方调研,最终决定采用Django框架+协同过滤算法的技术方案。这个组合在保证开发效率的同时,也能满足个性化推荐的核心需求。
整个系统分为前台展示和后台管理两大板块。前台面向普通用户,提供动漫浏览、收藏、推荐和可视化分析功能;后台则供管理员进行数据维护。最核心的推荐模块采用了基于物品的协同过滤算法(Item-based CF),通过分析用户历史行为数据,找出相似动漫进行推荐。
选择这个方案主要基于几点考虑:
- Django自带完善的用户认证系统和ORM,能快速搭建基础功能
- SQLite轻量级且零配置,适合中小型项目
- 基于物品的协同过滤相比基于用户的算法,在动漫这类物品相对稳定的场景下效果更好
- Echarts可视化库学习成本低,能满足基础数据分析需求
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构详解
2.1 后端架构设计
整个后端采用经典的MVT模式:
code复制├── anime_recommend/
│ ├── models.py # 数据模型定义
│ ├── views.py # 业务逻辑处理
│ ├── urls.py # 路由配置
│ └── utils/ # 工具类
│ └── recommender.py # 推荐算法实现
数据库设计关键表结构:
python复制class Anime(models.Model):
title = models.CharField(max_length=200)
genre = models.CharField(max_length=100)
country = models.CharField(max_length=50)
year = models.IntegerField()
cover_url = models.URLField()
# 其他字段...
class UserBehavior(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
anime = models.ForeignKey(Anime, on_delete=models.CASCADE)
behavior_type = models.CharField(max_length=10) # view/collect
created_at = models.DateTimeField(auto_now_add=True)
2.2 推荐算法实现
核心的协同过滤算法封装在recommender.py中:
python复制class ItemBasedCF:
def __init__(self, user_behavior_data):
self.train = self._preprocess_data(user_behavior_data)
def _preprocess_data(self, raw_data):
# 将用户行为数据转换为{user: {item: score}}格式
processed = {}
for record in raw_data:
user = record.user_id
item = record.anime_id
processed.setdefault(user, {})
processed[user][item] = 1 # 隐式反馈设为1
return processed
def calculate_similarity(self):
# 构建共现矩阵
cooccur = defaultdict(lambda: defaultdict(int))
item_counts = defaultdict(int)
for user, items in self.train.items():
for i in items:
item_counts[i] += 1
for j in items:
if i != j:
cooccur[i][j] += 1
# 计算余弦相似度
self.similarity = defaultdict(dict)
for i, related_items in cooccur.items():
for j, count in related_items.items():
self.similarity[i][j] = count / math.sqrt(item_counts[i] * item_counts[j])
return self.similarity
def recommend(self, user_id, top_n=10):
user_items = self.train.get(user_id, {})
scores = defaultdict(float)
for item, _ in user_items.items():
for related_item, sim in self.similarity.get(item, {}).items():
if related_item not in user_items:
scores[related_item] += sim
return sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_n]
算法优化点:
- 采用隐式反馈处理用户行为(浏览/收藏都记为1)
- 使用defaultdict提升字典操作效率
- 相似度计算使用余弦相似度公式
- 支持动态调整推荐数量
3. 核心功能实现
3.1 用户行为采集
在views.py中埋点采集用户行为:
python复制def anime_detail(request, anime_id):
anime = get_object_or_404(Anime, pk=anime_id)
# 记录浏览行为
if request.user.is_authenticated:
UserBehavior.objects.create(
user=request.user,
anime=anime,
behavior_type='view'
)
# 获取相似推荐
recommender = ItemBasedCF(get_user_behavior_data())
recommendations = recommender.recommend(request.user.id)
return render(request, 'anime_detail.html', {
'anime': anime,
'recommendations': recommendations
})
3.2 可视化数据接口
为Echarts提供数据的API接口:
python复制def genre_statistics(request):
data = Anime.objects.values('genre').annotate(count=Count('id'))
result = {
'legend': [item['genre'] for item in data],
'series': [{
'name': '类型分布',
'data': [{'value': item['count'], 'name': item['genre']} for item in data]
}]
}
return JsonResponse(result)
3.3 推荐结果缓存
使用Django缓存提升推荐性能:
python复制from django.core.cache import cache
def get_recommendations(user_id):
cache_key = f'rec_{user_id}'
recommendations = cache.get(cache_key)
if not recommendations:
recommender = ItemBasedCF(get_user_behavior_data())
recommendations = recommender.recommend(user_id)
cache.set(cache_key, recommendations, timeout=3600) # 缓存1小时
return recommendations
4. 部署与性能优化
4.1 生产环境部署
推荐使用Nginx+Gunicorn部署方案:
bash复制# 安装依赖
pip install gunicorn
# 启动命令
gunicorn --workers 4 --bind 0.0.0.0:8000 anime_recommend.wsgi:application
Nginx配置示例:
nginx复制server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /static/ {
alias /path/to/your/static/files/;
}
}
4.2 性能优化技巧
- 数据库优化:
python复制# 使用select_related/prefetch_related减少查询
animes = Anime.objects.select_related('studio').prefetch_related('tags')
- 推荐算法优化:
- 使用稀疏矩阵存储相似度
- 定期离线计算相似度矩阵
- 对热门物品进行降权处理
- 前端优化:
- 使用Django模板缓存
- 异步加载推荐结果
- 图片懒加载
5. 常见问题与解决方案
5.1 冷启动问题
新用户没有行为数据时的解决方案:
- 热门推荐兜底
python复制def get_recommendations(user_id):
if not UserBehavior.objects.filter(user_id=user_id).exists():
return Anime.objects.order_by('-views')[:10]
# ...正常推荐逻辑
- 基于内容特征的混合推荐
python复制# 在Anime模型中添加内容特征字段
content_features = models.TextField() # 存储类型、国家等特征的向量
# 计算内容相似度
def content_similarity(anime1, anime2):
vec1 = json.loads(anime1.content_features)
vec2 = json.loads(anime2.content_features)
return cosine_similarity(vec1, vec2)
5.2 数据稀疏性问题
当用户-物品矩阵过于稀疏时的处理:
- 使用Slope One算法
python复制class SlopeOne:
def __init__(self):
self.diffs = defaultdict(dict)
self.freqs = defaultdict(dict)
def update(self, data):
for user, items in data.items():
for item1, rating1 in items.items():
for item2, rating2 in items.items():
if item1 != item2:
self.freqs[item1].setdefault(item2, 0)
self.diffs[item1].setdefault(item2, 0)
self.freqs[item1][item2] += 1
self.diffs[item1][item2] += rating1 - rating2
for item1, related in self.diffs.items():
for item2 in related:
self.diffs[item1][item2] /= self.freqs[item1][item2]
- 矩阵分解技术
python复制from sklearn.decomposition import NMF
def matrix_factorization(data):
# 构建用户-物品矩阵
matrix = build_sparse_matrix(data)
# 非负矩阵分解
model = NMF(n_components=10)
user_factors = model.fit_transform(matrix)
item_factors = model.components_
return user_factors, item_factors
6. 项目扩展方向
- 实时推荐:使用Redis存储用户最近行为,实现实时推荐更新
python复制import redis
r = redis.Redis()
def track_behavior(user_id, anime_id):
r.lpush(f'recent:{user_id}', anime_id)
r.ltrim(f'recent:{user_id}', 0, 9) # 保留最近10条
- 多算法融合:结合基于内容和协同过滤的结果
python复制def hybrid_recommend(user_id):
cf_rec = collaborative_filtering(user_id)
cb_rec = content_based(user_id)
# 加权融合
combined = {}
for item, score in cf_rec:
combined[item] = score * 0.7
for item, score in cb_rec:
combined[item] = combined.get(item, 0) + score * 0.3
return sorted(combined.items(), key=lambda x: x[1], reverse=True)
- 用户画像构建:基于行为数据构建用户兴趣标签
python复制def build_user_profile(user_id):
behaviors = UserBehavior.objects.filter(user_id=user_id)
genre_counter = defaultdict(int)
for b in behaviors:
anime = b.anime
for genre in anime.genre.split(','):
genre_counter[genre.strip()] += 1
total = sum(genre_counter.values())
return {k: v/total for k, v in genre_counter.items()}
