资讯动态

Python+Django双算法商品推荐系统全链路实现与解析

发布时间:2026/10/19 10:58:41 来源:尧图企业网站定制
最近陪一个朋友把他毕业设计的核心代码完整过了一遍题目叫“Python 基于双算法的商品推荐系统”。名字看着长拆开其实就是四件事用 Django 把电商购物和管理后台跑起来用协同过滤这类双算法给用户做商品推荐把运营数据用图表可视化呈现再接入一个大模型 Agent 当智能导购。整条链路里算法、工程、交互都有答辩时也能讲清楚“我到底做了什么”所以我觉得它特别适合推荐系统方向的毕设选题也是一个能直接写进简历的完整项目。如果你正卡在选题阶段或者系统已经跑通了但不知道往哪个方向加深度这篇内容值得你花十分钟慢慢看。我会把设计思路、算法实现、后端关键代码、可视化方案和大模型接入细节全部拆开讲包括那些常规文档里不会写的坑。1. 项目整体设计与思路拆解1.1 这个系统到底在解决什么问题很多电商类毕设做着做着就变成了“增删改查展示页”管理员录入商品用户下单完事。这个项目不一样的地方在于它把三类真实场景里的需求捏在了一起第一是“用户找不到想买的东西”。商品一多用户面对几百上千个 SKU 根本无从下手靠搜索只能解决已知需求解决不了潜在需求。推荐系统的存在意义就是让用户一进首页就看到“我可能喜欢”的商品缩短决策路径。第二是“运营方看不清数据”。商品卖得好不好、哪个分类贡献最大、用户活跃趋势如何如果全靠手工查数据库效率太低。所以系统里必须有可视化看板让管理员一进后台就能看到核心指标的变化。第三是“传统推荐缺乏交互感”。协同过滤只能给出一排“你可能喜欢”但用户如果说“我想找一款适合跑步戴的无线耳机预算五百以内”传统算法就有点懵。这就引出了大模型 Agent 的定位它负责把用户的口语化需求翻译成结构化检索条件再调用推荐和搜索工具返回结果。说白了系统分三层数据层管商品、用户、评分和行为记录算法层跑双算法推荐应用层做商城管理、可视化看板和大模型导购。三层各司其职也正好对应了毕设答辩时老师最爱问的“你的系统架构是怎样的”这个问题。1.2 为什么技术栈是 Python Django选型这事我在帮朋友梳理的时候反复跟他确认过最后得出的结论是Python Django 在毕设这个场景下几乎是性价比最高的组合原因有三。第一算法生态完整。协同过滤说白了就是算相似度、做矩阵运算pandas 和 NumPy 处理十万级评分数据也就是几秒钟的事scikit-learn 里甚至连余弦相似度都给你封装好了。用 Java 写推荐算法不是不行但同样的功能Python 表达起来代码量少一半不止。第二Django 自带的东西刚好覆盖一个管理系统的大部分需求。Admin 后台开箱即用ORM 帮你省掉写原生 SQL 的重复劳动用户认证、session、表单校验这些都是现成的。毕设阶段你不需要去琢磨 Spring Security 那套复杂配置Django 的 auth 模块已经够用。第三前后端衔接简单。这个项目没有强行上前后端分离而是采用“Django 模板 API 接口”的混合模式。管理后台和可视化页面用模板渲染推荐数据和大模型对话用 JSON 接口返回。对毕设来说这样既省去了跨域和部署的麻烦又能顺便讲清楚“我既会模板渲染也写过接口”。1.3 “双算法”到底指哪两个算法这里的“双算法”最经典的组合是基于用户的协同过滤UserCF和基于物品的协同过滤ItemCF。为什么选这两个而不是上来就搞深度学习我当时的判断是毕设项目讲求的是“原理能讲透、效果能解释、代码能跑通”。UserCF 的逻辑是“和你喜好相似的人喜欢什么就推荐给你”适合用户量相对小、物品更新快的场景ItemCF 的逻辑是“你买过的商品的相似商品也推荐给你”适合物品相对稳定、用户兴趣变化快的电商场景。这两个算法互补性很强一个从“人”的角度找相似一个从“物”的角度找关联合在一起正好覆盖用户冷启动之外的大部分情况。还要加一个兜底逻辑当用户没有任何行为数据时UserCF 和 ItemCF 都会失效。这时候系统会退回到“热门商品推荐”按销量和评分热度给新用户推东西。这个三级降级链路双算法 → 热门榜在答辩时是非常好的加分项因为老师一听就知道你考虑过冷启动问题。2. 核心算法拆解与实现2.1 协同过滤的底层直觉协同过滤说穿了就是“物以类聚人以群分”。它的核心是计算相似度最常用的度量方式是余弦相似度。公式不复杂两个向量夹角的余弦值越接近 1说明两个向量越相似。举个例子用户 A 对商品打分是[5, 3, 0, 4]用户 B 对同样四个商品打分是[5, 3, 2, 4]这两个向量趋势基本相同余弦相似度就会很高系统因此判定 A 和 B 兴趣相近。理解了这个点后面看代码就不会发怵。实际项目里我不会直接用 scikit-learn 的cosine_similarity一把梭因为当数据量大到几万用户 × 几万用户时全量矩阵的相似度计算会占用非常大的内存。后面第 5 节我会专门讲这个坑。2.2 基于用户的协同过滤UserCF实现UserCF 的流程分四步构建“用户-商品”评分矩阵计算用户之间的相似度找到和目标用户最相似的 K 个用户把这 K 个用户喜欢而目标用户没见过的商品按分数汇总推荐。核心代码长这样import numpy as np import pandas as pd from collections import defaultdict def build_user_item_matrix(ratings_df): # 生成 用户ID × 商品ID 的评分矩阵缺失值填 0 matrix ratings_df.pivot_table(indexuser_id, columnsproduct_id, valuesrating) return matrix.fillna(0) def user_similarity(matrix): # 按行归一化后计算余弦相似度 norm np.sqrt((matrix ** 2).sum(axis1)) matrix_norm matrix.div(norm, axis0) return matrix_norm matrix_norm.T def user_cf_recommend(user_id, matrix, sim_matrix, K10, top_n20): # 取和目标用户最相似的 K 个用户 sim_scores sim_matrix[user_id].sort_values(ascendingFalse) sim_scores sim_scores.drop(indexuser_id).head(K) # 收集相似用户评分过的商品按相似度加权汇总 score_dict defaultdict(float) for similar_user, sim in sim_scores.items(): rated_items matrix.loc[similar_user] rated_items rated_items[rated_items 0] for product_id, rating in rated_items.items(): score_dict[product_id] sim * rating # 过滤掉目标用户已经买过的商品 purchased set(matrix.columns[matrix.loc[user_id] 0]) candidates [(pid, s) for pid, s in score_dict.items() if pid not in purchased] candidates.sort(keylambda x: x[1], reverseTrue) return [pid for pid, _ in candidates[:top_n]]这里有个细节容易被忽略相似用户里可能存在“只给一个商品打了高分”的极端用户这种人算出来的相似度虚高会把推荐带偏。所以我在实际代码里加了一个过滤条件——只保留至少对 3 个商品有过行为的用户参与相似度计算。这个细节虽然小但对推荐质量的影响很明显。2.3 基于物品的协同过滤ItemCF实现ItemCF 的流程是反过来走的先统计“喜欢物品 X 的用户还喜欢哪些物品”算出物品之间的相似度再根据用户历史行为里的商品找到这批商品的相似商品做推荐。电商场景下 ItemCF 比 UserCF 表现通常更好因为用户兴趣相对稳定而且 ItemCF 的相似度矩阵可以离线计算、在线直接查表实时性压力小。实现代码如下def item_similarity(matrix): # 矩阵转置后列与列之间的余弦相似度就是物品相似度 transposed matrix.T norm np.sqrt((transposed ** 2).sum(axis1)) transposed_norm transposed.div(norm, axis0) return transposed_norm transposed_norm.T def item_cf_recommend(user_id, matrix, item_sim_matrix, top_n20): # 找到用户互动过的商品 interacted matrix.loc[user_id] interacted_items interacted[interacted 0] score_dict defaultdict(float) for product_id, rating in interacted_items.items(): # 取该商品的相似商品 similar_items item_sim_matrix[product_id].sort_values(ascendingFalse).head(10) for similar_id, sim in similar_items.items(): if similar_id product_id: continue score_dict[similar_id] sim * rating purchased set(interacted_items.index) candidates [(pid, s) for pid, s in score_dict.items() if pid not in purchased] candidates.sort(keylambda x: x[1], reverseTrue) return [pid for pid, _ in candidates[:top_n]]ItemCF 有个比 UserCF 好的特性相似度矩阵的规模取决于商品数而不是用户数电商平台商品数通常远小于用户数所以矩阵计算和存储压力都小很多。这也是为什么我在项目里推荐把 ItemCF 作为实时推荐的主通道UserCF 作为补充通道。2.4 双算法融合与效果评估两个算法单独跑完怎么合我采用的是最简单也最好解释的加权融合最终得分 α × UserCF 得分 (1 - α) × ItemCF 得分。α 取 0.5 时是五五开也可以根据用户行为丰富度动态调整——行为数据越多的老用户UserCF 权重可以适当加大新用户则更依赖 ItemCF 和热门榜。def hybrid_recommend(user_id, matrix, user_sim, item_sim, alpha0.5, top_n20): user_cf_scores user_cf_recommend(user_id, matrix, user_sim) item_cf_scores item_cf_recommend(user_id, matrix, item_sim) final_score defaultdict(float) for pid, score in user_cf_scores.items(): final_score[pid] alpha * score for pid, score in item_cf_scores.items(): final_score[pid] (1 - alpha) * score # 双重过滤已购买商品避免重复推荐 purchased set(matrix.columns[matrix.loc[user_id] 0]) candidates [(pid, s) for pid, s in final_score.items() if pid not in purchased] candidates.sort(keylambda x: x[1], reverseTrue) return [pid for pid, _ in candidates[:top_n]]融合之后务必做评估不然答辩时老师问“你怎么证明你的推荐是有效的”你没法回答。我建议把评分数据集按 8:2 拆成训练集和测试集用训练集生成推荐列表到测试集里检查用户是否真的购买了推荐的商品然后计算三个指标准确率推荐命中数 / 推荐总数、召回率推荐命中数 / 测试集购买数、F1 值两者的调和平均。实际操作中我会把不同的 α 值从 0.1 到 0.9 各跑一遍画一条小曲线告诉老师“我选 0.5 是因为 F1 在这条曲线上最高”。这个实验过程拿到答辩现场说服力比单纯贴代码强得多。3. Django 后端与购物管理系统的落地3.1 数据表设计推荐算法再好数据模型设计不合理也白搭。这个系统的核心表我设计了五张用户表继承 Django 自带 AbstractUser、商品分类表、商品表、评分/行为表和订单表。from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): nickname models.CharField(max_length32, blankTrue) avatar models.URLField(blankTrue) class Category(models.Model): name models.CharField(max_length32, uniqueTrue) parent models.ForeignKey(self, nullTrue, blankTrue, on_deletemodels.CASCADE) class Product(models.Model): name models.CharField(max_length128) category models.ForeignKey(Category, on_deletemodels.CASCADE, related_nameproducts) price models.DecimalField(max_digits10, decimal_places2) stock models.IntegerField(default0) sales models.IntegerField(default0) cover_url models.URLField(blankTrue) description models.TextField(blankTrue) created_at models.DateTimeField(auto_now_addTrue) class Rating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) product models.ForeignKey(Product, on_deletemodels.CASCADE) score models.FloatField() created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, product) class Order(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameorders) total_amount models.DecimalField(max_digits10, decimal_places2) status models.CharField(max_length16, defaultpending) created_at models.DateTimeField(auto_now_addTrue)这里有一个我特别想提醒的点Rating表必须加unique_together否则一个用户可以对同一商品重复评分后续算相似度矩阵时pivot_table会直接报错或者出现重复索引。第一次跑数据的时候我就是没注意这个约束结果相似度矩阵算出来全乱了排查了半天才发现是数据源有重复记录。3.2 推荐接口与缓存推荐不是每次请求都现算那样响应时间扛不住。我的做法是离线定时把相似度矩阵算好存成文件或者写回缓存表在线接口只做“查表 加权 排序”。Django 视图里我封装了一个统一的推荐入口from django.core.cache import cache from django.http import JsonResponse from .services.recommender import hybrid_recommend, load_matrices def recommend_api(request): user_id request.GET.get(user_id) if not user_id: return JsonResponse({code: 400, msg: 缺少 user_id}) # 缓存 key 带上用户 ID推荐结果缓存 30 分钟 cache_key frecommend:{user_id} product_ids cache.get(cache_key) if product_ids is None: matrix, user_sim, item_sim load_matrices() product_ids hybrid_recommend(user_id, matrix, user_sim, item_sim) cache.set(cache_key, product_ids, timeout1800) products Product.objects.filter(id__inproduct_ids) data [{id: p.id, name: p.name, price: str(p.price)} for p in products] return JsonResponse({code: 200, data: data})加缓存的原因很直接我在本机测过加载两万条评分数据算一次综合推荐大概要两秒多但用户不会接受每次刷新首页都等两秒。加了 30 分钟缓存之后接口平均响应时间降到了 50 毫秒以内这个数据在答辩时也很有说服力说明你确实做过性能优化。3.3 购物流程与后台管理购物管理部分是标准的电商闭环商品列表、商品详情、购物车、下单、订单列表、后台管理。Django 的 Admin 在这里起了大作用商品、分类、订单都可以直接进 Admin 管理省了大量写后台页面时间。订单模块要注意一个并发问题用户同时下单时库存扣减容易出现超卖。毕设阶段当然不用上 Redis 分布式锁但至少要在save()的时候做一次条件更新from django.db.models import F updated Product.objects.filter(idproduct_id, stock__gtequantity).update(stockF(stock) - quantity) if updated 0: return {code: 400, msg: 库存不足}这段代码的意思是只有库存大于等于购买数量时才执行扣减update返回 0 就说明扣减失败。用F表达式能保证“读到的库存”和“写回的库存”之间不产生竞态条件比“先查再改”安全得多。这个细节虽然小但属于真实项目中一定会遇到的并发问题写进答辩稿里是加分项。4. 可视化看板与大模型 Agent 导购4.1 可视化看板怎么搭可视化的核心价值在于把数据库里的数字变成一眼能看懂的趋势和分布。我的方案是 Django 后端聚合数据、输出 JSON前端用 ECharts 渲染图表。没有引入重型前端框架因为毕设阶段完全不需要。具体做了四类图表折线图展示近 30 天订单量和 GMV 趋势饼图展示商品分类销售占比柱状图展示销量 Top10 商品还有一个雷达图展示不同用户群体的购买偏好对比。后端聚合代码大约长这样from django.db.models import Sum, Count from django.utils import timezone from datetime import timedelta def sales_trend_data(request): end_date timezone.now() start_date end_date - timedelta(days30) orders (Order.objects .filter(created_at__range[start_date, end_date], statuspaid) .values(created_at__date) .annotate(totalSum(total_amount), countCount(id)) .order_by(created_at__date)) dates [item[created_at__date].strftime(%m-%d) for item in orders] totals [float(item[total]) for item in orders] return JsonResponse({dates: dates, totals: totals})前端接入 ECharts 其实就三步引入脚本、初始化图表实例、把 JSON 数据塞进option。我建议在option里把tooltip和dataZoom都配上因为这两个组件能让图表交互感提升一个档次老师演示时鼠标一滑就能看到每日具体数值观感很好。有一个我踩过的坑图表的中文标签偶尔会显示成方框乱码原因是页面没有指定 UTF-8 字符集或者 ECharts 字体加载失败。解决办法是在模板head里显式加meta charsetutf-8顺便给option.textStyle设置fontFamily: Microsoft YaHei。4.2 大模型 Agent 导购怎么接入这块是这个项目最大的亮点也是朋友当时最没底的部分总觉得“接大模型是不是很玄”。实际上拆开看就是一个“意图识别 工具调用”的循环。我给 Agent 设计了三个工具search_products按商品名和分类搜索recommend_for_user调用前面写的双算法推荐接口get_product_detail取指定商品详情。大模型的作用则是把用户口语化的一句话自动转成结构化调用参数。tools [ { name: search_products, description: 根据关键词搜索商品, parameters: { type: object, properties: { keyword: {type: string, description: 商品关键词}, category: {type: string, description: 商品分类} }, required: [keyword] } }, { name: recommend_for_user, description: 为指定用户生成个性化商品推荐, parameters: { type: object, properties: { user_id: {type: integer, description: 用户ID}, top_n: {type: integer, description: 返回数量} }, required: [user_id] } } ]用户输入“帮我找一款适合跑步的无线耳机预算五百以内”Agent 拿到这句话后由大模型判断应该调用search_products参数为{keyword: 无线耳机, category: 运动}然后系统把搜索结果返回给大模型大模型再组织成自然语言回复给用户。这整个过程里大模型没做任何计算只做“翻译”和“组织语言”真正干活的是后端的搜索和推荐函数。这种架构在技术上叫工具调用理解成“大模型是个会打电话的客服但真正办事的是电话那头的人”就行。提示词工程上有一个关键点必须在系统提示词里明确告诉大模型“如果用户没有提供预算不要自己编造直接追问”。不然大模型为了讨好用户可能真的编一个“这款耳机只要 499”出来而数据库里根本没有这个价格。我还加了降级逻辑大模型接口调用超时或报错时直接跳到规则匹配的普通搜索保证对话功能永远可用。5. 实操踩坑与问题排查实录5.1 相似度矩阵内存爆炸现象数据量上到五万用户、一万商品之后user_similarity一跑直接内存红盘笔记本风扇狂转。原因五万用户的全量相似度矩阵是 5 万 × 5 万每个浮点数占 8 字节算下来就有 20 GB本机内存根本扛不住。解决第一层优化是只保留每个用户的 Top K 相似用户用稀疏结构代替稠密矩阵第二层优化是用物品侧的 ItemCF 作为主力把用户相似度矩阵的规模控制在“活跃用户”子集范围内第三层是离线计算、定时更新不在请求链路上跑全量相似度。这几层优化做完内存占用能降两个数量级。5.2 冷启动问题现象新注册用户没有任何评分和购买记录UserCF 和 ItemCF 都推不出东西。解决前端在用户第一次进入首页时直接展示“热门商品榜”按销量和平均评分倒序排。同时在用户注册时增加一个“兴趣标签选择”步骤用户选完标签后系统先用标签匹配商品分类做粗粒度推荐等用户产生行为数据后再切换成双算法推荐。这个“注册问卷 热门兜底”的组合能覆盖绝大多数冷启动场景。5.3 推荐结果同质化现象推荐列表里 20 个商品全是同一个分类比如全是耳机。原因ItemCF 的相似商品中同类商品天然相似度高如果不加多样性控制列表很容易被同一分类霸占。解决在融合排序之后加一个“分类打散”步骤——每取一个商品检查同类商品是否已超过列表总数的 20%如果超过就跳过优先从其他分类里取。代价是准确率指标可能会轻微下降但用户实际体验会好很多。在推荐系统里多样性本来就是和准确率同等重要的指标这个权衡可以在答辩时主动讲。5.4 大模型 Agent 响应不稳定现象同样的问题有时候回复很规范有时候答非所问。原因大模型生成有随机性温度参数设太高导致输出不稳定。解决调用时把temperature降到 0 到 0.2 之间同时在提示词里给出一个“回答模板”要求模型按“发现需求 - 调用工具 - 总结结果”的结构输出最后在代码里对模型输出做了一次 JSON 解析校验解析失败就返回兜底文案“换个说法试试”。经过这三层处理之后整个导购对话的稳定性明显提升。5.5 常见问题速查表现象可能原因解决办法相似度矩阵算出来全是 NaN评分矩阵里有用户或商品全零行过滤掉行为数少于 3 的用户/商品推荐接口响应慢每次请求都在线计算离线预计算 Redis/本地缓存首页图表不显示后端 JSON 字段名和 ECharts 对不上打印接口返回数据逐一核对字段下单后库存变负数扣减库存没加条件用F表达式加stock__gte条件更新大模型导购老是乱编价格提示词里没限制写明“未知信息必须追问不得虚构”Admin 后台中文乱码数据库字符集不是 utf8mb4建库时指定 utf8mb4连接加charset参数6. 最后分享一点我的体会帮朋友调试这个项目的过程中我最大的感受是毕设项目不需要追求算法多前沿但一定要把链路做完整。从数据采集、算法训练、系统集成到可视化展示每一环都有能拿得出手的东西答辩时才能讲得理直气壮。特别是双算法融合和大模型 Agent 这两个点一个展示了你的算法功底一个展示了你的工程视野两者结合足以让这个项目在一堆“xx管理系统”里显得不一样。最后再提醒一句所有离线计算和缓存相关的代码记得写清注释因为答辩现场老师最喜欢翻你看似“不起眼”的工具文件。这个项目后续还能继续扩展成基于深度排序模型的推荐链路或者给 Agent 加上多轮记忆能力如果你有兴趣完全可以在它的骨架上继续往下长。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