news 2026/10/9 8:22:04

Python美食推荐系统实战:Django协同过滤与Echarts可视化大屏

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python美食推荐系统实战:Django协同过滤与Echarts可视化大屏

最近把一个美食推荐系统完整整理了一遍,从数据采集到算法实现再到可视化展示,整个项目用到的技术正好是 Python 岗位需求里最常见的组合:爬虫、Echarts 可视化、协同过滤推荐算法和 Django 框架。项目核心是围绕“店铺推荐”做个性化推荐,不是简单按照评分排序,而是根据用户的历史行为,猜你可能喜欢哪家店,同时用可视化大屏把店铺评分分布、美食分类占比、用户口味偏好这些数据全部展示出来。适合正在学 Python 想做实战项目的同学,也适合准备往数据分析或推荐算法方向转的朋友参考。

1. 项目整体设计与技术选型

1.1 需求拆解:美食推荐系统到底要解决什么问题

我最早拿到这个需求时的第一反应是,这玩意儿到底该怎么拆。市面上的推荐系统资料很多,但真正从数据、算法、展示端到端串起来的项目不多。很多教程只讲一个孤立算法,或者只给一个爬虫脚本,很难在简历上讲清楚“我能独立做完整项目”。所以我把项目目标拆成三个层次:第一层是数据层,至少要把美食店铺的基础信息采集下来;第二层是算法层,要根据用户的历史交互行为给出店铺推荐结果;第三层是展示层,要有一个可用的 Web 界面和可视化大屏,让别人一眼看懂系统在做什么。

拆完需求之后,我发现整个系统可以分成四条相互独立又能串联的线。第一是数据采集线,负责把店铺名称、分类、评分、人均价格、地址这些字段抓取入库;第二是算法线,负责从用户行为记录里构建评分矩阵,训练协同过滤模型,生成 TopN 店铺推荐;第三是 Web 应用线,负责用户注册、登录、店铺浏览、评分收藏和个人推荐页;第四是可视化线,负责用 Echarts 把统计数据和推荐结果变成大屏图表。每条线之间用数据库解耦,任何一条挂了,其他线还能独立开发调试。这个拆法帮我省了很多时间,因为不用盯着一个巨大的 views.py 来回改。

1.2 技术栈选型:为什么是 Django 而不是 Flask

技术选型上,语言当然选 Python,生态太成熟了,爬虫、数据分析、算法都有现成库。真正纠结的是 Web 框架,我在 Django 和 Flask 之间犹豫过。最后选了 Django,不是因为它比 Flask 高级,而是因为本项目需要用户系统、后台管理、多张数据表之间的关联,Django 自带 ORM、Admin 后台和认证体系,开发效率高一大截。Flask 灵活,适合纯 API 或特别小规模的项目,但要自己拼一堆扩展,在这个场景下属于增加工作量。

对比项DjangoFlask
自带ORM有,模型关系清晰无,需要自己选 SQLAlchemy
用户认证内置 auth 系统,直接用需要扩展
Admin后台内置,可快速维护数据需要自己写
适合场景数据模型多、完整Web应用轻量API、微服务

爬虫部分我直接用 requests + lxml,requests 负责拿 HTML,lxml 负责用 XPath 解析,这两个库组合足够应对大多数静态页面。可视化层选了 Echarts,不用 Highcharts 的最主要原因是 Echarts 开源免费、中文文档全、社区案例多,而且图表配置方式对前端新手很友好。数据库在开发阶段其实可以用 SQLite,但表关系复杂以后我更推荐 MySQL,因为联表查询、并发写入都会更稳。Redis 可以作为可选项,用来缓存相似度矩阵和热门店铺,聊性能优化时很有话题度。

1.3 数据流与系统架构设计

整个系统的数据流可以用一句话概括:爬虫脚本采集店铺数据入库,超市用户在 Web 端产生评分和收藏行为,行为数据进入行为表,推荐算法离线读取行为表生成相似度矩阵,Django 接口根据当前用户行为拿到推荐结果并返回给前端页面展示,同时统计数据接口给 Echarts 大屏使用。

