基于Agent与SSE的岗位雷达:12分钟生成可验证薪资报告

发布时间:2026/11/2 8:15:20

基于Agent与SSE的岗位雷达:12分钟生成可验证薪资报告 1. 从投了三百份简历没回音说起这个雷达到底在解决什么去年年底帮一个学弟看简历他跟我说投了快三百份回音不到十个。我让他把投递记录拉出来一看问题特别明显他投的岗位里有将近一半的薪资区间跟他期望的差了三四千还有一批岗位的JD里写的技术栈跟他实际会的东西根本对不上。他不是不努力是压根没做筛选看到后端开发四个字就投。这件事让我意识到一个很现实的问题求职这件事信息筛选的成本被严重低估了。招聘平台上的岗位信息是海量的但真正跟你匹配的可能只有百分之几。你要做的不是多投而是精准投。而精准投的前提是你得先把这些岗位信息结构化地抓下来然后按自己的条件去过滤、去排序、去验证。我这次做的事情就是用Seed-2.1-pro-0915搭了一个岗位雷达。它的核心逻辑很简单输入你的求职条件城市、薪资下限、技术栈关键词、经验要求等它自动去抓取、解析、结构化岗位信息然后生成一份带薪资验证链接的报告。整个过程实测下来从触发到出报告12 分钟左右。报告里每一条薪资都不是干巴巴一个数字而是可以点开去看原始来源的。这个雷达适合谁如果你是正在找工作的开发者、产品、运营或者你只是想定期监控某个行业的岗位薪资变化这套东西都能用。它不依赖某个特定平台的接口核心是一套抓取—解析—结构化—验证—报告的流水线。下面我把整套东西拆开讲包括为什么这么设计、中间踩了哪些坑、以及你可以怎么复现。关键词里提到的Seed-2.1-pro-0915、Claude Code、Agent、Next.js、SSE这几个东西基本构成了这套系统的技术骨架。我会在后面的章节里逐个说清楚它们各自承担什么角色以及为什么是它们而不是别的方案。2. 为什么是雷达而不是爬虫架构选型的底层逻辑2.1 爬虫思维和雷达思维的区别大多数人做岗位信息聚合第一反应是写个爬虫定时跑把数据存数据库然后前端查。这个思路没错但它有个致命问题岗位信息是高度动态的。一个岗位可能今天挂出来明天就下架薪资可能随时调整JD 描述可能被 HR 反复修改。你用爬虫定时抓抓到的永远是某个时间点的快照而且你很难知道这个快照是不是还有效。雷达思维不一样。雷达的核心不是我抓了多少数据而是我能不能在需要的时候快速扫一遍当前的有效信号并且判断这个信号可不可信。所以这套系统的设计目标不是建一个大数据库而是按需触发、实时扫描、即时验证。具体到实现上我没有用传统的定时任务 数据库架构而是用了Agent 驱动 SSE 流式推送的方式。用户在前端填好条件点一下扫描后端启动一个 Agent 任务Agent 分步骤去执行先确定扫描范围再逐个抓取再解析结构化再做薪资验证最后生成报告。每一步的进度都通过 SSE 实时推给前端用户能看到正在扫描第 3 个来源已解析 47 条岗位正在验证薪资链接这样的实时状态。2.2 为什么用 Agent 而不是写死的流水线你可能会问这不就是个固定的流程吗为什么要用 Agent写死一个 pipeline 不就行了我一开始也是这么想的写了个固定的抓取脚本。但实际跑起来发现不同来源的岗位信息格式差异太大了。有的来源是标准的结构化数据直接能解析有的来源是 HTML 页面需要根据页面结构提取还有的来源返回的是非结构化的文本描述需要做语义理解才能提取出薪资、经验要求这些字段。如果用写死的 pipeline你得为每个来源写一套解析逻辑来源一多维护成本就爆炸。而 Agent 的好处是它可以根据当前拿到的内容动态决定下一步怎么处理。比如它拿到一段文本会先判断这是结构化数据还是非结构化描述然后选择对应的解析策略。这个判断逻辑不需要你写死Agent 自己会根据上下文做决策。当然Agent 也不是银弹。它的不确定性比写死的 pipeline 高所以我在关键节点上加了校验。比如薪资字段解析出来之后会做一个合理性检查如果解析出的薪资是负数或者超过了某个合理范围就标记为待人工确认而不是直接写进报告。2.3 Seed-2.1-pro-0915 在里面的角色Seed-2.1-pro-0915在这套系统里承担的是理解与决策的角色。具体来说它负责三件事第一意图理解。用户输入的条件往往是模糊的比如我想找北京的后端薪资别太低。Seed-2.1-pro-0915 需要把这种模糊表达转成结构化的查询条件城市北京岗位方向后端薪资下限某个合理值根据行业数据推断。第二内容解析。抓回来的岗位描述是五花八门的Seed-2.1-pro-0915 负责从中提取出结构化的字段岗位名称、公司、薪资区间、经验要求、技术栈、学历要求等。第三验证判断。薪资验证环节它需要判断一个薪资链接是否有效、链接里的薪资信息是否跟报告里的一致。我选 Seed-2.1-pro-0915 而不是别的模型主要原因是它在中文岗位描述的理解上表现比较稳。岗位 JD 里经常有一些行业黑话和缩写比如具备良好的抗压能力翻译加班多、有较强的自驱力翻译没人带你这些隐含信息如果能被识别出来对求职者的参考价值是很大的。3. 12 分钟出报告的背后SSE 流式推送怎么扛住长任务3.1 为什么不用轮询这套系统里一次扫描任务可能要跑十几分钟。如果前端用轮询的方式去问后端好了没会有两个问题一是轮询间隔不好定太短了浪费请求太长了用户觉得卡二是轮询拿不到中间过程用户只能看到转圈和完成两个状态体验很差。所以我用了SSEServer-Sent Events。SSE 的本质是服务器主动往客户端推消息客户端只需要建立一个长连接后面所有进度更新都是服务器推过来的。这样用户能看到实时的进度而且不需要频繁发请求。SSE 和 WebSocket 的区别这里顺带说一下WebSocket 是双向的SSE 是单向的服务器到客户端。对于服务器推进度给客户端这个场景SSE 足够了而且 SSE 基于 HTTP实现和调试都比 WebSocket 简单。你不需要引入额外的协议库浏览器原生就支持EventSource。3.2 SSE 长连接的坑idle timeoutSSE 用起来简单但有个坑特别容易踩idle timeout。就是如果服务器长时间不往客户端推消息中间的网络设备负载均衡、代理、网关可能会认为这个连接已经死了直接把它断掉。你会在控制台看到类似stream disconnected before completion: idle timeout waiting for sse这样的报错。我一开始就踩了这个坑。扫描任务跑到第 8 分钟左右前端突然收不到消息了一看控制台连接被断了。原因是中间有几分钟 Agent 在做一些耗时的解析没有往 SSE 通道里推任何东西超过了网关的 idle 超时时间。解决办法有两个我两个都用了第一个是心跳保活。在服务端起一个定时器每隔 15 秒往 SSE 通道里推一个注释消息:\n\n这个注释消息不会触发前端的onmessage事件但能保持连接活跃。代码大概长这样const heartbeat setInterval(() { res.write(: heartbeat\n\n); }, 15000); req.on(close, () { clearInterval(heartbeat); });第二个是把长任务拆成多个可推送的节点。不要让 Agent 闷头跑几分钟不吭声而是在每个子步骤完成时都推一条进度消息。比如已确定扫描范围已抓取第 1 个来源已解析 20 条岗位等等。这样既解决了 idle timeout又提升了用户体验。3.3 前端怎么消费 SSE前端这块我用的是Next.js。Next.js 的 App Router 里消费 SSE 有两种方式一种是在客户端组件里直接用EventSource另一种是通过 Route Handler 做一层代理。我选的是直接在客户端组件里用EventSource因为这样最简单少一层转发。核心代码大概是这样const eventSource new EventSource(/api/scan/stream?taskId taskId); eventSource.onmessage (event) { const data JSON.parse(event.data); if (data.type progress) { setProgress(data.payload); } else if (data.type result) { setReport(data.payload); eventSource.close(); } }; eventSource.onerror () { eventSource.close(); // 触发重连或提示用户 };这里有个细节要注意EventSource默认会在连接断开后自动重连。如果你的任务已经完成了一定要记得手动close()否则它会一直重连浪费资源。4. 薪资可点开验证是怎么做到的4.1 为什么薪资验证是这套系统的核心价值岗位信息聚合这件事市面上不缺工具。但大多数工具的问题是你不敢信。它给你一个薪资数字你不知道这个数字是从哪来的是岗位标称的薪资还是某个用户匿名爆料的还是系统瞎猜的。信息不可验证价值就大打折扣。所以我在设计这套系统的时候把薪资可验证作为核心指标。报告里每一条薪资都必须附带一个可点击的验证链接点进去能看到这个薪资的原始来源。如果某个薪资找不到可靠来源那它就不会出现在报告里或者会被明确标记为未验证。4.2 验证链路的设计验证链路分三步第一步来源标记。Agent 在抓取岗位信息的时候会同时记录这条信息的来源标识。这个标识可能是一个 URL也可能是一个来源编号。关键是从抓取的那一刻起这条信息就跟它的来源绑定了后面不管怎么处理来源标识都不会丢。第二步薪资提取与归一化。不同来源的薪资表达方式不一样有的是15k-25k有的是15-25K·13薪有的是年薪 20-30 万。Agent 需要把这些统一归一化成可比较的格式。我用的归一化规则是统一转成月薪区间 薪资月数。比如15-25K·13薪就归一化成月薪 15k-25k年薪月数 13。第三步链接有效性校验。生成报告之前系统会对每条薪资的验证链接做一次有效性检查。如果链接已经失效比如岗位下架了这条薪资会被标记为来源已失效但仍然保留在报告里只是加一个警示标记。这样做的好处是用户能看到这个岗位可能已经没了但它的薪资信息曾经是这样的而不是直接消失。4.3 验证环节的实操心得这里分享几个我在做验证时踩过的坑第一个坑是链接重定向。有些来源的链接会经过多次重定向你直接请求原始链接可能拿到的是一个中间页而不是最终的岗位详情页。解决办法是在请求的时候跟随重定向拿到最终的 URL 再做校验。第二个坑是动态渲染页面。有些岗位详情页是前端渲染的你直接请求 HTML 拿到的是一片空白薪资信息是后面通过 JavaScript 加载的。这种情况要么用无头浏览器去渲染要么找到它背后的数据接口直接请求。我优先选后者因为无头浏览器太重了跑一次扫描要开一堆浏览器实例资源消耗太大。第三个坑是薪资信息的时效性。同一个岗位今天看到的薪资和一周后看到的可能不一样。所以我在报告里会标注每条薪资的抓取时间让用户知道这个数字是什么时候的。5. 从零复现这套雷达的最小可行版本5.1 环境准备与依赖选择如果你想复现这套东西最小可行版本需要这些东西一个能跑 Node.js 的环境我用的是 Node 20Next.js 项目App Router一个能调用 Seed-2.1-pro-0915 的 API 通道一个 SSE 的服务端实现Next.js 的安装就不展开了官方文档很清楚。这里重点说一下 SSE 服务端的实现位置。在 Next.js 的 App Router 里SSE 要放在 Route Handler 里而且必须设置runtime nodejs不能用 Edge Runtime因为 Edge Runtime 不支持长连接。// app/api/scan/stream/route.js export const runtime nodejs; export const dynamic force-dynamic; export async function GET(request) { const encoder new TextEncoder(); const stream new ReadableStream({ async start(controller) { // 这里启动 Agent 任务并通过 controller.enqueue 推送消息 } }); return new Response(stream, { headers: { Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive, }, }); }5.2 Agent 任务的分步设计Agent 任务我拆成了五个步骤每个步骤完成都会推一条 SSE 消息条件解析把用户输入的自然语言条件转成结构化查询范围确定根据条件确定要扫描哪些来源信息抓取逐个来源抓取岗位信息结构化解析把抓取到的内容解析成结构化字段薪资验证与报告生成验证薪资链接生成最终报告每个步骤内部还可以再细分比如信息抓取这一步每抓完一个来源就推一条消息。这样用户能看到实时的进度不会觉得系统卡死了。5.3 并发控制的取舍抓取环节有个绕不开的问题并发。你如果串行抓12 分钟可能变成 30 分钟你如果并发太高又容易被来源方限流甚至封禁。我的做法是分级并发。对于不同的来源设置不同的并发上限。对于响应快、限制松的来源并发可以高一点对于响应慢、限制严的来源并发压到 1 或者 2。同时加一个全局的速率限制保证整体请求频率不会太高。具体实现上我用了一个简单的信号量来控制并发class Semaphore { constructor(max) { this.max max; this.current 0; this.queue []; } async acquire() { if (this.current this.max) { this.current; return; } await new Promise(resolve this.queue.push(resolve)); this.current; } release() { this.current--; if (this.queue.length 0) { const next this.queue.shift(); next(); } } }这个信号量虽然简单但足够用了。关键是你要根据实际来源的反馈去调整max的值没有一个通用的数字。6. 实测中的意外情况与处理经验6.1 报告生成到一半 Agent 卡住了这是我在测试阶段遇到的最头疼的问题。Agent 跑到薪资验证这一步有时候会卡住不动SSE 也不推消息了前端就一直转圈。排查下来发现是某个来源的请求一直没有返回也没有超时。Agent 在等这个请求的结果就一直卡在那里。解决办法是给所有外部请求加超时const controller new AbortController(); const timeout setTimeout(() controller.abort(), 10000); try { const res await fetch(url, { signal: controller.signal }); // 处理结果 } catch (e) { // 超时或出错标记这条为验证失败 } finally { clearTimeout(timeout); }加了超时之后即使某个来源挂了整个任务也不会卡死最多是这条薪资标记为验证失败其他部分照常进行。6.2 解析结果的不一致性Agent 解析岗位信息的时候同一个字段在不同来源下的解析结果可能不一致。比如经验要求有的来源写3-5年有的写三年以上有的写资深。如果不做归一化报告里就会出现各种奇怪的表达。我的处理方式是定义一个字段的标准表达然后在 Agent 的解析指令里明确要求输出标准表达。比如经验要求统一输出成X-Y年的格式如果原文是三年以上就输出3年及以上。这个归一化规则需要在 Agent 的 prompt 里写清楚并且给出几个示例。6.3 用户看到报告后的反馈闭环报告生成出来不是终点。我在报告页面加了一个简单的反馈机制用户可以对每条岗位信息标记有用或没用。这些反馈数据会回流到系统里用来优化后续的扫描策略。比如某个来源的岗位信息被大量标记为没用那下次扫描时就会降低这个来源的权重。这个反馈闭环看起来简单但实际用起来效果很好。因为岗位信息的质量是动态变化的今天有用的来源明天可能就全是垃圾信息了。有了反馈数据系统能自己调整。7. 这套东西还能怎么扩展7.1 从岗位雷达到行业薪资监控现在这套系统是围绕个人求职场景做的但它的核心能力——抓取、解析、结构化、验证——是可以复用的。如果你把输入条件从个人求职条件改成某个行业 某个岗位方向它就能变成一个行业薪资监控工具。定期跑一次把结果存下来就能看到某个岗位的薪资随时间的变化趋势。这个扩展的关键是历史数据的存储和对比。你需要把每次扫描的结果存下来然后做一个时间序列的对比。存储这块我用的是简单的 JSON 文件因为数据量不大没必要上数据库。如果数据量大了再考虑换 SQLite 或者别的。7.2 多 Agent 协作的可能性现在这套系统是单 Agent 串行执行的。如果扫描范围很大比如要扫几十个来源单 Agent 就会很慢。这时候可以考虑多 Agent 协作一个调度 Agent负责分配任务多个执行 Agent并行去抓取和解析。多 Agent 协作的难点在于状态同步和结果汇总。调度 Agent 需要知道每个执行 Agent 的进度执行 Agent 之间不能重复抓取同一个来源。这块我还在实验阶段目前的想法是用一个共享的任务队列来做协调调度 Agent 往队列里放任务执行 Agent 从队列里取任务。7.3 报告的可视化增强现在的报告是列表形式的每条岗位信息一行带薪资和验证链接。如果要做可视化增强可以考虑加一个薪资分布的直方图让用户一眼看到这个条件下薪资主要集中在哪个区间。这个用简单的图表库就能做不需要太复杂。不过我要提醒一句可视化是锦上添花不是核心。这套系统的核心价值在于信息的准确性和可验证性而不是图表好不好看。如果薪资数据本身不准图表做得再漂亮也没用。8. 一些掏心窝子的实操建议如果你打算自己动手做一套类似的东西我有几个建议第一先跑通最小闭环再优化。不要一上来就想着做多 Agent、做可视化、做反馈闭环。先把输入条件—抓取—解析—出报告这个最小闭环跑通哪怕只支持一个来源哪怕报告很简陋。跑通之后你才知道真正的瓶颈在哪里。第二验证环节不能省。我见过太多岗位聚合工具数据来源不明薪资数字随便填。这种工具做出来用户用一次就不会再用第二次。验证环节虽然麻烦但它是建立信任的关键。第三给 Agent 的指令要具体。Agent 不是万能的你给它的指令越模糊它的输出就越不可控。比如解析岗位信息这个指令就太模糊了你应该说从以下文本中提取岗位名称、公司名称、薪资区间、经验要求、技术栈薪资区间统一输出成 Xk-Yk 的格式如果原文没有薪资信息就输出未标注。第四做好错误处理。外部请求会失败解析会出错链接会失效。这些都不是异常情况而是常态。你的系统要能优雅地处理这些错误而不是一遇到错误就整个崩掉。第五别忽视用户体验。12 分钟的出报告时间如果用户全程看着转圈体验是很差的。但如果你把中间过程通过 SSE 实时推给用户让用户看到正在扫描第 3 个来源已解析 47 条岗位用户就会觉得系统在干活等待也变得可以接受。这套东西我前后折腾了大概两周中间踩了不少坑但跑通之后确实好用。现在我每隔一段时间就会跑一次看看目标岗位的薪资变化心里有个数。如果你也在找工作或者你对岗位信息聚合这件事感兴趣希望这篇东西能给你一些参考。
延伸阅读

更多相关文章

2026/11/2 8:10:20

智能数据分析工具如何助力人文社科研究

1. 项目概述:打破学科壁垒的数据分析革命当我在大学图书馆看到文科研究生对着Excel表格抓耳挠腮时,突然意识到一个残酷现实:在实证研究成为主流的今天,数据能力缺失正在扼杀多少人文社科学者的学术潜力。这正是宏智树AI最新推出的…

2026/11/2 8:10:20

2026大模型学习全景图:工具、框架与实战路线

2026年聊大模型学习,和三四年前完全是两个画风。当年大家还在纠结“怎么把环境跑通”,现在的问题早就变成了“生态太庞大,不知道把精力放在哪”。终端工具、微调框架、Agent中间件、部署方案、垂直领域路线,每一块都有大量选择&am…

2026/11/2 8:10:20

分库分表本质与落地决策指南:从瓶颈识别到分片策略

1. 这不是“加机器”能解决的问题:分库分表到底在解决什么?你有没有遇到过这样的场景:某天凌晨三点,监控告警疯狂弹窗——数据库CPU飙到98%,慢查询日志里全是“SELECT * FROM order WHERE user_id ?”这种语句&#…

2026/11/2 11:46:10

Cobalt Strike Beacon隐蔽技术:BeaconGate+UDRL+Sleepmask实战

1. 项目概述在红队攻防对抗中,Cobalt Strike(简称CS)作为主流渗透测试框架,其Beacon载荷的隐蔽性直接决定了攻击行动的成败。最近我在实际项目中遇到了一个棘手问题:传统睡眠混淆技术已逐渐被EDR产品识别,急…

2026/11/2 11:46:10

AI辅助测试用例编写:提示词设计与实践

1. 为什么需要AI辅助测试用例编写 在软件测试领域,编写高质量的测试用例往往需要耗费大量时间。根据业界统计,测试人员平均每天要花费40%的工作时间在用例编写和维护上。传统手工编写方式存在三个明显痛点: 首先,测试场景覆盖率与…

2026/11/2 11:46:10

工业自动化TVA选型:避免误区与全生命周期成本优化

1. 项目概述:TVA选型的核心痛点解析 在工业自动化领域,终端执行器(Terminal Valve Actuator,简称TVA)的选型决策常常陷入两个极端:要么过度关注技术参数表中的数字竞赛,要么单纯追求采购成本最低…

2026/11/2 11:46:10

HTML转PDF实战:渲染引擎选型、分页控制与字体避坑指南

简介:这份资源面向.NET开发者与文档处理场景,聚焦HTML到PDF的转换需求,提供基于Aspose.Pdf库的完整示例工程。压缩包共32个文件,约10.62MB,以cs源码、aspx页面、dll程序集、config配置及pdf示例文档为主,另…

2026/11/2 11:46:10

CTFshow Web安全实战:Base64编码与HTTP头注入解析

1. CTFshow Web安全入门实战解析作为一名长期活跃在CTF赛场的Web安全研究员,我经常遇到新手对CTFshow平台上的Web题目感到困惑。今天我们就来深度剖析CTFshow《Web应用安全与防护》第一章的核心考点,通过6个典型场景带你掌握Web安全基础技能。1.1 Base64…

2026/11/2 11:41:09

城市运管服平台安全架构与关键技术解析

1. 城市运管服平台的安全定位与核心价值 城市运管服平台作为现代城市治理的"数字中枢",其安全防线直接决定了城市运行的底线保障能力。这个看似抽象的概念,在实际场景中体现为:当暴雨导致城市内涝时,平台能否实时感知积…

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/2 6:39:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

工业级SPI隔离设计:硬件抗扰与信号完整性实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

PLFM_RADAR:主动探测数据通道,构建低误报的数据感知系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

工业SPI隔离设计:MAX14431与STM32F746硬件抗干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/2 6:39:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