mcpField
O
openroles

openroles

本地服务
github.com/aranculleton商业与营销14 天前更新

关于 openroles

openroles 是用于从雇主招聘页面查找职位的 Python 库、命令行工具和 MCP 服务。它统一读取 15 种 ATS 系统及符合条件的招聘网站职位信息。

主要功能

  1. 01读取 15 种 ATS 系统的职位
  2. 02按名称、网址或招聘页识别职位板
  3. 03按关键词、地点、日期或远程状态筛选
  4. 04返回统一字段和申请链接
  5. 05提供 Python 库与命令行工具

适用场景

  1. 01查找指定雇主的当前职位
  2. 02按职位词、地点或发布时间搜索
  3. 03匹配不同网站上的相同职位

MCP 相关

stdio MCP 服务提供 `find_job_board` 和 `list_open_roles` 工具,供 MCP 客户端和 Agent 识别招聘板并获取职位。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"aranculleton-openroles": {
"command": "uvx",
"args": [
"openroles"
]
}
}
}
启动命令uvx openroles

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypiopenroles0.4.0

工具

2

收录的工具列表 · 更新于 3 天前

参数类型必填说明
targetstring是公司名称、招聘页面 URL 或职位板 URL

详细介绍

openroles

**所有开放职位,均直接来自雇主自己的招聘系统。**涵盖 15 种申请人跟踪系统(Greenhouse、Lever、Ashby、Workday、Workable、Teamtailor、Personio、 Recruitee、Rippling、JazzHR、Gem、Polymer、Manatal、Homerun 和 ClearCompany),以及任何向搜索引擎公开职位的招聘网站,统一为一种格式。读取雇主自家招聘页面使用的 Feed 和 API:无需 API 密钥、无需账户、无需爬取服务,且只使用 Python 标准库。

CI PyPI Python License: MIT

$ 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招聘信息中列出的所有地点,分别以拼接字符串和列表形式提供
workplaceremote、hybrid、onsite;若招聘网站未说明,则为空字符串 ""
remoteTrue、False;若招聘网站未说明,则为 None(混合办公时为 None)
postedYYYY-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、Pinpointrobots.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.

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。