我没有把推荐计算放在用户请求的同步链路里,而是做成了离线计算加在线查询的结构。相似度矩阵是推荐算法里最重的计算部分,如果每次请求都重新算一遍用户和物品的矩阵,那数据库再快也扛不住。所以我用一个定时任务去训练模型并落盘,线上接口只负责读矩阵、查邻居、排序,这样响应时间能控制在几十毫秒级别。这个设计的核心思路是把“计算密集”和“交互密集”分离,也是我在实际项目里最常用的一套套路。

1.4 协同过滤算法选型解析

推荐算法方面,我没有一上来就套深度学习模型。美食推荐这个场景,数据量级通常在一万店铺以下,做复杂模型既没有数据支撑,也难以解释结果,上线之后都是黑盒,出了问题都不好排查。协同过滤是解释性强、实现成本低、效果又稳定的算法,很适合这个项目。

协同过滤分两类。UserCF 的核心是先找到和你口味相似的用户,再把这些用户喜欢的店铺推荐给你;ItemCF 的核心是先找到和你已经去过、喜欢的店铺相似的店铺,再推荐给你。美食推荐场景里,用户数量往往远大于店铺数量,而且用户口味随时会变,UserCF 计算代价大;反过来,店铺更新频率不高,用户对店铺的行为数据比较稳定,所以最终在线推荐我用了 ItemCF。UserCF 也没有完全扔掉,新用户冷启动的时候我会用它做候选补充。简单说,项目里两种算法都写了,主力是 ItemCF,UserCF 作为辅助策略。

2. 数据采集:爬虫模块设计与实现

2.1 目标站点分析与爬虫方案设计

爬虫第一步不是急着写代码,而是先确定目标页面。我找的是一个允许普通用户公开浏览的本地美食频道,没有登录墙,也没有明显的反爬声明。这里要提醒一句:如果你要抓取别人的网站,一定要先看网站的 robots.txt 和相关条款,爬虫只适合学习和技术验证,不要对生产网站做高频采集。我测试时用的就是一个公开可访问的列表页,并且把请求频率控制到一秒一次,数据量不大,不会给对方服务器造成压力。

拿到目标页面后,先用浏览器开发者工具检查页面结构。我主要确认店铺名称、评分、人均价格、地址这几个字段分别在哪个区块里。这类列表页通常结构比较规律,每个店铺都是一个卡片区块,字段用固定的 class 包裹。分析清楚了之后,用 requests 发送 get 请求,然后把响应文本交给 lxml 去解析。先写一个最简版本,确认能拿到 HTML,再去写解析逻辑。这一步最忌讳的就是直接从网上复制一段 XPath 就开始抓,因为页面一改就全崩。

import requests from lxml import etree HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } resp = requests.get("https://example.com/shops", headers=HEADERS, timeout=15) print(resp.status_code, len(resp.text))

2.2 基于 XPath 解析字段与 text 函数使用

拿到 HTML 文本后,我用etree.HTML()把它转成可查询的节点树,然后用 XPath 提取字段。这里特别容易踩一个坑:XPath 返回的是列表,不是单个字符串。很多新手写name = html.xpath('//h3/a/text()')之后直接对 name 调用 strip(),结果报错说列表没有 strip 方法。正确做法是先取出列表,再判断长度,最后对每个元素做清理。

另一个常见问题是text()的用法。//span[@class="score"]/text()只取当前节点的直接子文本,如果评分是被一层 span 嵌套包着的,比如<span class="score"><span class="num">4.8</span>分</span>,直接取 text() 会返回空或者只剩一个“分”字。这时候可以用string()函数,它会取当前节点的全部后代文本;如果页面里匹配多个节点,就要用//span[@class="score"]//text()然后手动拼接。我在项目里写了一个小函数,专门负责对每个 xpath 表达式的结果做提取和清理。

html = etree.HTML(resp.text) shop_names = html.xpath('//div[@class="shop-info"]//h3/a/text()') scores = html.xpath('//div[@class="shop-info"]//span[@class="score"]/text()')

这里的逻辑看起来简单,实际调试时却是最花时间的。有些页面里的店铺名称不在 a 标签里,而是单独放在 div 节点中;有些评分是“4.8”而不是“4.8分”,清洗时我就直接把非数字字符替换掉,再转成 float。清洗规则我单独写了一个方法:先 strip 空白,再按字段类型做转换,空值填成 0 或空字符串,尽量避免脏数据进数据库,因为后面推荐算法对数据质量非常敏感。

