关于 openroles
openroles 是用于从雇主招聘页面查找职位的 Python 库、命令行工具和 MCP 服务。它统一读取 15 种 ATS 系统及符合条件的招聘网站职位信息。
主要功能
- 01读取 15 种 ATS 系统的职位
- 02按名称、网址或招聘页识别职位板
- 03按关键词、地点、日期或远程状态筛选
- 04返回统一字段和申请链接
- 05提供 Python 库与命令行工具
适用场景
- 01查找指定雇主的当前职位
- 02按职位词、地点或发布时间搜索
- 03匹配不同网站上的相同职位
MCP 相关
stdio MCP 服务提供 `find_job_board` 和 `list_open_roles` 工具,供 MCP 客户端和 Agent 识别招聘板并获取职位。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
2收录的工具列表 · 更新于 3 天前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| target | string | 是 | 公司名称、招聘页面 URL 或职位板 URL |
详细介绍
openroles
**所有开放职位,均直接来自雇主自己的招聘系统。**涵盖 15 种申请人跟踪系统(Greenhouse、Lever、Ashby、Workday、Workable、Teamtailor、Personio、 Recruitee、Rippling、JazzHR、Gem、Polymer、Manatal、Homerun 和 ClearCompany),以及任何向搜索引擎公开职位的招聘网站,统一为一种格式。读取雇主自家招聘页面使用的 Feed 和 API:无需 API 密钥、无需账户、无需爬取服务,且只使用 Python 标准库。
$ pip install openroles
$ openroles figma --query data --limit 3
2026-09-23 Data Engineer Intern (2027) San Francisco, CA • New York, NY https://boards.greenhouse.io/figma/jobs/6178851004?gh_jid=6178851004
2026-09-21 Data Platform Engineer San Francisco, CA • New York, NY • Unite https://boards.greenhouse.io/figma/jobs/6201407004?gh_jid=6201407004
2026-09-21 PhD Intern, Data Science (2027) San Francisco, CA • New York, NY https://boards.greenhouse.io/figma/jobs/6200626004?gh_jid=6200626004
3 open role(s)
提供公司名称、招聘页面、招聘职位页面 URL 或 ats:token。它会找到招聘职位页面,读取所有开放职位,并以相同字段返回结果,无论雇主使用哪种系统。
为什么
招聘网站会漏掉职位、延迟展示,还会过期后继续转载。雇主自己的招聘页面才是信息来源:内容完整、及时,并附有真实的申请链接。大多数申请人跟踪系统都会以 JSON、RSS 或 XML 的形式公开招聘职位页面,供雇主自家的招聘页面使用。openroles 也会以相同方式读取这些信息,遵循礼貌访问原则,并通过一个函数统一处理所有允许这样做的系统。
安装
pip install openroles # or: uvx openroles <company>
Python 3.9 或更高版本。无需依赖项。
命令行
openroles linear # every open role at Linear
openroles https://jobs.ashbyhq.com/linear # the same, from the board URL
openroles lever:zoox --query "data" --since 14 # title words, posted in the last 14 days
openroles figma --location "New York" # filter by place (--remote for remote only)
openroles https://adobe.wd5.myworkdayjobs.com/external_experienced \
--query "data scientist" --details # Workday, with each posting's description
openroles detect "Figma" # which ATS, and the board's token
openroles hosts greenhouse:figma lever:zoox # the hosts to allow in a network policy
openroles personio:acme # any system, as ats:token
openroles careersite:careers.acme.example # a careers site with no job API
openroles linear --json > roles.json # JSON (the default when piped), or --jsonl
openroles linear --cache ~/.cache/openroles # an unchanged board sends nothing next run
Python
import openroles
openroles.detect("Figma")
# [Board(ats='greenhouse', token='figma'), Board(ats='ashby', token='figma')]
jobs = openroles.fetch("greenhouse:figma", query="data", location="New York")
for job in jobs:
print(job.posted, job.title, job.location, job.url)
job.to_dict() # plain JSON-ready dict
openroles.hosts("personio:acme")
# ['acme.jobs.personio.de', 'acme.jobs.personio.com']: what a firewall must allow
openroles.job_id("https://www.acme.example/careers/?gh_jid=4012345")
# 'gh-4012345': the same id fetch() gives that posting, from its address alone
job_id 用于匹配在两个地方找到的同一条招聘信息:从雇主的招聘页面读取,并在招聘网站上链接。它可读取 Greenhouse(任何带有 gh_jid 的页面也适用)、Lever、Ashby、Rippling、Workable、Personio 和 Workday 地址,不会获取任何内容;对于不含该系统自身职位 ID 的地址,则返回 ""。Workday 有一种情况存在歧义:一个职位列表发送的路径已进行百分号编码(“M%C3%BCnchen”),另一个发送的是未编码的形式(“München”),两者会得到相同地址,而 job_id 会将其读取为未编码形式。
当无法读取招聘职位页面时(令牌错误、没有响应),fetch() 会引发 openroles.BoardError;当主机的 robots.txt 禁止访问时,则会引发 openroles.RobotsDisallowed。
在自己的应用程序中
openroles 首先是一个库:CLI 和 MCP 服务只是构建在 fetch 和
detect 之上的轻量封装。嵌入该库的应用程序只需在启动时声明一次自身名称:
openroles.configure(
user_agent="acme-jobs/1.0 (+https://acme.example/contact)", # sent with every request
robots_agents=["acme-jobs"], # a Disallow for your agent stops a read too
cache_dir="~/.cache/acme-jobs/openroles", # optional: keep answers between runs
)
设置 cache_dir 后,每个招聘网站上次返回的结果都会连同网站提供的 ETag 或
Last-Modified 日期一起保存;下次运行时会带上这些信息发起请求:支持这些信息且内容未
更改的招聘网站会返回 304 Not Modified,不发送任何内容,程序则读取已保存的副本。
对于带有 Last-Modified 日期但没有 ETag 的结果,在上次完整读取后的一天内(由
openroles._http 中的 MODIFIED_MAX_AGE 设置,时长为 24 小时),程序会按这种方式发起请求;
之后会完整读取一次。因此,如果主机的数据发生变化时日期却不更新,最多会让招聘网站延迟一天;
在单次运行中,无论是否设置文件夹,情况也相同。每次读取仍会向招聘网站发起请求。默认关闭。
只有满足以下条件时才会保存结果:请求方法为 GET,结果未标记为 no-store,并且所有内容均已接收:
完整的 Content-Length、分块结果中的每个数据块,或完整读取到末尾的 gzip 流。若结果未提供长度,
也不是分块传输或 gzip 压缩,则连接关闭时就视为读取完毕,无论是否被截断,因此每次都会完整读取。
保存的结果位于仅所有者可访问的文件中;未使用 30 天后会过期,最多约 500 MB。每个进程都会将文件夹
清理至此限制,因此两个进程同时写入同一文件夹(例如,长期运行的应用程序和一次命令行运行)时,
文件夹大小可能会超出限制,直到其中一个进程下次检查整个文件夹:开始使用文件夹时至少检查一次,
之后使用期间每小时至少检查一次;如果该进程自身写入的内容超过上限,也会立即检查。
搜索中所有由应用程序自行决定的内容都作为参数传入:query、location、remote、
since_days、country 和 details;其中 details 可以是一个函数,用于判断哪些招聘信息值得
请求其职位描述。report={} 会返回每个招聘网站上的失败信息。对于应用程序获取的其他内容,
openroles.robots_allows(host, paths) 会回答与 openroles 自身相同的 robots.txt 检查问题。
面向 AI Agent(MCP)
openroles mcp 是一个通过
stdio 运行的 Model Context Protocol 服务,因此 Claude、Cursor
及其他 Agent 可以自行查询任何雇主的开放职位。提供两个工具:find_job_board 和
list_open_roles(默认不包含描述,以保持回答简短)。
Claude Code:
claude mcp add openroles -- uvx openroles mcp
Claude Desktop、Cursor 和其他客户端(在配置中使用 mcpServers):
{
"mcpServers": {
"openroles": { "command": "uvx", "args": ["openroles", "mcp"] }
}
}
此外还有 llms.txt,这是为需要使用该库或 CLI 的模型准备的简短说明。
你将获得什么
来自任何系统的每个职位都具有相同的字段:
| 字段 | |
|---|---|
id | 在不同运行间保持稳定:系统前缀(gh-、lv-、ab-、wd- 等)加上系统自身的 ID |
ats、board | 职位来源,例如 greenhouse、figma |
title、url | 职位名称及其申请页面 |
location、locations | 招聘信息中列出的所有地点,分别以拼接字符串和列表形式提供 |
workplace | remote、hybrid、onsite;若招聘网站未说明,则为空字符串 "" |
remote | True、False;若招聘网站未说明,则为 None(混合办公时为 None) |
posted | YYYY-MM-DD,系统提供时为首次发布日期(而非最后编辑日期);见下文 |
department、employment_type | 招聘网站提供时包含 |
compensation | 雇主公布薪资范围时,按招聘网站原文提供 |
salary_min、salary_max、salary_currency、salary_period | 同一薪资范围,以数字、ISO 货币代码以及 hour、day、week、month、year 或 "" 表示(例如每两周一次的周期);仅在招聘网站以数字形式公布时提供(Lever、Ashby、Recruitee、Rippling、ClearCompany 和招聘网站);绝不会从职位描述中读取 |
deadline | 招聘网站提供时包含 |
description | 纯文本,已移除 HTML |
支持的系统
| 系统 | 请求数 | 说明 |
|---|---|---|
| Greenhouse | 每个招聘板 1 次 | 职位描述;职位列出的每个办公地点都计作该职位的一个地点 |
| Lever | 每个招聘板 1 次 | 包含要求列表的职位描述;自动发现欧盟招聘板 |
| Ashby | 每个招聘板 1 次 | 职位描述和公布的薪酬。Ashby 不公布首次发布的日期,因此 posted 表示职位最后一次发布的时间 |
| Workday | 每 20 个职位 1 次,另加每份职位描述 1 次 | --query 会搜索招聘板,并在职位名称持续匹配时继续读取;--country 根据招聘板用于归类国家的筛选条件缩小范围;--details 会获取职位描述,但仅限职位名称符合 --query 的职位 |
| Workable | 每个招聘板 1 次 | 职位描述;无薪酬。根据 Workable 的文档构建,尚未在实际招聘板上运行(编写时其主机正在限制请求频率) |
| Teamtailor | 每 100 个职位 1 次 | 职位描述;混合办公和远程办公;雇主自有域名上的招聘网站也适用 |
| Personio | 每个招聘板 1 次 | 雇主以英文撰写的职位描述;适用于 .de 和 .com 账户 |
| Recruitee | 每个招聘板 1 次 | 职位描述、薪酬和截止日期。从 2027 年 2 月 10 日起,Recruitee 要求使用每个雇主自己的令牌,此后读取招聘板时会提示需要该令牌 |
| Rippling | 每个招聘板 1 次,另加每份职位描述 1 次 | 职位描述和日期来自每个职位自己的记录,仅在使用 --details 或 --query 时获取;每个招聘板最多 80 个(Rippling 允许 10 分钟内发出 100 次请求) |
| JazzHR | 每个招聘板 1 次 | 职位描述;无日期或薪酬。信息流每天重新生成一次 |
| Gem | 每个招聘板 1 次 | 职位描述;混合办公和远程办公 |
| Polymer | 每 20 个职位 1 次,另加每份职位描述 1 次 | 薪酬;职位描述的获取方式与 Rippling 相同,每个招聘板最多 60 个 |
| Manatal | 每 100 个职位 1 次 | 职位描述;无日期 |
| Homerun | 每个招聘板 1 次 | 职位描述和薪酬,来自每个 Homerun 招聘页面链接到的信息流 |
| ClearCompany | 每个招聘板 1 次 | 职位描述和薪酬;令牌就是招聘页面显示的网站 ID。只读取第一页,如果还有更多页面会发出警告 |
任意招聘网站(careersite) | 每个站点地图 1 次,另加每个职位页面 1 次 | 读取每个职位页面为搜索引擎提供的 schema.org JobPosting(包括 iCIMS 和 Eightfold 等网站),每次调用最多读取 40 个页面,按最新优先 |
Workday 上标为“3 Locations”的职位会将主要地点显示为“以及另外 2 个”;如果对非主要地点使用
--location,则会读取职位页面,以确认该地点是否属于其他地点之一。Workday 每个招聘板最多读取前
2,000 个职位;超过此数量时会发出警告,提示使用 --query 搜索。
detect 可以通过公司名称查找招聘板(除 Workday 和 ClearCompany 外的所有系统,均会尝试雇主可能使用的名称)、通过任意招聘板 URL 查找,也可以通过链接到或嵌入招聘板的招聘页面查找,无论该页面采用何种写法。无法通过名称推测 Workday 招聘板:请提供其 URL(tenant.wdN.myworkdayjobs.com 或
wdN.myworkdaysite.com/recruiting/tenant)。也不会推测招聘网站:请将其写为
careersite:<address>。如果招聘页面使用 openroles 无法读取的系统,系统会发出警告并指出该系统。
不支持的系统及其原因
每个系统在被排除前都经过了检查。只有当系统发布公开的职位板块,且其条款和 robots.txt 都允许读取时,才会被加入上面的列表。
| 系统 | 原因 |
|---|---|
| SmartRecruiters | 其 robots.txt 禁止除 LinkedIn 爬虫以外的所有爬虫访问其公共 API |
| SuccessFactors、Dayforce、UKG、Paycom、Pinpoint | robots.txt 禁止访问职位页面,或职位板块需要登录 |
| BambooHR、Breezy | 其条款禁止自动读取招聘页面 |
| Cornerstone、BrassRing、Hireology、Comeet | 需要通过发给浏览器的会话令牌读取职位板块 |
| Phenom | 检查过的网站通过 robots.txt 的 Content-Signal 要求不得将其内容用作 AI 输入 |
| Oracle Recruiting | 其职位 API 标记为内部使用,且职位页面会在浏览器中构建 JobPosting |
| Freshteam | 其供应商正在关闭该服务 |
| iCIMS、Eightfold | 没有开放的职位 API,但其招聘网站通常会发布 JobPosting 页面:使用 careersite: 读取 |
| Taleo、ADP、Paylocity、JOIN、TalentLyft、ApplicantPro | 尚未充分检查,无法构建读取器 |
设计上尊重访问规则
- 首先读取 robots.txt,并遵守拒绝规则,绝不设法绕过。对于供应商将其端点说明为公共 API 的情况(Greenhouse、Lever、Workable、Teamtailor、Personio、Recruitee、Rippling、JazzHR、Gem、Polymer、Manatal),明确的 Disallow 会阻止读取,而无法读取 robots.txt 则不会阻止读取。其他所有情况(Ashby、Workday、Homerun、ClearCompany 和任何招聘网站)都必须能够读取 robots.txt,且规则必须允许读取。对于 Workday,招聘网站自身的规则也适用。
- 不会读取要求不得用作 AI 输入的网站。 遵守 robots.txt 中的
Content-Signal: ai-input=no,因为 openroles 通常由 AI Agent 运行。 - 只有新主机的 robots.txt 允许时,才会跟随重定向,且最多跟随五次。
- 读取 robots.txt 后,会遵守主机的
Crawl-delay,在请求之间等待相应时长(Lever 要求等待一秒);并按 RFC 9309 的要求,每天重新读取一次 robots.txt。 - 每个主机同时进行的请求最多为两个;每次调用都会设置超时;传输时使用 gzip;对于同一次运行中重复读取的内容,以及使用缓存文件夹时跨运行读取的内容,都会发送条件请求(
ETag和Last-Modified);并使用如实标明本项目的User-Agent。 - 绝不解析声明了 DTD 或实体的 XML,以免恶意数据源展开实体。
- 若职位板块返回 429 或 503,本次运行剩余时间内将不再访问它,并遵守文档中说明的速率限制(Rippling 的限制为 10 分钟内 100 次请求)。
这样做合规吗?
其中大多数系统都将其端点说明为公共端点,正是为了在其他地方展示雇主发布的职位。每个读取器的源文件都引用了供应商自己的说明,并标明出处。Workday、Ashby、Homerun 和 ClearCompany 的端点是其招聘页面调用的端点;这些端点没有文档说明,可能随时更改,openroles 只会在 robots.txt 允许的情况下读取。招聘网站只会从其为搜索引擎发布的职位页面读取,并遵守 robots.txt 要求的访问频率。请按这些职位板块的设计用途使用它们:以人工的速度查找职位并申请。它不是用于猛烈请求网站或整批转载他人职位板块内容的工具。
项目来源
openroles 是从 last-job-search 提取出来的雇主职位板块读取器。last-job-search 是由 Claude 端到端运行的求职工具,提取后任何人都可以使用。
贡献
欢迎贡献;最有用的贡献是为另一个系统添加读取器,或通过检查将某个系统从“不支持”列表中移除。CONTRIBUTING.md 介绍了设置方法、保持库精简且尊重访问规则的几项规定,以及添加系统的检查清单。请在 Issues 中提交错误报告和功能请求。
pip install -e . pytest ruff
pytest && ruff check . # offline: every answer comes from tests/fixtures/
Licence
MIT. See LICENSE.
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。