淘宝商铺推荐系统实战:Python协同过滤从零搭建与优化

发布时间:2026/10/11 13:42:25
淘宝商铺推荐系统实战:Python协同过滤从零搭建与优化
简介这份资源是面向Python Web开发与推荐算法学习者的完整项目源码包以淘宝商铺场景为例实现基于物品的协同过滤推荐系统。项目采用Django作为后端框架处理请求与数据库管理Vue.js配合Element-UI构建响应式前端界面并通过Scrapy爬虫采集电商商品与用户行为数据再借助NumPy、Pandas完成相似度计算与推荐列表生成适合作为课程设计、毕业设计或推荐系统入门实战参考。压缩包共122个文件约1.77MB其中40个py文件承载后端逻辑与算法实现36个pyc为编译缓存26个js与7个css构成前端交互与样式另含sqlite3数据库、html模板及字体图片等静态资源目录结构清晰便于按模块阅读。目前已有378人学习下载读者可从中获取完整的推荐系统实现思路、协同过滤算法落地代码与前后端联调结构用于理解电商推荐从数据采集到结果展示的全流程。1. 淘宝商铺推荐系统从零搭一套能跑通的协同过滤淘宝商铺推荐系统这个题目很多人第一反应是“电商大厂才玩得转”但真拆开看核心无非是拿用户对商铺的历史行为算出一个“你可能还喜欢哪些店”的排序列表。协同过滤就是干这个的经典路子不依赖商品文案、不依赖图像特征只靠用户和商铺之间的交互矩阵就能把相似用户喜欢过的店推给你。这套方案适合谁手里有几千到几十万条用户-商铺行为数据、想快速验证推荐效果、又不想一上来就上深度学习的中小团队或个人开发者。Python 生态里 numpy、pandas、scipy 足够把整套流程跑通不需要 GPU一台普通笔记本就能复现。下面我把从数据准备到离线评估的完整路径拆开讲中间会穿插淘宝商品数据常见的坑和参数调法。2. 协同过滤在商铺推荐里的两条路线UserCF 和 ItemCF 怎么选2.1 用户相似度和物品相似度的本质差别协同过滤分两支UserCF 找“和你口味相似的人”把他们喜欢的商铺推给你ItemCF 找“和你历史逛过的店相似的店”直接推给你。淘宝商铺场景下商铺数量通常远小于用户数量而且商铺的相似关系比用户相似关系稳定得多——一个用户今天逛女装明天逛数码兴趣漂移快但“卖女装的店”和“卖配饰的店”之间的关联不会天天变。所以 ItemCF 在商铺推荐里更常用离线计算物品相似度矩阵可以定期跑线上只做查表和排序响应快。但 UserCF 也不是没用。新用户冷启动时ItemCF 需要他至少有过一两次行为才能算相似UserCF 可以拿注册时填的偏好标签做粗匹配先推一批“相似人群都在逛的店”兜底。实际落地常见做法是主链路走 ItemCF冷启动和召回补充走 UserCF两路结果融合后排序。2.2 相似度计算余弦、皮尔逊和调整余弦的适用边界余弦相似度只看向量方向不看数值大小适合行为是“点击/收藏/加购”这种 0-1 或计数型数据。皮尔逊相似度会减去用户均值能抵消“有人逢店必逛、有人只逛精品”的评分尺度差异但淘宝商铺数据里显式评分很少大多是隐式反馈皮尔逊用起来反而引入噪声。调整余弦Adjusted Cosine在物品相似度计算时减去物品均分对热门商铺的惩罚更合理——一个被所有人逛过的店不应该因为“大家都逛”就认为它和任何店都相似。我一般会先算余弦如果发现推荐结果里全是头部大店再切到调整余弦。下面这段代码用 scipy 的稀疏矩阵算物品-物品余弦相似度数据是用户-商铺交互矩阵行是用户列是商铺。import numpy as np from scipy.sparse import csr_matrix from sklearn.preprocessing import normalize # 假设 interaction_matrix 是 scipy 稀疏矩阵shape(n_users, n_shops) # 值可以是点击次数、加购次数这里用二值化后的 0/1 def compute_item_similarity(interaction_matrix): # 转置成 shop-user 矩阵按行归一化后做点积得到余弦相似度 item_user interaction_matrix.T.tocsr() # L2 归一化每行模长变为 1 item_user_norm normalize(item_user, norml2, axis1) # 相似度矩阵 归一化矩阵乘自己的转置 sim_matrix item_user_norm.dot(item_user_norm.T) # 对角线置零避免自己和自己相似度为 1 干扰推荐 sim_matrix.setdiag(0) sim_matrix.eliminate_zeros() return sim_matrix逻辑说明interaction_matrix.T把用户-商铺矩阵转成商铺-用户矩阵每一行是一个商铺在所有用户上的行为向量。normalize做 L2 归一化后两个归一化向量的点积就是余弦相似度。setdiag(0)把对角线清零因为推荐时不需要“自己推自己”。参数上norml2是固定选择axis1表示按行归一化如果数据里商铺数量超过 10 万sim_matrix会变得很大需要改用 topK 截断或分块计算。2.3 从相似度到推荐列表打分公式和 TopN 截断有了物品相似度矩阵给用户 u 推荐商铺 i 的打分公式是遍历用户 u 历史交互过的商铺集合 S对每个 s 取sim(i, s)累加。如果用户对商铺有过不同强度的行为点击1收藏2加购3购买5可以把行为权重乘进去。公式写成score(u, i) sum_{s in S} sim(i, s) * weight(u, s)算完所有候选商铺的分数后排除用户已经交互过的按分数降序取 TopN。这里有个工程细节如果商铺数量是百万级不可能对每个用户遍历所有商铺算分常见做法是先用相似度矩阵对每个商铺取 TopK 相似邻居然后只对用户历史商铺的邻居集合做打分候选集能缩小两个数量级。def recommend_for_user(user_id, interaction_matrix, sim_matrix, top_k20, top_n10): # 取用户历史交互过的商铺索引和对应行为权重 user_row interaction_matrix[user_id].toarray().flatten() interacted_shops np.where(user_row 0)[0] if len(interacted_shops) 0: return [] # 冷启动交给 UserCF 或热门兜底 # 候选商铺 历史商铺的相似邻居并集 candidate_shops set() for s in interacted_shops: # sim_matrix 是稀疏矩阵取第 s 行非零列 neighbors sim_matrix[s].nonzero()[1] candidate_shops.update(neighbors) # 排除已交互 candidate_shops candidate_shops - set(interacted_shops) # 打分 scores {} for i in candidate_shops: score 0.0 for s in interacted_shops: score sim_matrix[i, s] * user_row[s] scores[i] score # 排序取 TopN ranked sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n] return ranked逻辑说明user_row是用户行为向量值越大表示行为越强。candidate_shops只取历史商铺的相似邻居避免全量扫描。打分时sim_matrix[i, s]是商铺 i 和 s 的相似度user_row[s]是用户对 s 的行为权重。top_k控制每个商铺取多少相似邻居top_n是最终推荐数量。参数调法top_k一般设 50 到 200太小召回不足太大引入噪声且计算变慢top_n按业务定首页推荐位通常 10 到 20 个。3. 用 Python 把淘宝商铺数据跑成推荐结果数据准备到离线评估3.1 淘宝商品数据清洗去重、去刷单、时间窗口截断淘宝商品数据拿到手第一件事不是急着建模而是清洗。常见问题有三个同一用户对同一商铺有多条重复行为记录需要按时间戳去重只保留最近一条刷单行为表现为某用户短时间内对大量商铺做相同操作可以用“单位时间行为数超过阈值”过滤时间窗口截断是指只用最近 N 天的数据太老的行为参考价值低而且能减少矩阵稀疏度。我一般会保留最近 90 天购买行为权重给 5加购给 3收藏给 2点击给 1。import pandas as pd def clean_taobao_behavior(df, days90): # df 列user_id, shop_id, behavior_type, timestamp df[timestamp] pd.to_datetime(df[timestamp]) cutoff df[timestamp].max() - pd.Timedelta(daysdays) df df[df[timestamp] cutoff] # 去重同一用户同一商铺同一行为只保留最近一次 df df.sort_values(timestamp).drop_duplicates( subset[user_id, shop_id, behavior_type], keeplast) # 过滤刷单单用户单日行为数超过 200 条视为异常 df[date] df[timestamp].dt.date daily_count df.groupby([user_id, date]).size() normal_users daily_count[daily_count 200].index.get_level_values(0) df df[df[user_id].isin(normal_users)] # 行为权重映射 weight_map {click: 1, collect: 2, cart: 3, buy: 5} df[weight] df[behavior_type].map(weight_map) return df逻辑说明cutoff控制时间窗口days90是经验值数据量大可以缩到 30。drop_duplicates的subset指定去重维度keeplast保留最近一次。刷单过滤用daily_count 200这个阈值需要根据实际数据分布调整可以先画用户日行为数直方图看拐点。weight_map把行为类型转成数值权重后续构建矩阵时直接填这个值。3.2 构建用户-商铺交互矩阵稀疏存储和内存控制清洗完的数据是长表需要转成用户-商铺矩阵。用户数和商铺数可能都是几万到几十万稠密矩阵内存扛不住必须用 scipy 稀疏矩阵。构建时用csr_matrix行索引是用户列索引是商铺值是行为权重累加。from scipy.sparse import csr_matrix def build_interaction_matrix(df): # 把 user_id 和 shop_id 映射成从 0 开始的连续整数 user_ids df[user_id].unique() shop_ids df[shop_id].unique() user_to_idx {uid: i for i, uid in enumerate(user_ids)} shop_to_idx {sid: i for i, sid in enumerate(shop_ids)} rows df[user_id].map(user_to_idx).values cols df[shop_id].map(shop_to_idx).values # 同一用户对同一商铺的多次行为权重累加 data df.groupby([user_id, shop_id])[weight].sum().values # 注意groupby 后的顺序和 rows/cols 不一致需要重新对齐 grouped df.groupby([user_id, shop_id])[weight].sum().reset_index() rows grouped[user_id].map(user_to_idx).values cols grouped[shop_id].map(shop_to_idx).values data grouped[weight].values matrix csr_matrix((data, (rows, cols)), shape(len(user_ids), len(shop_ids))) return matrix, user_to_idx, shop_to_idx逻辑说明user_to_idx和shop_to_idx是映射字典后续推荐结果需要反查回原始 ID。groupby后重新取 rows/cols 是因为直接对原 df 取 values 顺序和 groupby 结果不对齐这是个容易翻车的地方。csr_matrix的 shape 必须显式指定否则会按最大索引推断导致维度错误。内存方面假设 10 万用户、5 万商铺、100 万条交互稀疏矩阵非零元素 100 万存储约 12MB完全可控。3.3 离线评估命中率、召回率和覆盖率怎么算推荐系统不能只看“推出来的东西像不像”要用离线指标量化。常用三个命中率Hit Rate看 TopN 里有多少是用户真实交互过的召回率Recall看用户真实交互的商铺有多少被推出来了覆盖率Coverage看所有商铺中有多少被推荐过避免全推头部店。评估时把数据按时间切分前 80% 做训练后 20% 做测试。def evaluate(recommendations, test_matrix, n_shops): hits 0 total_rec 0 total_test 0 recommended_shops set() for user_id, rec_list in recommendations.items(): true_shops set(test_matrix[user_id].nonzero()[1]) rec_shops set([shop for shop, _ in rec_list]) hits len(rec_shops true_shops) total_rec len(rec_shops) total_test len(true_shops) recommended_shops.update(rec_shops) hit_rate hits / total_rec if total_rec 0 else 0 recall hits / total_test if total_test 0 else 0 coverage len(recommended_shops) / n_shops return hit_rate, recall, coverage逻辑说明recommendations是字典key 是用户 IDvalue 是[(shop_id, score), ...]。test_matrix是测试集的交互矩阵。hits统计推荐列表和真实交互的交集大小。hit_rate分母是推荐总数recall分母是真实交互总数。coverage用推荐过的商铺去重后除以总商铺数。参数上如果hit_rate高但coverage低说明推荐集中在少数商铺需要调整相似度计算或加多样性重排。4. 协同过滤推荐系统避坑数据稀疏、冷启动和实时性4.1 现象新用户打开首页推荐全是热门店原因和解决现象是冷启动用户没有历史行为ItemCF 算不出相似系统只能推全局最热门的商铺结果所有新用户看到一样的内容。原因是协同过滤依赖交互矩阵矩阵里没有这个用户的行任何基于相似度的打分都失效。解决办法分两层第一层用注册信息做粗召回比如用户填了“女装”偏好就从女装类目里取 Top 销量商铺第二层用 UserCF 的变体把新用户随机分配到几个“兴趣簇”按簇内热门推荐等用户产生几次点击后再切回 ItemCF。4.2 现象相似度矩阵太大跑不动原因和解决现象是商铺数量到几十万时sim_matrix稠密化后内存爆掉或者计算时间从几分钟变成几小时。原因是物品-物品相似度矩阵理论上是 N×N即使稀疏非零元素也可能到千万级。解决办法是只保留每个商铺的 TopK 相似邻居K 取 50 到 100用scipy.sparse的csr_matrix存储计算时用矩阵分块每次只算一批商铺和全量商铺的相似度算完立即截断 TopK 再存。4.3 现象推荐结果几天不变原因和解决现象是用户昨天逛了新的商铺今天推荐列表还是老样子。原因是离线相似度矩阵和用户行为向量没有及时更新。解决办法是用户行为向量实时更新每次用户产生新行为就更新对应行物品相似度矩阵按小时或按天增量更新只重算有行为变化的商铺的相似邻居。工程上可以用 Redis 存用户最近行为推荐服务每次请求时实时拼装用户向量相似度矩阵从离线任务加载。4.4 现象评估指标很好但线上点击率低原因和解决现象是离线 Hit Rate 到 0.3 以上上线后点击率却不如热门推荐。原因是离线评估用的是历史数据存在曝光偏差——用户只点击了系统展示过的商铺没展示过的商铺即使用户喜欢也无从点击。解决办法是离线评估时对未曝光商铺做加权或者用 IPS逆倾向分数矫正线上做 A/B 测试用小流量对比协同过滤和热门推荐的真实点击率以线上为准。5. 让推荐结果更耐看相似度融合和多样性重排的实操技巧协同过滤跑通之后最容易出现的问题是推荐结果“太窄”——用户逛了一家女装店接下来推的全是女装店虽然相似度高但用户可能已经买完了。我一般会在打分之后加一步多样性重排对 TopN 候选按类目打散同一个一级类目最多出现 3 个商铺剩下的名额留给相似度稍低但类目不同的商铺。具体做法是给每个商铺打上类目标签重排时维护一个类目计数字典超过阈值的候选跳过取下一个。另一个技巧是相似度融合。纯 ItemCF 只用了共现信息如果商铺有标题或类目文本可以用 TF-IDF 算文本相似度和协同相似度加权平均。权重我一般设 0.7 给协同、0.3 给文本文本相似度能缓解数据稀疏——两个商铺没有共同用户但标题里都出现“复古”“碎花”文本相似度能兜住。融合公式sim_final alpha * sim_cf (1 - alpha) * sim_textalpha用离线评估调从 0.5 到 0.9 扫一遍看 Hit Rate 拐点。验证融合和重排是否有效不能只看 Hit Rate还要看覆盖率和新类目曝光率。我习惯每次改动后跑三组指标Hit Rate、Coverage、以及 TopN 里的类目熵。类目熵越高说明推荐越多样。如果 Hit Rate 降了 2% 但 Coverage 涨了 15%线上点击率大概率是涨的因为用户看到的东西不再千篇一律。最后说个血泪经验协同过滤的离线指标和线上效果之间隔着一道曝光偏差的墙别迷信离线数字。我早期调参把 Hit Rate 从 0.25 刷到 0.35上线后点击率反而跌了后来加了多样性重排、把 Coverage 提上去线上才稳住。每次改完参数先跑离线看趋势再上小流量 A/B以线上为准。希望帮到你。本文还有配套的精品资源点击获取