2.3 动态页面与请求频率控制

有些美食网站并不是服务端直接渲染完 HTML 的,而是先返回一个空壳,再通过 JavaScript 请求 JSON 接口把列表渲染出来。如果遇到这种情况,直接 requests 只能拿到一堆空的 div,xpath 什么都匹配不到。我的处理原则是:先别急着上 Selenium,打开浏览器开发者工具里的 Network 选项卡,刷新页面,看 XHR 请求列表,找到真正返回店铺数据的那个 JSON 接口。之后用 requests 直接请求那个接口,再用resp.json()解析数据,效率和稳定性都远高于模拟浏览器。

如果遇到登录验证或者验证码,通常说明请求频率太高,或者目标站点本身不允许采集。正确做法是降低频率、检查请求头、换数据源,而不是强行绕过。我在爬虫脚本里对每个页面请求之间加了time.sleep(1),整个采集过程大概几分钟,不会对目标站点造成明显影响。做爬虫项目一定要把握好边界,学技术没问题,但别把自己做成骚扰别人服务器的工具。

2.4 数据入库与店铺字段设计

数据入库之前,我把表结构先定好,因为表结构直接决定后面 Django 页面和推荐算法怎么写。店铺表shop我设计了 7 个字段:id、name、category、avg_score、price、address、reviews。其中avg_score是浮点数,price是整数,reviews是评论数量,用于后续排序和可视化统计。

用户行为表rating更关键,字段至少是:id、user_id、shop_id、rating、created_at。这张表可以看作推荐算法的“原料”,用户对店铺的打分越高,说明偏好越强。项目里我造了一批模拟用户行为数据,给 200 个用户和 1000 家店铺生成随机的 1 到 5 分评价,这样算法有足够的输入可以跑。真实用户后续产生的评分行为也会写入同一张表,冷启动问题就没有那么严重了。

3. 协同过滤推荐算法核心实现

3.1 构建评分矩阵与数据准备

推荐算法第一步,是把行为表转换成评分矩阵。我习惯用 pandas 的 pivot_table 来实现。行为表里有user_id、shop_id、rating三列,pivot 之后就变成了行是用户、列是店铺的二维矩阵,每个格子代表该用户对该店铺的评分,没有评分就填 0。

数据准备阶段有个细节:不能直接拿原始评分矩阵去算物品相似度,因为不同用户的打分尺度不一样。有人习惯打 4 分保底,有人偏好打极端分,如果不处理,相似度会被“打分习惯”而不是“真实偏好”主导。解决办法是先对每个用户做中心化,也就是把每个用户的所有评分减去该用户的平均分。这样处理后,评分大于 0 表示比该用户平时喜欢的店更喜欢,小于 0 表示相对不喜欢,物品向量在不同用户之间才有可比性。

3.2 ItemCF 物品相似度计算

ItemCF 的核心是算店铺两两之间的相似度。我先定义两个店铺的相似度等于“共同给它们打过分的用户向量”的余弦相似度。公式看着复杂,实际代码里就是一个矩阵乘法加归一化。用 Python 写出来如下:

import pandas as pd import numpy as np def compute_item_sim(bhv_df): pivot = bhv_df.pivot_table( index="user_id", columns="shop_id", values="rating" ).fillna(0) user_mean = pivot.mean(axis=1) pivot_adjusted = pivot.sub(user_mean, axis=0) item_matrix = pivot_adjusted.T norm = np.sqrt((item_matrix ** 2).sum(axis=1)) norm[norm == 0] = 1e-10 sim = item_matrix.dot(item_matrix.T) / np.outer(norm, norm) sim = sim.clip(lower=-1, upper=1) return pd.DataFrame(sim, index=item_matrix.index, columns=item_matrix.index)

这段代码里最容易忽略的是分母为零的情况。如果某家店铺在矩阵里的行为向量全为零,或者调整后所有维度都为零,norm就会是 0,除出来全是 NaN。我先用1e-10替换掉零,避免警告,后面再用clip把极端值收进来。实际计算出来的相似度矩阵是一个 1000 乘 1000 的方阵,对角线是 1,表示每家店铺和自身完全相似,这个矩阵保存下来之后,在线推荐阶段直接查表就行。

