---
name: pxb-liepin
description: >-
  Pixboom 招聘搜人筛人作业流程(猎聘 liepin-cli),为任一岗位搜候选人、筛简历、提报、
  维护候选人池/去重台账/JD 记录。当用户说"搜人""下一轮""继续搜""筛简历""提报候选人"
  "开简历给我看""出 PDF""更新候选人池",或要为某岗找人/筛人时使用。岗位标准看该岗 JD,本流程岗位中立。
---

# Pixboom 搜人筛人作业流程
> ⚠️ **改本 Skill 前先想**:这里只放**泛用作业流程**(所有岗通用、可复用)。单一案例/某轮/某岗的标准经验 → 进该岗 `JD.md`,不进这里。
>
> 每岗:`JD.md`(对外JD + 内部备注表,**判断只看它**)· `去重台账.csv`(`resume_id,status`,仅去重)· `候选人池.md`(确认候选人)· `待定/`(简历 PDF 中转)。
> **路径**:命令里 `<岗位>\…` 等相对路径相对工作区根目录(本 CLAUDE.md 所在);脚本统一在 `.claude/skills/pxb-liepin/scripts/`(下文简写 `SC`)。机械活全在脚本里、跨 Win/mac;判断在本文+agent。

## 全流程:两层循环(主 agent 直接串行跑,**不派 subagent**)
- **主 agent 串行自跑、不派 subagent**:单账号单锁串行,subagent 无并行收益、反而启动慢易卡死。
- **小循环(步骤 1-3,主 agent 自跑、不打扰用户)**:搜一页 → 粗筛挑 ~10 → 精筛 → 合适标 `待提报` → 没攒够回步骤 1 再搜,连轮跑到攒够 ~5 个 `待提报`。**绝不跑一轮就停等用户**。
  > 🔴 **边跑边报,别闷头静默**:每跑完一轮,**先输出一行进度再继续下一轮**,别一口气跑到底让用户 10-20 分钟看不到动静。"不打扰用户"指**没攒够时别停下问用户拍板**,不是全程不吭声;进度是单向心跳,不算打扰。
  > **每轮播报格式**:`第N轮(词:X):搜A/精筛B/新增待提报C,累计待提报 D/目标 E,继续…`(熔断/出批时另行说明)。**带上本轮关键词**,方便步骤 6 复盘哪个词有效。
- **大循环(步骤 4-6,用户校准 JD)**:一批出 PDF → 用户复核 → 入池 → 迭代回写 JD(**重点是进化下一轮关键词**)→ 下一批。JD 越调越准、小循环越搜越准。
- **上下文**:脚本只输出窄表(非原始 JSON)。看完窄表做完判断后,**记住结论即可**(待提报的 `id=标签`、方向感、哪个词有效),不必把整页窄表一直留在上下文;轮数多了丢掉旧轮明细。

**P. 确认/补全 JD(搜之前最先做)** → 0. 排队抢锁 → **0.5 登录(自己探,没登才引导扫码)** → 1. 粗筛 → 2. 精筛 → 3. 够一批没(没够回 1)→ 4. 出 PDF 提醒 → 5. 入池 → 6. 迭代回 0。

**全程红线**:① 单账号串行,**绝不并发**调 liepin ② 反爬宁慢勿封:脚本非零退出即停、别密集连跑(脚本内已带抖动)③ 原始 JSON 不进上下文(脚本只输出窄表)④ 隐私:简历只存本地、不外发、不传外部服务 ⑤ 只 `search`/`resume`,**不 `greet`**(沟通归 HR)⑥ **无头硬执行 + 脚本独占**:**所有 liepin 访问只走脚本**(`search.mjs`/`fetch.mjs`/`pdf.mjs`,已内置强制无头)——**除用户手动 `liepin login` 外,绝不直接敲 `liepin search/resume`**。裸调用在 npm 发布版默认有头会弹窗(`new CdpBrowser()` 不传参,只看 config 默认)。过程中**绝不该弹出任何浏览器窗口**;一旦看到浏览器弹窗又关闭=有人裸调了 liepin、或没先登录,**立即停手排查,别继续刷**(可选:在该机器跑一次 `patch-headless.mjs` 把发布版默认也改成无头,当安全网)。

