Get Offer 秋招投递助手插件的实现
重复填网申信息实在是非常烦,并且投递后的信息管理需要额外使用飞书,于是vibe-coding了一个投递工具,在ai 完成后单独进行学习和消化。 github连接:https://github.com/put4o/get-offer-assistant
网申助手的一键填写是怎么实现的
网申时最重复的工作,就是把姓名、电话、学校、实习经历和项目经历反复填写到不同公司的招聘网站中。
我做的网申助手允许用户提前保存一份个人资料。进入招聘页面后,点击“一键填写”,插件会自动识别页面字段,并把个人资料写入对应位置。
这个功能看起来像是简单的复制粘贴,但真正实现时主要要解决三个问题:
- 如何判断网页上的输入框代表什么;
- 如何把输入框与个人资料准确对应;
- 如何让招聘网站真正接受插件写入的内容。
一、一键填写的整体流程
一次完整的自动填写过程可以分为五步:
读取个人资料 ↓扫描当前页面的表单控件 ↓判断每个控件对应的资料字段 ↓处理重复经历和动态表单 ↓写入内容并触发网页事件用户点击按钮后,插件先从浏览器本地读取已经保存的个人资料,然后扫描页面中的输入框、文本域、下拉框和文件上传控件。
识别完成后,每个网页控件都会得到一个业务类型,例如姓名、手机号、学校、专业、公司、岗位或项目描述。插件再根据这些业务类型,从个人资料中取出对应值并写入网页。
二、如何识别网页字段
不同招聘网站没有统一的字段命名规范。同一个姓名字段,可能叫 name、fullName、realName,也可能没有明确名称,只在输入框上方显示“申请人姓名”。
因此,字段识别不能只看输入框名称,而要综合多种信息:
- 字段名称和编号;
- 输入框类型;
- 占位提示;
- 自动填充属性;
- 与输入框关联的标签文字;
- 父容器中的业务名称;
- 字段所在的表单模块。
例如,一个输入框自身可能没有任何有意义的名称,但它所在的容器写着“毕业院校”,插件仍然可以将它识别为学校字段。
对于日期字段,还要结合模块上下文判断。同样是“开始时间”,出现在教育经历中代表入学时间,出现在实习经历中代表入职时间,出现在项目经历中则代表项目开始时间。
三、字段如何与个人资料对应
插件不会直接保存网页字段名称,而是先定义一组统一的业务字段,例如:
| 业务字段 | 可能出现的网页名称 |
|---|---|
| 姓名 | name、fullName、realName、申请人姓名 |
| 手机号 | phone、mobile、tel、联系方式 |
| 邮箱 | email、mail、电子邮箱 |
| 学校 | school、university、毕业院校 |
| 专业 | major、specialty、所学专业 |
| 公司 | company、employer、工作单位 |
| 岗位 | position、jobTitle、职位名称 |
| 项目名称 | projectName、projectTitle、项目名称 |
扫描网页时,插件把字段名称、标签和上下文进行标准化处理,例如拆分驼峰、去掉下划线、统一大小写和空格,然后与这些业务字段的关键词进行匹配。
匹配结果带有置信度,而不是简单判断“是”或“不是”。字段类型明确、名称完全一致时,置信度较高;只有模糊关键词或上下文时,置信度较低。只有超过阈值的结果才会自动填写。
这种方式可以避免一些常见误判。例如,不能因为 projectName 中包含 name,就把它识别成个人姓名;也不能因为某个字段包含 id,就把它当成身份证号。对于 name、id、title 这类歧义较大的短词,必须要求更严格的匹配条件,并优先判断“项目名称”“公司名称”等更具体的含义。
如果本地规则无法确定字段类型,可以把字段名称、标签和占位提示交给 AI 做一次语义分类。AI 只负责返回字段属于哪一种业务类型,不直接修改网页。最终结果仍要经过有效性检查,无法判断的字段保持空白。
四、教育、实习和项目经历如何对应
姓名和手机号只有一个值,比较容易填写。教育、实习和项目经历则是多条记录,页面中可能重复出现相同类型的字段。
插件按照字段在页面中的出现顺序进行对应:
- 第一个学校字段对应第一段教育经历;
- 第二个学校字段对应第二段教育经历;
- 第一个公司字段对应第一段实习经历;
- 第二个公司字段对应第二段实习经历。
专业、学历、岗位、描述和日期分别维护自己的顺序,从而保证同一条经历中的信息落到同一行。
在填写前,还要先检查页面是否有足够的经历行。假设用户保存了三段实习经历,而页面默认只显示一行,插件会先找到“添加”按钮,补足到三行,等待页面生成新控件,然后重新扫描并填写。
日期范围需要单独处理。很多网站把开始和结束日期放在同一个“起止时间”组件中,两个输入框没有独立名称。插件会根据它们在组件中的左右位置判断开始和结束,并结合所在模块选择教育、实习或项目日期。
部分日期组件会立即校验“开始时间不能晚于结束时间”。为了避免中间状态触发错误,填写时通常先写结束时间,再写开始时间。
五、如何让网页真正接收填写结果
直接修改输入框的值,在普通网页中可能有效,但在 React、Vue 等框架构建的招聘网站中经常失败。
这些网站不仅保存输入框当前显示的文字,还在框架内部维护一份表单状态。如果插件只改变页面上的值,可能出现以下问题:
- 页面显示已经填写,提交时却提示为空;
- 输入框失去焦点后内容消失;
- 表单校验没有执行;
- 页面重新渲染后恢复成旧值。
因此,插件写入时会使用浏览器原生的值更新方式,并模拟用户输入过程中产生的事件,包括输入、内容变化和失去焦点等。这样网页框架才能更新内部状态,并执行原有的校验逻辑。
写入完成后还要读取控件的最终状态,确认网页是否真的接受了目标值。只有确认成功,才能把这个字段计入已填写数量。
六、下拉框和文件上传的处理
原生下拉框可以直接遍历所有选项,根据选项值和显示文字进行匹配。例如,个人资料中保存的是“本科”,网页选项可能显示“大学本科”,可以通过标准化和部分匹配找到最接近的候选项。
自定义下拉框则不能直接设置值。它通常需要先点击控件,等待选项列表出现,再找到目标选项并模拟鼠标点击。选择完成后还要检查页面显示结果,确认组件已经更新。
简历上传也不是填写文件路径。插件会把本地保存的简历内容重新还原成浏览器文件对象,再模拟用户选择文件,并触发网页的文件变化事件。整个过程在本地完成,不需要经过额外的中转服务器。
七、动态页面为什么要重复扫描
现代招聘网站的表单经常在运行过程中变化。新增经历、切换学历类型或选择某个选项后,页面可能插入新的字段,也可能销毁旧控件并重新创建。
所以插件会监听页面结构变化。当发现新的输入控件时,重新执行字段识别。正式填写前,在补足经历行之后也必须再次扫描,不能继续使用之前保存的页面元素。
这一步解决了很多“第一次能填,新增一行后就失效”的问题。
八、规则与 AI 如何配合
一键填写以本地规则为主,AI 只作为补充。
姓名、手机号、邮箱等明确字段优先由规则识别,这样速度快、没有调用成本,结果也更稳定。只有字段命名复杂、规则置信度不足时,才使用 AI 判断字段语义。
AI 返回结果后仍然需要程序校验,而且不会主动覆盖网页中已经存在的内容。无法确定时保持空白,比填写错误的信息更安全。
对于一键填写后剩余的空白字段,还可以让用户框选一个页面区域,由 AI 根据个人资料进行补填。框选功能只收集区域内仍为空的字段,不处理已有值,也不会自动提交表单。
总结
网申助手的一键填写并不是简单地把一组字符串写进网页,而是一条完整的处理链路:
- 从页面属性、标签和模块上下文中识别字段;
- 将不同网站的字段统一映射到个人资料模型;
- 按顺序处理多段教育、实习和项目经历;
- 为动态表单补足行数并重新扫描;
- 模拟真实输入事件,让网页框架接受新值;
- 对规则无法判断的字段使用 AI 兜底。
其中最重要的原则是:高置信度字段自动填写,无法确认的字段保持空白,最终提交前由用户检查。这样才能在提高填写效率的同时,尽量避免自动化带来的错误。
