# 如何构建职位聚合器：来源、去重和新鲜度

一个好的职位聚合器涉及三个工程问题：从哪里获取职位列表，如何去重，以及如何保持新鲜度。解决这些问题后，抓取就变得简单了——以下是蓝图。

职位聚合器是一个职位搜索引擎：它从多个来源提取列表并在一个地方展示。难点不在于HTTP请求，而在于选择正确的来源，去重出现在五个网站上的同一职位，以及保持一切新鲜，以免展示上个月已填补的职位。做好这三点，你就有了一个有竞争力的产品；做不好，你就会有一个充满重复和过时信息的源，损害信任。此指南是工程蓝图：来源组合、规范去重、新鲜度、职位SERP垂直，以及需要遵守的法律界限。这是信息性指导，而非法律建议。

## 来源层级1：公共ATS端点

互联网上最干净的职位数据不在职位板上，而是在由申请追踪系统支持的公司招聘页面上，其中大多数提供结构化JSON。Greenhouse、Lever、Ashby、BambooHR、iCIMS、Paylocity和Workday都提供可直接请求的列表，无需HTML解析。一个开源聚合器可以从这七个来源并行提取数据，并根据每个平台的速率限制调整工作者数量——大约50个并发用于Workday，30个用于Greenhouse/Lever/iCIMS，10个用于BambooHR，5个用于限制最严格的。这一层提供来自雇主的规范职位、地点、薪资范围和申请URL，是任何严肃聚合器的基础。

```python
import httpx

# Greenhouse exposes a public board JSON endpoint per company slug
def fetch_greenhouse(slug: str) -> list[dict]:
    url = f"https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true"
    r = httpx.get(url, timeout=20)
    r.raise_for_status()
    jobs = r.json().get("jobs", [])
    return [{
        "source": "greenhouse",
        "external_id": str(j["id"]),
        "title": j["title"],
        "company": slug,
        "location": (j.get("location") or {}).get("name"),
        "apply_url": j["absolute_url"],
        "updated_at": j.get("updated_at"),
    } for j in jobs]

rows = fetch_greenhouse("examplecompany")
print(len(rows), "roles")
```

