如何使用 Python 爬取 Indeed

了解如何使用Python从Indeed获取职位列表,处理反爬虫难题,并高效简化数据收集流程。
6 min read
如何使用Python爬取Indeed——分步指南

在本文中,您将了解到:

  • 什么是 Indeed 爬虫,以及它的工作原理
  • 可以从 Indeed 自动提取哪些数据
  • 如何使用 Python 构建一个 Indeed 爬虫脚本
  • 何时以及为何可能需要更高级的解决方案

让我们开始吧!

什么是 Indeed 爬虫?

Indeed 爬虫可以自动从 Indeed 网站上提取职位列表和相关数据。它通过模拟人工操作来浏览职位搜索页面,随后识别特定元素,如职位标题、公司、地点和职位描述。最后,爬虫程序会从中提取数据并将数据导出以供分析。

可以在 Indeed 上找到哪些数据

Indeed 是一个与工作相关的数据宝库,对于市场分析、招聘或研究目的而言都极具价值。以下列出了在其中可以爬取的主要数据点:

  • 职位标题:招聘广告中的具体职能或岗位。
  • 公司名称:雇主的详细信息,包括公司简介。
  • 地点:职位所在的城市、州或国家。
  • 职位描述:关于角色、职责以及要求的详细信息。
  • 薪资范围:招聘广告中列出的薪资范围(若有)。
  • 职位类型:全职、兼职、合同、实习等。
  • 发布时间:职位发布的时间。
  • 标签和属性:例如 “紧急招聘” 或 “远程” 等关键字。
  • 评分和评价:对雇主的评分以及员工反馈。
  • 申请选项:如 “Easy Apply(轻松申请)” 等标识。

如果您感兴趣的核心是职位信息,请参阅我们的如何爬取招聘信息指南。

如何爬取 Indeed:分步指南

在本教程部分,您将学习如何创建一个 Indeed 爬虫。我们将引导您构建一个 Python 脚本,用于爬取 Indeed 上 “data scientist” (数据科学家)职位的页面:

Indeed 上的数据科学家职位页面

跟随以下步骤,学习如何爬取 Indeed!

第 1 步:项目设置

在开始之前,请确保您的计算机已安装 Python 3。如果尚未安装,请下载并进行安装。

现在,在终端中执行以下命令,为您的项目创建一个目录:

mkdir indeed_scraper

indeed_scraper 将用于存放您的 Python Indeed 爬虫。

在终端中进入该目录,然后在其中初始化虚拟环境

cd indeed_scraper
python -m venv env

接下来,在您喜欢的 Python IDE 中加载该项目文件夹。Visual Studio Code(装有 Python 插件)PyCharm Community Edition都是不错的选择。

在项目目录中创建一个名为 scraper.py 的文件,此时项目目录结构应如下所示:

在项目目录中新建 scraper.py 文件

scraper.py 很快就会包含爬取逻辑。

现在在 IDE 的终端中激活虚拟环境。对于 Linux 或 macOS,使用以下命令:

./env/bin/activate

在 Windows 上,可以使用:

env/Scripts/activate

很好!现在您已经为 Indeed 爬虫准备好了 Python 环境。

第 2 步:选择合适的爬取库

接下来要确认 Indeed 页面是动态加载还是静态加载。为此,您可以在浏览器的无痕模式下打开Indeed 目标页面并进行操作。您会发现页面上的大部分数据都是动态加载的:

示例:页面大部分数据是动态加载的

由此可见,您需要使用类似 Selenium 这样的浏览器自动化工具来有效地爬取 Indeed。想要了解更多细节,请参见我们的 Selenium 爬虫教程

Selenium 能够编程控制浏览器模拟用户交互,并获取由 JavaScript 渲染的内容。是时候安装并体验它了!

第 3 步:安装并配置 Selenium

在已激活的虚拟环境中执行以下命令来安装 Selenium:

pip install -U selenium

scraper.py 中导入 Selenium,并设置一个 WebDriver 对象:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service

# Set up a controllable Chrome instance
driver = webdriver.Chrome(service=Service())

以上代码初始化了一个可控的 Chrome 浏览器实例。

注意:Indeed 部署了反爬机制,阻止无头(headless)浏览器访问页面。因此,如果加上 --headless 参数,脚本通常会失败。想要进一步防范检测,请参考 Playwright Stealth

在脚本的最后一行,不要忘记关闭浏览器:

driver.quit()

准备就绪!您已经可以开始爬取 Indeed 了。

第 4 步:访问目标页面

使用 Selenium 的 get() 方法,让浏览器转到目标页面:

