招聘板 API
六家招聘管理系统都通过自己明确记录为公开、免认证使用的 API 发布公司的在招职位。scrape_template 直接读取这些 API:数值精确,链路中没有 LLM,合规性来自结构本身而不是靠论证。
1. 为什么平台自有 API 优于抓取招聘板
公司的招聘页面只是一个数据库的渲染结果。它背后的招聘管理系统——Greenhouse、Lever、Ashby、Workable、Recruitee、Teamtailor——才是雇主真正发布职位的记录系统,而这六家都记录了一个以 JSON 或 RSS 提供同一批记录的公开端点。
读取这个端点并不是一种更便宜的抓取方式。它是一种不同的操作,失败模式也不同:没有会被解析错的标记,没有需要点击的分页控件,也没有语言模型来判断某个字段是什么意思。
同一个招聘板的两种做法
| 抓取渲染后的招聘板 | 读取平台的 API | |
|---|---|---|
| 数值 | 从标记中解析得来;页面改版会悄悄改变它们 | 精确,与雇主录入的一致 |
| 覆盖范围 | 只有页面首次渲染出来的内容 | 招聘板上已发布的全部职位 |
| 请求数 | 每页一次,外加分页 | 招聘板一次请求,仅在平台本身分页时才需分页 |
| 解释 | 由选择器或 LLM 判断字段的含义 | 不做任何推断;缺失的字段读作 null |
| 许可 | 逐个案例论证 | 平台已把该端点记录为公开可用 |
2. 六个招聘板连接器
每个连接器都指向平台自己记录为公开、免认证使用的端点,且所在主机的 robots.txt 允许访问。传入招聘板 URL,连接器会为您解析出 API 端点;也可以在 params 中传入该公司在这个平台上的标识符。
greenhouse-jobscontent: true 获取。招聘板 token 来自 job-boards.greenhouse.io/<token>
lever-postingsskip 与 limit 分页。公司标识来自 jobs.lever.co/<company>
ashby-jobs招聘页名称来自 jobs.ashbyhq.com/<name>
workable-jobsdetails: true 获取。账户子域来自 apply.workable.com/<subdomain>
recruitee-offers子域来自 <company>.recruitee.com
teamtailor-jobstt: 命名空间,使地点、部门、角色和事业部得以保留。除非用 per_page 另行指定,否则返回 100 个职位。子域来自 <company>.teamtailor.com
content: true 会加上完整的 HTML 描述,使一个大型招聘板从 349 KB 增至 4.2 MB(Stripe 的 571 个职位的招聘板,2026-08-28 实测)。Workable 的 details: true 表现相同。需要描述时再请求,不要养成习惯性索取。api.lever.co 在其 robots.txt 中声明了 Crawl-delay: 1,因此 lever-postings 把该值以 crawlDelaySeconds: 1 的形式重新发布,交由真正发起请求的那一层遵守。连接器自身从不发起任何请求——它们只构造 URL 并解析响应,从而把超时、SSRF 策略和速率限制留在负责这些事情的那一层。3. 六个平台,一种职位结构
六个连接器都归一到同样的十二个字段,因此两个招聘板可以直接合并,不需要按数据源单独映射。平台本身不提供的字段返回 null,绝不会给出一个看似合理的猜测。
共享的职位结构
| 字段 | 含义 |
|---|---|
id | 平台自身的标识符,统一转为字符串,使合并后的列表只有一种标识符类型 |
title | 职位名称 |
url | 公开的职位页面 |
location | 平台所标注的地点 |
department | 部门,当平台具备该层级时 |
team | 团队,当平台具备该层级时 |
employment_type | 按平台自己的措辞原样传递,不强行归一到同一套词汇 |
remote | true、false,或当数据源的用词无法回答该问题时为 null |
published_at | ISO 8601,由六种不同日期格式归一而来 |
updated_at | ISO 8601,当平台发布了该时间时 |
description | 纯文本,已去除平台的 HTML |
source | 产生该记录的连接器 |
- 不做任何推断。 Greenhouse 根本不发布雇佣类型,因此 Greenhouse 的职位在该字段返回
null,而不是一个看着很确定的Full-time。 - `remote` 只回答一个问题: 这份工作能否在任何地方完成?混合办公确实是部分远程,因此读作
null,并把平台自己的用词保留在旁边。把混合办公的职位读作远程,和读作坐班一样错。 - `employment_type` 保留平台的原始写法。 Lever 的
Regular Full Time (Salary)究竟算什么,应由能看到自己数据的您来判断。 - 招聘方联系方式被丢弃,而不是映射出来。 Recruitee 会在每个职位上标注一个专属的应聘邮箱;该字段绝不会进入输出。职位信息是公司数据,这些连接器返回的也只有公司数据。
两个招聘板,一个列表
因为结构是共享的,合并一个 Greenhouse 招聘板和一个 Lever 招聘板不需要任何映射代码。
// Every job-board connector returns the same twelve fields, so two boards
// concatenate with no per-source mapping step.
const API = 'https://crawlforge.dev/api/v1/tools/scrape_template';
async function board(template: string, params: Record<string, unknown>) {
const response = await fetch(API, {
method: 'POST',
headers: {
'X-API-Key': process.env.CRAWLFORGE_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({ template, params }),
});
const payload = await response.json();
return payload.data.data.items;
}
const jobs = [
...(await board('greenhouse-jobs', { company: 'stripe' })),
...(await board('lever-postings', { company: 'matterport' })),
];
// One filter over both boards. `remote` is null wherever the platform's own
// word did not answer the question — hybrid is not remote and not on-site.
const remote = jobs.filter((j) => j.remote === true);
// Cost: 1 credit per call — 2 credits for both boards, however many jobs.4. 调用方式:params 与 auto
招聘板连接器需要的是公司在该平台上的标识符——它是招聘板 URL 中的一小段字符串,而不是整个 URL。请在 params 中传入。
如果您手上已经有 URL,又不想去判断该由哪个模板处理,可以发送 template: "auto",由 CrawlForge 根据 URL 选择模板——判定是确定性的,锚定到具体主机的模式优先于仅匹配路径形状的模式。
两种调用风格
同一个招聘板,两种到达方式。
# A job-board connector needs the company's identifier on that platform,
# not the whole careers-page URL. That is what `params` carries.
curl -X POST https://crawlforge.dev/api/v1/tools/scrape_template \
-H "X-API-Key: $CRAWLFORGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "greenhouse-jobs",
"params": {
"company": "stripe"
}
}'
# Add "content": true to the params for full descriptions — it takes a large
# board past 4 MB, so ask for it only when you need the text.{ "template": "list" } 且不带 URL,即可获得全部模板及其标识符、描述和 mode:list 表示一次调用返回多条记录的连接器,entity 表示只返回单条记录的连接器。5. 同样的思路,用在招聘之外
招聘板是最清晰的例子,但这条原则在任何地方都成立:凡是运营方自己发布了数据,就读那份数据。
shopify-collectionproducts.json 读取某个集合中的全部商品——与 shopify-product 针对单件商品返回的精确价格、原价和库存完全同源,因此集合页与商品页不可能互相矛盾。nhtsa-vinnpi-provider6. 我们不做什么,以及原因
下面这份清单不是路线图。每一条都是我们考虑过、技术上做得到、但基于明确理由选择不做的路径。
仅限公开、免认证的数据源
smartrecruiters-postingsapi.smartrecruiters.com/robots.txt 对所有代理禁止一切访问,唯一的例外是 LinkedInBot(2026-08-28 核实)。以其他身份访问该端点,就意味着每次调用都要覆盖 robots.txt,而这不是连接器可以替您做的决定。在与 SmartRecruiters 达成协议之前,暂不提供。遵守 robots.txt——我们没有覆盖它
wday/cxs 端点是招聘站点自身的内部端点,并非 Workday 记录为公开可用的 API。任何针对它的连接器都以客户书面确认为前提,目前没有任何一个上线。不是文档化的公开 API
任何套餐都不提供
npi-provider 正因如此才是一次登记库查询:每个 NPI 一条记录,不附加任何关联信息。公司数据,不是个人数据
这些连接器所遵循的规则
- 仅限公开、免认证的页面和端点。
- 只要存在文档化的 API,就优先使用它而不是抓取。
- 所有会发起请求的工具默认遵守
robots.txt。覆盖必须显式指定、逐请求生效,并记录在您的 API key 名下——而且它永远不会触及 CrawlForge 永久排除名单上的主机。 - CrawlForge 诚实标识自己:单一 user agent、真实的产品名称和一个联系 URL。请参阅爬虫验证。
- 默认采用温和的请求速率,并遵守
Crawl-delay和Retry-After。 - 退出请求和下架要求在平台层永久生效。
scrape_template 每次调用消耗 1 credit,无论使用哪个连接器,也无论返回多少个职位。