要找到要轮询的公司，可以从网络索引大规模收集ATS标识符，而不是手动操作——扫描Common Crawl的URL档案以查找ATS域模式可以发现成千上万的公司标识符。这个发现抓取本身就是一个抓取任务；通过一个[数据中心代理](https://quantumproxies.io/datacenter-proxies)池路由，因为索引宽松，速度比IP声誉更重要。

## 来源层级2：职位SERP垂直

ATS源漏掉了只发布到聚合器网站、地区板或Google自己的职位体验的内容。覆盖这一长尾的高效方式是在职位SERP垂直——一个结构化查询，返回Google为某个职位和地点展示的职位，已经标准化。我们的[SERP API](https://quantumproxies.io/serp-api)在新闻、图片和购物旁边提供职位垂直，因此你可以通过关键词和地理位置以JSON格式提取职位列表，并将其整合到同一管道中：

```python
import httpx

def fetch_jobs_serp(query: str, location: str) -> list[dict]:
    r = httpx.post(
        "https://api.quantumproxies.io/v1/scraper/serp",
        headers={"Authorization": "Bearer QP_API_KEY"},
        json={"query": query, "search_type": "jobs", "location": location},
        timeout=120,
    )
    jobs = r.json()["payload"].get("jobs_results", [])
    return [{
        "source": "jobs_serp",
        "title": j.get("title"),
        "company": j.get("company"),
        "location": j.get("location"),
        "apply_url": j.get("link"),
    } for j in jobs]

serp_rows = fetch_jobs_serp("react developer", "Austin, TX")
```

在目标城市定期运行相同的查询，你就有了单一ATS源无法提供的地理覆盖。因为SERP结果因地点而异，地理定位每个查询——我们关于[2026年SERP抓取工作原理](https://quantumproxies.io/blog/how-serp-scraping-works-in-2026)的文章涵盖了地理和分页机制。

[从SERP API中提取职位垂直](https://quantumproxies.io/serp-api)

![五步职位聚合流水线：从 ATS API、职位 SERP 和直接招聘网站拉取职位，统一到同一 schema，按规范键去重，定期重新抓取并清理过期职位。卡片给出规则：对职位名称、公司、地点和 ID 做哈希，每小时或每天重新抓取，30 天后清理。](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335191/blog/generated/qp-b9-059-diag1.png)
*三个来源层级供给一个架构；去重和新鲜度窗口将原始列表转化为值得信赖的聚合器。*

## 通过规范键去重

同一个职位经常出现在公司网站、两个聚合器和一个Google职位卡上。四次展示它是看起来出问题的最快方式。标准修复是规范键：标准化并哈希职位名称、公司、地点的组合，以及——如果可用——来源的外部职位ID。哈希之前先小写、去除标点并合并空白，以便“Sr. Software Engineer”和“senior software engineer”合并在一起。

```python
import re, hashlib

def canonical_key(job: dict) -> str:
    def norm(s):
        return re.sub(r"[^a-z0-9]+", " ", (s or "").lower()).strip()
    basis = "|".join([
        norm(job.get("title")),
        norm(job.get("company")),
        norm(job.get("location")),
        (job.get("external_id") or ""),
    ])
    return hashlib.sha1(basis.encode()).hexdigest()

def dedupe(rows: list[dict]) -> list[dict]:
    seen, out = set(), []
    for job in rows:
        k = canonical_key(job)
        if k not in seen:
            seen.add(k)
            out.append(job)
    return out
```

## 新鲜度是一个特性，而不是事后考虑

职位板的可信度在于其新鲜度。两个机制保持其诚实：按计划重新获取每个来源（高流量ATS源每小时，长尾每日），并删除在滚动窗口中未重新看到的任何内容——30天是一个合理的默认值，快速变化的市场则更短。跟踪每个来源的最后看到时间戳和每日计数，以便在来源无声中断时发现；一个监控自身流量的聚合器可以在平台数据量骤降时立即发出警报。我们关于[将抓取工具作为生产软件运行](https://quantumproxies.io/blog/schedule-monitor-web-scrapers)的笔记涵盖了调度、漂移检测和警报。

## 需要遵守的法律界限

职位数据是抓取法律变得真实的地方，因为职位发布可能包含个人数据（招聘者姓名、联系方式）和详细描述是有版权的。法国的数据保护机构因未经适当同意抓取LinkedIn联系数据而对KASPR公司罚款24万欧元；GDPR罚款可达2000万欧元或全球营业额的4%，美国案件中的版权损害赔偿已达每项作品15万美元。更安全的姿态是结构性的：优先选择授权的ATS源和职位SERP，而不是强行登录有墙的板，存储事实字段（职位、公司、地点）而不是重新发布完整的版权描述，并删除不需要的个人数据。我们关于[2026年网络抓取的合法性](https://quantumproxies.io/blog/is-web-scraping-legal-2026)和[LinkedIn合规](https://quantumproxies.io/blog/linkedin-public-data-scraping-compliance)的概述更深入。

![根据各 ATS 平台速率限制调整的并发 worker 数：Workday 约 50 个，Greenhouse、Lever 和 iCIMS 各 30 个，BambooHR 10 个，限制最严的平台 5 个。卡片列出了背后的数字：七个 ATS 平台、30 天的新鲜度窗口，以及 KASPR 因抓取 LinkedIn 联系人数据被罚 €240,000。](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335191/blog/generated/qp-b9-059-diag2.png)
*结构化ATS源加上规范去重在成本、覆盖和法律风险上胜过强行HTML抓取。*

## 代理的作用

ATS层很少需要住宅IP，但发现抓取、SERP垂直和任何直接板抓取需要——Google和更大的板按IP限速并根据地理位置变化结果。通过[旋转住宅代理](https://quantumproxies.io/rotating-proxies)进行路由，每次请求旋转和地理定位，以便每个城市查询来自匹配的位置。保持层级分开：宽松索引使用廉价数据中心，敏感目标使用住宅。

## 常见问题解答

### 什么是职位聚合器？

它是一个职位发布的搜索引擎，从多个来源收集列表——公司招聘页面、ATS平台、其他板和职位SERP——将它们标准化为一个架构，去除重复，并在一个可搜索的地方展示给求职者。工程工作在于来源、去重和新鲜度，而不仅仅是抓取。

### 如何从多个来源去重职位列表？

通过标准化和哈希职位名称、公司、地点和来源的外部职位ID来构建规范键。首先小写、去除标点并合并空白，以便变体拼写合并在一起。保留第一次出现并删除匹配项。这可以捕捉到同一职位出现在雇主网站、聚合器和Google职位上的情况。

### 抓取职位板合法吗？

这取决于来源、数据和你的司法管辖区。公共事实列表字段的风险低于个人数据或完整的版权描述，而绕过登录墙会增加CFAA风格的风险。授权的ATS源和职位SERP是更干净的路径。公司因抓取个人数据而被重罚——商业用途请获得法律建议。

### 聚合的职位数据应该有多新鲜？

每小时重新获取高流量来源如ATS源，长尾每日，然后删除在滚动窗口中未重新看到的任何列表——30天是一个常见的默认值。跟踪每个职位的最后看到时间戳并监控每个来源的量，以便在用户看到过时职位之前发现破损的来源。

将聚合作为三个问题——来源、去重、新鲜度——处理，抓取成为支持细节。以干净的ATS源和职位SERP垂直为主，基于规范键去重，积极修剪，并保持敏感抓取在旋转住宅IP上。这是人们信任的产品与死链接墓地的区别。

[用Scraper API为你的聚合器提供动力](https://quantumproxies.io/scraper-api)