driver.get("https://www.indeed.com/jobs?q=data+scientist&l=New+York%2C+NY&from=searchOnHP%2Cwhatautocomplete&vjk=45d1ba700870fbef")

此时,scraper.py 文件的内容应类似如下:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service

# Set up a controllable Chrome instance
driver = webdriver.Chrome(service=Service())

# Open the target page in the browser
driver.get("https://www.indeed.com/jobs?q=data+scientist&l=New+York%2C+NY&from=searchOnHP%2Cwhatautocomplete&vjk=45d1ba700870fbef")

# Scraping logc...

# Close the web driver
driver.quit()

在最后一行添加一个调试断点并运行脚本进行调试,您应看到类似效果:

运行脚本调试后看到的界面

注意:“Chrome 正在受自动测试软件控制” 这条提示说明 Selenium 正在操控浏览器。

干得好!

第 5 步:选择职位元素

Indeed 的搜索页面上会展示多个职位。由于我们的目标是爬取所有职位,因此先初始化一个数组来存储爬取到的数据:

jobs = []

接下来,查看页面上职位元素对应的 HTML 结构,以确定如何选择这些元素:

职位元素的 HTML 结构

可以看到,每个职位元素都是 #mosaic-provider-jobcards 容器内的一个 slider_item 节点。

通常,您可能会使用 CSS 类选择器来抓取页面元素。但这里的类名疑似是随机生成的(可能在构建时生成),它们变化较大。为了更稳妥,建议使用不易频繁改变的 iddata-testid 属性。

使用 Selenium 选择这些职位元素:

jobs_container_element = driver.find_element(By.CSS_SELECTOR, "#mosaic-provider-jobcards")
job_elements = jobs_container_element.find_elements(By.CSS_SELECTOR, "[data-testid=\"slider_item\"]")

find_elements() 方法会根据指定的选择策略找出页面上所有匹配的元素。在这里,我们使用的是 CSS 选择器

别忘了导入 By

from selenium.webdriver.common.by import By

现在,遍历上面获取到的所有职位元素,为后续爬取做准备:

for job_element in job_elements:
    # scrape data from each job opening

很好!现在您已为 Indeed 爬取逻辑打下了基础。

第 6 步:爬取职位的主要信息

先查看职位卡片上方的主要信息:

职位标题元素的 HTML

您会看到可以爬取:

  • <h2> 标签中的职位标题
  • <h2><a> 标签的职位链接 URL
  • [data-testid="company-name"] 节点中的公司名称
  • [data-testid="text-location"] 节点中的公司地点

将以上信息综合,写出如下的爬取逻辑:

title_element = job_element.find_element(By.CSS_SELECTOR, "h2.jobTitle")
title = title_element.text

url_element = title_element.find_element(By.CSS_SELECTOR, "a")
url = url_element.get_attribute("href")

company_element =job_element.find_element(By.CSS_SELECTOR, "[data-testid=\"company-name\"]")
company = company_element.text

location_element = job_element.find_element(By.CSS_SELECTOR, "[data-testid=\"text-location\"]")
location = location_element.text

find_element() 会选中与所给选择器匹配的第一个元素。然后,通过 text 属性获取其文本内容;要获取节点某个属性值,可以使用 get_attribute() 方法。

目前为止,Indeed 爬虫的主要信息获取逻辑已经就绪,但还有更多有用的数据可以爬取。

第 7 步:爬取职位详情

再来关注职位卡片中的详情部分:

查看职位详情部分的 HTML

在这里,可以爬取的信息包括:

  • 职位标签,它们位于 .jobMetaDataGroup <div> 内一个或多个 [data-testid="attribute_snippet_testid"] 元素中
  • 是否支持在 Indeed 上 “Easily apply(轻松申请)”
  • 职位描述分点信息,位于 [role="presentation"] <div> 的一个或多个 ul li

让我们先来获取这些标签。可以通过以下方式爬取所有标签:

tags = []
tags_container_element = job_element.find_element(By.CSS_SELECTOR, ".jobMetaDataGroup")
tag_elements = tags_container_element.find_elements(By.CSS_SELECTOR, "[data-testid=\"attribute_snippet_testid\"]")
for tag_element in tag_elements:
    tag = tag_element.text
    tags.append(tag)

首先需要为每个职位初始化一个数组来存储标签信息,因为单个职位卡片可能包含多个标签。选中这些标签后,依次提取文本并存入标签数组。

至于 “Easily apply” 选项,相对要更麻烦一点,因为只有部分职位支持此选项,其对应的 HTML 元素并不一定存在。也就是说,只在支持 “Easily apply” 的卡片上才会出现相关节点。

如果尝试选择一个并不存在的元素,Selenium 会抛出一个 NoSuchElementException。因此,可以通过捕获该异常来判断是否支持 “Easily apply”:

