那段时间我一直在调推荐接口的返回结果,前端的商品卡片要么刷不出来,要么推荐得毫无逻辑,最后发现问题根本不在算法,而在服务之间互相等待。后来我把这套基于SpringBoot、SpringCloud、Vue和大数据技术的商品推荐系统重新梳理了一遍,从爬虫采集商品数据开始,到Spark离线计算,再到微服务治理和可视化大屏展示,才真正把整条链路走通。如果你也在做类似的推荐系统,不管是课程设计、毕业设计,还是想在公司内部搭一个可演示的推荐项目,这篇文章会非常有参考价值。我会把架构设计的思路、代码骨架、踩过的坑以及能直接复用的配置都写出来。
1. 项目定位与整体架构拆解
1.1 这个系统到底在解决什么问题
商品推荐的本质,是在海量商品和用户有限注意力之间做匹配。假设平台上架了几万件商品,用户不可能一个个翻完,他打开App的真实诉求是“我感兴趣的东西直接出现在我面前,最好不用我费心筛选”。要让这件事发生,需要的技术链路比想象中长得多:用户点了一个商品、停留了几秒、加购了一件外套,这些行为要被抓下来,清洗干净,送去计算,生成候选集,再通过接口返回到前端页面。任何一个环节出问题,用户看到的就是空白页或者一堆不相关的商品。
我见过很多人一开始就埋头写推荐算法,代码写了一堆,结果数据没有、接口没有、前端页面也对不上,最后整个系统根本跑不起来。这个项目跟那种做法正好相反,核心目标是先把链路打通。你可以把整条路理解成“采数—存数—算数—服务化—可视化”,每一步我都做了相对完整的实现。最终交付的是一个能演示、能二次开发、也能写进简历的完整项目,而不是一个只能跑通单测的算法脚本。
1.2 微服务的拆分边界怎么画
这个系统如果做成单体应用,也不是不能跑,但维护性会很差。爬虫在跑定时任务,推荐服务在做矩阵计算,前端调用的在线接口可能因为资源竞争慢到超时,三者互相抢内存和CPU,问题很难定位。所以我按业务域拆成了几个独立服务,每个服务只管自己那一摊事。
| 微服务 | 核心职责 | 主要数据 | 依赖组件 |
|---|---|---|---|
| 商品服务 | 商品增删改查、分类管理、商品搜索 | MySQL、Elasticsearch | SpringBoot、MyBatis-Plus |
| 用户服务 | 注册登录、用户画像、行为记录 | MySQL、Redis | SpringBoot、JWT |
| 推荐服务 | 离线计算、相似度矩阵、推荐列表生成 | MySQL、Redis、HDFS | Spark、SpringBoot |
| 采集服务 | 爬虫调度、数据清洗、日志入库 | MySQL | Quartz、HttpClient、Jsoup |
| 可视化服务 | 大屏统计接口、报表聚合 | MySQL、Redis | SpringBoot |
| 网关服务 | 统一路由、鉴权、限流 | Redis | Spring Cloud Gateway |
这个拆分方式不是拍脑袋定的,核心依据是“数据边界”。商品数据归商品服务管,用户行为归用户服务管,推荐结果归推荐服务管,谁也不许随便去动别人的表。这样做的好处非常直接:采集服务某个时间段内爬虫任务特别重时,不会拖垮推荐服务;推荐服务在做Spark离线任务时,也不会影响用户登录和商品查询。
另外,采集服务和推荐服务的拆分还有一个隐藏价值:开发时可以并行推进。两个人同时改代码,一个人碰爬虫,一个人碰推荐,基本不会冲突。这也是微服务的初衷之一,不是为了服务数量好看,而是为了隔离和演化。
1.3 技术选型与组件清单
技术选型上,我尽量用主流且稳定的组合。SpringBoot负责业务接口,SpringCloud Alibaba体系负责微服务治理,Vue3+Vite做前端,Spark做离线计算,ECharts做可视化大屏。下面这张表是最终定下来的组件清单。
| 技术 | 版本/组件 | 用途 |
|---|---|---|
| 后端框架 | SpringBoot 2.7.x | 各微服务基础框架 |
| 微服务治理 | Spring Cloud Alibaba | 注册、配置、网关、熔断 |
| 注册配置中心 | Nacos | 服务注册发现 + 配置管理 |
| 网关 | Spring Cloud Gateway | 统一入口和路由转发 |
| 服务调用 | OpenFeign | 服务间声明式调用 |
| 限流熔断 | Sentinel | 接口限流与降级 |
| 前端框架 | Vue3 + Vite + Element Plus | 管理后台与推荐页面 |
| 数据可视化 | ECharts + WebSocket | 大屏图表实时展示 |
| 离线计算 | Spark 3.x | 协同过滤离线任务 |
| 数据存储 | MySQL + Redis + Elasticsearch | 业务数据 / 缓存 / 搜索 |
| 任务调度 | Quartz | 爬虫定时采集与推荐任务刷新 |
Nacos和Eureka之间我选了Nacos,理由很实际:它一个组件同时干注册中心和配置中心两件事,演示环境不需要维护两套中间件,控制台能直接看服务健康状态,对排查问题特别方便。网关选了Spring Cloud Gateway而不是Zuul,主要原因是性能和响应式编程模型更贴合后续扩展需求;不过如果团队对Zuul更熟,这里不算硬性指标。
这套选型整体下来不冷门,资料多,遇到问题能搜到解决方案,也比那种“为了炫技上K8s+ServiceMesh”的工程更务实。数据量没到几十亿级之前,这套组合完全够用。
2. 爬虫模块:商品数据采集与清洗
2.1 采集目标与库表设计
爬虫模块要解决的是“数据从哪来”。真实电商平台的数据不会开放给你,所以项目里我采用的方式是采集公开的商品信息页面,同时结合脚本生成模拟用户行为数据,两者结合起来形成推荐系统需要的数据集。
采集字段主要包括:商品标题、价格、原价、销量、分类、商品图片、详情链接、入库时间。设计商品表时,我特意把price和original_price分开存,因为很多商品会显示划线价,推荐和统计时需要区分。销量字段用于热度兜底,分类字段用于类目推荐,图片地址则直接存原始URL,前端渲染时用懒加载,避免首屏图片请求太多导致卡顿。
用户行为表是推荐算法的核心输入,字段包括:user_id、item_id、behavior_type、timestamp。behavior_type我分成了浏览、收藏、加购、购买四类,后续计算相似度时会给不同行为不同的权重。这张表是增量写入的,每次用户点击商品,就通过接口写入一条记录;离线Spark任务再从这张表里拉数据计算。
这里有个容易忽略的细节:爬虫采集到的数据不能直接拿来当推荐数据源。商品数据有了,但没有用户和商品之间的交互数据,协同过滤算法是跑不起来的。所以项目里我写了一个行为日志生成脚本,按照“少数热门商品被大量交互、长尾商品交互稀疏”的分布规律,给模拟用户批量生成浏览和购买记录。这一步做完,推荐算法才有东西可算。
2.2 Java爬虫的代码骨架与解析细节
爬虫我用Java实现,核心技术是HttpClient + Jsoup,整体流程很直接:构造HTTP请求、获取HTML页面、用CSS选择器解析出商品卡片、存入数据库。先看核心代码骨架。
public class ProductCrawler { private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"; public void crawlCategory(String categoryUrl) { // 1. 构造请求并设置请求头,模拟真实浏览器 HttpGet request = new HttpGet(categoryUrl); request.setHeader("User-Agent", USER_AGENT); request.setHeader("Accept", "text/html,application/xhtml+xml"); // 2. 发送请求,获取HTML String html = execute(request); Document doc = Jsoup.parse(html); // 3. 解析商品卡片 Elements cards = doc.select("div.product-card"); for (Element card : cards) { String title = card.select(".title").text(); String priceStr = card.select(".price").text(); double price = parsePrice(priceStr); String salesStr = card.select(".sales").text(); int sales = parseSales(salesStr); String image = card.select("img").attr("data-src"); Product product = new Product(); product.setTitle(title); product.setPrice(price); product.setSales(sales); product.setImageUrl(image); productService.save(product); } } }解析页面的关键不在于代码多复杂,而在于怎么找准选择器。我每次都是先用浏览器开发者工具审查元素,找到商品卡片的公共class,再写选择器去匹配。正式跑批量任务之前,一定要先抓一个页面验证选择器能正确解析,否则可能整个目录几万条数据全存成同一个标题。
爬虫任务我用Quartz做了定时调度,比如每30分钟跑一次分类页。请求之间加随机休眠,避免固定间隔触发对方频率检测。采集范围限定在公开页面,并且只做学习验证用途,不冲击目标网站的正常运行。这里多说一句,爬虫代码写得再漂亮,合规是底线,不能为了数据量去绕过对方限制,这个边界要守住。
2.3 数据清洗与行为日志的生成
爬虫抓回来的原始数据远没有想象中干净。价格字段可能是“¥99.9元起”,销量可能是“已售1.2万”,标题里可能混着空格和换行。如果这些脏数据直接入库,后续推荐计算会出一堆幺蛾子。所以我加了一层清洗逻辑,规则很朴素但很有效:
- 价格字符串统一去除非数字字符,保留小数,转成Double类型。
- 销量“1.2万”先转成12000,“500+”去掉加号。
- 标题去掉首尾空白、HTML标签,长度超过200截断。
- 图片缺失的用默认占位图,避免前端显示破图。
- 商品去重:用“标题+价格”做唯一指纹,重复数据直接跳过。
行为日志生成这块,我写了一个模拟脚本,按幂律分布生成用户行为。注意看这里,推荐系统里有个著名的现象叫“头部效应”,少数热门商品占了绝大多数交互。如果行为数据均匀分布,算出来的推荐结果未必有说服力。脚本思路是:先生成一批热门商品ID,让大部分行为集中在这批商品上;再给随机用户分配浏览、加购、购买行为,时间戳随机分布在最近7天内。这样产出的数据集更接近真实场景,跑推荐算法的效果也更可信。
3. 推荐引擎:从协同过滤到Spark离线计算
3.1 为什么选协同过滤而不是深度学习
推荐算法那么多,为什么这个项目选协同过滤?一句话:在数据规模和项目阶段上,它性价比最高。深度学习模型动辄要特征工程、Embedding、GPU训练,演示项目很难玩转,而且解释性很差——用户问“为什么给我推这个”,你答不上来。协同过滤不一样,它的逻辑是人话:“和你相似的人喜欢什么,我也推给你”,或者“你喜欢的商品和另一个商品经常一起出现,所以我把另一个也推给你”。
基于用户的协同过滤适合用户数量比较小的场景,基于物品的协同过滤适合商品数量相对稳定、用户行为持续增长的场景。电商平台明显属于后者:用户不断增长,商品相对固定。所以我最终采用基于物品的协同过滤,算法算的是商品与商品之间的相似度,线上推荐时只要找到用户最近交互过的商品,再找这些商品的相似商品,就能组装出推荐列表。
计算相似度我用的是余弦相似度。核心思路是:把每个商品看作一个向量,向量维度是所有用户,向量值是用户与该商品的交互强度;两个商品越相似,它们共同被交互过的用户就越多,余弦夹角就越小。这一步在Spark里做,可以轻松扩展到几万个商品和几百万条行为数据。
3.2 离线计算+在线召回:Spark任务怎么设计
离线任务负责计算商品相似度和生成推荐列表,在线接口负责把算好的结果快速返回给前端。两者分开,既能保证推荐列表实时响应,又不用每次请求都去跑一遍矩阵运算。
Spark离线任务的流程大致如下:
- 从用户行为表读取行为数据,过滤掉异常和测试数据。
- 将浏览、收藏、加购、购买四类行为映射为不同权重,购买权重最高。
- 构建“用户-商品”交互矩阵,统计每个商品被交互过的用户集合。
- 计算商品之间的余弦相似度,过滤低于阈值的组合。
- 针对用户最近交互过的N个商品,召回相似商品,聚合排序。
- 取每个用户TopN结果,写入Redis或MySQL推荐结果表。
在线推荐接口的逻辑则简单得多:先根据userId从缓存里取推荐列表;缓存没有,就把商品热度榜将回来;同时异步触发一次离线任务刷新。这套“缓存优先、兜底降级”的设计,保证了接口即使在新用户没有历史行为时也有数据返回。
很多人担心协同过滤的大矩阵算不动,实际上热门商品截断就能解决大部分问题。我们不需要计算所有商品两两相似度,只需要关注用户交互过的那几万件商品。Spark任务在跑之前加一个过滤条件,比如“只保留交互次数超过5次的商品”,矩阵规模会小很多,计算时间从小时级降到分钟级。
3.3 推荐效果评估与调参心得
代码跑通不是终点,推荐好不好用还得量化。我采用的方式是把行为数据按时间切分:前7天做训练集,最后一天做测试集,然后看测试集里用户真正交互的商品,有没有出现在我们生成的推荐列表里。三个关键指标分别是准确率、召回率和覆盖率。
准确率理解起来最简单:推荐列表里的商品,有多少是用户真实交互过的。召回率则看用户交互过的商品里,有多少被推荐出来了。覆盖率代表推荐结果覆盖面,如果算法只推那几百个热门商品,覆盖率肯定低,用户体验也不会好。
我在调参过程中的几个实际经验值得分享:第一,行为权重不能拍脑袋,购买权重我给了浏览的5倍左右,加购和收藏介于中间;第二,相似度阈值太低会导致推荐列表混入大量弱相关商品,我最终设置在0.1到0.2之间,太低就过滤掉;第三,时间衰减很重要,用户三个月前买过的东西对当前推荐的影响应该远小于今天刚看的商品。加入时间衰减因子后,推荐列表的相关性肉眼可见地提升。别小看这些细节,推荐效果的差异往往就在这里拉开。
4. SpringCloud微服务治理与联调
4.1 Nacos、Gateway、Feign的配置笔记
微服务拆完了,如何让这几个服务互相发现、互相调用、统一对外,就要靠SpringCloud这一套组合了。第一步是启动Nacos,在控制台里建好命名空间和配置,每个服务的yml文件都放到配置中心,避免改个数据库地址还要逐个服务重启。
网关配置是微服务架构的入口,所有外部请求先经过网关,再由网关转发到具体服务。路由配置的核心写法如下:
spring: cloud: gateway: routes: - id: product-service uri: lb://product-service predicates: - Path=/api/product/** filters: - StripPrefix=1网关层还统一加了鉴权,除了登录接口,其他请求都要先校验JWT Token。这样做的好处是不用在每个服务里塞一套鉴权逻辑,改一处就全局生效。Redis在这里用来存Token黑名单,用户退出登录后Token立即失效,而不是等到自然过期。
服务间调用我用OpenFeign,接口定义跟写本地方法差不多,实际上底层帮我们处理了负载均衡和连接管理。
@FeignClient(name = "recommend-service", fallback = RecommendFallback.class) public interface RecommendClient { @GetMapping("/recommend/list") Result<List<Long>> getRecommendList(@RequestParam("userId") Long userId, @RequestParam("size") int size); }定义好Feign接口后,商品服务就可以像调用本地Service方法一样调用推荐服务。强烈建议每个Feign接口都配一个fallback降级类,哪怕只是返回默认兜底数据。因为微服务调用不可能百分之百成功,如果不配降级,推荐服务一旦抖动,商品服务也跟着报错,故障就像多米诺骨牌一样传导出去。
4.2 服务间通信与数据一致性的取舍
服务之间通信不可能全用同步调用。接口A调接口B,B再调C,链路一旦长起来,任何一个环节慢都会拖垮整条调用链。我的处理原则是:在线请求链路尽量短,能一步到位就不绕路;异步场景全部走MQ解耦。
采集服务抓完一批商品数据后,不需要立即通知所有服务。它把消息丢到RocketMQ,商品服务消费者收到后更新商品索引,推荐服务消费者收到后把“待刷新推荐”的标记写入Redis,后台任务再去刷推荐列表。整个过程服务间不直接等待,采集服务吞吐量再高,也不会拖累在线接口。
数据一致性方面,我一开始也考虑过引入Seata做分布式事务,但实际算了一笔账之后放弃了。这个项目涉及的商品入库、行为记录、推荐结果更新,都不是强一致场景——就算有几秒延迟,用户完全无感知。如果强行上分布式事务,不但增加开发和维护成本,还容易因为事务锁导致接口性能下降。最终用的是“本地消息表+最终一致”方案:核心业务先落库,异步任务再同步到其他服务,配合定时对账任务保证数据最终对齐。
这里有一个我在实践中踩过的坑:Feign调用默认超时时间是1秒,推荐服务因为离线任务正在跑,GC时间变长,响应超过1秒就直接超时报错。后来在配置里把readTimeout调到3秒,开启了重试,同时注意所有被调用的接口都要做幂等设计。否则重试一次就重复插入一条数据,问题反而更严重。
4.3 配置管理、熔断降级与部署形态
配置中心的价值平时看不出来,等到需要改推荐阈值的时候才会体会。相似度阈值、热门商品数量、Redis过期时间……这些配置全部放在Nacos里,改完直接发布,服务不用重启。我在演示的时候经常现场改一个参数,等几秒刷新页面就能看到推荐列表变化,这个效果对评委来说非常直观。
限流熔断我用了Sentinel,规则线上配置好后推送到各服务。可视化服务的统计接口最容易被打满,因为大屏数据一旦并发刷新,SQL聚合压力会很大。我给它配了QPS限流,超过阈值直接返回降级结果,而不是把数据库拖垮。推荐服务则配置了熔断规则,当错误率超过20%时,熔断器打开,直接走Fallback返回热门榜。
部署形态上,这套系统用Docker Compose编排最省心。MySQL、Redis、Elasticsearch、Nacos、网关、四个业务服务、前端Nginx,全部写进一个docker-compose.yml,一条命令拉起整个集群。本地开发时也可以用这种方式,保证所有人生理环境一致,不用花时间处理各种“在我电脑上明明能跑”的问题。
5. Vue前端与数据可视化联动
5.1 页面模块与路由设计
前端我选了Vue3+Vite+Element Plus,没有用Vue2,因为Vue3的组合式API写起来更清爽,Vite的冷启动速度在开发阶段非常提神。接口请求用Axios封装,统一处理后端返回的数据结构,如登录过期时跳转登录页。路由设计上,项目分成用户端、管理后台、数据大屏三大部分。
用户端的核心页面是推荐首页,接口根据userId返回个性化推荐商品列表,没登录时走系统默认推荐。商品详情页展示基本信息,同时利用“看了又看”接口推荐同类商品。管理后台则负责商品管理、分类管理和爬虫任务状态查看。数据大屏单独占一个路由,生产环境可以通过大屏展示当前平台的实时运营数据。
Vue Router的动态路由我用来做权限控制:用户角色不同,看到的菜单不同。管理员能看到“爬虫管理”,普通用户看不到。实现方式不复杂,前端根据用户角色过滤路由配置,路由守卫在跳转前做校验。但这块的坑在于刷新时路由会重置,所以用户信息一定要在Pinia里持久化,并且刷新后重新拉取用户角色,再动态添加路由。
5.2 ECharts可视化大屏与聚合接口
可视化大屏是这套系统最直观的展示环节。我用ECharts画了四类核心图表:商品分类占比饼图、价格区间分布柱状图、点击热度Top10排行榜、推荐点击转化率走势图。为了大屏效果,颜色主题用了深色系,数据刷新用WebSocket推送,爬虫抓取新商品或推荐任务刷新时,前端图表自动更新。
后端为了支撑大屏,单独设计了可视化服务,提供聚合接口。这个接口的做法不是一张表查出来直接用,而是组合几段SQL:
@GetMapping("/dashboard/overview") public Result<DashboardVO> overview() { // 1. 商品总量、用户总量、行为总量 // 2. 分类商品数量 group by category // 3. 价格区间分布 group by price range // 4. 热门商品Top10 order by interaction_count desc }这里容易踩的坑是SQL实时聚合性能差。数据量小时没感觉,一旦行为表到了几百万行,大屏每次刷新都要全表group by,MySQL CPU会直接飙高。我后来加了一层汇总表:定时任务每五分钟把统计数据写入汇总表,大屏接口只查汇总表,响应时间从几秒降到毫秒级。如果预算允许,还可以把大屏数据放到Redis里,再套一层缓存。
ECharts初始化时还有一个细节:图表组件的容器必须要有明确高度,不能是“0px继承”。我一开始把容器写到flex布局里,结果图表死活渲染不出来,打开控制台才发现容器高度是0。给每个图表容器设定固定高度,问题立刻解决。
5.3 前后端联调与部署避坑
前后端联调最大的障碍是跨域。开发阶段我让Vite代理请求到网关,配置里把/api前缀转发到8080端口,这样浏览器看到的请求是同源的。代码里这样写:
// vite.config.js server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }生产部署时,前端的dist目录直接用Nginx托管,Nginx再把/api请求反代到网关。这里有一个我踩过很深的坑:网关层和Nginx层都配置了跨域,导致浏览器发出OPTIONS预检请求时两边都加了一遍跨域头,直接报错“Access-Control-Allow-Origin重复”。最后的解决方案是只在网关层统一处理跨域,Nginx只做转发,不加跨域配置。
打包路径也要注意。Vue打包默认是绝对路径,部署在二级目录时资源全部404。我一般在vite.config.js里设置base: './',这样打包后资源路径变成相对路径,无论部署在根目录还是子目录都能正常加载。
6. 常见问题与实战排查记录
6.1 微服务故障:从一次雪崩说起
这个项目开发过程中让我印象最深的一次事故是服务雪崩。当时推荐服务正在跑一次全量离线任务,CPU几乎占满,商品服务通过Feign调用推荐接口,超时后线程并没有立刻释放,而是继续等待。随着并发请求越来越多,商品服务的线程池被打满,紧接着网关也出现大面积超时,整个系统几乎不可用。
排查过程花了不少时间,最后还是从Sentinel的监控面板上看到了端倪:推荐服务的响应时间在某个时间点之后直线上升,商品服务的线程活跃数同步飙升。解决办法分三步:第一步,在Feign调用上配置合理的超时时间和重试次数;第二步,给推荐服务配置Sentinel线程池隔离,即使推荐服务响应慢,也不能占满商品服务的线程;第三步,给推荐接口配置熔断降级,失败率超过阈值直接返回热门榜。
事后复盘,这套组合缺一不可。超时保证单个请求不会被无限拖住,隔离保证故障不跨服务传播,降级保证用户体验不归零。微服务架构下,防故障扩散比消灭故障更重要。
6.2 推荐冷启动与数据稀疏应对
推荐系统最头疼的问题永远是冷启动。新用户一条行为记录都没有,基于协同过滤的算法完全无法工作;新商品刚上架,没有任何用户交互,也不会被推荐出去。我在项目里用“兜底+加权”的组合逻辑处理了这两个问题。
新用户没有行为数据时,推荐服务直接返回商品热度榜。热度榜不是简单按销量排,而是综合销量、收藏量、最近点击量算出来的一个热度分。这样即使新用户第一次打开推荐页,看到的内容也是平台上比较受欢迎、质量相对可靠的商品。新商品上架后,给它在推荐权重里加一个“新品加权系数”,上架时间越短权重越高,保证新品有曝光机会。
数据稀疏是另一座大山。用户动辄几万个,但一个用户可能只有几条行为记录,构建出来的矩阵稀疏度超过95%。稀疏矩阵直接算相似度,结果会非常不稳定。我的做法是限制参与计算的商品范围:只保留行为量排名前10%的热门商品参与相似度矩阵计算,长尾商品用热度兜底。这样矩阵规模更小、计算速度更快,推荐结果也更有集中度。
6.3 爬虫、数据质量和大屏性能的坑
爬虫模块的常见问题主要集中在反爬和数据质量上。有些站点会检测请求频率,短时间请求太密集直接拒绝服务。我的应对很简单:控制频率、随机延时、设置合理请求头。这些是正常爬虫应该具备的基础素养。
数据质量的问题更隐蔽。比如价格字段“¥99.9元起”,直接转Double会变成99.9吗?不会,因为中间有中文字符。正则表达式清洗时要考虑到所有异常格式。还有一类情况:同一件商品,两个页面价格不一致,入库时就会出现同ID不同价格。我增加了按商品链接去重的规则,同一链接只保留最近一次采集结果。
大屏性能问题前面提过汇总表方案,但还有一个关联问题:大屏接口并发高时,MySQL连接池会被打满。解决方式很简单,给可视化服务配置独立的数据库连接池,参数调大一点,同时在网关层对大屏路由单独配置限流规则,超过设定并发直接拒绝,保护下游数据库。
最后再分享一段我实际操作中的体会:做这类全栈项目,最大的难点其实不是某个单独的技术点,而是把各个模块粘合在一起的能力。爬虫、算法、微服务、前端,每一块都有大量现成的教程,但很少有人讲它们之间如何协作、出问题时怎么排查。这个项目做完之后,我对“工程化”这个词的理解比写十个算法题都深刻。如果你也想动手做一个类似的项目,建议不要一上来就追求算法复杂度,先把一条最简链路跑通,再加微服务治理,再加可视化,每一步都稳扎稳打,最后你会发现它能扩展的方向远比想象中多。