最近在帮某人力资源公司做技术咨询时,发现他们手头积累了近5年的招聘平台数据,却一直堆在数据库里"吃灰"。这些包含岗位信息、薪资范围、技能要求的原始数据,如果能进行系统化分析,本可以成为企业制定招聘策略的黄金参考。这促使我开发了这套基于Python的招聘数据分析系统,它实现了从原始数据清洗到多维可视化的完整闭环。
这个系统的核心价值在于:
系统采用分层架构设计,主要技术组件包括:
| 层级 | 技术选型 | 选择理由 |
|---|---|---|
| 数据采集 | Scrapy + Selenium | 应对招聘网站反爬策略,支持动态渲染页面采集 |
| 数据存储 | MongoDB + MySQL | MongoDB存储非结构化原始数据,MySQL存储清洗后的结构化数据 |
| 数据处理 | Pandas + PySpark | Pandas处理中小规模数据,PySpark支持分布式计算应对千万级数据 |
| 可视化展示 | ECharts + Flask | ECharts提供丰富的交互图表,Flask轻量易扩展 |
| 任务调度 | Celery + Redis | 实现异步任务队列和定时爬取 |
提示:MongoDB的文档结构特别适合存储不同招聘网站的异构数据,字段差异不会导致表结构变更
python复制def field_mapping(raw_data):
# 建立招聘网站字段到标准字段的映射规则
mapping_rules = {
'job_title': ['职位名称', 'title', '岗位'],
'salary': ['薪资范围', 'money', '薪酬'],
# 其他字段映射规则...
}
standardized = {}
for std_field, variants in mapping_rules.items():
for var in variants:
if var in raw_data:
standardized[std_field] = raw_data[var]
break
return standardized
python复制import re
def parse_salary(salary_str):
# 处理"15K-30K·14薪"这类复杂薪资表示
pattern = r'(\d+)[kK]-(\d+)[kK].*?(\d+)薪'
match = re.search(pattern, salary_str)
if match:
min_salary = int(match.group(1)) * 1000
max_salary = int(match.group(2)) * 1000
annual_months = int(match.group(3))
return {
'monthly_min': min_salary,
'monthly_max': max_salary,
'annual_months': annual_months,
'annual_min': min_salary * annual_months,
'annual_max': max_salary * annual_months
}
# 其他薪资格式处理...
采用分布式爬虫架构设计,主要解决三个技术难点:
python复制class IncrementalSpider(scrapy.Spider):
def __init__(self):
# 加载上次采集的最新时间戳
self.latest_timestamp = load_checkpoint()
def parse(self, response):
current_items = extract_items(response)
new_items = [i for i in current_items
if i['publish_time'] > self.latest_timestamp]
if new_items:
self.latest_timestamp = max(i['publish_time']
for i in new_items)
save_checkpoint(self.latest_timestamp)
yield from process_items(new_items)
采用TF-IDF结合BiLSTM-CRF的混合模型:
python复制from transformers import BertTokenizer, BertModel
import torch.nn as nn
class SkillExtractor(nn.Module):
def __init__(self, bert_path):
super().__init__()
self.bert = BertModel.from_pretrained(bert_path)
self.bilstm = nn.LSTM(
input_size=768,
hidden_size=256,
bidirectional=True
)
self.crf = CRF(num_tags=5) # B/I/O等标签
def forward(self, input_ids):
outputs = self.bert(input_ids)
lstm_out, _ = self.bilstm(outputs.last_hidden_state)
return self.crf(lstm_out)
实现四层分析维度:
python复制def analyze_salary(df):
# 计算各城市薪资中位数
city_stats = df.groupby('city')['salary_mid'].agg(
['count', 'median', 'std']
).sort_values('median', ascending=False)
# 生成薪资分布雷达图数据
tech_skills = ['Python', 'Java', 'Go']
radar_data = []
for skill in tech_skills:
skill_df = df[df['skills'].str.contains(skill)]
stats = skill_df.groupby('work_year')['salary_mid'].median()
radar_data.append({
'name': skill,
'data': stats.values.tolist()
})
return {
'city_stats': city_stats,
'radar_data': radar_data
}
采用三层信息展示结构:
javascript复制// ECharts关系图配置
option = {
series: [{
type: 'graph',
layout: 'force',
data: skills.nodes,
links: skills.links,
categories: skills.categories,
emphasis: {
focus: 'adjacency',
label: {
show: true
}
},
force: {
repulsion: 100,
edgeLength: [50, 150]
}
}]
};
python复制# 生成城市-职位薪资热力数据
def gen_heatmap_data(df):
pivot_table = df.pivot_table(
values='salary_mid',
index='city',
columns='job_type',
aggfunc='median'
)
return {
'cities': pivot_table.index.tolist(),
'jobs': pivot_table.columns.tolist(),
'data': pivot_table.values.tolist()
}
采用Docker Compose编排服务:
yaml复制version: '3'
services:
mongodb:
image: mongo:5.0
volumes:
- ./data/db:/data/db
redis:
image: redis:6.2
web:
build: ./web
ports:
- "5000:5000"
depends_on:
- mongodb
- redis
celery:
build: ./worker
command: celery -A tasks worker --loglevel=info
depends_on:
- redis
db.jobs.createIndex({city:1, job_type:1, publish_date:-1})问题1:招聘网站频繁变更页面结构
解决方案:
问题2:验证码触发频率过高
应对策略:
问题1:薪资字段格式混乱
清洗方案:
python复制def clean_salary(text):
# 统一处理"面议"情况
if '面议' in text:
return None
# 标准化千/万单位
text = text.replace('千', 'K').replace('万', 'W')
# 范围型薪资解析
if '-' in text:
low, high = text.split('-')
return (parse_number(low) + parse_number(high)) / 2
# 其他格式处理...
问题2:技能词同义合并
解决方法:
这个系统在实际交付后,客户反馈最有价值的功能是技能关联网络图,它能直观展示如"掌握Python的求职者通常还具备哪些附加技能"这类关键洞察。一个实用的建议是:在初期数据量不足时,可以先使用公开数据集(如前程无忧、拉勾的公开岗位数据)进行模型预训练,待积累足够业务数据后再进行微调。