try:
job_element.find_element(By.CSS_SELECTOR, "[data-testid=\"indeedApply\"]")
easily_apply = True
except NoSuchElementException:
easily_apply = False

如果 [data-testid="indeedApply"] 节点不存在,Selenium 会抛出 NoSuchElementException,一旦被捕获,就将 easily_apply 置为 False

针对职位描述分点信息,也可与标签爬取类似:

description = []
description_container_element = job_element.find_element(By.CSS_SELECTOR, "[role=\"presentation\"]")
description_elements = description_container_element.find_elements(By.CSS_SELECTOR, "ul li")
for description_element in description_elements:
    description_item_text = description_element.text
    # Ignore empty description strings
    if (description_item_text != ""):
        description.append(description_item_text)

就这样,Indeed 爬虫即将完成。

第 8 步:收集爬取数据

汇总从各个职位获取的数据,生成一个 job 字典:

job = {
    "title": title,
    "url": url,
    "company": company,
    "location": location,
    "tags": tags,
    "easily_apply": easily_apply,
    "description": description
}

随后,将其添加到 jobs 数组中:

jobs.append(job)

for 循环结束后,products(或者这里的 jobs) 中应包含类似这样的结果:

[{'title': 'Data Scientist', 'url': 'https://www.indeed.com/rc/clk?jk=efc7b7f4a8be2882&bb=NM368jsOPyYGAfEtQk2NNae8tSeBHdJ8Y9tImVa1Q9GAipGe0zzddcUozFEL0Na_pYCR4W6ljgljsBxWTUrluVuL8Gom7x7UZlgMzs0spo3NRgisrZ7meuaPfaEcjWoe&xkcb=SoD767M34WNyEaSTwx0FbzkdCdPP&fccid=8678bc4e64c24580&vjs=3', 'company': 'GQR', 'location': 'New York, NY', 'tags': [], 'easily_apply': False, 'description': ['Stay current with industry trends and emerging technologies to ensure competitive edge.', 'Apply statistical and machine learning techniques to improve investment…']},
# omitted for brevity...
{'title': 'Data Scientist, Financial Crimes - USDS', 'url': 'https://www.indeed.com/rc/clk?jk=aaa16dfd1cc6ef01&bb=NM368jsOPyYGAfEtQk2NNdxizAZQnHpzRrlr6WgbV1RtxmXz4vto1qiiqGiIj9CJFQQCV6cW59nE4hGw1yeNdokPfu8Fgl3EALBx5zdWjPm4COEu78DCFh4KTUMIFWkh&xkcb=SoAT67M34WNyEaSTwx0pbzkdCdPP&fccid=caed318a9335aac0&vjs=3', 'company': 'TikTok', 'location': 'Hybrid work in New York, NY', 'tags': [], 'easily_apply': False, 'description': ['As a Financial Crime Data Scientist, you will play a crucial role in leveraging machine learning, analytics and visualization techniques to enhance our…']}]

完成得很好!现在只需要把这些数据转换成一种更优的输出格式即可。

第 9 步:将爬取结果导出为 CSV

为了让爬取得到的数据更加易于查看和分享,建议以可读性强的格式导出。例如,将其写入 CSV 文件。可以使用如下代码:

csv_file = "scraped_jobs.csv"
csv_headers = ["title", "url", "company", "location", "tags", "easily_apply", "description"]

with open(csv_file, mode="w", newline="", encoding="utf-8") as file:
    writer = csv.DictWriter(file, fieldnames=csv_headers)
    writer.writeheader()
    for job in jobs:
        writer.writerow({
            "title": job["title"],
            "url": job["url"],
            "company": job["company"],
            "location": job["location"],
            "tags": ";".join(job["tags"]),
            "easily_apply": "Yes" if job["easily_apply"] else "No",
            "description": ";".join(job["description"])
        })

open() 函数用于创建 CSV 输出文件,然后通过 csv.DictWriter 将数据写入。由于 tagsdescription 字段是数组,需要借助 join() 来将它们用 ; 连接成单个字符串。

别忘了从 Python 标准库导入 csv

import csv

这样就大功告成了!Indeed 爬虫已经完成。

第 10 步:查看全部代码

最终,您的 scraper.py 文件应大致如下:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.common import NoSuchElementException
import csv

# Set up a controllable Chrome instance
driver = webdriver.Chrome(service=Service())

# Open the target page in the browser
driver.get("https://www.indeed.com/jobs?q=data+scientist&l=New+York%2C+NY&from=searchOnDesktopSerp")

# A data structure where to store the scraped job openings
jobs = []

