
简介基于协同过滤的淘宝商铺推荐系统是一份面向电商个性化推荐场景的完整Python项目源码包适合具备一定编程基础、希望深入理解推荐算法或完成Web开发实战的开发者。压缩包内共122个文件包含40个Python源码涵盖Django后端逻辑、Scrapy爬虫脚本与协同过滤算法实现、36个pyc编译文件、26个JavaScript前端脚本、7个CSS样式文件以及SQLite数据库、字体文件和Scrapy配置等整体体积仅1.77MB小巧易用。目前已有378人学习下载。系统以基于物品的协同过滤为核心完整展示了用户行为数据处理、物品相似度计算、推荐列表生成以及Django接口与Vue.jsElement-UI前端展示的对接过程同时附带Scrapy采集配置和可运行的工程目录读者既能参考其前后端分离的架构也能直接获取可运行或改造的电商商铺推荐系统适合课程设计、毕业设计或推荐系统入门实战。1. 这不是一个 Demo把基于协同过滤的淘宝商铺推荐系统拆开看当手里拿到一份“python 基于协同过滤的淘宝商铺推荐系统”源码包如果只是跑通它你可能觉得它不过是个课设如果停下来把用户-商铺评分矩阵、相似度计算、topN 推荐逻辑一条一条跟下来你会发现它把推荐系统里最朴素的协同过滤路径走全了。这个项目解决的是很具体的问题用户没有明确搜索意图、只有零散的历史浏览或购买行为时怎么从海量淘宝商铺里挑出他可能感兴趣的下一批店铺。它适合两类人一类是准备做大数据或推荐方向毕业设计的学生一类是想在本地快速复现一个“Python 分析 Vue.js 展示”全栈小项目的工程师。这份资源解决的不是“看懂公式”而是“跑起来并知道每一行代码为什么这么写”。2. 协同过滤与技术栈为什么这个项目用 Python Vue.js2.1 基于物品 vs 基于用户的协同过滤场景决定选型协同过滤的核心假设是人以群分、物以类聚。基于用户的协同过滤UserCF先找到与目标用户兴趣最相似的 K 个用户再把这 K 个用户买过的商铺推给目标用户基于物品的协同过滤ItemCF则先计算商铺之间的相似度再根据用户历史上交互过的商铺找出这批商铺的相似商铺做推荐。在淘宝商铺这个场景下商铺数量远小于用户数量且单个用户的行为记录通常非常稀疏。如果走 UserCF需要实时计算用户之间的相似度用户量上来之后矩阵膨胀极快新用户没有历史行为也根本找不到相似用户。ItemCF 的优势在于商铺相似度矩阵可以离线算好用户行为一进来直接查矩阵就能算出推荐结果。我在源码里看到的也是典型的 ItemCF 路径先构建商铺相似度再算用户对候选商铺的预测评分最后按分数排序列出 topN。这里有个工程上常用的判断标准如果物品数量比用户少一个数量级且物品相对稳定优先考虑 ItemCF反过来如果是新闻、短视频这种物品大量产生且快速过期的场景UserCF 更合适。这个项目选 ItemCF是贴着淘宝商铺的数据特点来的不是随便挑的。新手容易犯的错是把两种方法混着写最后相似度矩阵的语义既不面向物品也不面向用户调试起来非常痛苦。2.2 技术栈分层Python 做计算Vue.js 做展示这个项目最显眼的技术关键词就是 Python 和 Vue.js。它们不是并列关系而是明确的分层。Python 负责数据清洗、评分矩阵构建、相似度计算和推荐结果生成这是推荐系统的核心Vue.js 负责把推荐结果渲染成页面是用户看得到的脸面。我在源码里理顺的分工大致是pandas 做数据聚合、numpy 算相似度矩阵、Flask 暴露 HTTP 接口前端 Vue.js 通过 axios 请求接口拿到 JSON再在模板里渲染出“为您推荐的商铺”列表。这种前后端分离的结构对准备毕设答辩的人来说刚好用到课程里的 RESTful API 概念对已经工作的人而言它和真实生产环境的 web 服务结构是一致的不是那种把页面和算法揉在一个文件里的玩具代码。层级选型职责数据层pandas / numpy清洗原始行为日志、构建用户-商铺评分矩阵、计算物品相似度接口层Flask / FastAPI接收前端查询参数返回 topN 推荐结果的 JSON展示层Vue.js axios渲染推荐页面、封装请求、绑定响应数据运行层Python 3.x 虚拟环境隔离依赖、固定版本、保证环境可复现2.3 源码包的整体结构先看目录再动手拿到 zip 包之后第一步不是急着运行而是先看目录结构。常见做法是确认几个关键文件是否齐全数据处理脚本、推荐算法核心模块、后端 API 入口、前端 Vue 项目、依赖清单和说明文档。我习惯先把目录树拉出来再看文件之间的调用关系。taobao_shop_recommend/ ├── backend/ # Python 后端 │ ├── app.py # Flask 入口定义 /api/recommend 接口 │ ├── recommender.py # 协同过滤核心算法 │ ├── data_loader.py # 数据读取与预处理 │ └── requirements.txt # Python 依赖清单 ├── frontend/ # Vue.js 前端 │ ├── src/ │ │ ├── views/ # 页面组件 │ │ └── api/ # axios 请求封装 │ └── package.json ├── data/ │ └── user_behavior.csv # 原始用户行为数据 └── README.md这段目录结构里backend 下的 data_loader.py 和 recommender.py 是算法核心app.py 只负责把算法封装成接口。很多课程项目喜欢把算法逻辑全塞进 app.py那是典型的没分层这个项目把数据加载、推荐算法、接口层拆开后面定位问题会快很多。python 入门阶段最容易忽视的也是这种代码组织方式等你想换一种相似度计算函数时就会发现拆开写有多省事。3. 数据预处理与评分矩阵推荐效果的源头3.1 原始行为数据长什么样怎么转成评分淘宝商铺场景下的原始数据通常是一张用户行为日志表。每一行代表一次用户与商铺的交互字段无非是用户 ID、商铺 ID、行为类型浏览、点击、收藏、购买、行为时间。它不直接是评分数据要先转换成“用户-商铺”二维矩阵才能喂给协同过滤算法。这里最常见的做法是给不同行为加权购买记 5、收藏记 3、点击记 1.5、浏览记 0.5最后按用户和商铺聚合成一个综合评分。如果直接用购买次数当评分没买过东西的用户就全是空行矩阵会稀疏到没法用再加上时间衰减会更精确但课程项目里通常保留一个加权逻辑就够了。import pandas as pd # 读取原始行为日志 df pd.read_csv(data/user_behavior.csv, encodingutf-8) # 行为权重映射按对用户兴趣的揭示程度赋分 behavior_weight { buy: 5.0, collect: 3.0, click: 1.5, view: 0.5 } df[score] df[behavior].map(behavior_weight) # 同一用户对同一商铺的所有行为加权求和 rating df.groupby([user_id, shop_id])[score].sum().reset_index() rating.rename(columns{score: rating}, inplaceTrue) print(rating.head())这里最关键的是 behavior_weight 映射。它决定了评分矩阵的语义如果你把点击和购买权重拉平推荐结果会更偏向热度而非用户真实偏好如果购买权重过高那些浏览多但购买少的用户会被严重低估。实际调试中这组权重是第一个值得反复调的参数不要拿到默认值就用到底后面做离线评估时你会回来改它的。3.2 用 pivot_table 构建稀疏评分矩阵pandas 的 pivot_table 方法可以把长表转成宽表得到标准的用户-商铺评分矩阵。行是用户列是商铺值是行为加权后的评分没有交互的位置留空或填零。rating_matrix rating.pivot_table( indexuser_id, columnsshop_id, valuesrating, fill_value0.0 ) print(rating_matrix.shape) # (用户数, 商铺数)这里用 fill_value0.0 把空位填成零是为了让后续的相似度计算不需要做额外空值判断。但这个填充方式有个隐患它把“没有交互”和“不喜欢”混为一谈在后面的避坑章节我会专门展开。pivot 之后一定要检查矩阵的稀疏度如果非零元素占比小于 1%说明数据太碎通常要过滤掉行为数少于 5 条的用户或商铺否则算出来的相似度基本是噪声。3.3 相似度计算余弦相似度与皮尔逊相关系数的取舍协同过滤里核心的数学模型是相似度计算。两个候选最常用皮尔逊相关系数和余弦相似度。皮尔逊会先对向量去均值消除不同用户或商铺的评分尺度差异适合评分不受零干扰的稠密场景余弦相似度更关注方向一致性在大量零值存在的场景里表现更稳计算也简单。这个项目的商铺评分向量里大量位置是 0我一般选择余弦相似度配合 numpy 的矩阵运算效率高且不需要额外处理稀疏值。实现方式就是把矩阵做 L2 范数归一化后做一次矩阵乘法。import numpy as np def cosine_similarity(matrix): 计算商铺之间的余弦相似度矩阵 matrix: 用户-商铺评分矩阵行为用户列为商铺 norm np.linalg.norm(matrix, axis0) norm[norm 0] 1e-6 # 防止全零商铺导致除零但后面会说明这样做会埋坑 norm_matrix matrix / norm.reshape(1, -1) similarity np.dot(norm_matrix.T, norm_matrix) return similarity item_sim cosine_similarity(rating_matrix.values) np.fill_diagonal(item_sim, 0.0) # 去掉自身相似度避免把商铺自己推荐给自己这段代码里matrix 是用户-商铺矩阵axis0 按列算范数对应商铺维度。norm[norm 0] 的处理是为了避免那些行为数据全空的商铺向量除零。最后那行 np.fill_diagonal(item_sim, 0.0) 是很容易漏的如果不把对角线清零推荐候选里永远第一是商铺自己这在推荐系统里是低级错误。注意fill_value0 和范数置 1e-6 都是工程上的简化手段它们会让相似度矩阵出现小规模的噪声。后面第 4 章会专门讨论这类处理方式的边界问题。4. 避坑稀疏矩阵、冷启动与前后端联调的五个常见问题4.1 现象相似度矩阵全是 NaN一推荐就崩原因评分矩阵稀疏度过高两个商铺的评分向量几乎没有非零交集余弦相似度分母在极端情况下为零范数处理写得不严谨NaN 通过矩阵乘法传染到整个相似度矩阵。解决计算范数时先统计向量长度把长度为 0 的列替换为极小值而不是套一个固定 1e-6 敷衍了事更稳妥的方式是在构建矩阵之前过滤掉行为记录太少的用户和商铺从源头拉稠矩阵。我见过不少翻车现场解法都是先回到数据预处理那一步重新过滤。4.2 现象商铺数量到 5000 个相似度矩阵内存直接爆掉原因5000 × 5000 的 float64 矩阵占大约 200MB这只是中间结果如果商铺数量到 2 万个矩阵膨胀到 3.2GB普通开发机直接卡死前端页面也会跟着请求超时。解决不要保存全量稠密相似度矩阵只保留每个商铺最相似的 topK 个邻居比如 30 个。常用做法是算完一行相似度后立即用 np.argsort 取前 K 个索引其余丢弃或者直接用 scipy.sparse 的 csr_matrix 存储稀疏相似度内存能降一个数量级代码改动也不大。4.3 现象新用户没有任何行为记录后端返回空推荐列表原因协同过滤完全依赖历史行为做推断冷启动用户的评分向量是零找不到任何可以关联的相似商铺。解决在接口层做兜底策略。如果用户评分向量全为零直接返回全站热门商铺 topN。推荐系统的生产环境里这叫 fallback 策略课程项目里同样适用。在 app.py 的推荐接口里加一个 if user_ratings.sum() 0 分支走全站点击量最高的店铺列表这个逻辑简单且能明显提升产品完成度。4.4 现象Vue.js 页面能打开但 axios 请求后端接口报 CORS 错误原因前端跑在 localhost:8080后端跑在 localhost:5000端口不同就构成跨域。浏览器默认拦截跨域响应后端接口没有返回 CORS 头请求被浏览器直接挡掉。解决用 flask-cors 扩展一行 CORS(app) 就能解决FastAPI 则用 CORSMiddleware 配置 allowed_origins比如 [http://localhost:8080]。这里提醒一句allowed_origins 不要直接配成 *本地调试可以后续如果部署到线上会有安全隐患。4.5 现象requirements.txt 装一半报错Python 环境直接翻车原因numpy、pandas、scikit-learn 等包之间存在版本兼容问题特别是 numpy 2.x 和旧版 pandas 的接口冲突另外缺少系统级依赖也很难排查。解决先建虚拟环境再装依赖。requirements.txt 里把关键包版本固定写死比如 numpy1.26.4、pandas2.0.3pip 安装慢的话换国内镜像源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt。pycharm 配置 python 环境时也建议直接让解释器指向虚拟环境路径别用全局 Python不然你换个项目就冲突一次这是 Python 项目里血泪经验最多的一环。5. 前后端联调Vue.js 页面与推荐接口的完整对接5.1 后端推荐接口把算法封装成 HTTP 路由有了相似度矩阵后推荐逻辑要暴露成接口才能被前端调用。常见设计是 GET /api/recommend?user_id1001返回该用户的 topN 店铺列表。这里有两个关键点相似度矩阵应该在后端启动时一次性加载不能在每次请求时重新计算接口返回的 JSON 结构要稳定前端才好解析。from flask import Flask, jsonify, request import numpy as np app Flask(__name__) # 假设 rating_matrix 和 item_sim 已经在启动时由 data_loader 和 recommender 加载完毕 rating_matrix ... item_sim ... app.route(/api/recommend, methods[GET]) def recommend(): user_id request.args.get(user_id, typeint) if user_id is None or user_id not in rating_matrix.index: return jsonify({code: 1, msg: user not found, data: []}) user_ratings rating_matrix.loc[user_id].values # 冷启动兜底全零评分用户返回全站热门 if user_ratings.sum() 0: hot_index np.argsort(-item_sim.sum(axis0))[:10] return jsonify({code: 0, data: [{shop_id: int(i)} for i in hot_index]}) # 用户评分向量 × 商铺相似度矩阵 用户对每个商铺的预测评分 scores np.dot(user_ratings, item_sim) top_indices np.argsort(-scores)[:10] result [{shop_id: int(i), score: float(scores[i])} for i in top_indices] return jsonify({code: 0, data: result}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)这里最值得关注的是 scores np.dot(user_ratings, item_sim) 这行。它把逐商铺循环的推荐逻辑压缩成一次矩阵乘法用户当前评分向量乘以商铺相似度矩阵得到用户对每一个商铺的预测评分。分数越高的商铺越可能是用户没接触过但风格相似的店。这种写法效率高也更接近真实推荐系统的做法比用 for 循环累加相似度要优雅得多。5.2 前端请求与渲染axios 封装 页面动态绑定Vue.js 部分的常规做法是在 src/api 里封装一个请求函数然后在页面组件的 created 生命周期里调用把返回的推荐列表绑到 data 上模板里用 v-for 渲染出来。// frontend/src/api/recommend.js import axios from axios; const api axios.create({ baseURL: http://localhost:5000, // 指向 Flask 后端 timeout: 3000 }); export function getRecommend(userId) { return api.get(/api/recommend, { params: { user_id: userId } }); }template div classshop-list div v-forshop in shopList :keyshop.shop_id classshop-item span店铺ID{{ shop.shop_id }}/span span推荐分{{ shop.score.toFixed(3) }}/span /div /div /template script import { getRecommend } from ../api/recommend; export default { name: RecommendView, data() { return { userId: 1001, shopList: [] }; }, created() { this.fetchRecommend(); }, methods: { async fetchRecommend() { const res await getRecommend(this.userId); if (res.data.code 0) { this.shopList res.data.data; } } } }; /script这个流程里的坑大多在环境配置而不是代码本身。axios 的 baseURL 是绝对地址如果前端和后端不在一台机器要把 localhost 换成后端所在机器的局域网 IP否则远程打开页面时请求打不通。另一个高频问题是后端返回时间超过 timeout 阈值如果相似度矩阵没有做启动时加载而是每次请求现算前端经常报 timeout这时候先回头优化后端不要单纯调大超时时间。5.3 完整启动流程从后端到前端一条命令走起来本地运行建议先启动后端再启动前端。后端先把数据和模型加载进内存Flask 监听 5000 端口前端 npm run serve 起在 8080浏览器访问页面后切换 user_id 参数验证不同用户的推荐差异。# 终端 1进入后端目录启动推荐服务 cd taobao_shop_recommend/backend python app.py # 终端 2进入前端目录启动 Vue 开发服务 cd ../frontend npm install # 首次运行需要安装依赖 npm run serve启动过程中如果 npm install 报 node-sass 或 esbuild 编译错误多半是 Node.js 版本和项目依赖冲突用 nvm 切换到项目 package.json 指定的 Node 版本是常见解法。这个项目前端依赖不重正常网络环境下一两分钟能装完。如果你是在 vscode 里做 python 环境配置记得把解释器指向虚拟环境路径避免终端和编辑器用的 Python 不是同一个。6. 进阶验证用离线指标检查推荐质量推荐系统跑起来只是第一步推荐得准不准需要指标说话。最基础的验证方式是取一部分用户行为数据做测试集在训练集上计算相似度矩阵再对测试集里的每个用户生成 topN 推荐统计推荐列表中命中测试集真实交互商铺的比例这个指标叫 PrecisionN。还有一个偏信息检索的指标叫召回率配合 precision 一起看才能判断推荐是偏保守还是偏发散。我一般会在项目里临时写一个评估脚本把 Precision10 打印出来。淘宝商铺这种多选择场景precision10 在 5% 到 15% 之间都算正常如果低于这个区间首选检查评分矩阵的稀疏度和行为权重分配而不是急着换算法模型。# 简单可跑的 Precision10 评估脚本 def precision_at_k(train_matrix, test_ratings, item_sim, k10): hit 0 valid_users 0 for user_id, true_shops in test_ratings.items(): user_vec train_matrix.loc[user_id].values if user_vec.sum() 0: continue scores np.dot(user_vec, item_sim) rec_shops set(np.argsort(-scores)[:k]) if rec_shops set(true_shops): hit 1 valid_users 1 return hit / valid_users if valid_users 0 else 0.0这个函数的关键在于 train_matrix 和 test_ratings 的划分方式。划分时要保证测试集里的每个用户都在训练集里有行为记录否则 user_vec 是空向量评估结果全是零还以为算法写错了。另一个细节是 test_ratings 里的 true_shops 要去除训练集里已经出现过的商铺否则模型相当于在背答案precision 虚高到没参考意义。从那以后我每次拿到推荐系统项目都会先强制走一遍这个离线评估流程再去看页面效果。没有指标的调参全是玄学行为权重怎么设、相似度用余弦还是皮尔逊、topN 取多少这些参数靠肉眼根本判断不了对错。希望这份拆解能帮到你动手复现这个项目时少走点弯路。本文还有配套的精品资源点击获取