做招聘数据分析这个题目的人很多,但能做到“能跑、能看、能答辩、能交差”的其实不多。大部分同学的卡点不是不会写爬虫,而是卡在数据处理和可视化大屏的衔接上——数据抓下来了不知道洗成什么样,洗完不知道拿什么画,画完不知道大屏怎么布局。这篇就把我做完整个项目的过程详细拆开讲一遍,从环境搭建到代码实现再到调试避坑,全部按实操流程来,尤其适合做课程设计、毕业设计或者单纯想练手数据分析的朋友参考。
1. 项目拆解:招聘数据分析到底在做什么
这个项目标题看起来只是“Python + 可视化”,实际上背后是完整的数据工程链路:采集、清洗、存储、分析、展示。很多人拿到这种题目就开始写爬虫,爬到一半发现数据分析做不下去,数据乱七八糟,可视化大屏更不知道从哪下手。
我先讲一下这个项目的整体数据流,你心里有个底,后面每一步都知道自己在干嘛。
1.1 核心需求解析
招聘数据分析的本质是回答几个问题:
- 哪些城市的招聘需求量最大、薪资最高
- 什么岗位最热门、竞争最激烈
- 学历和经验对薪资的影响到底有多大
- 哪些技能关键词在岗位描述里出现频率最高
围绕这几个问题,整个项目就需要完成:
- 从招聘网站上爬取岗位数据(职位名称、公司、城市、薪资、学历要求、经验要求、技能标签等)
- 对爬下来的数据做清洗和标准化(尤其薪资字段“1-1.5万/月”这种要转成数字)
- 按维度做统计分析(城市维度、岗位维度、学历维度、经验维度)
- 把分析结果用图表展示出来,做成可视化大屏
你得先想清楚自己要交付什么。这类项目一般需要交付源码、文档、调试说明和可视化大屏展示页。文档一般是开发文档和答辩PPT材料,大屏是HTML页面(建议用ECharts做)。
1.2 技术方案选型:为什么是Python
对比Java、R这种方案,Python在这类项目里几乎是唯一的合理性选择,理由就三条:
- 爬虫生态成熟:Requests + BeautifulSoup(或者Scrapy)小半天就能上手
- 数据处理一条龙:Pandas能把清洗、分组、聚合全干完,省去来回导数据的痛苦
- 可视化链路短:数据直接转JSON,ECharts拿到就能画,中间不用写复杂的后端接口
还有一个隐藏理由:调试方便。Pycharm断点一打,爬虫数据对不对立刻能看到;Power BI那种拖拽式的工具反而不好展示你个人的技术能力。
所以这套方案的链路就是:Requests爬数据 → Pandas洗数据 → MySQL存数据 → Flask/ECharts可视化大屏。一条链走完,所有技术点都能在文档里展开写。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开局准备:环境、工具和依赖清单
这个项目去答辩的场合挺多的,环境配置写错了会直接导致演示失败,所以我把这块放在前面讲。项目建议使用Python 3.9或3.10版本,太新的版本某些库还没跟进,太旧的库兼容性也头疼。
2.1 基础环境搭建
我推荐用Anaconda管理环境,原因有两条。第一,自带上百个常用包(Pandas、NumPy这些不用单独装);第二,环境隔离做得好,项目打包换电脑不会因为缺依赖跑不起来。
bash复制# 创建虚拟环境并激活
conda create -n recruit python=3.9
conda activate recruit
如果是自己手动装Python,记得安装时勾选“Add Python to PATH”,不然后续命令行里python都起不来。
2.2 核心依赖安装
依赖清单分两类:一类是数据抓取和清洗用的,一类是可视化用的。
bash复制# 数据采集与处理
pip install requests beautifulsoup4 pandas mysql-connector-python
# 可视化相关(如果直接在Python里生成网页用Flask)
pip install flask pyecharts
注:Pyecharts是Python封装的ECharts库,可以方便地生成HTML页面,但如果你想把大屏做成更自由的布局,我建议直接写原生HTML + ECharts,后面大屏部分会讲。
数据存储方面,可以用MySQL 5.7/8.0,也可以只用SQLite。课程设计用MySQL加分,毕设也推荐MySQL;如果只是想尽快跑通看效果,SQLite零配置更方便。MySQL需要自己建表,SQLite直接Pandas一行就能写入。
2.3 开发工具配置
开发工具建议用PyCharm,社区版就够用。核心调试配置只有一个:
- 菜单栏 File → Settings → Project: 工程名 → Python Interpreter,选择刚才创建虚拟环境的python.exe路径
- 菜单栏 Run → Edit Configurations,确认脚本路径和参数无误
这里有个调试技巧:在入口代码 if __name__ == "__main__": 里打断点,复用右侧计算器模式的替换,F8向下执行、F9跳到下一断点,比对Content的数据和你预期是否一致。
用VSCode的朋友也可以,但记得启动VSCode时用终端激活环境再输code .,否则解释器会选错。
3. 数据从哪来:爬虫模块设计与实现
爬虫是整个项目的地基,地基不牢后面全白干。这一段我按开发思路来说,你可别真的去爬那些需要登录才能看的平台,正常爬公开静态数据的站点就够了,否则你时间全浪费在逆向JS和验证码上。
3.1 爬虫设计思路
这里要规划清楚爬哪些字段,最终存什么粒度。我建议字段设计成:
- 岗位名称
- 公司名称
- 所在城市
- 薪资文本(如“1-1.5万/月”)
- 学历要求
- 工作经验
- 技能标签列表
- 岗位描述摘要
- 发布日期
需要注意,不同网站的这个模块结构完全不同,网上代码不能直接套用。拿到一个网站,先分析它的数据结构。
3.2 拿到数据接口的方法
这里说的“接口”有正路子:
- 打开目标招聘网站,按Ctrl+Shift+I打开开发者工具
- 切换到“网络(Network)”面板,刷新页面
- 在筛选器里选“XHR”类型,再滑动页面加载更多
- 拉到“响应(Response)”里看JSON数据结构
绝大多数防守不严的网站,岗位数据在Ajax请求里就能直接拿到JSON,比去解析HTML方便太多了。JSON里字段名都是英文,对应到中文含义记下来,就得到了字段对照表。
json复制{
"jobName": "Python开发工程师",
"companyName": "某科技有限公司",
"city": "深圳",
"salary": "1.5-2.5万/月",
"eduLevel": "本科",
"workYear": "3-5年",
"skillTags": ["Python", "Django", "Redis"]
}
拿到接口后,构造请求时要注意Headers里的User-Agent(浏览器标识)和Referer(来源页),尽量不要直接暴露脚本身份,否则很容易被临时封掉。
3.3 清洗实现与字段映射
源数据的字段经常是这个样子:岗位名称带着行业缩写、公司名称后面带“- B轮”这种融资阶段字段混在一起、城市有的叫“深圳”有的叫“深圳-南山区”。抓到的原样入库会把你后续分析行搞乱的。
所以清洗规则就两条:能合并的合并,能规范化的规范化。
- 学校去掉空格、去换行符
- 城市统一取市级名称,包含“区”的做截断
- 薪资文本里的“万/月”“K·15薪”等做正则提取
- 工作经验的“不限”映射为“经验不限”
清洗完的中间代码就长这样:
python复制import re
import pandas as pd
def clean_salary(salary_text):
# "1.5-2.5万/月" -> (1.5, 2.5)
match = re.findall(r'(\d+\.?\d*)', salary_text)
if len(match) == 2:
return float(match[0]), float(match[1])
return None, None
def clean_city(city_text):
# "深圳-南山区" -> "深圳"
if '-' in city_text:
return city_text.split('-')[0]
return city_text
注意:薪资单位有“万/月”和“K/月”两种,不能统一按数值处理。先把单位一并提取出来,做统一换算成“月薪万元”方便后面的画图直观。
整个清洗过程我在项目里单独放到 data_clean.py,避免主程序里代码太长。后面调试也好定位问题在哪一步。
4. 数据入库与统计分析:Pandas的正确打开方式
爬下来、洗干净的数据,接下来要么存MySQL,要么直接进Pandas分析。我的做法是:先入库,再读取分析。这样即便后续分析代码写错了,重新读库就行,不用重新爬。
4.1 MySQL建表与写入
建表语句根据字段规划来。注意几点:主键用自增ID,薪资下限和薪资上限单独存数字字段(数值型才能做聚合计算),技能标签单独一张表(多对多)或者用JSON类型。课程设计可以偷懒一起存入文本字段。
sql复制CREATE TABLE job_info (
id INT AUTO_INCREMENT PRIMARY KEY,
job_name VARCHAR(100),
company_name VARCHAR(100),
city VARCHAR(50),
salary_min DECIMAL(5,2),
salary_max DECIMAL(5,2),
edu_level VARCHAR(20),
work_year VARCHAR(20),
skills TEXT,
publish_date DATE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
写入用Pandas的to_sql搭配SQLAlchemy,比手动拼接insert语句快非常多。
python复制from sqlalchemy import create_engine
import pymysql
engine = create_engine("mysql+pymysql://root:123456@localhost:3306/recruit?charset=utf8mb4")
df.to_sql(name='job_info', con=engine, if_exists='replace', index=False)
4.2 分析维度设计:六张图表怎么选
为了让可视化大屏内容丰富,分析维度至少要做六类,这也是我项目里最终呈现的六块:
- 岗位需求TOP10:按岗位名称分组,统计数量
- 城市需求分布:按城市分组,统计岗位数,重点城市做成柱状图
- 薪资与学历交叉分析:按学历分组,计算平均薪资上限和下限
- 经验要求分布:按工作经验字段分组,统计各区间岗位数
- 热门技能词频TOP15:把所有岗位的技能标签拆词,统计频次
- 薪资区间分布:连续数值变量的分布展示(直方图/箱线图)
这里核心的逻辑是“交叉分析”。单看城市需求哪个多意义有限,得看“学历门槛”和“薪资中位数”,体现岗位质量。
4.3 聚合计算代码示例
python复制# 按城市聚合
city_group = df.groupby('city').agg(
job_count=('job_name', 'count'),
avg_salary_max=('salary_max', 'mean'),
avg_salary_min=('salary_min', 'mean')
).reset_index().sort_values('job_count', ascending=False)
# 按学历聚合
edu_group = df.groupby('edu_level').agg(
job_count=('job_name', 'count'),
avg_salary=('salary_min', 'mean')
).reset_index()
做完分组后,每组统计结果转字典,最后用json.dumps生成前端要的数据格式。前端的图表数据准备在 analysis.py 里一步到位。
5. 可视化大屏:比预期好用的ECharts布局方案
可视化大屏是答辩的视觉核心。这里提醒一点:不要用截图贴图,直接打开HTML页面演示,大屏的自动刷新和数据联动才有冲击力。
5.1 大屏布局设计原则
大屏和普通Dashboard不一样,要遵循三条原则:核心指标居中,趋势数据放左右,次要信息放边角。
我项目里的布局是这样的,参考了最常见的“品”字形结构:
- 顶部:总岗位数、平均薪资、覆盖城市、TOP技能等4个KPI卡片,居中
- 中间左侧:岗位需求TOP10柱状图、热门技能词频条形图(左右排)
- 中间右侧:城市需求地图/柱状图、薪资与学历交叉分析热力图(左右排)
- 中间主视觉区:薪资区间分布玫瑰图(一定放中间,因为最出片)
- 底部:经验要求分布饼图 + 数据更新日志
大屏背景建议深色——深蓝或黑色底,图表用亮色系,视觉效果会好很多,间距调大不显得拥挤。
5.2 用原生HTML + ECharts 手写大屏
这里不推荐Pyecharts去生成大屏,原因是Pyecharts默认布局流程固定,很难自由拖放。手工写ECharts的步骤:
html复制<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
然后把上面分析好的JSON数据,通过Flask接口返回或者直接写成静态JS数据文件。静态方式更适合答辩,打开页面即出数据,不用开两个服务。
javascript复制$.getJSON('/api/city_jobs', function(data) {
var chart = echarts.init(document.getElementById('city_chart'));
chart.setOption({
title: { text: '城市岗位需求TOP10' },
tooltip: {},
xAxis: { type: 'category', data: data.cities },
yAxis: { type: 'value' },
series: [{
name: '岗位数',
type: 'bar',
data: data.counts
}]
});
});
5.3 大屏坐标系和配色参考
深色背景配色我实测比较耐看的是:背景 #0d1b2a,色系使用 #00d4ff 和 #ff6b6b,KA卡数值用金色 #ffd700 提升层次。图表间距通过grid属性调整,大屏屏幕如果比较宽,记得在HTML最外层设置最小宽度和放大比例。
另一个画图细节:柱状图用圆角(itemStyle.borderRadius),而不是默认直角,视觉柔和度高很多。字体统一用 “Microsoft YaHei”。
6. 调试要点与常见问题排查
这一部分复盘我在开发过程中踩过的主要坑,条条都真实有效。这比看十篇报错攻略都有用。
6.1 爬虫被拒的排查策略
爬虫写完第一次跑可能直接报403(禁止访问),不要慌,按顺序排查:
- Headers不完整:检查User-Agent、Referer、Origin是否完整
- 请求频率太快:循环里加time.sleep(1)~2秒,最好设置随机延时
- 数据接口动态签名:若是加了动态token的接口,老老实实解析HTML模板,别硬碰难度
- 本机IP被临时限制:等30分钟后再试,不要反复强行请求
有一种比较好用的策略:分级回退。先用requests请求接口,拿到数据就解析;如果拿不到,抓包看页面HTML里是否包含完整数据,直接从HTML用BeautifulSoup解析。
python复制import time
import random
for page in range(1, 20):
try:
resp = session.get(url, headers=headers, timeout=10)
resp.raise_for_status()
except Exception as e:
print(f"第{page}页请求失败: {e}")
time.sleep(5)
continue
# 解析逻辑
time.sleep(random.uniform(1, 3))
6.2 数据分析结果与预期不符
最典型的情况是:城市分组统计出来“深圳-南山区”和“深圳”被当成两个城市。
排查逻辑很简单:先df.head(10)打印原始文本,再打印清洗后的字段。你肉眼一看就发现清洗规则漏了。所以代码里要写字段清洗断言:
python复制assert df['city'].str.contains('-').sum() == 0, "城市字段仍有未清洗脏数据"
另一个常见问题是薪资平均被字符串搞乱了。如果在画折线图时提示类型错误,确认是否清洗后的列还是object类型,需要pd.to_numeric转换。
6.3 大屏图表不显示的排查顺序
大屏打开后出现空白的情况,我碰到过十几次。排查顺序固定是:
- 未定义echarts实例:打开浏览器F12控制台,看有没有报错
document.getElementById里的id和HTML对不上,大小写敏感- 图表数据格式不对:数据里的值为字符串而非数字,用console.log打印核实
- HTML布局宽高为0:给div设置了百分比高度,但父容器没高度,设成固定高度或flex布局解决
修复完记得点击浏览器刷新键强刷,不要只看文件编辑器里的代码。
6.4 数据库写入报错排查
pymysql.err.OperationalError: (1054, "Unknown column 'xxx' in 'field list'") 这种报错,原因99%是字段名没对上,表里实际字段和df列名不一致。还有个坑是 df.to_sql 的时候中文列名直接建表时报错,这就是为什么我们建表用英文列名,写入时Pandas列名设成英文或者重命名后再写入,一劳永逸。
6.5 环境运行报错汇总
经常有同学拿到项目源码后双击还是跑不起来,这是环境问题,也不是源码问题。常见三种:
- ModuleNotFoundError – 依赖没装齐,按文档requirements.txt或者pip逐个装
- Python版本不匹配 – 项目写的是3.8+,你用3.6跑库都装不上
- 文件路径找不到 – 相对路径的坑,main.py从项目根目录启动,别在子目录里右键运行
这里建议所有代码运行时统一用工作目录定位:
python复制import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
这样后续打包给别人,不管在哪个路径运行都不会报找不到文件。
7. 代码工程结构与其他拓展方向
最后把这个项目的工程目录贴一下,供大家照着整理自己的项目。
code复制recruit_analysis/
│
├── app.py # 入口文件,Flask启动或调用各模块
├── config.py # 公共配置(数据库、路径、Headers)
├── requirements.txt # 依赖清单
│
├── spider/
│ ├── __init__.py
│ ├── fetch_data.py # 请求与解析
│ └── clean_data.py # 数据清洗
│
├── analysis/
│ ├── __init__.py
│ └── aggregate.py # 聚合统计
│
├── web/
│ ├── templates/
│ │ └── dashboard.html # 可视化大屏页面
│ └── static/
│ ├── css/
│ ├── js/
│ └── data/
│ └── chart_data.json # 分析结果数据
│
├── docs/
│ ├── 项目文档.md
│ └── 答辩PPT大纲.md
│
└── data/
├── raw_data.csv # 原始数据存档
└── clean_data.csv # 清洗后数据存档
参考这个结构,你的代码、文档、数据、大屏各归其位,答辩时老师问你的文件在哪里,你能快速给出来。
这个项目做到这一步已经具备完整交付能力了。后续如果想升级,我建议可以扩展两个方向:一是动手写一个简单的岗位推荐功能(基于技能匹配算相似度),二是把大屏加一个数据自动刷新的定时任务(每5分钟拉一次接口并重新聚合),加一个岗位趋势预测的时间序列分析。
实例测试下来,做好数据清洗最后用原生ECharts画大屏的这套流程,个人觉得效率非常高。所有图表都能紧凑联网展示,不用反复截屏贴图和精简文字细节,保守估计可以省出不少时间,也足够在答辩现场撑住场面和提问。整体来说,选对技术栈是捷径,按上面这个执行路线稳扎稳打往下走,最后交出来的东西不仅完整度在线,代码逻辑也能经得起讲解和扩展。
