Skip to content

Repository files navigation

BOSS 人才索引采集助手

这是一个本地 RPA MVP,用于辅助 HR 在已经登录的 BOSS 直聘人才库页面中读取当前可见候选人信息,并写入轻量索引文件。它不是无人值守爬虫,也不绕过登录、验证码、风控或隐藏接口。

新方向:HR Browser Agent

下一阶段会把项目从“采集脚本”升级成“用户本机 HR 浏览器操作 agent”:用户和 OpenClaw HR agent 对话,agent 通过本机 FastAPI service 控制可见 Chrome,完成 BOSS 招聘者登录、发 JD、搜索候选人等操作。

设计文档:

本机启动:

npm run hr:start

如果由 Hermes/OpenClaw agent 自动启动,请用后台模式:

npm run hr:daemon

面向最终用户的轻量安装入口:

curl -fsSL https://raw.githubusercontent.com/nateEc/candidate-intel-agent/main/scripts/bootstrap_boss_hr_agent.sh | bash

安装后会提供本机 CLI:

~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent status
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent start
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent stop
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent doctor

边界不变:验证码、App 安全确认和平台验证必须由用户本人完成;系统只负责打开页面、点击、填表、等待和识别状态。

P0 招聘闭环目前覆盖:招聘者登录、发布/更新/关闭职位、投递收件箱巡检、投递候选人评估入库、弱去重合并,以及“准备打招呼 + 招聘者确认后发送”。投递处理默认使用同一个本机 HR service:

curl -X POST http://127.0.0.1:8790/v1/boss/applications/scan \
-H 'content-type: application/json' \
-d '{"job_filter":"AI工程师 _ 北京 20-30K","limit":20,"include_resumes":true,"dry_run":true}'

智能简历库 v1

智能简历库现在使用 Postgres + pgvector 作为主库。BOSS/SQLite 只保留为采集缓存和回放来源;agent 查询、匹配、复评、归池、邮件草稿都走 Postgres。

本地开发可以直接启动项目自带的 pgvector Docker 容器:

npm run talent:db:start

这会启动/复用 boss-talent-postgres 容器,监听 127.0.0.1:54329,并把连接串写入本地 .env

DATABASE_URL=postgresql://talent:talent_dev_password@127.0.0.1:54329/talent_library

如果使用外部数据库,只要手动在 .env 或 shell 里配置:

export DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/talent_library"

初始化 schema / migration:

npm run talent:migrate

启动 Agent API:

npm run talent:service
curl http://127.0.0.1:8792/health

共享给其他 agent 使用时,不要让对方直连 Postgres。把 Smart Talent Library API 部署到你的服务器上,并配置 API token:

export DATABASE_URL="postgresql://talent:<password>@127.0.0.1:5432/talent_library"export TALENT_LIBRARY_API_TOKEN="<shared-secret>"export TALENT_LIBRARY_HOST="127.0.0.1"export TALENT_LIBRARY_PORT="8792"
npm run talent:service

