BS直聘职位数据采集与分析(爬虫)
一、项目介绍
在当今竞争激烈的就业市场中,及时掌握职位信息和市场动态变得尤为重要。本文将详细介绍如何使用Python开发一个爬虫项目,自动采集BOSS直聘网站的职位数据,并对数据进行处理和分析。
1.1 项目意义
- 帮助求职者快速获取职位信息
- 为HR提供招聘市场分析数据
- 辅助研究人员进行就业市场研究
- 提供薪资水平参考数据
1.2 技术特点
- 采用面向对象的编程方式
- 实现了完整的反爬虫机制
- 支持数据的自动化处理和合并
- 提供灵活的配置选项
1.3 数据获取方式
API接口爬取
- 主要通过BOSS直聘的API接口
https://www.zhipin.com/wapi/zpgeek/search/joblist.json获取职位列表 - 使用requests库发送GET请求,携带必要的请求参数和headers
- 返回数据格式为JSON,包含职位的基本信息
- 主要通过BOSS直聘的API接口
详情页解析
- 通过职位ID访问详情页
https://www.zhipin.com/job_detail/{job_id}.html - 使用BeautifulSoup4解析HTML页面
- 提取职位描述等详细信息
- 通过职位ID访问详情页
二、技术栈
2.1 核心技术
- Python 3.x:编程语言
- requests:网络请求库
- BeautifulSoup4:HTML解析
- pandas:数据处理
- openpyxl:Excel文件操作
2.2 开发环境
- 操作系统:支持Windows/Linux/MacOS
- IDE:推荐PyCharm或VS Code
- 依赖管理:pip
三、项目实现
3.1 项目结构
bosszhipin_spider/
├── README.md # 项目说明文档
├── zp_spider_main.py # 主爬虫程序
├── merge_job_excel.py # Excel数据合并工具
└── *.xlsx # 爬取的数据文件
3.2 核心功能实现
3.2.1 爬虫初始化配置
def __init__(self):
self.base_url = "https://www.zhipin.com/wapi/zpgeek/search/joblist.json"
self.headers = {
"User-Agent": "Mozilla/5.0 ...",
"Referer": "https://www.zhipin.com/",
"Cookie": "your_cookie_here"
}
self.params = {
"query": "Python高级开发工程师",
"city": "101220100",
"experience": "106",
"scale": "303,304,305",
"page": 1,
"pageSize": 30
}
3.2.2 数据采集流程
- 获取职位列表
def fetch_data(self, max_pages=3):
for page in range(1, max_pages + 1):
self.params['page'] = page
resp = requests.get(self.base_url,
headers=self.headers,
params=self.params)
# 处理响应数据
job_list = resp.json().get("zpData", {}).get("jobList", [])
for job in job_list:
job_id = job.get("encryptJobId")
job_desc = self.get_job_detail(job_id)
self.process_job_data(job, job_desc)
- 解析职位详情
def get_job_detail(self, job_id):
url = f"https://www.zhipin.com/job_detail/{job_id}.html"
resp = requests.get(url, headers=self.detail_headers)
soup = BeautifulSoup(resp.text, 'html.parser')
desc_tag = soup.select_one('.job-sec-text')
return desc_tag.text.strip() if desc_tag else ""
3.3 反爬虫策略
3.3.1 请求头处理
headers = {
"User-Agent": "Mozilla/5.0 ...",
"Referer": "https://www.zhipin.com/",
"Cookie": "your_cookie_here"
}
3.3.2 请求频率控制
import random
import time
def request_with_delay(self):
time.sleep(random.uniform(1, 1.5)) # 随机延时
# 执行请求操作
3.4 数据处理
3.4.1 数据结构设计
def process_job_data(self, job, job_desc):
job_data = {
"职位": job.get("jobName"),
"公司": job.get("brandName"),
"薪资": job.get("salaryDesc"),
"地区": job.get("cityName"),
"经验": job.get("jobExperience"),
"学历": job.get("jobDegree"),
"公司规模": job.get("brandScaleName"),
"行业": job.get("brandIndustry"),
"福利标签": ",".join(job.get("welfareList", [])),
"技能标签": ",".join(job.get("skills", [])),
"职位描述": job_desc
}
self.data_list.append(job_data)
3.4.2 数据存储
def save_excel(self):
filename = f"python岗位数据_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
df = pd.DataFrame(self.data_list)
df.to_excel(filename, index=False)
print(f"数据已保存到: {filename}")
四、项目优化
4.1 性能优化
异步请求处理
- 使用aiohttp实现异步爬取
- 控制并发数量
- 优化请求队列
数据处理优化
- 使用生成器处理大数据
- 实现增量更新
- 优化内存使用
4.2 稳定性提升
- 异常处理机制
def safe_request(self, url, retries=3):
for i in range(retries):
try:
response = requests.get(url, headers=self.headers)
response.raise_for_status()
return response
except Exception as e:
print(f"请求失败 ({i+1}/{retries}): {str(e)}")
if i == retries - 1:
raise
time.sleep(2 ** i) # 指数退避
- 日志记录
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
filename='spider.log'
)
五、实践经验
5.1 常见问题解决
Cookie失效问题
- 实现Cookie池
- 定期更新Cookie
- 多账号轮换
IP限制处理
- 使用代理IP池
- 动态IP切换
- 请求频率控制
数据质量保证
- 数据完整性检查
- 字段格式验证
- 重复数据处理
5.2 项目扩展
- 数据分析功能
def analyze_salary(self):
df = pd.DataFrame(self.data_list)
salary_stats = df['薪资'].value_counts()
plt.figure(figsize=(10, 6))
salary_stats.plot(kind='bar')
plt.title('职位薪资分布')
plt.show()
可视化展示
- 使用matplotlib绘制图表
- 集成echarts可视化
- 开发Web展示界面
自动化部署
- Docker容器化
- 定时任务调度
- 监控告警机制
六、总结
本项目通过Python实现了BS直聘职位数据的自动化采集和处理。在开发过程中,我们重点解决了以下问题:
反爬虫机制的突破
- 模拟浏览器行为
- 控制请求频率
- 使用代理IP
大量数据的高效处理
- 异步请求优化
- 数据结构优化
- 存储方式优化
程序的稳定性保证
- 完善的异常处理
- 日志记录机制
- 数据备份策略
七、参考资料
八、声明
本项目仅供学习交流使用,请勿用于商业用途。使用本项目时请遵守相关法律法规,尊重网站的robots协议。对于因使用本项目造成的任何问题,本项目不承担任何责任。
九最终实现效果

