猎聘职位搜索
搜索猎聘的公开职位列表。免登录、免 API key、零运行时依赖 —— 有 bun 就能跑。
⚠️ 仅供个人求职使用
本技能访问猎聘的公开页面与接口。猎聘 www 主机的 robots.txt 禁止带查询串的路径,
站点也部署了风控脚本。请保持低频访问,不要用于商业用途或批量数据采集。
CLI 内置了 1.5 秒最小请求间隔与退避重试,但仍请自行控制调用量,风险自负。
何时使用
- 按关键词 + 城市搜索在招职位
- 只看最近 N 天更新的职位
- 取某个职位的完整描述
命令
搜索职位
node .agents/skills/liepin-search/cli/src/cli.ts search -q "<关键词>" -l "<城市>" [参数]
--query <文本>/-q— 必填,关键词(职位名、技能)。--location <城市>/-l— 必填,中文城市名(北京、上海、天津、广州、深圳、南京、 杭州、合肥、福州、成都)或猎聘城市码。其他城市名会报BAD_CITY错误。--jobage <天数>— 只保留 N 天内更新的职位。客户端过滤(猎聘的服务端时间参数无效,见「注意事项」)。--page <n>— 页码,1 起始,上限 10 页。--limit <n>/-n— 客户端截断条数。--edu/--years/--salary/--comp-scale/--industry— 猎聘原生筛选码,见url-reference.md。 ⚠️ 求职流程里不要传这五个(workflows/job-scrape.mdStep 1b 第 5 条有实测账:按本科过滤会排掉 283 个岗,其中 24 个本来评到「可以投」;锁行业会砍掉 225 个行业字段为空的,其中 57 个能投)。服务端排掉的进不了库、回收不了,而这几个字段是展示值——eduLevel与 JD 正文实测 58% 对不上。接口本身没问题,这五个参数是给「我就要看某一类」那种一次性查询用的。--format json|table|plain— 默认json。
数字类参数(--jobage/--page/--limit)做严格整数校验 —— 传 3abc 这类带
拖尾垃圾字符的值会直接报 BAD_ARG 并退出码 1,不会被静默截断成 3。
取职位详情
node .agents/skills/liepin-search/cli/src/cli.ts detail <id|url> [--format json|plain]
id 是搜索结果里的 id。也可以直接传完整职位 URL。推荐传 URL —— 猎聘的企业直招页
(/job/<id>.shtml)和猎头职位页(/a/<id>.shtml)路径不同,传裸 id 时 CLI 需要依次
试两条路径,多一次请求。
detail 返回的字段中 title/company/location 现在都来自详情页的 schema.org
JSON-LD 结构化数据,正常情况下三者都有值(不再是 null);date/eduLevel/workYears/
compScale/compIndustry/compStage/recruiterTitle/recruiterSurname 详情页本身
不提供,恒为 null —— 需要这些字段请从 search 的结果里取。
使用示例
# 北京的后端岗位,近 14 天更新
node .agents/skills/liepin-search/cli/src/cli.ts search -q "后端开发" -l "北京" --jobage 14 --format table
# 上海的产品经理,取前 10 条
node .agents/skills/liepin-search/cli/src/cli.ts search -q "产品经理" -l "上海" --limit 10 --format plain
# 深圳的数据分析岗第 2 页
node .agents/skills/liepin-search/cli/src/cli.ts search -q "数据分析" -l "深圳" --page 2
# 某个职位的完整描述
node .agents/skills/liepin-search/cli/src/cli.ts detail "https://www.liepin.com/job/1983665159.shtml" --format plain
输出格式
| 格式 | 适用场景 |
|---|---|
json |
默认 —— 程序化使用,把 id/url 传给 detail |
table |
快速人工扫读 |
plain |
读单个职位的完整详情 |
搜索结果每条包含契约字段 id/title/company/location/date/url,以及国内求职
关键字段:salary(原样保留「薪资面议」)、salaryMonths(如 15 薪则为 15)、
eduLevel、workYears、compScale、compIndustry、compStage(融资阶段)、
recruiterTitle、recruiterSurname、isHeadhunter。缺失值一律为 null,不会省略键。
recruiterSurname是跟你说话的那个人的姓,从接口的recruiter.recruiterName截出来的。CLI 不输出全名 —— 下游要它只为跟进消息里的那句称呼 (<姓>女士),而全名是第三方个人信息,截断放在最上游才不用赌下游每一层 都记得脱敏。非中文名返回null(拉丁名分不出姓在前在后,猜错就是把全名 原样吐出去)。 ⚠️ 它和recruiterTitle不是一回事:后者装的是职务(「猎头顾问」 「HRBP」「招聘专员」「研发总监」…),同一个值会挂在几十个不同的招聘者身上 —— 那是角色,不是人,当称呼用不了。
search 的 json 格式在 meta 里还带 count/page/totalPage/city/hasNext,
以及 skipped —— 接口返回的卡片里解析失败被跳过的条数。正常应为 0;非 0 就是信号:
要么这一批数据本身有畸形卡片,要么猎聘改了字段结构,值得去 url-reference.md 核对。
所有错误写入 stderr,格式 {"error": "...", "code": "..."},退出码 1。已知错误码
包括 NO_QUERY/NO_LOCATION/BAD_CITY/BAD_ARG/NO_ID/BAD_ID/BAD_CMD(参数或
用法问题)、RATE_LIMITED(含义不单一,见下)、REQUEST_FAILED(网络/HTTP 错误)、
NOT_FOUND(detail 的职位不存在或已下线)、PARSE_FAILED(抓到了页面/响应但解析不出
预期数据,通常意味着猎聘改了 markup)、INTERNAL_ERROR(未分类的异常,来自 cli.ts
顶层兜底)、SEARCH_FAILED/DETAIL_FAILED(search/detail 各自 catch 块的兜底错误码,
出现在抛出的异常不是 LiepinError 时——通常是意外的代码错误或环境问题,而不是可归类的
猎聘接口/参数问题)。
RATE_LIMITED 含义不单一,调用方需要自行区分:这个 code 目前同时覆盖两种不同性质的
情况——
真正的风控/限流:HTTP 429/5xx 重试耗尽,或接口返回 HTML 而非 JSON(触发了验证页)。 这种等一会再重试通常会自愈。
请求参数非法:
parseJobCards发现响应flag !== 1时也会抛RATE_LIMITED(消息形如「猎聘接口返回 flag=0,通常意味着参数非法或触发风控」),但猎聘对非法筛选码 (--edu/--salary/--comp-scale/--industry传了猎聘不认的值)同样会返回flag !== 1。 这种情况重试无用——不会自愈,需要检查这几个筛选参数的值是否是url-reference.md里列出的合法猎聘码。看到
RATE_LIMITED报错时,先检查本次调用有没有传--edu/--salary/--comp-scale/--industry:如果传了,先怀疑参数值不合法;只有确认参数都合法(或根本没传这几个 筛选参数)时,才当作风控/限流处理、退避重试。(这是已知的错误码粒度问题,helpers.ts尚未把两种情况拆成不同 code——阶段 2 可能会改进,本文档只是如实反映现状。)
注意事项
--jobage是客户端过滤。 猎聘接口的pubTime参数是哑的 —— 传「一天内」照样返回 两年前的职位,所以 CLI 按结果里的更新时间自行过滤。代价是:过滤发生在取回 42 条之后, 过滤后条数可能远少于一页。- 没有更新时间的卡片也会被丢掉,而
refreshTime不是每张卡都有 (实测某用户职位库里 2232 个猎聘岗只有 259 个带日期)。 - 所以
meta里分开报两个数:droppedTooOld(有日期、太旧 —— 过滤器在干活) 和droppedNoDate(没日期,被一起丢了 —— 那些可能是新岗)。droppedNoDate明显偏大时,这一轮考虑别传--jobage, 宁可多抓一些旧的,也好过把新岗一起扔掉。 - 原来这里写「翻页时干脆别传它」,2026-08-27 那个前提被实测推翻了。
旧依据(某用户职位库 2026-08-23):带日期的比例 第 1 页 250/860(29%)·
第 2 页往后 9/710(1%),据此得出「
--jobage从第 2 页起几乎会把收获清空」。 而 2026-08-27 从同一个 CLI、同样不传--jobage抓了 4 个词 × 前 2 页, 入库 140 个:第 1 页 97/97、第 2 页 43/43,两边都是 100% 带日期。 所以那个 1% 不是页码的性质,是当时那批数据的性质。 改成:翻页照常可以传,但每一轮都看一眼droppedNoDate—— 它看的是这一次请求的实际情况,不是一个会过期的分布。
- 没有更新时间的卡片也会被丢掉,而
- 翻页上限 10 页。 接口报的总数(约 800)不可信,实际只能翻到第 10 页(约 400 条)。
- 每页 42 条里有 2 条不是本市的。 猎聘会掺入推广位。CLI 不过滤它们(过滤会误伤真实的
跨区职位),使用时请看
location字段。 - 猎头职位占比不低。
isHeadhunter为true的职位由猎头顾问发布而非企业 HR 直招, 沟通链路更长。这是信息,不是缺点。 date是「更新时间」不是「发布时间」。 猎聘的职位会被反复刷新,一个长期挂着的岗位 可以有很新的更新时间。评估时结合04-job-evaluation.md的职位真伪信号一起看。- 限速是进程内的。 CLI 内置 1.5 秒最小请求间隔,同一个进程里的请求会串行排队。但并行运行
多个 CLI 进程(例如
/job-scrape同时跑多个 portal)时,各进程各有各的计时,互不约束 —— 真正的总量控制取决于你怎么调用它。 - 触发风控/限流,或传了非法筛选参数时,CLI 都会以
RATE_LIMITED报错退出,不会静默 返回空结果。两者含义不同、处理方式也不同——见上面「已知错误码」一节里RATE_LIMITED的展开说明,不要不加区分地一律退避重试。 - markup 变更时的修复入口是
url-reference.md,它记录了全部接口参数与解析锚点。