3.3 生成 TopN 店铺推荐

有了相似度矩阵,推荐过程就变得很直接。假设当前用户曾经给店铺 A 打过分,A 在相似度矩阵里有十个最相似的邻居店铺,那么这十个邻居店铺就会进入候选池,得分等于“用户对 A 的评分 × A 与该邻居的相似度”。用户有多家历史店铺时,候选店铺的得分就是所有来源的累加,最后按照总分排序,取前 N 家。

def recommend_for_user(user_id, sim_matrix, bhv_df, top_n=20): user_items = bhv_df[bhv_df["user_id"] == user_id] if user_items.empty: return [] user_shop_ids = set(user_items["shop_id"]) scores = {} for _, row in user_items.iterrows(): shop_id = row["shop_id"] rating = row["rating"] if shop_id not in sim_matrix.index: continue sim_series = sim_matrix.loc[shop_id].sort_values(ascending=False) for candidate, sim_score in sim_series.head(10).items(): if candidate == shop_id or candidate in user_shop_ids: continue scores[candidate] = scores.get(candidate, 0) + sim_score * rating sorted_shops = sorted(scores.items(), key=lambda x: x[1], reverse=True) return [shop_id for shop_id, _ in sorted_shops[:top_n]]

这里有一个必须处理的逻辑:用户已经去过的店绝对不能出现在推荐结果里。我刚写的时候没加这个过滤,结果推荐结果里总出现用户已经打分的店铺,看起来非常傻。另一个细节是邻居数量 K 的选择,我默认取 10,K 太大会把相似度很低的店铺也拉进来,K 太小则推荐结果不够丰富。如果数据量变大,可以把head(10)改成按相似度阈值过滤,只保留相似度大于 0.3 的邻居,效果更稳。

3.4 冷启动与推荐多样性优化

推荐算法跑通之后,我马上遇到冷启动问题。新注册用户没有任何行为,user_items是空的,推荐函数直接返回空列表。这个体验太差了。我的方案是:没有行为时回退到“热门店铺 Top10”,按平均分高、评论数多排序,先把页面撑起来;用户有过一次评分行为后,系统立刻切换成个性化推荐。

还有一个问题,是推荐结果同质化严重。用户喜欢过一家串串店,算法会把周围所有串串店全推出来,品类极端单一。虽然从相似度角度看没错,但用户的真实需求往往是“今天想吃个不一样的”,只看相似度会把用户圈在固定口味里。我加了一个分类打散策略:生成推荐结果后按店铺分类分组,每个分类最多保留 3 到 4 家,如果不够 20 家,再从剩余候选中补足。这样推荐列表里既有用户偏好的品类,也有其他高频分类,明显更贴近现实选择场景。

3.5 在 Django 中集成推荐服务

算法写好后,不能留在 notebook 里,要把它接入 Web 系统。我单独建了一个recommend/services.py文件,把评分矩阵的读取、相似度矩阵的加载和推荐函数都封装起来。Django 视图只需要调用recommend_for_user(user_id),拿到的店铺 id 列表再查一次数据库,就可以渲染页面了。

相似度矩阵我避免在每次请求时重复计算。项目启动时,通过AppConfig.ready()把矩阵加载到全局变量,或者保存成 pickle 文件,需要更新时才重新训练。平时线上请求只做查表和排序,性能完全够用。这个“离线训练、在线服务”的分层思想,即使以后把算法换成深度学习,整体架构也不用推翻。

4. Echarts 数据可视化大屏

4.1 大屏需求与图表选型

可视化大屏是这个项目里最直观的部分。我主要想表达三类信息:第一类是店铺基础数据,比如美食分类占比、评分分布、人均价格区间;第二类是用户行为数据,比如最受欢迎店铺 Top10;第三类是推荐系统效果,比如各分类推荐命中率。这些数据全部来自 Django 接口,前端用 Echarts 渲染。

图表选型上,分类占比用饼图,评分分布用柱状图,热门店铺用横向柱状图,人均价格区间用折线图。大屏整体采用深色背景,卡片式布局,方便后续投到展示屏幕上。Echarts 的好处是配置项丰富,官方示例可以直接改参数,我花了一个上午就把五张图全部搭起来了,比从零写 SVG 不知道快多少。

4.2 Django 返回统计数据接口

