news 2026/9/7 9:21:58

Python爬虫+数据分析实战:从招聘网站获取就业数据,解析城市薪资与学历要求

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬虫+数据分析实战:从招聘网站获取就业数据,解析城市薪资与学历要求

简介:这是一份基于Python爬虫与数据分析的完整实战项目包,面向想掌握Scrapy框架、数据清洗及可视化全流程的初学者或课程设计者。项目以重庆大学毕业生就业信息网为数据源,覆盖爬虫构建、数据提取、Pandas/NumPy清洗统计,以及Matplotlib绘制饼图、折线图等图表,能够帮助读者快速复现从网络抓取到就业去向分布、薪酬对比、宣讲会热度的分析链路。压缩包共23个文件,以16个Python脚本为主,配套3张可视化结果PNG、依赖说明txt、Scrapy配置cfg及README文档,整体仅116KB,轻量易部署。该资源已被634人学习下载,内容包含完整项目源码、统计脚本和结果图,适合直接参考或二次开发,也可作为毕业设计、数据采集课程的实用范本。

1. 项目背景与技术选型:为什么用Python做就业数据爬取与分析

1.1 毕业生就业数据从哪来、能解决什么问题

每年毕业季,“就业”都是绕不开的话题。但如果你手上没有真实数据,聊薪资、聊行业趋势基本靠感觉。这个项目的出发点很简单:把招聘网站上毕业生相关的岗位数据批量抓下来,用Python做一轮完整的“采集—清洗—分析—可视化”,用数据回答几个最实际的问题——哪些城市对毕业生需求最大、哪些岗位薪资中位数最高、学历和经验要求到底卡在哪一档。

数据源的选取上,我优先考虑的是公开招聘网站的搜索页,比如智联招聘、BOSS直聘这类平台。它们有公开的职位搜索入口,数据量大、字段丰富(岗位名称、公司、薪资、学历要求、工作经验、城市、发布时间等),非常适合做统计。另一个优势是这些网站的前端结构相对稳定,短时期内不会大刀阔斧改版,适合跑一轮完整的爬虫项目。

这个项目的价值在于:它不只是一次爬虫练习,而是一条完整的“数据流水线”。爬虫只占三分之一的工作量,后面还有清洗、归一化、聚合分析和可视化呈现。如果你正在学Python数据分析,或者准备做毕设、求职作品集,这个项目能一次性练到requests、pandas、matplotlib、pyecharts这几个核心库,比零散刷教程有效得多。

1.2 技术栈与选型逻辑:requests + pandas + echarts组合

技术选型不是越新越好,而是越稳越好。我的选择是:

  • requests + BeautifulSoup:数据量不大、不需要登录态的场景,用这两个库最省事。requests处理HTTP请求,BeautifulSoup解析HTML结构。相比Scrapy,少了学习成本和框架配置,适合快速验证想法。
  • pandas:清洗和聚合的绝对主力。DataFrame做筛选、去重、分组统计、透视表,都是几行代码的事。
  • matplotlib + pyecharts:matplotlib适合快速出静态图,适合写论文那种严肃风格;pyecharts生成的是交互式HTML图表,鼠标悬停能看到数值,适合做展示和汇报。
  • SQLite:如果抓上万条数据,直接存CSV后面清洗时会很痛苦。先存进SQLite,用SQL做初步筛选,再导出到pandas做精细处理,流程更可控。

这里有个经验:千万不要一上来就追求分布式爬虫或Scrapy + Redis那种重型架构。个人项目的数据量级通常在几千到几万条,单线程 + 适当延时完全够用。架构太重反而会分散精力,让你陷进“环境配置”而不是“数据分析”本身。

2. 数据采集:从URL分析到字段设计,爬虫的完整拆解

2.1 锁定目标页面结构,设计爬取字段

以智联招聘的搜索页为例,URL里一般包含关键词、城市和页码参数。比如搜索“应届毕业生”岗位,请求参数会带着kw=应届毕业生cityCode。第一步要做的不是盲目写代码,而是打开浏览器开发者工具,进入Network面板,观察页面加载时实际发出了哪些请求

你会发现,职位列表的数据往往是接口直接返回JSON,而不是写死在HTML里。这时候用requests模拟请求拿到JSON,解析效率比BeautifulSoup解析HTML高得多。如果页面是服务端渲染的(HTML里直接有数据),那就用BeautifulSoup定位职位卡片节点。

我实际用的数据结构如下(核心字段):

字段说明举例
job_title岗位名称Java开发工程师
company_name公司名称XX科技有限公司
salary_min薪资下限(K/月)8
salary_max薪资上限(K/月)15
city工作城市北京
education学历要求本科
experience经验要求经验不限
industry所属行业互联网/电子商务
publish_time发布时间2025-06-10

薪资字段我拆成两个数值列来存,后面分析时可以直接算中位数和平均值,比存“8-15K”这种字符串要灵活得多。这一步属于“设计先行”,不要在数据落地之后才想着拆分。

2.2 请求头、翻页逻辑与反爬应对