> 所有脚本无参运行会打印用法;任一脚本退出码 ≠ 0 = 停手,按其报错处置(多半反爬/被踢/未登录)。

---

## P. 开搜前:把 JD + 人员画像聊全(搜之前最先做,主 agent+用户,**不碰 liepin、不抢锁**)
搜任何人**之前**,先把该岗的 **JD + 人员画像**补到能搜的状态——**判断全程只看它,补不全 = 瞎搜、多轮返工**(本流程返工几乎全因开搜前没聊清就动手)。在对话里完成,不动 liepin、不抢锁。

> 🟢 **怎么聊(最重要):像个懂行的猎头跟用人方对齐需求,用人话把"要什么样的人、这种人在哪、什么样的看着像其实不是"聊明白。** 下面的"硬约束 / 三锚 / 锚一二三"全是**给你对照的内部清单,不是台词**——**别把这些词报给客户,别"锚一锚二锚三""硬约束剩余项"这样列条目念给他听**。自然地问、一次一两点;你拿到的每样东西,本质都只是在补 JD 和人员画像这两件事。

**① 补齐事实(填进 JD 硬约束行)**:预算 / 城市 / 经验 / 学历 / 年龄 / 目标人数。用户给了就填,缺的顺嘴问一句即可,别追问"预算够不够"——**按预算搜**,真枯竭(步骤 3)再谈预算。

