本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目基于Python技术栈,实现对智联招聘网站的招聘信息自动化爬取,涵盖不同城市地区的近期职位数据。通过使用Requests、BeautifulSoup等爬虫技术,结合Selenium处理动态加载内容、应对反爬机制,并完成数据解析、存储及异常处理流程。项目旨在帮助用户高效获取招聘市场数据,适用于求职分析、人才研究和人力资源数据挖掘等场景。附带完整源码与实战操作文档,适合爬虫初学者和数据分析师提升实战能力。
利用python数据爬取技术,爬取智联招聘网站上,不同城市地区的近期招聘信息.zip

1. Python网络爬虫概述

网络爬虫(Web Crawler)是一种自动从互联网上抓取数据的程序,广泛应用于搜索引擎、数据分析、舆情监控、商业情报等领域。Python凭借其简洁易读的语法、丰富的第三方库(如Requests、BeautifulSoup、Scrapy、Selenium等)以及活跃的社区支持,成为网络爬虫开发的首选语言。

本章将围绕“爬取智联招聘网站上的职位信息”这一实战目标,系统介绍网络爬虫的核心流程:从发送HTTP请求、解析HTML内容、应对反爬机制到数据存储,帮助读者构建完整的爬虫知识体系。通过本章学习,读者将理解爬虫的工作原理,为后续章节中具体技术的深入实践打下坚实基础。

2. Requests库发送HTTP请求

在Python网络爬虫开发中, HTTP请求的发送 是整个流程的起点,也是决定数据获取效率和成功率的关键环节。Requests 是 Python 中最流行的 HTTP 客户端库之一,以其简洁易用、功能强大著称,广泛应用于爬虫、接口测试、自动化脚本等领域。

本章将围绕 Requests 库的核心功能展开,深入讲解如何使用其发送 GET 和 POST 请求、构造请求参数、处理 Cookies 和 Session、解析 JSON 响应、应对异常情况,以及模拟浏览器行为和配置代理 IP 等实用技巧。这些内容不仅为后续爬取智联招聘网站打下技术基础,也适用于广泛的网络数据采集场景。

2.1 HTTP协议基础

HTTP(HyperText Transfer Protocol)是互联网上最常用的通信协议之一,它定义了客户端与服务器之间的请求-响应交互方式。在爬虫开发中,理解 HTTP 协议的核心机制是构建稳定、高效的网络请求系统的基础。

2.1.1 请求方法与状态码

HTTP 协议定义了多种请求方法,最常见的有 GET POST ,其他还包括 PUT DELETE PATCH 等。

请求方法 描述
GET 获取服务器上的资源,请求参数通常附在 URL 后面
POST 向服务器提交数据,通常用于创建或更新资源
PUT 替换指定资源
DELETE 删除指定资源
PATCH 局部更新指定资源

每种请求都会返回一个 HTTP状态码 ,用于表示请求的处理结果:

状态码 含义
200 请求成功
301/302 重定向
400 请求有误
403 拒绝访问
404 资源不存在
500 服务器内部错误

了解这些状态码可以帮助我们更好地处理请求异常,提升爬虫的容错能力。

2.1.2 请求头与响应结构

HTTP 请求由请求行、请求头和请求体组成,响应则包括状态行、响应头和响应体。

graph TD
    A[请求行] --> B[请求方法 URL 协议版本]
    A --> C[请求头]
    A --> D[请求体]
    E[响应行] --> F[状态码 状态描述]
    E --> G[响应头]
    E --> H[响应体]

其中, 请求头 (Headers)是爬虫中非常重要的部分,常用于设置 User-Agent、Referer、Cookies 等信息。例如:

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36',
    'Referer': 'https://www.google.com/',
    'Accept-Language': 'zh-CN,zh;q=0.9',
}

通过设置合适的请求头,可以模拟浏览器行为,有效规避反爬机制。

2.2 Requests库核心功能

Requests 是 Python 的第三方库,提供了简洁的 API 来发送 HTTP 请求,并处理响应内容。它是爬虫开发的首选工具之一,支持 GET、POST、PUT、DELETE 等方法,并能处理 Cookies、Session、JSON、文件上传等复杂场景。

2.2.1 发送GET与POST请求

GET 请求 用于获取数据,参数通常附加在 URL 上:

import requests

url = 'https://api.example.com/data'
params = {'key': 'value'}
response = requests.get(url, params=params)
print(response.text)

POST 请求 用于提交数据,通常以表单或 JSON 格式发送:

data = {'username': 'admin', 'password': '123456'}
response = requests.post('https://api.example.com/login', data=data)
print(response.status_code)

代码逻辑分析:
- 第一行导入 requests 模块。
- 第三行定义请求参数 data
- 第四行使用 requests.post() 发送 POST 请求, data 参数将自动编码为 application/x-www-form-urlencoded 格式。
- 第五行打印响应状态码。

2.2.2 处理Cookies与Session

有些网站需要登录才能访问特定资源,此时需要使用 requests.Session() 来维持会话状态。

session = requests.Session()
login_data = {
    'username': 'user123',
    'password': 'pass123'
}
session.post('https://example.com/login', data=login_data)

# 后续请求自动携带登录状态
response = session.get('https://example.com/dashboard')
print(response.text)

代码逻辑分析:
- 创建一个 Session 对象,它会自动处理 Cookies。
- 使用 session.post() 登录网站,Cookies 被保存在 session 中。
- 后续的 session.get() 请求会自动携带登录信息,无需手动维护 Cookies。

2.3 请求参数构造与响应处理

构造请求参数和处理响应内容是网络爬虫中的关键操作。Requests 提供了丰富的参数构造方式和响应处理方法,支持 URL 参数、表单数据、JSON 数据等。

2.3.1 URL参数与表单数据提交

URL 参数用于 GET 请求,而表单数据通常用于 POST 请求:

# GET 请求带 URL 参数
params = {'page': 2, 'limit': 10}
response = requests.get('https://api.example.com/list', params=params)

# POST 请求带表单数据
data = {'name': 'John', 'age': 30}
response = requests.post('https://api.example.com/submit', data=data)