十 完整代码
zp_spider_main.py
import requests
import pandas as pd
import time
import random
from bs4 import BeautifulSoup
import datetime
class BossSpiderAPI:
def __init__(self):
"""
Cookie需要登录后去网页上获取
"""
self.base_url = "https://www.zhipin.com/wapi/zpgeek/search/joblist.json"
self.headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
"Referer": "https://www.zhipin.com/",
"Cookie": "lastCity=101220100; __g=-; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1744206399; HMACCOUNT=42C280446392FB0C; wt2=DlNySPpvwfKbhVut0Gr0mgKTqXmBwfqcUqRyST97FmpG1q4fuYfpOEhCiOr1dttN64ZIAMYJ2WlH238k6edxPQw~~; wbg=0; zp_at=5MyB3H6qawmfWoNUp314MzOYgaBoo-yV_45NFKxF4IU~; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1744208479; bst=V2QNMjEeP_0llgXdJtyh0QICmw7D7Rwg~~|QNMjEeP_0llgXdJtyh0QICmw7DvRwQ~~; __c=1744206399; __a=15610779.1712562134.1730179058.1744206399.26.3.12.26; __zp_stoken__=6578fRUnDnsOIwp3DiD42DRYRGxg9LUxJOzJLRTVDSUlFSUVLS0VJPSk9NTLDhl%2FDg8OSX8OcwrzCvUY2RUlFRUlDSURCJkU9wr5FSjA2w4Njw4rDkl%2FDjRjFhcOMHMKww4w3wpXDiRHCqMOIGFUvNyvDiEg9REjDssOLw6XDgcKOw4fDoMOIbcOLw6TCvT1MSMOJLkESaRxpQUxVW2sPWmFcYWVOFlNPVjpIRkpEccOML0sNHBgcGBYTDxMPExYaEQ0WEw8TDxcSDhIOPEXCnsOMWMK8w6DEosO5xKrCnk3CocK7xITCosKtZMKSwqXEgWPCtVhUwrPCrsK2wqbCrsKzXsKvw4tNwr7CpFjCqV%2FCrlFXw4hPHMKAd8OEw4p4WcK%2BVQ4ZwokPFEwVH1nDlw%3D%3D"
}
self.detail_headers = {
"User-Agent": self.headers["User-Agent"],
"Cookie": self.headers["Cookie"]
}
self.params = {
"scene": "1",
"query": "Python高级开发工程师",
"city": "101220100",# 城市:福州
"experience": "106",# 经验:3-5年
"scale": "303,304,305",# 公司规模:100-999人
"page": 1,
"pageSize": 30
}
self.data_list = []
def fetch_data(self, max_pages=3):
for page in range(1, max_pages + 1):
print(f"抓取第 {page} 页...")
self.params['page'] = page
try:
resp = requests.get(self.base_url, headers=self.headers, params=self.params)
resp.raise_for_status()
except requests.RequestException as e:
print(f"请求失败:{e}")
continue
result = resp.json()
job_list = result.get("zpData", {}).get("jobList", [])
if not job_list:
print("没有更多数据了")
break
for job in job_list:
job_id = job.get("encryptJobId")
job_desc = self.get_job_detail(job_id)
item = {
"职位": job.get("jobName"),
"公司": job.get("brandName"),
"薪资": job.get("salaryDesc"),
"地区": job.get("cityName"),
"经验": job.get("jobExperience"),
"学历": job.get("jobDegree"),
"公司规模": job.get("brandScaleName"),
"行业": job.get("brandIndustry"),
"福利标签": ",".join(job.get("welfareList", [])),
"技能标签": ",".join(job.get("skills", [])),
"职位描述": job_desc
}
self.data_list.append(item)
time.sleep(random.uniform(1, 1.5))
def get_job_detail(self, job_id):
url = f"https://www.zhipin.com/job_detail/{job_id}.html"
try:
resp = requests.get(url, headers=self.detail_headers)
if resp.status_code != 200:
return ""
soup = BeautifulSoup(resp.text, 'html.parser')
desc_tag = soup.select_one('.job-sec-text')
return desc_tag.text.strip() if desc_tag else ""
except Exception as e:
print(f"详情页获取失败: {e}")
return ""
def save_excel(self):
filename = f"python岗位数据_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
df = pd.DataFrame(self.data_list)
df.to_excel(filename, index=False)
print(f"保存成功:{filename},共 {len(df)} 条职位")
def run(self):
self.fetch_data()
self.save_excel()
if __name__ == "__main__":
spider = BossSpiderAPI()
spider.run()
zp_spider_main.py
import os
import pandas as pd
from openpyxl import load_workbook
def merge_excelfiles(dir_path, save_path):
file_list = [os.path.join(dir_path, f) for f in os.listdir(dir_path) if f.endswith(".xlsx")]
df_list = []
headers = None
for file in file_list:
try:
wb = load_workbook(file, data_only=True)
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
data = list(ws.values)
if not data:
print(f"️ 跳过空文件或空Sheet: {file} - {sheet_name}")
continue
if headers is None:
headers = list(data[0])
data_rows = data[1:] # 去掉表头
else:
if list(data[0]) == headers:
data_rows = data[1:]
else:
data_rows = data # 有些 sheet 可能没有表头
if not data_rows:
continue
df = pd.DataFrame(data_rows, columns=headers)
df_list.append(df)
except Exception as e:
print(f" 读取文件失败: {file},错误: {str(e)}")
continue
if df_list:
merge_data = pd.concat(df_list, axis=0)
merge_data.to_excel(save_path, index=False)
print(f" 合并完成,保存至: {save_path}")
else:
print("️ 没有数据可以合并。")
# 示例路径
dir_path = "/Users/melon/Desktop/zj/odoo18/Celery/bosszhipin_spider" # 设置Excel所在目录
save_path = "merged_后端开发.xlsx" # 合并后的保存路径
merge_excelfiles(dir_path, save_path)
BS直聘职位数据采集与分析(爬虫)的更多相关文章
- Pyhton爬虫实战 - 抓取BOSS直聘职位描述 和 数据清洗
Pyhton爬虫实战 - 抓取BOSS直聘职位描述 和 数据清洗 零.致谢 感谢BOSS直聘相对权威的招聘信息,使本人有了这次比较有意思的研究之旅. 由于爬虫持续爬取 www.zhipin.com 网 ...
- 基于‘BOSS直聘的招聘信息’分析企业到底需要什么样的PHP程序员
原文地址:http://www.jtahstu.com/blog/scrapy_zhipin_php.html 基于'BOSS直聘的招聘信息'分析企业到底需要什么样的PHP程序员 标签(空格分隔): ...
- python分析BOSS直聘的某个招聘岗位数据
前言 毕业找工作,在职人员换工作,离职人员找工作……不管什么人群,应聘求职,都需要先分析对应的招聘岗位,岗位需求是否和自己匹配,常见的招聘平台有:BOSS直聘.拉钩招聘.智联招聘等,我们通常的方法都是 ...
- 爬虫系列---scrapy post请求、框架组件和下载中间件+boss直聘爬取
一 Post 请求 在爬虫文件中重写父类的start_requests(self)方法 父类方法源码(Request): def start_requests(self): for url in se ...
- 从零学习Fluter(六):Flutter仿boss直聘v1.0重构
今天继续学习flutter,觉得这个优秀的东西,许多方面还需要完善,作为一个后来者,要多向别人学习.俗话说,“学无先后,达者为师”.今天呢,我又重新把flutter_boss这个项目代码 从头到脚看了 ...
- Python爬虫——Scrapy整合Selenium案例分析(BOSS直聘)
概述 本文主要介绍scrapy架构图.组建.工作流程,以及结合selenium boss直聘爬虫案例分析 架构图 组件 Scrapy 引擎(Engine) 引擎负责控制数据流在系统中所有组件中流动,并 ...
- scrapy——7 scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置
scrapy——7 什么是scrapy-redis 怎么安装scrapy-redis scrapy-redis常用配置文件 scrapy-redis键名介绍 实战-利用scrapy-redis分布式爬 ...
- Scrapy 爬取BOSS直聘关于Python招聘岗位
年前的时候想看下招聘Python的岗位有多少,当时考虑目前比较流行的招聘网站就属于boss直聘,所以使用Scrapy来爬取下boss直聘的Python岗位. 1.首先我们创建一个Scrapy 工程 s ...
- Python的scrapy之爬取boss直聘网站
在我们的项目中,单单分析一个51job网站的工作职位可能爬取结果不太理想,所以我又爬取了boss直聘网的工作,不过boss直聘的网站一次只能展示300个职位,所以我们一次也只能爬取300个职位. jo ...
- 打造IP代理池,Python爬取Boss直聘,帮你获取全国各类职业薪酬榜
爬虫面临的问题 不再是单纯的数据一把抓 多数的网站还是请求来了,一把将所有数据塞进去返回,但现在更多的网站使用数据的异步加载,爬虫不再像之前那么方便 很多人说js异步加载与数据解析,爬虫可以做到啊,恩 ...
随机推荐
- Python基础-模块和包(hashlib、random、json、time、datetime和os模块)
什么是模块和包? 模块:python中的.py文件,将一些功能按照某一种维度进行划分: 自定义.内置..第三方. 包:文件夹 里面好多个.py文件. 在讨论的时候,一般统称为:模块. 学习: 自定义模 ...
- Matlab转python的索引问题
python 中numpy库可以实现类似matlab多维数组的运算.但两者在索引方式上存在一些差异.这是需要注意的.例如: % 定义一个4*4矩阵 A=1:16; A=reshape(A,[4,4]) ...
- [Qt基础-07 QSignalMapper]
QSignalMapper 本文主要根据QT官方帮助文档以及日常使用,简单的介绍一下QSignalMapper的功能以及使用 文章目录 QSignalMapper 简介 使用方法 主要的函数 信号和槽 ...
- https证书中的subject alternative name字段作用及如何生成含该字段的证书
背景 最近,某个运维同事找到我,说测试环境的某个域名(他也在负责维护),假设域名为test.baidu.com,以前呢,证书都是用的生产的证书,最近不让用了.问为啥呢,说不安全,现在在整改了,因为证书 ...
- ModuleNotFoundError: No module named '_sqlite3' when Python3
前言 运行 python 报错:ModuleNotFoundError: No module named '_sqlite3' 解决 重新编译安装 python ./configure --enabl ...
- gin Http请求Body和Header的获取 request post form Query header
gin Http请求Body和Header的获取 request post form Query header 请求参数 POST /post?id=1234&page=1 HTTP/1.1 ...
- linux 各种防火墙
一.iptables防火墙1.基本操作 # 查看防火墙状态 service iptables status # 停止防火墙 service iptables stop # 启动防火墙 service ...
- Docker镜像介绍
一.Docker镜像介绍 镜像是Docker的三大核心概念之一. Docker运行容器前需要本地存在对应的镜像,如果镜像不存在本地,Docker会尝试先从默认的镜像仓库下载(默认使用Docker Hu ...
- leetcode3208. 交替组 II
循环数组问题,指针问题 代码 比较好实现的,只需要对右端点维护,如果达到了>=k便可以被计数,循环数组可以两边循环做到 点击查看代码 class Solution { public int nu ...
- BUUCTF---古典密码知多少
题目 知识 一共给出四种古典密码,分别是:猪圈密码.圣堂武士密码.标准银河字母.栅栏密码 猪圈之前有介绍 圣: 标准银河字母 更多加密方式 解题 对照解密 FGCPFLIRTUASYON 再使用栅栏 ...