重要提醒:法律与道德风险 (最重要!)
在开始之前,你必须了解并遵守以下规则:
- 遵守
robots.txt协议:每个网站都有一个robots.txt文件(https://www.zhaopin.com/robots.txt),它规定了哪些页面允许爬虫访问,哪些不允许。请务必先查看并遵守该协议,不遵守可能导致你的 IP 被封禁。 - 尊重服务条款:仔细阅读目标招聘网站的用户协议,大多数网站都明确禁止未经授权的自动化数据抓取,违反协议可能会带来法律风险。
- 控制爬取频率:不要用高频请求轰炸服务器,这会给对方网站带来巨大压力,是一种不友好的行为,极易导致 IP 被封,设置合理的请求间隔(2-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(轻量级)、MySQL或MongoDB(NoSQL,适合非结构化数据)。
爬取招聘网站的核心步骤
我们以一个虚构的、简单的静态页面为例,讲解基本流程,我们再讨论如何应对现代的动态页面。
场景:抓取某个职位列表页上的所有职位名称、公司、薪资和地点。
步骤 1:分析目标网页
-
打开浏览器,进入目标职位列表页(智联招聘、前程无忧的某个搜索结果页)。
-
按
F12打开“开发者工具”,切换到 “Elements” (元素) 标签页。 -
定位数据元素:将鼠标悬停在你想抓取的数据上(比如一个职位名称),观察右侧的 HTML 代码,找到包裹这些数据的最外层标签,通常会包含一个唯一的
class或id。- 你可能会发现所有职位信息都在一个
<div class="job-item">这样的容器里。 - 每个职位名称在
<span class="job-name">标签里。 - 公司名称在
<span class="company-name">标签里。 - 薪资在
<span class="salary">标签里。
- 你可能会发现所有职位信息都在一个
-
检查分页:查看“下一页”按钮的链接规律,通常是
?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 工作流程:
- 启动浏览器:
Selenium会打开一个真实的浏览器(如 Chrome)或一个无头浏览器(Headless,后台运行)。 - 打开网页:让浏览器访问目标 URL。
- 等待数据加载:使用显式等待 (
WebDriverWait),等待某个元素(如职位列表)加载完成,这是最关键的一步,可以避免抓取到不完整的数据。 - 获取页面源码:当数据加载完成后,获取当前浏览器页面的完整 HTML 源码。
- 解析数据:将获取到的 HTML 源码传给
BeautifulSoup,然后和之前一样进行解析。
Selenium 示例代码 (抓取前程无忧)
准备工作:
- 安装
selenium:pip install selenium - 下载对应浏览器的 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("浏览器已关闭。")
高级技巧与最佳实践
- 使用代理 IP 池:当你的爬虫被大量封禁时,可以使用代理 IP 来切换不同的出口 IP 地址。
- 处理验证码:如果遇到验证码,可以手动输入,或者集成第三方打码平台(如 2Captcha、Anti-Captcha)进行自动识别。
- 处理 AJAX 请求:很多网站的数据是通过 AJAX 请求异步加载的,你可以使用浏览器的开发者工具(
Network->Fetch/XHR)来找到这些 API 请求,直接模拟这些 API 请求,通常比用 Selenium 渲染整个页面快得多! - 构建分布式爬虫:对于大规模数据采集,可以使用 Scrapy-Redis 框架,将多个爬虫节点组成一个分布式系统,共同工作,提高效率。
- 日志记录:使用 Python 的
logging模块记录爬虫的运行状态、成功和失败的请求,便于排查问题。 - 数据清洗:爬取到的数据往往很“脏”,包含多余的空格、换行、特殊符号等,使用正则表达式或字符串方法进行清洗,使其格式统一。
爬取招聘网站是一个集网络请求、HTML 解析、动态内容处理、数据存储于一体的综合性项目。
- 入门:从
requests+BeautifulSoup开始,理解基本流程。 - 进阶:学习
Selenium处理动态页面,这是现代爬虫的必备技能。 - 实践:尝试爬取一个你感兴趣的、结构相对简单的网站,然后逐步挑战更复杂的。
- 牢记:合法合规、尊重网站、控制频率是爬虫生存的第一原则。
