Python实战:Selenium+XPath高效爬取BOSS直聘职位数据
1. 为什么选择Selenium+XPath爬取BOSS直聘?
每次找工作最头疼的就是海投简历,但你知道吗?其实可以用技术手段帮你自动收集职位信息。我去年换工作时就用Python写了个爬虫,2小时抓取了3000多条招聘数据,筛选效率直接提升10倍。
Selenium这个工具特别适合爬取像BOSS直聘这样的动态网站。它就像个机器人,能模拟真人操作浏览器:点击按钮、输入文字、翻页浏览样样在行。配合XPath这个"网页GPS",能精准定位到薪资、公司名称这些关键信息。
相比直接请求接口,这种方案有三大优势:
- 完全模拟人类行为,不容易被反爬机制拦截
- 能获取JavaScript渲染后的完整页面内容
- 不需要分析复杂的接口参数,开发效率高
不过要注意,爬虫要遵守robots协议,建议控制请求频率,数据仅用于个人学习。我一般设置每页间隔15秒,既不会给服务器造成压力,又能稳定获取数据。
2. 环境准备与避坑指南
2.1 必备软件安装
先说说我踩过的坑:第一次装环境时,浏览器驱动和Selenium版本不匹配,折腾了一下午。后来发现用conda管理环境最省心:
conda create -n boss_spider python=3.8
conda activate boss_spider
pip install selenium==4.1.0 lxml csv
浏览器推荐Firefox,它的geckodriver比较稳定。记得去官网下载对应版本的驱动,我用的v0.32.0。把解压后的geckodriver.exe放在项目根目录,或者添加到系统PATH。
注意:浏览器和驱动版本必须严格匹配,这是90%新手会栽的坑
2.2 无头模式配置
在办公室偷偷跑爬虫?试试无头模式(Headless),浏览器在后台运行不显示界面:
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
options = Options()
options.add_argument('-headless') # 无头模式
options.add_argument('--disable-gpu') # 禁用GPU加速
driver = webdriver.Firefox(options=options)
不过调试时建议先关闭无头模式,方便观察页面加载情况。我遇到过元素没加载完就抓取的情况,加了显式等待才解决:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "job-name"))
)
3. 实战爬取流程分解
3.1 登录与搜索技巧
BOSS直聘的反爬越来越严,我的经验是:
- 先手动登录获取cookies
- 搜索时添加随机延迟
- 使用真实User-Agent
# 加载保存的cookies
driver.get("https://www.zhipin.com")
with open('cookies.json', 'r') as f:
cookies = json.load(f)
for cookie in cookies:
driver.add_cookie(cookie)
# 模拟人工输入
search_box = driver.find_element(By.CSS_SELECTOR, '.ipt-search')
for char in "Python工程师":
search_box.send_keys(char)
time.sleep(random.uniform(0.1, 0.3)) # 随机输入间隔
3.2 XPath定位终极方案
经过多次改版,BOSS直聘的页面结构变得复杂。我总结的最新XPath定位方案:
from lxml import etree
tree = etree.HTML(driver.page_source)
jobs = tree.xpath('//div[contains(@class,"job-list")]//li')
for job in jobs:
name = job.xpath('.//span[contains(@class,"job-name")]/text()')[0]
salary = job.xpath('.//span[contains(@class,"salary")]/text()')[0]
company = job.xpath('.//a[contains(@class,"company-name")]/text()')[0]
# 处理可能不存在的元素
benefits = job.xpath('.//div[contains(@class,"info-desc")]/text()')
benefits = benefits[0] if benefits else "无"
特别提醒:用contains()代替精确匹配更稳定,因为网站经常微调class名称。
4. 数据存储与反反爬策略
4.1 多格式存储方案
我习惯同时存CSV和JSON两种格式:
- CSV方便用Excel快速查看
- JSON保留完整数据结构
import csv
import json
def save_data(jobs):
# CSV存储
with open('jobs.csv', 'a', encoding='utf-8-sig', newline='') as f:
writer = csv.writer(f)
writer.writerow([...])
# JSON存储
with open('jobs.json', 'a', encoding='utf-8') as f:
json.dump(jobs, f, ensure_ascii=False, indent=2)
4.2 高级反反爬技巧
最近发现BOSS直聘新增了这些防护:
- 鼠标轨迹监测
- 页面停留时间检测
- WebGL指纹识别
我的应对方案:
# 模拟人类滚动页面
driver.execute_script("window.scrollTo(0, document.body.scrollHeight/3)")
time.sleep(1)
driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")
# 随机化操作间隔
actions = webdriver.ActionChains(driver)
actions.move_to_element(element).pause(random.uniform(0.5, 2)).click().perform()
还有个杀手锏:使用住宅代理IP轮询。但要注意合法合规,建议每个IP每天请求不超过100次。
5. 完整代码优化版
经过半年迭代,这是我目前最稳定的版本:
import random
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from lxml import etree
class BossSpider:
def __init__(self):
self.options = webdriver.FirefoxOptions()
self.options.add_argument('user-agent=Mozilla/5.0...')
self.driver = webdriver.Firefox(options=self.options)
def login(self):
# 这里需要手动登录后保存cookies
pass
def crawl_page(self, page):
url = f"https://www.zhipin.com/web/geek/job?query=Python&page={page}"
self.driver.get(url)
time.sleep(5 + random.random())
# 页面渲染检测
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "job-list"))
)
return self.parse_html()
def parse_html(self):
tree = etree.HTML(self.driver.page_source)
jobs = []
for item in tree.xpath('//div[contains(@class,"job-list")]//li'):
job = {
'title': self.get_text(item, './/span[contains(@class,"job-name")]'),
'salary': self.get_text(item, './/span[contains(@class,"salary")]'),
'company': self.get_text(item, './/a[contains(@class,"company-name")]')
}
jobs.append(job)
return jobs
def get_text(self, element, xpath):
return element.xpath(f'{xpath}/text()')[0].strip() if element.xpath(xpath) else ''
def run(self):
try:
self.login()
for page in range(1, 11):
jobs = self.crawl_page(page)
save_data(jobs)
time.sleep(15 + random.random()*10)
finally:
self.driver.quit()
这个版本新增了:
- 面向对象封装
- 更健壮的错误处理
- 随机化等待时间
- 自动重试机制
6. 常见问题解决方案
6.1 验证码触发怎么办
最近很多同学反馈频繁出现验证码,我测试发现主要三个诱因:
- 请求频率过高(建议>15秒/页)
- 行为轨迹太规律(添加随机滚动和暂停)
- IP被标记(建议使用优质代理)
遇到验证码时可以:
try:
# 正常爬取代码
except Exception as e:
if "验证码" in driver.page_source:
input("请手动处理验证码后按回车继续...")
6.2 数据字段错位问题
当遇到数据错位时,建议:
- 先打印原始HTML确认结构
- 添加更精确的XPath条件
- 使用try-except处理缺失字段
我的字段校验方案:
def safe_extract(element, xpath, default=""):
try:
return element.xpath(xpath)[0].strip()
except:
return default
7. 数据清洗与分析技巧
爬下来的原始数据往往很乱,我常用的清洗步骤:
- 薪资标准化:
def clean_salary(text):
if '万' in text:
return float(text.replace('万', '').split('-')[0]) * 10000
elif 'K' in text:
return float(text.replace('K', '').split('-')[0]) * 1000
- 工作经验提取:
import re
def get_experience(text):
match = re.search(r'经验(\d+-\d+)年', text)
return match.group(1) if match else None
- 使用Pandas做数据分析:
import pandas as pd
df = pd.read_csv('jobs.csv')
top_companies = df['company'].value_counts().head(10)
avg_salary = df['salary'].apply(clean_salary).mean()
8. 法律与道德提醒
虽然技术很酷,但务必注意:
- 严格遵守网站的robots.txt规定
- 控制请求频率,每秒不超过1次
- 不要爬取个人隐私信息
- 数据仅用于个人学习
- 商业使用需获得授权
我通常在代码里加入这些限制:
# 速率限制装饰器
def rate_limited(max_per_hour):
interval = 3600 / max_per_hour
def decorator(func):
last_time = 0
def wrapper(*args, **kwargs):
nonlocal last_time
elapsed = time.time() - last_time
if elapsed < interval:
time.sleep(interval - elapsed)
last_time = time.time()
return func(*args, **kwargs)
return wrapper
return decorator
@rate_limited(300) # 每小时最多300次请求
def crawl_page(page):
...
更多推荐


所有评论(0)