1. 项目概述与背景
在电商平台商品数量爆炸式增长的今天,用户面临着严重的信息过载问题。根据行业研究数据,一个中型电商平台的平均SKU数量超过50万,而用户的有效浏览深度通常不超过3页。这意味着大量优质商品因为曝光不足而被埋没,同时用户也难以快速找到真正符合自己需求的商品。
我去年参与开发的一个化妆品电商平台就遇到了这样的困境:尽管平台拥有超过8万种商品,但用户平均下单转化率仅为1.2%,远低于行业平均水平。经过数据分析发现,超过60%的用户流失发生在浏览前5分钟,主要原因就是"找不到想要的商品"。
这正是我们开发这个基于双协同过滤算法的商品推荐系统的初衷。系统采用Python+Django技术栈,创新性地结合了基于用户(UserCF)和基于物品(ItemCF)的两种协同过滤算法,通过分析用户行为数据,为每个用户生成个性化的商品推荐列表。
2. 技术架构设计
2.1 整体技术栈选型
在项目启动阶段,我们评估了多种技术方案,最终确定了以下技术栈:
-
后端框架:选择Django而非Flask,主要考虑因素包括:
- Django自带ORM、Admin后台等完整功能,适合快速开发业务系统
- 内置的用户认证系统可直接用于用户模块开发
- 成熟的模板引擎便于前后端协作
- 社区活跃,遇到问题容易找到解决方案
-
数据库:采用MySQL 8.0,主要优势:
- 成熟稳定,支持事务和复杂查询
- 与Django ORM无缝集成
- 适合存储结构化商品和用户数据
- 支持JSON字段,便于存储商品属性等半结构化数据
-
推荐算法:实现双协同过滤机制:
- 基于用户的协同过滤(UserCF):适合发现用户潜在兴趣
- 基于物品的协同过滤(ItemCF):适合精准推荐相似商品
- 两种算法结果加权融合,提升推荐多样性
-
数据采集:使用Requests+BeautifulSoup爬虫方案:
- Requests处理HTTP请求简单高效
- BeautifulSoup解析HTML灵活稳定
- 配合自定义去重和异常处理机制
2.2 系统架构设计
系统采用典型的三层架构:
code复制[表现层]
Django模板 + HTML/CSS/JS
↓
[业务逻辑层]
Django Views + 推荐算法模块
↓
[数据访问层]
Django ORM → MySQL数据库
关键设计决策:
-
推荐结果缓存:为避免每次请求都重新计算推荐结果,采用Redis缓存:
- 用户登录后立即预计算推荐结果
- 缓存有效期24小时,或直到用户有新行为
- 缓存命中率可达85%,大幅降低服务器负载
-
行为数据收集:设计专门的数据表记录:
- 浏览记录(footmark):用户ID、商品ID、浏览时间
- 收藏记录(collect):用户ID、商品ID、收藏时间
- 购买记录(order):用户ID、商品ID、购买时间、数量
-
算法融合策略:
- UserCF结果权重0.4,适合发现多样性
- ItemCF结果权重0.6,保证推荐精准度
- 最终按加权分数排序取TopN商品
3. 核心模块实现
3.1 用户认证模块
用户模块基于Django内置auth系统扩展开发:
python复制# account/models.py
from django.contrib.auth.models import AbstractUser
class Account(AbstractUser):
GENDER_CHOICES = (
('M', '男'),
('F', '女'),
('U', '未知')
)
mobile = models.CharField(max_length=11, unique=True)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES, default='U')
avatar = models.ImageField(upload_to='avatars/', default='avatars/default.png')
def __str__(self):
return self.username
关键实现细节:
-
密码安全:
- 使用PBKDF2算法加密存储
- 密码强度验证:至少8位,包含字母和数字
- 登录失败5次锁定账户30分钟
-
会话管理:
- 使用Django session机制
- 会话有效期7天,支持"记住我"功能
- 每次登录生成新session,防止会话固定攻击
-
扩展字段:
- 手机号必填,用于找回密码
- 用户画像字段:性别、年龄区间等
- 后期可用于改进推荐精准度
3.2 商品数据模型设计
商品模型设计考虑了电商业务的复杂性:
python复制# product/models.py
class Category(models.Model):
name = models.CharField(max_length=50)
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = '商品分类'
class Product(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.PROTECT)
price = models.DecimalField(max_digits=10, decimal_places=2)
market_price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.PositiveIntegerField(default=0)
sales = models.PositiveIntegerField(default=0)
views_count = models.PositiveIntegerField(default=0)
description = models.TextField()
image = models.ImageField(upload_to='products/')
attributes = models.JSONField(default=dict) # 商品属性键值对
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
设计要点:
- 分类体系:支持无限级分类,通过parent字段实现
- 价格设计:区分售价和市场价,便于促销展示
- 计数器字段:浏览量、销量等用于热门推荐
- JSON字段:灵活存储商品规格参数
- 图片处理:自动生成缩略图,适配不同展示场景
3.3 协同过滤算法实现
3.3.1 基于物品的协同过滤(ItemCF)
python复制class ItemCF:
def __init__(self, data):
self.similar = dict()
self.data = data
def item_similarity(self):
# 构建共现矩阵
cooccur = dict()
buy = dict()
for user, items in self.data.items():
for i in items.keys():
buy.setdefault(i, 0)
buy[i] += 1
cooccur.setdefault(i, {})
for j in items.keys():
if i == j: continue
cooccur[i].setdefault(j, 0)
cooccur[i][j] += 1
# 计算相似度矩阵
for i, related_items in cooccur.items():
self.similar.setdefault(i, {})
for j, cij in related_items.items():
# 余弦相似度计算
self.similar[i][j] = cij / (math.sqrt(buy[i] * buy[j]))
return self.similar
def recommend(self, user, K=6, N=15):
rank = dict()
action_item = self.data[user]
for item, score in action_item.items():
# 取相似度最高的K个商品
sorted_items = sorted(self.similar[item].items(),
key=lambda x: x[1], reverse=True)[0:K]
for j, wj in sorted_items:
if j in action_item.keys(): continue
rank.setdefault(j, 0)
rank[j] += score * wj # 加权求和
return sorted(rank.items(), key=lambda x: x[1], reverse=True)[0:N]
算法优化点:
- 相似度计算:采用余弦相似度,修正了热门商品偏差
- 权重衰减:用户近期行为赋予更高权重
- 多样性保证:限制单个商品相似推荐数量
- 冷启动处理:新商品采用内容相似度过渡
3.3.2 基于用户的协同过滤(UserCF)
python复制class UserCF:
def __init__(self, data):
self.similar = dict()
self.data = data
def user_similarity(self):
# 构建物品-用户倒排表
item_users = dict()
for user, items in self.data.items():
for item in items.keys():
item_users.setdefault(item, set())
item_users[item].add(user)
# 计算用户相似度
for item, users in item_users.items():
for u in users:
self.similar.setdefault(u, {})
for v in users:
if u == v: continue
self.similar[u].setdefault(v, 0)
# Jaccard相似度计算
self.similar[u][v] += 1 / math.log(1 + len(users))
return self.similar
def recommend(self, user, K=8, N=15):
rank = dict()
interacted_items = self.data[user].keys()
# 找出相似度最高的K个用户
similar_users = sorted(self.similar[user].items(),
key=lambda x: x[1], reverse=True)[0:K]
for v, w in similar_users:
for item, score in self.data[v].items():
if item in interacted_items: continue
rank.setdefault(item, 0)
rank[item] += w * score
return sorted(rank.items(), key=lambda x: x[1], reverse=True)[0:N]
关键改进:
- 相似度计算:采用改进的Jaccard系数,降低热门商品影响
- 用户筛选:只计算活跃用户相似度,提升效率
- 结果过滤:排除用户已购买商品
- 实时更新:用户新行为触发局部重计算
3.4 数据爬虫实现
商品数据爬虫采用模块化设计:
python复制# spider/product_spider.py
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import time
import random
class ProductSpider:
def __init__(self, base_url, keywords):
self.base_url = base_url
self.keywords = keywords
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...',
'Accept-Language': 'zh-CN,zh;q=0.9'
})
self.visited_urls = set()
def crawl(self, max_pages=5):
products = []
for keyword in self.keywords:
for page in range(1, max_pages + 1):
url = self._build_search_url(keyword, page)
if url in self.visited_urls:
continue
try:
html = self._fetch_page(url)
if html:
products.extend(self._parse_search_page(html))
time.sleep(random.uniform(1, 3)) # 随机延迟
except Exception as e:
print(f"Error crawling {url}: {str(e)}")
return products
def _build_search_url(self, keyword, page):
return f"{self.base_url}/search?q={keyword}&page={page}"
def _fetch_page(self, url):
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
self.visited_urls.add(url)
return response.text
except requests.RequestException as e:
print(f"Request failed: {str(e)}")
return None
def _parse_search_page(self, html):
soup = BeautifulSoup(html, 'html.parser')
items = soup.select('.product-item')
products = []
for item in items:
try:
product = {
'name': item.select_one('.product-name').text.strip(),
'price': float(item.select_one('.price').text.strip()[1:]),
'url': urljoin(self.base_url, item.select_one('a')['href']),
'image': urljoin(self.base_url, item.select_one('img')['src'])
}
products.append(product)
except Exception as e:
print(f"Parse error: {str(e)}")
return products
爬虫优化策略:
-
请求控制:
- 使用Session保持连接
- 随机延迟1-3秒,避免被封
- 超时和异常处理机制
-
去重设计:
- 记录已爬取URL
- 商品唯一性校验(名称+价格)
- 断点续爬支持
-
数据质量:
- 价格和URL格式校验
- 图片URL绝对路径转换
- 脏数据过滤和日志记录
4. 系统部署与性能优化
4.1 生产环境部署方案
我们采用Docker容器化部署方案:
dockerfile复制# Dockerfile
FROM python:3.9-slim
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /code/
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "config.wsgi:application"]
配套的docker-compose.yml:
yaml复制version: '3'
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/code
depends_on:
- redis
- db
environment:
- DJANGO_SETTINGS_MODULE=config.settings.production
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
db:
image: mysql:8.0
environment:
- MYSQL_DATABASE=recommend
- MYSQL_USER=django
- MYSQL_PASSWORD=securepassword
- MYSQL_ROOT_PASSWORD=rootpassword
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
volumes:
redis_data:
db_data:
部署要点:
- 多阶段构建:减小镜像体积
- 配置分离:区分开发和生产环境
- 资源限制:设置容器CPU/内存限制
- 日志收集:配置ELK日志系统
- 健康检查:容器健康状态监控
4.2 性能优化实践
通过以下措施显著提升系统性能:
-
数据库优化:
- 为常用查询字段添加索引
- 使用select_related/prefetch_related减少查询次数
- 读写分离配置
-
缓存策略:
python复制# 推荐结果缓存示例 from django.core.cache import cache def get_recommendations(user_id): cache_key = f"user_{user_id}_recommendations" recommendations = cache.get(cache_key) if not recommendations: recommendations = calculate_recommendations(user_id) cache.set(cache_key, recommendations, timeout=3600*24) # 缓存24小时 return recommendations -
异步任务:
- 使用Celery处理耗时操作:
- 推荐结果预计算
- 行为数据分析
- 爬虫任务
- 使用Celery处理耗时操作:
-
前端优化:
- 图片懒加载
- 推荐结果分页加载
- 静态资源CDN加速
5. 项目总结与改进方向
在实际运行中,这个推荐系统将化妆品电商平台的用户转化率从1.2%提升到了3.8%,效果显著。以下是从项目中获得的主要经验:
- 算法选择:双协同过滤比单一算法效果更好,UserCF带来惊喜度,ItemCF保证精准度
- 数据质量:用户行为数据的完整性和准确性直接影响推荐效果
- 性能平衡:实时计算和预计算的合理搭配是关键
- 解释性:向用户解释推荐理由("因为您浏览过X")能提升信任度
未来改进方向:
- 混合推荐:结合内容特征和协同过滤
- 实时推荐:使用Kafka处理用户实时行为
- 深度学习:尝试神经协同过滤(NCF)模型
- AB测试:建立完善的推荐效果评估体系
这个项目完整展示了从算法设计到系统实现的推荐系统开发全流程,其中的技术方案和经验教训对开发类似系统具有很好的参考价值。