当前位置: 首页 > article >正文

使用Python爬取BOSS直聘职位数据并保存到Excel

使用Python爬取BOSS直聘职位数据并保存到Excel

在数据分析和挖掘中,爬取招聘网站数据是一项常见的任务。本文将详细介绍如何使用Python爬取BOSS直聘上与“测试工程师”相关的职位数据,并将其保存到Excel文件中。通过逐步分解代码和添加详细注释,我们将深入了解整个实现过程。


项目概述

本项目的功能是爬取BOSS直聘指定条件的职位数据,并将数据保存到Excel文件。以下是主要步骤:

  1. 配置浏览器驱动:使用Selenium模拟用户操作。
  2. 加载网页:动态加载职位列表。
  3. 解析职位信息:提取职位的名称、薪资、技能要求等。
  4. 保存数据:数据持久化到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文件。以下是本项目的特点:

  1. 自动化操作:Selenium模拟用户操作。
  2. 全面的数据保存:包括职位详情和公司信息。
  3. 注释增强可读性:通过Excel注释标注列含义。

您可以根据需求调整代码,实现更灵活的爬取和存储功能!

后续目标

在爬取职位列表数据的基础上,我们计划实现以下功能:

  1. 爬取职位详情页面
    职位列表中通常只提供部分信息,如职位名称、薪资范围、公司信息等。而更多详细信息(如岗位职责、任职要求)存储在职位的详情页面中。下一步将实现爬取职位详情页面的功能。
  2. 数据清洗与存储
    在爬取到更多字段后,我们需要进行数据清洗,包括去除重复项、处理缺失值、统一数据格式等。清洗后的数据将更适合训练机器学习模型。
  3. 训练机器学习模型
    使用爬取到的数据构建模型,尝试解决以下问题:
    • 薪资预测:基于职位要求预测合理薪资。
    • 职位分类:通过岗位职责将职位分类到不同的技术方向(如“测试”、“开发”、“运维”等)。
    • 城市分析:分析不同城市的职位需求分布和薪资水平。

http://www.kler.cn/a/460683.html

相关文章:

  • Flutter踩坑记-第三方SDK不兼容Gradle 8.0,需适配namespace
  • 算法-判断4的次幂
  • JavaScript的diff库详解(示例:vue项目实现两段字符串比对标黄功能)
  • 【详解】AndroidWebView的加载超时处理
  • 生态碳汇涡度相关监测与通量数据分析实践技术应用
  • Java-多种方法实现多线程计数
  • 记录开发工具
  • MySQL第二弹----CRUD
  • Caffeine Cache Java缓存组件
  • 以图像识别为例,关于卷积神经网络(CNN)的直观解释
  • 【Golang 面试题】每日 3 题(十二)
  • FPGA交通灯实现
  • Spring Boot 3 构建统一的请求响应参数、异常处理、以及统一的异常状态码
  • 在计算机网络中,什么是集群?
  • SPI扩展类与普通bean类的区别
  • 税务门户网站:构建安全的在线税务服务环境
  • macos 远程开发,实现文件自动同步
  • 全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之循环结构(for循环语句)(六)
  • 【C++】类和对象(中):类的默认成员函数
  • xterm + vue3 + websocket 终端界面
  • [2474].第04节:Activiti官方画流程图方式
  • 【开源免费】基于SpringBoot+Vue.JS安康旅游网站(JAVA毕业设计)
  • spring cloud-skywalking入门指南
  • XShell实现自动化执行脚本.sh文件)(网络安全检查)
  • 2024年的年终总结
  • vue.js 组件通信