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

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

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

来源层级1:公共ATS端点

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

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域模式可以发现成千上万的公司标识符。这个发现抓取本身就是一个抓取任务;通过一个数据中心代理池路由,因为索引宽松,速度比IP声誉更重要。

来源层级2:职位SERP垂直

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

import httpx

def fetch_jobs_serp(query: str, location: str) -> list[dict]:
    r = httpx.get(
        "https://api.quantumproxies.io/serp",
        params={"engine": "google_jobs", "q": query,
                "location": location, "api_key": "QP_API_KEY"},
        timeout=30,
    )
    jobs = r.json().get("jobs", [])
    return [{
        "source": "jobs_serp",
        "external_id": j.get("job_id"),
        "title": j.get("title"),
        "company": j.get("company_name"),
        "location": j.get("location"),
        "apply_url": (j.get("apply_options") or [{}])[0].get("link"),
    } for j in jobs]

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

在目标城市定期运行相同的查询,你就有了单一ATS源无法提供的地理覆盖。因为SERP结果因地点而异,地理定位每个查询——我们关于2026年SERP抓取工作原理的文章涵盖了地理和分页机制。

从SERP API中提取职位垂直

管道图显示从ATS API、职位SERP和直接板获取的职位数据,然后进行标准化、去重和刷新
三个来源层级供给一个架构;去重和新鲜度窗口将原始列表转化为值得信赖的聚合器。

通过规范键去重

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

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天是一个合理的默认值,快速变化的市场则更短。跟踪每个来源的最后看到时间戳和每日计数,以便在来源无声中断时发现;一个监控自身流量的聚合器可以在平台数据量骤降时立即发出警报。我们关于将抓取工具作为生产软件运行的笔记涵盖了调度、漂移检测和警报。

需要遵守的法律界限

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

统计面板显示七个ATS平台、30天新鲜度窗口和KASPR因LinkedIn抓取被罚24万欧元
结构化ATS源加上规范去重在成本、覆盖和法律风险上胜过强行HTML抓取。

代理的作用

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

常见问题解答

什么是职位聚合器?

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

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

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

抓取职位板合法吗?

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

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

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

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

用Scraper API为你的聚合器提供动力