招聘数据清洗与分析:Python结构化处理实战指南
简介本资源是一套完整、可直接运行的招聘网站数据分析与可视化实战项目面向Python初学者及数据科学入门学习者解决从网页数据采集、清洗、分析到多维可视化落地的全流程实践需求适合作为毕业设计、课程设计或期末大作业。压缩包共54个文件涵盖4个核心Python脚本数据获取与清洗、4个Jupyter Notebook含数据分析.ipynb、词云图.ipynb、数据可视化.ipynb等、6张可视化图表PNG/JPG、5个HTML交互页面如工作经验饼图、学历分布图、以及ECharts大屏展示模块和原始CSV招聘数据集辅以JS、CSS等前端支持文件整体大小仅6.68MB轻量易部署。已有573人学习下载项目经教师指导并获高分评价代码纯手写、注释清晰、模块解耦明确包含前程无忧等主流招聘平台数据采集逻辑、中文分词与词云生成、学历/经验/薪资等维度统计分析及响应式图表呈现开箱即用小白可快速上手复现完整分析链路。1. 这不是又一个“爬完就跑”的招聘数据项目而是一套可复现、可验证、带完整清洗逻辑的 Python 分析闭环你可能已经下载过几十个标着“招聘网站数据分析”的 Python 项目解压后发现只有三张 CSV 和一个plotly.show()就戛然而止——没有字段校验、没有缺失值归因、没有薪资单位统一、没有岗位名称标准化更别提「工作经验」字段里混着“3-5年”“应届生”“5年以上”“不限”四种表达方式。这个高分毕业设计项目不同它用pandas的str.extract() 正则锚定提取工龄数值区间用replace()构建学历映射字典把“本科”“学士”“Bachelor”全归为bachelor用apply()函数对薪资字段做单位换算K/月 → 万元/年甚至在数据清洗.ipynb里专门写了check_salary_consistency()函数比对原始 CSV 中“月薪”与“年薪”字段的量级差异是否超出 10 倍阈值。它面向的是真实课程设计场景老师会查清洗逻辑是否可追溯、可视化是否带交互控件、结论是否能从原始数据反向推导。适合需要交作业但不想被问“你这个饼图的百分比是怎么算出来的”的本科生也适合想快速搭建招聘行业分析 baseline 的转行者。2. 数据获取与清洗从 raw CSV 到结构化 DataFrame 的三道硬关卡2.1 前程无忧数据抓取逻辑解析不依赖 Selenium纯 requests BeautifulSoup 的稳健方案项目中前程无忧.py并未使用浏览器自动化工具而是通过分析前程无忧 PC 端搜索接口的请求链路定位到真实数据接口/jobs/searchproduct/ajax.json。关键在于构造合法的headers和paramsimport requests import pandas as pd headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Referer: https://www.51job.com/, X-Requested-With: XMLHttpRequest } params { keyword: Python开发, # 搜索关键词 city: 000000, # 全国 page: 1, # 页码 sortField: 0, # 排序字段0相关度1发布时间 pageSize: 50 # 每页条数 } response requests.get(https://www.51job.com/api/job/search, headersheaders, paramsparams) data response.json()注意该接口返回 JSON 格式无需解析 HTML DOM。data[result]即为岗位列表每个 item 包含jobName职位名、companyName公司名、salary薪资字符串、workYear工作经验、degree学历要求等字段。项目已预置测试.py验证接口稳定性——运行后若len(data[result]) 0即表示接口可用避免学生因反爬策略更新导致整套流程中断。2.2 清洗核心用正则与映射解决非结构化字段的语义歧义招聘数据最大的清洗难点是字段高度口语化。项目在数据清洗.ipynb中针对三大字段做了精细化处理2.2.1 工作经验字段标准化从文本描述到数值区间原始workYear字段包含1-3年、3-5年、5年以上、应届毕业生、不限等。项目采用双阶段清洗# 第一阶段提取数字区间 df[work_year_min] df[workYear].str.extract(r(\d)-(\d)年, expandTrue)[0].astype(float) df[work_year_max] df[workYear].str.extract(r(\d)-(\d)年, expandTrue)[1].astype(float) # 第二阶段处理特殊值 def normalize_work_year(x): if pd.isna(x): return (0, 0) elif 应届 in x: return (0, 0) elif 不限 in x: return (0, 20) elif 以上 in x: num float(re.search(r(\d)年以上, x).group(1)) return (num, num 5) # 向上延展5年作为合理上限 else: return (0, 0) df[[work_year_min, work_year_max]] df[workYear].apply( lambda x: pd.Series(normalize_work_year(x)) )逻辑说明str.extract()提取连续数字对apply()处理模糊描述。5年以上被映射为(5, 10)既保留业务含义资深又避免后续计算时出现inf。清洗后新增work_year_avg字段(min max) / 2供后续箱线图和散点图使用。2.2.2 学历字段归一化构建多源同义词映射表degree字段存在中英文混杂、缩写、错别字如“大专”写成“大砖”。项目在中国大学.py中定义了权威映射字典DEGREE_MAP { 本科: bachelor, 学士: bachelor, Bachelor: bachelor, 硕士: master, 研究生: master, Master: master, 博士: phd, Doctor: phd, 大专: associate, 专科: associate, 高中: high_school, 中专: high_school, 不限: unspecified, 无要求: unspecified } df[degree_clean] df[degree].map(DEGREE_MAP).fillna(unspecified)参数说明map()比replace()更安全未匹配项自动转为NaN再用fillna()统一兜底。该映射表已覆盖前程无忧、智联招聘、BOSS 直聘三平台常见学历表述可直接复用于其他招聘数据源。2.2.3 薪资字段单位统一从“8K-15K/月”到“96-180万元/年”薪资字段salary是典型非结构化文本。项目用正则分三步解析原始值解析步骤输出万元/年8K-15K/月提取8,15, 单位K, 周期月(8*12, 15*12) (96, 180)15-25万/年提取15,25, 单位万, 周期年(15, 25)面议匹配关键词设为(-1, -1)(-1, -1)def parse_salary(s): if pd.isna(s) or 面议 in s: return (-1, -1) # 匹配数字单位周期 pattern r(\d\.?\d*)[Kk]?[-~—](\d\.?\d*)[Kk]?[/每](月|年) match re.search(pattern, s) if match: low, high, period float(match.group(1)), float(match.group(2)), match.group(3) if period 月: return (low * 12, high * 12) else: return (low, high) # 单值情况如 15K single_pattern r(\d\.?\d*)[Kk] single_match re.search(single_pattern, s) if single_match: val float(single_match.group(1)) return (val * 12, val * 12) return (-1, -1) df[[salary_min, salary_max]] df[salary].apply( lambda x: pd.Series(parse_salary(x)) )提示salary_min和salary_max为float类型-1表示面议后续可视化中用np.nan替换便于统计。该函数已通过前程无忧.csv中全部 237 条薪资记录测试准确率 99.2%2 条含“美金”需人工标注项目已注明。2.3 清洗质量验证用断言机制确保每一步可审计项目在数据清洗.ipynb末尾加入清洗质量校验模块强制执行三项断言# 断言1工作经验字段必须有 min/max 且 min max assert (df[work_year_min] df[work_year_max]).all(), 工作经验最小值大于最大值 # 断言2学历字段必须在预设枚举内 valid_degrees {bachelor, master, phd, associate, high_school, unspecified} assert df[degree_clean].isin(valid_degrees).all(), 存在未映射的学历值 # 断言3薪资字段 min/max 必须为数值或 -1 assert df[salary_min].apply(lambda x: isinstance(x, (int, float))).all(), salary_min 含非数值类型为什么重要课程设计答辩时老师常问“你如何证明清洗没出错”。这三行断言就是答案——运行失败即暴露问题而非靠肉眼抽查。项目还提供check_data_quality_report()函数输出缺失率、重复率、异常值比例表格可直接粘贴进答辩 PPT。3. 多维可视化实现从静态图表到 ECharts 大屏的渐进式交付3.1 基于 Matplotlib/Seaborn 的探索性分析EDA图表生成逻辑数据分析.ipynb中的图表并非简单调用plt.show()而是封装为可配置函数。以工作经验分布饼图为例如下def plot_work_year_pie(df, output_path工作经验饼图.html): # 按 avg_work_year 分组0-1, 1-3, 3-5, 5-10, 10 bins [0, 1, 3, 5, 10, 20] labels [应届/0年, 1-3年, 3-5年, 5-10年, 10年以上] df[work_year_group] pd.cut(df[work_year_avg], binsbins, labelslabels, rightFalse) # 统计并排序 group_counts df[work_year_group].value_counts().reindex(labels) # 绘制 plt.figure(figsize(10, 8)) wedges, texts, autotexts plt.pie( group_counts, labelslabels, autopct%1.1f%%, startangle90, colorsplt.cm.Set3(range(len(labels))) ) plt.title(招聘岗位工作经验分布, fontsize16, pad20) plt.savefig(output_path.replace(.html, .jpg), dpi300, bbox_inchestight) plt.show() plot_work_year_pie(df)参数说明pd.cut()实现等距分组reindex()强制保持标签顺序避免饼图顺序混乱plt.cm.Set3使用色盲友好配色。生成的工作经验饼图.html实际是 HTML 文件但项目用plt.savefig()保存为 JPG 供答辩展示——这是课程设计常见妥协HTML 交互图在答辩现场易出兼容性问题静态高清图更稳妥。3.2 词云图生成用 jieba 精确分词 自定义停用词表过滤噪声词云图.ipynb不直接对jobName字段做wordcloud.WordCloud()而是先做中文分词清洗import jieba from wordcloud import WordCloud import numpy as np from PIL import Image # 加载自定义停用词表项目含 stopwords_zh.txt with open(stopwords_zh.txt, r, encodingutf-8) as f: stopwords set([line.strip() for line in f]) # 对职位名分词并过滤 def extract_keywords(job_names): words [] for name in job_names: if pd.isna(name): continue seg_list jieba.lcut(name) # 过滤单字、停用词、数字 filtered [w for w in seg_list if len(w) 1 and w not in stopwords and not w.isdigit()] words.extend(filtered) return words keywords extract_keywords(df[jobName].dropna()) word_freq pd.Series(keywords).value_counts() # 生成词云使用蜡笔小新.jpg 作为蒙版 mask np.array(Image.open(蜡笔小新.jpg)) wc WordCloud( font_pathsimhei.ttf, # 中文字体路径 background_colorwhite, maskmask, max_words100, colormapviridis ).generate_from_frequencies(word_freq) wc.to_file(招聘信息词云图.jpg)关键细节jieba.lcut()比cut()更精确stopwords_zh.txt包含“招聘”“急聘”“诚聘”等招聘平台高频噪声词simhei.ttf字体文件已打包在项目根目录避免 Windows 下中文乱码。生成的招聘信息词云图.txt是词频统计文本可直接用于答辩材料中的“关键词分析”章节。3.3 ECharts 大屏集成用 Flask Jinja2 实现本地部署Echarts大屏展示/myEcharts/目录下是一个完整 Web 应用app.pyFlask 后端提供/api/salary_stats等接口返回 JSON 数据templates/index.htmlECharts 初始化代码从/api/动态加载数据static/js/charts.js封装柱状图、折线图、地图组件核心接口示例# app.py app.route(/api/salary_by_city) def salary_by_city(): # 按城市聚合平均薪资取 salary_max 代表市场高位 city_stats df.groupby(city)[salary_max].agg([mean, count]).round(1) city_stats city_stats[city_stats[count] 5] # 过滤样本少的城市 return jsonify({ cities: city_stats.index.tolist(), salaries: city_stats[mean].tolist() })// static/js/charts.js function initSalaryBarChart() { const chart echarts.init(document.getElementById(salary-bar)); $.get(/api/salary_by_city).done(data { const option { tooltip: { trigger: axis }, xAxis: { type: category, data: data.cities }, yAxis: { type: value, name: 平均年薪万元 }, series: [{ type: bar, data: data.salaries, itemStyle: { color: #5470C6 } }] }; chart.setOption(option); }); }部署命令在myEcharts目录下执行pip install flask python app.py访问http://127.0.0.1:5000即可看到响应式大屏。项目已预置requirements.txt含 Flask2.3.3, Jinja23.1.2避免版本冲突。这是课程设计中“加分项”的标准实现——比静态图更能体现工程能力。4. 招聘数据洞察落地从图表到业务结论的三层验证法4.1 薪资-经验散点图识别“薪资溢价”与“经验陷阱”区域数据分析.ipynb中的plot_salary_vs_experience()函数不仅画图更内置业务规则标记def plot_salary_vs_experience(df): plt.figure(figsize(12, 8)) scatter plt.scatter( df[work_year_avg], df[salary_max], cdf[salary_max], cmapYlOrRd, alpha0.6, s30 ) # 标记异常点经验2年但年薪30万可能为虚假高薪 outlier_mask (df[work_year_avg] 2) (df[salary_max] 30) plt.scatter( df[outlier_mask][work_year_avg], df[outlier_mask][salary_max], cred, s80, markerx, label高薪低经验需核实 ) # 标记价值洼地经验5年但年薪15万可能为传统行业或外包岗 value_mask (df[work_year_avg] 5) (df[salary_max] 15) plt.scatter( df[value_mask][work_year_avg], df[value_mask][salary_max], cblue, s80, markero, label高经验低回报潜在机会 ) plt.xlabel(平均工作经验年) plt.ylabel(最高年薪万元) plt.title(工作经验与薪资关系分析) plt.legend() plt.colorbar(scatter, label年薪万元) plt.grid(True, alpha0.3) plt.show()业务解读红色x标记的点需人工核查是否为“实习岗标正式岗薪资”蓝色o标记的点可进一步分析其所属行业如df[value_mask][industry]若集中于“制造业”“传统零售”则结论为“IT 技能在非科技行业存在估值折价”。这种带业务语义的可视化远超课程设计基础要求。4.2 福利词云与岗位词云对比发现企业宣传话术与实际需求的偏差项目提供两个词云文件福利词云图.txt来自welfare字段和招聘信息词云图.txt来自jobName。对比二者高频词可得关键洞察福利词云 Top5岗位词云 Top5业务启示弹性工作Python企业强调灵活性但技术栈明确要求 Python —— 符合五险一金开发基础保障全覆盖岗位属性清晰 —— 符合年度旅游后端福利具象技术方向聚焦 —— 符合股票期权Java福利指向长期激励但技术栈为成熟语言 ——警惕期权兑现条件苛刻免费三餐算法福利侧重生活保障但岗位要求高门槛算法能力 ——需评估团队技术氛围操作建议在词云图.ipynb中运行compare_wordclouds(福利词云图.txt, 招聘信息词云图.txt)函数自动计算 Jaccard 相似度。若相似度 0.15说明企业宣传与岗位实质脱节此结论可写入课程设计报告的“风险提示”章节。4.3 城市-学历交叉热力图定位区域人才结构断层数据可视化.ipynb中的热力图代码使用seaborn.heatmap()但关键在数据聚合逻辑# 构建交叉表行城市列学历值该城市该学历岗位数 pivot_table pd.crosstab( df[city], df[degree_clean], valuesdf[jobName], aggfunccount, marginsTrue # 添加总计行/列 ) # 计算占比去除总计行/列后 pivot_pct pivot_table.iloc[:-1, :-1].div(pivot_table.iloc[:-1, :-1].sum(axis1), axis0) * 100 # 绘制热力图 plt.figure(figsize(10, 8)) sns.heatmap( pivot_pct, annotTrue, fmt.1f, cmapBlues, cbar_kws{label: 该城市该学历岗位占比%} ) plt.title(各城市学历需求分布热力图) plt.show()实战技巧marginsTrue生成总计行列iloc[:-1, :-1]切片排除避免总计干扰热力图颜色梯度。当发现某城市master占比超 40%如杭州而bachelor不足 20%可推论“该城市偏好高学历人才初级岗竞争更激烈”此结论直接指导求职策略——若学历为本科可优先投递bachelor占比 35% 的城市如成都、西安。本文还有配套的精品资源点击获取