- 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.
Jobindex.dk 是丹麦最大的在线职位门户之一,覆盖 IT、工程、设计、营销等全行业实时职位。本项目内置的jobindex-cli(位于 .agents/skills/jobindex-search/cli/)是一个免认证、基于 Bun 的 TypeScript 命令行工具:它内部解析 Jobindex 返回的 HTML 与内嵌数据,对外统一输出干净的 JSON、表格或纯文本,并配套search/detail两条子命令形成"搜索 → 取详情"的完整工作流。读完本文,你将掌握该 CLI 的全部命令参数、响应字段语义、底层解析与重试机制,并能把它接入 AI 求职自动化流程中。
概览:设计定位与数据流
从 CLI 文档 可以概括出该工具的三个核心事实:
- Base URL:
https://www.jobindex.dk/ - 认证:无(Jobindex 的公开搜索与职位详情页无需任何凭据)
- 数据格式:Jobindex 的接口返回 JSON 与内嵌 HTML 的混合体,
jobindex-cli在内部解析 HTML 片段,向调用方输出结构化数据
这套设计让它非常适合脚本化集成:既可以直接在终端里检索,也可以被 AI Agent 通过 .agents/skills/jobindex-search/SKILL.md 的触发规则(Bash(bun run .agents/skills/jobindex-search/cli/src/cli.ts *))调用,用于评估丹麦职位、筛选岗位后进入简历定制与求职信撰写环节。
安装:两条命令跑起来
CLI 以 Bun 运行时开发(package.json 声明了@bunli/core、zod、node-html-parser等依赖,且"type": "module"),安装方式见 CLI 文档:
cd skills/jobindex-search/cli bun install从 package.json 可以看到,项目还提供了便利入口:bun start等价于bun run src/cli.ts,并声明了jobindex作为 bin 名称(bun link后可直接使用jobindex命令);bun test运行测试套件(超时 30 秒),bun run typecheck执行tsc --noEmit。
命令总览与全局约定
CLI 提供两条子命令,入口文件为 .agents/skills/jobindex-search/cli/src/cli.ts:
| 命令 | 作用 |
|---|---|
search | 按关键词搜索职位列表 |
detail | 抓取单个职位的完整详情 |
所有命令都接受--format json|table|plain(默认json;detail仅支持json|plain)。所有错误统一写入stderr,格式为{ "error": "...", "code": "..." },进程以退出码1结束。
值得注意 cli.ts 的"未知 flag 拒绝"机制:命令分发前,CLI 会逐 token 校验--long与-short两种形式。源码注释记录了动机——被静默丢弃的过滤参数会改变搜索结果而不报任何错误,曾经一个拼错的 flag 名导致整个门户数据库被当作"匹配结果"返回。因此任何未声明的 flag(包括未注册的短 flag、负数形式的取值)都会触发:
{ "error": "unknown flag ... - flags are never silently ignored ...", "code": "UNKNOWN_FLAG" }并以退出码1结束,防止产生误导性的"全量返回"结果。
search:搜索职位列表
端点与调用方式
文档记录的端点为GET https://www.jobindex.dk/jobsoegning.json;从 search.ts 源码看,实际请求的是${BASE_URL}/jobsoegning?${params}。两者并不矛盾:Jobindex 曾以jobsoegning.json提供结果,但随后改为客户端渲染——helpers.ts 的注释明确说明该 JSON 端点如今返回204 No Content,完整结果内嵌在 HTML 页面var Stash = {...}脚本块中(路径jobsearch/result_app -> storeData -> searchResponse -> { hitcount, results[] })。CLI 通过extractStash扫描大括号深度并兼顾字符串转义地截取该 blob,再由findSearchResponse递归定位结果对象。
bun run src/cli.ts search [flags]Flags 一览
| Flag | 类型 | 默认值 | 说明 |
|---|---|---|---|
--query/-q | string | — | 关键词搜索(如python、grafisk designer),必需 |
--page | number | 1 | 页码(1 起始) |
--jobage | number | 9999 | 职位发布最大天数:1、7、14、30或9999(全部) |
--sort | string | score | 排序:score(相关度)或date(最新优先) |
--limit | number | — | 客户端侧截断返回结果总数 |
--format | string | json | 输出格式:json、table、plain |
参数在源码层由 search.ts 的 zod 模式约束:page必须是>=1的整数(z.coerce.number().int().min(1).default(1)),jobage同样>=1,limit为可选整数,format为枚举。数值参数先coerce再校验,传入--page not-a-number会得到validation类错误(见 cli-contract.test.ts)。
查询参数按q、page、jobage、sort拼装进 URL(URLSearchParams自动编码,空格/中文/丹麦语字符无需手动处理)。--query缺失时立即报MISSING_REQUIRED并退出(search.ts)。
排序与时效选项
Sort options
| 值 | 说明 |
|---|---|
score | 相关度 / 最佳匹配(默认) |
date | 最新发布优先 |
Jobage options
| 值 | 说明 |
|---|---|
1 | 仅今天发布 |
7 | 最近 7 天 |
14 | 最近 14 天 |
30 | 最近 30 天 |
9999 | 全部时间(默认) |
典型用法示例
# 搜索最近 7 天发布的 Python 职位,按日期排序 bun run src/cli.ts search --query python --jobage 7 --sort date # 搜索 "grafisk designer" 职位,仅显示前 5 条 bun run src/cli.ts search --query "grafisk designer" --limit 5 # data engineer 第 2 页,表格输出 bun run src/cli.ts search --query "data engineer" --page 2 --format table响应结构(JSON 格式)
{ "meta": { "total": 237, "page": 1, "perPage": 20 }, "results": [ { "id": "h1647303", "title": "Data Engineer til opbygning af Gavefabrikkens dataplatform", "company": "Gavefabrikken", "companyUrl": "https://www.gavefabrikken.dk/", "location": "Valby", "date": "2026-03-12", "url": "https://www.jobindex.dk/jobannonce/h1647303/data-engineer-til-opbygning-af-gavefabrikkens-dataplatform", "description": "Vi søger en dygtig Data Engineer til at opbygge og vedligeholde vores dataplatform..." } ] }字段说明
id— 以h前缀开头的字符串 ID(如h1647303),请原样传给detail命令。company— 公司名;部分聚合型列表可能为null。companyUrl— 公司主页 URL;不存在时为null。location— 城市或区域;未列出时为null。date—<time>元素datetime属性的 ISO 日期(YYYY-MM-DD);可能为null。description— 职位卡片内的简介摘录;可能为null或空。url— 该职位的完整 Jobindex.dk URL。meta.total— 从hitcount解析;Jobindex 内部使用丹麦式千分位点号(如18.903),CLI 会剥离.后转为整数18903。
从源码看,search 结果字段实际来自 Stash blob 的results[]:id取r.tid,title取r.headline,公司取r.company?.name ?? r.companytext,位置优先r.area、回退到r.geojson.features[0].properties.title,日期取r.firstdate,并额外提取apply_deadline/lastdate得到deadline(apply_deadline_asap为真时 deadline 强制为null,表示"尽快申请、无固定截止日")。perPage固定为20,这是 Jobindex 的分页硬限制,CLI 不提供--per-page参数。
关于区域过滤的重要提示
Jobindex API 并不可靠地支持通过查询参数做地区/区域过滤。
area与geoareaid参数会被静默忽略。若要按地点筛选,请在--query中加入城市名(如--query "python aarhus"),或使用--limit截断后对 JSON 输出做外部过滤。
SKILL 文档给出了同款建议,例如--query "data engineer københavn"、--query "python aarhus"。若要系统化地按地域浏览,.agents/skills/jobindex-search/url-reference.md 还提供了可直接构造的 URL 形态(如/jobsoegning/storkoebenhavn?q=...、/jobsoegning/it/itdrift/storkoebenhavn?q=data+engineer),以及仅能通过 UI 面板筛选的雇佣类型、工时、远程工作等条件——这些无法通过本 CLI 的查询参数表达,可作为人工复核阶段的补充手段。
输出格式与底层解析
--format的行为在 search.ts 中实现:
json:JSON.stringify(output, null, 2)美化输出到 stdout,便于程序化处理与管道传递;table:固定列宽对齐(id 11 字符、title 40 字符截断、company 20 字符截断),适合快速扫读;plain:逐字段id:/title:/company:/location:/date:/deadline:/url:/description:分行输出。
历史上的解析路径(README 保留的"Parsing notes")基于对result_list_box_html的正则提取:每个职位卡片包裹在[data-beacon-tid]/div#jobad-wrapper-<id>内,字段选择器为h4 > a(标题与链接)、.jix-toolbar-top__company a(公司与公司链接)、span.jix_robotjob--area(地点)、time[datetime](日期)、首个<p>(描述);卡片分div.PaidJob(赞助)与div.jix_robotjob(聚合)两种类型,选择器一致。该逻辑至今保留在 helpers.ts 的 parseJobCards 中(注释指出 node-html-parser 对这类含未闭合标签的 HTML 存在嵌套解析 bug,正则方案反而更可靠),而现行实现则优先走 Stash JSON 路径。
detail:抓取单个职位完整详情
URL:https://www.jobindex.dk/jobannonce/{id}/{slug}
bun run src/cli.ts detail <id> [--format json|plain]id即search结果中的职位 ID(如h1647303)。slug 是可选的——CLI 会先构造https://www.jobindex.dk/jobannonce/{id}并跟随重定向得到规范 URL;也可以直接把search返回的完整url传进来。你也可以把完整 URL 直接作为id参数传入。
Flags
| Flag | 类型 | 默认值 | 说明 |
|---|---|---|---|
--format | string | json | 输出格式:json、plain |
示例
# 使用 search 结果中的 ID bun run src/cli.ts detail h1647303 # 使用完整 URL bun run src/cli.ts detail "https://www.jobindex.dk/jobannonce/h1647303/data-engineer-til-opbygning-af-gavefabrikkens-dataplatform" # 纯文本输出 bun run src/cli.ts detail h1647303 --format plain响应结构(JSON 格式)
{ "id": "h1647303", "title": "Data Engineer til opbygning af Gavefabrikkens dataplatform", "company": "Gavefabrikken", "companyUrl": "https://www.gavefabrikken.dk/", "location": "Valby, København", "date": "2026-03-12", "deadline": "2026-04-01", "employmentType": "Fastansættelse", "hours": "Fuldtid", "applyUrl": "https://www.gavefabrikken.dk/jobs/apply/123", "url": "https://www.jobindex.dk/jobannonce/h1647303/data-engineer-til-opbygning-af-gavefabrikkens-dataplatform", "description": "Full job description text here..." }两种页面形态与字段差异
Jobindex 的详情页有两种形态,字段可用性不同:
- jobindex 原生页:可通过其
jd-*事实块识别(如jd-deadline、jd-location、jd-type、jd-workhours),携带公司、地点、ISO 截止日期、雇佣类型与工时; - 外部 ATS 透传页:即雇主在自己托管系统(如 hr-manager/Talentech)上的广告经 jobindex 转发,页面上没有可靠的公司锚点——此时
company为null(而不是填成 ATS 品牌名),地点与截止日期若有则取自广告自身的部件。
字段说明
id/url— 始终是 jobindex 的 ID 及其jobannonceURL,绝不使用页面的og:url/canonical(透传页上这些指向外部 ATS,而非职位本身);deadline—YYYY-MM-DD或null;丹麦语长日期(如13. september 2026)与部件里的DD-MM-YYYY都会被转换;employmentType/hours— 取自原生事实块;透传页上为null;companyUrl— 当前恒为null,两种页面形态都没有可用的公司链接;applyUrl— 存在时为 Jobindex 的跳转链接(/c?t=...),否则为null;description— 广告正文纯文本(HTML 已剥离),正文为空时回退到页面的 meta description;- 除
id、title、url外,所有字段都可能为null。
URL 规范化:安全边界
detail.ts 的 buildUrl 是"存下来的(不可信)URL → 网络请求"之间的闸门:URL 输入必须满足 host 为jobindex.dk或其子域、路径匹配/jobannonce/<id>(id 形如[a-zA-Z]\d+,兼容h1647303、r13677312等),随后用提取出的 ID 重建https://www.jobindex.dk/jobannonce/{id}短链——即裸 ID 路径一直使用的规范形态。裸 ID 则保持宽松的字母数字下划线横线 token 约束,未知 ID 由服务端 404 兜底。源码注释记录了一次真实事故:旧版会原样抓取任意 http(s) URL,导致重定向目标、仿冒域名或首页被解析成"格式良好的虚假职位"并以退出码 0 返回;这个漏洞正是该严格校验的由来。任何无法解析的输入都会得到BAD_ID错误。
细节工程:日期归一化与 HTML 实体解码
丹麦语页面给日期解析带来不少"坑",detail.ts 的 toIsoDate 与 helpers.ts 的 decodeHtmlEntities 专门处理了三类形态:
- ISO:
YYYY-MM-DD,直接保留; - 部件格式:
DD-MM-YYYY(hr-manager 部件常见),翻转为YYYY-MM-DD; - 丹麦语长格式:
13. september 2026,借助内置的 12 个月丹麦语映射表(januar→01 … december→12)转换。
实体解码覆盖&、<、>、"、'、',以及丹麦语特有的ø→ø、æ→æ、å→å(含大写),再加上十进制(é)与十六进制(é)数字实体。数字实体解码使用String.fromCodePoint而非fromCharCode,确保补充平面码点(如 emoji U+1F600)正确解码、越界值直接丢弃——parsing.test.ts 中有对应回归用例(如ø→ø、😀→😀)。描述正文的提取还会先剔除<head>、<script>、<style>与注释,避免文本扫描读到 CSS/JS;广告正文为空或过短时回退到og:description或descriptionmeta。
错误处理契约
所有错误统一写入stderr,JSON 格式,进程退出码1:
{ "error": "Job not found", "code": "NOT_FOUND" } { "error": "API request failed: 500 Internal Server Error", "code": "API_ERROR" } { "error": "Failed to parse job listing HTML", "code": "PARSE_ERROR" } { "error": "--query is required", "code": "MISSING_REQUIRED" }这个契约被测试显式锁定:cli-contract.test.ts 断言"无 query 的 search"与"无 ID 的 detail"都以退出码 1、stdout 为空、stderr 为上述 JSON 结构结束;非法数字参数则产出{ ok: false, error: { kind: "validation", option: "page", ... } }。stdout 永远只承载正常结果,这让管道处理(如... | jq)不会被错误信息污染。
请求健壮性:超时、重试与退避
helpers.ts 的 htmlFetch(以及对称的apiFetch)内置了稳健的请求策略,这是把 CLI 用于自动化时的关键保障:
- 超时:每次请求
AbortSignal.timeout(15000),15 秒无响应即中止; - 重试:最多 6 次;遇到
429(限流)或5xx时指数退避重试——初始延迟 500ms,每次翻倍,上限 5000ms,并叠加 0–500ms 随机抖动(jitter)避免惊群; - UA 与语言:携带
Mozilla/5.0 (compatible; jobindex-cli/1.0)UA 与Accept-Language: da,en;q=0.9,redirect: "follow"自动跟随; - 404:
detail场景下 404 被翻译为明确的Job not found。
重试耗尽后抛出API request failed: <status> <statusText>,最终以API_ERROR呈现。
实战工作流:把 CLI 接入求职自动化
结合 SKILL.md 的使用建议,推荐模式是search→detail两步走:
- 用
search按关键词、时效、排序定位职位,拿到id列表; - 对候选职位逐个
detail <id>,取完整描述、截止日期、雇佣类型、工时与投递链接,供后续的简历定制、求职信撰写与面试准备环节消费。
高频技巧:--jobage 7或--jobage 1只看新鲜职位(不带则含全部历史);--sort date看最新发布;--format table快速扫读、--format json交给程序处理、--format plain通读单条详情;跨页浏览用--page(每页固定 20 条),单页内截断用--limit。
值得留意的两个已知限制(均为 Jobindex 侧行为,而非 CLI 缺陷):分页大小固定 20、不可配置;区域过滤参数不生效,按城市筛选必须在 query 里带城市名。这两个约束在 README 与 SKILL.md 中被反复强调,写自动化脚本时务必遵守。
最后,本仓库还内置了面向其他门户的同构 CLI(freehire、jobbank、jobdanmark、jobnet、linkedin-search,均位于 .agents/skills/),它们共享相同的"JSON 错误契约 + 未知 flag 拒绝 + 重试退避"设计,掌握了jobindex-cli的用法即可快速迁移到其余求职数据源上。
- 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 项目实战:jobbank-cli 详解——基于 RSS 与 JSON-LD 的丹麦高端职位检索工具
ai job search 项目实战:jobbank cli 详解——基于 RSS 与 JSON LD 的丹麦高端职位检索工具 本篇文章围绕 ai job se
AI 应用AI 技能ai-job-search 实战:用 jobbank-search 技能精准检索丹麦高学历职位(RSS + JSON-LD 双通道 CLI 指南)
ai job search 实战:用 jobbank search 技能精准检索丹麦高学历职位(RSS + JSON LD 双通道 CLI 指南) 本指南以 a
AI 应用AI 技能Volcano PDB 插件设计与实现:让 reclaim、preempt 和 shuffle 驱逐尊重 PodDisruptionBudget
Volcano PDB 插件设计与实现:让 reclaim、preempt 和 shuffle 驱逐尊重 PodDisruptionBudget 本文基于 Vo
AI 应用AI 技能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考