找工作时最耗时的是什么?不是投简历,是"判断该不该投"和"怎么改简历"。我做了几年RPA项目,一直觉得纯RPA做求职自动化有个硬伤:它能帮你把100个岗位从招聘网站上一页页翻出来,但它没法告诉你这100个岗位里哪些跟你的技术栈匹配,更没法针对每个岗位把简历里的项目经验改得正中要害。这半年我把大语言模型接进RPA流程里,做了一个"智能求职自动化系统"的实战项目,整个过程踩了不少坑,也总结了一套比较稳的搭建方案,今天把这些经验一次性写出来。
这个系统做的东西可以用一条链路说清楚:定时去招聘平台抓取新发布的职位信息 → 用LLM解析每个岗位的职责描述和要求 → 跟个人简历做匹配度评估 → 对高分岗位自动生成定制化简历版本和求职信 → RPA自动填写投递表单 → 投递完成后回写结果并沉淀到数据库。核心的思路就是一句话:RPA负责"手"——所有页面操作、表单填写、数据抓取都交给它;LLM负责"脑"——判断岗位值不值得投、简历怎么改、求职信怎么写。手脑配合,整个求职流程才真正跑得起来。
这篇文章适合两类人看:一类是做RPA开发的,想知道怎么用LLM给现有流程加上"智能判断"能力;另一类是有点编程基础、想给自己搭一套求职提效工具的开发者。我会把架构设计、关键代码、踩坑记录全部摊开讲,包括LLM的Prompt怎么设计、RPA怎么选型、投递频率怎么控制才算安全,不会藏着掖着。
1. 项目概述:为什么"RPA+LLM"组合是求职自动化的正解
1.1 传统求职自动化方案的局限性
先说清楚我之前用纯RPA做求职自动化时遇到的问题。最早我写了一套脚本,功能很简单:每天定时去几个招聘平台搜索关键词,把搜索结果里的岗位名称、公司、薪资、链接抓下来,按发布时间排序存到表格里。说实话这套东西用起来确实省事,每天早上打开表格就能看到前一天新增了哪些岗位,不用再手动刷页面。
但用了一段时间我发现一个尴尬的情况:表格里躺着200个岗位,我还是要一个一个点进去看岗位要求,判断自己合不合适。有些岗位标题写的是"Python开发",点进去发现要求精通C++和嵌入式开发,明显是标题和内容对不上;有些岗位要求写得含糊,比如"熟悉常用框架",但实际项目用的是很冷门的技术栈。这些判断靠RPA完全做不了,因为它只能按照写死的规则抓数据,理解不了语义。
后来我又尝试做规则匹配,就是维护一个关键词表,用岗位描述里出现的关键词做加权打分。比如出现"Python"加2分,出现"Django"加3分,出现"K8s"加1分。这个方法比纯手动强一些,但还是有硬伤:关键词同义变体太多了,"熟悉Python"和"扎实的Python功底"语义几乎一样,但规则匹配会给它们完全相同的分数;更麻烦的是,规则没法处理否定和条件,比如"不需要Python经验"这种描述,规则照样会加分。规则表还越维护越复杂,今天加一个词,明天加一个词,最后我自己都懒得改了。
1.2 LLM补上了最关键的语义理解环节
LLM进场之后,情况完全变了。我最初只是试着把岗位描述扔给大模型,让它帮我分析一下这个岗位的核心要求,结果发现它不仅能抓住岗位描述里的显性要求,还能推断出隐性要求。比如一份JD里写"负责支付系统的日常迭代",LLM能结合岗位所属行业推断出需要了解支付清结算流程、对账逻辑这些背景知识,这个推断能力是关键词规则完全不具备的。
这意味着我可以把"匹配评估"这个环节彻底交给LLM:把完整JD文本和我的简历摘要一起发给它,让它从技能匹配度、经验匹配度、行业相关性几个维度打分,并给出投递建议。输出格式用JSON结构化,方便后面的流程用代码处理。这一步做完,整个系统的智能程度直接上了一个台阶。
其实RPA和LLM的关系像职场里的"执行"和"决策"两个角色。RPA是那个把脏活累活全干了的执行者,但它没有判断力,你让它投哪个它就投哪个;LLM是那个坐在办公室里看报告的决策者,分析能力很强,但没有手,没法自己点鼠标、填表单。所以最合理的架构不是二选一,而是让决策者指挥执行者,LLM把"投哪些、怎么改"的结论传给RPA,RPA老老实实去干活,形成一个完整的闭环。
1.3 这个系统最终解决了什么问题
整套系统跑起来之后,我最大的感受是每天的"决策成本"被大幅压缩。以前我每天早上要花半小时到一小时逐条看岗位、判断值不值得投,现在系统晚上自动跑完一轮,早上我只需要看一份匹配度排名表,重点浏览评分靠前的十几个岗位,决定要不要调整投递策略。从"全量人工判断"变成"机器筛选+人工复核",效率提升非常明显。
而且系统带来的另一个好处是投递质量更高。以前手动投简历,一份简历走天下,投10个岗位用的都是同一版内容;现在系统会根据每个岗位的JD自动调整项目经验的描述顺序和关键词密度,让简历更贴合目标岗位的要求。虽然不能说每投必中,但面试邀约率确实比广撒网时代高了不少。
2. 系统架构设计与技术选型
2.1 整体流程拆解:一个完整的自动化闭环
这个系统的核心流程我拆成了七步,每一环都有明确的输入输出:
- 定时触发:用定时任务每天早上和晚上各触发一次流程。
- 职位采集:RPA登录招聘平台,按预设关键词搜索,翻页抓取搜索结果页和详情页的数据。
- 数据清洗:把抓下来的HTML文本转成结构化字段,包括岗位名称、公司、薪资范围、经验要求、JD全文文本。
- LLM分析:对每条JD做语义解析,提取核心技术要求、软性要求、加分项,并生成结构化JSON。
- 匹配评估:把解析结果和简历摘要合并,让LLM给出匹配度评分和投递建议。
- 内容生成:对评分达标的岗位,生成定制化简历版本和求职信。
- 自动投递+结果回写:RPA打开投递表单,填入定制内容,提交后在系统里记录投递时间、岗位和简历版本号。
这个流程最需要想清楚的是第4步和第5步为什么要分开。我最初设计的时候考虑过把"解析JD"和"匹配评估"合并成一次LLM调用,省一次API请求。但实际测试发现,合并后模型容易把JD解析和匹配混在一起,输出的JSON字段经常缺项,而且每次重新匹配都要重复解析一遍同样的JD文本,浪费token。拆开后,JD解析结果可以缓存复用,一份JD只需要解析一次,后面反复匹配评估都不需要重新花这个钱。这个设计后来帮我省了不少成本。
2.2 技术选型:RPA工具、LLM服务和数据存储怎么定
先说RPA部分。市面上的RPA工具分两类:一类是商业RPA平台,带可视化流程编辑器和组件库,适合非技术人员,但对于我们要跟LLM做深度集成、动不动要写自定义代码的场景反而有点笨重;另一类是浏览器自动化框架,本质上就是用代码直接操作浏览器,灵活度和可控性都更高。我最终选择的是"浏览器自动化框架+少量自建封装"的路线,原因有三个:一是我们能直接拿到页面的DOM结构,选择器和数据提取都是自己控制,出错时排查起来更方便;二是和Python生态的LLM库、数据处理库天然配套,不用在RPA平台和执行脚本之间来回倒数据;三是投递表单千奇百怪,商业RPA的通用组件经常识别不了页面元素,自己写定位逻辑反而更可靠。
当然这个选择也要付出代价,比如招聘平台改版时所有选择器都可能失效,你得自己维护一套元素定位规则;再比如需要处理登录态保持、验证码识别这些浏览器自动化特有的问题。这部分我在后面的"常见问题"章节详细说。
LLM服务的选择上,我用了两类模型配合。日常的JD解析、匹配评估、简历改写,用的是公有云的大模型API,效果稳定、速度也够快;另外还本地部署了一个参数量较小的开源模型做备用和数据脱敏预处理,在涉及个人敏感信息的时候先在本地过滤一遍,避免把过多隐私内容直接送给公有云API。公有云API的调用我做了统一封装,留好接口,方便以后换模型供应商不用改业务代码。
数据存储就比较简单了,全部落在一个SQLite数据库里。岗位信息表存唯一标识、来源平台、岗位名称、公司、JD原文、解析结果JSON;投递记录表存每次投递的状态、使用的简历版本、匹配分;简历版本表存每次LLM生成的定制简历内容,方便后续追溯"我投这个岗位的时候用的是什么简历版本"。数据库设计不用过度复杂,几张表之间用岗位ID关联就够了。
2.3 数据结构设计:LLM输出如何跟RPA流程耦合
这是整个项目里最容易翻车的地方。LLM返回的是自然语言加JSON,RPA需要的是明确的执行指令,两者之间的桥梁就是数据结构。我的做法是给LLM定义一套严格的输出Schema,所有分析结果都要求返回固定格式的JSON,字段包括:required_skills(数组,每个元素含技能名和重要度)、preferred_qualifications(数组)、min_experience_years(数字)、match_score(0到100的整数)、match_reasons(数组)、suggestion(枚举值:强烈推荐/推荐/观望/不推荐)。为了让模型输出稳定,我在Prompt里给了few-shot示例,并且在代码层做了兜底解析:如果返回的JSON解析失败,就用正则提取关键字段,再失败就跳过这条JD,绝不阻塞整个流程。
这套结构的好处是,RPA流程完全不用关心LLM内部怎么理解JD,它只需要读JSON里的match_score字段,大于70就走投递分支,小于70就走观望分支,逻辑非常干净。我后来还额外加了一层"置信度"字段,让模型在JD文本内容太少、明显分析依据不足的时候主动标记低置信度,这类岗位系统会转入人工处理队列,避免伪智能耽误事。
3. 核心模块详解:从JD解析到自动投递的实现要点
3.1 职位采集模块:不只是"抓下来"那么简单
职位采集是整个流程的起点,也是最容易被低估的一步。很多新手写爬虫觉得只要用选择器把标题和JD文本抓下来就完事了,实际上坑非常多。招聘平台普遍有登录态校验,未登录状态下要么看不到完整JD,要么JD被折叠需要额外点击;搜索结果列表页往往只显示摘要,完整JD要跳到详情页;详情页的JD内容有些直接渲染在HTML里,有些是异步加载的,需要等待接口返回。
我的处理方式是三步走:第一步,维护一个持久化的浏览器会话,首次手动扫码登录后保存Cookie和会话状态,后续自动启动时优先复用会话,避免每天重新登录;第二步,搜索任务按照关键词+城市+页数参数组合执行,每抓完一页提取岗位链接列表,然后逐个打开详情页抓完整JD;第三步,所有抓取结果在入库前做去重和增量判断,岗位ID已存在且JD文本没变化的直接跳过,避免重复调用LLM浪费成本。
采集频率也要讲究。我实测下来,大多数招聘平台的搜索结果页翻页速度控制在每2到3秒一页比较安全,详情页每5到8秒一个,整体频率大约每分钟访问10到15个页面。这个频率下运行几周没有出现过封禁,但我也见过有人图快把间隔调到1秒以内,结果第二天账号就被限制了搜索功能。另外,平台方可能随时调整页面结构,我习惯在采集模块里加一个"页面结构自检":如果连续三个页面的抓取结果为空或者字段缺失率异常,就自动暂停流程并发通知,等人工确认页面是不是改版了再恢复。
3.2 JD解析模块:Prompt设计的核心方法论
JD解析是整个系统里Prompt最值得打磨的地方。我第一版Prompt写得非常随意,就跟模型说"请解析这段JD",结果输出的格式五花八门,有的给表格、有的给列表、有的给JSON但是字段名不一致,解析流程根本没法稳定消费。后来我总结了一套适用于所有"信息抽取类"LLM任务的Prompt模板,包含四个核心部分:角色定义、输入说明、输出约束、示例。
角色定义要具体,比如"你是一名资深技术招聘顾问,擅长从岗位描述中提取结构化招聘要求",这比"你是AI助手"效果好得多。输入说明要明确告知JD文本从哪里开始到哪里结束,防止模型把Prompt里的示例也当作输入内容。输出约束是重点,必须明确要求只输出JSON、字段名不可改变、缺失信息用null填充、不要额外解释。最后一定给一个完整的few-shot示例,让模型照着示例的格式走。下面这段是我JD解析模块的核心Prompt,可以拿去改:
你是一名资深技术招聘顾问,负责从岗位描述中提取结构化招聘要求。 输入是一段招聘JD文本,从===JD_START===到===JD_END===之间。 请严格按照以下JSON Schema输出,不要输出任何额外内容: { "job_title": "岗位名称", "required_skills": [{"skill": "技能名", "level": "等级:基础/熟悉/精通", "important": true或false}], "preferred_qualifications": ["加分项1", "加分项2"], "min_experience_years": 数字, "education_requirement": "学历要求或null", "responsibilities": ["核心职责1", "核心职责2"], "industry_context": "行业/业务背景推断", "confidence": 0到1的小数 } ===JD_START=== {jd_text} ===JD_END===这里有几个细节值得说。第一,required_skills里面每一项都要给出level和important,因为后面做匹配评估时要区分"硬性要求"和"软性要求",硬性要求不符直接一票否决。第二,min_experience_years我明确要求必须是数字,如果JD里写"3-5年"就让模型取中间值4,这比让模型自由发挥要稳定得多。第三,industry_context是我后来加的字段,让模型推断岗位背后的业务场景,匹配评估阶段会拿这个和简历中的项目背景做比对。
3.3 匹配评估模块:多维度打分与投递决策
匹配评估我采用了两阶段策略。第一阶段是硬性条件过滤,完全用代码判断,不依赖LLM。比如JD里明确要求5年以上经验,简历只有2年,直接标记为不推荐,不用浪费一次大模型调用;再比如要求全日制本科及以上,简历是专科,同样一票否决。硬性条件过滤放在LLM前面,能过滤掉大约30%的明显不匹配岗位,Long-term看成本节省效果非常明显。
第二阶段是语义匹配评分。把JD解析结果和简历摘要一起发送给LLM,让它从技能覆盖度、项目经验相关度、行业背景匹配度、软性素质匹配度四个维度分别打分,最后加权算总分。我的权重设定是技能40%、项目经验30%、行业背景20%、软性素质10%,这个比例可以根据自己的求职方向调整。Prompt要求模型不仅给分数,还要给出具体的匹配理由和不匹配风险,这些文本会跟在分数后面展示,方便我在早上看报告时快速判断系统给的分数是否合理。
决策阈值的设定要特别注意。我一开始的阈值是60分就投,结果投出去的简历里混了不少明显不合适的岗位,原因是有些JD写得比较宽泛,模型给的分数虚高。后来我加了两条辅助规则:一是硬性要求里important=true的技能只要有一项缺失,总分上限直接扣20分;二是要求模型对每个岗位输出"最大风险因素"字段,如果风险因素属于"薪资严重低于预期""公司规模过小"这类非技术因素,即使分数达标也转入人工确认队列。加完这两条规则之后,投递准确率明显提升,面试邀约率从原来的6%左右提升到了接近12%。
3.4 简历定制与求职信生成:最容易出彩也最容易翻车
简历定制是系统里对体验影响最直接的一个模块,也是我迭代最多的地方。最开始我让LLM直接生成完整简历,发现效果很差:模型经常会编造不存在的项目细节,甚至把别人的技术栈安到我头上。比如我对某块业务的描述是"参与过订单系统的性能优化",模型会在生成时扩写成"主导了订单系统架构重构,将QPS从2000提升到20000",这种内容拿去面试一追问就露馅。
后来我调整了策略,不再让LLM"生成"简历,而是让它"改写"和"重排"已有的真实素材。具体做法是:维护一个素材库,里面按项目记录我真实做过的技术点、解决的问题、取得的效果,LLM只允许从素材库提取相关内容,根据目标岗位进行调整,包括调整项目描述的顺序(跟岗位要求最相关的放前面)、强化某些关键词的表述密度、把通用描述改成贴近目标岗位行话的表达,但绝对不允许新增素材库里不存在的业绩数据。生成完之后,我还会额外加一个"事实核验"步骤,让模型检查输出文本里每一项事实是否都能在素材库中找到出处,找不到就删掉。
求职信生成相对简单,模板化程度更高。我准备了三个版本的框架模板:详细版(适合校招或转行)、项目版(适合社招,重点讲项目经验)、简洁版(适合内推或猎头渠道),LLM在模板框架内根据JD做填空和重写。求职信的核心是首段和末段,首段要直接点明"我应聘的岗位+我为什么合适",末段要明确"期待下一步沟通",中间段从素材库抽取2到3个跟JD最相关的项目做简要展示。模板框架保证了基本结构不出错,LLM负责让语言表达更贴近目标岗位的语境。
3.5 自动投递与结果跟踪:最后一个环节往往最脆弱
自动投递是RPA最擅长的领域,但也是整个系统里出问题最多的地方。招聘平台的投递表单形态完全不一样,有的用弹窗、有的是独立页面、有的是多步向导,有的把"投递"按钮藏在"更多操作"菜单里面。我维护了一套表单元素定位规则库,按平台、表单类型、元素特征分层管理,定位逻辑失败时自动降级:先是CSS选择器,然后是XPath,再然后是基于文本内容的模糊匹配,实在找不到就截图留存并标记为待人工处理。
投递完成后的结果跟踪也很重要。系统会在投递记录里保存岗位ID、投递时间、使用简历版本、求职信版本、匹配分,以及投递时的页面快照URL。这些数据一方面用于复盘什么类型的岗位转化率高,另一方面也是合规自查的依据。我还写了一个简单的数据看板,按周汇总投递数、邀约数、转化率,配合匹配分区间分析,能直观看出哪个分数段的投递最有价值,反过来再调优匹配评估的阈值。
4. 实操过程记录:关键代码与参数调优
4.1 环境准备与项目结构
整个项目用Python实现,依赖主要包括浏览器自动化、LLM调用、数据存储、定时调度这几个部分。项目目录按照功能模块划分:
job_bot/ ├── config/ # 配置文件:关键词、搜索参数、模型配置 ├── collector/ # 职位采集模块 │ ├── browser.py # 浏览器会话管理 │ ├── selectors.py # 元素定位规则库 │ └── scraping.py # 列表页与详情页抓取逻辑 ├── analyzer/ # LLM分析模块 │ ├── prompts.py # 所有Prompt模板 │ ├── jd_parser.py # JD解析 │ ├── matcher.py # 匹配评估 │ └── resume_gen.py# 简历定制与求职信生成 ├── executor/ # RPA执行模块 │ ├── apply_form.py# 投递表单填写 │ └── tracker.py # 投递结果跟踪 ├── storage/ # SQLite读写层 ├── scheduler/ # 定时调度 └── main.py # 主入口开发的时候我强烈建议把LLM相关的逻辑全部收敛到analyzer模块里,不要散落在各个采集和执行流程中。这个项目做到一半时我中途换过一次模型供应商,因为所有LLM调用都集中在几个文件里,改造只花了半天;如果当初把Prompt散落在各处,换模型就等于重写一遍系统。
4.2 LLM调用封装与重试策略
LLM调用封装看起来简单,但隐藏的细节不少。第一是超时设置,JD解析和简历生成这类任务,模型响应时间差异很大,我统一设置为60秒超时,超过就重试一次,重试仍失败就跳过并记录日志。第二是速率限制,公有云API通常有每分钟请求数限制,我在封装层做了简单的令牌桶限流,避免批量处理JD时请求过快触发限流报错。第三是Error Handling,网络抖动、服务端500错误、内容审核拦截这些都要区分处理,网络抖动可以重试,内容审核拦截一般是Prompt触发了敏感词,重试也没用,需要人工检查输入。
下面是我实际的LLM调用封装代码,简化后保留了核心逻辑:
import json import time import requests class LLMClient: def __init__(self, api_url, api_key, model_name, max_retries=2, timeout=60): self.api_url = api_url self.api_key = api_key self.model_name = model_name self.max_retries = max_retries self.timeout = timeout def complete(self, system_prompt, user_prompt, temperature=0.2): payload = { "model": self.model_name, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], "temperature": temperature, "response_format": {"type": "json_object"} } headers = {"Authorization": f"Bearer {self.api_key}"} for attempt in range(self.max_retries + 1): try: resp = requests.post( self.api_url, json=payload, headers=headers, timeout=self.timeout ) resp.raise_for_status() data = resp.json() content = data["choices"][0]["message"]["content"] return json.loads(content) except requests.exceptions.Timeout: if attempt == self.max_retries: raise time.sleep(2 ** attempt) except json.JSONDecodeError: # 输出不是合法JSON时尝试用正则提取 import re match = re.search(r"\{.*\}", content, re.DOTALL) if match: return json.loads(match.group(0)) if attempt == self.max_retries: raise return None注意temperature参数我设置了0.2,信息抽取和决策类任务温度必须低,否则同样的JD每次解析出来的结果可能不一样,匹配分忽高忽低,后面的决策就乱了。简历改写这类偏创意生成的任务,温度可以调到0.5到0.7之间,让表达更自然,但也别太高,太高容易放飞自我编造事实。
4.3 核心数据解析:从HTML到结构化字段
采集模块拿到的是网页HTML,清洗成结构化数据是连接RPA和LLM的关键中间步骤。我做了两级清洗:第一级是通用清洗,用BeautifulSoup抽取正文区域,去掉script、style标签,把弹窗、导航栏、页脚等噪音节点删除;第二级是字段级提取,岗位名称从h1标签取,公司名从特定class的节点取,薪资范围用正则匹配"xxK-xxK""xx-xx万"这类模式,JD正文则直接取结构化文本。
这里有一个非常实用的技巧:不要试图让万能规则适应所有平台。每个招聘平台的页面结构差异很大,我针对每个平台单独维护一个解析配置,配置里写清楚各个字段的选择器和正则。宁可每个平台写一套专属解析,也不要去写一个试图通吃所有平台的智能解析器,后者维护成本高到崩溃。解析配置独立存放在config目录下,平台改版时只需要改配置,不用动代码。
4.4 定时调度与运行监控
调度逻辑用的是系统自带的定时任务,每天9点和21点各跑一轮。为什么选这两个时间点?因为大多数岗位是上午更新,9点跑能拿到前一夜和当天的增量;21点再跑一轮可以覆盖下午更新的岗位,同时给晚上的LLM批量处理留出时间。每轮流程跑完之后会生成一份运行报告,内容包括:采集到的岗位数、过滤掉的岗位数、LLM调用次数、投递成功数、失败数及失败原因。
运行监控是我教训最深的一块。系统上线第一周一切正常,第二周突然有一天早上报告显示"采集到0个岗位",我看了一眼以为是平台当天没更新就没在意,结果第三天、第四天连续零采集,登录上去才发现是平台改版把搜索结果页的结构完全换了,选择器全部失效。后来我加了三层保护:一是零结果告警,连续两次运行采集数为0就发通知;二是字段缺失率监控,正常情况缺失率应该低于5%,超过20%就判定页面结构可能变了;三是运行日志自动保留最近30天,排查问题时有据可查。
5. 常见问题与排查技巧实录
5.1 平台改版导致采集失效
这是采集类项目永远躲不开的问题。我遇到过两次招聘平台改版,每次都造成选择器大面积失效。排查流程建议按这个顺序来:先看是不是登录态失效了(Cookie过期是所有采集失败的常见原因),再看列表页还是详情页挂了,最后才怀疑页面结构改了。登录态问题修起来最快,重新扫码登录就完事;页面结构问题则需要打开开发者工具,重新定位元素、更新选择器配置。
我把应对平台改版的预案写成了文档,核心原则是"快速止损、逐步恢复":发现异常后先暂停流程,避免带着错误的选择器空跑浪费请求;然后手动分析改版后的页面结构,更新配置;更新后用一小批真实岗位做验证,确认无误再恢复全量运行。整个恢复过程控制在半小时以内,不要把时间耗在自动化排查上,人工直接看页面反而最快。
5.2 LLM输出不稳定和JSON解析失败
LLM输出不稳定是这种系统里占比最大的问题类型。表现包括:返回的不是合法JSON、字段名和Prompt里要求的不一致、数字字段返回了字符串、数组字段返回了空值。我的处理原则是三层兜底:第一层,Prompt里通过few-shot示例尽量约束输出格式;第二层,代码里用宽松解析策略,提取出JSON片段再修复常见错误;第三层,解析失败就跳过这条数据,并记录完整日志供事后分析。
值得注意的一个坑是:某些模型的JSON模式要求必须在API请求里显式开启response_format参数,否则再怎么写Prompt输出都可能不稳定。我之前就吃过这个亏,换模型供应商之后忘了在新服务上开启JSON模式,结果半个月的系统日志里全是解析失败记录。排查这类问题最快的办法是打开日志,看模型返回的原始内容,对比Prompt要求,基本一眼就能看出差异在哪。
5.3 自动投递被平台限制或封禁
自动投递是有风险的操作,我必须强调:每个招聘平台都有自己的用户协议,自动化填写表单是否被允许需要自己评估。我的经验是,只要把投递频率控制在接近人手速率的水平,绝大多数平台不会触发风控。我实测下来,每天控制在30到50个投递量,投递间隔在40到90秒之间随机化,运行几周没有出过问题。但我也见过有开发者把每天投递量拉到200以上,结果账号被要求二次验证,甚至被限制了搜索功能。
除此之外还有几个容易被忽视的触发点:同一IP短时间频繁登录多个账号、登录后不浏览页面直接点投递按钮、投递间隔严格固定(机器特征太明显)、短时间内反复修改简历内容。我的做法是给投递动作加入随机延迟、模拟先浏览页面再投递的行为路径,并且控制单平台的日投递量上限,宁可少投不要冒险。
5.4 数据误判和匹配分数虚高
这个问题的典型场景是:某岗位JD写得比较空泛,比如"熟悉互联网常用技术,有良好的学习能力",模型基于这么点信息给了一个高分,但实际投出去根本没有回音。为什么会虚高?因为LLM面对信息不足的输入时,倾向于补全最可能的答案,而不是承认自己不知道。我在Prompt里加了明确指令:"如果JD信息不足,confidence字段必须低于0.4,不得强行推断",同时加了一条代码规则:confidence低于0.5的岗位强制转入人工队列,不自动投递。
还有一类误判来自简历摘要的质量。如果简历摘要写得过于简略,LLM评估时没有足够素材,分数就会偏向中庸,区分度很差。我后来把简历摘要写成了结构化风格,每个项目列出技术栈、业务场景、个人职责、量化结果,LLM做匹配评估的时候能精准定位到"这个项目跟JD里的要求能对上",分数质量明显提升。这算是"磨刀不误砍柴工"的典型例子。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查与解决方案 |
|---|---|---|
| 连续两天采集到0个岗位 | 平台改版、登录态失效、搜索参数异常 | 先检查会话状态,再人工查看页面结构,更新选择器配置 |
| LLM返回JSON解析失败 | JSON模式未开启、Prompt约束不足、模型服务异常 | 查看原始返回内容,确认response_format参数,补few-shot示例 |
| 投递后账号被要求验证 | 投递频率过高、行为特征太机器化 | 降低频率、加入随机延迟、模拟阅读页面行为 |
| 匹配分虚高但投递无回音 | JD信息量太少、简历摘要质量差 | 检查confidence字段,低置信度转人工;优化简历摘要 |
| RPA表单填写总失败 | 表单结构变化、动态加载未完成 | 确认页面加载状态,降级定位策略,失败截图留证 |
| 同一岗位重复投递 | 去重逻辑缺失、岗位ID提取错误 | 检查岗位ID的提取方式,入库前做唯一性校验 |
6. 合规边界与实操心得
6.1 自动化求职必须守住的三条线
写这篇文章的最后,我必须把合规这件事单独拿出来说。系统跑得再顺,如果触碰了红线,账号被封都是小事,严重的可能涉及法律风险。我的原则是三条线:第一条,遵守各招聘平台的用户协议,对平台明确禁止自动化操作的规则保持尊重,投递频率和行为设计都模拟人工节奏;第二条,控制好爬取和投递的强度,不给目标网站造成压力,把请求频率控制在合理范围;第三条,个人信息保护不松懈,简历内容、求职信里都包含大量个人信息,在和公有云LLM服务交互时先做好脱敏预处理,本地数据库加密存储,投递记录定期清理。
另外,使用LLM生成的简历内容,一定要自己审一遍。模型基于素材库改写的内容可能仍然存在表述夸大、事实偏差的情况,直接使用风险很大。我个人在每次投递前保留人工复核环节,重点检查两个地方:一是简历里有没有素材库之外的新增事实,二是求职信里提到的项目是否都能在面试时讲清楚细节。技术可以帮你提效,但对自己简历的真实性负责,永远是求职者的底线。
6.2 从实用主义出发的几点心得体会
系统跑了三个月之后,有几个认知我觉得值得分享。第一个体会是"自动化不是目的,省下时间才是"。我一开始沉迷于把流程做到全自动,连早上看运营报告这一步都想省掉,后来发现完全无人值守反而让我对系统的信任度下降——每次看到投递成功通知都不知道系统为什么投、投得对不对。后来我保留了"人工复核关键节点"的设计,反而整体效率更高。第二个体会是"先跑通再优化"比"先设计完美再动手"靠谱得多。我的第一版系统很多地方都是硬编码,代码丑得没法看,但正是这第一版让我确认了流程跑得通、方向没问题,后来才逐步抽象出模块和配置。如果你也想做类似的系统,我的建议是:先用最少的功能把闭环跑起来,再考虑优化。
6.3 这个项目后续还能怎么扩展
目前这套系统只覆盖了"筛选、投递、跟踪"这条主干,后续扩展空间其实很大。比如我可以把面试环节接进来,用LLM解析收到的面试邀约邮件,提取面试时间、形式、面试官信息,同步到日历并生成面试准备提纲;再比如可以对接一些公开的技术评测题库,根据目标岗位的技术栈,自动生成针对性的刷题计划;再长远一点,还可以统计所有投递和反馈数据,做个人求职画像分析,搞清楚自己在哪个行业、哪个岗位类型、哪个薪资段位的命中率最高,反过来指导职业规划。这些扩展本质上都没有改变系统架构,只是在现有流程上增加新的数据源处理和LLM应用场景。这种"积累数据、沉淀能力、持续扩展"的思路,也是我觉得RPA加LLM这种组合最有价值的地方——你每运行一天,数据都在积累,系统的智能程度也会随着数据沉淀慢慢提升。
最后分享一个我踩过好几次坑之后养成的习惯:每次调整完系统里的Prompt或者决策阈值,我都会保留一个旧版本配置,并且在改动后的一周内持续对比新旧配置下的投递转化率,确认新配置确实更好再删除旧配置。这个习惯帮我避免了很多次"拍脑袋优化结果越优化越差"的情况。做这种半智能化的自动化系统,靠的不是某个灵光一现的提示词,而是持续的迭代和数据验证。