1. 项目概述
最近在做一个基于协同过滤算法的体育运动场馆推荐小程序,这个项目挺有意思的,把推荐算法和实际应用场景结合起来。作为一个做过不少推荐系统的开发者,我想分享一下这个项目的完整实现过程,特别是如何把协同过滤算法落地到微信小程序中。
这个项目本质上是一个体育运动场馆的O2O平台,核心功能包括场馆浏览、预约、支付等基础功能,最大的亮点是加入了基于用户行为的个性化推荐系统。通过分析用户的历史评分数据,系统能够预测用户可能感兴趣的其他场馆,提升用户体验和转化率。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 需求分析与系统设计
2.1 核心功能拆解
这个系统需要实现以下几个核心模块:
-
用户系统:包括微信授权登录、个人信息管理等功能。微信小程序的优势在于可以直接使用微信的登录体系,省去了很多账号管理的麻烦。
-
场馆管理:场馆信息的CRUD操作,包括场馆的基本信息、图片、位置、价格等。这部分需要特别注意场馆数据的完整性和准确性。
-
评价系统:用户可以对场馆进行评分和评论,这是推荐系统的数据基础。评分采用1-5分的标准体系。
-
推荐系统:基于协同过滤算法,根据用户的历史行为推荐可能感兴趣的其他场馆。这是整个项目的技术难点。
-
预约支付:完整的预约流程,包括选择时间、支付等环节。需要与微信支付API对接。
2.2 技术选型考量
在技术选型上,我们做了如下选择:
后端:Python + Flask框架。选择Python主要是因为推荐算法生态丰富,Flask相比Django更轻量灵活,适合这个规模的项目。
数据库:MySQL。关系型数据库适合存储结构化数据,而且与Python生态兼容性好。PostgreSQL也是不错的选择,但考虑到部署成本选择了MySQL。
推荐算法库:Surprise。这是一个专门用于推荐系统的Python库,实现了多种协同过滤算法,API设计也很友好。
前端:微信小程序原生开发。虽然Uniapp可以跨平台,但考虑到性能和微信生态的深度整合,还是选择了原生开发。
3. 数据库设计详解
3.1 主要数据表结构
数据库设计是整个系统的基础,我们设计了以下几个核心表:
- user表:存储用户信息
sql复制CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
openid VARCHAR(64) UNIQUE NOT NULL,
nickname VARCHAR(64),
avatar_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
- venue表:存储场馆信息
sql复制CREATE TABLE venue (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
address VARCHAR(255),
latitude DECIMAL(10,7),
longitude DECIMAL(10,7),
price DECIMAL(10,2),
description TEXT,
cover_image VARCHAR(255)
);
- rating表:存储用户评分
sql复制CREATE TABLE rating (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
venue_id INT NOT NULL,
score TINYINT NOT NULL CHECK (score BETWEEN 1 AND 5),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id),
FOREIGN KEY (venue_id) REFERENCES venue(id),
UNIQUE KEY (user_id, venue_id)
);
- booking表:存储预约信息
sql复制CREATE TABLE booking (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
venue_id INT NOT NULL,
booking_date DATE NOT NULL,
time_slot VARCHAR(32) NOT NULL,
status TINYINT DEFAULT 0 COMMENT '0-待支付 1-已支付 2-已取消',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id),
FOREIGN KEY (venue_id) REFERENCES venue(id)
);
3.2 数据库设计要点
-
索引优化:在user表的openid字段、rating表的(user_id, venue_id)组合上建立了唯一索引,提高查询效率。
-
数据一致性:通过外键约束保证数据完整性,比如删除用户时,相关的评分和预约记录也会级联删除。
-
评分去重:通过UNIQUE KEY确保一个用户对同一个场馆只能评分一次。
4. 推荐算法实现
4.1 协同过滤算法原理
协同过滤分为基于用户的和基于物品的两种,我们选择了基于用户的协同过滤,因为:
- 用户数量相对场馆数量更少,计算量更小
- 用户兴趣相对稳定,推荐结果更可靠
- 实现起来更直观,适合作为第一个版本
算法主要步骤:
- 计算用户相似度矩阵(余弦相似度)
- 找出目标用户的k个最近邻
- 根据邻居的评分预测目标用户对未评分场馆的偏好
- 按预测评分排序,返回Top N推荐
4.2 代码实现细节
使用Surprise库实现的核心代码:
python复制from surprise import Dataset, KNNBasic
from surprise.model_selection import train_test_split
from surprise.accuracy import rmse
import pandas as pd
from sqlalchemy import create_engine
# 从数据库加载评分数据
engine = create_engine('mysql+pymysql://user:password@localhost/sport_venue')
query = "SELECT user_id, venue_id, score FROM rating"
df = pd.read_sql(query, engine)
# 转换数据格式
reader = surprise.Reader(rating_scale=(1, 5))
data = surprise.Dataset.load_from_df(df[['user_id', 'venue_id', 'score']], reader)
# 划分训练集和测试集
trainset, testset = train_test_split(data, test_size=0.2)
# 配置算法参数
sim_options = {
'name': 'cosine', # 使用余弦相似度
'user_based': True # 基于用户的协同过滤
}
# 训练模型
algo = KNNBasic(k=20, min_k=5, sim_options=sim_options)
algo.fit(trainset)
# 评估模型
predictions = algo.test(testset)
print(f'RMSE: {rmse(predictions)}')
# 为用户推荐
def recommend_for_user(user_id, n=5):
venues = df['venue_id'].unique()
rated = df[df['user_id'] == user_id]['venue_id'].tolist()
unrated = [v for v in venues if v not in rated]
predictions = []
for venue_id in unrated:
pred = algo.predict(str(user_id), str(venue_id))
predictions.append((venue_id, pred.est))
# 按预测评分降序排序
predictions.sort(key=lambda x: x[1], reverse=True)
return [v[0] for v in predictions[:n]]
4.3 算法优化实践
-
冷启动问题:新用户或新场馆缺乏评分数据时,采用混合推荐策略:
- 新用户:推荐热门场馆
- 新场馆:随机推荐给活跃用户
-
数据稀疏性:使用矩阵分解(SVD)改进:
python复制from surprise import SVD
algo = SVD(n_factors=50, n_epochs=20, biased=True)
- 实时性:用户行为数据每小时全量更新一次模型,推荐结果缓存10分钟。
5. 微信小程序开发
5.1 前端页面设计
小程序主要包含以下几个页面:
- 首页:展示个性化推荐列表,顶部有搜索栏
- 场馆详情页:展示场馆详细信息、评价、预约入口
- 预约页面:选择日期和时间段
- 个人中心:用户信息、我的预约、我的评价
5.2 关键API接口
后端提供的主要API接口:
- 推荐接口 GET /api/recommend
json复制{
"code": 0,
"data": [
{
"id": 1,
"name": "XX篮球馆",
"address": "XX路123号",
"price": 80,
"cover_image": "https://...",
"avg_rating": 4.5
},
...
]
}
- 场馆详情 GET /api/venue/
json复制{
"code": 0,
"data": {
"id": 1,
"name": "XX篮球馆",
"address": "XX路123号",
"description": "专业木地板...",
"price": 80,
"images": ["https://...", ...],
"ratings": [
{
"user": {"nickname": "用户A", "avatar": "..."},
"score": 5,
"comment": "场地很棒",
"created_at": "2023-05-01"
},
...
]
}
}
- 提交评分 POST /api/rating
json复制{
"venue_id": 1,
"score": 5,
"comment": "体验很好"
}
5.3 微信登录实现
小程序端代码:
javascript复制wx.login({
success: res => {
if (res.code) {
wx.request({
url: 'https://yourdomain.com/api/login',
method: 'POST',
data: { code: res.code },
success: res => {
wx.setStorageSync('token', res.data.token)
}
})
}
}
})
后端验证代码:
python复制import requests
from flask import jsonify
@app.route('/api/login', methods=['POST'])
def login():
code = request.json.get('code')
# 调用微信接口获取openid
url = f'https://api.weixin.qq.com/sns/jscode2session?appid={APPID}&secret={SECRET}&js_code={code}&grant_type=authorization_code'
resp = requests.get(url).json()
openid = resp.get('openid')
if not openid:
return jsonify({'code': 1, 'msg': '登录失败'})
# 查找或创建用户
user = User.query.filter_by(openid=openid).first()
if not user:
user = User(openid=openid)
db.session.add(user)
db.session.commit()
# 生成JWT token
token = generate_token(user.id)
return jsonify({'code': 0, 'token': token})
6. 系统集成与测试
6.1 接口联调要点
-
数据格式统一:前后端约定好统一的返回格式,包括状态码、错误信息等。
-
参数校验:后端对所有输入参数进行严格校验,防止非法数据。
-
错误处理:定义清晰的错误码体系,前端根据错误码显示对应的提示信息。
6.2 推荐算法评估
我们使用RMSE(均方根误差)作为评估指标:
python复制from surprise.accuracy import rmse
predictions = algo.test(testset)
print(f'RMSE: {rmse(predictions)}')
实际测试中,我们的模型RMSE值在0.8左右,意味着预测评分与实际评分的平均差异在0.8分以内,效果可以接受。
6.3 A/B测试方案
为了验证推荐效果,我们设计了A/B测试:
- A组:看到算法推荐结果
- B组:看到随机推荐结果
关键指标对比:
- 点击率(CTR)
- 转化率(预约率)
- 用户停留时长
测试结果显示,A组的各项指标均显著优于B组,验证了推荐算法的有效性。
7. 部署与上线
7.1 后端部署
我们使用Nginx + Gunicorn部署Flask应用:
- 安装依赖:
bash复制pip install gunicorn
- 启动命令:
bash复制gunicorn -w 4 -b 0.0.0.0:8000 app:app
- 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;
}
}
7.2 小程序上线
- 开发版本:用于开发和测试
- 体验版本:供产品经理和测试人员体验
- 审核版本:提交微信审核
- 发布版本:审核通过后发布
7.3 监控与维护
- 日志收集:使用ELK栈收集和分析日志
- 性能监控:使用Prometheus + Grafana监控系统性能
- 异常报警:设置关键指标阈值,异常时发送报警
8. 关键注意事项与经验分享
8.1 数据稀疏性问题
在实际运行中发现,很多用户只评价了很少的场馆,导致用户相似度计算不准确。我们采取了以下措施:
- 数据增强:主动邀请活跃用户评价更多场馆
- 混合推荐:当用户数据不足时,结合热门场馆推荐
- 隐式反馈:将浏览、预约等行为也作为用户兴趣的指标
8.2 实时性优化
最初的实现是每天全量更新一次推荐模型,但用户反馈推荐结果不够及时。改进方案:
- 增量更新:每小时更新新增的评分数据
- 实时调整:当用户有新行为时,立即调整推荐结果
- 缓存策略:推荐结果缓存10分钟,平衡实时性和性能
8.3 性能调优
随着用户量增长,推荐接口响应变慢。我们做了以下优化:
- 预计算:在低峰期预计算热门用户的推荐结果
- 分页加载:推荐结果分页返回,减少单次数据量
- CDN加速:静态资源使用CDN加速
8.4 业务指标提升
通过数据分析,我们发现几个提升业务指标的关键点:
- 评分引导:在用户完成预约后适时引导评分,增加数据量
- 推荐多样性:避免总是推荐相同类型的场馆,保持新鲜感
- 季节性调整:根据不同季节调整推荐权重(如夏季多推荐游泳馆)
9. 项目总结与展望
这个项目从技术角度实现了基于协同过滤的推荐系统,并将其成功应用到体育运动场馆的微信小程序中。在实际运营中,推荐系统显著提升了用户的活跃度和场馆的预约率。
几个关键的技术收获:
- 算法与工程的平衡:不能只追求算法精度,还要考虑工程实现的复杂度和性能
- 数据质量的重要性:推荐系统的效果很大程度上取决于数据的质量和数量
- 用户体验的考量:推荐结果不仅要准确,还要考虑多样性、新鲜度等用户体验因素
未来可能的改进方向:
- 引入深度学习模型,如神经协同过滤(NCF)
- 增加多模态数据,如图片、文本描述等
- 实现跨平台的推荐系统,整合小程序、App、网站等多端数据
这个项目让我深刻体会到推荐系统在实际业务中的应用价值,也积累了不少工程实践的经验。特别是在处理数据稀疏性和冷启动问题上,有很多值得深入探索的空间。