# Select the job opening elements on the page
jobs_container_element = driver.find_element(By.CSS_SELECTOR, "#mosaic-provider-jobcards")
job_elements = jobs_container_element.find_elements(By.CSS_SELECTOR, "[data-testid=\"slider_item\"]")

# Scrape each job opening on the page
for job_element in job_elements:
    title_element = job_element.find_element(By.CSS_SELECTOR, "h2.jobTitle")
    title = title_element.text

    url_element = title_element.find_element(By.CSS_SELECTOR, "a")
    url = url_element.get_attribute("href")

    company_element =job_element.find_element(By.CSS_SELECTOR, "[data-testid=\"company-name\"]")
    company = company_element.text

    location_element = job_element.find_element(By.CSS_SELECTOR, "[data-testid=\"text-location\"]")
    location = location_element.text

    tags = []
    tags_container_element = job_element.find_element(By.CSS_SELECTOR, ".jobMetaDataGroup")
    tag_elements = tags_container_element.find_elements(By.CSS_SELECTOR, "[data-testid=\"attribute_snippet_testid\"]")
    for tag_element in tag_elements:
        tag = tag_element.text
        tags.append(tag)

    # Check whether the "Easy Apply" element is on the page
    try:
        job_element.find_element(By.CSS_SELECTOR, "[data-testid=\"indeedApply\"]")
        easily_apply = True
    except NoSuchElementException:
        easily_apply = False

    description = []
    description_container_element = job_element.find_element(By.CSS_SELECTOR, "[role=\"presentation\"]")
    description_elements = description_container_element.find_elements(By.CSS_SELECTOR, "ul li")
    for description_element in description_elements:
        description_item_text = description_element.text
        # Ignore empty description strings
        if (description_item_text != ""):
            description.append(description_item_text)

    # Store the scraped data
    job = {
        "title": title,
        "url": url,
        "company": company,
        "location": location,
        "tags": tags,
        "easily_apply": easily_apply,
        "description": description
    }
    jobs.append(job)

# Export the scraped data to an output CSV file
csv_file = "jobs.csv"
csv_headers = ["title", "url", "company", "location", "tags", "easily_apply", "description"]

with open(csv_file, mode="w", newline="", encoding="utf-8") as file:
    writer = csv.DictWriter(file, fieldnames=csv_headers)
    writer.writeheader()
    for job in jobs:
        writer.writerow({
            "title": job["title"],
            "url": job["url"],
            "company": job["company"],
            "location": job["location"],
            "tags": ";".join(job["tags"]),
            "easily_apply": "Yes" if job["easily_apply"] else "No",
            "description": ";".join(job["description"])
        })

# Close the web driver
driver.quit()

不到 100 行代码,您就构建了一个 Indeed 爬虫!

使用以下命令运行该爬虫:

python3 script.py

或者在 Windows 下:

python script.py

此时,项目文件夹中会生成一个 jobs.csv 文件。打开查看后,可看到:

包含爬取结果的 jobs.csv 文件

完美呈现!大功告成。

轻松获取 Indeed 数据

Indeed 很清楚其数据价值,并采取了强力措施来保护这些数据。因此,当您使用类似 Selenium 这样的浏览器自动化工具访问其页面时,很可能会遇到 CAPTCHA:

Indeed 上的 Cloudflare CAPTCHA 验证

首先,您可以按照我们的Python 绕过 CAPTCHA 教程进行操作。但即使如此,网站仍可能通过其他反爬手段对您的请求进行封锁。想了解更多可参加我们关于反爬技术的线上研讨。

这些障碍说明了没有合适工具时爬取 Indeed 可能非常繁琐且低效。此外,无头(headless)浏览器无法使用也会使您的脚本更慢、更耗资源。

解决方案?Bright Data 的 Indeed Scraper API,让您可通过简单的 API 调用无缝获取 Indeed 上的数据,无需担心 CAPTCHA、阻断或其他麻烦!

结论

通过这篇分步教程,您已经了解到 Indeed 爬虫是什么,可以提取哪些数据,以及如何在 Python 中快速实现一个 Indeed 爬虫。只需大约 100 行代码,就能自动收集 Indeed 上的职位数据。

但是,爬取 Indeed 仍然面临挑战:平台监管严格且会使用 CAPTCHA 等方式来进行反爬检测。这些措施难以绕过,而且会拖慢您的爬取速度,使效率降低。使用我们的 Indeed Scraper API,您可以一键解决所有这些难题。

如果您并不想亲自爬取,但仍然需要职位数据,不妨看看我们现成的 Indeed 数据集

立即注册一个免费的 Bright Data 账户,试用我们的爬虫 API 或探索我们的数据集吧。