BOSS 直聘岗位 JD 提取
2026-07-31 重构:不再依赖第三方 boss_agent_cli。本 skill 内部用
shared/recruiter_job_catalog.py(浏览器内 fetch 拿 BOSS 后端 API)解析 query → encryptJobId,然后仍走 patchright 直连 CDP 浏览器抓 BOSS 编辑页 iframe 拿完整 JD 表单。
前提条件
- Edge/Chrome 以
--remote-debugging-port=9222启动 - Boss 招聘者 session 已登录(
zp_at+wt2+bst三 cookie 都存在) - 可选:用
shared/cdp_preflight.check_login()自检登录态
用法
python scripts/boss_jd.py <查询条件> [--job-name <name>] [--encrypt-job-id <encryptJobId>] [--run-id <id>]
参数:
<查询条件>— 三选一:加密岗位ID:a2bb7b1e7a76f0440nZ-39S0E1NS数字 jobId:524499312岗位名称:管培生(精确匹配优先,否则模糊匹配)
--job-name(必填,元数据用,目录名不再用它):岗位中文名,会写入jobs.json作可读标识。--encrypt-job-id(必填,新设计):BOSS 返回的encryptJobId,直接作为工作区目录名。例如9a7759badfd95d350nFz3d-_F1NX。5 步脚本(list → download → score → HTML → greet)必须传同一个值,产物才落在同一个工作区目录。- 也可设环境变量
BOSS_HR_ENCRYPT_JOB_ID=<encryptJobId>作为 fallback(CLI 参数优先)。 --run-id(可选,新任务入口):本次 run 的 ID。- 不传 → 自动调
create_new_run(),生成新 run_id(YYYY-MM-DD_HHMMSS,同秒冲突自动加_N后缀)。每个新任务必须生成新 run_id——禁止沿用旧 run_id。 - 传 --run-id → 调
bind_existing_run(run_id),校验 run_dir 存在 + encrypt_job_id 匹配。不通过报错。 - 拿到 run_id 后必须传给 Step 2~5 所有后续脚本(
recommend_list.py/recommend_download.py/score_resumes.py/generate_html_report.py/auto_greet.py)——这些脚本的--run-id是required=True,不传 argparse 直接退出 2。
- 不传 → 自动调
🚨 2026-07-30 重构:不再有
state/current_run.json。每个 run 的状态独立写到runs/<run_id>/run.json(含confirmed标志位、steps_done、finished等)。Step 1 完成后run.json.confirmed=false,必须等用户在 BOSS 调整完筛选条件后调:python -X utf8 shared/confirm_run.py \ --job-name "<岗位名>" --encrypt-job-id "<id>" --run-id "<run_id>"把
confirmed切到 true 才能跑 Step 2。
🚨 严格模式:缺
--encrypt-job-id(且未设BOSS_HR_ENCRYPT_JOB_ID)时脚本直接报错退出,不会静默回退到中文目录名——避免你以为跑了新路径、实际又落到中文路径的事故。
工作流程
- 用
shared/recruiter_job_catalog.resolve_recruiter_job(query)把 query 解析成encryptJobId- 支持三种 query:encryptJobId 精确 / jobId 数字精确 / 岗位名(精确优先,模糊兜底)
- 返回
{'encryptJobId', 'jobId', 'jobName', 'address', 'salaryDesc', ...}
- 用 patchright 连接 CDP 浏览器
- 导航到岗位编辑页(
/web/chat/job/edit?encryptId=...) - 等待 iframe 加载完成后提取表单内容(含职位描述富文本 + 关键词 + 福利)
- 输出到
~/Desktop/boss-hr-output/<encryptJobId>/runs/<run_id>/process/job_detail.json
输出目录规范(新设计 · 2026-07-29+)
目录名 = encryptJobId(不再用中文岗位名),job_name 仅作为 jobs.json 里的可读元数据。
~/Desktop/boss-hr-output/
├── jobs.json # JobRegistry:encryptJobId → {name, company}
└── <encryptJobId>/ # 目录名 = BOSS 返回的 encryptJobId(如 9a7759badfd95d350nFz3d-_F1NX)
├── state/ # 跨 run 保留(不覆盖)
│ ├── candidate_pool.json
│ ├── download_state.json
│ ├── resumes_master.json
│ ├── collection_state.json
│ ├── scored_state.json
└── runs/
└── <run_id>/ # 一次筛选任务
├── <run_id>_screening_report.html
└── process/
├── job_detail.json ← 本脚本输出
├── recommend_geek_ids.json ← Step 2a: list 输出
├── new_resumes.json ← Step 2b: download 输出
├── scoring/ ← Step 3a: prepare 输出
│ ├── manifest.json
│ ├── inputs/candidate_<geek_id>.json
│ ├── outputs/candidate_<geek_id>.json
│ └── _skipped.json
├── _llm_scores.json ← Step 3b: collect 合并产物
├── screening_results.json ← Step 3c: score 收尾产物
└── greet_log.json ← Step 5
路径选择集中在
shared/output_manager.JobOutputManager——CLI 脚本只接收并透传--encrypt-job-id,不参与路径拼接,避免中文路径在 URL/文件 IO 里翻车。多机器可移植:通过环境变量
BOSS_HR_OUTPUT_DIR改工作区根(默认~/Desktop/boss-hr-output)。
输出格式
{
"jobName": "管培生",
"encryptJobId": "a2bb7b1e...",
"bodyText": "完整页面文本",
"formValues": ["岗位职责:...", "职位名称", "..."],
"parsed": {
"title": "",
"positionType": "",
"location": "",
"description": "岗位职责 + 任职要求 原文"
},
"_meta": {
"run_id": "2026-07-27_083015",
"fetched_at": "2026-07-27 08:30:30"
}
}
工作区约定:所有数据统一存放在
~/Desktop/boss-hr-output/<encryptJobId>/下,目录名直接用 BOSS 的encryptJobId(避免中文 URL 编码 / 文件名编码问题)。job_name(中文岗位名)只作为jobs.json里的可读元数据。 由shared/output_manager.JobOutputManager统一管理(boss-recommend-downloader/resume-screener/html-report/boss-hr-greet共用同一规范)。 6 个 CLI 脚本都必须显式传--encrypt-job-id(或设 envBOSS_HR_ENCRYPT_JOB_ID),缺则直接报错——不会静默回退到中文目录名。
技术要点
- BOSS 管理后台为 iframe 架构:主页面是导航壳,表单内容在
src="/web/frame/job/edit?..."的子框架中 - 使用
domcontentloaded而非networkidle以提速(5-8s) - 登录态与 HTTP 调用:所有 BOSS HTTP 调用都走浏览器内 fetch(
page.evaluate('fetch(...)')), 复用浏览器真实 TLS 指纹 + 自动带 cookie。不需要单独同步__zp_stoken__。 - 岗位 query 解析由
shared/recruiter_job_catalog.resolve_recruiter_job()提供, 支持精确/模糊匹配、同名兜底;详见 shared/SKILL.md § recruiter_job_catalog。 encryptJobId是后续推荐/下载/评分所有步骤的岗位标识,会作为candidate_key的前半段被使用
调用示例(boss_jd.py 内部)
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'shared'))
from recruiter_job_catalog import resolve_recruiter_job
# 三种 query 都接受
job = resolve_recruiter_job('9a7759badfd95d350nFz3d-_F1NX') # eid 精确
job = resolve_recruiter_job('559622717') # jobId 精确
job = resolve_recruiter_job('线控底盘制动、转向工程师') # jobName 精确
job = resolve_recruiter_job('工程师') # 模糊兜底
if not job:
raise SystemExit(f"岗位未找到")
encrypt_job_id = job['encryptJobId'] # 进入 fetch_jd + 落盘流程