- AI 应用
- AI 技能
【免费下载链接】ai-job-search
The job search that runs on your machine. AI job application framework built on Claude Code: evaluate postings, tailor CVs, write cover letters, prep interviews. Fork it and own it.
导读
linkedin-cli 是 ai-job-search 仓库中linkedin-search技能附带的命令行工具:它直接调用 LinkedIn 公开的jobs-guest匿名接口,让你可以在任意国家/地区(含全球 Remote 职位)、任意行业下搜索职位列表,并拉取单个职位的完整详情。它无需任何认证、无需 API Key,且零运行时依赖——只要装有bun,克隆仓库即可直接运行。读完本文,你将掌握它的安装方式、search与detail两条命令的全部参数用法、三种输出格式、JSON 错误协议,以及从 URL 构造到 HTML 解析、指数退避重试的完整底层实现原理。
一、工具定位:一个免安装、免认证的求职搜索 CLI
linkedin-cli的核心设计目标可以概括为三点:
- 免认证:数据源是 LinkedIn 的公开
jobs-guest端点(seeMoreJobPostings/search与jobPosting/<id>),不需要登录态、Cookie 或 API Key; - 零依赖:使用纯
bun+ 内置fetch实现,没有任何运行时依赖,bun install是可选的,只用于安装 TypeScript 开发类型定义; - 全球化:
jobs-guest端点对所有市场都是同一套,CLI 的 HTML 解析与具体国家无关,只需通过--location传入不同的地点字符串即可切换市场,同一套代码开箱即用地适用于任何地区的求职者。
正如 SKILL.md 中所注明的,它是仓库"职位门户技能模式"(job-portal-skill pattern)的一个国家无关(country-agnostic)的落地示例,而整个技能与 CLI 的触发入口在仓库中由allowed-tools限定为Bash(bun run .agents/skills/linkedin-search/cli/src/cli.ts *)。
⚠️ 个人使用声明:该工具读取 LinkedIn 的公开职位页面,自动化访问违反 LinkedIn 服务条款(Terms of Service)。请保持低频率访问、不要用于商业用途或批量数据采集,并自行承担运行责任。这一限制贯穿 CLI README、SKILL.md 与 url-reference.md 三处文档。
二、安装与运行前提
由于没有运行时依赖,安装步骤极其简单:
cd .agents/skills/linkedin-search/cli bun install # 可选——仅安装 TypeScript 开发类型定义即使跳过bun install,CLI 也可以直接运行。package.json 明确声明dependencies为空对象,devDependencies中仅有typescript ^5.4.0与@types/bun 1.3.14;main指向src/cli.ts,并提供了linkedin-search的bin入口,同时预置了三条脚本:
| Script | 命令 | 用途 |
|---|---|---|
start | bun run src/cli.ts | 直接启动 CLI |
test | bun test --timeout 30000 | 运行测试套件(30 秒超时) |
typecheck | tsc --noEmit | 类型检查 |
运行时唯一硬性要求是安装 bun(仓库环境的运行时),并具备网络访问 LinkedIn 的能力。
三、命令总览与参数速查
CLI 提供两条命令,入口文件为 src/cli.ts:
| 命令 | 说明 | 输出格式 |
|---|---|---|
search | 搜索职位列表(--location必填) | --format json\|table\|plain(默认json) |
detail | 获取单个职位的完整详情 | --format json\|plain |
所有错误统一写入stderr,格式为{ "error": "...", "code": "..." },进程退出码为1。
3.1search完整参数表
| Flag | 别名 | 说明 |
|---|---|---|
--location | -l | 必填。地点字符串,例如"Mumbai, Maharashtra, India"、"Berlin, Germany"、"London, United Kingdom"、"Remote"。 |
--query | -q | 关键词(职位 / 技能 / 角色),推荐使用。 |
--jobage | 发布于最近 N 天内的职位,取值为1、7、14、30;省略则返回全部。 | |
--jobage-minutes | 发布于最近 N 分钟内的职位(亚天级精度,如30)。与--jobage冲突,二者只能传一个。 | |
--remote | 工作场所类型过滤:remote|hybrid|onsite。 | |
--page | 页码(从 1 开始,每页固定 10 条结果)。 | |
--limit | -n | 客户端侧截断,限制最终输出的结果条数。 |
--format | json|table|plain,默认json。 |
3.2detail命令的输入与输出
detail命令接收一个<id|url>参数,随后用--format json|plain控制输出(默认json)。id取自search结果的数字型职位 ID(例如4426311357)。从 SKILL.md 可知,除裸数字 ID 外,它还可以接受:
- 完整的 LinkedIn
jobs/view/...URL; urn:li:jobPosting:...URN。
返回内容包含:完整职位描述(description)、资历级别(seniority)、雇佣类型(employment type)、职位职能(job function)与所属行业(industries),以及职位是否仍然开放的状态。
四、快速示例(可直接复制运行)
以下示例全部来自 CLI README 与 SKILL.md,可直接在仓库根目录执行:
# 海得拉巴的软件工程师职位,最近 7 天发布 bun run .agents/skills/linkedin-search/cli/src/cli.ts search -q "backend engineer" -l "Hyderabad, Telangana, India" --jobage 7 --format table # 伦敦的产品设计师职位 bun run .agents/skills/linkedin-search/cli/src/cli.ts search -q "product designer" -l "London, United Kingdom" --format table # 全远程的文档工程师职位 bun run .agents/skills/linkedin-search/cli/src/cli.ts search -q "technical writer" -l "Remote" --remote remote --format table # 班加罗尔的数据工程师职位,最近 30 天发布 bun run .agents/skills/linkedin-search/cli/src/cli.ts search -q "data engineer" -l "Bengaluru, Karnataka, India" --jobage 30 --format table # 柏林的产品经理职位,限远程 bun run .agents/skills/linkedin-search/cli/src/cli.ts search -q "product manager" -l "Berlin, Germany" --remote remote --format table # 全球远程的律师助理职位 bun run .agents/skills/linkedin-search/cli/src/cli.ts search -q "paralegal" -l "Remote" --format table # 最近 30 分钟内发布的远程工程师职位(亚天级时间窗) bun run .agents/skills/linkedin-search/cli/src/cli.ts search -q "engineer" -l "Remote" --jobage-minutes 30 --format table # 获取单个职位的完整详情 bun run .agents/skills/linkedin-search/cli/src/cli.ts detail 4426311357 --format plain在仓库的 Agent 工作流中,这些命令通常经由linkedin-search技能触发(触发短语包括 "find a job"、"job search"、"remote jobs"、"are there any X jobs in " 等,见 SKILL.md),把搜索结果中的职位 ID 直接交给detail获取完整描述,进而与仓库中的职位评估、简历定制等流程衔接。
五、输出格式详解
| 格式 | 最佳用途 |
|---|---|
json | 默认格式,适合程序化消费,可把返回结果中的 ID 直接传给detail |
table | 快速人眼扫描的紧凑表格 |
plain | 阅读单个职位的完整详情(配合detail命令) |
search --format json的输出结构(见 src/commands/search.ts)为:
{ "meta": { "count": 10, "page": 1 }, "results": [ { "id": "4426311357", "title": "Backend Engineer", "company": "Example Corp", "companyUrl": "https://www.linkedin.com/company/example", "location": "Hyderabad, Telangana, India", "date": "2026-09-22", "url": "https://www.linkedin.com/jobs/view/4426311357" } ] }其中每个结果对应一条职位卡片,字段来自 helpers.ts 中定义的JobCard接口。table格式会输出 ID / TITLE / COMPANY / LOCATION / DATE 五列的对齐表格;plain格式则把每个职位压缩为「标题 / 公司 · 地点 · 日期 / id / url」几行。detail --format plain会输出标题、公司、地点、资历、雇佣类型、职能、行业、状态(ACTIVE或CLOSED / EXPIRED)、完整描述与 URL 的易读文本。
六、底层实现原理:从 URL 构造到 HTML 解析
6.1 数据源与请求 URL
两个端点在 helpers.ts 中硬编码:
- 搜索:
https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search - 详情:
https://www.linkedin.com/jobs-guest/jobs/api/jobPosting
url-reference.md 对这两个端点给出了完整的参数对照:
| 参数 | 含义 | 示例 |
|---|---|---|
keywords | 自由文本查询 | data engineer |
location | 地点字符串 | Mumbai, Maharashtra, India·Berlin, Germany·Remote |
f_TPR | 发布时间窗口(秒) | r604800(7 天)、r2592000(30 天) |
f_WT | 工作场所类型 | 1现场 ·2远程 ·3混合 |
start | 分页偏移(每页 10 条) | 0、10、20、… |
搜索 URL 的组装逻辑在 src/commands/search.ts 的buildUrl中:--query映射为keywords,--location映射为location,时间窗通过minutesToTPR/jobageToTPR换算成f_TPR秒值,--remote经workTypeFlag映射为f_WT,start = (page - 1) * 10。两个换算函数在 helpers.ts 中实现:
jobageToTPR(days):days <= 0或>= 9999时返回null(表示不设置时间窗),否则返回r${days * 86400},例如 7 天 →r604800、30 天 →r2592000;minutesToTPR(minutes):返回r${minutes * 60},例如 30 分钟 →r1800(该行为有测试用例直接断言,见 search.test.ts);workTypeFlag(mode):remote→2、hybrid→3、onsite/on-site→1,未知值返回null。
6.2 HTML 解析策略:浅层标记 + 正则
两个端点返回的都是 HTML 而非 JSON。开发者有意不引入 DOM 解析器:注释中说明 LinkedIn 的卡片标记浅且稳定,而node-html-parser在 LinkedIn 卡片上存在已知的嵌套 bug(helpers.ts)。因此:
- 搜索页解析(
parseJobCards):响应是扁平的一串<li>职位卡片,按data-entity-urn="urn:li:jobPosting:切分成独立块逐块解析,单张损坏的卡片不会拖垮其余结果;每块提取 ID、base-card__full-link中的 URL、base-search-card__title(或sr-onlyspan)中的标题、base-search-card__subtitle中的公司与公司主页、job-search-card__location中的地点、job-search-card__listdate中的datetime属性(helpers.ts); - 详情页解析(
parseJobDetail):提取top-card-layout__title/topcard__title标题、topcard__org-name-link公司、topcard__flavor--bullet地点,用extractDivContent以标签深度计数的方式(正确处理嵌套<div>)抓取show-more-less-html__markup或description__text富文本描述块,并把<br>、</p>等标签替换为换行保留段落结构;职位标准项(资历、雇佣类型、职能、行业)通过description__job-criteria-subheader标签名与description__job-criteria-text取值的正则成对抓取(helpers.ts); - 状态检测:仅限顶卡范围内检查
closed-job__flavor或 "no longer accepting applications" 文本;实现注释特别强调,该检测只认"是否出现关闭横幅",isActive: true仅表示"未发现关闭横幅",不代表职位必然开放(标记漂移或同意墙响应同样不会渲染横幅)。
6.3 请求健壮性:超时、限流与指数退避
htmlFetch(helpers.ts)是全部请求的统一出口,具备以下行为:
- 设置自定义
User-Agent(Mozilla/5.0 (compatible; linkedin-search-cli/1.0))、Accept、Accept-Language与X-Requested-With: XMLHttpRequest头; - 15 秒请求超时(
AbortSignal.timeout(15000)); - 对429 / 5xx做最多 6 次重试:初始延迟 500ms、指数翻倍至上限 8000ms,并附加 0–500ms 随机抖动(jitter)打散重试时间点;
- 404 返回空字符串(上层据此判定
NOT_FOUND),其他非 2xx 状态直接抛出错误。
6.4detail的 ID 归一化与安全防线
detail并不会盲目接受任何输入。normalizeId(src/commands/detail.ts)依次尝试:
urn:li:jobPosting:<digits>URN 匹配;- 纯 6 位以上数字串;
- URL 形式(带或不带 scheme 均可)——仅接受主机名落在
linkedin.com域内的jobs/view/<id>路径; - 无 scheme 无斜杠的标题 slug(如
software-engineer-1234567890)。
代码注释揭示了一个真实安全教训:旧实现会从任何 URL中取第一个 6 位以上数字段,导致 Greenhouse 或 Lever 的投递链接(职位页自己派发的申请外链)被误解析,从而拉取到恰好同号的无关 LinkedIn 职位并成功退出;如今通过真实的 URL 解析同时拦截了形似域名(linkedin.com.evil.io)与 userinfo 注入(linkedin.com@evil.io)等手段。
七、严格参数校验:宁可报错,不可静默放行
src/cli.ts 实现了两层强校验,并有对应的测试套件(cli-flag-validation.test.ts)逐一验证:
- 未知标志一律拒绝:
search/detail各自维护KNOWN_FLAGS白名单,任何未声明的 flag 都会以UNKNOWN_FLAG错误退出。设计动机来自一次真实事故:在另一门户上,拼错的 flag 名被静默丢弃,导致一次请求把整个门户数据库(13862 条结果)当成匹配结果返回——被丢弃的过滤器改变了搜索结果却毫无报错。 - 数字参数严格整形校验:
--jobage、--jobage-minutes、--page、--limit使用Number()(而非parseInt)解析,必须是大于等于 1 的整数,否则以BAD_ARG退出。这修复了parseInt("0.5") === 0导致f_TPR被静默丢弃的历史缺陷(测试中引用了 issue #371)。 - 冲突参数检测:
--jobage与--jobage-minutes同时传入时以CONFLICTING_AGE_FLAGS退出;--location缺失以NO_LOCATION退出;detail缺 ID 以NO_ID退出;未知命令以BAD_CMD退出;运行期异常统一封装为INTERNAL_ERROR。
完整错误码速查:
| 错误码 | 触发场景 |
|---|---|
NO_LOCATION | search缺少必填的--location/-l |
CONFLICTING_AGE_FLAGS | 同时传了--jobage与--jobage-minutes |
BAD_ARG | 数字参数非整数、小于 1 或非数字 |
UNKNOWN_FLAG | 传入了未声明的 flag |
NO_ID | detail缺少<id\|url>参数 |
BAD_CMD | 未知命令 |
BAD_ID | detail的输入无法解析出职位 ID(含非 LinkedIn 域名 URL) |
NOT_FOUND | 详情页返回 404 |
SEARCH_FAILED/DETAIL_FAILED | 对应命令运行期请求失败 |
INTERNAL_ERROR | 未捕获的运行时异常 |
所有错误均以单行 JSON 写入 stderr,退出码为1,与仓库其他门户 CLI 的错误契约保持一致。
八、常见问题与使用建议
- 为什么
--jobage与--jobage-minutes不能同时用:二者都用于构造同一个f_TPR时间窗参数,同时传入会产生歧义,CLI 选择显式报错而非猜一个值。 --limit与分页的关系:--page决定请求哪一页(每页固定 10 条,即start = (page-1)*10);--limit是客户端侧截断,即在解析出的卡片列表上slice(0, limit)(src/commands/search.ts),不会改变发往 LinkedIn 的请求。- 收到 429 怎么办:CLI 内置最多 6 次、最长约 8 秒延迟的指数退避重试,但仍建议降低请求频率、缩小
--jobage窗口,遵守"个人使用、低频率"的边界。 detail报BAD_ID:确认输入是纯数字 ID、jobs/view/开头的 LinkedIn URL 或urn:li:jobPosting:URN,且主机确为 linkedin.com——申请外链(Greenhouse/Lever 等)无法用于查询。- 想快速验证正确性:直接运行
bun test(在.agents/skills/linkedin-search/cli目录下)执行测试套件,覆盖 flag 校验、未知标志拒绝、f_TPR构造、--limit 0行为、ID 归一化、重试退避与请求超时等场景;运行bun run src/cli.ts --help(或search --help)可随时查看内嵌的完整用法帮助。
九、延伸阅读
- 技能完整定义(触发条件、上下文与 ToS 说明):.agents/skills/linkedin-search/SKILL.md
- CLI 官方说明(本文主体来源):.agents/skills/linkedin-search/cli/README.md
- 数据端点与查询参数对照:/.agents/skills/linkedin-search/url-reference.md
- CLI 入口与参数解析/校验:.agents/skills/linkedin-search/cli/src/cli.ts
- 搜索命令实现:.agents/skills/linkedin-search/cli/src/commands/search.ts
- 详情命令与 ID 归一化:.agents/skills/linkedin-search/cli/src/commands/detail.ts
- 请求、解析与参数换算核心:.agents/skills/linkedin-search/cli/src/helpers.ts
- 依赖与脚本声明:.agents/skills/linkedin-search/cli/package.json
- 参数校验测试:.agents/skills/linkedin-search/cli/tests/cli-flag-validation.test.ts
- AI 应用
- AI 技能
【免费下载链接】ai-job-search
The job search that runs on your machine. AI job application framework built on Claude Code: evaluate postings, tailor CVs, write cover letters, prep interviews. Fork it and own it.
相关推荐
ai-job-search 中的 Jobdanmark 职位搜索技能:基于 Jobdanmark.dk 公共 API 的丹麦求职检索 CLI 实战指南
ai job search 中的 Jobdanmark 职位搜索技能:基于 Jobdanmark.dk 公共 API 的丹麦求职检索 CLI 实战指南 本文围绕
AI 应用AI 技能用 Bun 打造零依赖、全球通用的职位搜索 CLI:ai-job-search 仓库 LinkedIn Search Skill 实战指南
用 Bun 打造零依赖、全球通用的职位搜索 CLI:ai job search 仓库 LinkedIn Search Skill 实战指南 本指南围绕 ai j
AI 应用AI 技能ai-job-search 项目 Jobdanmark 职位搜索 CLI 实战指南:基于 Jobdanmark.dk 公共 API 的丹麦职位检索工具
ai job search 项目 Jobdanmark 职位搜索 CLI 实战指南:基于 Jobdanmark.dk 公共 API 的丹麦职位检索工具 本指南围
AI 应用AI 技能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考