代码逻辑分析:
- params 用于构造 URL 查询字符串,例如 ?page=2&limit=10
- data 用于构造 POST 表单数据,通常为字典格式,自动编码为 form-urlencoded 格式。

2.3.2 JSON响应解析与异常处理

很多现代 Web API 返回的数据格式为 JSON,Requests 提供了 .json() 方法来直接解析响应内容:

try:
    response = requests.get('https://api.example.com/jsondata')
    response.raise_for_status()  # 如果响应码非 2xx,抛出异常
    data = response.json()
    print(data['result'])
except requests.exceptions.HTTPError as err:
    print(f"HTTP error occurred: {err}")
except requests.exceptions.RequestException as err:
    print(f"Request error occurred: {err}")

代码逻辑分析:
- 使用 response.raise_for_status() 可以自动检测 HTTP 错误(如 404、500)。
- response.json() 将响应内容转换为 Python 字典。
- 使用 try-except 结构捕获请求异常,提高程序健壮性。

2.4 模拟浏览器行为与代理配置

为了绕过反爬策略,模拟浏览器行为和使用代理 IP 是爬虫开发中常用的手段。通过设置请求头和使用代理服务器,可以显著提高请求的成功率。

2.4.1 请求头伪装技巧

很多网站会检查 User-Agent、Referer、Accept 等字段来判断是否为爬虫。我们可以通过构造合理的请求头来模拟浏览器行为:

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36',
    'Referer': 'https://www.google.com/',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive'
}

response = requests.get('https://example.com', headers=headers)

代码逻辑分析:
- User-Agent 字段模拟主流浏览器标识。
- Referer 字段模拟从搜索引擎跳转。
- Accept-Language 指定语言偏好。
- 设置 Accept-Encoding 支持压缩数据。
- Connection: keep-alive 保持连接复用,提升性能。

2.4.2 使用代理IP提升稳定性

使用代理 IP 可以避免 IP 被封,适用于高频率访问或大规模爬取场景:

proxies = {
    'http': 'http://10.10.1.10:3128',
    'https': 'http://10.10.1.10:1080',
}

response = requests.get('https://example.com', proxies=proxies)

代码逻辑分析:
- proxies 参数用于指定代理服务器地址和端口。
- 支持分别配置 HTTP 和 HTTPS 代理。
- 若代理需要认证,可添加 http://user:password@10.10.1.10:3128 格式。

此外,可以结合 IP 池技术实现自动轮换代理:

import random

proxy_pool = [
    'http://192.168.1.101:8080',
    'http://192.168.1.102:8080',
    'http://192.168.1.103:8080'
]

proxy = random.choice(proxy_pool)
response = requests.get('https://example.com', proxies={'http': proxy, 'https': proxy})

代码逻辑分析:
- 使用 random.choice() 从代理池中随机选择一个代理地址。
- 提高 IP 轮换频率,降低被封概率。

通过本章内容的学习,我们掌握了使用 Requests 库发送 HTTP 请求的核心技能,包括构造请求、处理 Cookies 与 Session、解析响应数据以及模拟浏览器行为和配置代理 IP 等实用技巧。这些知识为后续实战爬取智联招聘网站提供了坚实的技术基础。在下一章中,我们将进入 HTML 数据解析阶段,学习使用 BeautifulSoup 和 Selenium 等工具提取结构化数据。

3. HTML解析与动态网页处理

在爬虫开发中,HTML解析是数据提取的关键环节。对于静态页面,直接通过HTML解析工具即可完成数据抽取,但对于动态网页,尤其是依赖JavaScript渲染的内容,需要结合Selenium或Splash等技术进行处理。本章将系统讲解如何使用 BeautifulSoup 解析HTML内容,如何利用 Selenium 模拟浏览器行为获取动态数据,以及如何通过 Splash 中间件渲染JavaScript页面。最后,将结合 XPath CSS选择器 ,展示招聘信息字段的精准提取策略。

3.1 BeautifulSoup解析HTML数据

BeautifulSoup 是 Python 中一个用于解析 HTML 和 XML 文档的强大库,尤其适合处理不规范的网页内容。它将 HTML 文档转换为一棵树状结构(称为文档对象模型 DOM),便于开发者通过标签、属性等方式快速定位和提取数据。

3.1.1 基本语法与节点定位

使用 BeautifulSoup 解析 HTML 的基本流程如下:

  1. 导入 BeautifulSoup 类;
  2. 将 HTML 字符串或响应内容传入 BeautifulSoup 构造函数;
  3. 使用 find() find_all() 方法定位节点;
  4. 提取所需数据。
from bs4 import BeautifulSoup
import requests

# 发送GET请求获取网页内容
url = 'https://example.com/jobs'
response = requests.get(url)
html_content = response.text

# 使用BeautifulSoup解析HTML
soup = BeautifulSoup(html_content, 'html.parser')

# 查找第一个匹配的节点
title_tag = soup.find('h1')
print(title_tag.text)

# 查找所有class为'job-title'的div节点
job_titles = soup.find_all('div', class_='job-title')
for title in job_titles:
    print(title.text)
逻辑分析与参数说明:
  • BeautifulSoup(html_content, 'html.parser') :使用 Python 内置的 html.parser 解析器解析 HTML 内容;
  • find('h1') :查找第一个 <h1> 标签;
  • find_all('div', class_='job-title') :查找所有 class 为 job-title <div> 标签;
  • .text 属性:提取标签内部的文本内容。

提示:如果 HTML 结构复杂,推荐使用 lxml 解析器,其性能优于 html.parser

3.1.2 标签筛选与内容提取

BeautifulSoup 提供了多种筛选方式,包括通过标签名、属性值、正则表达式等进行匹配。

示例:提取职位名称和薪资信息
# 假设HTML结构如下:
# <div class="job">
#     <h2 class="title">Python工程师</h2>
#     <span class="salary">15k-20k</span>
# </div>