大屏数据不能直接写在模板里,最合理的做法是提供一个 JSON 接口。我在 dashboard app 里写了一个视图,查询店铺表,统计各分类数量、评分区间数量、价格区间数量和热门店铺列表,然后通过JsonResponse返回。前端页面用 fetch 请求这个接口,拿到数据后更新图表。

from django.http import JsonResponse from django.db.models import Count, Avg def dashboard_data(request): shops = Shop.objects.all() category_counts = shops.values("category").annotate(num=Count("id")) score_ranges = [ {"name": "0-3.5", "value": shops.filter(avg_score__lt=3.5).count()}, {"name": "3.5-4.0", "value": shops.filter(avg_score__gte=3.5, avg_score__lt=4.0).count()}, {"name": "4.0-4.5", "value": shops.filter(avg_score__gte=4.0, avg_score__lt=4.5).count()}, {"name": "4.5+", "value": shops.filter(avg_score__gte=4.5).count()}, ] hot_shops = list(shops.order_by("-reviews")[:10].values("name", "reviews")) return JsonResponse({ "category": list(category_counts), "score": score_ranges, "hot": hot_shops, })

4.3 Echarts 核心图表实现

前端部分,我在模板里放了一个大屏容器,然后引入 echarts.min.js 和自定义的 dashboard.js。每个图表都有一个容器<div>,必须显式设置高度,否则 Echarts 初始化后会是空白。这一步是新手踩坑重灾区,很多人图表不显示,最后发现是容器高度为 0。

fetch('/api/dashboard/') .then(res => res.json()) .then(data => { const scoreChart = echarts.init(document.getElementById('scoreChart')); scoreChart.setOption({ tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: data.score.map(item => item.name) }, yAxis: { type: 'value' }, series: [{ name: '店铺数', type: 'bar', data: data.score.map(item => item.value) }] }); window.addEventListener('resize', () => scoreChart.resize()); });

这段代码看起来简单,但需要注意一点:fetch 是异步的,一定要在then里再来执行setOption,不能在页面加载时直接同步调用。如果你把初始化写在 fetch 之前,图表就会先拿到空数据,后面数据到了不去刷新,自然白屏。我还给每个图表加了resize监听,浏览器窗口变化时自动重绘,这样投屏到不同分辨率的屏幕上时不会变形。

4.4 前端接口安全与防爬

关于前端防爬,我自己的经验是:不要指望通过混淆页面源码来彻底防住爬虫,因为任何数据只要在浏览器里展示出来,就一定有办法被别人拿到。更现实的做法是:第一,敏感接口必须要求登录,用户未登录时只返回汇总数据,不返回原始行为明细;第二,对接口做频率限制,用 Django 中间件记录每个 IP 的请求次数,超过阈值直接返回 429;第三,统计接口只返回聚合结果,不在 JSON 里携带太多原始数据。做到这几点,已经能挡掉大部分脚本采集者。

5. Django 框架整合与系统功能实现

5.1 项目目录结构与模块划分

整个项目我按 app 做了模块化拆分,而不是把所有功能堆在同一个 Django app 里。目录大致是这样的:

config/ # 项目配置 apps/ accounts/ # 用户登录注册 shops/ # 店铺模型与列表页 recommend/ # 推荐算法服务 dashboard/ # 可视化接口与页面 static/ css/ js/ templates/

这种拆分方式带来的好处非常明显。我第一次做这个项目时,把推荐代码、视图、爬虫解析全写在views.py里,结果改一个功能就要重启整个项目,测试一个接口要把日志翻半天。拆分成 app 之后,每个模块的边界清晰了,维护成本立刻降下来。推荐算法相关的函数都放在recommend/services.py,爬虫脚本则放在独立目录,不进 Web 项目的 app 目录。

5.2 用户注册登录与行为数据采集

用户系统直接用 Django 自带的auth应用实现,注册、登录、退出都能在半个小时内搞定。用户登录后,我在店铺详情页增加了收藏和评分按钮,前端通过 post 请求把shop_id和rating传到后端。后端视图里用request.user.id获取当前登录用户,然后检查行为表里是否已有记录,有则更新评分,没有则新增记录。这个逻辑虽然简单,但却是推荐系统最重要的数据来源。

