1. 项目背景与核心价值
最近在技术社区看到不少同行对招聘平台的薪资数据感兴趣,这类数据对于职业规划、行业分析和市场调研确实很有参考价值。今天我就以某联招聘平台为例,分享一个完整的Python爬虫实战案例,重点解析如何逆向采集全国城市薪资排行榜单数据。
这个项目有几个典型应用场景:
- 求职者可以了解不同城市、职位的薪资水平,辅助职业决策
- 企业HR能掌握行业薪资趋势,优化招聘策略
- 数据分析师可以基于这些数据做更深入的行业研究
重要提示:本文仅用于技术交流学习,所有数据采集行为必须遵守平台规则和相关法律法规。实际操作中请控制请求频率,避免对目标服务器造成负担。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术方案设计
2.1 目标分析
首先需要明确我们要采集的数据特征:
- 城市维度:包括一线城市、新一线城市及其他分级城市
- 职位分类:技术类、产品类、运营类等主要岗位
- 薪资指标:平均薪资、中位数、分位数等关键数据
- 时间维度:最新数据及历史趋势(如可选)
2.2 技术选型
经过对目标网站的分析,我选择了以下技术栈组合:
python复制主要工具:
- Requests:处理HTTP请求
- BeautifulSoup:HTML解析
- PyExecJS:执行JavaScript解密
- Pandas:数据清洗与存储
辅助工具:
- Fiddler:抓包分析
- Chrome DevTools:逆向调试
- Redis:请求去重
选择这些工具主要基于以下考虑:
- 该平台前端有JavaScript加密逻辑,需要PyExecJS来处理
- 反爬机制较为复杂,需要结合多种工具分析
- 数据结构化程度高,适合用Pandas处理
3. 逆向工程实战
3.1 请求分析
首先使用Chrome开发者工具分析网络请求:
- 打开薪资排行榜页面
- 捕获XHR请求,发现关键API接口
- 分析请求参数和响应结构
典型请求特征:
bash复制GET /api/salary/rank?city=101&position=200 HTTP/1.1
Headers:
x-sign: d41d8cd98f00b204e9800998ecf8427e
x-timestamp: 1625097600
3.2 签名算法逆向
通过调试发现平台使用了请求签名机制,主要逆向步骤:
- 定位到核心加密函数位于
main.xxxxxx.js - 使用AST工具分析加密逻辑
- 提取关键代码用PyExecJS重写
签名算法核心逻辑:
javascript复制function generateSign(params) {
const secret = "xxxxxx";
let str = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join("&");
return md5(str + secret);
}
Python实现方案:
python复制import execjs
with open('encrypt.js', 'r') as f:
ctx = execjs.compile(f.read())
def get_sign(params):
return ctx.call('generateSign', params)
3.3 反爬绕过策略
该平台采用了多种反爬措施,需要针对性处理:
- IP限制:使用代理池轮换,建议每5秒1个请求
- User-Agent检测:准备20+常用UA随机切换
- 行为验证:模拟鼠标移动轨迹,避免纯脚本行为
- Cookie时效:定期更新关键cookie字段
代理配置示例:
python复制proxies = {
'http': 'http://user:pass@ip:port',
'https': 'https://user:pass@ip:port'
}
headers = {
'User-Agent': random.choice(user_agents),
'Referer': 'https://www.zhipin.com/'
}
4. 数据采集实现
4.1 核心采集流程
完整的数据采集流程如下:
- 获取城市列表(静态数据)
- 遍历各城市获取职位分类
- 对每个城市-职位组合发起请求
- 解析响应并存储结果
python复制def main():
cities = get_city_list()
positions = get_position_list()
for city in cities:
for position in positions:
data = fetch_salary_data(city, position)
save_to_db(data)
time.sleep(random.uniform(3, 5))
4.2 数据解析技巧
响应数据通常是多层嵌套的JSON,需要特别注意:
- 薪资数据可能以K为单位(如"15K-30K")
- 部分字段可能是HTML格式需要二次解析
- 城市编码与名称的映射关系
数据处理示例:
python复制def parse_salary(salary_str):
if 'K' in salary_str:
low, high = salary_str.replace('K', '').split('-')
return int(low)*1000, int(high)*1000
return None, None
4.3 数据存储方案
根据数据量大小推荐两种存储方式:
- 小规模数据:
python复制# CSV存储
df.to_csv('salary_data.csv', index=False)
- 大规模数据:
python复制# MongoDB存储
client = pymongo.MongoClient()
db = client['salary_db']
collection = db['city_salary']
collection.insert_many(data_list)
5. 数据分析与可视化
5.1 基础分析维度
采集到的数据可以进行多种分析:
- 城市薪资排名Top10
- 各职位薪资对比
- 城市-职位交叉分析
- 薪资随时间变化趋势
5.2 可视化示例
使用Matplotlib绘制城市薪资热力图:
python复制import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(12, 8))
heatmap_data = df.pivot_table(values='avg_salary',
index='city',
columns='position')
sns.heatmap(heatmap_data, annot=True, fmt=".1f")
plt.title('城市-职位薪资热力图')
plt.show()
6. 常见问题与解决方案
6.1 请求被拦截
现象:返回403状态码或验证码页面
解决方案:
- 检查请求头是否完整(特别是Referer和Origin)
- 降低请求频率至5秒/次
- 更新签名算法(平台可能定期变更)
6.2 数据解析失败
现象:获取到数据但字段缺失或格式异常
排查步骤:
- 检查响应结构是否变化
- 验证字段映射关系
- 添加异常捕获和日志记录
python复制try:
salary = data['salary']['avg']
except KeyError as e:
logger.error(f"字段缺失: {e}")
6.3 性能优化建议
当需要采集大量数据时,可以考虑:
- 使用异步请求(aiohttp)
- 实现断点续爬功能
- 分布式采集架构设计
异步采集示例:
python复制import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.json()
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
return await asyncio.gather(*tasks)
7. 法律与道德考量
在开发这类爬虫时,必须注意:
- 严格遵守robots.txt协议
- 控制请求频率,避免影响网站正常运行
- 不采集个人隐私数据
- 数据使用限于分析研究目的
建议在代码中添加自律控制:
python复制# 自律设置
MAX_REQUESTS_PER_MINUTE = 12
MIN_INTERVAL = 5 # 秒
在实际项目中,我通常会设置严格的采集间隔,并在非工作时间段运行爬虫。对于特别敏感的数据,建议先尝试联系平台获取官方API接口。
