使用Python爬取BOSS直聘职位数据并保存到Excel
使用Python爬取BOSS直聘职位数据并保存到Excel
在数据分析和挖掘中,爬取招聘网站数据是一项常见的任务。本文将详细介绍如何使用Python爬取BOSS直聘上与“测试工程师”相关的职位数据,并将其保存到Excel文件中。通过逐步分解代码和添加详细注释,我们将深入了解整个实现过程。
项目概述
本项目的功能是爬取BOSS直聘指定条件的职位数据,并将数据保存到Excel文件。以下是主要步骤:
- 配置浏览器驱动:使用Selenium模拟用户操作。
- 加载网页:动态加载职位列表。
- 解析职位信息:提取职位的名称、薪资、技能要求等。
- 保存数据:数据持久化到Excel文件,并为列标题添加注释,便于理解。
环境准备
安装依赖库
请确保已安装以下库:
pip install selenium pandas openpyxl
下载ChromeDriver
根据你的Chrome浏览器版本,下载对应的ChromeDriver,并将其路径配置到代码中。
我这里是用的chrome浏览器:
先查看浏览器版本
114及更早版本
113及113以后下载
130及以后
详细代码解析
1. 配置Selenium WebDriver
目标:通过Selenium启动浏览器,模拟用户访问BOSS直聘。
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
def setup_driver():
"""
配置并启动Selenium WebDriver,用于模拟浏览器操作。
:return: 返回配置完成的Chrome浏览器驱动对象。
"""
options = webdriver.ChromeOptions()
# 添加可选的启动参数
# options.add_argument('--headless') # 无头模式,隐藏浏览器界面
# options.add_argument('--disable-gpu') # 禁用GPU,优化性能
# options.add_argument('--no-sandbox') # 禁用沙箱模式
# options.add_argument('--start-maximized') # 最大化窗口
# 创建浏览器驱动服务
service = Service('D:\\tool\\chromedriver-win64\\chromedriver.exe')
# 初始化WebDriver
driver = webdriver.Chrome(service=service, options=options)
return driver
- 重点:
webdriver.ChromeOptions
可以设置浏览器的启动参数,如无头模式(headless)。 - 路径配置:
Service
指定了ChromeDriver的路径。
2. 加载网页并滚动加载
目标:加载指定页面,并模拟滚动加载更多内容。
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
def scrape_jobs(driver, url):
"""
爬取BOSS直聘的职位数据。
:param driver: 已配置的Selenium WebDriver实例。
:param url: BOSS直聘的目标页面URL。
:return: 返回包含职位信息的列表。
"""
driver.get(url) # 打开指定页面
time.sleep(15) # 等待页面完全加载
# 模拟滚动操作,加载更多职位信息
for _ in range(5): # 假设需要滚动5次加载更多内容
driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.END)
time.sleep(2) # 等待页面加载完成
# 定位职位列表
jobs = driver.find_elements(By.CSS_SELECTOR, '.job-list-box > li') # 找到所有职位块
job_data = [] # 用于存储职位数据
- 页面滚动:通过发送
Keys.END
键模拟用户滚动操作。 - 延迟加载:
time.sleep
确保页面元素完全加载。
3. 提取职位数据
目标:解析页面中每个职位的数据。
for job in jobs:
try:
# 获取职位信息
job_link_element = job.find_element(By.CSS_SELECTOR, '.job-card-left') # 职位链接所在的元素
job_link = job_link_element.get_attribute('href') # 获取链接
job_name = job.find_element(By.CSS_SELECTOR, '.job-name').text # 职位名称
salary_desc = job.find_element(By.CSS_SELECTOR, '.salary').text # 薪资描述
city_area = job.find_element(By.CSS_SELECTOR, '.job-area').text.split('·') # 城市和区域
city_name = city_area[0] # 城市名称
area_district = city_area[1] if len(city_area) > 1 else "" # 区域名称
# 获取技能标签
skills = [skill.text for skill in job.find_elements(By.CSS_SELECTOR, '.job-card-footer .tag-list li')]
# 获取经验和学历要求
tags = job.find_elements(By.CSS_SELECTOR, '.job-info .tag-list li')
job_experience = tags[0].text if len(tags) > 0 else "" # 工作年限要求
job_degree = tags[1].text if len(tags) > 1 else "" # 学历要求
# 获取公司信息
brand_name = job.find_element(By.CSS_SELECTOR, '.company-name').text # 公司名称
brand_info = [tag.text for tag in job.find_elements(By.CSS_SELECTOR, '.company-tag-list li')]
brand_industry = brand_info[0] if len(brand_info) > 0 else "" # 公司行业
brand_stage_name = brand_info[1] if len(brand_info) > 1 else "" # 公司发展阶段
brand_scale_name = brand_info[2] if len(brand_info) > 2 else "" # 公司规模
# 保存数据到列表
job_data.append({
'job_name': job_name,
'salary_desc': salary_desc,
'skills': ','.join(skills), # 用逗号分隔技能
'job_experience': job_experience,
'job_degree': job_degree,
'city_name': city_name,
'area_district': area_district,
'brand_name': brand_name,
'brand_industry': brand_industry,
'brand_stage_name': brand_stage_name,
'brand_scale_name': brand_scale_name,
'job_link': job_link,
})
except Exception as e:
print(f"Error processing job: {e}")
- 结构化数据:将提取的数据存储为字典。
- 错误处理:使用
try-except
捕获解析错误。
4. 保存到Excel并添加注释
目标:将数据保存到Excel文件,并为每列添加注释。
import pandas as pd
from openpyxl import load_workbook
from openpyxl.comments import Comment
def save_job_to_excel(jobs, filename="./jobs.xlsx"):
"""
保存职位信息到 Excel 文件。
:param jobs: 职位信息列表。
:param filename: 保存的 Excel 文件路径。
"""
columns = ['职位名称', '薪资描述', '岗位要求', '年限要求', '学历要求', '城市', '区域',
'公司名称', '行业类型', '是否上市', '公司规模', '职位链接']
comments = {
'职位名称': '职位的名称,如“软件测试工程师”。',
'薪资描述': '职位的薪资范围,例如“15-25K”。',
'岗位要求': '技能要求,例如“Python, Java”。',
'年限要求': '职位所需的工作经验。',
'学历要求': '职位所需的最低学历。',
'城市': '职位所在城市,例如“深圳”。',
'区域': '职位所在区域,例如“南山区”。',
'公司名称': '招聘公司名称。',
'行业类型': '公司所属行业。',
'是否上市': '公司发展阶段,如“上市公司”。',
'公司规模': '公司的人员规模。',
'职位链接': '职位的详情链接。'
}
new_data = pd.DataFrame(jobs) # 将数据转换为DataFrame
try:
existing_data = pd.read_excel(filename) # 读取已有数据
combined_data = pd.concat([existing_data, new_data], ignore_index=True) # 合并
except FileNotFoundError:
combined_data = new_data
combined_data.to_excel(filename, index=False) # 保存到文件
add_comments_to_excel(filename, comments)
def add_comments_to_excel(filename, comments):
"""
为Excel文件的列标题添加注释。
:param filename: Excel 文件路径。
:param comments: 列注释字典。
"""
wb = load_workbook(filename)
ws = wb.active
for col_num, col_name in enumerate(ws[1], start=1):
col_letter = chr(64 + col_num) # 获取列的字母
if col_name.value in comments:
ws[f'{col_letter}1'].comment = Comment(comments[col_name.value], "系统")
wb.save(filename)
5. 完整代码示例
import json
import os
import time
import pandas as pd
from openpyxl import load_workbook
from openpyxl.comments import Comment
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
# 配置 WebDriver
def setup_driver():
options = webdriver.ChromeOptions()
# options.add_argument('--headless') # 隐藏浏览器界面
# options.add_argument('--disable-gpu')
# options.add_argument('--no-sandbox')
# options.add_argument('--start-maximized')
service = Service('D:\\tool\\chromedriver-win64\\chromedriver.exe')
driver = webdriver.Chrome(service=service, options=options)
return driver
# 爬取页面数据
def scrape_jobs(driver, url):
driver.get(url)
time.sleep(15) # 等待页面加载
# 滚动页面加载更多内容(若有分页)
for _ in range(5):
driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.END)
time.sleep(2)
# 解析所有 job-list-box 下的 li
jobs = driver.find_elements(By.CSS_SELECTOR, '.job-list-box > li')
job_data = []
for job in jobs:
try:
# 定位到职位链接的 <a> 标签
job_link_element = job.find_element(By.CSS_SELECTOR, '.job-card-left')
# 获取职位详情链接
job_link = job_link_element.get_attribute('href')
job_name = job.find_element(By.CSS_SELECTOR, '.job-name').text
salary_desc = job.find_element(By.CSS_SELECTOR, '.salary').text
city_area = job.find_element(By.CSS_SELECTOR, '.job-area').text.split('·')
city_name = city_area[0]
area_district = city_area[1] if len(city_area) > 1 else ""
# 获取技能
skills = [skill.text for skill in job.find_elements(By.CSS_SELECTOR, '.job-card-footer .tag-list li')]
# 获取经验和学历
tags = job.find_elements(By.CSS_SELECTOR, '.job-info .tag-list li')
job_experience = tags[0].text if len(tags) > 0 else ""
job_degree = tags[1].text if len(tags) > 1 else ""
# 公司信息
brand_name = job.find_element(By.CSS_SELECTOR, '.company-name').text
brand_info = [tag.text for tag in job.find_elements(By.CSS_SELECTOR, '.company-tag-list li')]
brand_industry = brand_info[0] if len(brand_info) > 0 else ""
brand_stage_name = brand_info[1] if len(brand_info) > 1 else ""
brand_scale_name = brand_info[2] if len(brand_info) > 2 else ""
# 保存数据
job_data.append({
'job_name': job_name,
'salary_desc': salary_desc,
'skills': ','.join(skills),
'job_experience': job_experience,
'job_degree': job_degree,
'city_name': city_name,
'area_district': area_district,
'brand_name': brand_name,
'brand_industry': brand_industry,
'brand_stage_name': brand_stage_name,
'brand_scale_name': brand_scale_name,
'job_link': job_link,
})
except Exception as e:
print(f"Error processing job: {e}")
return job_data
# 保存数据到Excel
def save_job_to_excel(jobs, filename="./jobs.xlsx"):
"""
保存职位信息到 Excel 文件中,如果文件不存在则创建新文件。
:param jobs: 职位信息列表,每个元素为包含职位字段的字典。
:param filename: 保存的 Excel 文件路径。
"""
# 定义列名和中文注释
columns = [
'职位名称', '薪资描述', '岗位要求', '年限要求', '学历要求', '城市',
'区域', '公司名称', '行业类型', '是否上市', '公司规模', '任职要求', '职位链接'
]
comments = {
'职位名称': '职位的名称,如“软件测试工程师”。',
'薪资描述': '职位的薪资区间,如“12-20K”。',
'岗位要求': '职位要求的技能,通常是一个列表,如“Python, Java”等。',
'年限要求': '职位要求的工作经验年限。',
'学历要求': '职位要求的最低学历。',
'城市': '职位所在的城市,如“深圳”。',
'区域': '职位所在的区域,如“光明区”。',
'公司名称': '招聘公司的名称。',
'行业类型': '招聘公司所属的行业类型。',
'是否上市': '公司是否上市,如“已上市”。',
'公司规模': '招聘公司的人数规模,如“1000-9999人”。',
'任职要求': '职位的详细任职要求,来自职位详情页面。',
'职位链接': '职位详情的跳转链接,可直接点击查看职位详情。'
}
# 尝试读取已有数据
try:
existing_df = pd.read_excel(filename)
except FileNotFoundError:
existing_df = pd.DataFrame(columns=columns)
# 准备新数据
new_data = [
{
'职位名称': job_data['job_name'],
'薪资描述': job_data['salary_desc'],
'岗位要求': job_data['skills'], # 将列表转换为字符串
'年限要求': job_data['job_experience'],
'学历要求': job_data['job_degree'],
'城市': job_data['city_name'],
'区域': job_data['area_district'],
'公司名称': job_data['brand_name'],
'行业类型': job_data['brand_industry'],
'是否上市': job_data['brand_stage_name'],
'公司规模': job_data['brand_scale_name'],
'任职要求': job_data.get('job_requirements', ''),
'职位链接': job_data.get('job_link', '') # 添加职位链接,默认为空
}
for job_data in jobs
]
new_df = pd.DataFrame(new_data)
# 合并数据
combined_df = pd.concat([existing_df, new_df], ignore_index=True)
# 保存到 Excel 文件
combined_df.to_excel(filename, index=False)
# 添加注释到 Excel 文件
add_comments_to_excel(filename, comments)
def add_comments_to_excel(filename, comments):
"""
为 Excel 文件的列标题添加注释。
:param filename: Excel 文件路径。
:param comments: 列注释字典,键为列名,值为注释内容。
"""
wb = load_workbook(filename)
ws = wb.active
for col_num, col_name in enumerate(ws[1], start=1): # 遍历第一行的列
col_letter = chr(64 + col_num) # 将列索引转为字母,例如 1 -> A
if col_name.value in comments:
comment = Comment(comments[col_name.value], "系统")
ws[f'{col_letter}1'].comment = comment
wb.save(filename)
print(f"职位信息保存到 {filename} 并添加了注释。")
if __name__ == "__main__":
for page in range(1, 1):
url = f"https://www.zhipin.com/web/geek/job?query=测试工程师&city=101280600&page={page}"
driver = setup_driver()
try:
jobs = scrape_jobs(driver, url)
save_job_to_excel(jobs)
print(f"Scraped {len(jobs)} jobs.")
finally:
driver.quit()
总结
通过以上步骤,您可以爬取BOSS直聘的职位数据并存储为结构化的Excel文件。以下是本项目的特点:
- 自动化操作:Selenium模拟用户操作。
- 全面的数据保存:包括职位详情和公司信息。
- 注释增强可读性:通过Excel注释标注列含义。
您可以根据需求调整代码,实现更灵活的爬取和存储功能!
后续目标
在爬取职位列表数据的基础上,我们计划实现以下功能:
- 爬取职位详情页面
职位列表中通常只提供部分信息,如职位名称、薪资范围、公司信息等。而更多详细信息(如岗位职责、任职要求)存储在职位的详情页面中。下一步将实现爬取职位详情页面的功能。 - 数据清洗与存储
在爬取到更多字段后,我们需要进行数据清洗,包括去除重复项、处理缺失值、统一数据格式等。清洗后的数据将更适合训练机器学习模型。 - 训练机器学习模型
使用爬取到的数据构建模型,尝试解决以下问题:- 薪资预测:基于职位要求预测合理薪资。
- 职位分类:通过岗位职责将职位分类到不同的技术方向(如“测试”、“开发”、“运维”等)。
- 城市分析:分析不同城市的职位需求分布和薪资水平。