1. boos直聘自动打招呼插件到底在解决什么问题
每天打开 boos直聘,重复点“打招呼”、翻简历、点下载,是很多 HR 和招聘方的日常。一个岗位挂出去,消息列表里几十上百个候选人,手动一个个点,手指点到发麻,效率还低。boos直聘自动打招呼插件,本质就是把这套重复动作交给浏览器里的 JS 脚本去完成:监听页面 DOM 变化,识别候选人卡片,按关键词筛选,再触发打招呼按钮或下载按钮。
它适合谁?一是每天要处理大量候选人的招聘方,二是想学浏览器插件 + JS 操作 DOM 的前端同学。你不需要懂爬虫框架,只要会写一点 JavaScript,能看懂document.querySelector和事件监听,就能跟着做。核心链路有三条:自动打招呼、自动筛选简历、自动下载简历。三条链路共用同一套 DOM 监听与触发逻辑,区别只在“匹配到什么元素、触发什么动作”。
我试过用纯手动方式跑一天,大概能处理 80 到 100 个候选人;用插件把筛选和打招呼串起来后,同样的时间能覆盖 300 个以上,而且不会漏掉关键词匹配的简历。下面从零拆解这套逻辑,包括可复制的配置片段和本地验证步骤。需要说明的是,所有操作都在你自己登录的浏览器会话里完成,不涉及账号密码外传,也不绕过平台登录态,属于本地自测范畴。
先明确一个概念:浏览器插件在这里的角色是“页面增强”,不是“后台爬虫”。它运行在你已经打开的 boos直聘 页面上,通过content script注入 JS,读取当前 DOM,模拟点击。所以它的能力边界取决于页面结构,页面改版时选择器要跟着调。这也是为什么下面会重点讲“怎么找稳定的 class 和属性”,而不是给你一个永远不变的选择器。
另外,自动下载简历这条链路要特别注意:下载动作依赖页面上的“下载”按钮或附件链接,插件只负责触发点击,文件保存路径由浏览器下载设置决定。你可以在 Chrome 的chrome://settings/downloads里把“下载前询问保存位置”打开,方便确认每次下载的文件。合规前提下,建议只对自己有权限查看的简历做本地保存,不要批量外传。
2. TaoToken 前置准备:给插件接一个稳定的模型与 Key 管理
插件本身是纯前端 JS,为什么还要提 TaoToken?因为“自动筛选简历”如果只靠关键词硬匹配,误判率不低。比如岗位要求“熟悉 React”,候选人写“用过 Vue 和 React 做过项目”,硬匹配能过,但语义上要判断是否真的匹配,就需要模型介入。把模型调用放在插件里,用 TaoToken 做统一入口,可以避免在插件代码里硬编码多家厂商的 Key。
TaoToken 的定位是模型调用与 Key 管理平台,官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。你可以在控制台创建 API Key,然后在插件里用fetch调用兼容接口。这样插件只认一个 Base URL 和一个 Key,换模型时改 Model ID 就行,不用动业务代码。
具体操作:先打开控制台页面 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,登录后进入 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,创建一个新 Key,复制保存。注意 Key 只显示一次,丢了就重建。然后确认你要用的 Model ID,比如做简历语义匹配可以用通用对话模型,做代码相关筛选可以用 coding 类模型。模型对话入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite ,可以先在那里试一句“判断这段简历是否匹配 React 岗位”,确认返回格式再写进插件。
如果你打算长期跑招聘自动化,甚至把筛选逻辑做成 Agent,可以看 Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它更适合持续编码和任务编排的场景。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有 Base URL、鉴权头和请求示例,照着填就行。
这里要强调:插件里调用模型是“可选增强”,不是必须。你可以先跑纯 DOM 关键词匹配,验证打招呼和下载链路通了,再把模型判断加进去。这样排障时能分清是 DOM 选择器问题还是网络请求问题。Key 不要写死在content.js里提交到公开仓库,建议放在插件的options页面,用chrome.storage.local存,代码里读配置。
3. 可复制配置:manifest、content script 与筛选规则
先给插件的目录结构,方便你对照:
boos-helper/ manifest.json content.js options.html options.js rules.jsonmanifest.json用 Manifest V3,权限只开必要的:
{ "manifest_version": 3, "name": "boos直聘自动打招呼助手", "version": "1.0.0", "description": "本地自测:自动筛选、打招呼、下载简历", "permissions": ["storage", "downloads"], "host_permissions": ["https://www.zhipin.com/*"], "content_scripts": [ { "matches": ["https://www.zhipin.com/*"], "js": ["content.js"], "run_at": "document_idle" } ], "options_page": "options.html", "action": { "default_title": "boos助手" } }rules.json放筛选规则,支持关键词和模型判断开关:
{ "keywords": ["React", "TypeScript", "前端"], "excludeKeywords": ["实习", "外包"], "useModel": true, "model": { "baseUrl": "https://taotoken.net/api", "modelId": "你的ModelID", "apiKeyStorageKey": "taotoken_key" }, "delay": { "minMs": 800, "maxMs": 2000 } }content.js的核心是三个函数:scanCards扫描候选人卡片,matchRules匹配规则,triggerAction触发打招呼或下载。下面这段是可运行的骨架,选择器用[class*=""]做模糊匹配,降低改版影响:
const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; async function loadRules() { const res = await fetch(chrome.runtime.getURL('rules.json')); return res.json(); } function scanCards() { const nodes = document.querySelectorAll('[class*="candidate-card"], [class*="geek-card"]'); return Array.from(nodes).map((el) => ({ el, text: el.innerText || '', name: el.querySelector('[class*="name"]')?.innerText || '', greetBtn: el.querySelector('[class*="btn-greet"], [class*="chat-btn"]'), downloadBtn: el.querySelector('[class*="btn-download"], [class*="resume-download"]') })); } function matchRules(card, rules) { const hit = rules.keywords.some((k) => card.text.includes(k)); const blocked = rules.excludeKeywords.some((k) => card.text.includes(k)); return hit && !blocked; } async function triggerAction(card, rules) { if (card.greetBtn) { card.greetBtn.click(); await sleep(rand(rules.delay.minMs, rules.delay.maxMs)); } if (card.downloadBtn) { card.downloadBtn.click(); await sleep(rand(rules.delay.minMs, rules.delay.maxMs)); } } async function run() { const rules = await loadRules(); const cards = scanCards(); for (const card of cards) { if (matchRules(card, rules)) { await triggerAction(card, rules); } else { await sleep(rand(rules.delay.minMs, rules.delay.maxMs)); } } } run();options.js负责存 Key:
document.getElementById('save').addEventListener('click', () => { const key = document.getElementById('key').value.trim(); chrome.storage.local.set({ taotoken_key: key }, () => { document.getElementById('status').innerText = '已保存'; }); });模型判断部分,在matchRules里加一个异步分支,调用 TaoToken 的兼容接口:
async function modelMatch(card, rules) { const { taotoken_key } = await chrome.storage.local.get('taotoken_key'); if (!taotoken_key) return false; const resp = await fetch(`${rules.model.baseUrl}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${taotoken_key}` }, body: JSON.stringify({ model: rules.model.modelId, messages: [ { role: 'system', content: '你是招聘筛选助手,只回答 yes 或 no。' }, { role: 'user', content: `岗位要求 React 前端,这段简历是否匹配:${card.text.slice(0, 500)}` } ] }) }); const data = await resp.json(); return (data.choices?.[0]?.message?.content || '').toLowerCase().includes('yes'); }注意baseUrl填https://taotoken.net/api,不要带 UTM 参数到代码里,鉴权头用Bearer。Model ID 从控制台或文档里确认,不要照抄示例。
4. 本地验证:加载插件、跑通打招呼与下载
加载插件:打开 Chrome,地址栏输入chrome://extensions/,右上角打开“开发者模式”,点“加载已解压的扩展程序”,选择boos-helper目录。加载成功后,在扩展列表里能看到它,点“详情”可以打开选项页填 Key。
验证第一步,先关掉模型判断,把rules.json里useModel设为false,只跑关键词匹配。打开 boos直聘 的候选人列表页,按 F12 打开控制台,切到 Console,应该能看到脚本执行。如果没反应,在 Console 里手动执行scanCards(),看返回的数组长度。长度为 0 说明选择器没匹配到,去 Elements 面板找候选人卡片的真实 class,把scanCards里的选择器改掉。
验证第二步,观察打招呼。把delay调大一点,比如minMs: 2000, maxMs: 4000,方便肉眼确认。刷新页面后,匹配到关键词的卡片应该会依次触发打招呼按钮。如果按钮点了没反应,检查greetBtn是不是null,或者按钮被父元素拦截了点击事件,可以改成card.greetBtn.dispatchEvent(new MouseEvent('click', { bubbles: true }))。
验证第三步,验证下载。下载按钮触发后,文件会进浏览器默认下载目录。如果没下载,看 Console 有没有报错,常见的是downloadBtn为null,或者页面用了iframe嵌套。boos直聘 的简历下载有时在详情页,不在列表页,这种情况需要先点进详情,再触发下载。你可以在triggerAction里加一个判断:如果列表页没有下载按钮,就先card.el.click()进详情,等 DOM 变化后再找下载按钮。
验证第四步,打开模型判断。在选项页填入 TaoToken Key,把useModel设为true,刷新页面。Console 里应该能看到网络请求发往https://taotoken.net/api/v1/chat/completions。如果返回 401,说明 Key 没读到或格式不对;如果返回 404,检查baseUrl和路径拼接。模型返回内容里包含yes才触发动作,这样能过滤掉一部分关键词误匹配。
实测下来,纯关键词匹配跑 100 个候选人大概 3 到 5 分钟,加上模型判断会慢一些,因为每个卡片多一次网络请求。建议先用小批量验证,比如把scanCards改成只取前 10 个:Array.from(nodes).slice(0, 10)。确认逻辑没问题再放开全量。
5. 常见报错排查:401、local proxy failed、reading choices
401 Unauthorized:模型请求返回 401,九成是 Key 问题。先在选项页确认 Key 已保存,再在 Console 执行chrome.storage.local.get('taotoken_key')看能不能读到。如果读不到,检查options.js是否绑定到了正确的按钮 ID。如果读到了还 401,去 TaoToken 控制台确认 Key 是否被删除或过期,重新建一个。注意请求头是Authorization: Bearer <key>,不要漏掉Bearer和空格。
local proxy failed:这个报错通常出现在你本地起了代理工具,或者插件请求被浏览器代理拦截。先检查系统代理设置,关掉不必要的本地代理。如果是在公司网络下,确认https://taotoken.net/api能正常访问。可以在浏览器直接打开 API 地址,看是否返回正常响应。插件里的host_permissions要包含https://taotoken.net/*,否则跨域请求会被拦。Manifest V3 里跨域请求需要在host_permissions声明,不要只写zhipin.com。
reading 'choices':报错类似Cannot read properties of undefined (reading 'choices'),说明resp.json()返回的结构里没有choices。先打印完整响应:const data = await resp.json(); console.log(data);。常见原因是接口路径不对,比如把baseUrl写成了https://taotoken.net/api/v1,然后又拼了/v1/chat/completions,变成双v1。正确做法是baseUrl只到/api,路径拼/v1/chat/completions。另外确认 Model ID 拼写正确,模型不存在时也可能返回错误结构。
OAuth 相关报错:如果你在插件里用了需要 OAuth 的模型服务,可能会遇到 token 过期。TaoToken 的 Key 方式是静态 Bearer,不涉及 OAuth 跳转,所以插件里不要引入 OAuth 流程。如果看到OAuth字样,检查是不是误用了其他 SDK。统一用fetch+Bearer最省事。
打招呼按钮点了没反应:除了选择器问题,还可能是页面用了事件委托,按钮本身没有绑定 click。这种情况用dispatchEvent冒泡,或者找到父级容器触发。另外 boos直聘 对频繁操作有频率限制,delay不要设太小,建议最小 800ms 以上,否则可能触发验证。
下载文件为空或 0KB:说明下载按钮触发时资源还没加载完。可以在点击下载前加一个等待,监听 DOM 变化或固定sleep(1500)。如果下载的是 PDF,确认浏览器没有拦截弹窗。Chrome 下载设置里关掉“下载前询问”可以减少中断。
CC Switch / Cline MCP / Codex auth.json 场景:如果你把筛选逻辑放到这些编码工具里做本地调试,配置要写全三件套。Base URL 填https://taotoken.net/api,Key 填控制台创建的 Key,Model ID 填你确认可用的模型。以auth.json为例:
{ "baseUrl": "https://taotoken.net/api", "apiKey": "你的Key", "model": "你的ModelID" }Cline MCP 的配置里同样三项都要有,缺一个就会报连接失败。不要只填 Base URL 就以为能通。
6. 把三条链路串起来:从自测到稳定运行
三条链路的执行顺序建议是:先筛选,再打招呼,最后下载。因为打招呼会改变卡片状态,下载依赖详情页,顺序乱了容易找不到按钮。在run函数里可以改成两阶段:第一阶段只做matchRules,把命中的卡片收集起来;第二阶段对命中卡片依次triggerAction。这样即使中途某个卡片报错,也不影响已收集的列表。
稳定性方面,加一个MutationObserver监听列表容器,当新卡片加载进来时自动触发扫描。boos直聘 的列表是滚动加载的,只跑一次scanCards会漏掉后面的候选人。示例:
const observer = new MutationObserver(() => { clearTimeout(window.__scanTimer); window.__scanTimer = setTimeout(run, 1000); }); observer.observe(document.body, { childList: true, subtree: true });这样页面新增卡片后 1 秒会重新跑一轮。注意加防抖,否则 DOM 频繁变化会导致重复触发。run里也要做去重,用一个Set记录已处理的卡片文本或 ID,避免同一个候选人被打两次招呼。
日志方面,建议在 Console 里输出结构化信息,方便排查:
console.log('[boos-helper]', { total: cards.length, matched, greeted, downloaded });每次运行后看这行日志,就知道筛选了多少、打招呼多少、下载多少。如果matched为 0,回去调关键词;如果greeted小于matched,检查按钮选择器。
最后提醒几个边界:插件只在你登录的浏览器里运行,不要分享带 Key 的插件包;下载的简历文件自己保管好,不要上传到公开网盘;模型判断只作为辅助,最终决策还是人工确认。把delay设合理,既是对平台友好,也是对自己账号负责。跑通之后,你可以把rules.json里的关键词换成自己岗位的,模型提示词也按岗位改,这套骨架就能复用到不同招聘场景。