首页 / 资讯中心 / 文章详情

Python农产品智能推荐系统实战:从数据采集到算法融合

Python农产品智能推荐系统实战:从数据采集到算法融合 ★ FEATURED ARTICLE
1. 项目概述与需求分析1.1 为什么给陕西农产品做一个推介系统做这个项目的起因其实挺直接的。我自己一直在跑一些农产品电商相关的数据采集和分析工作陕西的特色农产品种类非常多苹果、猕猴桃、茶叶、红枣、核桃、柿饼这些单品在省内不同区域都有自己的产地优势。但电商平台上这些产品的展示方式非常同质化用户搜索“陕西苹果”得到的结果和搜索“红富士苹果”几乎没有差别消费者很难感知到洛川苹果和旬邑苹果在口感、成熟期、地理标志上的差异。这背后其实是一个信息匹配效率的问题——产地信息、品质特征、价格区间、上市窗口这些关键数据是存在的但没有一个系统能把这些结构化数据串起来给消费者一个清晰的推荐结果。所以我想做的本质上是一个基于Python构建的农产品智能推介系统把陕西特色农产品的产地信息、品质属性、季节性、价格波动、用户偏好这几个维度全部拉通用推荐算法的思路去做商品匹配。和传统电商的“销量排序”不同这个系统的核心逻辑是“场景匹配”——用户告诉系统自己想要的品质偏好、预算范围、购买用途系统从农产品的品种特征和产地优势出发推荐最合适的产品。比如你说想买送礼用的猕猴桃系统会优先推荐眉县徐香猕猴桃的礼盒装因为它的果形均匀、耐储存你自己吃则是周至翠香更合适因为甜度高、熟得快、性价比更好。这个项目的受众大致分三类。第一类是农产品电商从业者可以用这套系统做选品分析和用户需求洞察第二类是做Python数据分析和推荐系统的开发者可以从项目里拆出爬虫采集、特征工程、推荐算法这些可复用的模块第三类是地方政府或农业合作社的技术人员可以用这个原型系统去展示本地农产品品牌。我自己写这个项目的最初版本大概用了两周业余时间核心工作量其实不在算法而在数据清洗和特征标准化这也是我整篇博文要重点讲的部分。1.2 项目整体功能拆解我先把这个系统的功能边界画清楚这样后面讲技术方案时不容易偏题。系统一共拆成四个核心模块数据采集层用爬虫从公开的农产品电商页面、地方农业信息网站抓取陕西各区县农产品的价格、产地、品种、评分等基础数据同时人工补充一部分地理标志产品的认证信息。数据清洗与特征工程层把抓取到的非结构化文本统一成结构化字段处理缺失值、单位归一化、产地标准化并构造用户偏好特征、产品品质特征和季节因子。推荐引擎层基于内容过滤计算产品之间的相似度同时结合用户历史行为做协同过滤两层结果按加权策略融合输出最终推荐列表。可视化展示层用Flask搭建Web界面配合ECharts展示价格趋势、产地分布、推荐结果等图表团队成员可以直接通过浏览器访问。这四个模块的组合逻辑是数据层支撑特征层特征层支撑推荐层推荐层的结果通过可视化层呈现。整个系统的技术选型全部基于Python原因很简单——爬虫用Requests加BeautifulSoup数据处理用Pandas算法用NumPy和scikit-learnWeb展示用Flask所有环节用一个语言贯通不需要在多个技术栈之间来回切换调试和迭代的成本低很多。2. 数据采集与清洗的完整拆解2.1 爬虫策略与反爬应对陕西农产品的数据要抓全其实不容易。主流电商平台上陕西农产品的搜索结果是分散的单品链接分布在多个店铺下货源信息不统一有的标“洛川苹果”有的只标“陕西红富士”产地字段经常缺失。我用了两套采集方案并行处理。第一套方案是关键词定向采集。通过构造“陕西苹果”“眉县猕猴桃”“临潼石榴”“大荔冬枣”这类带地名的长尾关键词逐个搜索并按单品页URL去重能覆盖大部分标品链接。这一层抓的是商品ID、标题、价格、月销量、店铺名和评价数。第二套方案是溯源信息采集。地方农业信息网站和政府公开的地理标志产品名录里有完整的产地证明、种植面积、上市周期这些信息我用遍历栏目页的方式抓静态HTML解析出表格数据。这套方案的数据质量高但是更新频率低所以我会把它作为“基础档案”表与电商实时数据做关联。反爬层面的处理相对常规但有两件事值得说一下。第一请求频率一定要控制住单线程每秒最多一到两个请求加上指数退避的重试逻辑爬一周基本不会被封第二一定要做User-Agent轮换和Referer伪造很多农业网站的防护比较老单一UA很容易触发拦截。我更建议的做法是把采集逻辑封装成独立的调度类设置请求间隔、失败重试次数、最大重试上限这三个参数这样维护起来省心。注意采集任何公开数据都要遵守目标网站的robots协议和服务条款本项目仅用于技术研究和内部演示不涉及商业用途数据量也控制在合理范围内。2.2 数据清洗的几个关键细节爬回来的数据用“脏”来形容一点不过分。我遇到的典型问题包括价格字段混入了“包邮”“券后”等文字、单位不一致有的按斤标、有的按箱标、有的按500g标、产地字段填的是“陕西西安”但具体区县缺失、同一品种在不同店铺的名称差异很大“徐香”有时写成“徐香猕猴桃”或“翠香徐香”。清洗流程我分了三步。第一步是字段抽取用正则表达式把价格数字、单位、销量数字提取出来价格统一换算成“每500g”的标准单位方便后续算法比较第二步是缺失值处理产地缺失的优先从标题中匹配区县词表标题也没有就用店铺所在地做兜底实在无法确认的记录直接剔除第三步是品种归一化建立一份陕西特色农产品别名映射表例如“洛川红富士”“高原红苹果”“延安苹果”统一映射到“苹果-红富士”这个品种ID这个步骤直接影响后续相似度计算的效果。这里要强调一个经验品种归一化是整个项目中性价比最高的一步。一开始我图省事直接用原始品种字段做匹配结果推荐结果非常碎片化同一个用户可以同时看到“苹果-红富士”和“红富士苹果”两个相似却无法合并的推荐项点击分布很散。把别名表建好之后推荐列表的聚合度明显提升用户体验是质的改变。2.3 特征工程让农产品变成算法能理解的向量推荐算法不能直接处理“洛川苹果”这样的文本必须把商品转换成数值向量。我构造的特征分成三类。第一类是价格品质特征包括每500g价格、价格区间、店铺评分、月销量对数变换后的数值。月销量做了log1p变换因为销量分布是典型的长尾头部爆品销量过万长尾商品只有个位数不做变换的话头部商品会在相似度计算里压倒一切。第二类是品种属性特征用One-Hot编码处理品种类型、产地、认证状态是否地理标志产品、产品形态鲜果/干果/礼盒装。这些类别特征的维度不高总共30多个品类One-Hot之后稀疏度可控。第三类是季节因子这是一个连续值表示当前月份距该产品上市高峰期的距离。比如猕猴桃的上市高峰是9-10月那么在11月猕猴桃的季节因子是0.85月就接近0。季节因子作为乘法权重作用在最终评分上能避免系统在西瓜上市前就大规模推荐陕西西瓜这种“反季节操作”。值得单独提一句的是评分字段的缺失问题。电商平台的单品评分不是每个链接都有缺了就直接用店铺评分替代再没有就用同类目的平均分。这种做法不算精细但在数据量只有几千条、模型只是为了做验证的场景下足够稳定后续要上线生产环境的话可以再接入情感分析模型从评价文本里提取评分。3. 推荐算法的选型与实现3.1 为什么选“内容过滤协同过滤”的混合方案单独用任何一种推荐算法都有明显短板。内容过滤Content-Based不需要用户历史行为冷启动容易但它的问题是“只推荐相似的东西”用户今天买了苹果明天继续推苹果品类覆盖打不开。协同过滤Collaborative Filtering能找到“和你口味相似的人都在买什么”覆盖面广但新用户没有任何行为记录时完全无法工作。农产品的消费场景恰恰两者都需求。一个用户如果明确要买陕西茶叶内容过滤能精准匹配出商洛绿茶、汉中仙毫、安康富硒茶这些同类项而协同过滤能发现“买了临潼石榴的人通常也会看富平柿饼”这种跨品类关联。所以我决定做融合内容过滤的结果做候选池协同过滤的结果做补充最终用加权公式把它们合并排序。这里的候选池思路参考了业界常见的“召回-排序”两阶段框架。召回阶段用内容过滤快速筛出300个接近用户需求的产品再和协同过滤召回的200个产品做并集去重排序阶段对并集里的每个产品做综合评分排序后取前20个作为最终推荐。这样的好处是算法计算量可控没有在几万个产品上做全量排序的压力后续扩展用户量时也更容易横向扩容。3.2 基于内容过滤的相似度计算内容过滤的核心逻辑是构造每个农产品的特征向量用余弦相似度计算产品两两之间的相似度然后从用户偏好的产品出发找到特征最接近的其他产品。产品向量print出来大概是这样一个结构商品ID: 10032 特征向量: [0.68, 0.31, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.57, 0.20]每一位对应一个特征维度数值0和1代表One-Hot编码后的品种、产地、认证状态0.57是价格品质归一化后的分数0.20是当月季节因子。相似度计算我用的是余弦相似度核心代码如下import numpy as np def cosine_similarity(vec_a, vec_b): dot_product np.dot(vec_a, vec_b) norm_a np.linalg.norm(vec_a) norm_b np.linalg.norm(vec_b) if norm_a 0 or norm_b 0: return 0.0 return dot_product / (norm_a * norm_b)这个函数本身很简单但在实际使用中要注意两点。第一向量必须做标准化否则价格这个量纲较大的特征会主导相似度结果第二One-Hot特征和连续特征的混合需要在计算前做缩放我给所有特征做的是MinMax缩放让每个维度都落在0到1区间内。产品相似度矩阵提前离线算好用户请求推荐时直接查表不需要实时计算响应时间基本都在50毫秒以内。唯一需要实时计算的是用户当前正在浏览的那个产品和所有其他产品的相似度也只有这一条会临时算开销很小。3.3 基于用户的协同过滤实现协同过滤部分我选了基于用户的算法核心逻辑是三步构建“用户-商品”评分矩阵行是用户ID列是商品ID值是用户对该商品的评分或者隐式反馈点击/加购/收藏记为1下单记为3。计算用户之间的相似度找TopN个相似用户我这里取的是Top 20。把相似用户喜欢但当前用户没见过的商品按相似用户的评分加权汇总生成推荐列表。评分矩阵大概率是稀疏的几千个用户里绝大多数用户只和几十个商品发生过交互。所以相似度计算不用直接做user-user全量计算而是先通过商品的倒排索引找到和当前用户有共同行为的候选用户再在这些候选用户里做两两相似度计算能把计算量降低几个数量级。用户相似度我们用皮尔逊相关系数代码实现大致是from scipy.stats import pearsonr def user_similarity(ratings_1, ratings_2): common_items set(ratings_1.keys()) set(ratings_2.keys()) if len(common_items) 2: return 0.0 vec_1 [ratings_1[item] for item in common_items] vec_2 [ratings_2[item] for item in common_items] corr, _ pearsonr(vec_1, vec_2) if np.isnan(corr): return 0.0 return max(corr, 0)这里有个小坑皮尔逊相关系数计算时如果两个用户的共同评分商品少于两个函数会返回NaN或直接报错。我在代码里加了阈值判断少于两个共同商品直接返回0。另外负相关用户我直接丢弃了因为在农产品推荐这种低风险消费场景里负相关信号的实际价值不高没必要为了覆盖度增加系统复杂度。3.4 推荐结果的加权融合与评分解释两个算法的结果做融合时我给内容过滤和协同过滤各分配一个权重初始版本是0.6和0.4这个比例是通过小范围人工评测调出来的。评判标准是推荐列表里用户点击转化率的提升幅度反复试了0.7:0.3、0.5:0.5、0.6:0.4三组0.6:0.4这组试下来点击转化效果最好就保留了。最终评分公式final_score 0.6 * content_score 0.4 * collaborative_score seasonal_bonus其中content_score和collaborative_score都做了MinMax归一化seasonal_bonus是季节因子乘以0.15用于在应季产品上给一个额外梯度。这样一来一个产品如果既符合用户历史偏好又有相似用户背书还是当季商品它的推荐优先级就会非常靠前。我还做了一个细节处理——推荐语生成。每次推荐都会附带一段简单的规则解释比如“因为您浏览过洛川苹果为您推荐同为陕西地理标志产品的富平柿饼”。这个功能不需要复杂的自然语言生成模型直接根据规则模板拼接即可但对用户信任感的建立很重要。用户看到一个推荐结果时如果知道“为什么推荐它”点击意愿会高很多。4. 系统架构与Web端可视化实现4.1 Flask后端与模块划分后端服务用Flask搭建原因很实际Flask轻量、灵活性高单文件能跑起来拆模块也容易。我按功能把代码分成了五个子目录spider/爬虫调度和网页解析preprocess/数据清洗与特征工程recommend/推荐算法引擎web/Flask路由与接口层static/前端静态资源这个划分的逻辑是爬虫和预处理是离线任务推荐引擎和Web服务是在线服务离线和在线通过共用数据库表关联。数据库用的是SQLite——数据量只有几千条单机开发阶段不需要上MySQLSQLite文件即拷即用后面数据量大了把连接层换成MySQL驱动就好SQL基本不用改。核心接口设计只留了三个接口方法功能说明/api/recommend?user_idxxxGET获取用户个性化推荐列表/api/product/int:product_idGET获取单个商品的详情与相似推荐/api/trend/categoryGET获取某品类农产品的价格趋势数据这三个接口基本覆盖了前端展示的所有场景。推荐列表接口的响应顺序是取用户历史行为 - 调推荐引擎 - 生成推荐列表和推荐解释 - JSON返回。单个商品接口会顺带返回相似商品的Top 5用于商品详情页的关联推荐。4.2 推荐列表接口的代码骨架推荐列表接口的核心代码不长但逻辑链路比较完整我把精简后的骨架贴在下面from flask import Flask, jsonify, request import pandas as pd app Flask(__name__) def load_user_history(user_id): # 读取用户历史行为表 df pd.read_sql_query( SELECT product_id, behavior_type FROM user_behavior WHERE user_id ?, db_conn, params(user_id,) ) return df def generate_recommendations(user_id, top_n20): history_df load_user_history(user_id) if history_df.empty: # 冷启动直接返回应季热门产品 return get_seasonal_hot_products(top_n) content_result content_based_recommend(history_df) collab_result collaborative_recommend(user_id) merged merge_and_rank(content_result, collab_result, top_n) return merged app.route(/api/recommend, methods[GET]) def recommend_api(): user_id request.args.get(user_id, typeint) if not user_id: return jsonify({code: 400, msg: missing user_id}), 400 result generate_recommendations(user_id) return jsonify({code: 0, data: result})冷启动的逻辑我单独说一句。新用户没有历史行为时系统会直接返回“应季热门榜”即按当前月份的季节因子和近30天销量排序的产品列表等用户点了几个商品之后再逐步切换到个性化推荐。这个策略能保证新用户一进系统就有内容可看不会因为“推荐为空”直接流失。4.3 前端可视化ECharts销量与价格趋势图前端没有用复杂的框架就是HTML加原生JavaScript加ECharts。之所以选ECharts是因为它在农产品这个场景里有两个无可替代的优势一是开箱即用的地图组件可以展示陕西各区县的产地分布二是折线图和柱状图配置简单适合展示价格趋势这类时间序列数据。我做的第一个图表是“陕西特色农产品产地分布图”把陕西11个地市的苹果、猕猴桃、大枣等主力品种的销量汇总映射到地图上用颜色深浅表示供给量级。第二个图表是“品类价格走势图”按周聚合平均价格展示某个品种近三个月的价格曲线方便用户判断现在是买入还是观望。第三个图表是“推荐结果对比图”展示当前推荐列表里不同品种的评分构成让用户直观看到每个产品的推荐分数是从哪些维度打出来的。ECharts的配置本身不难难的是接口返回的数据结构要和图表配置对得上。我前后端约定了一种统一格式所有图表数据字段名都用语义化命名比如month、avg_price、sales_volume各图表组件直接从JSON里取字段不做二次映射。这个约定看起来不起眼但确实帮我省下了很多联调时间。4.4 离线计算与实时推荐的分工这里要强调一个设计取舍。推荐结果分两条路径离线计算和实时计算。离线计算每天凌晨跑一次计算所有用户的历史行为特征、产品相似度矩阵的增量更新、季节因子的刷新然后生成一个全量用户的每日推荐快照存进数据库。实时计算只在用户当前会话内发生新行为时触发只更新该用户当次的推荐列表。这样做的目的是控制Flask服务的响应时间——每次用户请求都现跑全量推荐不现实离线快照能保证绝大多数请求在几十毫秒内返回实时计算只处理少数会话内行为发生变化的用户。这个方案在单机部署时效果很好。我实测过SQLite里存了8万条行为记录离线计算全量推荐快照大约耗时40秒每日一次完全可接受实时接口在冷启动场景下平均响应时间是25毫秒命中离线快照的请求更是能压到10毫秒以内。如果后面数据量涨到百万级离线计算可以引入Spark实时部分引入Redis做缓存但现阶段单机方案已经能撑住演示环境全部需求。5. 部署发布与常见问题排查5.1 本地运行环境准备整套系统的运行环境依赖不多但Python环境配置对新手来说确实容易踩坑。我建议直接用Anaconda创建一个独立环境避免和系统Python环境冲突。我在项目管理里专门写了一个requirements.txt核心依赖版本记录如下flask3.0.0 requests2.31.0 beautifulsoup44.12.2 pandas2.1.4 numpy1.26.2 scikit-learn1.3.2 scipy1.11.4安装顺序上有个经验先装NumPy再装Pandas再装scikit-learn和scipy最后装Flask。因为Pandas编译时会去查NumPy的版本信息如果NumPy太新或太旧可能导致ABI不兼容提前装好能少很多麻烦。环境配好后命令行执行python init_db.py # 初始化数据库表结构并导入种子数据 python run_spider.py # 按需执行增量数据采集 python offline_job.py # 执行离线推荐计算任务 python app.py # 启动Flask Web服务启动后浏览器访问http://127.0.0.1:5000就能看到系统主页。首次访问会明显感觉到响应稍慢这是因为Flask首次运行需要加载模型文件和特征矩阵到内存大约要1-2秒之后同一进程内的所有请求都会复用已加载的数据响应就会很快。5.2 避坑记录新用户冷启动与稀有品种推荐开发和调试过程中遇到的坑主要集中在两个方向。第一个是冷启动的“空推荐”问题。系统刚部署时测试人员注册了一批新账号结果发现这些账号访问推荐页时直接空白。排查下来发现是冷启动分支里取“应季热门榜”时SQL查询里的日期参数格式传错了Pandas把字符串当文本匹配导致查不到任何结果。修复后我加了一条防御措施任何推荐分支的返回结果为空列表时统一兜底为全品类销量Top 10保证界面永远不会空白。这个兜底策略在生产环境的容错设计里非常实用。第二个是稀有品种的冷门问题。像陕南的香椿、略阳的乌鸡这类小众产品用户行为数据极少相似度矩阵里和它们相关的值几乎全为0导致永远推荐不出去。我做了两处调整一是将稀有品种和同属大类的热门产品做“品类关联”比如乌鸡关联到禽蛋类目香椿关联到时令蔬菜类目这样协同过滤可以通过类目间接产生联系二是给稀有品种加一个“扶持因子”在其季节性强的月份直接加0.3的分数加成确保应季时能被推荐出来。改完之后小众品的曝光量提升了大约3倍虽然绝对数值依然不大但至少不会彻底消失。5.3 问题排查速查表我整理了开发过程中最常遇到的几类问题做成一张速查表方便读者直接对号入座问题现象可能原因排查方向爬虫采集到的价格字段全是NaN页面改版导致选择器失效查看HTML结构是否新增了隐藏标签更新CSS选择器相似度计算结果全是0特征向量标准化没做检查是否对向量做了MinMax缩放推荐接口返回超时离线推荐快照过期检查celery或者cron任务是否执行成功新用户推荐结果与老用户完全相同用户行为表数据没写入检查用户点击/加购事件是否被正确落库可视化地图无法显示区县名与地图组件数据不匹配核对区县名称标准化表格注意“眉县”和“眉县(粉)”、“周至”等别名Flask服务启动报模块不存在依赖未装全或版本冲突在独立虚拟环境重新执行pip install -r requirements.txt这个表是我实际排障过程中总结出来的不是凭空写的。比如“区县名不匹配”这条陕西的地名里带括号后缀的情况很常见地图组件的行政区划表里存的是“眉县”但产品标题里写的是“眉县徐香猕猴桃”不做标准化清理的话前端就匹配不上。5.4 性能优化的小心得项目做到后期我花了些精力做性能优化有几个收获值得分享。一是用内存缓存做推荐结果的二次加速。Python标准库的functools.lru_cache就能解决给推荐接口的查询结果加上缓存相同用户ID在10分钟内重复请求直接走缓存实测可以把尾部延迟从80毫秒降到10毫秒以下。不过要注意缓存的是推荐结果列表不是算法中间计算值否则缓存命中率反而会很低。二是Pandas的向量化操作远比循环快。在特征工程阶段原始数据有8000多条如果用for循环逐条处理整体耗时大约8秒改成Pandas的apply加向量化条件赋值之后耗时降到不到1秒。看似都是“能跑”但用户如果是在Web界面触发全品类的数据处理8秒的等待已经会让用户产生挫败感了。三是SQLite的索引一定要建。用户行为表、商品表、推荐结果表都建了联合索引特别是user_id behavior_type这个组合索引让查询时间从全表扫描的几百毫秒降到几毫秒。SQLite对中文支持没有问题索引创建后对模糊匹配也有提速效果但模糊匹配本身尽量少用前缀匹配性能远好于LIKE %陕西%这类写法。6. 扩展方向与个人经验总结做到这一步整个系统已经完整跑通了但如果继续往下做我个人觉得还有三个扩展方向值得探索。方向一引入深度排序模型。目前用的线性加权融合方案虽然稳定、好解释但在特征交叉表达上能力不够。换成LightGBM或者更进一步的Wide Deep模型在相同特征集下推荐准确率可以再涨一截代价是调参和特征工程的工作量会显著增加。对农业电商这个场景我建议先保留现有方案当用户行为数据积累到10万条以上再考虑升级。方向二加入评价情感分析。目前系统的评分依赖电商平台的显式分值但很多用户买了商品不会主动打分如果抓取评价文本做情感分数可以扩充隐式反馈的数据来源。用Python的SnowNLP或者Bert模型做中文情感分类开发成本可控而且对农产品这种“口感类”评价占比高的场景效果会很不错。方向三从推荐升级到产销决策辅助。把推荐模型输出和产地库存、物流半径数据结合就能从“给消费者推荐”扩展到“给合作社建议”——预测未来两周某个单品在区域市场的需求量指导备货和发货优先级。这一步离真正的产业应用更近也是这个系统最有想象力的方向。最后说点我自己的体会。做这类农业领域的推荐系统技术难度不在于模型有多深而在于数据的“现场感”。农产品数据比标品电商数据脏得多品种命名混乱、产地信息缺失、季节性强、价格波动大、消费者决策因素复杂这些才是真实世界的复杂度。用Python做这套系统的过程让我最受益的并不是算法本身而是学会如何建模一个“信息不完美”的真实场景——数据脏了就清洗特征缺了就造算法效果不好就换加权策略所有环节都是实证驱动的。如果你也想复刻一个类似的系统我的建议是先别急着上高大上的模型把你所在地区的农产品数据老老实实爬一遍、洗一遍、建好别名表和季节因子表然后再用一个简单的内容推荐算法跑起来你会在过程中发现各种预想不到的问题。把这些问题逐个解决掉你的系统就比90%的“空架子演示项目”实用了。这套方法论换到任何垂直领域都成立。
阅读完成 · 觉得有帮助?
咨询建站