这里有个细节:用户评分行为不能只记录分数,还应该记录时间。推荐结果中加入时间衰减后,可以做到“最近喜欢的店影响更大,几个月前的口味逐渐淡出”,这样更符合真实用户心理。不过时间因子会引入更多调参,我把它作为扩展点留着了,基础版先不做。

5.3 店铺列表、搜索与个性化推荐页

店铺列表页需要支持分类筛选和关键词搜索。Django ORM 的icontains就能解决搜索需求,用filter(name__icontains=query)对店铺名做模糊匹配。列表页还需要分页,我用 Django 内置的 Paginator,一页 12 家,切换页码时分页参数会带到 URL 里。

推荐页和列表页结构相似,但数据来源不同。登录用户进入“猜你喜欢”页面,视图会调用recommend_for_user拿到推荐店铺 id 列表,然后查询店铺表,最后渲染模板。如果用户没有登录,就展示热门店铺。这里要注意 N+1 查询问题:Django 查询店铺列表时,如果用到了关联表字段,记得用select_related或prefetch_related,否则页面一大,数据库查询次数会爆炸,接口响应速度直接掉到几秒。

5.4 定时爬虫与性能优化

爬虫不能放在 Web 请求线程里执行,否则用户点一下页面就要等好几秒,体验极差。我单独做了一个定时任务模块,用 APScheduler 每小时执行一次增量爬虫。增量爬虫只请求最近热度出现变化的店铺,比如评论数有变化的店铺,而不会每天全量抓取。

Web 端性能优化主要集中在缓存上。热门店铺 Top10 的查询频率非常高,但数据变化不频繁,我用 Redis 缓存了结果,缓存 10 分钟。相似度矩阵也做了缓存,文件版本存在本地,Redis 版本适合多实例部署。这些优化做完后,本地开发环境接口响应基本都在 100ms 以内,部署到普通服务器也能流畅跑。

6. 常见问题与排错实录

6.1 爬虫解析结果为空或字段错位

爬虫踩坑最多的地方是 XPath 解析结果为空。遇到这种情况,我一般按照三条路径排查:先打印resp.text前 1000 个字符,确认请求到的不是登录跳转页或者验证页;再看 XPath 表达式的 class 属性是否带多余空格,比如class="shop list"不能匹配class="shop-list";最后用etree.tostring(html, pretty_print=True)打印局部 HTML,确认解析目标结构没有变化。字段错位通常是因为页面里某些店铺卡片缺了某个字段,导致后续元素频繁错位。解决办法是每个字段都做独立 XPath 提取,而不是用“按索引对齐整个卡片”这种脆弱的方案。

6.2 Echarts 白屏、tooltip 不显示怎么办

Echarts 白屏九成是容器高度问题。echarts.init执行时,容器需要有明确的高度,不能用 height:auto。我惯用的做法是给大屏容器设置一个固定高度,比如 300px,或者用 CSS Grid 布局给每个图表卡片分配min-height: 240px。Tooltip 不显示通常是配置 trigger 不对,柱状图上挂了tooltip: {trigger: 'item'},但坐标轴数据是从 xAxis 来的,应该改成trigger: 'axis'。如果图表还是没反应,打开浏览器开发者工具看 Console 有没有报错,多半是 JSON 数据结构没对齐。

6.3 Django 静态文件加载失败

本地开发时静态文件 404,先检查settings.py里的STATICFILES_DIRS是否包含了项目 static 目录。部署到服务器上就会遇到另一个问题:DEBUG=False之后 Django 默认不提供静态文件服务,必须执行python manage.py collectstatic把所有静态文件收集到一个目录,然后交给 Nginx 或其他 Web 服务器去托管。如果这两个都没问题,记得清浏览器缓存再刷新页面。

6.4 推荐结果太同质化、全是大热门

我最早跑通协同过滤时,推荐出来的 20 家店几乎全是连锁火锅店,因为热门店铺在行为矩阵里和大量用户都有交集,相似度天然偏高,算法会给它们更高的候选得分。解决方式有三个:第一,过滤掉用户已经访问过的店;第二,对热门店铺做降权,比如相似度乘以一个惩罚系数;第三,引入多样性重排,按分类限制推荐数量。我最终把分类打散逻辑集成进了推荐函数,效果好了很多,用户反馈也明显更丰富。