**② 聊出人员画像(别问"最看重什么能力/不要哪类人"这种抽象话——客户答不出、也喂不进搜索)**:
- **这种人在哪、叫什么** — JD 不写目标公司,得你来配:读 `_共享\公司档案.md`(行业/竞对/业务特点)× 这个岗的性质,**网络检索 + 判断**出"对标 / 竞品 / 上下游 / 相邻行业藏这类人的公司"分类清单,带**职位名变体 + 锚点词**,交用户勾选 / 砍(同公司不同岗清单不同)。落 JD「开搜前三锚」块 + 喂第一轮强搜索词。
- **标杆长什么样** — 用户心里有样板简历,就请他放 `<岗位>\参考简历\`(没有就建该文件夹),**我真读这几份**(不读=没意义),抽出正向画像 / 出身公司 / 技能锚点 → 落 JD 正向建议首行 + 补搜索词。没样板就靠上一条兜。
- **什么看着像、其实不是** — JD 只说谁合适、从不说谁不合适;自然问一句"**有没有哪些行业 / 公司 / 岗位,乍看像这份 JD、其实不对路?**"(走量客服像客服、器件方向像硬件、纯塑胶件像结构、Shopify 电商像独立站前端)→ 落 JD 负向建议首行 + 排除词。

用户当下答不全很正常(真实录取线常要看到拒绝才说清)→ 别硬逼,有样板就靠样板三角定位,答不出的那块标"第一轮校准时确认",照样能开搜。

**intake 完成判据(逐项勾齐才回写 JD、进步骤 0 抢锁;别用"感觉够了"代替勾选)**:① 硬约束行填满 ② 目标公司清单已交用户确认 ③ 样板简历已读、或已标"无/欠" ④ 反向锚已问、或已标"欠" ⑤ 用户说"就按这个搜"。
- **快速通道**:用户明说"立刻搜、先欠着" → 放行,但**记一笔欠了哪块**;欠 = 池子大概率搜歪、多花轮次,**第一轮当校准用**(出批后按反馈补,别假装第一轮就交终选)。
- 已有成熟 JD + 画像齐 + 用户明说直接搜 → 快速过一眼即可,别为流程而流程。

## 0. 排队 + 抢锁
搜人先往 `_共享/搜索队列.md` 追一行 `待办`。当 worker:抢锁 → 挑最早 `待办` → 跑 → 标完成 → 解锁。
```bash
node SC/lock.mjs acquire <岗位>     # 退出码 1 = 别处在搜,排队;0 = 占到锁
# …本批全部 liepin 调用(粗筛/精筛多轮,持同一把锁)…
node SC/lock.mjs renew <岗位>       # 每轮回搜前续命,防长循环被误判过期抢走
node SC/lock.mjs release            # 步骤 4 出 PDF、交用户复核时释放(不是流程最后)
```
- **单账号单锁**:同一时刻只一个对话/进程持锁跑 liepin,多岗/多线只能**排队**(账号物理唯一,与是否用 subagent 无关)。
- **锁的生命周期 = 一"批" = 一次持锁**:`acquire`(本批第一轮前)→ 每轮 `renew` → **步骤 4 出 PDF 交用户复核时 `release`**(复核是纯等待、不碰 liepin,把账号让给别的岗)→ 用户复核完、回来搜下一批时再 `acquire`。**别一把锁从头持到尾**(会把同时在招的别的岗饿死整段复核时间),也别每轮 acquire/release(和排队逻辑打架)。
- **多岗位同招**:各岗在 `_共享/搜索队列.md` 排队;靠"批次边界释放锁"自然交错——A 岗出批给用户复核时让出账号,B 岗就能搜它那批。

## 0.5 登录:自己探,别问用户登没登
开搜**不问**用户"这台机器登录过吗"——**直接拿第一轮 search 当探针**:`search.mjs` 强制无头、不弹窗,跑得通=已登录,直接连着往下跑;只有脚本报未登录才去登录。
- **脚本报未登录/被踢**(非零退出 + 登录相关报错)→ 运行(唯一允许有头的命令):
  ```bash
  LIEPIN_HEADLESS=false liepin login    # Windows PowerShell: $env:LIEPIN_HEADLESS='false'; liepin login
  ```
  跟用户说「我打开猎聘登录页了,扫一下弹出的二维码就行」;扫完登录成功 → **重跑刚才那条 search**,继续。
- 探针只用 `search.mjs`(强制无头);**别裸跑 `liepin search` 探**——发布版默认有头会弹窗。

**开搜前一次性告诉用户预期**:「开始搜了,每轮大概 10–20 分钟,慢是为了悠着点别被网站封,我每轮报你一句,凑够一批叫你看。」别让用户干等以为卡死。

## 1. 粗筛(脚本自动登 CSV)
```bash
node SC/search.mjs "<岗位>\去重台账.csv" "关键词" --city 深圳 --limit 40
```
脚本一步做完:search → 去掉台账已召回的 → 新人登记 `未精筛` → 打印窄表(id/姓名/现职/公司/年限/薪资/学历)。**登 CSV 是查重命根,脚本已强制做**。
- **卡住保护**:search 内置 6 分钟硬超时(resume 2.5 分钟),liepin 卡死(撞反爬/滑块/未登录/Chrome 卡住)会**强杀并报错**,不会静默挂死。**一旦报超时**:告诉用户"搜索卡住已中断",**关掉残留 Chrome、确认已登录、过几分钟再试或把 `--limit` 临时调小**,别立刻重刷(越刷越被反爬盯上)。

卡片有 现职/公司/年限/期望薪资/学历/**年龄/期望职位**,判不了能力(实做/真伪/方向细节要精筛),粗筛只做三件:① **卡面就能判死的**(硬约束出局:城市/期望薪资/年限/学历/年龄;**期望职位明显转行、偏离本岗方向**;+ 卡面可见的通用排除,见步骤 2)→ **标 `粗筛不合适`**;② 按 JD 正/负向挑 **~10 个最对口**进精筛(别只挑 5、也别宽到 20+);③ 其余**卡面过得去 / 拿不准**的留 `未精筛`——这是**干净库存**:召回变稀时直接挑 `未精筛` 再拉精筛,不重搜。
> **`粗筛不合适` 只给卡片一眼就否的;拿不准 = 留 `未精筛`,别误标**(库存要保干净,误标会把好货埋了)。卡面否的落库:
> ```bash
> node SC/dedup.mjs set "<岗位>\去重台账.csv" 粗筛不合适 <卡面否的id...>
> ```
- `--limit 40` 起步(自动翻页,别 `--page`):漏斗要宽,召回 ~40-50 里才挑得出 ~10(约 4-5:1)。**别为了快降到 20**——那样挑 10 几乎没筛。只有反复卡死时才临时调小排查,排查完放回。健康比 `未精筛:精筛 ≈ 4:1`,随时 `node SC/dedup.mjs stats "<岗位>\去重台账.csv"` 自查。

## 2. 精筛(批量取、按 JD 下判决)
拉这页挑出的 ~10 份,**一个循环拉完 = 1 个回合,别开一份判一份**:
```bash
node SC/fetch.mjs <id1> <id2> ...   # 逐份打窄字段,每份间随机抖动(脚本内置)
```
每份过三关(对照该岗 JD):
1. **硬约束 & 去向**:硬约束(城市/期望薪资/年限/学历/年龄/期望职位)粗筛卡片已初筛;精筛复核 `want_city`——**期望城市不在本岗城市 = 想迁走**,慎留。(`want_salary` 仍带回,供关 3 判薪资-成色错配。)
2. **正/负向建议**:命中正向画像几条?命中负向排除信号 → 砍。
3. **真伪**:**实做主看 `work_history` 每段的职责**(已补丁,每段时间线下带 `duty`=那家公司实际做了啥)——这是判成色/方向的主料;`self_descr`(自述)作补充,详略不一、空泛堆形容词/技能名词的警惕注水。再叠:薪资-成色错配(带队多年要地板价＝注水)、方向偏离 → 砍。`work_history` 段头(时长/公司)同时看稳定性(跳槽/空窗)。

**通用排除(岗位中立,叠加在 JD 上,命中即砍)**:① 简历空洞、看不出实际职责产出(是空洞、不是写得短);② 频繁跳槽——多段 <1 年且无任何 ≥2 年稳定段;③ **在职 + 暂无跳槽打算**(`work_status` 为"在职-暂不考虑/暂无跳槽打算"等)——统一砍:约不动、即便约到也议价难,性价比低;"离职找工作""在职-看机会"保留;④ **近一年无工作经历**(最近一段 `work_history` 结束已超 1 年 / 空窗 >1 年)——直接砍。①卡片看不出留精筛、②卡片能初判的粗筛先排、③看 `work_status`(精筛见;卡片若显示在职状态可粗筛先排)、④看 `work_history` 末段结束时间(精筛见;卡片若显示离职已久可粗筛先排)。

**归档**:过三关、对口 → `待提报`;**真·边界且方向稀缺**才 `候补`;其余 `精筛不合适`("够得着但不对"直接砍)。判完升级状态:
```bash
node SC/dedup.mjs set "<岗位>\去重台账.csv" 待提报   <过关id...>
node SC/dedup.mjs set "<岗位>\去重台账.csv" 候补     <边界id...>
node SC/dedup.mjs set "<岗位>\去重台账.csv" 精筛不合适 <淘汰id...>
```

## 3. 够一批没?
攒够 ~5 个 `待提报`(或到目标人数)没?
- **没够** → 回步骤 1 再搜下一页(别停问用户)。
- **够了** → 步骤 4。
- **方向熔断**:本次循环已粗筛 ~100 人(≈2 页)却 **0 个进 `待提报`** → **停循环、反馈用户**:搜了 N 人 0 命中,把卡住的共性(都倒在哪关 / 哪类问题)说清楚,多半**方向或 JD 标准有偏**,等校准再搜。**别闷头继续刷。**
- **路径枯竭**(召回越来越稀)→ 先扫 `未精筛` 库存(`dedup.mjs seen` 比对);仍无 → 停,报情况 + 换路径建议。

## 4. 出 PDF + 一次性提醒(不开网页)
这批 `待提报` 一次性出 PDF 到 `待定/`,对话提醒用户复核。**默认不开 Chrome**(占 profile 挡并行搜索)。
```bash
LIEPIN_PDF_DIR="<岗位>\待定" node SC/pdf.mjs "<id1>=张三-大疆" "<id2>=李四-影石"
node SC/lock.mjs release    # 出完 PDF 即释放——接下来是用户复核、不碰 liepin,把账号让给别的岗
```
告诉用户,并**把每份 PDF 直接列成可点链接发在对话里**——`pdf.mjs` 打印的「已存 …」就是各份路径,逐个做成 markdown 链接 `[姓名·标签](<岗位>/待定/简历_<标签>.pdf)`(href 用正斜杠),用户点一下就开,不必回文件夹翻;**再问出"为什么"**(合适/不合适各带理由——下一轮 JD 收敛的唯一燃料,不能只收"行/不行"):
> 「这批 N 份出好了,直接点开看:
> 1. [张三 · 大疆](<岗位>/待定/简历_张三-大疆.pdf)
> 2. [李四 · 影石](<岗位>/待定/简历_李四-影石.pdf)
>
> 逐个看完跟我说哪些合适、哪些不合适,**各自为什么**:对的对在哪(背景/公司/作品/某段经历)、不对的差在哪(方向偏/成色虚/硬约束)。说得越细,我下一轮越能搜对路。」
- 用户只甩"这个行那个不行"没给理由时,**追问为什么**——步骤 6 的正/负向建议全靠它,缺了就是瞎搜。
- 只存本地、不外发;**别只贴文字不出 PDF**;仅用户要"在线看原页"才临时开 Chrome(看完关掉再跑 liepin)。
- **`待定/` 留删**:入选的 PDF 留下(候选人池直链,是成品);落选/候补的拍板后删;下批出新 PDF 前清掉上批未入选的。

## 5. 入池
用户复核拍板后:
- 合适 → `候选人池.md` 加一行(姓名·现职·经验·薪资·学历·一句判断 + 两列链接 `[PDF](待定/简历_<标签>.pdf)`、`[猎聘](https://lpt.liepin.com/cvview/showresumedetail?resIdEncode=<id>)`),`dedup.mjs set ... 入选`,PDF 留 `待定/`。
- 不合适 → `dedup.mjs set ... 精筛不合适`、删 PDF;留候补标 `候补`。

## 6. 迭代:进化下一轮的搜索关键词(**本流程成败在此**)
**核心认知:每轮真正改变候选池的只有"搜索关键词"。** 关键词决定"捞来谁"(池子),正/负向建议只决定"怎么筛"(过滤)。**池子捞错了,再严的筛也救不回来。** 所以**别"随便换个词、再用更严的标准卡一轮"**——要让上一轮的结果告诉你下一轮搜什么:把搜索词当假设、把结果当数据来修正。

**A. 关键词进化的发动机:扒「入选」简历,反推下一轮的词。** 逐个打开本批入选简历(**必须真读、不能拍脑袋**),从他们身上抽出真实可搜的词:
- **职位名变体** ← 他们简历上**自称什么**?同一岗常有多种叫法(动效设计 / 交互动效 / motion designer / UI动效 / H5动效)——都拿来当下一轮的并列(OR)词。
- **出身公司 + 当时岗位** ← 入选的人**来自哪些公司、在那家做的什么**?某段经历特别对口,往往是**那家公司 + 当时那个岗位做的事**正好契合 → 开一条「**目标公司**」轴,**先顺藤摸瓜搜同公司、同岗位的其他人**(最快找到类似合适的),再扩到其对标 / 竞品 / 上下游。
- **技术/工具/项目名词** ← 反复出现的工具(AE / Lottie / Figma…)、项目或产品词,都是锚点词。

**B. 诊断本轮每个关键词,决定怎么调(一次只动一个轴,才看得出是哪步起效):**

| 本轮表现 | 诊断 | 怎么调 |
|---|---|---|
| 召回多但大多不对口 | 太宽/有歧义 | 收紧:加硬约束、换更具体职位变体、加排除词 |
| 召回太少 / 快枯竭 | 太窄 | 放宽:同义词/近义词、职位变体(品牌↔市场、外贸↔贸易),或扩邻近行业(医美→生物科技/个护) |
| 连 ≥2 轮净入池 = 0 | 该词判死 | 移进弱搜索词弃用,换锚点 |

- 关键词轴轮换:① 职位名+变体/同义词 ② 目标公司(对标/竞品/上下游)③ 技术/工具名词 ④ 项目/产品名词 ⑤ 邻近行业(本行枯竭时)。

**C. 追加一行到 JD 内部备注表(别改旧行,一轮一行):**
- **强搜索词** ← 本轮有效词 + A 步从入选简历新抽出的词(职位变体/目标公司/锚点词)。
- **弱搜索词** ← 本轮无效/太宽/枯竭的词,**写清为什么废**,别下轮再踩。
- **正向建议** ← 用户说的"为什么合适" + 你真读入选简历的交叉印证(画像/背景/公司/技能锚点),落成下批筛人要找的信号。
- **负向建议** ← 用户说的"为什么不合适",归纳成排除信号(方向偏/成色虚/某类背景)。
- 每格要点即可,别散文、别重抄硬约束。

> ⚠️ **锁**:步骤 4 已 `release`。下面 D(复判)和 E(新搜)要再调 liepin——动手前先 `node SC/lock.mjs acquire <岗位>` 重新占锁(占不到=别的岗在搜,排队)。

**D. 回扫错杀(本轮标准/方向有调整时必做——否则越搜越少):** 标准是动态的,上几轮按**旧**标准砍掉的人,换**新**标准可能是错杀。**回去新搜之前,先按新正/负向建议复判已召回的库存**,别只顾往前找新词:
- `node SC/dedup.mjs list "<岗位>\去重台账.csv" 候补` → 先复判**候补**(本就是边界,最易翻案)。
- `node SC/dedup.mjs list "<岗位>\去重台账.csv" 未精筛` → 库存里没细看过的,直接精筛(本就是该挖的库存)。
- **标准明显变向时**(某硬约束放宽 / 新增了正向信号)→ `node SC/dedup.mjs list "<岗位>\去重台账.csv" 精筛不合适` 取近几轮的,挑**可能受这次变化影响的**重新 `fetch.mjs` + 复判。注意这要重拉简历、耗 liepin 调用,**只挑相关的、别全表重刷**。
- 复判翻案的 → `dedup.mjs set ... 待提报`,并入本批。
> 错杀回收常比新搜更快补人,尤其方向一调。**"越搜越少"多半就是只往前搜、不回头捡。**

**E.** 用**进化后的关键词**回步骤 0 继续,到满额收尾。

**收尾自检**:召回全员登 CSV 了(脚本已保证)?`待提报` 都归档了?`未精筛:精筛` 还 ~4:1(`dedup.mjs stats`)?入选进候选人池了?JD 表追加本批行了(**强搜索词含从入选简历新抽的词**、正向建议含真读印证)?`lock.mjs release` 了?

---
## 脚本清单(`SC = .claude/skills/pxb-liepin/scripts/`)
| 脚本 | 用法 | 干什么 |
|---|---|---|
| `lock.mjs` | `acquire\|renew\|release <岗位>` | 单账号搜索锁,30 分钟过期,退出码表态 |
| `search.mjs` | `<csv> "<词>" [--city --limit --experience --salary --degree]` | 搜+去重+登记`未精筛`+打窄表 |
| `fetch.mjs` | `<id...>` | 批量拉简历窄字段,内置反爬抖动 |
| `dedup.mjs` | `seen\|add\|set\|list\|stats <csv> [状态] [id...]` | 台账查重/改状态/**按状态列 id(回扫错杀)**/比例自查 |
| `pdf.mjs` | `<id=标签...>`(配 `LIEPIN_PDF_DIR`) | 整页存 PDF;`npm root -g` 动态定位 liepin-cli |
| `patch-headless.mjs` | (装完跑一次,可选) | 把发布版 liepin-cli 默认改无头,免疫裸调用弹窗;幂等 |
| `patch-resume.mjs` | (装完跑一次,**需要**) | 让 resume 把每段 `duty`(逐段职责)吐进 `work_history`——精筛主料,不打就只剩时间线;幂等。上游修复发布后可弃用 |

> liepin 命令本身细节见同目录 `liepin-cli` skill。环境/安装见仓库根 `README.md`。