jobs = soup.find_all('div', class_='job')
for job in jobs:
    title = job.find('h2', class_='title').text
    salary = job.find('span', class_='salary').text
    print(f"职位:{title},薪资:{salary}")
参数 说明
job.find(...) 查找子节点
class_='job' 通过 class 属性筛选标签
.text 获取节点文本内容
mermaid 流程图:BeautifulSoup 解析流程
graph TD
A[获取HTML内容] --> B[创建BeautifulSoup对象]
B --> C{是否静态页面?}
C -->|是| D[使用find或find_all定位节点]
D --> E[提取文本或属性值]
C -->|否| F[使用Selenium或Splash获取动态内容]

3.2 Selenium处理动态网页技术

Selenium 是一个用于自动化浏览器操作的工具,常用于处理依赖 JavaScript 动态加载内容的网页。它通过模拟真实浏览器行为(如点击、滚动、输入等)来获取页面渲染后的完整 HTML。

3.2.1 WebDriver基础操作

Selenium 使用 WebDriver 与浏览器进行交互。以下是基本操作示例:

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

# 配置Chrome驱动路径
service = Service('/path/to/chromedriver')
driver = webdriver.Chrome(service=service)

# 打开目标网页
driver.get('https://zhaopin.com')

# 等待页面加载完成(推荐使用显式等待)
driver.implicitly_wait(10)

# 查找搜索框并输入内容
search_box = driver.find_element(By.NAME, 'keyword')
search_box.send_keys('Python工程师')

# 点击搜索按钮
search_button = driver.find_element(By.CLASS_NAME, 'search-btn')
search_button.click()

# 获取渲染后的HTML
rendered_html = driver.page_source
逻辑分析与参数说明:
  • webdriver.Chrome() :启动 Chrome 浏览器实例;
  • driver.get(url) :访问指定网页;
  • find_element(By.NAME, 'keyword') :通过 name 属性查找元素;
  • send_keys() :模拟键盘输入;
  • click() :模拟点击操作;
  • page_source :获取当前页面的 HTML 内容。

建议:使用 WebDriverWait 显式等待元素加载完成,提高脚本稳定性。

3.2.2 页面元素定位与交互模拟

Selenium 支持多种元素定位方式:

定位方式 示例
By.ID find_element(By.ID, "username")
By.NAME find_element(By.NAME, "password")
By.CLASS_NAME find_element(By.CLASS_NAME, "login-btn")
By.CSS_SELECTOR find_element(By.CSS_SELECTOR, ".container > .form-group")
By.XPATH find_element(By.XPATH, "//input[@type='text']")
示例:模拟点击分页按钮加载更多职位信息
# 查找下一页按钮并点击
next_page = driver.find_element(By.XPATH, '//a[@class="next"]')
next_page.click()

# 等待新内容加载
driver.implicitly_wait(5)

# 获取更新后的HTML内容
updated_html = driver.page_source

3.3 Splash中间件动态渲染处理

对于某些复杂的 JavaScript 渲染场景,Selenium 可能效率较低。 Splash 是一个轻量级的浏览器渲染服务,专为爬虫设计,支持异步渲染、截图、执行脚本等高级功能。

3.3.1 Splash环境搭建与API使用

  1. 安装 Docker(推荐方式);
  2. 拉取 Splash 镜像并启动容器:
docker run -p 8050:8050 scrapinghub/splash
  1. 使用 requests 调用 Splash API 渲染页面:
import requests

splash_url = 'http://localhost:8050/render.html'
params = {
    'url': 'https://zhaopin.com',
    'wait': 2  # 等待2秒确保JS执行完成
}

response = requests.get(splash_url, params=params)
rendered_html = response.text
参数说明:
  • url :要渲染的目标网页;
  • wait :等待时间(秒),确保 JavaScript 执行完成;
  • response.text :返回渲染后的 HTML 内容。

3.3.2 渲染JavaScript页面的实践技巧

对于需要执行脚本或处理 AJAX 请求的页面,可使用 Splash 的 Lua 脚本功能:

