拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用 jobindex-cli 在丹麦 Jobindex.dk 上检索职位:一条命令完成搜索、详情抓取与 JSON 结构化输出

用 jobindex-cli 在丹麦 Jobindex.dk 上检索职位:一条命令完成搜索、详情抓取与 JSON 结构化输出
  • 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.

项目地址:https://gitcode.com/GitHub_Trending/ai/ai-job-search
点击查看免费下载

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/-qstring—关键词搜索(如python、grafisk designer),必需
--pagenumber1页码(1 起始)
--jobagenumber9999职位发布最大天数:1、7、14、30或9999(全部)
--sortstringscore排序:score(相关度)或date(最新优先)
--limitnumber—客户端侧截断返回结果总数
--formatstringjson输出格式: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类型默认值说明
--formatstringjson输出格式: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)转换。

实体解码覆盖&amp;、&lt;、&gt;、&quot;、&#39;、&apos;,以及丹麦语特有的&oslash;→ø、&aelig;→æ、&aring;→å(含大写),再加上十进制(&#233;)与十六进制(&#xE9;)数字实体。数字实体解码使用String.fromCodePoint而非fromCharCode,确保补充平面码点(如 emoji U+1F600)正确解码、越界值直接丢弃——parsing.test.ts 中有对应回归用例(如&#xF8;→ø、&#128512;→😀)。描述正文的提取还会先剔除<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两步走:

  1. 用search按关键词、时效、排序定位职位,拿到id列表;
  2. 对候选职位逐个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.

项目地址:https://gitcode.com/GitHub_Trending/ai/ai-job-search
点击查看免费下载
上一篇:GoReleaser v2.5 深度解读:Rust 与 Zig 多语言构建支持上线,附源码级配置剖析
下一篇:Android Studio中文界面终极指南:告别英文困扰,5分钟快速汉化完整教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表