拓冰建站拓冰建站
首页 / 资讯中心 / 正文

农产品推荐系统实战:基于Python+Django的协同过滤实现与部署

简介这是一份基于Python和Django的农产品推荐系统完整源码主要面向学习Web全栈、爬虫开发与推荐算法的高校学生、毕业设计者及初级工程师。项目围绕农产品电商场景以“惠农网”为目标数据源使用Scrapy框架完成数据抓取再经清洗、规范化处理后存入MySQL及Hadoop分布式文件系统推荐部分采用协同过滤算法依据用户历史浏览和购买记录计算相似度并结合Vue与Element Plus搭建前端页面完整覆盖数据采集、预处理、算法推荐到用户管理的业务闭环。压缩包共22个文件含png界面截图、py爬虫脚本、scala与java辅助代码、jpg图片及md说明文档整体大小约5.05MB目录结构简洁便于按模块查阅。目前已有140人学习下载。通过源码不仅能快速搭建一套农产品推荐系统还可深入理解Hadoop、Spark等大数据组件在真实项目中的整合方式适合作为课程设计或毕设参考。1. 农产品推荐系统用 Python 和 Django 落地先解决「推荐什么」再谈算法农产品和标准工业品的推荐逻辑完全不同用户买手机看参数买土豆看产地和价格同一品类换个产地复购决策就变了。这套基于 Python 和 Django 的农产品推荐系统源码价值不在算法多炫而在于把用户行为、商品属性、推荐策略串成一条 Django 能跑的生产流水线。Django 负责数据建模、后台管理和请求分发推荐部分用协同过滤就能覆盖大多数场景数据量大了再替换成向量检索也留得住接口。适合正在做课设选型的学生也适合给生鲜电商 MVP 搭推荐位的开发——拿到任何同主题源码都可以按这套思路改自己的数据集。2. 数据模型设计用 Django ORM 建出推荐系统需要的三张表2.1 农产品 SKU 的特殊性决定了 item 的粒度先想清楚推荐什么再写代码。农产品没有品牌壁垒SKU 跟季节走一个「红富士苹果」的规格、价格、库存每个月都在变如果把每个 SKU 当成独立的 item评分矩阵立刻稀疏成筛子。常见的做法是把 item 粒度放在「品类 品种 产地」上例如「烟台红富士」和「洛川红富士」是两个可比较的实体不同包装规格只是在价格字段上做乘法。这套设计直接决定了后面相似度计算用的是商品属性向量而不是对描述文本做分词。以我搭过的生鲜类项目为例推荐位要能解释「为什么推这个」所以商品画像至少要留三个字段品类、产地、季节标签。这三个字段既参与相似度计算也能在前端渲染成筛选标签一套数据两处用。同类项目里旅游推荐系统推的是线路item 数量固定且属性稳定而农产品商品随时令变动所以模型里必须给「季节/上架状态」留字段否则换季时推荐列表里全是下架商品。2.2 模型代码三张表的最小可用版本下面这个模型集是这类源码里最常见的骨架。Rating 单独成表而不是在 Product 上挂一个平均分字段是因为推荐算法需要的是原始行为记录随时可以重算聚合值不需要为了展示去维护冗余列。from django.db import models from django.contrib.auth.models import User class Category(models.Model): name models.CharField(max_length50, uniqueTrue, verbose_name品类) class Product(models.Model): name models.CharField(max_length100, verbose_name商品名) category models.ForeignKey(Category, on_deletemodels.CASCADE, related_nameproducts) origin models.CharField(max_length50, verbose_name产地) price models.DecimalField(max_digits8, decimal_places2, verbose_name参考价) rating models.FloatField(default0.0, verbose_name综合评分) def __str__(self): return self.name class Rating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameratings) product models.ForeignKey(Product, on_deletemodels.CASCADE, related_nameratings) score models.PositiveSmallIntegerField(default5, verbose_name评分) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, product) indexes [ models.Index(fields[user, product]), ]几个参数值得解释。on_deletemodels.CASCADE表示用户或商品删除时评分级联删除避免孤儿数据如果电商系统走软删除这里要改成 PROTECT。unique_together保证同一用户对同一商品只有一条评分配合 Django 的update_or_create写入评分数据天然干净。related_name让user.ratings、product.ratings反向查询可用算法代码里少写一串 filter。最后那个联合索引很重要协同过滤循环里反复按user_id取product_id没有这个索引数据量到几千条以后查询时间会肉眼可见地涨。2.3 先造数据用 admin 和 fixture 把开发环境跑起来模型建好先别急着写算法用 Django Admin 灌一批假数据把链路打通。在admin.py里注册from django.contrib import admin from .models import Category, Product, Rating admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display (id, name) admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display (id, name, category, origin, price, rating) list_filter (category, origin) search_fields (name, origin) admin.register(Rating) class RatingAdmin(admin.ModelAdmin): list_display (user, product, score, created_at)python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver访问/admin录入 5 个用户、20 个商品、每用户 810 条评分即可。数据太少时相似度全是零是正常的后面章节会给冷启动兜底策略。如果不想手点也可以用 Django fixture先python manage.py dumpdata shop --indent 2 shop/fixtures/dev.json换环境后python manage.py loaddata dev.json一键恢复多人协作时这套流程比互相传数据库文件省事得多。2.4 三种推荐策略的选型对照不同数据规模对应不同实现这个表可以作为选型依据方案输入数据冷启动表现适合的规模实现成本基于用户的协同过滤评分/收藏行为差新用户无邻居千级用户以下低基于物品的协同过滤行为 商品画像较好新商品可用属性兜底商品数少、用户多中内容相似度推荐商品属性好任意低但效果糙农产品系统商品数量通常远小于用户数所以实践中我更推荐基于物品的协同过滤商品相似度矩阵可以定时预计算线上只做查表。下一章先讲实现起来最快、也最容易理解错的用户协同过滤再讲物品方案的矩阵构建思路。3. 协同过滤推荐算法实现Python 写的相似度计算要注意的边界3.1 先把 ORM 数据拉成字典矩阵推荐算法不关心 Django 对象关心的是一个{user_id: {product_id: score}}的嵌套结构。常见做法是python manage.py startapp recommender建一个独立 app把算法代码和视图解耦。从 ORM 直接拉模型对象列表会触发 N1 查询正确做法是用values_list只取需要的列from collections import defaultdict from .models import Rating def build_user_item_matrix(): rows Rating.objects.values_list(user_id, product_id, score) matrix defaultdict(dict) for uid, pid, score in rows: matrix[uid][pid] float(score) return matrixvalues_list返回的是轻量元组而不是模型对象内存占用小一个量级。农产品项目的评分表上万行完全没问题到了百万级再换iterator()分块取或者直接读 CSV 都行只要这个函数返回结构不变后面算法层就不用动。3.2 余弦相似度注意零向量和共同评分基于用户的协同过滤核心是算两个用户评分向量的相似度再用相似用户对目标用户没买过的商品做加权评分。余弦相似度的实现看起来短坑都在边界import math def cosine_similarity(vec_a, vec_b): common set(vec_a.keys()) set(vec_b.keys()) if not common: return 0.0 dot sum(vec_a[p] * vec_b[p] for p in common) norm_a math.sqrt(sum(v * v for v in vec_a.values())) norm_b math.sqrt(sum(v * v for v in vec_b.values())) if norm_a 0 or norm_b 0: return 0.0 return dot / (norm_a * norm_b)三个必看的点。第一只取common里的商品参与点积但分母用的是各自全量向量的模不这么写就会出现两个人都给同一商品打了 5 分、却因为其中一人评分多导致相似度被稀释。第二norm_a或norm_b为零时直接返回 0否则除零异常会让整个推荐接口 500。第三评分为 0 的隐式反馈浏览、点击不要混进这个函数隐式反馈只有 0/1 两种值更适合用 Jaccard 或共现次数行为类型数据形式相似度算法是否需要中心化评分1~5 连续值余弦相似度需要浏览/点击0/1 布尔Jaccard / 共现不需要提示要区分显式评分和隐式反馈两者可以加权合并但不要在同一个函数里混算。3.3 Top-N 推荐加权聚合与三个常见误用有了相似度推荐就是两遍循环找邻居聚合邻居的评分。def recommend_for_user(target_user_id, top_n6): matrix build_user_item_matrix() target_vec matrix.get(target_user_id) if not target_vec: return popular_products(top_n) scores defaultdict(float) for uid, vec in matrix.items(): if uid target_user_id: continue sim cosine_similarity(target_vec, vec) if sim 0.0: continue for pid, score in vec.items(): if pid in target_vec: continue scores[pid] sim * score ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return [pid for pid, _ in ranked[:top_n]]这段代码的效率问题在数据量上去后会暴露两层循环的复杂度是 O(用户数 × 平均评分商品数)。先加一个简单门槛sim 0直接跳过避免把负相关用户的商品也聚合进来。第二个常见误用是分数没有中心化购物多、打高分多的用户会压倒性占优简单修正办法是把聚合值改成sim * (score - user_mean)让评分围绕零上下波动再做加权。第三个坑在返回阶段top_n不要超过商品总数的三分之一农产品类目下用户真正会买的品类就那几个列表塞太多反而让点击率下降后面第 5 章会给出具体调法。3.4 冷启动兜底新用户返回热销榜冷启动在农产品场景里特别常见新注册用户没有任何评分行为。两层兜底第一层用户没有行为数据时返回热销榜第二层商品没有评分时用品类均值填充。代码里已经有一个popular_products入口def popular_products(top_n6): from .models import Product return list( Product.objects .select_related(category) .order_by(-rating, -id) .values_list(id, flatTrue)[:top_n] )排序键用-rating再叠加-id保证评分相同时新上架商品有更高优先级。这个顺序在生鲜场景里符合「上新即送曝光」的运营习惯而且热销榜本身也是评估协同过滤效果的基准线。4. Django 视图、模板与缓存把推荐结果送到模板的完整链路4.1 推荐视图套缓存而不是裸算算法算完只是第一步线上响应速度才是能用的关键。视图里不直接调用上一章的函数而是套一层缓存from django.shortcuts import render from django.core.cache import cache from .recommender import recommend_for_user from .models import Product def recommendation_view(request, user_id): cache_key frec:{user_id}:6 rec_ids cache.get(cache_key) if rec_ids is None: rec_ids recommend_for_user(user_id, top_n6) cache.set(cache_key, rec_ids, 60 * 30) products Product.objects.filter(id__inrec_ids).select_related(category) return render(request, shop/recommend.html, {products: products})缓存键里带用户 ID 和 top_n是为了让不同推荐位首页 banner、详情页关联、购物车底部的缓存互不干扰。TTL 设 30 分钟是因为农产品推荐对时效不敏感半小时内的结果不会造成体验差异。select_related(category)解决模板里p.category.name的 N1 查询这类优化在 Django 里属于必做项。如果做前后端分离把render换成JsonResponse返回rec_ids对应的商品字典即可算法层不用改。4.2 信号机制评分变化后自动失效缓存上面缓存有个隐患用户刚给商品打了 5 分刷新推荐列表拿到的还是旧数据。标准解法是用 Django 信号from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.core.cache import cache from .models import Rating receiver([post_save, post_delete], senderRating) def invalidate_recommendation_cache(sender, instance, **kwargs): cache.delete(frec:{instance.user_id}:6)post_delete也要监听否则删评分后缓存还是旧的。这里的失效粒度是用户级精确度已经够如果评分行为很频繁可以把失效粒度扩大到「该用户所有推荐位」但没必要为省一个缓存键把代码搞复杂。缓存参数可以按下面的值去设配置项建议值原因key 前缀rec:{user_id}:{top_n}推荐位之间不互相覆盖TTL1800 秒半小时内结果可接受失效粒度用户级实现简单误伤可控4.3 模板渲染推荐位的信息密度推荐位模板要展示的不是评分数字而是用户做决策需要的字段产地、价格、品类。信息密度太低推荐算法再准也会被运营否决。div classrec-grid {% for p in products %} div classrec-card h3{{ p.name }}/h3 p classmeta{{ p.category.name }} · {{ p.origin }}/p p classprice¥{{ p.price }}/p a href{% url product_detail p.id %}查看/a /div {% empty %} p暂无推荐去热门商品逛逛/p {% endfor %} /div{% empty %}分支不能省。农产品推荐系统最怕后端返回空列表时前端渲染出一片空白用空分支给运营文案比抛异常体面也不会让用户误以为页面坏了。另外注意模板里不要出现p.rating综合评分只参与排序不参与用户决策展示。4.4 推荐接口的访问控制推荐接口如果被测试脚本或爬虫高频请求协同过滤的计算压力会直接打到数据库。常见做法是给视图加一个轻量计数用cache.add(key, 1, 60)做每分钟请求计数超过阈值直接返回 429。这个中间件逻辑简单两小时就能写完却能把开发环境从偶发卡死里救出来属于这类系统里性价比最高的防护。5. 推荐算法的离线评估与 Django 部署验收5.1 用留出法评估推荐质量推荐系统上线前必须有一个可量化的验收动作。最省事的是留出法把每个用户的评分按时间排序最后一条当测试集其余当训练集然后算 PrecisionK。def precision_at_k(test_items, rec_ids, k6): rec set(rec_ids[:k]) return len(rec set(test_items)) / k这个指标的合理基线不是 100%而是热销榜的命中率。如果协同过滤命中率连热销榜都不如说明评分数据噪声太大先回头检查数据质量而不是继续调参数。5.2 三个必调参数评分中心化把聚合值从sim * score改成sim * (score - user_mean)消除爱打高分用户的系统性偏差通常能让 PrecisionK 提升 25 个百分点。相似度阈值sim小于 0.1 的邻居直接丢弃减少噪声聚合代价是覆盖率略降。top_n生鲜场景 610 个比较合适超过 15 个后点击率会明显下降因为用户没有耐心翻第二屏。5.3 部署到 Linux 的验收清单python manage.py check --deploy python manage.py migrate python manage.py collectstatic --noinput gunicorn mysite.wsgi:application -w 4 -b 127.0.0.1:8000check --deploy会输出一串生产环境警告逐一处理后上线。用宝塔面板的话在站点设置里把运行模式切到 Python进程填gunicorn mysite.wsgi即可。最后一条验证用两个评分完全不同的账号打开推荐页确认返回的商品列表不同给商品重新打分后列表在最长 30 分钟窗口内会翻新。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门