6.5 数据量变大后推荐计算变慢

当行为数据到几万条时,pandas 矩阵乘法还能跑,但如果到几十万用户、几十万店铺,二维矩阵点积的内存开销就没办法忽视了。这个项目的应对思路是离线计算、增量更新。相似度矩阵每天离线算一次,结果导出成 pickle 文件,线上接口只查矩阵。如果以后数据量继续扩大,再考虑用 Faiss 做近似最近邻搜索,或者直接把行为数据放到 Spark 里做分布式计算。这些扩展方向都不影响现有架构,换算法只是换一个服务实现。

最后分享一个我做项目时最深的体会:一开始我急着写爬虫,把店铺数据抓了一堆才开始做算法,结果发现用户行为数据根本不够,推荐效果一直不好。后来先构造了一份干净的用户模拟行为数据,把“数据采集 → 推荐算法 → 接口 → 页面”这条主链路完整跑通,再回过头去完善真实采集和真实行为,效率一下子提上来了。如果你也要复现这个项目,强烈建议先把一条最小链路走通,再逐步加功能。另一个小技巧是固定一份样本数据集跑回归测试,这样不管爬虫代码怎么改,推荐算法都不会被意外变化搞挂。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 8:21:41

从JSON/YAML到Pkl:三步实现配置类型安全与复用

配置管理大概是后端项目里最容易被忽视、又最能拖垮人的环节。你项目跑不起来&#xff0c;日志里报了个端口占用&#xff0c;一翻配置文件才发现&#xff0c;端口号写对了&#xff0c;可有一处JSON数组的缩进不规范&#xff0c;解析器直接跳过了一段配置&#xff1b;又或者YAML…

作者头像 李华
网站建设 2026/10/9 8:21:34

Wine 11.1实测:Linux下运行Windows应用更稳更流畅

从知道Wine要发新版本开始&#xff0c;我就在等这个版本。说实话&#xff0c;过去两年Wine的更新一直处于"修修补补又能用"的状态&#xff0c;虽然每个版本都在进步&#xff0c;但真正让人眼前一亮的变化不多。这次Wine 11.1发布后&#xff0c;我第一时间在主力机上装…

作者头像 李华
网站建设 2026/10/9 8:21:33

水平集分割实战:医学图像边界精修与GPU加速

简介&#xff1a;本资源是一套基于MATLAB实现的水平集图像分割算法实践代码包&#xff0c;面向计算机视觉初学者、图像处理研究者及医学影像分析方向的工程人员&#xff0c;解决不规则目标边界提取与拓扑变化场景下的精准分割问题。压缩包共6个文件&#xff08;3个MATLAB源码文…

作者头像 李华
网站建设 2026/10/9 8:19:46

飞牛NAS虚拟机搭建Ubuntu桌面:从安装到远程访问的完整指南

1. 写在前面&#xff1a;为什么要在“NAS”里塞一个“Linux 桌面” 我大概是两年前开始接触飞牛 fnOS 的&#xff0c;当时纯粹是想把手头几块闲置硬盘利用起来&#xff0c;做一个家庭影音中心。说实话&#xff0c;那时候我对“NAS”的理解还停留在“网络硬盘”这个层面——能存…

作者头像 李华
网站建设 2026/10/9 8:19:36

基于WebSocket的跨平台私人远程桌面:从采集到渲染的完整实现

简介&#xff1a;这是一套面向高校计算机相关专业毕业设计的完整项目源码&#xff0c;主题为基于WebSocket的跨平台私人远程桌面工具&#xff0c;适合正在准备毕设或希望深入理解网络协议与远程控制原理的学生与开发者。项目采用Java AWT、SpringBoot与WebSocket等技术实现&…

作者头像 李华
网站建设 2026/10/9 8:19:25

Windows共享文件夹旧账号登录问题:凭据缓存与SMB会话清理指南

你有没有遇到过这种情况&#xff1a;一台Windows电脑&#xff0c;之前用A账号连过公司NAS或者某台服务器的共享文件夹&#xff0c;后来人家改了密码&#xff0c;或者你想改用另一个有权限的账号登录&#xff0c;结果双击共享文件夹还是直接打开&#xff0c;根本不给你输入新账号…

作者头像 李华