【免费下载链接】freehire
freehire — the open-source search engine for job seekers
freehire 是一个开源的招聘搜索引擎,它的核心能力在于把分散在全球各地的招聘信息汇成一张可搜索、可筛选的统一版图:截至最新目录快照,freehire 从225 个数据源中抓取了330 万+ 条在招职位,覆盖29.4 万家公司,其中92 个 ATS 平台(Applicant Tracking System,招聘管理系统)贡献了绝对大头。这篇文章带你用 10 分钟看懂这张数据版图的分层结构、抓取调度机制,以及那些对外公布的数字是如何保持准确的。
上图是项目对外展示的数据概览卡片(卡片数字为较早一次快照,本文以最新目录快照为准)。
职位数据分三层:ATS 平台、聚合站、企业官网
freehire 把所有数据源归为三种类型,这个分类不是人工维护的配置,而是直接由适配器的 Go 类型推导出来的,定义逻辑位于 internal/ingest/sources/ 包中。每层的规模如下(完整清单见 docs/sources.md):
| 层级 | 数量 | 覆盖公司 | 在招职位 |
|---|---|---|---|
| 🏢 ATS 平台 | 92 个 | 157,399 | 2,644,796 |
| 🌐 聚合站与招聘板 | 100 个 | 172,884 | 629,571 |
| 🏷️ 企业招聘官网 | 30 个 | 59 | 26,222 |
注意:同一家公司可能同时出现在多个来源下,所以各层公司数相加会大于 29.4 万这个去重后的总数。
ATS 平台:真正的数据主力
92 个 ATS 平台贡献了近 264 万条在招职位,占全库的 80%。几个头部玩家的数据量级感受一下:
- workday:4,202 家公司,74.8 万条职位(单一平台最大)
- trudvsem:7.0 万家公司的长尾覆盖王,26.7 万条职位
- smartrecruiters / oracle / greenhouse:各自 15 万~22 万条职位
长尾同样惊人:从 phenom 到只有 1 家公司的 epam,最末位的数据源也稳定产出。每个 ATS 平台对应 internal/ingest/sources/ 下一个独立适配器文件(如workday.go、greenhouse.go),全部实现同一个Source接口,所以新平台接入的工作量是有界的。
聚合站与企业官网:补充覆盖与一手数据
聚合站(Adzuna、hh、whatjobs 系列、Telegram 频道等 100 个来源)价值在于覆盖面广,其中 whatjobs 一个系列就分拆出 40 多个国家/地区站点。而 30 个企业招聘官网来源则是"一手数据":Amazon 单家 7,680 条、Apple 4,268 条、Google 3,452 条职位直接来自公司自己的招聘页面,绕过了任何中间商。
抓取调度:一次一平台的单程工模式
freehire 的抓取不是"一个常驻爬虫进程跑所有平台",而是run-once-and-exit worker(运行一次即退出)模型,调度入口在 cmd/ingest/main.go:
- 每次运行只处理一个平台——比如这一次跑
greenhouse、下一次跑lever,各平台按自己的节奏独立调度,慢平台永远不阻塞快平台。 - 数据源目录是纯数据——
boards表每行对应"某公司在某 ATS 平台上的某个看板"(provider + board + region),建表语句见 migrations/0123_boards.sql,调度周期配置见 migrations/0132_ingest_schedule.sql。 - 统一流水线——每次运行把抓到的原始页面交给 internal/ingest/pipeline/pipeline.go 中的
pipeline.Runner:抓取 → 归一化成单一 schema → 去重 → 写入数据库。 - 失败有冷却——某个看板持续失败会进入记录的冷却期,而不是每次运行都反复重试。
这种设计的妙处:worker 之间不共享内存,所有协调都落在 Postgres 上,因此系统里看不到常驻的任务队列守护进程,排障和扩容都直白得多。架构全貌可参考 docs/architecture.md。
从原始职位到搜索结果:一条职位的完整路径
抓到一条职位只是开始,它还要经历三步才能出现在搜索结果里:
| 阶段 | 机制 | 关键实现 |
|---|---|---|
| 去重 | 以(source, external_id)为唯一键 upsert,重复抓取零成本 | jobs表唯一约束 |
| 事务出站队列 | 新职位写入同一事务中投递到enrichment_outbox/search_outbox | 事务性 outbox |
| 批量索引进 Meilisearch | cmd/search-drain/ 把整批文档作为单个任务推入索引 | 批量合并,避免打满磁盘 IO |
上图是 freehire 的职位搜索主页,左侧可按地区、工作形式、技术栈、资历等维度筛选——这些筛选维度的数据正来自上文各层数据源的归一化字段。
一个值得注意的工程细节:Meilisearch 每次推送都会对全量约 270 万条文档重建倒排索引(实测 90~180 秒),所以早期"每个看板进程各自直推"的设计曾打满宿主磁盘 IO,现在统一由出站队列把大量小推送合并成少量大批次——这也是为什么"未变化的重复抓取"只刷新时间戳、不触发重索引。
数字从哪来:快照保证全站口径一致
文章开头的 330 万、29.4 万这些数字,并不是各个页面各自实时估算的——那会导致"关于页"和"开放 API"说的数对不上。freehire 的做法是:
catalogstats(internal/ingest/catalogstats/catalogstats.go):定时 worker 精确统计一次全库快照(在招职位数、公司数、平台数),随后全站所有引用目录规模的页面都读同一个Snapshot,口径天然一致。sourcestats(internal/ingest/sourcestats/sourcestats.go):把每天的"按来源分组"测量折叠成source_stats快照行,驱动每个数据源的公开统计页。
统计还刻意区分了"测量失败"和"测量结果为零"两种状态,避免公开页面把一个仍在运行的来源错误地显示为 0 条职位——这种细节决定了用户看到的数据是否可信。
版图还在长大:新增一家公司只需一行数据
freehire 的数据版图并非静态,它靠社区持续生长,而扩容成本被刻意压到最低:
- 加一家已在支持平台上的公司=
boards目录里加一行(company + provider + board)。可以直接在网站的 "contribute a board" 表单提交,或用go run ./cmd/add-board命令(实现见 cmd/add-board/main.go),新行首次爬取成功后自动转正。 - 加一个全新的 ATS 平台= 在 internal/ingest/sources/ 写一个适配器 + 注册表加一行,工作量是有界的,因为所有适配器说同一种"语言"(
Source接口)。 - 下线一个看板= 把该行状态翻成
retired,永不删除——爬虫只爬活行,误操作随时可逆。
快速上手:按这个顺序读项目资料
| 想了解的内容 | 去哪里看 |
|---|---|
| 项目全貌与数据分层 | README.md |
| 每个数据源的详细计数 | docs/sources.md |
| 系统架构与数据流 | docs/architecture.md |
| 数据源适配器源码 | internal/ingest/sources/ |
| 抓取流水线 | internal/ingest/pipeline/ |
| 看板目录(数据源清单) | internal/ingest/boardcatalog/ |
上图演示了用 "Remote + Global/Worldwide" 组合筛选远程职位的效果——这类跨来源的联合筛选,正是 225 个数据源归一化的直接收益。
一句话总结:freehire 的数据版图 = 三层来源结构(ATS 平台为主力)+ 一次一平台的单程工抓取 + 事务化出站队列索引 + 统一快照统计。理解了这四件事,你就理解了这 330 万条职位是如何被"搭"起来的,以及它为什么还能持续长大。
【免费下载链接】freehire
freehire — the open-source search engine for job seekers
相关推荐
从 2 万到 3.3 万星:RustFS 的中文社区口碑是怎么滚起来的
从 2 万到 3.3 万星:RustFS 的中文社区口碑是怎么滚起来的 一个开源项目在 GitHub 上从 2 万星滚到 3.3 万星,意味着它在数月内让上万名
对象存储存储后端MMPR 300 万偏好数据是怎么来的?InternVL2_5-8B-MPO 多模态偏好数据集构造管线拆解
MMPR 300 万偏好数据是怎么来的?InternVL2_5 8B MPO 多模态偏好数据集构造管线拆解 InternVL2_5 8B MPO 是一个经过 混
视频编辑"全球第一"?H3 发布数据是怎么算出来的
视频编辑"全球第一"?H3 发布数据是怎么算出来的 2026 年 7 月底,MiniMax 正式发布 H3 并宣布开放权重,随之而来的是一连串极具冲击力的宣传口
大模型多模态媒体生成视频音频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考