lua_script = """
function main(splash)
  splash:go("https://zhaopin.com")
  splash:wait(2)
  splash:runjs("document.querySelector('.load-more').click()")
  splash:wait(3)
  return splash:html()
end

params = {
    'lua_source': lua_script
}
response = requests.get(splash_url, params=params)
rendered_html = response.text
逻辑分析:
  • splash:go() :访问目标页面;
  • splash:wait() :等待页面加载;
  • splash:runjs() :执行 JavaScript 脚本;
  • splash:html() :返回最终 HTML 内容。

3.4 数据提取与字段匹配策略

在实际项目中,准确提取目标字段是关键。BeautifulSoup、Selenium 和 Splash 都支持使用 XPath CSS选择器 进行高效提取。

3.4.1 XPath与CSS选择器的高级应用

表达式类型 示例 说明
XPath //div[@class='job']/h2/text() 提取 class 为 job 的 div 下的 h2 标签文本
CSS选择器 .job h2 提取 class 为 job 的元素下的 h2 子元素
示例:使用 XPath 提取职位信息
from lxml import html

tree = html.fromstring(rendered_html)
titles = tree.xpath('//div[@class="job"]/h2/text()')
salaries = tree.xpath('//div[@class="job"]/span[@class="salary"]/text()')

for title, salary in zip(titles, salaries):
    print(f"职位:{title},薪资:{salary}")

3.4.2 招聘信息字段的精准定位与提取

在爬取智联招聘网站时,常见的字段包括:

字段 定位方式
职位名称 //h2[@class='job-title']
薪资范围 //span[@class='salary']
工作地点 //div[@class='job-location']
公司名称 //div[@class='company-name']
示例:提取多个字段并组织为结构化数据
jobs = []
job_elements = tree.xpath('//div[@class="job"]')

for element in job_elements:
    title = element.xpath('.//h2/text()')[0]
    salary = element.xpath('.//span[@class="salary"]/text()')[0]
    location = element.xpath('.//div[@class="job-location"]/text()')[0]
    company = element.xpath('.//div[@class="company-name"]/text()')[0]
    jobs.append({
        '职位名称': title,
        '薪资范围': salary,
        '工作地点': location,
        '公司名称': company
    })

# 打印提取结果
import pprint
pprint.pprint(jobs)
说明 技术点
多字段提取 XPath 相对路径
结构化输出 字典与列表
数据清洗 后续可结合 Pandas 进行清洗

至此,第三章完整展示了 HTML 解析与动态网页处理的核心技术,从静态页面解析到 JavaScript 动态渲染,再到字段提取策略,为后续数据清洗与存储奠定了坚实基础。

4. 反爬机制与合规性应对

本章聚焦网络爬虫在实际运行过程中面临的反爬挑战,深入剖析智联招聘网站的结构特征,识别其动态加载与反爬机制,并结合实际案例,介绍如何通过请求头伪装、代理IP配置、频率控制等手段应对反爬策略。同时,结合robots协议和法律合规性要求,探讨数据采集过程中的伦理与合规边界,为构建稳定、合规的爬虫系统提供理论与实践指导。

4.1 智联招聘网站结构分析

在构建爬虫之前,首先需要对目标网站——智联招聘的页面结构进行深入分析。了解其URL构造方式、数据加载机制以及是否存在动态渲染,是制定有效爬取策略的前提。

4.1.1 页面URL结构与数据接口识别

智联招聘的职位搜索页面通常采用如下URL结构:

https://sou.zhaopin.com/?jl={城市编码}&kw={职位名称}&p={页码}

其中:
- jl :城市编码,代表职位所在城市。
- kw :关键词,表示职位名称。
- p :页码,表示当前页数。

例如,搜索“北京”的“Python工程师”职位,URL为:

https://sou.zhaopin.com/?jl=639&kw=Python%E5%B7%A5%E7%A8%8B%E5%B8%88&p=1

我们可以通过浏览器开发者工具(F12)的Network面板观察页面加载过程中请求的API接口,发现部分职位数据通过Ajax异步加载。例如:

GET https://fe-api.zhaopin.com/c/i/sou

该接口接受如下参数:
- cityId :城市ID。
- keyword :搜索关键词。
- page :当前页码。
- pageSize :每页条数。

使用Requests调用该接口的示例代码如下:

import requests

url = "https://fe-api.zhaopin.com/c/i/sou"

params = {
    "cityId": 639,
    "keyword": "Python工程师",
    "page": 1,
    "pageSize": 60
}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Referer": "https://sou.zhaopin.com/"
}

response = requests.get(url, params=params, headers=headers)

if response.status_code == 200:
    data = response.json()
    print(data)
else:
    print("请求失败,状态码:", response.status_code)

逐行分析:
- 第1行导入requests库。
- 第3~6行定义请求URL和参数。
- 第8~12行构造请求头,模拟浏览器行为。
- 第14行发送GET请求。
- 第16行检查响应状态码是否为200。
- 第17行将响应内容转为JSON格式并打印。
- 第20行处理请求失败情况。

输出结构示例(简化):

{
  "data": {
    "results": [
      {
        "jobName": "Python开发工程师",
        "salary": "15-25K·14薪",
        "city": {"display": "北京"},
        "companyName": "某科技公司",
        "timeState": "今天",
        "url": "https://jobs.zhaopin.com/CC231867823.htm"
      },
      ...
    ]
  }
}

通过分析接口返回的JSON数据,我们可以直接提取职位信息,而无需解析HTML页面,极大提升了效率与稳定性。

4.1.2 动态加载内容的判断与处理

部分网页内容通过JavaScript动态加载,例如职位详情页或企业信息页。我们可以通过浏览器开发者工具的Network面板查看是否有XHR/Fetch请求加载数据。

以职位详情页为例:

https://jobs.zhaopin.com/CC231867823.htm

该页面部分内容通过异步请求加载,例如企业信息可能通过如下接口获取:

GET https://jobs.zhaopin.com/CompanyDetail/GetCompanyInfo?companyId=123456

此时,我们可以直接调用该接口获取企业信息,而无需使用Selenium等自动化工具进行渲染。

但如果目标页面存在大量依赖JavaScript渲染的DOM节点,如职位要求、福利信息等,则建议使用Selenium或Splash进行渲染处理。

使用Selenium获取动态内容的示例代码如下:

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

service = Service(executable_path='/path/to/chromedriver')
driver = webdriver.Chrome(service=service)

driver.get("https://jobs.zhaopin.com/CC231867823.htm")

# 等待某个元素加载完成
job_desc = driver.find_element(By.CLASS_NAME, "job-description")

print("职位描述:", job_desc.text)

driver.quit()

逐行分析:
- 第1~3行导入Selenium相关模块并配置Chrome驱动。
- 第5行初始化浏览器实例。
- 第7行访问目标职位详情页。
- 第9行定位“职位描述”元素。
- 第11行打印职位描述内容。
- 第13行关闭浏览器。

结论:
在处理智联招聘网站结构时,优先识别其Ajax接口,通过Requests直接获取结构化数据;对于动态加载内容,视情况选择是否使用Selenium或Splash进行渲染处理,以提高爬虫效率和稳定性。

4.2 反爬机制识别与应对策略

爬虫在访问网站时,常常会触发网站的反爬机制,例如验证码、IP封禁、请求频率限制等。本节将重点分析智联招聘常见的反爬策略,并提供对应的应对方案。

4.2.1 验证码识别与处理方案

验证码是网站最常见的一种反爬手段,用于区分真实用户与机器人。智联招聘在高频访问或疑似异常行为时,可能会弹出滑动验证码或图形验证码。

常见验证码类型及应对方式:
类型 特征 应对方式
滑块验证码 用户需拖动滑块完成拼图 使用第三方OCR服务(如打码平台)
图形验证码 随机字母+干扰线 使用OCR识别工具(如Tesseract)
极验验证码 带有行为轨迹分析 需要专门的破解库或人工干预
示例:使用打码平台识别滑块验证码

以打码平台“云打码”为例,其提供API接口用于上传验证码图片并获取识别结果。

import requests

# 云打码登录接口
login_url = "http://yundama.com/index.php?m=User&a=login"
login_data = {
    "username": "your_username",
    "password": "your_password"
}

session = requests.Session()
session.post(login_url, data=login_data)

# 上传验证码图片
upload_url = "http://yundama.com/index.php?m=Up&c=upload"
files = {"file": open("slider_captcha.jpg", "rb")}
data = {"type": "9101"}  # 滑块验证码类型
response = session.post(upload_url, data=data, files=files)

print(response.json())

逐行分析:
- 第1~4行导入requests库并设置登录参数。
- 第6行创建Session对象保持登录状态。
- 第7~8行执行登录操作。
- 第10~14行上传验证码图片并指定识别类型。
- 第15行输出识别结果。

注意: 使用第三方打码平台需遵守其使用协议,且需处理账号安全问题。

4.2.2 IP封禁与请求频率控制

IP封禁是网站识别高频访问行为后采取的限制手段。智联招聘可能在短时间内多次访问后对IP地址进行封禁。

应对策略:
  1. 请求频率控制 :使用 time.sleep() random.uniform() 设置随机延迟,避免连续请求。
  2. 代理IP轮换 :使用代理IP池,实现IP地址轮换,降低被封禁风险。
示例:使用代理IP发起请求
import requests
import time
import random

proxies_list = [
    {"http": "http://user:pass@192.168.1.10:8080"},
    {"http": "http://192.168.1.11:8080"},
    {"http": "http://192.168.1.12:8080"}
]

url = "https://fe-api.zhaopin.com/c/i/sou"
params = {
    "cityId": 639,
    "keyword": "Python工程师",
    "page": 1,
    "pageSize": 60
}

for i in range(5):  # 模拟5次请求
    proxy = random.choice(proxies_list)
    try:
        response = requests.get(url, params=params, proxies=proxy, timeout=5)
        if response.status_code == 200:
            print(f"第{i+1}次请求成功")
        else:
            print(f"第{i+1}次请求失败,状态码:{response.status_code}")
    except Exception as e:
        print(f"第{i+1}次请求出错:{e}")
    time.sleep(random.uniform(1, 3))  # 随机等待1~3秒

逐行分析:
- 第1~3行导入所需库并定义代理IP池。
- 第5~10行设置目标URL和请求参数。
- 第12~22行循环模拟5次请求,每次随机选择代理IP。
- 第24行设置随机等待时间,避免触发频率限制。

4.3 请求头伪装与代理IP配置

网站通常通过User-Agent、Referer等HTTP头信息识别爬虫行为。本节将介绍如何构造合理的请求头,并搭建代理IP池以提升爬虫稳定性。

4.3.1 User-Agent与Referer构造技巧

User-Agent用于标识客户端浏览器信息,Referer表示请求来源页面。伪造这两个字段可以有效降低被识别为爬虫的概率。

示例:构造多个User-Agent
import requests
import random

user_agents = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
]

headers = {
    "User-Agent": random.choice(user_agents),
    "Referer": "https://sou.zhaopin.com/"
}

response = requests.get("https://fe-api.zhaopin.com/c/i/sou", headers=headers)
print(response.status_code)

逐行分析:
- 第1~3行导入库并定义多个User-Agent。
- 第5~9行构造请求头,随机选择User-Agent。
- 第11行发送请求并打印状态码。

4.3.2 代理IP池的搭建与轮换策略

代理IP池可通过以下方式构建:

  • 付费代理服务 :如芝麻代理、快代理、讯代理等。
  • 自建代理服务器 :使用Nginx或Squid搭建私有代理。
  • 免费代理IP爬取 :通过公开代理网站抓取可用IP(稳定性较差)。
示例:使用免费代理IP网站爬取可用IP
import requests
from bs4 import BeautifulSoup

def fetch_proxies():
    url = "https://www.xicidaili.com/nn/"
    headers = {
        "User-Agent": "Mozilla/5.0"
    }
    response = requests.get(url, headers=headers)
    soup = BeautifulSoup(response.text, "html.parser")
    proxies = []
    for row in soup.find_all("tr")[1:]:  # 跳过表头
        cols = row.find_all("td")
        ip = cols[1].text
        port = cols[2].text
        protocol = cols[5].text.lower()
        proxies.append({protocol: f"{protocol}://{ip}:{port}"})
    return proxies

print(fetch_proxies())

逐行分析:
- 第1~2行导入requests和BeautifulSoup。
- 第4~14行定义 fetch_proxies 函数,从西刺代理网站爬取代理IP。
- 第16行调用函数并打印结果。

4.4 robots协议与法律合规性说明

在进行网络爬虫开发时,必须遵守目标网站的robots协议,并遵循相关法律法规,避免因数据采集行为引发法律纠纷。

4.4.1 网站robots.txt解析

robots.txt是网站根目录下的一个文本文件,用于告知搜索引擎哪些页面可以被抓取,哪些页面禁止爬取。

示例:解析智联招聘robots.txt

访问:

https://www.zhaopin.com/robots.txt

部分内容如下:

User-agent: *
Disallow: /job/
Disallow: /company/

这表明所有爬虫都不允许访问 /job/ /company/ 路径下的页面。

Python解析robots.txt示例:
import requests
from urllib.robotparser import RobotFileParser

rp = RobotFileParser()
rp.set_url("https://www.zhaopin.com/robots.txt")
rp.read()

print(rp.can_fetch("*", "https://www.zhaopin.com/job/123456.html"))

逐行分析:
- 第1~2行导入模块。
- 第4~5行设置robots.txt地址并读取。
- 第7行检查是否允许爬取指定URL。

4.4.2 合规性采集与伦理考量

在实际采集过程中,应遵循以下原则:

  • 尊重robots协议 :不爬取被禁止的路径。
  • 控制请求频率 :避免给网站服务器造成过大负载。
  • 避免敏感数据采集 :如用户隐私、企业机密等。
  • 遵守《网络安全法》《数据安全法》等相关法规

建议:
- 在爬虫项目中加入 robots.txt 解析模块,自动判断是否允许访问。
- 设置合理的请求间隔,避免高频访问。
- 对采集到的数据进行脱敏处理,确保不泄露个人隐私。

本章深入分析了智联招聘网站的结构特征与反爬机制,提出了验证码识别、IP轮换、请求头伪装等应对策略,并强调了robots协议与法律合规性的重要性,为构建稳定、高效、合法的网络爬虫系统提供了理论与实践支撑。

5. 数据清洗、存储与性能优化

在完成网络爬虫的数据采集与解析之后,接下来的步骤是数据清洗、结构化处理以及数据的持久化存储。这一阶段不仅决定了数据质量的高低,还直接影响后续分析和应用的准确性。此外,为了提升爬虫的整体效率,性能优化也是不可或缺的一环。本章将围绕数据清洗与结构化处理、使用Pandas进行数据整理、数据存储至CSV/JSON/MySQL,以及多线程与异步IO爬取优化四个主要方面展开详细讲解。

5.1 数据清洗与结构化处理

5.1.1 数据去重与格式标准化

在爬取数据过程中,由于网页结构、分页机制或重复链接等问题,可能会导致采集到重复的记录。因此,去重是数据清洗的第一步。

数据去重方法:

  • 使用 pandas.DataFrame.drop_duplicates() 进行数据去重:
import pandas as pd

df = pd.read_csv('zhilian_jobs.csv')
df_clean = df.drop_duplicates()
df_clean.to_csv('zhilian_jobs_cleaned.csv', index=False)

参数说明:

  • subset : 指定去重的列,默认为全部列;
  • keep : 保留重复项的方式,可选 'first' (保留第一个)、 'last' (保留最后一个)或 False (全部删除);
  • inplace : 是否在原数据上修改。

逻辑分析:

该段代码读取CSV文件后,调用 drop_duplicates() 函数删除重复行,并将结果保存为新文件。适用于数据量较小的场景。

格式标准化处理:

以薪资字段为例,原始数据可能包含多种格式如“8K-12K”、“15-20万/年”等,需统一为标准格式如“万元/年”或“元/月”进行存储。

def standardize_salary(salary):
    if '万' in salary and '年' in salary:
        return float(salary.split('万')[0]) * 10000 / 12
    elif '千' in salary and '月' in salary:
        return float(salary.split('千')[0]) * 1000
    elif '万' in salary and '月' in salary:
        return float(salary.split('万')[0]) * 10000
    else:
        return None

逻辑分析:

该函数根据薪资字符串中的单位进行判断,返回统一格式的月均薪资(单位:元)。适用于后续分析时薪资字段的标准化。

5.1.2 缺失值与异常值处理

缺失值处理:

在爬取过程中,某些字段可能未抓取到有效内容,形成缺失值(NaN)。处理方式包括删除缺失记录、填充默认值或通过模型预测。

# 填充缺失值
df.fillna({'experience': '不限', 'education': '不限'}, inplace=True)

# 删除缺失值较多的列
df.dropna(thresh=len(df) * 0.7, axis=1, inplace=True)

参数说明:

  • thresh : 保留列所需的非空值数量;
  • axis : 0表示行,1表示列。

异常值处理:

异常值通常表现为极端值或不符合逻辑的数值。例如薪资字段中出现“99999999”这类明显异常的数据。

# 使用IQR法检测异常值
Q1 = df['salary'].quantile(0.25)
Q3 = df['salary'].quantile(0.75)
IQR = Q3 - Q1

# 筛选出异常值
outliers = df[(df['salary'] < (Q1 - 1.5 * IQR)) | (df['salary'] > (Q3 + 1.5 * IQR))]

逻辑分析:

该段代码使用四分位距(IQR)方法识别薪资字段中的异常值,适用于数值型字段的异常检测。

5.2 使用Pandas进行数据整理

5.2.1 DataFrame操作与字段转换

Pandas是Python中最强大的数据处理库之一,广泛用于数据清洗与整理。

字段类型转换示例:

df['salary'] = pd.to_numeric(df['salary'], errors='coerce')
df['publish_date'] = pd.to_datetime(df['publish_date'])

参数说明:

  • errors='coerce' : 转换失败时返回NaN;
  • pd.to_datetime() : 将字符串转换为日期格式。

新增字段示例:

df['salary_level'] = pd.cut(df['salary'], bins=[0, 5000, 10000, 20000, float('inf')],
                            labels=['低', '中', '高', '超高'])

逻辑分析:

此段代码新增“薪资等级”字段,将薪资分为四个等级,便于后续可视化与分析。

5.2.2 数据聚合与导出操作

数据聚合示例:

# 按城市统计平均薪资
city_salary = df.groupby('city')['salary'].mean().reset_index()

参数说明:

  • groupby('city') : 按城市分组;
  • mean() : 计算每组的平均值;
  • reset_index() : 重置索引。

导出数据示例:

df.to_excel('zhilian_jobs_final.xlsx', index=False)

参数说明:

  • index=False : 不导出索引列。

Mermaid流程图展示数据整理流程:

graph TD
    A[原始数据] --> B{数据清洗}
    B --> C[去重]
    B --> D[缺失值处理]
    B --> E[异常值处理]
    C --> F{字段标准化}
    D --> F
    E --> F
    F --> G[导出结构化数据]

5.3 数据存储至CSV/JSON/MySQL

5.3.1 文件格式写入技巧

CSV写入:

df.to_csv('zhilian_jobs.csv', index=False, encoding='utf-8-sig')

参数说明:

  • encoding='utf-8-sig' : 保留中文字符并兼容Excel打开。

JSON写入:

df.to_json('zhilian_jobs.json', orient='records', force_ascii=False)

参数说明:

  • orient='records' : 每行数据作为一个JSON对象;
  • force_ascii=False : 保留中文字符。

5.3.2 数据库连接与批量插入

使用MySQL存储数据:

from sqlalchemy import create_engine

engine = create_engine('mysql+pymysql://user:password@localhost:3306/zhilian')
df.to_sql('jobs', con=engine, if_exists='append', index=False)

参数说明:

  • create_engine : 创建数据库连接;
  • to_sql : 将DataFrame写入SQL表;
  • if_exists='append' : 若表存在则追加数据。

表格:不同存储方式对比

存储方式 优点 缺点 适用场景
CSV 简单、便于查看 不适合大数据 小型项目
JSON 支持嵌套结构 读写效率低 API交互
MySQL 支持查询、事务 部署复杂 企业级系统

5.4 多线程与异步IO爬取优化

5.4.1 Gevent与Aiohttp异步请求

使用Aiohttp发起异步请求:

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        return await asyncio.gather(*tasks)

urls = ['https://sou.zhaopin.com/jobs-search-result.htm?jl=北京&kw=Python&p=1',
        'https://sou.zhaopin.com/jobs-search-result.htm?jl=上海&kw=Python&p=1']

htmls = asyncio.run(main(urls))

参数说明:

  • ClientSession : 异步HTTP客户端;
  • asyncio.gather : 并发执行多个协程。

逻辑分析:

该段代码通过 aiohttp 库实现异步HTTP请求,大幅提高爬虫效率,尤其适用于大规模数据采集。

5.4.2 并发控制与资源调度优化

使用Gevent控制并发:

import gevent
from gevent import monkey
monkey.patch_all()
import requests

def fetch_url(url):
    response = requests.get(url)
    print(f"Fetched {url} with status {response.status_code}")

urls = ['https://www.zhaopin.com', 'https://www.zhaopin.com/beijing', 'https://www.zhaopin.com/shanghai']

tasks = [gevent.spawn(fetch_url, url) for url in urls]
gevent.joinall(tasks)

逻辑分析:

通过 gevent.spawn() 创建多个并发任务, joinall() 等待所有任务完成。适用于I/O密集型任务。

性能优化建议:

  • 设置最大并发数防止资源耗尽;
  • 使用连接池复用TCP连接;
  • 添加请求间隔( time.sleep() )避免触发反爬机制。

总结与下章预告

本章详细讲解了数据清洗与结构化处理、使用Pandas进行数据整理、数据存储至CSV/JSON/MySQL,以及多线程与异步IO爬取优化等内容,并通过代码实例、表格与流程图帮助读者深入理解。这些内容构成了爬虫系统的后处理核心环节,为下一章的招聘数据分析与系统部署打下坚实基础。

6. 招聘数据分析与系统部署

6.1 招聘数据分析与可视化初步

在完成智联招聘网站数据的采集和存储后,下一步是对这些数据进行分析和可视化,挖掘其中的潜在价值。常见的分析维度包括薪资分布、职位热度、地域分布、公司规模等。

6.1.1 薪资分布与职位热度分析

以存储在MySQL数据库中的招聘数据为例,我们可以使用Pandas和Matplotlib进行初步的数据分析和可视化。

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import mysql.connector

# 连接数据库
conn = mysql.connector.connect(
    host="localhost",
    user="root",
    password="password",
    database="zhilian_data"
)

# 查询数据
query = "SELECT job_title, salary, city, company_size FROM job_info"
df = pd.read_sql(query, conn)

# 处理薪资字段(示例:'10k-15k' → 12.5)
def parse_salary(s):
    if '-' in s:
        parts = s.split('-')
        low, high = float(parts[0].replace('k', '')), float(parts[1].replace('k', ''))
        return (low + high) / 2
    else:
        return float(s.replace('k', ''))

df['avg_salary'] = df['salary'].apply(parse_salary)

# 绘制薪资分布直方图
plt.figure(figsize=(10, 6))
sns.histplot(df['avg_salary'], bins=20, kde=True, color='blue')
plt.title('薪资分布直方图')
plt.xlabel('平均薪资(千/月)')
plt.ylabel('频数')
plt.grid(True)
plt.show()

代码说明:
- 使用 pandas.read_sql 从MySQL中读取数据;
- 定义函数 parse_salary 将薪资字符串转换为数值型数据;
- 使用 seaborn.histplot 绘制薪资分布图;
- 添加KDE曲线增强趋势感知。

6.1.2 地域分布与公司规模统计

我们还可以分析职位在不同城市的分布情况以及公司规模的占比。

# 地域分布柱状图
city_counts = df['city'].value_counts().head(10)
plt.figure(figsize=(12, 6))
sns.barplot(x=city_counts.values, y=city_counts.index, palette='viridis')
plt.title('招聘职位地域分布(Top 10)')
plt.xlabel('数量')
plt.ylabel('城市')
plt.xticks(rotation=45)
plt.show()

# 公司规模饼图
company_size_counts = df['company_size'].value_counts()
plt.figure(figsize=(8, 8))
plt.pie(company_size_counts, labels=company_size_counts.index, autopct='%1.1f%%', startangle=140)
plt.title('公司规模分布')
plt.axis('equal')  # 确保饼图为圆形
plt.show()

图表说明:
- 第一个图表展示前10城市的职位数量分布;
- 第二个图表展示不同公司规模在样本中的占比情况;
- 使用Seaborn和Matplotlib进行可视化,清晰直观。

6.2 爬虫定时任务与部署方案

6.2.1 Cron与APScheduler任务调度

为了实现爬虫的自动化运行,我们可以使用Linux的 cron 或Python中的 APScheduler 库进行任务调度。

使用 Linux Cron

编辑当前用户的crontab文件:

crontab -e

添加如下定时任务(每天凌晨1点运行):

0 1 * * * /usr/bin/python3 /path/to/zhilian_crawler.py >> /path/to/cron.log 2>&1

参数说明:
- 0 1 * * * :表示每天1:00执行;
- >> /path/to/cron.log :输出日志到文件,便于后续排查问题。

使用 APScheduler(Python代码示例)
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime
import subprocess

def run_crawler():
    print(f"[{datetime.now()}] 开始执行爬虫任务...")
    subprocess.run(["python3", "/path/to/zhilian_crawler.py"])

scheduler = BlockingScheduler()
scheduler.add_job(run_crawler, 'cron', hour=1, minute=0)
scheduler.start()

特点:
- 更适合与Python项目集成;
- 支持多种调度器类型(如间隔、日期、Cron表达式);
- 可以动态添加/移除任务。

6.2.2 云服务器部署与日志监控

爬虫部署建议使用云服务器(如阿里云、腾讯云、AWS等),并结合以下工具进行日志监控和进程管理:

  • Nginx :作为反向代理和静态资源服务器;
  • Supervisor :用于管理Python进程,实现开机自启;
  • Flask/Django :可构建一个简单的可视化界面展示爬取数据;
  • Logrotate :定期切割日志,避免日志文件过大;
  • Prometheus + Grafana :实现系统资源和爬虫状态的可视化监控。

6.3 网络爬虫异常处理机制

6.3.1 异常类型识别与重试机制

在实际运行中,网络请求可能会遇到各种异常,例如连接超时、状态码错误、反爬拦截等。我们需要为这些情况设计重试机制。

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def get_response(url, max_retries=3):
    session = requests.Session()
    retry = Retry(
        total=max_retries,
        backoff_factor=0.5,
        status_forcelist=[500, 502, 503, 504],
        allowed_methods=("HEAD", "GET", "OPTIONS")
    )
    adapter = HTTPAdapter(max_retries=retry)
    session.mount('https://', adapter)
    session.mount('http://', adapter)

    try:
        response = session.get(url, timeout=10)
        response.raise_for_status()
        return response
    except requests.exceptions.RequestException as e:
        print(f"请求失败:{e}")
        return None

参数说明:
- total :最大重试次数;
- backoff_factor :重试间隔时间指数增长因子;
- status_forcelist :哪些HTTP状态码触发重试;
- timeout :设置请求超时时间。

6.3.2 日志记录与错误追踪

为了方便排查问题,我们建议为爬虫程序添加详细的日志记录机制。

import logging

logging.basicConfig(
    filename='zhilian_crawler.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

try:
    # 爬虫逻辑
    pass
except Exception as e:
    logging.error(f"发生异常:{e}", exc_info=True)

日志内容示例:

2025-04-05 12:34:56,789 - ERROR - 发生异常:HTTP 500 Error
Traceback (most recent call last):
  File "zhilian_crawler.py", line 42, in <module>
    response.raise_for_status()
  ...

6.4 智联招聘数据采集系统完整流程与实战

6.4.1 系统架构设计与模块划分

一个完整的招聘数据采集系统通常由以下几个模块组成:

模块 功能描述
请求模块 使用Requests或Selenium发起HTTP请求
解析模块 使用BeautifulSoup、XPath或CSS选择器提取数据
存储模块 将数据写入CSV、JSON或MySQL数据库
调度模块 使用APScheduler或Cron控制爬虫执行周期
监控模块 使用日志和监控工具实时追踪系统状态
异常处理模块 对网络异常、反爬机制、数据异常进行捕获与处理

架构图(Mermaid格式):

graph TD
    A[用户输入参数] --> B(请求模块)
    B --> C{是否成功?}
    C -->|是| D[解析模块]
    C -->|否| E[重试/记录日志]
    D --> F[存储模块]
    F --> G[MySQL/CSV/JSON]
    G --> H[调度模块定时执行]
    H --> I[日志与监控模块]
    I --> J[可视化展示]

6.4.2 从请求到存储的全流程整合与测试

我们将前面章节中各模块整合为一个完整的爬虫脚本,并进行测试。

import requests
from bs4 import BeautifulSoup
import pandas as pd
import mysql.connector
import logging
from datetime import datetime

# 日志配置
logging.basicConfig(
    filename='zhilian_crawler.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def fetch_page(url):
    headers = {
        'User-Agent': 'Mozilla/5.0',
        'Referer': 'https://www.zhaopin.com/'
    }
    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()
        return response.text
    except Exception as e:
        logging.error(f"请求失败:{e}", exc_info=True)
        return None

def parse_data(html):
    soup = BeautifulSoup(html, 'lxml')
    jobs = []
    for item in soup.select('.job-list-box li'):
        job = {
            'title': item.select_one('.job-name').text.strip(),
            'salary': item.select_one('.red').text.strip(),
            'city': item.select_one('.ellipsis-1').text.strip(),
            'company': item.select_one('.company-name').text.strip()
        }
        jobs.append(job)
    return jobs

def save_to_mysql(data):
    conn = mysql.connector.connect(
        host="localhost",
        user="root",
        password="password",
        database="zhilian_data"
    )
    cursor = conn.cursor()
    for job in data:
        sql = """
        INSERT INTO job_info (job_title, salary, city, company)
        VALUES (%s, %s, %s, %s)
        """
        cursor.execute(sql, (job['title'], job['salary'], job['city'], job['company']))
    conn.commit()
    cursor.close()
    conn.close()

def main():
    url = "https://sou.zhaopin.com/?jl=653&kw=Python&sm=0"
    html = fetch_page(url)
    if html:
        data = parse_data(html)
        save_to_mysql(data)
        print(f"[{datetime.now()}] 数据抓取完成,共 {len(data)} 条职位信息。")

if __name__ == '__main__':
    main()

执行流程说明:
- fetch_page() :发送请求并获取HTML内容;
- parse_data() :解析HTML并提取招聘数据;
- save_to_mysql() :将数据存储到MySQL数据库;
- main() :主函数协调各模块,形成完整流程。

测试建议:
- 先在本地测试单次运行是否成功;
- 然后配置定时任务进行持续采集;
- 观察日志文件确认是否出现异常;
- 可结合Flask构建一个可视化后台展示数据。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目基于Python技术栈,实现对智联招聘网站的招聘信息自动化爬取,涵盖不同城市地区的近期职位数据。通过使用Requests、BeautifulSoup等爬虫技术,结合Selenium处理动态加载内容、应对反爬机制,并完成数据解析、存储及异常处理流程。项目旨在帮助用户高效获取招聘市场数据,适用于求职分析、人才研究和人力资源数据挖掘等场景。附带完整源码与实战操作文档,适合爬虫初学者和数据分析师提升实战能力。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