写爬虫最容易遇到的坑就是请求头不全导致被403拒绝。浏览器在正常访问时会携带User-Agent、Referer、Accept-Language等请求头,用requests裸请求很容易被服务器的风控识别。我的做法是构造一个完整的请求头字典:

headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Referer": "https://www.zhaopin.com/", "Accept": "application/json, text/plain, */*", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", }

翻页逻辑上,多数招聘网站的页码参数是pageIndexpageNum,从1开始递增。但这里有个隐藏坑:页面显示的总页数和实际接口可返回的最大页数是两回事。很多网站为了防止抓取,接口层会把最大可访问页数限制在10页或20页以内。设计爬虫时,应该以“接口实际返回的数据条数”作为循环终止条件,而不是以页面显示的页码数。每抓完一页,加一个1-2秒的随机延时,避免短时间内请求频率过快。

如果遇到需要登录才能查看更多数据的情况,优先考虑两个方向:一是检查是否存在免登录的移动端接口(如m站、APP的H5页面),这类接口往往鉴权较弱;二是给请求加上Cookie(从浏览器复制登录后的Cookie即可)。对于验证码和滑块这种强验证,不建议死磕破解,直接换个数据源更实际。

3. 数据清洗与预处理:分析结果准不准,全看这步

3.1 去重、空值与类型转换

爬下来的数据远远达不到“拿来就能分析”的标准。最常见的三类脏数据是:完全重复的记录、薪资字段缺失或格式混乱、城市字段取名不统一(比如“北京”和“北京市”并存)。清洗这一步的核心代码如下:

import pandas as pd df = pd.read_csv("jobs.csv") # 去除完全重复的记录 df = df.drop_duplicates(subset=["job_title", "company_name", "city", "salary_min", "salary_max"]) # 过滤掉关键字段缺失的行 df = df.dropna(subset=["salary_min", "salary_max", "city"]) # 统一城市名称,去掉'市'后缀 df["city"] = df["city"].str.replace("市", "")

这里要重点提醒一个细节:薪资字段务必先转成数值类型再做运算。如果你的薪资字段是“8-15K”这种字符串,用pandas读取后会被识别为object类型,直接求均值会报错。所以我建议在爬虫阶段就分开存储salary_minsalary_max两个数值列。如果你的原始数据已经是字符串格式,可以用str.extract把数字提取出来:

# 从 "8-15K" 中提取两个数值 df["salary_min"] = df["salary"].str.extract(r"(\d+)-(\d+)")[0].astype(int) df["salary_max"] = df["salary"].str.extract(r"(\d+)-(\d+)")[1].astype(int)

3.2 构造分析字段:把文本变成可统计的维度

原始数据里有“学历要求”“经验要求”这种分类字段,但它们往往是不标准的,比如“本科”、“统招本科”、“本科及以上”同时存在。为了让后续聚合统计有意义,需要做归一化映射

edu_map = { "学历不限": "不限", "中专/中技": "专科及以下", "大专": "专科及以下", "大专及以上": "专科及以下", "本科": "本科", "统招本科": "本科", "本科及以上": "本科", "硕士": "硕士及以上", "硕士及以上": "硕士及以上", } df["edu_level"] = df["education"].map(edu_map)

经验字段的处理同样如此——“经验不限”“应届生”“1年以内”都可以归为“应届/1年内”,“1-3年”归为“1-3年”,“3-5年”归为“3-5年”,“5年以上”归为“5年+”。归一化做完之后,用df.groupby()聚合就非常顺手了。

另外我建议生成一个mid_salary字段(薪资中位数 = (下限+上限)/2),后续画箱线图、算城市平均薪资时可以直接用这个字段,比分别看上下限更直观。这一小步能让后续分析代码简单很多。

4. 数据分析维度与可视化:从图表到结论的完整链路

4.1 四个分析维度:城市、行业、学历、经验

拿到干净数据之后,我确定了四个核心分析维度,每个维度回答一个具体问题:

维度一:城市供需分布。统计每个城市的岗位发布数量,画柱状图,直观看出哪些城市对毕业生的需求量大。通常北上广深杭是TOP5,但二线城市的数据会给你惊喜,比如成都、武汉、南京的毕业生岗位数量逐年增长。

维度二:薪资水平对比。按城市分组计算mid_salary的中位数,用箱线图展示薪资分布。注意这里要用中位数而不是平均值,因为少数高薪岗位会把平均值拉高,中位数更能反映普通毕业生能拿到的薪资水平。

维度三:学历门槛分析。统计各学历层次对应的岗位数量占比,用饼图或环形图展示。结合薪资数据做透视表,看看学历提升到底能带来多少薪资溢价。

维度四:经验要求分布。统计“经验不限”类岗位在所有岗位中的占比,这是毕业生最关心的数据。再结合岗位名称做分词,生成热门岗位词云,看看市场上到底缺什么人。

4.2 可视化图表的选择与实现要点

图表不能为了炫技而做,每一张图都要对应一个具体的结论或洞察。以城市薪资分析为例:

