如何抓取Indeed职位发布:结构、模块和SERP路线
Indeed是网络上最大的招聘网站,也是实时劳动力市场信号的来源。它还使用Cloudflare,类名每周轮换。以下是如何持久地提取数据——以及何时完全跳过该网站。
Indeed每月约有5.42亿次访问,使其列表成为劳动力市场最清晰的实时信号之一——按角色的招聘需求、薪资范围、哪些公司在招聘。它也是一个受Cloudflare保护的React网站,其可见的CSS类名经常轮换,足以在几周内破坏一个简单的抓取器。本指南展示了如何持久地抓取Indeed职位发布,如何分页和去重,Cloudflare现实,以及让你完全跳过争斗的职位SERP垂直。
阅读搜索页面,锚定在稳定的属性上
Indeed数据的单位是搜索结果页面:查询加上位置为你提供一个职位卡片页面。陷阱在于选择Indeed重新生成的哈希类名(如eu4oa1w0)。相反,锚定在变化频率较低的data-testid属性上:
import requests
from bs4 import BeautifulSoup
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
}
proxies = {"http": "http://USER:PASS@gate.quantumproxies.io:8000",
"https": "http://USER:PASS@gate.quantumproxies.io:8000"}
url = "https://www.indeed.com/jobs?q=python&l=New+York%2C+NY"
html = requests.get(url, headers=headers, proxies=proxies, timeout=30).text
soup = BeautifulSoup(html, "html.parser")
jobs = []
for card in soup.select("div.job_seen_beacon"):
title = card.find("a", class_="jcs-JobTitle")
company = card.find("span", attrs={"data-testid": "company-name"})
location = card.find("div", attrs={"data-testid": "text-location"})
jobs.append({
"title": title.get_text(strip=True) if title else None,
"company": company.get_text(strip=True) if company else None,
"location": location.get_text(strip=True) if location else None,
"jk": title["href"].split("jk=")[-1][:16] if title else None,
})
print(len(jobs), "jobs on page 1")
每个卡片都有一个职位键(jk)——一个你用来跨页面和运行去重的稳定ID。存储在jk上,而不是URL,因为同一发布会以不同的跟踪参数出现。
分页和国家网站
Indeed页面使用一个start参数,每次增加10。并且它不是一个网站——每个国家是一个子域:美国是www.indeed.com,英国是uk.indeed.com,等等。将你的代理出口与查询的国家匹配,以确保请求在地理上连贯:
def crawl(query, location, country="www", pages=5):
base = f"https://{country}.indeed.com/jobs"
for start in range(0, pages * 10, 10):
params = {"q": query, "l": location, "start": start}
html = requests.get(base, params=params, headers=headers,
proxies=proxies, timeout=30).text
# parse as above, yield rows
yield html
# freshness filter: fromage accepts only 1, 3, 7 or 14 days
# .../jobs?q=python&l=NYC&fromage=7
一个值得了解的特点:Indeed的“发布于”过滤器(fromage)只接受1、3、7或14天——其他值会被忽略,因此围绕这些桶构建你的新鲜度逻辑。

Cloudflare现实
Indeed位于Cloudflare之后,因此使用数据中心IP进行简单请求通常会得到挑战而不是结果。解决方法是常见的反机器人堆栈:一致的浏览器User-Agent和头设置,以及——最大的杠杆——一个未被预先评分为自动化的住宅出口。住宅代理呈现为真实用户的连接,这就是Cloudflare继续提供页面的原因。当出现挑战时,匹配浏览器指纹与IP同样重要;我们关于2026年绕过Cloudflare挑战的指南涵盖了界限所在。
捷径:职位SERP垂直
如果你真正想要的是劳动力市场数据——而不是Indeed的HTML——那么有一个更简洁的路径。Google的职位垂直整合了来自Indeed和许多其他网站的发布,SERP API以解析的JSON返回该职位垂直:标题、公司、位置、发布日期和来源,无需维护CSS选择器,也无需与Cloudflare斗争。你以一些字段级别的控制换取一个跨多个网站的稳定结构化提要,对于聚合招聘分析来说,这通常是更好的选择。这也是基于SERP提要构建职位板聚合器而不是每个网站一个抓取器的原因。
数据的用途
除了职位搜索之外,这些数据还推动了实际分析:按角色和城市的薪资基准,特定技能随时间的需求跟踪,竞争对手招聘信号(竞争对手组建新团队是战略信号),以及丰富——将公司的开放职位与公司特征结合用于外部。将其与Glassdoor薪资数据结合,你会得到比单一来源更完整的薪酬图景。

常见问题解答
可以合法地抓取Indeed职位发布吗?
职位发布是公共数据,收集公开可见的列表在广泛上是可辩护的,但Indeed的条款限制自动访问,你应避免个人数据并尊重速率限制。许多团队通过使用聚合的职位SERP垂直而不是直接抓取Indeed来规避条款问题。这是一般信息,而非法律建议——在进行大型项目之前请查看当前条款。
为什么我的Indeed抓取器总是崩溃?
有两个原因。首先,Indeed轮换其哈希CSS类名,因此固定在它们上的选择器在几周内失效——相反,锚定在data-testid属性和职位键上。其次,一旦你的IP被标记,Cloudflare就会开始挑战;住宅出口和一致的浏览器指纹保持真实页面加载。修复这两点,抓取器就会稳定。
如何从多个国家抓取职位?
Indeed为每个国家提供自己的子域(www.indeed.com,uk.indeed.com,de.indeed.com,等等)。查询正确的子域并通过该国家的代理出口路由,以确保请求在地理上连贯——否则你可能会得到不匹配或重定向的结果。如果你不想管理子域,职位SERP垂直也接受国家参数。
Google职位SERP比抓取Indeed更好吗?
对于聚合劳动力市场分析,通常是的。职位垂直将来自Indeed和许多其他网站的发布作为结构化JSON返回,无需处理旋转选择器和Cloudflare挑战。你会失去一些Indeed特定的字段,但你会获得一个稳定的多源提要,并且维护工作大大减少。只有当你需要Indeed自己页面上的字段时,才直接抓取Indeed。
Indeed奖励持久的方法:锚定在稳定的属性上,通过职位键去重,按十分页,并使用住宅IP敲门以便Cloudflare为你服务。当你只需要劳动力市场信号而不是Indeed的确切HTML时,职位SERP垂直以JSON形式提供。选择与你实际需要的字段控制相匹配的路径。