再用 Nginx/Caddy 把 HTTPS 反代到 127.0.0.1:8792/health 可公开用于探活;所有 /v1/talent/* 请求在配置 token 后都必须带:

-H "Authorization: Bearer <shared-secret>"

如果临时测试必须直接监听公网地址,启动脚本会要求先设置 TALENT_LIBRARY_API_TOKEN

TALENT_LIBRARY_HOST=0.0.0.0 TALENT_LIBRARY_API_TOKEN="<shared-secret>" npm run talent:service

不要开放 Postgres 端口到公网。给外部 agent 配置 docs/hr_agent_skill.md 时,只给 TALENT_LIBRARY_BASE_URLTALENT_LIBRARY_API_TOKEN

完整 ECS/云端部署步骤见 Smart Talent Library Cloud Deployment

把已有 BOSS 采集缓存导入 Postgres:

npm run talent:ingest -- --sqlite-db data-python/boss_talent.sqlite --limit 100

直接把一份简历文本/文件导入智能简历库:

npm run talent:ingest-resume -- --file-path "/path/to/resume.txt"

如果需要一套可重复的测试数据,可以导入 20 个合成候选人:

npm run talent:seed-fake -- --reset-synthetic

这会写入 20 个 synthetic_resume 候选人,覆盖 AI Agent/RAG、后端平台、算法、数据、产品、HRBP/招聘、运营/市场等画像;其中 5 人会追加第二版简历,用于测试 /resume-diff/timeline--reset-synthetic 只清理上一轮合成测试数据,不会删除真实/手工候选人。

Hermes/HR agent 更推荐调用 API,把“LLM 抽取后的结构化 JSON + 原始简历文本”一起写入:

curl -X POST http://127.0.0.1:8792/v1/talent/ingest/resume \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "resume_text": "张三\\n邮箱 zhangsan@example.com\\n期望职位:AI Agent 后端工程师\\n...", "structured_data": { "profile": { "display_name": "张三", "expected_position": "AI Agent 后端工程师", "city": "北京", "education_level": "本科", "work_years_text": "5年", "expected_salary_text": "45-60K" }, "skills": [{"skill_name": "RAG", "evidence_text": "负责 RAG 平台"}], "work_experiences": [] } }'

常用接口:

POST /v1/talent/ingest/boss-snapshot 从现有 BOSS/SQLite 采集缓存入 Postgres
POST /v1/talent/ingest/resume 从粘贴文本/本地文件/base64 文件导入简历
POST /v1/talent/recommend 从公司/项目上下文自动推断招聘需求并推荐候选人
POST /v1/talent/candidates/resolve 用 email/手机号/简历 hash/姓名学校等定位候选人
POST /v1/talent/candidates/merge 合并重复候选人档案
POST /v1/talent/candidates/{id}/enrich 结构化简历、生成信号和证据
GET /v1/talent/candidates/{id} 查看完整候选人档案
POST /v1/talent/candidates/{id}/resume 给已有候选人追加/更新一份新简历版本
GET /v1/talent/candidates/{id}/resume-diff 比较候选人最近两版或指定两版简历
GET /v1/talent/candidates/{id}/timeline 查看候选人简历、匹配、互动、任务时间线
GET /v1/talent/search 关键词/城市/学历/评级/人才池检索
POST /v1/talent/semantic-search 本地 hash embedding 语义检索
POST /v1/talent/match-job 给定 JD 生成候选人排序
POST /v1/talent/pools/auto-assign 自动归入人才池
POST /v1/talent/tasks/review 生成复评/回捞任务
POST /v1/talent/outreach/email-draft 基于可见/导入 email 生成邮件草稿
POST /v1/talent/outreach/batch-draft 为多个候选人批量生成邮件草稿
POST /v1/talent/outreach/email-sent 外部邮件工具发送成功后回写互动记录

这版支持两种简历入库方式:Hermes/HR agent 先用 LLM 抽取结构化 JSON 再调用 /v1/talent/ingest/resume,或服务端用本地规则兜底抽取。已有候选人的新简历走 /v1/talent/candidates/{id}/resume,旧版本会保留,后续可用 /resume-diff/timeline 看变化。所有结构化字段尽量带 evidence_span_idconfidenceextractor_version。email 只来自简历可见、HR 手动补充、或导入数据,不做自动猜测。婚姻/家庭/健康等敏感属性只进入 candidate_sensitive_attributes 受限表,默认不参与匹配分、薪资判断或自动触达。

BOSS HR Browser Agent 在 DATABASE_URL 存在时,会在投递巡检后自动把该次 scan 同步到 Postgres;未配置时仍只写本地采集缓存。

目标边界

  • 读取 HR 当前可见的搜索结果卡片。
  • 可选点开详情弹窗,对在线简历 canvas 截图 OCR,保存简历快照和轻量索引字段。
  • 不保存账号密码。
  • 在线简历长文本只保存 HR 当前账号可见内容的 OCR 快照;联系方式会做基础脱敏。
  • 不采集联系方式。
  • 遇到登录、验证码、账号异常时由人工处理,脚本暂停或退出。

安装

npm install
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

默认使用本机 Chrome。也可以通过环境变量指定浏览器路径:

BOSS_BROWSER_EXECUTABLE="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npm run capture

使用

npm run capture -- --limit 10 --details

首次运行会打开一个独立 Chrome 用户目录 .browser-profile。请在浏览器里手动登录 BOSS,进入人才库搜索页,设置好城市、岗位、关键词和筛选条件。准备好后回到终端按回车,脚本会读取页面上可见候选人。

常用参数:

--limit 20 最多读取多少个候选人卡片
--details 点开详情弹窗并抽取轻量索引
--no-details 只读取列表页卡片
--start-url <url> 起始页面
--user-data-dir <dir> 浏览器登录态目录
--output-dir <dir> 输出目录
--headless 无头模式,不推荐用于首次登录

输出文件。capture 默认写入 data/capture:cdp 默认写入 data-python/

<output-dir>/candidates.ndjson 候选人索引,追加写入
<output-dir>/observations.ndjson 每次搜索观察记录,追加写入
<output-dir>/resume_snapshots.ndjson 在线简历 OCR 快照,追加写入
<output-dir>/runs/run-*.json 单次运行结果,便于复核

候选人索引会保留页面上的 active_status 原始活跃标签,例如 刚刚活跃3日内活跃本周活跃,并推断一个保守的 last_seen_at。区间类标签按区间起点推断,例如 今日活跃 记为当天 00:00,本周活跃 记为本周一 00:00,3日内活跃 记为采集时间减 3 天。

如果你已经在普通 Chrome 里打开并登录了 BOSS 页面,但 Chrome 没有开启远程调试,也没有允许 Apple Events 执行 JavaScript,可以用辅助功能 fallback:

npm run capture:chrome-ax -- --limit 15

这个模式只读取当前 Chrome 窗口里辅助功能树暴露出来的候选人卡片文本,不会点开详情页。

Python + CDP 连接已启动 Chrome:

open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-chrome \
--no-first-run \
https://www.zhipin.com/web/chat/search
npm run capture:cdp -- --limit 10 --skip-apply --detail-max-pages 3

这个模式会连接 9222 端口上的 Chrome,用 DOM 读取页面,比辅助功能 fallback 快很多。 详情页正文由 BOSS 的 canvas 渲染,脚本会裁剪详情弹窗截图并用 macOS Vision OCR 读取文字;原始截图会保存在 data-python/resume-screenshots/<run-id>/ 便于复核。

如果 BOSS 的职位侧和人才库侧必须使用两个不同账号,请启动两个独立 Chrome 用户目录和两个 CDP 端口。不要在同一个 Chrome profile 里开两个账号,同站 cookie 会互相覆盖:

# 人才库/招聘者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-candidates \
--no-first-run \
https://www.zhipin.com/web/chat/search
# 职位侧/求职者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9223 \
--remote-allow-origins=http://127.0.0.1:9223 \
--user-data-dir=/tmp/boss-rpa-jobs \
--no-first-run \
"https://www.zhipin.com/web/geek/jobs?city=100010000"

分别登录两个账号后,统一入口默认会用 9222 抓人才库、9223 抓职位侧:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

如果你想换端口,可以显式传:

npm run org:intel -- \
--company 月之暗面 \
--jobs-cdp-url http://127.0.0.1:9223 \
--candidates-cdp-url http://127.0.0.1:9222 \
--refresh auto \
--report

也可以让脚本先设置搜索条件,再抓取。例如搜索「上海 / 不限职位 / 腾讯」:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--limit 90 \
--detail-max-pages 2

只设置搜索条件、不抓取:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--apply-only

注意:BOSS 下拉里的「热门」是城市列表分组,不是最终城市值;脚本会真实点击它,但页面城市不会变成「热门」。量产时建议传具体城市,比如 北京上海深圳

如果当前搜索结果每次只加载 15 个,可以让脚本先滚动加载更多:

npm run capture:cdp -- --limit 100 --skip-apply --no-details

--limit 100 会滚动到累计 100 个候选人或没有更多结果为止。要一直加载到列表没有新增候选人:

npm run capture:cdp -- --load-all --skip-apply --no-details

量产抓详情时可以去掉 --no-details,并用 --detail-max-pages 12 控制每份在线简历 OCR 的页数。

BOSS 职位侧采集

组织情报还需要招聘岗位信号。职位侧采集默认连接 9223 端口上的职位侧/求职者 Chrome,会打开 BOSS 的 /web/geek/jobs 职位搜索页,先读取左侧职位列表,再逐个点击职位卡读取右侧详情:

npm run capture:jobs -- \
--company 腾讯 \
--cdp-url http://127.0.0.1:9223 \
--limit 30

默认城市是全国,等价于 BOSS URL 里的 city=100010000。如果要限定城市,可以传 --city 上海--city 101020100--city 热门--city-group hot 仍会展开成常用城市组:北京、上海、深圳、广州、杭州、成都、武汉、南京、苏州、西安。--limit 表示每个城市最多读取多少条左侧职位卡。左侧列表每次加载约 15 条时,脚本会持续滚动到目标数量或没有新增职位。

如果你已经手动打开了类似下面的页面,也可以直接读取当前搜索:

npm run capture:jobs -- --company 月之暗面 --limit 90 --cdp-url http://127.0.0.1:9223 --no-manual-ready

默认会点击每个职位卡并读取右侧 JD;只想快速读取左侧列表时加 --no-details

职位采集输出:

<output-dir>/boss_job_postings.ndjson
<output-dir>/runs/run-*.json

导入 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

组织情报报告

统一入口会编排职位侧采集、人才库采集、入库和报告生成:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

--refresh auto 会先看 SQLite 里是否已有近 24 小时的新鲜数据;缺职位侧数据就跑 capture:jobs,缺人才侧观察就跑 capture:cdp。如果只想生成报告不刷新:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh none \
--report

也可以只刷新某一侧:

npm run org:intel -- --company 月之暗面 --refresh jobs --report
npm run org:intel -- --company 月之暗面 --refresh candidates --report

OpenClaw 对接时使用本地 FastAPI service:

npm run org:service

固定机器部署时建议直接用一键脚本,它会启动两套 CDP Chrome,并把 FastAPI worker 指向对应端口:

ORG_INTEL_HOST=0.0.0.0 ORG_INTEL_PORT=8787 ./scripts/start_org_intel_stack.sh

生产对接建议配置 API token:

ORG_INTEL_API_TOKEN=<shared-secret> npm run org:service

配置后,除 /health 外的 /v1/org-intel/* 请求都需要:

-H "Authorization: Bearer <shared-secret>"

默认端口约定:

BOSS_CANDIDATES_CDP_URL=http://127.0.0.1:9222 # 人才库/招聘者账号
BOSS_JOBS_CDP_URL=http://127.0.0.1:9223 # 职位侧/求职者账号

提交异步情报任务:

curl -X POST http://127.0.0.1:8787/v1/org-intel/requests \
-H 'content-type: application/json' \
-d '{ "company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音", "TikTok", "飞书"], "mode": "standard", "refresh": "auto", "client_request_id": "openclaw-example" }'

如果库里没有新鲜报告,接口会立即返回 job_ideta_secondseta_at。OpenClaw 到时间后轮询:

curl http://127.0.0.1:8787/v1/org-intel/requests/<job_id>

状态可能是 queuedrunning_jobsrunning_candidatesimportinggenerating_reportreadyblocked_needs_humanfailedready 时返回 report_markdown 和结构化 findings;如果 BOSS 触发验证,会返回 blocked_needs_human,由人工在采集浏览器里处理。

CEO 定时周/月报由 Hipilot 或外部 cron 触发,本服务只保存订阅和生成 digest。创建订阅:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "owner_id": "ceo-1", "display_name": "CEO 重点公司监控", "cadence": "weekly_and_monthly", "companies": [ {"company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音"], "mode": "standard"}, {"company": "腾讯", "aliases": ["Tencent", "腾讯云", "微信"], "mode": "standard"}, {"company": "月之暗面", "aliases": ["Moonshot", "Kimi"], "mode": "standard"} ], "timezone": "Asia/Shanghai", "freshness_policy": "auto" }'

到点后触发 digest:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions/<subscription_id>/digest-runs \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{"cadence":"weekly","client_request_id":"hipilot-cron-run-id"}'

如果返回 queued / running,按 eta_at 轮询:

curl -H "Authorization: Bearer <shared-secret>" \
http://127.0.0.1:8787/v1/org-intel/digest-runs/<digest_job_id>

也可以按 owner/subscription/cadence 取最近 digest,例如查看上一次月报:

curl -H "Authorization: Bearer <shared-secret>" \
"http://127.0.0.1:8787/v1/org-intel/digest-runs?owner_id=ceo-1&cadence=monthly&limit=1"

ready 时返回多公司 digest_markdownpartial_ready 会返回可用公司摘要,并在风险区说明阻塞公司;blocked_needs_human 表示 BOSS 账号验证需要运营处理。

导入人才库和职位侧 run 后,可以生成 BOSS-only 组织情报 Markdown:

npm run report:org -- \
--company 腾讯 \
--alias Tencent \
--alias 腾讯科技 \
--db data-python/boss_talent.sqlite

也可以用目标配置:

npm run report:org -- \
--company 腾讯 \
--target-config org_targets.example.json \
--db data-python/boss_talent.sqlite

报告会写入 org-intel/<公司名>/report-*.md,并把报告正文、来源计数和结构化组织判断写入 SQLite 的 org_intel_reports / org_findings 表。正文只输出聚合判断;原始候选人信号以脱敏 ID 折叠展示。

数据策略

这个 MVP 采用 B+C 混合版:

  • B:Playwright 半自动读取页面可见信息。
  • C:候选人索引落主表,在线简历落快照表,避免覆盖历史。

OCR 快照用于内部检索和复核,最终原文仍以 BOSS 页面为准。

PostgreSQL

sql/schema.sql。第一版先落 NDJSON,确认字段稳定后再接 PostgreSQL。

当前也可以把单次 run 导入本地 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

测试

npm test

About

Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - nateEc/candidate-intel-agent: Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles. · GitHub
Skip to content

Repository files navigation

BOSS 人才索引采集助手

这是一个本地 RPA MVP,用于辅助 HR 在已经登录的 BOSS 直聘人才库页面中读取当前可见候选人信息,并写入轻量索引文件。它不是无人值守爬虫,也不绕过登录、验证码、风控或隐藏接口。

新方向:HR Browser Agent

下一阶段会把项目从“采集脚本”升级成“用户本机 HR 浏览器操作 agent”:用户和 OpenClaw HR agent 对话,agent 通过本机 FastAPI service 控制可见 Chrome,完成 BOSS 招聘者登录、发 JD、搜索候选人等操作。

设计文档:

本机启动:

npm run hr:start

如果由 Hermes/OpenClaw agent 自动启动,请用后台模式:

npm run hr:daemon

面向最终用户的轻量安装入口:

curl -fsSL https://raw.githubusercontent.com/nateEc/candidate-intel-agent/main/scripts/bootstrap_boss_hr_agent.sh | bash

安装后会提供本机 CLI:

~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent status
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent start
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent stop
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent doctor

边界不变:验证码、App 安全确认和平台验证必须由用户本人完成;系统只负责打开页面、点击、填表、等待和识别状态。

P0 招聘闭环目前覆盖:招聘者登录、发布/更新/关闭职位、投递收件箱巡检、投递候选人评估入库、弱去重合并,以及“准备打招呼 + 招聘者确认后发送”。投递处理默认使用同一个本机 HR service:

curl -X POST http://127.0.0.1:8790/v1/boss/applications/scan \
-H 'content-type: application/json' \
-d '{"job_filter":"AI工程师 _ 北京 20-30K","limit":20,"include_resumes":true,"dry_run":true}'

智能简历库 v1

智能简历库现在使用 Postgres + pgvector 作为主库。BOSS/SQLite 只保留为采集缓存和回放来源;agent 查询、匹配、复评、归池、邮件草稿都走 Postgres。

本地开发可以直接启动项目自带的 pgvector Docker 容器:

npm run talent:db:start

这会启动/复用 boss-talent-postgres 容器,监听 127.0.0.1:54329,并把连接串写入本地 .env

DATABASE_URL=postgresql://talent:talent_dev_password@127.0.0.1:54329/talent_library

如果使用外部数据库,只要手动在 .env 或 shell 里配置:

export DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/talent_library"

初始化 schema / migration:

npm run talent:migrate

启动 Agent API:

npm run talent:service
curl http://127.0.0.1:8792/health

共享给其他 agent 使用时,不要让对方直连 Postgres。把 Smart Talent Library API 部署到你的服务器上,并配置 API token:

export DATABASE_URL="postgresql://talent:<password>@127.0.0.1:5432/talent_library"export TALENT_LIBRARY_API_TOKEN="<shared-secret>"export TALENT_LIBRARY_HOST="127.0.0.1"export TALENT_LIBRARY_PORT="8792"
npm run talent:service

再用 Nginx/Caddy 把 HTTPS 反代到 127.0.0.1:8792/health 可公开用于探活;所有 /v1/talent/* 请求在配置 token 后都必须带:

-H "Authorization: Bearer <shared-secret>"

如果临时测试必须直接监听公网地址,启动脚本会要求先设置 TALENT_LIBRARY_API_TOKEN

TALENT_LIBRARY_HOST=0.0.0.0 TALENT_LIBRARY_API_TOKEN="<shared-secret>" npm run talent:service

不要开放 Postgres 端口到公网。给外部 agent 配置 docs/hr_agent_skill.md 时,只给 TALENT_LIBRARY_BASE_URLTALENT_LIBRARY_API_TOKEN

完整 ECS/云端部署步骤见 Smart Talent Library Cloud Deployment

把已有 BOSS 采集缓存导入 Postgres:

npm run talent:ingest -- --sqlite-db data-python/boss_talent.sqlite --limit 100

直接把一份简历文本/文件导入智能简历库:

npm run talent:ingest-resume -- --file-path "/path/to/resume.txt"

如果需要一套可重复的测试数据,可以导入 20 个合成候选人:

npm run talent:seed-fake -- --reset-synthetic

这会写入 20 个 synthetic_resume 候选人,覆盖 AI Agent/RAG、后端平台、算法、数据、产品、HRBP/招聘、运营/市场等画像;其中 5 人会追加第二版简历,用于测试 /resume-diff/timeline--reset-synthetic 只清理上一轮合成测试数据,不会删除真实/手工候选人。

Hermes/HR agent 更推荐调用 API,把“LLM 抽取后的结构化 JSON + 原始简历文本”一起写入:

curl -X POST http://127.0.0.1:8792/v1/talent/ingest/resume \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "resume_text": "张三\\n邮箱 zhangsan@example.com\\n期望职位:AI Agent 后端工程师\\n...", "structured_data": { "profile": { "display_name": "张三", "expected_position": "AI Agent 后端工程师", "city": "北京", "education_level": "本科", "work_years_text": "5年", "expected_salary_text": "45-60K" }, "skills": [{"skill_name": "RAG", "evidence_text": "负责 RAG 平台"}], "work_experiences": [] } }'

常用接口:

POST /v1/talent/ingest/boss-snapshot 从现有 BOSS/SQLite 采集缓存入 Postgres
POST /v1/talent/ingest/resume 从粘贴文本/本地文件/base64 文件导入简历
POST /v1/talent/recommend 从公司/项目上下文自动推断招聘需求并推荐候选人
POST /v1/talent/candidates/resolve 用 email/手机号/简历 hash/姓名学校等定位候选人
POST /v1/talent/candidates/merge 合并重复候选人档案
POST /v1/talent/candidates/{id}/enrich 结构化简历、生成信号和证据
GET /v1/talent/candidates/{id} 查看完整候选人档案
POST /v1/talent/candidates/{id}/resume 给已有候选人追加/更新一份新简历版本
GET /v1/talent/candidates/{id}/resume-diff 比较候选人最近两版或指定两版简历
GET /v1/talent/candidates/{id}/timeline 查看候选人简历、匹配、互动、任务时间线
GET /v1/talent/search 关键词/城市/学历/评级/人才池检索
POST /v1/talent/semantic-search 本地 hash embedding 语义检索
POST /v1/talent/match-job 给定 JD 生成候选人排序
POST /v1/talent/pools/auto-assign 自动归入人才池
POST /v1/talent/tasks/review 生成复评/回捞任务
POST /v1/talent/outreach/email-draft 基于可见/导入 email 生成邮件草稿
POST /v1/talent/outreach/batch-draft 为多个候选人批量生成邮件草稿
POST /v1/talent/outreach/email-sent 外部邮件工具发送成功后回写互动记录

这版支持两种简历入库方式:Hermes/HR agent 先用 LLM 抽取结构化 JSON 再调用 /v1/talent/ingest/resume,或服务端用本地规则兜底抽取。已有候选人的新简历走 /v1/talent/candidates/{id}/resume,旧版本会保留,后续可用 /resume-diff/timeline 看变化。所有结构化字段尽量带 evidence_span_idconfidenceextractor_version。email 只来自简历可见、HR 手动补充、或导入数据,不做自动猜测。婚姻/家庭/健康等敏感属性只进入 candidate_sensitive_attributes 受限表,默认不参与匹配分、薪资判断或自动触达。

BOSS HR Browser Agent 在 DATABASE_URL 存在时,会在投递巡检后自动把该次 scan 同步到 Postgres;未配置时仍只写本地采集缓存。

目标边界

  • 读取 HR 当前可见的搜索结果卡片。
  • 可选点开详情弹窗,对在线简历 canvas 截图 OCR,保存简历快照和轻量索引字段。
  • 不保存账号密码。
  • 在线简历长文本只保存 HR 当前账号可见内容的 OCR 快照;联系方式会做基础脱敏。
  • 不采集联系方式。
  • 遇到登录、验证码、账号异常时由人工处理,脚本暂停或退出。

安装

npm install
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

默认使用本机 Chrome。也可以通过环境变量指定浏览器路径:

BOSS_BROWSER_EXECUTABLE="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npm run capture

使用

npm run capture -- --limit 10 --details

首次运行会打开一个独立 Chrome 用户目录 .browser-profile。请在浏览器里手动登录 BOSS,进入人才库搜索页,设置好城市、岗位、关键词和筛选条件。准备好后回到终端按回车,脚本会读取页面上可见候选人。

常用参数:

--limit 20 最多读取多少个候选人卡片
--details 点开详情弹窗并抽取轻量索引
--no-details 只读取列表页卡片
--start-url <url> 起始页面
--user-data-dir <dir> 浏览器登录态目录
--output-dir <dir> 输出目录
--headless 无头模式,不推荐用于首次登录

输出文件。capture 默认写入 data/capture:cdp 默认写入 data-python/

<output-dir>/candidates.ndjson 候选人索引,追加写入
<output-dir>/observations.ndjson 每次搜索观察记录,追加写入
<output-dir>/resume_snapshots.ndjson 在线简历 OCR 快照,追加写入
<output-dir>/runs/run-*.json 单次运行结果,便于复核

候选人索引会保留页面上的 active_status 原始活跃标签,例如 刚刚活跃3日内活跃本周活跃,并推断一个保守的 last_seen_at。区间类标签按区间起点推断,例如 今日活跃 记为当天 00:00,本周活跃 记为本周一 00:00,3日内活跃 记为采集时间减 3 天。

如果你已经在普通 Chrome 里打开并登录了 BOSS 页面,但 Chrome 没有开启远程调试,也没有允许 Apple Events 执行 JavaScript,可以用辅助功能 fallback:

npm run capture:chrome-ax -- --limit 15

这个模式只读取当前 Chrome 窗口里辅助功能树暴露出来的候选人卡片文本,不会点开详情页。

Python + CDP 连接已启动 Chrome:

open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-chrome \
--no-first-run \
https://www.zhipin.com/web/chat/search
npm run capture:cdp -- --limit 10 --skip-apply --detail-max-pages 3

这个模式会连接 9222 端口上的 Chrome,用 DOM 读取页面,比辅助功能 fallback 快很多。 详情页正文由 BOSS 的 canvas 渲染,脚本会裁剪详情弹窗截图并用 macOS Vision OCR 读取文字;原始截图会保存在 data-python/resume-screenshots/<run-id>/ 便于复核。

如果 BOSS 的职位侧和人才库侧必须使用两个不同账号,请启动两个独立 Chrome 用户目录和两个 CDP 端口。不要在同一个 Chrome profile 里开两个账号,同站 cookie 会互相覆盖:

# 人才库/招聘者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-candidates \
--no-first-run \
https://www.zhipin.com/web/chat/search
# 职位侧/求职者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9223 \
--remote-allow-origins=http://127.0.0.1:9223 \
--user-data-dir=/tmp/boss-rpa-jobs \
--no-first-run \
"https://www.zhipin.com/web/geek/jobs?city=100010000"

分别登录两个账号后,统一入口默认会用 9222 抓人才库、9223 抓职位侧:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

如果你想换端口,可以显式传:

npm run org:intel -- \
--company 月之暗面 \
--jobs-cdp-url http://127.0.0.1:9223 \
--candidates-cdp-url http://127.0.0.1:9222 \
--refresh auto \
--report

也可以让脚本先设置搜索条件,再抓取。例如搜索「上海 / 不限职位 / 腾讯」:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--limit 90 \
--detail-max-pages 2

只设置搜索条件、不抓取:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--apply-only

注意:BOSS 下拉里的「热门」是城市列表分组,不是最终城市值;脚本会真实点击它,但页面城市不会变成「热门」。量产时建议传具体城市,比如 北京上海深圳

如果当前搜索结果每次只加载 15 个,可以让脚本先滚动加载更多:

npm run capture:cdp -- --limit 100 --skip-apply --no-details

--limit 100 会滚动到累计 100 个候选人或没有更多结果为止。要一直加载到列表没有新增候选人:

npm run capture:cdp -- --load-all --skip-apply --no-details

量产抓详情时可以去掉 --no-details,并用 --detail-max-pages 12 控制每份在线简历 OCR 的页数。

BOSS 职位侧采集

组织情报还需要招聘岗位信号。职位侧采集默认连接 9223 端口上的职位侧/求职者 Chrome,会打开 BOSS 的 /web/geek/jobs 职位搜索页,先读取左侧职位列表,再逐个点击职位卡读取右侧详情:

npm run capture:jobs -- \
--company 腾讯 \
--cdp-url http://127.0.0.1:9223 \
--limit 30

默认城市是全国,等价于 BOSS URL 里的 city=100010000。如果要限定城市,可以传 --city 上海--city 101020100--city 热门--city-group hot 仍会展开成常用城市组:北京、上海、深圳、广州、杭州、成都、武汉、南京、苏州、西安。--limit 表示每个城市最多读取多少条左侧职位卡。左侧列表每次加载约 15 条时,脚本会持续滚动到目标数量或没有新增职位。

如果你已经手动打开了类似下面的页面,也可以直接读取当前搜索:

npm run capture:jobs -- --company 月之暗面 --limit 90 --cdp-url http://127.0.0.1:9223 --no-manual-ready

默认会点击每个职位卡并读取右侧 JD;只想快速读取左侧列表时加 --no-details

职位采集输出:

<output-dir>/boss_job_postings.ndjson
<output-dir>/runs/run-*.json

导入 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

组织情报报告

统一入口会编排职位侧采集、人才库采集、入库和报告生成:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

--refresh auto 会先看 SQLite 里是否已有近 24 小时的新鲜数据;缺职位侧数据就跑 capture:jobs,缺人才侧观察就跑 capture:cdp。如果只想生成报告不刷新:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh none \
--report

也可以只刷新某一侧:

npm run org:intel -- --company 月之暗面 --refresh jobs --report
npm run org:intel -- --company 月之暗面 --refresh candidates --report

OpenClaw 对接时使用本地 FastAPI service:

npm run org:service

固定机器部署时建议直接用一键脚本,它会启动两套 CDP Chrome,并把 FastAPI worker 指向对应端口:

ORG_INTEL_HOST=0.0.0.0 ORG_INTEL_PORT=8787 ./scripts/start_org_intel_stack.sh

生产对接建议配置 API token:

ORG_INTEL_API_TOKEN=<shared-secret> npm run org:service

配置后,除 /health 外的 /v1/org-intel/* 请求都需要:

-H "Authorization: Bearer <shared-secret>"

默认端口约定:

BOSS_CANDIDATES_CDP_URL=http://127.0.0.1:9222 # 人才库/招聘者账号
BOSS_JOBS_CDP_URL=http://127.0.0.1:9223 # 职位侧/求职者账号

提交异步情报任务:

curl -X POST http://127.0.0.1:8787/v1/org-intel/requests \
-H 'content-type: application/json' \
-d '{ "company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音", "TikTok", "飞书"], "mode": "standard", "refresh": "auto", "client_request_id": "openclaw-example" }'

如果库里没有新鲜报告,接口会立即返回 job_ideta_secondseta_at。OpenClaw 到时间后轮询:

curl http://127.0.0.1:8787/v1/org-intel/requests/<job_id>

状态可能是 queuedrunning_jobsrunning_candidatesimportinggenerating_reportreadyblocked_needs_humanfailedready 时返回 report_markdown 和结构化 findings;如果 BOSS 触发验证,会返回 blocked_needs_human,由人工在采集浏览器里处理。

CEO 定时周/月报由 Hipilot 或外部 cron 触发,本服务只保存订阅和生成 digest。创建订阅:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "owner_id": "ceo-1", "display_name": "CEO 重点公司监控", "cadence": "weekly_and_monthly", "companies": [ {"company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音"], "mode": "standard"}, {"company": "腾讯", "aliases": ["Tencent", "腾讯云", "微信"], "mode": "standard"}, {"company": "月之暗面", "aliases": ["Moonshot", "Kimi"], "mode": "standard"} ], "timezone": "Asia/Shanghai", "freshness_policy": "auto" }'

到点后触发 digest:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions/<subscription_id>/digest-runs \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{"cadence":"weekly","client_request_id":"hipilot-cron-run-id"}'

如果返回 queued / running,按 eta_at 轮询:

curl -H "Authorization: Bearer <shared-secret>" \
http://127.0.0.1:8787/v1/org-intel/digest-runs/<digest_job_id>

也可以按 owner/subscription/cadence 取最近 digest,例如查看上一次月报:

curl -H "Authorization: Bearer <shared-secret>" \
"http://127.0.0.1:8787/v1/org-intel/digest-runs?owner_id=ceo-1&cadence=monthly&limit=1"

ready 时返回多公司 digest_markdownpartial_ready 会返回可用公司摘要,并在风险区说明阻塞公司;blocked_needs_human 表示 BOSS 账号验证需要运营处理。

导入人才库和职位侧 run 后,可以生成 BOSS-only 组织情报 Markdown:

npm run report:org -- \
--company 腾讯 \
--alias Tencent \
--alias 腾讯科技 \
--db data-python/boss_talent.sqlite

也可以用目标配置:

npm run report:org -- \
--company 腾讯 \
--target-config org_targets.example.json \
--db data-python/boss_talent.sqlite

报告会写入 org-intel/<公司名>/report-*.md,并把报告正文、来源计数和结构化组织判断写入 SQLite 的 org_intel_reports / org_findings 表。正文只输出聚合判断;原始候选人信号以脱敏 ID 折叠展示。

数据策略

这个 MVP 采用 B+C 混合版:

  • B:Playwright 半自动读取页面可见信息。
  • C:候选人索引落主表,在线简历落快照表,避免覆盖历史。

OCR 快照用于内部检索和复核,最终原文仍以 BOSS 页面为准。

PostgreSQL

sql/schema.sql。第一版先落 NDJSON,确认字段稳定后再接 PostgreSQL。

当前也可以把单次 run 导入本地 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

测试

npm test

About

Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - nateEc/candidate-intel-agent: Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles. · GitHub
Skip to content

Repository files navigation

BOSS 人才索引采集助手

这是一个本地 RPA MVP,用于辅助 HR 在已经登录的 BOSS 直聘人才库页面中读取当前可见候选人信息,并写入轻量索引文件。它不是无人值守爬虫,也不绕过登录、验证码、风控或隐藏接口。

新方向:HR Browser Agent

下一阶段会把项目从“采集脚本”升级成“用户本机 HR 浏览器操作 agent”:用户和 OpenClaw HR agent 对话,agent 通过本机 FastAPI service 控制可见 Chrome,完成 BOSS 招聘者登录、发 JD、搜索候选人等操作。

设计文档:

本机启动:

npm run hr:start

如果由 Hermes/OpenClaw agent 自动启动,请用后台模式:

npm run hr:daemon

面向最终用户的轻量安装入口:

curl -fsSL https://raw.githubusercontent.com/nateEc/candidate-intel-agent/main/scripts/bootstrap_boss_hr_agent.sh | bash

安装后会提供本机 CLI:

~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent status
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent start
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent stop
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent doctor

边界不变:验证码、App 安全确认和平台验证必须由用户本人完成;系统只负责打开页面、点击、填表、等待和识别状态。

P0 招聘闭环目前覆盖:招聘者登录、发布/更新/关闭职位、投递收件箱巡检、投递候选人评估入库、弱去重合并,以及“准备打招呼 + 招聘者确认后发送”。投递处理默认使用同一个本机 HR service:

curl -X POST http://127.0.0.1:8790/v1/boss/applications/scan \
-H 'content-type: application/json' \
-d '{"job_filter":"AI工程师 _ 北京 20-30K","limit":20,"include_resumes":true,"dry_run":true}'

智能简历库 v1

智能简历库现在使用 Postgres + pgvector 作为主库。BOSS/SQLite 只保留为采集缓存和回放来源;agent 查询、匹配、复评、归池、邮件草稿都走 Postgres。

本地开发可以直接启动项目自带的 pgvector Docker 容器:

npm run talent:db:start

这会启动/复用 boss-talent-postgres 容器,监听 127.0.0.1:54329,并把连接串写入本地 .env

DATABASE_URL=postgresql://talent:talent_dev_password@127.0.0.1:54329/talent_library

如果使用外部数据库,只要手动在 .env 或 shell 里配置:

export DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/talent_library"

初始化 schema / migration:

npm run talent:migrate

启动 Agent API:

npm run talent:service
curl http://127.0.0.1:8792/health

共享给其他 agent 使用时,不要让对方直连 Postgres。把 Smart Talent Library API 部署到你的服务器上,并配置 API token:

export DATABASE_URL="postgresql://talent:<password>@127.0.0.1:5432/talent_library"export TALENT_LIBRARY_API_TOKEN="<shared-secret>"export TALENT_LIBRARY_HOST="127.0.0.1"export TALENT_LIBRARY_PORT="8792"
npm run talent:service

再用 Nginx/Caddy 把 HTTPS 反代到 127.0.0.1:8792/health 可公开用于探活;所有 /v1/talent/* 请求在配置 token 后都必须带:

-H "Authorization: Bearer <shared-secret>"

如果临时测试必须直接监听公网地址,启动脚本会要求先设置 TALENT_LIBRARY_API_TOKEN

TALENT_LIBRARY_HOST=0.0.0.0 TALENT_LIBRARY_API_TOKEN="<shared-secret>" npm run talent:service

不要开放 Postgres 端口到公网。给外部 agent 配置 docs/hr_agent_skill.md 时,只给 TALENT_LIBRARY_BASE_URLTALENT_LIBRARY_API_TOKEN

完整 ECS/云端部署步骤见 Smart Talent Library Cloud Deployment

把已有 BOSS 采集缓存导入 Postgres:

npm run talent:ingest -- --sqlite-db data-python/boss_talent.sqlite --limit 100

直接把一份简历文本/文件导入智能简历库:

npm run talent:ingest-resume -- --file-path "/path/to/resume.txt"

如果需要一套可重复的测试数据,可以导入 20 个合成候选人:

npm run talent:seed-fake -- --reset-synthetic

这会写入 20 个 synthetic_resume 候选人,覆盖 AI Agent/RAG、后端平台、算法、数据、产品、HRBP/招聘、运营/市场等画像;其中 5 人会追加第二版简历,用于测试 /resume-diff/timeline--reset-synthetic 只清理上一轮合成测试数据,不会删除真实/手工候选人。

Hermes/HR agent 更推荐调用 API,把“LLM 抽取后的结构化 JSON + 原始简历文本”一起写入:

curl -X POST http://127.0.0.1:8792/v1/talent/ingest/resume \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "resume_text": "张三\\n邮箱 zhangsan@example.com\\n期望职位:AI Agent 后端工程师\\n...", "structured_data": { "profile": { "display_name": "张三", "expected_position": "AI Agent 后端工程师", "city": "北京", "education_level": "本科", "work_years_text": "5年", "expected_salary_text": "45-60K" }, "skills": [{"skill_name": "RAG", "evidence_text": "负责 RAG 平台"}], "work_experiences": [] } }'

常用接口:

POST /v1/talent/ingest/boss-snapshot 从现有 BOSS/SQLite 采集缓存入 Postgres
POST /v1/talent/ingest/resume 从粘贴文本/本地文件/base64 文件导入简历
POST /v1/talent/recommend 从公司/项目上下文自动推断招聘需求并推荐候选人
POST /v1/talent/candidates/resolve 用 email/手机号/简历 hash/姓名学校等定位候选人
POST /v1/talent/candidates/merge 合并重复候选人档案
POST /v1/talent/candidates/{id}/enrich 结构化简历、生成信号和证据
GET /v1/talent/candidates/{id} 查看完整候选人档案
POST /v1/talent/candidates/{id}/resume 给已有候选人追加/更新一份新简历版本
GET /v1/talent/candidates/{id}/resume-diff 比较候选人最近两版或指定两版简历
GET /v1/talent/candidates/{id}/timeline 查看候选人简历、匹配、互动、任务时间线
GET /v1/talent/search 关键词/城市/学历/评级/人才池检索
POST /v1/talent/semantic-search 本地 hash embedding 语义检索
POST /v1/talent/match-job 给定 JD 生成候选人排序
POST /v1/talent/pools/auto-assign 自动归入人才池
POST /v1/talent/tasks/review 生成复评/回捞任务
POST /v1/talent/outreach/email-draft 基于可见/导入 email 生成邮件草稿
POST /v1/talent/outreach/batch-draft 为多个候选人批量生成邮件草稿
POST /v1/talent/outreach/email-sent 外部邮件工具发送成功后回写互动记录

这版支持两种简历入库方式:Hermes/HR agent 先用 LLM 抽取结构化 JSON 再调用 /v1/talent/ingest/resume,或服务端用本地规则兜底抽取。已有候选人的新简历走 /v1/talent/candidates/{id}/resume,旧版本会保留,后续可用 /resume-diff/timeline 看变化。所有结构化字段尽量带 evidence_span_idconfidenceextractor_version。email 只来自简历可见、HR 手动补充、或导入数据,不做自动猜测。婚姻/家庭/健康等敏感属性只进入 candidate_sensitive_attributes 受限表,默认不参与匹配分、薪资判断或自动触达。

BOSS HR Browser Agent 在 DATABASE_URL 存在时,会在投递巡检后自动把该次 scan 同步到 Postgres;未配置时仍只写本地采集缓存。

目标边界

  • 读取 HR 当前可见的搜索结果卡片。
  • 可选点开详情弹窗,对在线简历 canvas 截图 OCR,保存简历快照和轻量索引字段。
  • 不保存账号密码。
  • 在线简历长文本只保存 HR 当前账号可见内容的 OCR 快照;联系方式会做基础脱敏。
  • 不采集联系方式。
  • 遇到登录、验证码、账号异常时由人工处理,脚本暂停或退出。

安装

npm install
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

默认使用本机 Chrome。也可以通过环境变量指定浏览器路径:

BOSS_BROWSER_EXECUTABLE="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npm run capture

使用

npm run capture -- --limit 10 --details

首次运行会打开一个独立 Chrome 用户目录 .browser-profile。请在浏览器里手动登录 BOSS,进入人才库搜索页,设置好城市、岗位、关键词和筛选条件。准备好后回到终端按回车,脚本会读取页面上可见候选人。

常用参数:

--limit 20 最多读取多少个候选人卡片
--details 点开详情弹窗并抽取轻量索引
--no-details 只读取列表页卡片
--start-url <url> 起始页面
--user-data-dir <dir> 浏览器登录态目录
--output-dir <dir> 输出目录
--headless 无头模式,不推荐用于首次登录

输出文件。capture 默认写入 data/capture:cdp 默认写入 data-python/

<output-dir>/candidates.ndjson 候选人索引,追加写入
<output-dir>/observations.ndjson 每次搜索观察记录,追加写入
<output-dir>/resume_snapshots.ndjson 在线简历 OCR 快照,追加写入
<output-dir>/runs/run-*.json 单次运行结果,便于复核

候选人索引会保留页面上的 active_status 原始活跃标签,例如 刚刚活跃3日内活跃本周活跃,并推断一个保守的 last_seen_at。区间类标签按区间起点推断,例如 今日活跃 记为当天 00:00,本周活跃 记为本周一 00:00,3日内活跃 记为采集时间减 3 天。

如果你已经在普通 Chrome 里打开并登录了 BOSS 页面,但 Chrome 没有开启远程调试,也没有允许 Apple Events 执行 JavaScript,可以用辅助功能 fallback:

npm run capture:chrome-ax -- --limit 15

这个模式只读取当前 Chrome 窗口里辅助功能树暴露出来的候选人卡片文本,不会点开详情页。

Python + CDP 连接已启动 Chrome:

open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-chrome \
--no-first-run \
https://www.zhipin.com/web/chat/search
npm run capture:cdp -- --limit 10 --skip-apply --detail-max-pages 3

这个模式会连接 9222 端口上的 Chrome,用 DOM 读取页面,比辅助功能 fallback 快很多。 详情页正文由 BOSS 的 canvas 渲染,脚本会裁剪详情弹窗截图并用 macOS Vision OCR 读取文字;原始截图会保存在 data-python/resume-screenshots/<run-id>/ 便于复核。

如果 BOSS 的职位侧和人才库侧必须使用两个不同账号,请启动两个独立 Chrome 用户目录和两个 CDP 端口。不要在同一个 Chrome profile 里开两个账号,同站 cookie 会互相覆盖:

# 人才库/招聘者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-candidates \
--no-first-run \
https://www.zhipin.com/web/chat/search
# 职位侧/求职者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9223 \
--remote-allow-origins=http://127.0.0.1:9223 \
--user-data-dir=/tmp/boss-rpa-jobs \
--no-first-run \
"https://www.zhipin.com/web/geek/jobs?city=100010000"

分别登录两个账号后,统一入口默认会用 9222 抓人才库、9223 抓职位侧:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

如果你想换端口,可以显式传:

npm run org:intel -- \
--company 月之暗面 \
--jobs-cdp-url http://127.0.0.1:9223 \
--candidates-cdp-url http://127.0.0.1:9222 \
--refresh auto \
--report

也可以让脚本先设置搜索条件,再抓取。例如搜索「上海 / 不限职位 / 腾讯」:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--limit 90 \
--detail-max-pages 2

只设置搜索条件、不抓取:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--apply-only

注意:BOSS 下拉里的「热门」是城市列表分组,不是最终城市值;脚本会真实点击它,但页面城市不会变成「热门」。量产时建议传具体城市,比如 北京上海深圳

如果当前搜索结果每次只加载 15 个,可以让脚本先滚动加载更多:

npm run capture:cdp -- --limit 100 --skip-apply --no-details

--limit 100 会滚动到累计 100 个候选人或没有更多结果为止。要一直加载到列表没有新增候选人:

npm run capture:cdp -- --load-all --skip-apply --no-details

量产抓详情时可以去掉 --no-details,并用 --detail-max-pages 12 控制每份在线简历 OCR 的页数。

BOSS 职位侧采集

组织情报还需要招聘岗位信号。职位侧采集默认连接 9223 端口上的职位侧/求职者 Chrome,会打开 BOSS 的 /web/geek/jobs 职位搜索页,先读取左侧职位列表,再逐个点击职位卡读取右侧详情:

npm run capture:jobs -- \
--company 腾讯 \
--cdp-url http://127.0.0.1:9223 \
--limit 30

默认城市是全国,等价于 BOSS URL 里的 city=100010000。如果要限定城市,可以传 --city 上海--city 101020100--city 热门--city-group hot 仍会展开成常用城市组:北京、上海、深圳、广州、杭州、成都、武汉、南京、苏州、西安。--limit 表示每个城市最多读取多少条左侧职位卡。左侧列表每次加载约 15 条时,脚本会持续滚动到目标数量或没有新增职位。

如果你已经手动打开了类似下面的页面,也可以直接读取当前搜索:

npm run capture:jobs -- --company 月之暗面 --limit 90 --cdp-url http://127.0.0.1:9223 --no-manual-ready

默认会点击每个职位卡并读取右侧 JD;只想快速读取左侧列表时加 --no-details

职位采集输出:

<output-dir>/boss_job_postings.ndjson
<output-dir>/runs/run-*.json

导入 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

组织情报报告

统一入口会编排职位侧采集、人才库采集、入库和报告生成:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

--refresh auto 会先看 SQLite 里是否已有近 24 小时的新鲜数据;缺职位侧数据就跑 capture:jobs,缺人才侧观察就跑 capture:cdp。如果只想生成报告不刷新:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh none \
--report

也可以只刷新某一侧:

npm run org:intel -- --company 月之暗面 --refresh jobs --report
npm run org:intel -- --company 月之暗面 --refresh candidates --report

OpenClaw 对接时使用本地 FastAPI service:

npm run org:service

固定机器部署时建议直接用一键脚本,它会启动两套 CDP Chrome,并把 FastAPI worker 指向对应端口:

ORG_INTEL_HOST=0.0.0.0 ORG_INTEL_PORT=8787 ./scripts/start_org_intel_stack.sh

生产对接建议配置 API token:

ORG_INTEL_API_TOKEN=<shared-secret> npm run org:service

配置后,除 /health 外的 /v1/org-intel/* 请求都需要:

-H "Authorization: Bearer <shared-secret>"

默认端口约定:

BOSS_CANDIDATES_CDP_URL=http://127.0.0.1:9222 # 人才库/招聘者账号
BOSS_JOBS_CDP_URL=http://127.0.0.1:9223 # 职位侧/求职者账号

提交异步情报任务:

curl -X POST http://127.0.0.1:8787/v1/org-intel/requests \
-H 'content-type: application/json' \
-d '{ "company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音", "TikTok", "飞书"], "mode": "standard", "refresh": "auto", "client_request_id": "openclaw-example" }'

如果库里没有新鲜报告,接口会立即返回 job_ideta_secondseta_at。OpenClaw 到时间后轮询:

curl http://127.0.0.1:8787/v1/org-intel/requests/<job_id>

状态可能是 queuedrunning_jobsrunning_candidatesimportinggenerating_reportreadyblocked_needs_humanfailedready 时返回 report_markdown 和结构化 findings;如果 BOSS 触发验证,会返回 blocked_needs_human,由人工在采集浏览器里处理。

CEO 定时周/月报由 Hipilot 或外部 cron 触发,本服务只保存订阅和生成 digest。创建订阅:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "owner_id": "ceo-1", "display_name": "CEO 重点公司监控", "cadence": "weekly_and_monthly", "companies": [ {"company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音"], "mode": "standard"}, {"company": "腾讯", "aliases": ["Tencent", "腾讯云", "微信"], "mode": "standard"}, {"company": "月之暗面", "aliases": ["Moonshot", "Kimi"], "mode": "standard"} ], "timezone": "Asia/Shanghai", "freshness_policy": "auto" }'

到点后触发 digest:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions/<subscription_id>/digest-runs \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{"cadence":"weekly","client_request_id":"hipilot-cron-run-id"}'

如果返回 queued / running,按 eta_at 轮询:

curl -H "Authorization: Bearer <shared-secret>" \
http://127.0.0.1:8787/v1/org-intel/digest-runs/<digest_job_id>

也可以按 owner/subscription/cadence 取最近 digest,例如查看上一次月报:

curl -H "Authorization: Bearer <shared-secret>" \
"http://127.0.0.1:8787/v1/org-intel/digest-runs?owner_id=ceo-1&cadence=monthly&limit=1"

ready 时返回多公司 digest_markdownpartial_ready 会返回可用公司摘要,并在风险区说明阻塞公司;blocked_needs_human 表示 BOSS 账号验证需要运营处理。

导入人才库和职位侧 run 后,可以生成 BOSS-only 组织情报 Markdown:

npm run report:org -- \
--company 腾讯 \
--alias Tencent \
--alias 腾讯科技 \
--db data-python/boss_talent.sqlite

也可以用目标配置:

npm run report:org -- \
--company 腾讯 \
--target-config org_targets.example.json \
--db data-python/boss_talent.sqlite

报告会写入 org-intel/<公司名>/report-*.md,并把报告正文、来源计数和结构化组织判断写入 SQLite 的 org_intel_reports / org_findings 表。正文只输出聚合判断;原始候选人信号以脱敏 ID 折叠展示。

数据策略

这个 MVP 采用 B+C 混合版:

  • B:Playwright 半自动读取页面可见信息。
  • C:候选人索引落主表,在线简历落快照表,避免覆盖历史。

OCR 快照用于内部检索和复核,最终原文仍以 BOSS 页面为准。

PostgreSQL

sql/schema.sql。第一版先落 NDJSON,确认字段稳定后再接 PostgreSQL。

当前也可以把单次 run 导入本地 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

测试

npm test

About

Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - nateEc/candidate-intel-agent: Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles. · GitHub
Skip to content

Repository files navigation

BOSS 人才索引采集助手

这是一个本地 RPA MVP,用于辅助 HR 在已经登录的 BOSS 直聘人才库页面中读取当前可见候选人信息,并写入轻量索引文件。它不是无人值守爬虫,也不绕过登录、验证码、风控或隐藏接口。

新方向:HR Browser Agent

下一阶段会把项目从“采集脚本”升级成“用户本机 HR 浏览器操作 agent”:用户和 OpenClaw HR agent 对话,agent 通过本机 FastAPI service 控制可见 Chrome,完成 BOSS 招聘者登录、发 JD、搜索候选人等操作。

设计文档:

本机启动:

npm run hr:start

如果由 Hermes/OpenClaw agent 自动启动,请用后台模式:

npm run hr:daemon

面向最终用户的轻量安装入口:

curl -fsSL https://raw.githubusercontent.com/nateEc/candidate-intel-agent/main/scripts/bootstrap_boss_hr_agent.sh | bash

安装后会提供本机 CLI:

~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent status
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent start
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent stop
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent doctor

边界不变:验证码、App 安全确认和平台验证必须由用户本人完成;系统只负责打开页面、点击、填表、等待和识别状态。

P0 招聘闭环目前覆盖:招聘者登录、发布/更新/关闭职位、投递收件箱巡检、投递候选人评估入库、弱去重合并,以及“准备打招呼 + 招聘者确认后发送”。投递处理默认使用同一个本机 HR service:

curl -X POST http://127.0.0.1:8790/v1/boss/applications/scan \
-H 'content-type: application/json' \
-d '{"job_filter":"AI工程师 _ 北京 20-30K","limit":20,"include_resumes":true,"dry_run":true}'

智能简历库 v1

智能简历库现在使用 Postgres + pgvector 作为主库。BOSS/SQLite 只保留为采集缓存和回放来源;agent 查询、匹配、复评、归池、邮件草稿都走 Postgres。

本地开发可以直接启动项目自带的 pgvector Docker 容器:

npm run talent:db:start

这会启动/复用 boss-talent-postgres 容器,监听 127.0.0.1:54329,并把连接串写入本地 .env

DATABASE_URL=postgresql://talent:talent_dev_password@127.0.0.1:54329/talent_library

如果使用外部数据库,只要手动在 .env 或 shell 里配置:

export DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/talent_library"

初始化 schema / migration:

npm run talent:migrate

启动 Agent API:

npm run talent:service
curl http://127.0.0.1:8792/health

共享给其他 agent 使用时,不要让对方直连 Postgres。把 Smart Talent Library API 部署到你的服务器上,并配置 API token:

export DATABASE_URL="postgresql://talent:<password>@127.0.0.1:5432/talent_library"export TALENT_LIBRARY_API_TOKEN="<shared-secret>"export TALENT_LIBRARY_HOST="127.0.0.1"export TALENT_LIBRARY_PORT="8792"
npm run talent:service

再用 Nginx/Caddy 把 HTTPS 反代到 127.0.0.1:8792/health 可公开用于探活;所有 /v1/talent/* 请求在配置 token 后都必须带:

-H "Authorization: Bearer <shared-secret>"

如果临时测试必须直接监听公网地址,启动脚本会要求先设置 TALENT_LIBRARY_API_TOKEN

TALENT_LIBRARY_HOST=0.0.0.0 TALENT_LIBRARY_API_TOKEN="<shared-secret>" npm run talent:service

不要开放 Postgres 端口到公网。给外部 agent 配置 docs/hr_agent_skill.md 时,只给 TALENT_LIBRARY_BASE_URLTALENT_LIBRARY_API_TOKEN

完整 ECS/云端部署步骤见 Smart Talent Library Cloud Deployment

把已有 BOSS 采集缓存导入 Postgres:

npm run talent:ingest -- --sqlite-db data-python/boss_talent.sqlite --limit 100

直接把一份简历文本/文件导入智能简历库:

npm run talent:ingest-resume -- --file-path "/path/to/resume.txt"

如果需要一套可重复的测试数据,可以导入 20 个合成候选人:

npm run talent:seed-fake -- --reset-synthetic

这会写入 20 个 synthetic_resume 候选人,覆盖 AI Agent/RAG、后端平台、算法、数据、产品、HRBP/招聘、运营/市场等画像;其中 5 人会追加第二版简历,用于测试 /resume-diff/timeline--reset-synthetic 只清理上一轮合成测试数据,不会删除真实/手工候选人。

Hermes/HR agent 更推荐调用 API,把“LLM 抽取后的结构化 JSON + 原始简历文本”一起写入:

curl -X POST http://127.0.0.1:8792/v1/talent/ingest/resume \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "resume_text": "张三\\n邮箱 zhangsan@example.com\\n期望职位:AI Agent 后端工程师\\n...", "structured_data": { "profile": { "display_name": "张三", "expected_position": "AI Agent 后端工程师", "city": "北京", "education_level": "本科", "work_years_text": "5年", "expected_salary_text": "45-60K" }, "skills": [{"skill_name": "RAG", "evidence_text": "负责 RAG 平台"}], "work_experiences": [] } }'

常用接口:

POST /v1/talent/ingest/boss-snapshot 从现有 BOSS/SQLite 采集缓存入 Postgres
POST /v1/talent/ingest/resume 从粘贴文本/本地文件/base64 文件导入简历
POST /v1/talent/recommend 从公司/项目上下文自动推断招聘需求并推荐候选人
POST /v1/talent/candidates/resolve 用 email/手机号/简历 hash/姓名学校等定位候选人
POST /v1/talent/candidates/merge 合并重复候选人档案
POST /v1/talent/candidates/{id}/enrich 结构化简历、生成信号和证据
GET /v1/talent/candidates/{id} 查看完整候选人档案
POST /v1/talent/candidates/{id}/resume 给已有候选人追加/更新一份新简历版本
GET /v1/talent/candidates/{id}/resume-diff 比较候选人最近两版或指定两版简历
GET /v1/talent/candidates/{id}/timeline 查看候选人简历、匹配、互动、任务时间线
GET /v1/talent/search 关键词/城市/学历/评级/人才池检索
POST /v1/talent/semantic-search 本地 hash embedding 语义检索
POST /v1/talent/match-job 给定 JD 生成候选人排序
POST /v1/talent/pools/auto-assign 自动归入人才池
POST /v1/talent/tasks/review 生成复评/回捞任务
POST /v1/talent/outreach/email-draft 基于可见/导入 email 生成邮件草稿
POST /v1/talent/outreach/batch-draft 为多个候选人批量生成邮件草稿
POST /v1/talent/outreach/email-sent 外部邮件工具发送成功后回写互动记录

这版支持两种简历入库方式:Hermes/HR agent 先用 LLM 抽取结构化 JSON 再调用 /v1/talent/ingest/resume,或服务端用本地规则兜底抽取。已有候选人的新简历走 /v1/talent/candidates/{id}/resume,旧版本会保留,后续可用 /resume-diff/timeline 看变化。所有结构化字段尽量带 evidence_span_idconfidenceextractor_version。email 只来自简历可见、HR 手动补充、或导入数据,不做自动猜测。婚姻/家庭/健康等敏感属性只进入 candidate_sensitive_attributes 受限表,默认不参与匹配分、薪资判断或自动触达。

BOSS HR Browser Agent 在 DATABASE_URL 存在时,会在投递巡检后自动把该次 scan 同步到 Postgres;未配置时仍只写本地采集缓存。

目标边界

  • 读取 HR 当前可见的搜索结果卡片。
  • 可选点开详情弹窗,对在线简历 canvas 截图 OCR,保存简历快照和轻量索引字段。
  • 不保存账号密码。
  • 在线简历长文本只保存 HR 当前账号可见内容的 OCR 快照;联系方式会做基础脱敏。
  • 不采集联系方式。
  • 遇到登录、验证码、账号异常时由人工处理,脚本暂停或退出。

安装

npm install
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

默认使用本机 Chrome。也可以通过环境变量指定浏览器路径:

BOSS_BROWSER_EXECUTABLE="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npm run capture

使用

npm run capture -- --limit 10 --details

首次运行会打开一个独立 Chrome 用户目录 .browser-profile。请在浏览器里手动登录 BOSS,进入人才库搜索页,设置好城市、岗位、关键词和筛选条件。准备好后回到终端按回车,脚本会读取页面上可见候选人。

常用参数:

--limit 20 最多读取多少个候选人卡片
--details 点开详情弹窗并抽取轻量索引
--no-details 只读取列表页卡片
--start-url <url> 起始页面
--user-data-dir <dir> 浏览器登录态目录
--output-dir <dir> 输出目录
--headless 无头模式,不推荐用于首次登录

输出文件。capture 默认写入 data/capture:cdp 默认写入 data-python/

<output-dir>/candidates.ndjson 候选人索引,追加写入
<output-dir>/observations.ndjson 每次搜索观察记录,追加写入
<output-dir>/resume_snapshots.ndjson 在线简历 OCR 快照,追加写入
<output-dir>/runs/run-*.json 单次运行结果,便于复核

候选人索引会保留页面上的 active_status 原始活跃标签,例如 刚刚活跃3日内活跃本周活跃,并推断一个保守的 last_seen_at。区间类标签按区间起点推断,例如 今日活跃 记为当天 00:00,本周活跃 记为本周一 00:00,3日内活跃 记为采集时间减 3 天。

如果你已经在普通 Chrome 里打开并登录了 BOSS 页面,但 Chrome 没有开启远程调试,也没有允许 Apple Events 执行 JavaScript,可以用辅助功能 fallback:

npm run capture:chrome-ax -- --limit 15

这个模式只读取当前 Chrome 窗口里辅助功能树暴露出来的候选人卡片文本,不会点开详情页。

Python + CDP 连接已启动 Chrome:

open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-chrome \
--no-first-run \
https://www.zhipin.com/web/chat/search
npm run capture:cdp -- --limit 10 --skip-apply --detail-max-pages 3

这个模式会连接 9222 端口上的 Chrome,用 DOM 读取页面,比辅助功能 fallback 快很多。 详情页正文由 BOSS 的 canvas 渲染,脚本会裁剪详情弹窗截图并用 macOS Vision OCR 读取文字;原始截图会保存在 data-python/resume-screenshots/<run-id>/ 便于复核。

如果 BOSS 的职位侧和人才库侧必须使用两个不同账号,请启动两个独立 Chrome 用户目录和两个 CDP 端口。不要在同一个 Chrome profile 里开两个账号,同站 cookie 会互相覆盖:

# 人才库/招聘者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-candidates \
--no-first-run \
https://www.zhipin.com/web/chat/search
# 职位侧/求职者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9223 \
--remote-allow-origins=http://127.0.0.1:9223 \
--user-data-dir=/tmp/boss-rpa-jobs \
--no-first-run \
"https://www.zhipin.com/web/geek/jobs?city=100010000"

分别登录两个账号后,统一入口默认会用 9222 抓人才库、9223 抓职位侧:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

如果你想换端口,可以显式传:

npm run org:intel -- \
--company 月之暗面 \
--jobs-cdp-url http://127.0.0.1:9223 \
--candidates-cdp-url http://127.0.0.1:9222 \
--refresh auto \
--report

也可以让脚本先设置搜索条件,再抓取。例如搜索「上海 / 不限职位 / 腾讯」:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--limit 90 \
--detail-max-pages 2

只设置搜索条件、不抓取:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--apply-only

注意:BOSS 下拉里的「热门」是城市列表分组,不是最终城市值;脚本会真实点击它,但页面城市不会变成「热门」。量产时建议传具体城市,比如 北京上海深圳

如果当前搜索结果每次只加载 15 个,可以让脚本先滚动加载更多:

npm run capture:cdp -- --limit 100 --skip-apply --no-details

--limit 100 会滚动到累计 100 个候选人或没有更多结果为止。要一直加载到列表没有新增候选人:

npm run capture:cdp -- --load-all --skip-apply --no-details

量产抓详情时可以去掉 --no-details,并用 --detail-max-pages 12 控制每份在线简历 OCR 的页数。

BOSS 职位侧采集

组织情报还需要招聘岗位信号。职位侧采集默认连接 9223 端口上的职位侧/求职者 Chrome,会打开 BOSS 的 /web/geek/jobs 职位搜索页,先读取左侧职位列表,再逐个点击职位卡读取右侧详情:

npm run capture:jobs -- \
--company 腾讯 \
--cdp-url http://127.0.0.1:9223 \
--limit 30

默认城市是全国,等价于 BOSS URL 里的 city=100010000。如果要限定城市,可以传 --city 上海--city 101020100--city 热门--city-group hot 仍会展开成常用城市组:北京、上海、深圳、广州、杭州、成都、武汉、南京、苏州、西安。--limit 表示每个城市最多读取多少条左侧职位卡。左侧列表每次加载约 15 条时,脚本会持续滚动到目标数量或没有新增职位。

如果你已经手动打开了类似下面的页面,也可以直接读取当前搜索:

npm run capture:jobs -- --company 月之暗面 --limit 90 --cdp-url http://127.0.0.1:9223 --no-manual-ready

默认会点击每个职位卡并读取右侧 JD;只想快速读取左侧列表时加 --no-details

职位采集输出:

<output-dir>/boss_job_postings.ndjson
<output-dir>/runs/run-*.json

导入 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

组织情报报告

统一入口会编排职位侧采集、人才库采集、入库和报告生成:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

--refresh auto 会先看 SQLite 里是否已有近 24 小时的新鲜数据;缺职位侧数据就跑 capture:jobs,缺人才侧观察就跑 capture:cdp。如果只想生成报告不刷新:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh none \
--report

也可以只刷新某一侧:

npm run org:intel -- --company 月之暗面 --refresh jobs --report
npm run org:intel -- --company 月之暗面 --refresh candidates --report

OpenClaw 对接时使用本地 FastAPI service:

npm run org:service

固定机器部署时建议直接用一键脚本,它会启动两套 CDP Chrome,并把 FastAPI worker 指向对应端口:

ORG_INTEL_HOST=0.0.0.0 ORG_INTEL_PORT=8787 ./scripts/start_org_intel_stack.sh

生产对接建议配置 API token:

ORG_INTEL_API_TOKEN=<shared-secret> npm run org:service

配置后,除 /health 外的 /v1/org-intel/* 请求都需要:

-H "Authorization: Bearer <shared-secret>"

默认端口约定:

BOSS_CANDIDATES_CDP_URL=http://127.0.0.1:9222 # 人才库/招聘者账号
BOSS_JOBS_CDP_URL=http://127.0.0.1:9223 # 职位侧/求职者账号

提交异步情报任务:

curl -X POST http://127.0.0.1:8787/v1/org-intel/requests \
-H 'content-type: application/json' \
-d '{ "company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音", "TikTok", "飞书"], "mode": "standard", "refresh": "auto", "client_request_id": "openclaw-example" }'

如果库里没有新鲜报告,接口会立即返回 job_ideta_secondseta_at。OpenClaw 到时间后轮询:

curl http://127.0.0.1:8787/v1/org-intel/requests/<job_id>

状态可能是 queuedrunning_jobsrunning_candidatesimportinggenerating_reportreadyblocked_needs_humanfailedready 时返回 report_markdown 和结构化 findings;如果 BOSS 触发验证,会返回 blocked_needs_human,由人工在采集浏览器里处理。

CEO 定时周/月报由 Hipilot 或外部 cron 触发,本服务只保存订阅和生成 digest。创建订阅:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "owner_id": "ceo-1", "display_name": "CEO 重点公司监控", "cadence": "weekly_and_monthly", "companies": [ {"company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音"], "mode": "standard"}, {"company": "腾讯", "aliases": ["Tencent", "腾讯云", "微信"], "mode": "standard"}, {"company": "月之暗面", "aliases": ["Moonshot", "Kimi"], "mode": "standard"} ], "timezone": "Asia/Shanghai", "freshness_policy": "auto" }'

到点后触发 digest:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions/<subscription_id>/digest-runs \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{"cadence":"weekly","client_request_id":"hipilot-cron-run-id"}'

如果返回 queued / running,按 eta_at 轮询:

curl -H "Authorization: Bearer <shared-secret>" \
http://127.0.0.1:8787/v1/org-intel/digest-runs/<digest_job_id>

也可以按 owner/subscription/cadence 取最近 digest,例如查看上一次月报:

curl -H "Authorization: Bearer <shared-secret>" \
"http://127.0.0.1:8787/v1/org-intel/digest-runs?owner_id=ceo-1&cadence=monthly&limit=1"

ready 时返回多公司 digest_markdownpartial_ready 会返回可用公司摘要,并在风险区说明阻塞公司;blocked_needs_human 表示 BOSS 账号验证需要运营处理。

导入人才库和职位侧 run 后,可以生成 BOSS-only 组织情报 Markdown:

npm run report:org -- \
--company 腾讯 \
--alias Tencent \
--alias 腾讯科技 \
--db data-python/boss_talent.sqlite

也可以用目标配置:

npm run report:org -- \
--company 腾讯 \
--target-config org_targets.example.json \
--db data-python/boss_talent.sqlite

报告会写入 org-intel/<公司名>/report-*.md,并把报告正文、来源计数和结构化组织判断写入 SQLite 的 org_intel_reports / org_findings 表。正文只输出聚合判断;原始候选人信号以脱敏 ID 折叠展示。

数据策略

这个 MVP 采用 B+C 混合版:

  • B:Playwright 半自动读取页面可见信息。
  • C:候选人索引落主表,在线简历落快照表,避免覆盖历史。

OCR 快照用于内部检索和复核,最终原文仍以 BOSS 页面为准。

PostgreSQL

sql/schema.sql。第一版先落 NDJSON,确认字段稳定后再接 PostgreSQL。

当前也可以把单次 run 导入本地 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

测试

npm test

About

Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - nateEc/candidate-intel-agent: Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles. · GitHub
Skip to content

Repository files navigation

BOSS 人才索引采集助手

这是一个本地 RPA MVP,用于辅助 HR 在已经登录的 BOSS 直聘人才库页面中读取当前可见候选人信息,并写入轻量索引文件。它不是无人值守爬虫,也不绕过登录、验证码、风控或隐藏接口。

新方向:HR Browser Agent

下一阶段会把项目从“采集脚本”升级成“用户本机 HR 浏览器操作 agent”:用户和 OpenClaw HR agent 对话,agent 通过本机 FastAPI service 控制可见 Chrome,完成 BOSS 招聘者登录、发 JD、搜索候选人等操作。

设计文档:

本机启动:

npm run hr:start

如果由 Hermes/OpenClaw agent 自动启动,请用后台模式:

npm run hr:daemon

面向最终用户的轻量安装入口:

curl -fsSL https://raw.githubusercontent.com/nateEc/candidate-intel-agent/main/scripts/bootstrap_boss_hr_agent.sh | bash

安装后会提供本机 CLI:

~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent status
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent start
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent stop
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent doctor

边界不变:验证码、App 安全确认和平台验证必须由用户本人完成;系统只负责打开页面、点击、填表、等待和识别状态。

P0 招聘闭环目前覆盖:招聘者登录、发布/更新/关闭职位、投递收件箱巡检、投递候选人评估入库、弱去重合并,以及“准备打招呼 + 招聘者确认后发送”。投递处理默认使用同一个本机 HR service:

curl -X POST http://127.0.0.1:8790/v1/boss/applications/scan \
-H 'content-type: application/json' \
-d '{"job_filter":"AI工程师 _ 北京 20-30K","limit":20,"include_resumes":true,"dry_run":true}'

智能简历库 v1

智能简历库现在使用 Postgres + pgvector 作为主库。BOSS/SQLite 只保留为采集缓存和回放来源;agent 查询、匹配、复评、归池、邮件草稿都走 Postgres。

本地开发可以直接启动项目自带的 pgvector Docker 容器:

npm run talent:db:start

这会启动/复用 boss-talent-postgres 容器,监听 127.0.0.1:54329,并把连接串写入本地 .env

DATABASE_URL=postgresql://talent:talent_dev_password@127.0.0.1:54329/talent_library

如果使用外部数据库,只要手动在 .env 或 shell 里配置:

export DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/talent_library"

初始化 schema / migration:

npm run talent:migrate

启动 Agent API:

npm run talent:service
curl http://127.0.0.1:8792/health

共享给其他 agent 使用时,不要让对方直连 Postgres。把 Smart Talent Library API 部署到你的服务器上,并配置 API token:

export DATABASE_URL="postgresql://talent:<password>@127.0.0.1:5432/talent_library"export TALENT_LIBRARY_API_TOKEN="<shared-secret>"export TALENT_LIBRARY_HOST="127.0.0.1"export TALENT_LIBRARY_PORT="8792"
npm run talent:service

再用 Nginx/Caddy 把 HTTPS 反代到 127.0.0.1:8792/health 可公开用于探活;所有 /v1/talent/* 请求在配置 token 后都必须带:

-H "Authorization: Bearer <shared-secret>"

如果临时测试必须直接监听公网地址,启动脚本会要求先设置 TALENT_LIBRARY_API_TOKEN

TALENT_LIBRARY_HOST=0.0.0.0 TALENT_LIBRARY_API_TOKEN="<shared-secret>" npm run talent:service

不要开放 Postgres 端口到公网。给外部 agent 配置 docs/hr_agent_skill.md 时,只给 TALENT_LIBRARY_BASE_URLTALENT_LIBRARY_API_TOKEN

完整 ECS/云端部署步骤见 Smart Talent Library Cloud Deployment

把已有 BOSS 采集缓存导入 Postgres:

npm run talent:ingest -- --sqlite-db data-python/boss_talent.sqlite --limit 100

直接把一份简历文本/文件导入智能简历库:

npm run talent:ingest-resume -- --file-path "/path/to/resume.txt"

如果需要一套可重复的测试数据,可以导入 20 个合成候选人:

npm run talent:seed-fake -- --reset-synthetic

这会写入 20 个 synthetic_resume 候选人,覆盖 AI Agent/RAG、后端平台、算法、数据、产品、HRBP/招聘、运营/市场等画像;其中 5 人会追加第二版简历,用于测试 /resume-diff/timeline--reset-synthetic 只清理上一轮合成测试数据,不会删除真实/手工候选人。

Hermes/HR agent 更推荐调用 API,把“LLM 抽取后的结构化 JSON + 原始简历文本”一起写入:

curl -X POST http://127.0.0.1:8792/v1/talent/ingest/resume \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "resume_text": "张三\\n邮箱 zhangsan@example.com\\n期望职位:AI Agent 后端工程师\\n...", "structured_data": { "profile": { "display_name": "张三", "expected_position": "AI Agent 后端工程师", "city": "北京", "education_level": "本科", "work_years_text": "5年", "expected_salary_text": "45-60K" }, "skills": [{"skill_name": "RAG", "evidence_text": "负责 RAG 平台"}], "work_experiences": [] } }'

常用接口:

POST /v1/talent/ingest/boss-snapshot 从现有 BOSS/SQLite 采集缓存入 Postgres
POST /v1/talent/ingest/resume 从粘贴文本/本地文件/base64 文件导入简历
POST /v1/talent/recommend 从公司/项目上下文自动推断招聘需求并推荐候选人
POST /v1/talent/candidates/resolve 用 email/手机号/简历 hash/姓名学校等定位候选人
POST /v1/talent/candidates/merge 合并重复候选人档案
POST /v1/talent/candidates/{id}/enrich 结构化简历、生成信号和证据
GET /v1/talent/candidates/{id} 查看完整候选人档案
POST /v1/talent/candidates/{id}/resume 给已有候选人追加/更新一份新简历版本
GET /v1/talent/candidates/{id}/resume-diff 比较候选人最近两版或指定两版简历
GET /v1/talent/candidates/{id}/timeline 查看候选人简历、匹配、互动、任务时间线
GET /v1/talent/search 关键词/城市/学历/评级/人才池检索
POST /v1/talent/semantic-search 本地 hash embedding 语义检索
POST /v1/talent/match-job 给定 JD 生成候选人排序
POST /v1/talent/pools/auto-assign 自动归入人才池
POST /v1/talent/tasks/review 生成复评/回捞任务
POST /v1/talent/outreach/email-draft 基于可见/导入 email 生成邮件草稿
POST /v1/talent/outreach/batch-draft 为多个候选人批量生成邮件草稿
POST /v1/talent/outreach/email-sent 外部邮件工具发送成功后回写互动记录

这版支持两种简历入库方式:Hermes/HR agent 先用 LLM 抽取结构化 JSON 再调用 /v1/talent/ingest/resume,或服务端用本地规则兜底抽取。已有候选人的新简历走 /v1/talent/candidates/{id}/resume,旧版本会保留,后续可用 /resume-diff/timeline 看变化。所有结构化字段尽量带 evidence_span_idconfidenceextractor_version。email 只来自简历可见、HR 手动补充、或导入数据,不做自动猜测。婚姻/家庭/健康等敏感属性只进入 candidate_sensitive_attributes 受限表,默认不参与匹配分、薪资判断或自动触达。

BOSS HR Browser Agent 在 DATABASE_URL 存在时,会在投递巡检后自动把该次 scan 同步到 Postgres;未配置时仍只写本地采集缓存。

目标边界

  • 读取 HR 当前可见的搜索结果卡片。
  • 可选点开详情弹窗,对在线简历 canvas 截图 OCR,保存简历快照和轻量索引字段。
  • 不保存账号密码。
  • 在线简历长文本只保存 HR 当前账号可见内容的 OCR 快照;联系方式会做基础脱敏。
  • 不采集联系方式。
  • 遇到登录、验证码、账号异常时由人工处理,脚本暂停或退出。

安装

npm install
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

默认使用本机 Chrome。也可以通过环境变量指定浏览器路径:

BOSS_BROWSER_EXECUTABLE="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npm run capture

使用

npm run capture -- --limit 10 --details

首次运行会打开一个独立 Chrome 用户目录 .browser-profile。请在浏览器里手动登录 BOSS,进入人才库搜索页,设置好城市、岗位、关键词和筛选条件。准备好后回到终端按回车,脚本会读取页面上可见候选人。

常用参数:

--limit 20 最多读取多少个候选人卡片
--details 点开详情弹窗并抽取轻量索引
--no-details 只读取列表页卡片
--start-url <url> 起始页面
--user-data-dir <dir> 浏览器登录态目录
--output-dir <dir> 输出目录
--headless 无头模式,不推荐用于首次登录

输出文件。capture 默认写入 data/capture:cdp 默认写入 data-python/

<output-dir>/candidates.ndjson 候选人索引,追加写入
<output-dir>/observations.ndjson 每次搜索观察记录,追加写入
<output-dir>/resume_snapshots.ndjson 在线简历 OCR 快照,追加写入
<output-dir>/runs/run-*.json 单次运行结果,便于复核

候选人索引会保留页面上的 active_status 原始活跃标签,例如 刚刚活跃3日内活跃本周活跃,并推断一个保守的 last_seen_at。区间类标签按区间起点推断,例如 今日活跃 记为当天 00:00,本周活跃 记为本周一 00:00,3日内活跃 记为采集时间减 3 天。

如果你已经在普通 Chrome 里打开并登录了 BOSS 页面,但 Chrome 没有开启远程调试,也没有允许 Apple Events 执行 JavaScript,可以用辅助功能 fallback:

npm run capture:chrome-ax -- --limit 15

这个模式只读取当前 Chrome 窗口里辅助功能树暴露出来的候选人卡片文本,不会点开详情页。

Python + CDP 连接已启动 Chrome:

open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-chrome \
--no-first-run \
https://www.zhipin.com/web/chat/search
npm run capture:cdp -- --limit 10 --skip-apply --detail-max-pages 3

这个模式会连接 9222 端口上的 Chrome,用 DOM 读取页面,比辅助功能 fallback 快很多。 详情页正文由 BOSS 的 canvas 渲染,脚本会裁剪详情弹窗截图并用 macOS Vision OCR 读取文字;原始截图会保存在 data-python/resume-screenshots/<run-id>/ 便于复核。

如果 BOSS 的职位侧和人才库侧必须使用两个不同账号,请启动两个独立 Chrome 用户目录和两个 CDP 端口。不要在同一个 Chrome profile 里开两个账号,同站 cookie 会互相覆盖:

# 人才库/招聘者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-candidates \
--no-first-run \
https://www.zhipin.com/web/chat/search
# 职位侧/求职者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9223 \
--remote-allow-origins=http://127.0.0.1:9223 \
--user-data-dir=/tmp/boss-rpa-jobs \
--no-first-run \
"https://www.zhipin.com/web/geek/jobs?city=100010000"

分别登录两个账号后,统一入口默认会用 9222 抓人才库、9223 抓职位侧:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

如果你想换端口,可以显式传:

npm run org:intel -- \
--company 月之暗面 \
--jobs-cdp-url http://127.0.0.1:9223 \
--candidates-cdp-url http://127.0.0.1:9222 \
--refresh auto \
--report

也可以让脚本先设置搜索条件,再抓取。例如搜索「上海 / 不限职位 / 腾讯」:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--limit 90 \
--detail-max-pages 2

只设置搜索条件、不抓取:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--apply-only

注意:BOSS 下拉里的「热门」是城市列表分组,不是最终城市值;脚本会真实点击它,但页面城市不会变成「热门」。量产时建议传具体城市,比如 北京上海深圳

如果当前搜索结果每次只加载 15 个,可以让脚本先滚动加载更多:

npm run capture:cdp -- --limit 100 --skip-apply --no-details

--limit 100 会滚动到累计 100 个候选人或没有更多结果为止。要一直加载到列表没有新增候选人:

npm run capture:cdp -- --load-all --skip-apply --no-details

量产抓详情时可以去掉 --no-details,并用 --detail-max-pages 12 控制每份在线简历 OCR 的页数。

BOSS 职位侧采集

组织情报还需要招聘岗位信号。职位侧采集默认连接 9223 端口上的职位侧/求职者 Chrome,会打开 BOSS 的 /web/geek/jobs 职位搜索页,先读取左侧职位列表,再逐个点击职位卡读取右侧详情:

npm run capture:jobs -- \
--company 腾讯 \
--cdp-url http://127.0.0.1:9223 \
--limit 30

默认城市是全国,等价于 BOSS URL 里的 city=100010000。如果要限定城市,可以传 --city 上海--city 101020100--city 热门--city-group hot 仍会展开成常用城市组:北京、上海、深圳、广州、杭州、成都、武汉、南京、苏州、西安。--limit 表示每个城市最多读取多少条左侧职位卡。左侧列表每次加载约 15 条时,脚本会持续滚动到目标数量或没有新增职位。

如果你已经手动打开了类似下面的页面,也可以直接读取当前搜索:

npm run capture:jobs -- --company 月之暗面 --limit 90 --cdp-url http://127.0.0.1:9223 --no-manual-ready

默认会点击每个职位卡并读取右侧 JD;只想快速读取左侧列表时加 --no-details

职位采集输出:

<output-dir>/boss_job_postings.ndjson
<output-dir>/runs/run-*.json

导入 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

组织情报报告

统一入口会编排职位侧采集、人才库采集、入库和报告生成:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

--refresh auto 会先看 SQLite 里是否已有近 24 小时的新鲜数据;缺职位侧数据就跑 capture:jobs,缺人才侧观察就跑 capture:cdp。如果只想生成报告不刷新:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh none \
--report

也可以只刷新某一侧:

npm run org:intel -- --company 月之暗面 --refresh jobs --report
npm run org:intel -- --company 月之暗面 --refresh candidates --report

OpenClaw 对接时使用本地 FastAPI service:

npm run org:service

固定机器部署时建议直接用一键脚本,它会启动两套 CDP Chrome,并把 FastAPI worker 指向对应端口:

ORG_INTEL_HOST=0.0.0.0 ORG_INTEL_PORT=8787 ./scripts/start_org_intel_stack.sh

生产对接建议配置 API token:

ORG_INTEL_API_TOKEN=<shared-secret> npm run org:service

配置后,除 /health 外的 /v1/org-intel/* 请求都需要:

-H "Authorization: Bearer <shared-secret>"

默认端口约定:

BOSS_CANDIDATES_CDP_URL=http://127.0.0.1:9222 # 人才库/招聘者账号
BOSS_JOBS_CDP_URL=http://127.0.0.1:9223 # 职位侧/求职者账号

提交异步情报任务:

curl -X POST http://127.0.0.1:8787/v1/org-intel/requests \
-H 'content-type: application/json' \
-d '{ "company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音", "TikTok", "飞书"], "mode": "standard", "refresh": "auto", "client_request_id": "openclaw-example" }'

如果库里没有新鲜报告,接口会立即返回 job_ideta_secondseta_at。OpenClaw 到时间后轮询:

curl http://127.0.0.1:8787/v1/org-intel/requests/<job_id>

状态可能是 queuedrunning_jobsrunning_candidatesimportinggenerating_reportreadyblocked_needs_humanfailedready 时返回 report_markdown 和结构化 findings;如果 BOSS 触发验证,会返回 blocked_needs_human,由人工在采集浏览器里处理。

CEO 定时周/月报由 Hipilot 或外部 cron 触发,本服务只保存订阅和生成 digest。创建订阅:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "owner_id": "ceo-1", "display_name": "CEO 重点公司监控", "cadence": "weekly_and_monthly", "companies": [ {"company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音"], "mode": "standard"}, {"company": "腾讯", "aliases": ["Tencent", "腾讯云", "微信"], "mode": "standard"}, {"company": "月之暗面", "aliases": ["Moonshot", "Kimi"], "mode": "standard"} ], "timezone": "Asia/Shanghai", "freshness_policy": "auto" }'

到点后触发 digest:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions/<subscription_id>/digest-runs \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{"cadence":"weekly","client_request_id":"hipilot-cron-run-id"}'

如果返回 queued / running,按 eta_at 轮询:

curl -H "Authorization: Bearer <shared-secret>" \
http://127.0.0.1:8787/v1/org-intel/digest-runs/<digest_job_id>

也可以按 owner/subscription/cadence 取最近 digest,例如查看上一次月报:

curl -H "Authorization: Bearer <shared-secret>" \
"http://127.0.0.1:8787/v1/org-intel/digest-runs?owner_id=ceo-1&cadence=monthly&limit=1"

ready 时返回多公司 digest_markdownpartial_ready 会返回可用公司摘要,并在风险区说明阻塞公司;blocked_needs_human 表示 BOSS 账号验证需要运营处理。

导入人才库和职位侧 run 后,可以生成 BOSS-only 组织情报 Markdown:

npm run report:org -- \
--company 腾讯 \
--alias Tencent \
--alias 腾讯科技 \
--db data-python/boss_talent.sqlite

也可以用目标配置:

npm run report:org -- \
--company 腾讯 \
--target-config org_targets.example.json \
--db data-python/boss_talent.sqlite

报告会写入 org-intel/<公司名>/report-*.md,并把报告正文、来源计数和结构化组织判断写入 SQLite 的 org_intel_reports / org_findings 表。正文只输出聚合判断;原始候选人信号以脱敏 ID 折叠展示。

数据策略

这个 MVP 采用 B+C 混合版:

  • B:Playwright 半自动读取页面可见信息。
  • C:候选人索引落主表,在线简历落快照表,避免覆盖历史。

OCR 快照用于内部检索和复核,最终原文仍以 BOSS 页面为准。

PostgreSQL

sql/schema.sql。第一版先落 NDJSON,确认字段稳定后再接 PostgreSQL。

当前也可以把单次 run 导入本地 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

测试

npm test

About

Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - nateEc/candidate-intel-agent: Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles. · GitHub
Skip to content

Repository files navigation

BOSS 人才索引采集助手

这是一个本地 RPA MVP,用于辅助 HR 在已经登录的 BOSS 直聘人才库页面中读取当前可见候选人信息,并写入轻量索引文件。它不是无人值守爬虫,也不绕过登录、验证码、风控或隐藏接口。

新方向:HR Browser Agent

下一阶段会把项目从“采集脚本”升级成“用户本机 HR 浏览器操作 agent”:用户和 OpenClaw HR agent 对话,agent 通过本机 FastAPI service 控制可见 Chrome,完成 BOSS 招聘者登录、发 JD、搜索候选人等操作。

设计文档:

本机启动:

npm run hr:start

如果由 Hermes/OpenClaw agent 自动启动,请用后台模式:

npm run hr:daemon

面向最终用户的轻量安装入口:

curl -fsSL https://raw.githubusercontent.com/nateEc/candidate-intel-agent/main/scripts/bootstrap_boss_hr_agent.sh | bash

安装后会提供本机 CLI:

~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent status
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent start
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent stop
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent doctor

边界不变:验证码、App 安全确认和平台验证必须由用户本人完成;系统只负责打开页面、点击、填表、等待和识别状态。

P0 招聘闭环目前覆盖:招聘者登录、发布/更新/关闭职位、投递收件箱巡检、投递候选人评估入库、弱去重合并,以及“准备打招呼 + 招聘者确认后发送”。投递处理默认使用同一个本机 HR service:

curl -X POST http://127.0.0.1:8790/v1/boss/applications/scan \
-H 'content-type: application/json' \
-d '{"job_filter":"AI工程师 _ 北京 20-30K","limit":20,"include_resumes":true,"dry_run":true}'

智能简历库 v1

智能简历库现在使用 Postgres + pgvector 作为主库。BOSS/SQLite 只保留为采集缓存和回放来源;agent 查询、匹配、复评、归池、邮件草稿都走 Postgres。

本地开发可以直接启动项目自带的 pgvector Docker 容器:

npm run talent:db:start

这会启动/复用 boss-talent-postgres 容器,监听 127.0.0.1:54329,并把连接串写入本地 .env

DATABASE_URL=postgresql://talent:talent_dev_password@127.0.0.1:54329/talent_library

如果使用外部数据库,只要手动在 .env 或 shell 里配置:

export DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/talent_library"

初始化 schema / migration:

npm run talent:migrate

启动 Agent API:

npm run talent:service
curl http://127.0.0.1:8792/health

共享给其他 agent 使用时,不要让对方直连 Postgres。把 Smart Talent Library API 部署到你的服务器上,并配置 API token:

export DATABASE_URL="postgresql://talent:<password>@127.0.0.1:5432/talent_library"export TALENT_LIBRARY_API_TOKEN="<shared-secret>"export TALENT_LIBRARY_HOST="127.0.0.1"export TALENT_LIBRARY_PORT="8792"
npm run talent:service

再用 Nginx/Caddy 把 HTTPS 反代到 127.0.0.1:8792/health 可公开用于探活;所有 /v1/talent/* 请求在配置 token 后都必须带:

-H "Authorization: Bearer <shared-secret>"

如果临时测试必须直接监听公网地址,启动脚本会要求先设置 TALENT_LIBRARY_API_TOKEN

TALENT_LIBRARY_HOST=0.0.0.0 TALENT_LIBRARY_API_TOKEN="<shared-secret>" npm run talent:service

不要开放 Postgres 端口到公网。给外部 agent 配置 docs/hr_agent_skill.md 时,只给 TALENT_LIBRARY_BASE_URLTALENT_LIBRARY_API_TOKEN

完整 ECS/云端部署步骤见 Smart Talent Library Cloud Deployment

把已有 BOSS 采集缓存导入 Postgres:

npm run talent:ingest -- --sqlite-db data-python/boss_talent.sqlite --limit 100

直接把一份简历文本/文件导入智能简历库:

npm run talent:ingest-resume -- --file-path "/path/to/resume.txt"

如果需要一套可重复的测试数据,可以导入 20 个合成候选人:

npm run talent:seed-fake -- --reset-synthetic

这会写入 20 个 synthetic_resume 候选人,覆盖 AI Agent/RAG、后端平台、算法、数据、产品、HRBP/招聘、运营/市场等画像;其中 5 人会追加第二版简历,用于测试 /resume-diff/timeline--reset-synthetic 只清理上一轮合成测试数据,不会删除真实/手工候选人。

Hermes/HR agent 更推荐调用 API,把“LLM 抽取后的结构化 JSON + 原始简历文本”一起写入:

curl -X POST http://127.0.0.1:8792/v1/talent/ingest/resume \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "resume_text": "张三\\n邮箱 zhangsan@example.com\\n期望职位:AI Agent 后端工程师\\n...", "structured_data": { "profile": { "display_name": "张三", "expected_position": "AI Agent 后端工程师", "city": "北京", "education_level": "本科", "work_years_text": "5年", "expected_salary_text": "45-60K" }, "skills": [{"skill_name": "RAG", "evidence_text": "负责 RAG 平台"}], "work_experiences": [] } }'

常用接口:

POST /v1/talent/ingest/boss-snapshot 从现有 BOSS/SQLite 采集缓存入 Postgres
POST /v1/talent/ingest/resume 从粘贴文本/本地文件/base64 文件导入简历
POST /v1/talent/recommend 从公司/项目上下文自动推断招聘需求并推荐候选人
POST /v1/talent/candidates/resolve 用 email/手机号/简历 hash/姓名学校等定位候选人
POST /v1/talent/candidates/merge 合并重复候选人档案
POST /v1/talent/candidates/{id}/enrich 结构化简历、生成信号和证据
GET /v1/talent/candidates/{id} 查看完整候选人档案
POST /v1/talent/candidates/{id}/resume 给已有候选人追加/更新一份新简历版本
GET /v1/talent/candidates/{id}/resume-diff 比较候选人最近两版或指定两版简历
GET /v1/talent/candidates/{id}/timeline 查看候选人简历、匹配、互动、任务时间线
GET /v1/talent/search 关键词/城市/学历/评级/人才池检索
POST /v1/talent/semantic-search 本地 hash embedding 语义检索
POST /v1/talent/match-job 给定 JD 生成候选人排序
POST /v1/talent/pools/auto-assign 自动归入人才池
POST /v1/talent/tasks/review 生成复评/回捞任务
POST /v1/talent/outreach/email-draft 基于可见/导入 email 生成邮件草稿
POST /v1/talent/outreach/batch-draft 为多个候选人批量生成邮件草稿
POST /v1/talent/outreach/email-sent 外部邮件工具发送成功后回写互动记录

这版支持两种简历入库方式:Hermes/HR agent 先用 LLM 抽取结构化 JSON 再调用 /v1/talent/ingest/resume,或服务端用本地规则兜底抽取。已有候选人的新简历走 /v1/talent/candidates/{id}/resume,旧版本会保留,后续可用 /resume-diff/timeline 看变化。所有结构化字段尽量带 evidence_span_idconfidenceextractor_version。email 只来自简历可见、HR 手动补充、或导入数据,不做自动猜测。婚姻/家庭/健康等敏感属性只进入 candidate_sensitive_attributes 受限表,默认不参与匹配分、薪资判断或自动触达。

BOSS HR Browser Agent 在 DATABASE_URL 存在时,会在投递巡检后自动把该次 scan 同步到 Postgres;未配置时仍只写本地采集缓存。

目标边界

  • 读取 HR 当前可见的搜索结果卡片。
  • 可选点开详情弹窗,对在线简历 canvas 截图 OCR,保存简历快照和轻量索引字段。
  • 不保存账号密码。
  • 在线简历长文本只保存 HR 当前账号可见内容的 OCR 快照;联系方式会做基础脱敏。
  • 不采集联系方式。
  • 遇到登录、验证码、账号异常时由人工处理,脚本暂停或退出。

安装

npm install
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

默认使用本机 Chrome。也可以通过环境变量指定浏览器路径:

BOSS_BROWSER_EXECUTABLE="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npm run capture

使用

npm run capture -- --limit 10 --details

首次运行会打开一个独立 Chrome 用户目录 .browser-profile。请在浏览器里手动登录 BOSS,进入人才库搜索页,设置好城市、岗位、关键词和筛选条件。准备好后回到终端按回车,脚本会读取页面上可见候选人。

常用参数:

--limit 20 最多读取多少个候选人卡片
--details 点开详情弹窗并抽取轻量索引
--no-details 只读取列表页卡片
--start-url <url> 起始页面
--user-data-dir <dir> 浏览器登录态目录
--output-dir <dir> 输出目录
--headless 无头模式,不推荐用于首次登录

输出文件。capture 默认写入 data/capture:cdp 默认写入 data-python/

<output-dir>/candidates.ndjson 候选人索引,追加写入
<output-dir>/observations.ndjson 每次搜索观察记录,追加写入
<output-dir>/resume_snapshots.ndjson 在线简历 OCR 快照,追加写入
<output-dir>/runs/run-*.json 单次运行结果,便于复核

候选人索引会保留页面上的 active_status 原始活跃标签,例如 刚刚活跃3日内活跃本周活跃,并推断一个保守的 last_seen_at。区间类标签按区间起点推断,例如 今日活跃 记为当天 00:00,本周活跃 记为本周一 00:00,3日内活跃 记为采集时间减 3 天。

如果你已经在普通 Chrome 里打开并登录了 BOSS 页面,但 Chrome 没有开启远程调试,也没有允许 Apple Events 执行 JavaScript,可以用辅助功能 fallback:

npm run capture:chrome-ax -- --limit 15

这个模式只读取当前 Chrome 窗口里辅助功能树暴露出来的候选人卡片文本,不会点开详情页。

Python + CDP 连接已启动 Chrome:

open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-chrome \
--no-first-run \
https://www.zhipin.com/web/chat/search
npm run capture:cdp -- --limit 10 --skip-apply --detail-max-pages 3

这个模式会连接 9222 端口上的 Chrome,用 DOM 读取页面,比辅助功能 fallback 快很多。 详情页正文由 BOSS 的 canvas 渲染,脚本会裁剪详情弹窗截图并用 macOS Vision OCR 读取文字;原始截图会保存在 data-python/resume-screenshots/<run-id>/ 便于复核。

如果 BOSS 的职位侧和人才库侧必须使用两个不同账号,请启动两个独立 Chrome 用户目录和两个 CDP 端口。不要在同一个 Chrome profile 里开两个账号,同站 cookie 会互相覆盖:

# 人才库/招聘者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-candidates \
--no-first-run \
https://www.zhipin.com/web/chat/search
# 职位侧/求职者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9223 \
--remote-allow-origins=http://127.0.0.1:9223 \
--user-data-dir=/tmp/boss-rpa-jobs \
--no-first-run \
"https://www.zhipin.com/web/geek/jobs?city=100010000"

分别登录两个账号后,统一入口默认会用 9222 抓人才库、9223 抓职位侧:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

如果你想换端口,可以显式传:

npm run org:intel -- \
--company 月之暗面 \
--jobs-cdp-url http://127.0.0.1:9223 \
--candidates-cdp-url http://127.0.0.1:9222 \
--refresh auto \
--report

也可以让脚本先设置搜索条件,再抓取。例如搜索「上海 / 不限职位 / 腾讯」:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--limit 90 \
--detail-max-pages 2

只设置搜索条件、不抓取:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--apply-only

注意:BOSS 下拉里的「热门」是城市列表分组,不是最终城市值;脚本会真实点击它,但页面城市不会变成「热门」。量产时建议传具体城市,比如 北京上海深圳

如果当前搜索结果每次只加载 15 个,可以让脚本先滚动加载更多:

npm run capture:cdp -- --limit 100 --skip-apply --no-details

--limit 100 会滚动到累计 100 个候选人或没有更多结果为止。要一直加载到列表没有新增候选人:

npm run capture:cdp -- --load-all --skip-apply --no-details

量产抓详情时可以去掉 --no-details,并用 --detail-max-pages 12 控制每份在线简历 OCR 的页数。

BOSS 职位侧采集

组织情报还需要招聘岗位信号。职位侧采集默认连接 9223 端口上的职位侧/求职者 Chrome,会打开 BOSS 的 /web/geek/jobs 职位搜索页,先读取左侧职位列表,再逐个点击职位卡读取右侧详情:

npm run capture:jobs -- \
--company 腾讯 \
--cdp-url http://127.0.0.1:9223 \
--limit 30

默认城市是全国,等价于 BOSS URL 里的 city=100010000。如果要限定城市,可以传 --city 上海--city 101020100--city 热门--city-group hot 仍会展开成常用城市组:北京、上海、深圳、广州、杭州、成都、武汉、南京、苏州、西安。--limit 表示每个城市最多读取多少条左侧职位卡。左侧列表每次加载约 15 条时,脚本会持续滚动到目标数量或没有新增职位。

如果你已经手动打开了类似下面的页面,也可以直接读取当前搜索:

npm run capture:jobs -- --company 月之暗面 --limit 90 --cdp-url http://127.0.0.1:9223 --no-manual-ready

默认会点击每个职位卡并读取右侧 JD;只想快速读取左侧列表时加 --no-details

职位采集输出:

<output-dir>/boss_job_postings.ndjson
<output-dir>/runs/run-*.json

导入 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

组织情报报告

统一入口会编排职位侧采集、人才库采集、入库和报告生成:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

--refresh auto 会先看 SQLite 里是否已有近 24 小时的新鲜数据;缺职位侧数据就跑 capture:jobs,缺人才侧观察就跑 capture:cdp。如果只想生成报告不刷新:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh none \
--report

也可以只刷新某一侧:

npm run org:intel -- --company 月之暗面 --refresh jobs --report
npm run org:intel -- --company 月之暗面 --refresh candidates --report

OpenClaw 对接时使用本地 FastAPI service:

npm run org:service

固定机器部署时建议直接用一键脚本,它会启动两套 CDP Chrome,并把 FastAPI worker 指向对应端口:

ORG_INTEL_HOST=0.0.0.0 ORG_INTEL_PORT=8787 ./scripts/start_org_intel_stack.sh

生产对接建议配置 API token:

ORG_INTEL_API_TOKEN=<shared-secret> npm run org:service

配置后,除 /health 外的 /v1/org-intel/* 请求都需要:

-H "Authorization: Bearer <shared-secret>"

默认端口约定:

BOSS_CANDIDATES_CDP_URL=http://127.0.0.1:9222 # 人才库/招聘者账号
BOSS_JOBS_CDP_URL=http://127.0.0.1:9223 # 职位侧/求职者账号

提交异步情报任务:

curl -X POST http://127.0.0.1:8787/v1/org-intel/requests \
-H 'content-type: application/json' \
-d '{ "company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音", "TikTok", "飞书"], "mode": "standard", "refresh": "auto", "client_request_id": "openclaw-example" }'

如果库里没有新鲜报告,接口会立即返回 job_ideta_secondseta_at。OpenClaw 到时间后轮询:

curl http://127.0.0.1:8787/v1/org-intel/requests/<job_id>

状态可能是 queuedrunning_jobsrunning_candidatesimportinggenerating_reportreadyblocked_needs_humanfailedready 时返回 report_markdown 和结构化 findings;如果 BOSS 触发验证,会返回 blocked_needs_human,由人工在采集浏览器里处理。

CEO 定时周/月报由 Hipilot 或外部 cron 触发,本服务只保存订阅和生成 digest。创建订阅:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "owner_id": "ceo-1", "display_name": "CEO 重点公司监控", "cadence": "weekly_and_monthly", "companies": [ {"company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音"], "mode": "standard"}, {"company": "腾讯", "aliases": ["Tencent", "腾讯云", "微信"], "mode": "standard"}, {"company": "月之暗面", "aliases": ["Moonshot", "Kimi"], "mode": "standard"} ], "timezone": "Asia/Shanghai", "freshness_policy": "auto" }'

到点后触发 digest:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions/<subscription_id>/digest-runs \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{"cadence":"weekly","client_request_id":"hipilot-cron-run-id"}'

如果返回 queued / running,按 eta_at 轮询:

curl -H "Authorization: Bearer <shared-secret>" \
http://127.0.0.1:8787/v1/org-intel/digest-runs/<digest_job_id>

也可以按 owner/subscription/cadence 取最近 digest,例如查看上一次月报:

curl -H "Authorization: Bearer <shared-secret>" \
"http://127.0.0.1:8787/v1/org-intel/digest-runs?owner_id=ceo-1&cadence=monthly&limit=1"

ready 时返回多公司 digest_markdownpartial_ready 会返回可用公司摘要,并在风险区说明阻塞公司;blocked_needs_human 表示 BOSS 账号验证需要运营处理。

导入人才库和职位侧 run 后,可以生成 BOSS-only 组织情报 Markdown:

npm run report:org -- \
--company 腾讯 \
--alias Tencent \
--alias 腾讯科技 \
--db data-python/boss_talent.sqlite

也可以用目标配置:

npm run report:org -- \
--company 腾讯 \
--target-config org_targets.example.json \
--db data-python/boss_talent.sqlite

报告会写入 org-intel/<公司名>/report-*.md,并把报告正文、来源计数和结构化组织判断写入 SQLite 的 org_intel_reports / org_findings 表。正文只输出聚合判断;原始候选人信号以脱敏 ID 折叠展示。

数据策略

这个 MVP 采用 B+C 混合版:

  • B:Playwright 半自动读取页面可见信息。
  • C:候选人索引落主表,在线简历落快照表,避免覆盖历史。

OCR 快照用于内部检索和复核,最终原文仍以 BOSS 页面为准。

PostgreSQL

sql/schema.sql。第一版先落 NDJSON,确认字段稳定后再接 PostgreSQL。

当前也可以把单次 run 导入本地 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

测试

npm test

About

Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - nateEc/candidate-intel-agent: Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles. · GitHub
Skip to content

Repository files navigation

BOSS 人才索引采集助手

这是一个本地 RPA MVP,用于辅助 HR 在已经登录的 BOSS 直聘人才库页面中读取当前可见候选人信息,并写入轻量索引文件。它不是无人值守爬虫,也不绕过登录、验证码、风控或隐藏接口。

新方向:HR Browser Agent

下一阶段会把项目从“采集脚本”升级成“用户本机 HR 浏览器操作 agent”:用户和 OpenClaw HR agent 对话,agent 通过本机 FastAPI service 控制可见 Chrome,完成 BOSS 招聘者登录、发 JD、搜索候选人等操作。

设计文档:

本机启动:

npm run hr:start

如果由 Hermes/OpenClaw agent 自动启动,请用后台模式:

npm run hr:daemon

面向最终用户的轻量安装入口:

curl -fsSL https://raw.githubusercontent.com/nateEc/candidate-intel-agent/main/scripts/bootstrap_boss_hr_agent.sh | bash

安装后会提供本机 CLI:

~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent status
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent start
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent stop
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent doctor

边界不变:验证码、App 安全确认和平台验证必须由用户本人完成;系统只负责打开页面、点击、填表、等待和识别状态。

P0 招聘闭环目前覆盖:招聘者登录、发布/更新/关闭职位、投递收件箱巡检、投递候选人评估入库、弱去重合并,以及“准备打招呼 + 招聘者确认后发送”。投递处理默认使用同一个本机 HR service:

curl -X POST http://127.0.0.1:8790/v1/boss/applications/scan \
-H 'content-type: application/json' \
-d '{"job_filter":"AI工程师 _ 北京 20-30K","limit":20,"include_resumes":true,"dry_run":true}'

智能简历库 v1

智能简历库现在使用 Postgres + pgvector 作为主库。BOSS/SQLite 只保留为采集缓存和回放来源;agent 查询、匹配、复评、归池、邮件草稿都走 Postgres。

本地开发可以直接启动项目自带的 pgvector Docker 容器:

npm run talent:db:start

这会启动/复用 boss-talent-postgres 容器,监听 127.0.0.1:54329,并把连接串写入本地 .env

DATABASE_URL=postgresql://talent:talent_dev_password@127.0.0.1:54329/talent_library

如果使用外部数据库,只要手动在 .env 或 shell 里配置:

export DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/talent_library"

初始化 schema / migration:

npm run talent:migrate

启动 Agent API:

npm run talent:service
curl http://127.0.0.1:8792/health

共享给其他 agent 使用时,不要让对方直连 Postgres。把 Smart Talent Library API 部署到你的服务器上,并配置 API token:

export DATABASE_URL="postgresql://talent:<password>@127.0.0.1:5432/talent_library"export TALENT_LIBRARY_API_TOKEN="<shared-secret>"export TALENT_LIBRARY_HOST="127.0.0.1"export TALENT_LIBRARY_PORT="8792"
npm run talent:service

再用 Nginx/Caddy 把 HTTPS 反代到 127.0.0.1:8792/health 可公开用于探活;所有 /v1/talent/* 请求在配置 token 后都必须带:

-H "Authorization: Bearer <shared-secret>"

如果临时测试必须直接监听公网地址,启动脚本会要求先设置 TALENT_LIBRARY_API_TOKEN

TALENT_LIBRARY_HOST=0.0.0.0 TALENT_LIBRARY_API_TOKEN="<shared-secret>" npm run talent:service

不要开放 Postgres 端口到公网。给外部 agent 配置 docs/hr_agent_skill.md 时,只给 TALENT_LIBRARY_BASE_URLTALENT_LIBRARY_API_TOKEN

完整 ECS/云端部署步骤见 Smart Talent Library Cloud Deployment

把已有 BOSS 采集缓存导入 Postgres:

npm run talent:ingest -- --sqlite-db data-python/boss_talent.sqlite --limit 100

直接把一份简历文本/文件导入智能简历库:

npm run talent:ingest-resume -- --file-path "/path/to/resume.txt"

如果需要一套可重复的测试数据,可以导入 20 个合成候选人:

npm run talent:seed-fake -- --reset-synthetic

这会写入 20 个 synthetic_resume 候选人,覆盖 AI Agent/RAG、后端平台、算法、数据、产品、HRBP/招聘、运营/市场等画像;其中 5 人会追加第二版简历,用于测试 /resume-diff/timeline--reset-synthetic 只清理上一轮合成测试数据,不会删除真实/手工候选人。

Hermes/HR agent 更推荐调用 API,把“LLM 抽取后的结构化 JSON + 原始简历文本”一起写入:

curl -X POST http://127.0.0.1:8792/v1/talent/ingest/resume \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "resume_text": "张三\\n邮箱 zhangsan@example.com\\n期望职位:AI Agent 后端工程师\\n...", "structured_data": { "profile": { "display_name": "张三", "expected_position": "AI Agent 后端工程师", "city": "北京", "education_level": "本科", "work_years_text": "5年", "expected_salary_text": "45-60K" }, "skills": [{"skill_name": "RAG", "evidence_text": "负责 RAG 平台"}], "work_experiences": [] } }'

常用接口:

POST /v1/talent/ingest/boss-snapshot 从现有 BOSS/SQLite 采集缓存入 Postgres
POST /v1/talent/ingest/resume 从粘贴文本/本地文件/base64 文件导入简历
POST /v1/talent/recommend 从公司/项目上下文自动推断招聘需求并推荐候选人
POST /v1/talent/candidates/resolve 用 email/手机号/简历 hash/姓名学校等定位候选人
POST /v1/talent/candidates/merge 合并重复候选人档案
POST /v1/talent/candidates/{id}/enrich 结构化简历、生成信号和证据
GET /v1/talent/candidates/{id} 查看完整候选人档案
POST /v1/talent/candidates/{id}/resume 给已有候选人追加/更新一份新简历版本
GET /v1/talent/candidates/{id}/resume-diff 比较候选人最近两版或指定两版简历
GET /v1/talent/candidates/{id}/timeline 查看候选人简历、匹配、互动、任务时间线
GET /v1/talent/search 关键词/城市/学历/评级/人才池检索
POST /v1/talent/semantic-search 本地 hash embedding 语义检索
POST /v1/talent/match-job 给定 JD 生成候选人排序
POST /v1/talent/pools/auto-assign 自动归入人才池
POST /v1/talent/tasks/review 生成复评/回捞任务
POST /v1/talent/outreach/email-draft 基于可见/导入 email 生成邮件草稿
POST /v1/talent/outreach/batch-draft 为多个候选人批量生成邮件草稿
POST /v1/talent/outreach/email-sent 外部邮件工具发送成功后回写互动记录

这版支持两种简历入库方式:Hermes/HR agent 先用 LLM 抽取结构化 JSON 再调用 /v1/talent/ingest/resume,或服务端用本地规则兜底抽取。已有候选人的新简历走 /v1/talent/candidates/{id}/resume,旧版本会保留,后续可用 /resume-diff/timeline 看变化。所有结构化字段尽量带 evidence_span_idconfidenceextractor_version。email 只来自简历可见、HR 手动补充、或导入数据,不做自动猜测。婚姻/家庭/健康等敏感属性只进入 candidate_sensitive_attributes 受限表,默认不参与匹配分、薪资判断或自动触达。

BOSS HR Browser Agent 在 DATABASE_URL 存在时,会在投递巡检后自动把该次 scan 同步到 Postgres;未配置时仍只写本地采集缓存。

目标边界

  • 读取 HR 当前可见的搜索结果卡片。
  • 可选点开详情弹窗,对在线简历 canvas 截图 OCR,保存简历快照和轻量索引字段。
  • 不保存账号密码。
  • 在线简历长文本只保存 HR 当前账号可见内容的 OCR 快照;联系方式会做基础脱敏。
  • 不采集联系方式。
  • 遇到登录、验证码、账号异常时由人工处理,脚本暂停或退出。

安装

npm install
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

默认使用本机 Chrome。也可以通过环境变量指定浏览器路径:

BOSS_BROWSER_EXECUTABLE="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npm run capture

使用

npm run capture -- --limit 10 --details

首次运行会打开一个独立 Chrome 用户目录 .browser-profile。请在浏览器里手动登录 BOSS,进入人才库搜索页,设置好城市、岗位、关键词和筛选条件。准备好后回到终端按回车,脚本会读取页面上可见候选人。

常用参数:

--limit 20 最多读取多少个候选人卡片
--details 点开详情弹窗并抽取轻量索引
--no-details 只读取列表页卡片
--start-url <url> 起始页面
--user-data-dir <dir> 浏览器登录态目录
--output-dir <dir> 输出目录
--headless 无头模式,不推荐用于首次登录

输出文件。capture 默认写入 data/capture:cdp 默认写入 data-python/

<output-dir>/candidates.ndjson 候选人索引,追加写入
<output-dir>/observations.ndjson 每次搜索观察记录,追加写入
<output-dir>/resume_snapshots.ndjson 在线简历 OCR 快照,追加写入
<output-dir>/runs/run-*.json 单次运行结果,便于复核

候选人索引会保留页面上的 active_status 原始活跃标签,例如 刚刚活跃3日内活跃本周活跃,并推断一个保守的 last_seen_at。区间类标签按区间起点推断,例如 今日活跃 记为当天 00:00,本周活跃 记为本周一 00:00,3日内活跃 记为采集时间减 3 天。

如果你已经在普通 Chrome 里打开并登录了 BOSS 页面,但 Chrome 没有开启远程调试,也没有允许 Apple Events 执行 JavaScript,可以用辅助功能 fallback:

npm run capture:chrome-ax -- --limit 15

这个模式只读取当前 Chrome 窗口里辅助功能树暴露出来的候选人卡片文本,不会点开详情页。

Python + CDP 连接已启动 Chrome:

open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-chrome \
--no-first-run \
https://www.zhipin.com/web/chat/search
npm run capture:cdp -- --limit 10 --skip-apply --detail-max-pages 3

这个模式会连接 9222 端口上的 Chrome,用 DOM 读取页面,比辅助功能 fallback 快很多。 详情页正文由 BOSS 的 canvas 渲染,脚本会裁剪详情弹窗截图并用 macOS Vision OCR 读取文字;原始截图会保存在 data-python/resume-screenshots/<run-id>/ 便于复核。

如果 BOSS 的职位侧和人才库侧必须使用两个不同账号,请启动两个独立 Chrome 用户目录和两个 CDP 端口。不要在同一个 Chrome profile 里开两个账号,同站 cookie 会互相覆盖:

# 人才库/招聘者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-candidates \
--no-first-run \
https://www.zhipin.com/web/chat/search
# 职位侧/求职者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9223 \
--remote-allow-origins=http://127.0.0.1:9223 \
--user-data-dir=/tmp/boss-rpa-jobs \
--no-first-run \
"https://www.zhipin.com/web/geek/jobs?city=100010000"

分别登录两个账号后,统一入口默认会用 9222 抓人才库、9223 抓职位侧:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

如果你想换端口,可以显式传:

npm run org:intel -- \
--company 月之暗面 \
--jobs-cdp-url http://127.0.0.1:9223 \
--candidates-cdp-url http://127.0.0.1:9222 \
--refresh auto \
--report

也可以让脚本先设置搜索条件,再抓取。例如搜索「上海 / 不限职位 / 腾讯」:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--limit 90 \
--detail-max-pages 2

只设置搜索条件、不抓取:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--apply-only

注意:BOSS 下拉里的「热门」是城市列表分组,不是最终城市值;脚本会真实点击它,但页面城市不会变成「热门」。量产时建议传具体城市,比如 北京上海深圳

如果当前搜索结果每次只加载 15 个,可以让脚本先滚动加载更多:

npm run capture:cdp -- --limit 100 --skip-apply --no-details

--limit 100 会滚动到累计 100 个候选人或没有更多结果为止。要一直加载到列表没有新增候选人:

npm run capture:cdp -- --load-all --skip-apply --no-details

量产抓详情时可以去掉 --no-details,并用 --detail-max-pages 12 控制每份在线简历 OCR 的页数。

BOSS 职位侧采集

组织情报还需要招聘岗位信号。职位侧采集默认连接 9223 端口上的职位侧/求职者 Chrome,会打开 BOSS 的 /web/geek/jobs 职位搜索页,先读取左侧职位列表,再逐个点击职位卡读取右侧详情:

npm run capture:jobs -- \
--company 腾讯 \
--cdp-url http://127.0.0.1:9223 \
--limit 30

默认城市是全国,等价于 BOSS URL 里的 city=100010000。如果要限定城市,可以传 --city 上海--city 101020100--city 热门--city-group hot 仍会展开成常用城市组:北京、上海、深圳、广州、杭州、成都、武汉、南京、苏州、西安。--limit 表示每个城市最多读取多少条左侧职位卡。左侧列表每次加载约 15 条时,脚本会持续滚动到目标数量或没有新增职位。

如果你已经手动打开了类似下面的页面,也可以直接读取当前搜索:

npm run capture:jobs -- --company 月之暗面 --limit 90 --cdp-url http://127.0.0.1:9223 --no-manual-ready

默认会点击每个职位卡并读取右侧 JD;只想快速读取左侧列表时加 --no-details

职位采集输出:

<output-dir>/boss_job_postings.ndjson
<output-dir>/runs/run-*.json

导入 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

组织情报报告

统一入口会编排职位侧采集、人才库采集、入库和报告生成:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

--refresh auto 会先看 SQLite 里是否已有近 24 小时的新鲜数据;缺职位侧数据就跑 capture:jobs,缺人才侧观察就跑 capture:cdp。如果只想生成报告不刷新:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh none \
--report

也可以只刷新某一侧:

npm run org:intel -- --company 月之暗面 --refresh jobs --report
npm run org:intel -- --company 月之暗面 --refresh candidates --report

OpenClaw 对接时使用本地 FastAPI service:

npm run org:service

固定机器部署时建议直接用一键脚本,它会启动两套 CDP Chrome,并把 FastAPI worker 指向对应端口:

ORG_INTEL_HOST=0.0.0.0 ORG_INTEL_PORT=8787 ./scripts/start_org_intel_stack.sh

生产对接建议配置 API token:

ORG_INTEL_API_TOKEN=<shared-secret> npm run org:service

配置后,除 /health 外的 /v1/org-intel/* 请求都需要:

-H "Authorization: Bearer <shared-secret>"

默认端口约定:

BOSS_CANDIDATES_CDP_URL=http://127.0.0.1:9222 # 人才库/招聘者账号
BOSS_JOBS_CDP_URL=http://127.0.0.1:9223 # 职位侧/求职者账号

提交异步情报任务:

curl -X POST http://127.0.0.1:8787/v1/org-intel/requests \
-H 'content-type: application/json' \
-d '{ "company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音", "TikTok", "飞书"], "mode": "standard", "refresh": "auto", "client_request_id": "openclaw-example" }'

如果库里没有新鲜报告,接口会立即返回 job_ideta_secondseta_at。OpenClaw 到时间后轮询:

curl http://127.0.0.1:8787/v1/org-intel/requests/<job_id>

状态可能是 queuedrunning_jobsrunning_candidatesimportinggenerating_reportreadyblocked_needs_humanfailedready 时返回 report_markdown 和结构化 findings;如果 BOSS 触发验证,会返回 blocked_needs_human,由人工在采集浏览器里处理。

CEO 定时周/月报由 Hipilot 或外部 cron 触发,本服务只保存订阅和生成 digest。创建订阅:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "owner_id": "ceo-1", "display_name": "CEO 重点公司监控", "cadence": "weekly_and_monthly", "companies": [ {"company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音"], "mode": "standard"}, {"company": "腾讯", "aliases": ["Tencent", "腾讯云", "微信"], "mode": "standard"}, {"company": "月之暗面", "aliases": ["Moonshot", "Kimi"], "mode": "standard"} ], "timezone": "Asia/Shanghai", "freshness_policy": "auto" }'

到点后触发 digest:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions/<subscription_id>/digest-runs \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{"cadence":"weekly","client_request_id":"hipilot-cron-run-id"}'

如果返回 queued / running,按 eta_at 轮询:

curl -H "Authorization: Bearer <shared-secret>" \
http://127.0.0.1:8787/v1/org-intel/digest-runs/<digest_job_id>

也可以按 owner/subscription/cadence 取最近 digest,例如查看上一次月报:

curl -H "Authorization: Bearer <shared-secret>" \
"http://127.0.0.1:8787/v1/org-intel/digest-runs?owner_id=ceo-1&cadence=monthly&limit=1"

ready 时返回多公司 digest_markdownpartial_ready 会返回可用公司摘要,并在风险区说明阻塞公司;blocked_needs_human 表示 BOSS 账号验证需要运营处理。

导入人才库和职位侧 run 后,可以生成 BOSS-only 组织情报 Markdown:

npm run report:org -- \
--company 腾讯 \
--alias Tencent \
--alias 腾讯科技 \
--db data-python/boss_talent.sqlite

也可以用目标配置:

npm run report:org -- \
--company 腾讯 \
--target-config org_targets.example.json \
--db data-python/boss_talent.sqlite

报告会写入 org-intel/<公司名>/report-*.md,并把报告正文、来源计数和结构化组织判断写入 SQLite 的 org_intel_reports / org_findings 表。正文只输出聚合判断;原始候选人信号以脱敏 ID 折叠展示。

数据策略

这个 MVP 采用 B+C 混合版:

  • B:Playwright 半自动读取页面可见信息。
  • C:候选人索引落主表,在线简历落快照表,避免覆盖历史。

OCR 快照用于内部检索和复核,最终原文仍以 BOSS 页面为准。

PostgreSQL

sql/schema.sql。第一版先落 NDJSON,确认字段稳定后再接 PostgreSQL。

当前也可以把单次 run 导入本地 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

测试

npm test

About

Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - nateEc/candidate-intel-agent: Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles. · GitHub
Skip to content

Repository files navigation

BOSS 人才索引采集助手

这是一个本地 RPA MVP,用于辅助 HR 在已经登录的 BOSS 直聘人才库页面中读取当前可见候选人信息,并写入轻量索引文件。它不是无人值守爬虫,也不绕过登录、验证码、风控或隐藏接口。

新方向:HR Browser Agent

下一阶段会把项目从“采集脚本”升级成“用户本机 HR 浏览器操作 agent”:用户和 OpenClaw HR agent 对话,agent 通过本机 FastAPI service 控制可见 Chrome,完成 BOSS 招聘者登录、发 JD、搜索候选人等操作。

设计文档:

本机启动:

npm run hr:start

如果由 Hermes/OpenClaw agent 自动启动,请用后台模式:

npm run hr:daemon

面向最终用户的轻量安装入口:

curl -fsSL https://raw.githubusercontent.com/nateEc/candidate-intel-agent/main/scripts/bootstrap_boss_hr_agent.sh | bash

安装后会提供本机 CLI:

~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent status
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent start
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent stop
~/Library/Application\ Support/BossHrAgent/service/bin/boss-hr-agent doctor

边界不变:验证码、App 安全确认和平台验证必须由用户本人完成;系统只负责打开页面、点击、填表、等待和识别状态。

P0 招聘闭环目前覆盖:招聘者登录、发布/更新/关闭职位、投递收件箱巡检、投递候选人评估入库、弱去重合并,以及“准备打招呼 + 招聘者确认后发送”。投递处理默认使用同一个本机 HR service:

curl -X POST http://127.0.0.1:8790/v1/boss/applications/scan \
-H 'content-type: application/json' \
-d '{"job_filter":"AI工程师 _ 北京 20-30K","limit":20,"include_resumes":true,"dry_run":true}'

智能简历库 v1

智能简历库现在使用 Postgres + pgvector 作为主库。BOSS/SQLite 只保留为采集缓存和回放来源;agent 查询、匹配、复评、归池、邮件草稿都走 Postgres。

本地开发可以直接启动项目自带的 pgvector Docker 容器:

npm run talent:db:start

这会启动/复用 boss-talent-postgres 容器,监听 127.0.0.1:54329,并把连接串写入本地 .env

DATABASE_URL=postgresql://talent:talent_dev_password@127.0.0.1:54329/talent_library

如果使用外部数据库,只要手动在 .env 或 shell 里配置:

export DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/talent_library"

初始化 schema / migration:

npm run talent:migrate

启动 Agent API:

npm run talent:service
curl http://127.0.0.1:8792/health

共享给其他 agent 使用时,不要让对方直连 Postgres。把 Smart Talent Library API 部署到你的服务器上,并配置 API token:

export DATABASE_URL="postgresql://talent:<password>@127.0.0.1:5432/talent_library"export TALENT_LIBRARY_API_TOKEN="<shared-secret>"export TALENT_LIBRARY_HOST="127.0.0.1"export TALENT_LIBRARY_PORT="8792"
npm run talent:service

再用 Nginx/Caddy 把 HTTPS 反代到 127.0.0.1:8792/health 可公开用于探活;所有 /v1/talent/* 请求在配置 token 后都必须带:

-H "Authorization: Bearer <shared-secret>"

如果临时测试必须直接监听公网地址,启动脚本会要求先设置 TALENT_LIBRARY_API_TOKEN

TALENT_LIBRARY_HOST=0.0.0.0 TALENT_LIBRARY_API_TOKEN="<shared-secret>" npm run talent:service

不要开放 Postgres 端口到公网。给外部 agent 配置 docs/hr_agent_skill.md 时,只给 TALENT_LIBRARY_BASE_URLTALENT_LIBRARY_API_TOKEN

完整 ECS/云端部署步骤见 Smart Talent Library Cloud Deployment

把已有 BOSS 采集缓存导入 Postgres:

npm run talent:ingest -- --sqlite-db data-python/boss_talent.sqlite --limit 100

直接把一份简历文本/文件导入智能简历库:

npm run talent:ingest-resume -- --file-path "/path/to/resume.txt"

如果需要一套可重复的测试数据,可以导入 20 个合成候选人:

npm run talent:seed-fake -- --reset-synthetic

这会写入 20 个 synthetic_resume 候选人,覆盖 AI Agent/RAG、后端平台、算法、数据、产品、HRBP/招聘、运营/市场等画像;其中 5 人会追加第二版简历,用于测试 /resume-diff/timeline--reset-synthetic 只清理上一轮合成测试数据,不会删除真实/手工候选人。

Hermes/HR agent 更推荐调用 API,把“LLM 抽取后的结构化 JSON + 原始简历文本”一起写入:

curl -X POST http://127.0.0.1:8792/v1/talent/ingest/resume \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "resume_text": "张三\\n邮箱 zhangsan@example.com\\n期望职位:AI Agent 后端工程师\\n...", "structured_data": { "profile": { "display_name": "张三", "expected_position": "AI Agent 后端工程师", "city": "北京", "education_level": "本科", "work_years_text": "5年", "expected_salary_text": "45-60K" }, "skills": [{"skill_name": "RAG", "evidence_text": "负责 RAG 平台"}], "work_experiences": [] } }'

常用接口:

POST /v1/talent/ingest/boss-snapshot 从现有 BOSS/SQLite 采集缓存入 Postgres
POST /v1/talent/ingest/resume 从粘贴文本/本地文件/base64 文件导入简历
POST /v1/talent/recommend 从公司/项目上下文自动推断招聘需求并推荐候选人
POST /v1/talent/candidates/resolve 用 email/手机号/简历 hash/姓名学校等定位候选人
POST /v1/talent/candidates/merge 合并重复候选人档案
POST /v1/talent/candidates/{id}/enrich 结构化简历、生成信号和证据
GET /v1/talent/candidates/{id} 查看完整候选人档案
POST /v1/talent/candidates/{id}/resume 给已有候选人追加/更新一份新简历版本
GET /v1/talent/candidates/{id}/resume-diff 比较候选人最近两版或指定两版简历
GET /v1/talent/candidates/{id}/timeline 查看候选人简历、匹配、互动、任务时间线
GET /v1/talent/search 关键词/城市/学历/评级/人才池检索
POST /v1/talent/semantic-search 本地 hash embedding 语义检索
POST /v1/talent/match-job 给定 JD 生成候选人排序
POST /v1/talent/pools/auto-assign 自动归入人才池
POST /v1/talent/tasks/review 生成复评/回捞任务
POST /v1/talent/outreach/email-draft 基于可见/导入 email 生成邮件草稿
POST /v1/talent/outreach/batch-draft 为多个候选人批量生成邮件草稿
POST /v1/talent/outreach/email-sent 外部邮件工具发送成功后回写互动记录

这版支持两种简历入库方式:Hermes/HR agent 先用 LLM 抽取结构化 JSON 再调用 /v1/talent/ingest/resume,或服务端用本地规则兜底抽取。已有候选人的新简历走 /v1/talent/candidates/{id}/resume,旧版本会保留,后续可用 /resume-diff/timeline 看变化。所有结构化字段尽量带 evidence_span_idconfidenceextractor_version。email 只来自简历可见、HR 手动补充、或导入数据,不做自动猜测。婚姻/家庭/健康等敏感属性只进入 candidate_sensitive_attributes 受限表,默认不参与匹配分、薪资判断或自动触达。

BOSS HR Browser Agent 在 DATABASE_URL 存在时,会在投递巡检后自动把该次 scan 同步到 Postgres;未配置时仍只写本地采集缓存。

目标边界

  • 读取 HR 当前可见的搜索结果卡片。
  • 可选点开详情弹窗,对在线简历 canvas 截图 OCR,保存简历快照和轻量索引字段。
  • 不保存账号密码。
  • 在线简历长文本只保存 HR 当前账号可见内容的 OCR 快照;联系方式会做基础脱敏。
  • 不采集联系方式。
  • 遇到登录、验证码、账号异常时由人工处理,脚本暂停或退出。

安装

npm install
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

默认使用本机 Chrome。也可以通过环境变量指定浏览器路径:

BOSS_BROWSER_EXECUTABLE="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npm run capture

使用

npm run capture -- --limit 10 --details

首次运行会打开一个独立 Chrome 用户目录 .browser-profile。请在浏览器里手动登录 BOSS,进入人才库搜索页,设置好城市、岗位、关键词和筛选条件。准备好后回到终端按回车,脚本会读取页面上可见候选人。

常用参数:

--limit 20 最多读取多少个候选人卡片
--details 点开详情弹窗并抽取轻量索引
--no-details 只读取列表页卡片
--start-url <url> 起始页面
--user-data-dir <dir> 浏览器登录态目录
--output-dir <dir> 输出目录
--headless 无头模式,不推荐用于首次登录

输出文件。capture 默认写入 data/capture:cdp 默认写入 data-python/

<output-dir>/candidates.ndjson 候选人索引,追加写入
<output-dir>/observations.ndjson 每次搜索观察记录,追加写入
<output-dir>/resume_snapshots.ndjson 在线简历 OCR 快照,追加写入
<output-dir>/runs/run-*.json 单次运行结果,便于复核

候选人索引会保留页面上的 active_status 原始活跃标签,例如 刚刚活跃3日内活跃本周活跃,并推断一个保守的 last_seen_at。区间类标签按区间起点推断,例如 今日活跃 记为当天 00:00,本周活跃 记为本周一 00:00,3日内活跃 记为采集时间减 3 天。

如果你已经在普通 Chrome 里打开并登录了 BOSS 页面,但 Chrome 没有开启远程调试,也没有允许 Apple Events 执行 JavaScript,可以用辅助功能 fallback:

npm run capture:chrome-ax -- --limit 15

这个模式只读取当前 Chrome 窗口里辅助功能树暴露出来的候选人卡片文本,不会点开详情页。

Python + CDP 连接已启动 Chrome:

open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-chrome \
--no-first-run \
https://www.zhipin.com/web/chat/search
npm run capture:cdp -- --limit 10 --skip-apply --detail-max-pages 3

这个模式会连接 9222 端口上的 Chrome,用 DOM 读取页面,比辅助功能 fallback 快很多。 详情页正文由 BOSS 的 canvas 渲染,脚本会裁剪详情弹窗截图并用 macOS Vision OCR 读取文字;原始截图会保存在 data-python/resume-screenshots/<run-id>/ 便于复核。

如果 BOSS 的职位侧和人才库侧必须使用两个不同账号,请启动两个独立 Chrome 用户目录和两个 CDP 端口。不要在同一个 Chrome profile 里开两个账号,同站 cookie 会互相覆盖:

# 人才库/招聘者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9222 \
--remote-allow-origins=http://127.0.0.1:9222 \
--user-data-dir=/tmp/boss-rpa-candidates \
--no-first-run \
https://www.zhipin.com/web/chat/search
# 职位侧/求职者账号
open -na "Google Chrome" --args \
--remote-debugging-port=9223 \
--remote-allow-origins=http://127.0.0.1:9223 \
--user-data-dir=/tmp/boss-rpa-jobs \
--no-first-run \
"https://www.zhipin.com/web/geek/jobs?city=100010000"

分别登录两个账号后,统一入口默认会用 9222 抓人才库、9223 抓职位侧:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

如果你想换端口,可以显式传:

npm run org:intel -- \
--company 月之暗面 \
--jobs-cdp-url http://127.0.0.1:9223 \
--candidates-cdp-url http://127.0.0.1:9222 \
--refresh auto \
--report

也可以让脚本先设置搜索条件,再抓取。例如搜索「上海 / 不限职位 / 腾讯」:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--limit 90 \
--detail-max-pages 2

只设置搜索条件、不抓取:

npm run capture:cdp -- \
--city 上海 \
--position 不限职位 \
--keyword 腾讯 \
--clear-filters \
--apply-only

注意:BOSS 下拉里的「热门」是城市列表分组,不是最终城市值;脚本会真实点击它,但页面城市不会变成「热门」。量产时建议传具体城市,比如 北京上海深圳

如果当前搜索结果每次只加载 15 个,可以让脚本先滚动加载更多:

npm run capture:cdp -- --limit 100 --skip-apply --no-details

--limit 100 会滚动到累计 100 个候选人或没有更多结果为止。要一直加载到列表没有新增候选人:

npm run capture:cdp -- --load-all --skip-apply --no-details

量产抓详情时可以去掉 --no-details,并用 --detail-max-pages 12 控制每份在线简历 OCR 的页数。

BOSS 职位侧采集

组织情报还需要招聘岗位信号。职位侧采集默认连接 9223 端口上的职位侧/求职者 Chrome,会打开 BOSS 的 /web/geek/jobs 职位搜索页,先读取左侧职位列表,再逐个点击职位卡读取右侧详情:

npm run capture:jobs -- \
--company 腾讯 \
--cdp-url http://127.0.0.1:9223 \
--limit 30

默认城市是全国,等价于 BOSS URL 里的 city=100010000。如果要限定城市,可以传 --city 上海--city 101020100--city 热门--city-group hot 仍会展开成常用城市组:北京、上海、深圳、广州、杭州、成都、武汉、南京、苏州、西安。--limit 表示每个城市最多读取多少条左侧职位卡。左侧列表每次加载约 15 条时,脚本会持续滚动到目标数量或没有新增职位。

如果你已经手动打开了类似下面的页面,也可以直接读取当前搜索:

npm run capture:jobs -- --company 月之暗面 --limit 90 --cdp-url http://127.0.0.1:9223 --no-manual-ready

默认会点击每个职位卡并读取右侧 JD;只想快速读取左侧列表时加 --no-details

职位采集输出:

<output-dir>/boss_job_postings.ndjson
<output-dir>/runs/run-*.json

导入 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

组织情报报告

统一入口会编排职位侧采集、人才库采集、入库和报告生成:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh auto \
--report

--refresh auto 会先看 SQLite 里是否已有近 24 小时的新鲜数据;缺职位侧数据就跑 capture:jobs,缺人才侧观察就跑 capture:cdp。如果只想生成报告不刷新:

npm run org:intel -- \
--company 月之暗面 \
--aliases Moonshot Kimi moonshot.ai \
--refresh none \
--report

也可以只刷新某一侧:

npm run org:intel -- --company 月之暗面 --refresh jobs --report
npm run org:intel -- --company 月之暗面 --refresh candidates --report

OpenClaw 对接时使用本地 FastAPI service:

npm run org:service

固定机器部署时建议直接用一键脚本,它会启动两套 CDP Chrome,并把 FastAPI worker 指向对应端口:

ORG_INTEL_HOST=0.0.0.0 ORG_INTEL_PORT=8787 ./scripts/start_org_intel_stack.sh

生产对接建议配置 API token:

ORG_INTEL_API_TOKEN=<shared-secret> npm run org:service

配置后,除 /health 外的 /v1/org-intel/* 请求都需要:

-H "Authorization: Bearer <shared-secret>"

默认端口约定:

BOSS_CANDIDATES_CDP_URL=http://127.0.0.1:9222 # 人才库/招聘者账号
BOSS_JOBS_CDP_URL=http://127.0.0.1:9223 # 职位侧/求职者账号

提交异步情报任务:

curl -X POST http://127.0.0.1:8787/v1/org-intel/requests \
-H 'content-type: application/json' \
-d '{ "company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音", "TikTok", "飞书"], "mode": "standard", "refresh": "auto", "client_request_id": "openclaw-example" }'

如果库里没有新鲜报告,接口会立即返回 job_ideta_secondseta_at。OpenClaw 到时间后轮询:

curl http://127.0.0.1:8787/v1/org-intel/requests/<job_id>

状态可能是 queuedrunning_jobsrunning_candidatesimportinggenerating_reportreadyblocked_needs_humanfailedready 时返回 report_markdown 和结构化 findings;如果 BOSS 触发验证,会返回 blocked_needs_human,由人工在采集浏览器里处理。

CEO 定时周/月报由 Hipilot 或外部 cron 触发,本服务只保存订阅和生成 digest。创建订阅:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{ "owner_id": "ceo-1", "display_name": "CEO 重点公司监控", "cadence": "weekly_and_monthly", "companies": [ {"company": "字节", "aliases": ["字节跳动", "ByteDance", "抖音"], "mode": "standard"}, {"company": "腾讯", "aliases": ["Tencent", "腾讯云", "微信"], "mode": "standard"}, {"company": "月之暗面", "aliases": ["Moonshot", "Kimi"], "mode": "standard"} ], "timezone": "Asia/Shanghai", "freshness_policy": "auto" }'

到点后触发 digest:

curl -X POST http://127.0.0.1:8787/v1/org-intel/subscriptions/<subscription_id>/digest-runs \
-H 'content-type: application/json' \
-H "Authorization: Bearer <shared-secret>" \
-d '{"cadence":"weekly","client_request_id":"hipilot-cron-run-id"}'

如果返回 queued / running,按 eta_at 轮询:

curl -H "Authorization: Bearer <shared-secret>" \
http://127.0.0.1:8787/v1/org-intel/digest-runs/<digest_job_id>

也可以按 owner/subscription/cadence 取最近 digest,例如查看上一次月报:

curl -H "Authorization: Bearer <shared-secret>" \
"http://127.0.0.1:8787/v1/org-intel/digest-runs?owner_id=ceo-1&cadence=monthly&limit=1"

ready 时返回多公司 digest_markdownpartial_ready 会返回可用公司摘要,并在风险区说明阻塞公司;blocked_needs_human 表示 BOSS 账号验证需要运营处理。

导入人才库和职位侧 run 后,可以生成 BOSS-only 组织情报 Markdown:

npm run report:org -- \
--company 腾讯 \
--alias Tencent \
--alias 腾讯科技 \
--db data-python/boss_talent.sqlite

也可以用目标配置:

npm run report:org -- \
--company 腾讯 \
--target-config org_targets.example.json \
--db data-python/boss_talent.sqlite

报告会写入 org-intel/<公司名>/report-*.md,并把报告正文、来源计数和结构化组织判断写入 SQLite 的 org_intel_reports / org_findings 表。正文只输出聚合判断;原始候选人信号以脱敏 ID 折叠展示。

数据策略

这个 MVP 采用 B+C 混合版:

  • B:Playwright 半自动读取页面可见信息。
  • C:候选人索引落主表,在线简历落快照表,避免覆盖历史。

OCR 快照用于内部检索和复核,最终原文仍以 BOSS 页面为准。

PostgreSQL

sql/schema.sql。第一版先落 NDJSON,确认字段稳定后再接 PostgreSQL。

当前也可以把单次 run 导入本地 SQLite:

.venv/bin/python python/import_run_sqlite.py data-python/runs/run-*.json \
--db data-python/boss_talent.sqlite

测试

npm test

About

Local recruiting data agent for capturing, indexing, and reviewing visible candidate profiles.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages