菜鸟科技网

爬取招聘网

重要提醒:法律与道德风险 (最重要!)

在开始之前,你必须了解并遵守以下规则:

  1. 遵守 robots.txt 协议:每个网站都有一个 robots.txt 文件(https://www.zhaopin.com/robots.txt),它规定了哪些页面允许爬虫访问,哪些不允许。请务必先查看并遵守该协议,不遵守可能导致你的 IP 被封禁。
  2. 尊重服务条款:仔细阅读目标招聘网站的用户协议,大多数网站都明确禁止未经授权的自动化数据抓取,违反协议可能会带来法律风险。
  3. 控制爬取频率:不要用高频请求轰炸服务器,这会给对方网站带来巨大压力,是一种不友好的行为,极易导致 IP 被封,设置合理的请求间隔(2-5 秒)。
  4. 明确数据用途:确保你的爬取行为仅用于个人学习、研究或数据分析,而非商业用途或恶意行为。
  5. 不要泄露个人信息:爬取到的数据中可能包含求职者的姓名、电话、邮箱等敏感信息,请妥善保管,切勿泄露或用于非法目的。

技术选型

对于初学者和中小型项目,推荐使用 Python,因为它拥有非常成熟和强大的爬虫生态。

  • 核心库:

    • requests: 用于发送 HTTP 请求,获取网页的 HTML 内容,简单易用。
    • BeautifulSoup: 用于解析 HTML 文档,方便地提取你想要的数据,它能很好地处理不规范的 HTML。
    • lxml: 一个更快的 HTML/XML 解析器,BeautifulSoup 可以用它作为后端。
  • 进阶库 (处理 JavaScript 渲染页面):

    • Selenium: 自动化测试工具,可以模拟浏览器行为,用来抓取由 JavaScript 动态加载的内容(现代招聘网站的标配)。
    • Playwright / Pyppeteer: 类似 Selenium,但通常性能更好,更稳定。
  • 数据存储:

    • csv: 将数据保存为 CSV 文件,适合结构化数据,可以用 Excel 直接打开。
    • json: 保存为 JSON 文件,适合半结构化或嵌套数据。
    • pandas: 一个强大的数据分析库,可以轻松地将数据读取到 DataFrame 中,并进行清洗和分析,然后导出为各种格式。
    • 数据库: 如果数据量很大,需要持久化存储和查询,可以使用 SQLite (轻量级)、MySQLMongoDB (NoSQL,适合非结构化数据)。

爬取招聘网站的核心步骤

我们以一个虚构的、简单的静态页面为例,讲解基本流程,我们再讨论如何应对现代的动态页面。

场景:抓取某个职位列表页上的所有职位名称、公司、薪资和地点。

步骤 1:分析目标网页

  1. 打开浏览器,进入目标职位列表页(智联招聘、前程无忧的某个搜索结果页)。

  2. F12 打开“开发者工具”,切换到 “Elements” (元素) 标签页。

  3. 定位数据元素:将鼠标悬停在你想抓取的数据上(比如一个职位名称),观察右侧的 HTML 代码,找到包裹这些数据的最外层标签,通常会包含一个唯一的 classid

    • 你可能会发现所有职位信息都在一个 <div class="job-item"> 这样的容器里。
    • 每个职位名称在 <span class="job-name"> 标签里。
    • 公司名称在 <span class="company-name"> 标签里。
    • 薪资在 <span class="salary"> 标签里。
  4. 检查分页:查看“下一页”按钮的链接规律,通常是 ?pn=2&page=3 这样的形式。

步骤 2:编写 Python 代码 (静态页面示例)

假设我们找到了这些规律,代码如下:

import requests
from bs4 import BeautifulSoup
import csv
import time
# 目标 URL (这里用一个示例URL,实际使用时需要替换)
url = "https://example.com/jobs?q=python&page=1"
# 设置请求头,模拟浏览器访问
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
try:
    # 1. 发送请求,获取页面内容
    response = requests.get(url, headers=headers, timeout=10)
    response.raise_for_status()  # 如果请求失败 (状态码不是 200),则抛出异常
    # 2. 使用 BeautifulSoup 解析 HTML
    soup = BeautifulSoup(response.text, 'lxml')
    # 3. 找到所有职位的容器 (根据你实际分析的结果)
    job_list = soup.find_all('div', class_='job-item')
    # 准备存储数据的列表
    all_jobs_data = []
    # 4. 遍历每个职位容器,提取具体信息
    for job in job_list:
        # 使用 .get_text(strip=True) 来获取文本并去除首尾空白
        # 使用 try-except 来处理可能为空的情况,避免程序崩溃
        job_name = job.find('span', class_='job-name').get_text(strip=True) if job.find('span', class_='job-name') else 'N/A'
        company = job.find('span', class_='company-name').get_text(strip=True) if job.find('span', class_='company-name') else 'N/A'
        salary = job.find('span', class_='salary').get_text(strip=True) if job.find('span', class_='salary') else 'N/A'
        location = job.find('span', class_='location').get_text(strip=True) if job.find('span', class_='location') else 'N/A'
        # 将提取的数据存入字典
        job_data = {
            '职位名称': job_name,
            '公司名称': company,
            '薪资': salary,
            '工作地点': location
        }
        all_jobs_data.append(job_data)
        print(f"已抓取: {job_name} - {company}")
    # 5. 将数据保存到 CSV 文件
    if all_jobs_data:
        # 确定CSV的表头 (字典的键)
        headers = list(all_jobs_data[0].keys())
        with open('jobs.csv', 'w', newline='', encoding='utf-8-sig') as f:
            writer = csv.DictWriter(f, fieldnames=headers)
            writer.writeheader()  # 写入表头
            writer.writerows(all_jobs_data) # 写入数据行
        print(f"\n数据已成功保存到 jobs.csv 文件中,共 {len(all_jobs_data)} 条。")
    # 礼貌地等待一段时间,避免被封
    time.sleep(3)
except requests.exceptions.RequestException as e:
    print(f"请求失败: {e}")

应对现代动态网站 (使用 Selenium)

现在几乎所有招聘网站都使用 JavaScript 动态加载数据,直接用 requests 获取到的 HTML 可能是空的,或者只包含一个加载框,这时就需要 Selenium

Selenium 工作流程:

  1. 启动浏览器Selenium 会打开一个真实的浏览器(如 Chrome)或一个无头浏览器(Headless,后台运行)。
  2. 打开网页:让浏览器访问目标 URL。
  3. 等待数据加载:使用显式等待 (WebDriverWait),等待某个元素(如职位列表)加载完成,这是最关键的一步,可以避免抓取到不完整的数据。
  4. 获取页面源码:当数据加载完成后,获取当前浏览器页面的完整 HTML 源码。
  5. 解析数据:将获取到的 HTML 源码传给 BeautifulSoup,然后和之前一样进行解析。

Selenium 示例代码 (抓取前程无忧)

准备工作:

  1. 安装 selenium: pip install selenium
  2. 下载对应浏览器的 WebDriver (ChromeDriver),并将其路径添加到系统环境变量,或者直接在代码中指定路径。
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
import csv
# 1. 配置 Selenium WebDriver
# 确保你的电脑上已安装与 ChromeDriver 版本匹配的 Chrome 浏览器
# chromedriver 不在环境变量中,需要指定路径
# driver_path = '/path/to/your/chromedriver' 
# driver = webdriver.Chrome(executable_path=driver_path)
driver = webdriver.Chrome() # 如果已配置好环境变量
# 2. 目标 URL (前程无忧搜索 "python" 开发的结果页)
url = "https://search.51job.com/list/000000,000000,0000,00,9,99,python,2,1.html"
try:
    # 3. 打开网页
    driver.get(url)
    # 4. 显式等待:等待职位列表加载完成
    # 这里我们等待一个 class 为 'el' 的元素出现,这个元素通常代表一个职位条目
    # WebDriverWait(driver, 10).until(
    #     EC.presence_of_element_located((By.CSS_SELECTOR, ".el"))
    # )
    # 等待更明确的元素,如职位链接
    WebDriverWait(driver, 15).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, ".dw_table .el a.t1"))
    )
    print("页面加载完成,数据已出现。")
    # 5. 获取渲染后的页面源码
    html_source = driver.page_source
    # 6. 使用 BeautifulSoup 解析
    soup = BeautifulSoup(html_source, 'lxml')
    # 7. 提取数据 (需要根据前程无忧的实际HTML结构调整选择器)
    # 检查发现,每个职位在 <div class="el"> 中
    job_list = soup.find_all('div', class_='el')
    all_jobs_data = []
    for job in job_list:
        # 职位名称和链接在 <span class="t1"> 下的 <a> 标签里
        job_link_tag = job.find('span', class_='t1').find('a')
        job_name = job_link_tag.get_text(strip=True)
        job_url = job_link_tag['href'] if 'href' in job_link_tag.attrs else 'N/A'
        # 公司名称在 <span class="t2"> 里
        company = job.find('span', class_='t2').get_text(strip=True) if job.find('span', class_='t2') else 'N/A'
        # 薪资在 <span class="t4"> 里
        salary = job.find('span', class_='t4').get_text(strip=True) if job.find('span', class_='t4') else 'N/A'
        # 地点在 <span class="t3"> 里
        location = job.find('span', class_='t3').get_text(strip=True) if job.find('span', class_='t3') else 'N/A'
        job_data = {
            '职位名称': job_name,
            '公司名称': company,
            '薪资': salary,
            '工作地点': location,
            '职位链接': job_url
        }
        all_jobs_data.append(job_data)
        print(f"已抓取: {job_name} - {company}")
    # 8. 保存数据
    if all_jobs_data:
        headers = list(all_jobs_data[0].keys())
        with open('jobs_51.csv', 'w', newline='', encoding='utf-8-sig') as f:
            writer = csv.DictWriter(f, fieldnames=headers)
            writer.writeheader()
            writer.writerows(all_jobs_data)
        print(f"\n数据已成功保存到 jobs_51.csv 文件中,共 {len(all_jobs_data)} 条。")
    # 礼貌等待
    time.sleep(5)