import matplotlib.pyplot as plt # 按城市求薪资中位数 city_salary = df.groupby("city")["mid_salary"].median().sort_values(ascending=False).head(10) plt.figure(figsize=(12, 6)) city_salary.plot(kind="bar", color="#4C72B0") plt.title("各城市毕业生岗位薪资中位数 TOP10 (K/月)") plt.xlabel("城市") plt.ylabel("薪资中位数") plt.xticks(rotation=45) plt.tight_layout() plt.savefig("city_salary.png", dpi=300)

这里有个细节:matplotlib默认字体不包含中文字符,直接画图会出现方块乱码。必须显式指定字体:

plt.rcParams["font.sans-serif"] = ["SimHei"] # 或用 "Microsoft YaHei" plt.rcParams["axes.unicode_minus"] = False # 解决负号显示异常

如果做交互式展示,我强烈推荐pyecharts。它生成的HTML图表自带悬浮提示、图例控制和缩放功能,汇报时打开浏览器直接看效果,比静态图片强得多。比如制作一个城市岗位需求分布的地图热力图,pyecharts的Map类型几行代码就能搞定。做可视化大屏时,pyecharts的TabGrid组件可以把多个图表拼在同一个页面里,滚动切换,效果不输商业BI工具。

5. 从数据到报告:如何把图表转化成可用结论

5.1 数据交叉分析实践

单维度分析只能描述现象,交叉分析才能发现规律。我建议大家至少做两个交叉分析:

交叉分析一:学历 vs 薪资。用pivot_table生成透视表,行是学历等级,列是城市等级(一线/新一线/二线),值是薪资中位数。

pivot = pd.pivot_table( df, values="mid_salary", index="edu_level", columns="city_level", aggfunc="median" ) print(pivot)

这个透视表能同时回答两个问题:一线城市是否在每个学历层都给出更高薪资?学历溢价在不同线城市的表现差异有多大?我实测的结论是:一线城市中“本科 vs 硕士”的薪资差距约12%左右,而在新一线城市差距缩小到8%以下。这类洞察才是数据分析项目的灵魂。

交叉分析二:行业热度 vs 专业背景。如果数据里有行业字段,可以把行业分成互联网、金融、制造业、教育、医疗等类别,再按岗位名称提取关键词,统计不同行业的岗位技能需求频率。比如互联网行业“Java”“Python”“前端”出现频次极高,制造业则是“机械设计”“自动化”频次高。

5.2 把分析结果整理成叙事线

图表做完,最后要写成一篇有逻辑的分析报告。我的报告结构通常是:

  • 先说明数据来源和数据量(增强可信度)
  • 然后是一个全局概览,比如总岗位数、覆盖城市数、平均薪资
  • 接着按“城市—行业—学历—经验”四个维度逐层展开,每个小节给结论再配图表
  • 最后做交叉分析,给出一段有洞察的总结段

这一步很多人会忽略,但它恰恰是面试官或导师最看重的部分——数据本身没有价值,从数据中得出的结论才值钱

6. 常见问题与避坑实录

6.1 爬虫阶段的高频问题

问题1:请求返回403或验证码页面。

排查思路:先检查请求头是否完整,特别是User-Agent和Referer;再检查请求频率,如果已经连续抓取多页,建议暂停30-60秒再继续。If还是不行,检查IP是否被封,最简单的方法是换一个网络环境(比如手机热点)再跑一次。

问题2:翻页抓取后数据量没有增长。

排查思路:很多招聘网站的搜索结果接口,日访问超过一定次数后虽然返回200状态码,但JSON数据里是空数组或错误提示。建议每次请求后打印返回数据的长度,及时发现异常并把该页数据重新抓取一次。

问题3:页面改版后旧选择器失效。

排查思路:爬虫代码里尽量用稳定的属性选择器(如style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 9:21:40

异步任务状态管理实战:从原理到生产环境部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 9:19:15

从SAM到DALLE2:多模态大模型保姆级学习路线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 9:18:49

TVA具身架构驱动的自然语言指令高效解析方法

前沿技术探索:TVA智能体(简称TVA)TVA智能体(亦称“AI智能体视觉”或“TVA视觉智能体”)是依托Transformer架构与“因式智能体”理论构建的通用视觉技术体系。它有机融合深度强化学习(DRL)、卷积…

作者头像 李华
网站建设 2026/9/7 9:16:20

ESP32与ES8311音频编解码芯片驱动详解:I2S对接与播放录音实现

简介:面向Arduino与ESP32平台的音频开发,这套资源整合了ES8311高性能音频编解码器的驱动代码与ESP32-audioI2S音频库,帮助开发者快速实现I2S接口下的音频播放与录制。压缩包共4个文件,包含es8311驱动源码、寄存器定义头文件以及ES…

作者头像 李华
网站建设 2026/9/7 9:15:07

潍坊公墓选购全攻略:2026 最新各大陵园价格明细

潍坊公墓选购全攻略:2026 最新各大陵园价格明细导语随着潍坊殡葬服务需求的逐步释放,潍坊墓地、潍坊公墓的选购信息成为不少家庭关注的民生焦点。由于殡葬行业信息透明度较低,多数家庭首次接触潍坊陵园选购时,普遍面临不知如何挑选…

作者头像 李华