except Exception as e:
    print(f"发生错误: {e}")
finally:
    # 9. 关闭浏览器
    driver.quit()
    print("浏览器已关闭。")

高级技巧与最佳实践

  1. 使用代理 IP 池:当你的爬虫被大量封禁时,可以使用代理 IP 来切换不同的出口 IP 地址。
  2. 处理验证码:如果遇到验证码,可以手动输入,或者集成第三方打码平台(如 2Captcha、Anti-Captcha)进行自动识别。
  3. 处理 AJAX 请求:很多网站的数据是通过 AJAX 请求异步加载的,你可以使用浏览器的开发者工具(Network -> Fetch/XHR)来找到这些 API 请求,直接模拟这些 API 请求,通常比用 Selenium 渲染整个页面快得多!
  4. 构建分布式爬虫:对于大规模数据采集,可以使用 Scrapy-Redis 框架,将多个爬虫节点组成一个分布式系统,共同工作,提高效率。
  5. 日志记录:使用 Python 的 logging 模块记录爬虫的运行状态、成功和失败的请求,便于排查问题。
  6. 数据清洗:爬取到的数据往往很“脏”,包含多余的空格、换行、特殊符号等,使用正则表达式或字符串方法进行清洗,使其格式统一。

爬取招聘网站是一个集网络请求、HTML 解析、动态内容处理、数据存储于一体的综合性项目。

  • 入门:从 requests + BeautifulSoup 开始,理解基本流程。
  • 进阶:学习 Selenium 处理动态页面,这是现代爬虫的必备技能。
  • 实践:尝试爬取一个你感兴趣的、结构相对简单的网站,然后逐步挑战更复杂的。
  • 牢记合法合规、尊重网站、控制频率是爬虫生存的第一原则。
分享:
扫描分享到社交APP
上一篇
下一篇