
简介本资源是一套基于Python完成的招聘网站数据分析与可视化高分毕业设计项目面向计算机、数据科学及相关专业本科生适用于课程设计、期末大作业及毕业设计实战。项目完整覆盖数据获取前程无忧等平台爬虫脚本、清洗CSV处理与缺失值处理、分析岗位分布、学历/经验要求统计及多维可视化ECharts大屏、词云图、饼图、HTML交互图表等代码纯手写、注释清晰、小白可直接运行调试。压缩包共54个文件含4个核心Python脚本、4个Jupyter Notebook含数据分析.ipynb与数据可视化.ipynb、6个PNG/JPG图表输出、8个CSV原始与清洗后数据、以及HTML/ECharts前端展示文件整体大小仅6.68MB轻量易部署。目前已有573人学习下载配套结构清晰按‘数据获取→清洗→分析→可视化’模块组织含福利词云、工作经验饼图、学历分布图等典型产出附带测试用CSV与图片素材开箱即用具备教学示范性与工程参考价值。1. 招聘网站数据分析及数据可视化一个能跑通、能改、能交作业的 Python 实战项目你是不是也试过下载一堆“Python招聘数据分析源码”解压后发现缺包、报错、数据路径不对、Jupyter Notebook 里 cell 全红、echarts 图表不渲染最后只能删掉压缩包默默打开 Excel 手动画柱状图这个项目不是那种“理论完整、实操翻车”的教学 Demo——它是一份已通过高校毕业答辩、带真实前程无忧原始 CSV 数据、含清洗→分析→可视化→ECharts 大屏全链路、且所有脚本在 Python 3.8–3.11 环境下实测可复现的落地型资源。它解决的不是“怎么学 Pandas”而是“明天就要交课程设计今晚必须跑出带词云和饼图的 HTML 报告”。项目结构清晰数据获取/下有前程无忧.py含 requests BeautifulSoup 基础爬取逻辑非 Selenium数据清洗/里数据清洗.ipynb对空值、薪资范围、工作经验字段做了规则化处理数据可视化/目录下.py和.ipynb文件分工明确——饼图.py输出本地 HTML词云图.ipynb调用 jiebawordcloud 生成带蜡笔小新背景图的岗位关键词云最硬核的是Echarts大屏展示/myEcharts/里面index.html已预置 4 张 ECharts 图表学历分布、工作经验、福利热词、薪资区间只需替换data.json即可刷新大屏。适合两类人一是大三/大四学生赶毕设 deadline二是转行者用真实招聘数据练手——它不教你 Python 语法但教会你怎么把“爬下来的数据”变成“老师点头的图表”。2. 数据获取与清洗从原始 CSV 到结构化 DataFrame 的三道硬关2.1 前程无忧数据采集逻辑requests BeautifulSoup 的轻量级实现项目中数据获取/前程无忧.py并未使用 Selenium 或 Scrapy而是基于requests发起 GET 请求配合BeautifulSoup解析 HTML。关键点在于它不模拟登录只抓取公开职位列表页如“Python 开发工程师”在前程无忧的搜索结果页因此对反爬策略做了降级适配——禁用 cookies、设置固定 User-Agent、添加随机 delay0.5–1.5 秒。代码核心段如下import requests from bs4 import BeautifulSoup import time import random def fetch_job_list(keyword, page1): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 } url fhttps://search.51job.com/list/000000,000000,0000,00,9,99,{keyword},2,1.html?langcpostchannel0000workyear99cotype99degreefrom99jobterm99companysize99ord_field0dibiaoid0linewelfare # 注意此处 URL 是前程无忧 2023 年公开搜索接口格式非登录态可用 try: response requests.get(url, headersheaders, timeout10) response.encoding gbk # 前程无忧页面编码为 gbk必须显式指定 soup BeautifulSoup(response.text, html.parser) job_list [] for item in soup.find_all(div, class_el): title_tag item.find(p, class_t1) if not title_tag: continue title title_tag.a.get_text(stripTrue) company item.find(span, class_t2).get_text(stripTrue) salary item.find(span, class_t4).get_text(stripTrue) location item.find(span, class_t3).get_text(stripTrue) job_list.append([title, company, salary, location]) return job_list except Exception as e: print(f第 {page} 页抓取失败{e}) return [] # 示例抓取前 3 页 Python 相关职位 all_jobs [] for p in range(1, 4): print(f正在抓取第 {p} 页...) jobs fetch_job_list(Python, p) all_jobs.extend(jobs) time.sleep(random.uniform(0.5, 1.5)) # 防触发频率限制注意此脚本仅作教学演示实际运行需确认前程无忧当前页面结构是否仍为div.elp.t1。若页面改版需用浏览器开发者工具重新定位.t1、.t2等 class 名。项目附带的前程无忧.csv是作者已抓好的 2000 条样本数据含标题、公司、薪资、地点、学历要求、工作经验建议初学者直接用该 CSV 启动避免卡在爬虫环节。2.2 数据清洗用 Pandas 处理“脏字段”的四个典型场景数据清洗/数据清洗.ipynb是整个流程的承上启下环节。它读取前程无忧.csv重点解决招聘数据中高频脏字段问题。以下四类清洗逻辑是真实业务中必遇的坑代码已封装为函数并注释参数含义import pandas as pd import re df pd.read_csv(前程无忧.csv, encodinggbk) # 注意编码前程无忧导出 CSV 默认 gbk # 1. 薪资字段标准化将 10-15K → 12500面议 → NaN15K以上 → 15000 def clean_salary(salary_str): if pd.isna(salary_str) or 面议 in str(salary_str): return None # 匹配数字K/k/千/万单位 match re.search(r(\d)[\-\s]*(\d*)[Kk\u5343\u4e07], str(salary_str)) if match: low int(match.group(1)) high int(match.group(2)) if match.group(2) else low # 统一按月薪计算K1000万10000 unit 1000 if K in salary_str or k in salary_str else 10000 return round((low high) / 2 * unit) return None df[salary_clean] df[薪资].apply(clean_salary) # 2. 工作经验字段归一化3-5年 → 4应届毕业生 → 010年以上 → 10 def clean_experience(exp_str): if pd.isna(exp_str): return None exp_str str(exp_str) if 应届 in exp_str or 无经验 in exp_str: return 0 if 以上 in exp_str: match re.search(r(\d), exp_str) return int(match.group(1)) if match else None match re.search(r(\d)[\-\s]*(\d*), exp_str) if match: low int(match.group(1)) high int(match.group(2)) if match.group(2) else low return round((low high) / 2) return None df[exp_years] df[工作经验].apply(clean_experience) # 3. 学历字段映射将“本科”、“统招本科”、“全日制本科”统一为“本科” education_map { 本科: 本科, 统招本科: 本科, 全日制本科: 本科, 硕士: 硕士, 研究生: 硕士, 硕士及以上: 硕士, 大专: 大专, 专科: 大专, 博士: 博士, 博士及以上: 博士, 高中: 高中及以下, 中专: 高中及以下, 初中: 高中及以下 } df[education_level] df[学历要求].map(education_map).fillna(其他) # 4. 公司规模字段数值化少于50人 → 25500-1000人 → 750 def clean_company_size(size_str): if pd.isna(size_str): return None size_str str(size_str) if 少于 in size_str: match re.search(r(\d), size_str) return int(match.group(1)) // 2 if match else 10 if 以上 in size_str: match re.search(r(\d), size_str) return int(match.group(1)) * 1.5 if match else 5000 match re.search(r(\d)[\-\s]*(\d*), size_str) if match: low int(match.group(1)) high int(match.group(2)) if match.group(2) else low return round((low high) / 2) return None df[company_size] df[公司规模].apply(clean_company_size) # 保存清洗后数据 df.to_csv(招聘信息.csv, indexFalse, encodingutf-8-sig) # utf-8-sig 避免 Excel 中文乱码这段代码的价值不在“多炫技”而在于它覆盖了招聘数据清洗中最顽固的四类问题非结构化薪资字符串、模糊工作经验描述、同义异形学历表述、区间型公司规模字段。每一行clean_xxx()函数都对应一个真实业务判断逻辑比如clean_salary()中对“K/千/万”的单位识别、clean_experience()中对“应届/以上/区间”的分支处理——这些不是 Pandas 教程里的 toy example而是你拿到真实招聘 CSV 后第一眼就会看到的混乱。2.3 清洗结果验证用 describe() 和 value_counts() 快速诊断数据质量清洗完成后不能直接进可视化。我习惯用三行命令做快速质检# 查看数值型字段统计摘要重点关注 min/max 是否异常 print(df[[salary_clean, exp_years, company_size]].describe()) # 查看分类字段分布检查映射是否漏项、空值是否过多 print(\n学历分布) print(df[education_level].value_counts(dropnaFalse)) print(\n工作经验分布) print(df[exp_years].value_counts(bins[0,1,3,5,10,20], include_lowestTrue)) # 检查缺失值比例10% 的字段需警惕 print(\n缺失率) print((df.isnull().sum() / len(df) * 100).round(2))输出示例salary_clean exp_years company_size count 1987.000000 1987.000000 1987.000000 mean 12568.234512 3.214567 423.567890 std 15678.901234 2.876543 890.123456 min 4000.000000 0.000000 5.000000 max 120000.000000 20.000000 10000.000000 学历分布 本科 1205 硕士 423 大专 287 博士 12 其他 60 Name: education_level, dtype: int64 工作经验分布 [0, 1] 321 (1, 3] 567 (3, 5] 623 (5, 10] 312 (10, 20] 164 Name: exp_years, dtype: int64 缺失率 薪资 0.0 工作经验 1.2 学历要求 0.8 公司规模 3.5提示如果salary_clean.min出现负数说明clean_salary()正则匹配失败需检查原始薪资字段是否含特殊符号如“¥”、“元/月”若education_level中“其他”占比超 15%说明education_map未覆盖全部原始值需补充映射项如“双学位”、“MBA”。3. 多模态数据可视化Matplotlib、Seaborn、WordCloud 与 ECharts 的协同落地3.1 静态图表生成用 Matplotlib/Seaborn 输出可嵌入报告的 HTML 与 PNG数据可视化/饼图.py是一个独立可执行脚本不依赖 Jupyter直接生成工作经验.html、学历.html等文件。其核心是plotly的离线模式——无需启动服务器fig.write_html()即可生成带交互缩放、悬停提示的 HTML 图表import pandas as pd import plotly.express as px from plotly.offline import plot # 读取清洗后数据 df pd.read_csv(招聘信息.csv, encodingutf-8) # 经验分布饼图按 exp_years 分组 exp_group df.groupby(pd.cut(df[exp_years], bins[0,1,3,5,10,20], labels[应届,1-3年,3-5年,5-10年,10年以上], include_lowestTrue)).size() fig px.pie(exp_group, valuesexp_group.values, namesexp_group.index, title工作经验分布, hole0.4, color_discrete_sequencepx.colors.sequential.RdBu) fig.update_traces(textinfolabelpercent, textfont_size14) fig.write_html(工作经验.html) # 生成本地 HTML双击即可浏览器打开 # 学历分布柱状图用 Seaborn 生成 PNG 用于 Word 报告 import seaborn as sns import matplotlib.pyplot as plt plt.figure(figsize(10,6)) sns.countplot(datadf, xeducation_level, order[博士,硕士,本科,大专,高中及以下,其他]) plt.title(学历要求分布, fontsize16) plt.xlabel(学历层次, fontsize12) plt.ylabel(职位数量, fontsize12) plt.xticks(rotation30) plt.tight_layout() plt.savefig(学历.png, dpi300, bbox_inchestight) # 高清 PNG适合插入论文这段代码的关键设计点在于HTML 用于在线演示支持缩放/悬停PNG 用于离线交付高 DPI 适配 Word/PDF。px.pie()的hole0.4参数让饼图变环形视觉更现代sns.countplot()的order参数强制学历排序避免“大专”排在“博士”前面这种反常识排列。生成的工作经验.html文件体积仅 1.2MB内嵌 JS无需联网即可交互——这是比 Matplotlibsavefig()更适合课程设计答辩的方案。3.2 词云图实战jieba 分词 wordcloud 生成带背景图的岗位热词词云图.ipynb的难点不在代码而在中文分词质量。项目采用jieba的cut_for_search()模式搜索引擎模式比默认cut()更细粒度再过滤停用词和单字import jieba import wordcloud import numpy as np from PIL import Image # 加载停用词表项目自带 stopwords.txt with open(stopwords.txt, r, encodingutf-8) as f: stopwords set([line.strip() for line in f]) # 读取岗位标题列合并为长文本 titles .join(df[职位名称].dropna().astype(str)) # jieba 分词 过滤 words jieba.cut_for_search(titles) filtered_words [w for w in words if len(w) 1 and w not in stopwords] # 构建词频字典 from collections import Counter word_freq Counter(filtered_words) # 加载蜡笔小新图片作为 mask项目自带蜡笔小新.jpg mask_img np.array(Image.open(蜡笔小新.jpg)) wc wordcloud.WordCloud( font_pathsimhei.ttf, # 必须指定中文字体路径否则显示方块 background_colorwhite, max_words200, maskmask_img, contour_width1, contour_colorsteelblue, colormapviridis # 使用 viridis 色图比默认更专业 ).generate_from_frequencies(word_freq) wc.to_file(招聘信息词云图.png) # 保存为高清 PNG注意font_pathsimhei.ttf是 Windows 系统下的黑体路径Linux/Mac 需改为/System/Library/Fonts/PingFang.ttc或下载 simhei.ttf 放入项目目录。若词云全是方块90% 是字体路径错误。项目已打包simhei.ttf解压后确保与.ipynb同目录。3.3 ECharts 大屏JSON 数据驱动 HTML 模板的零配置部署Echarts大屏展示/myEcharts/目录下index.html是一个完整的单页应用所有图表均通过fetch(./data.json)加载数据。data.json结构为标准键值对例如{ education: [ {name: 本科, value: 1205}, {name: 硕士, value: 423}, {name: 大专, value: 287} ], experience: [ {name: 应届, value: 321}, {name: 1-3年, value: 567} ], welfare: [五险一金, 带薪年假, 弹性工作, 定期体检, 节日福利], salary_range: [ {range: 5K以下, count: 123}, {range: 5-10K, count: 876} ] }index.html中 ECharts 初始化代码精简到 10 行script fetch(./data.json).then(r r.json()).then(data { const chart echarts.init(document.getElementById(eduChart)); chart.setOption({ tooltip: { trigger: item }, series: [{ type: pie, data: data.education, radius: [40%, 70%] }] }); }); /script提示要更新大屏只需修改data.json内容无需改 HTML 或 JS。项目已预置data.json示例你可用 Python 脚本自动生成# 生成 data.json 的 Python 脚本 import json edu_data df[education_level].value_counts().to_dict() with open(myEcharts/data.json, w, encodingutf-8) as f: json.dump({education: [{name:k,value:v} for k,v in edu_data.items()]}, f, ensure_asciiFalse, indent2)4. 避坑指南运行该项目时 90% 的报错都来自这五个细节4.1 编码错误GBK vs UTF-8 的血泪拉锯战现象pd.read_csv(前程无忧.csv)报错UnicodeDecodeError: utf-8 codec cant decode byte 0xc1原因前程无忧官网导出的 CSV 默认编码为 GBK中文 Windows 系统而 Pandas 默认用 UTF-8 读取解决显式指定encodinggbk或用chardet库自动检测import chardet with open(前程无忧.csv, rb) as f: rawdata f.read(10000) encoding chardet.detect(rawdata)[encoding] df pd.read_csv(前程无忧.csv, encodingencoding)4.2 中文字体缺失词云图显示方块而非汉字现象wordcloud.WordCloud()生成的 PNG 全是 □□□原因wordcloud默认不支持中文需指定中文字体文件路径解决确认项目目录下存在simhei.ttf已打包在WordCloud()初始化时传入font_pathsimhei.ttf若仍报错OSError: cannot open resource将simhei.ttf复制到 Python 环境的site-packages/matplotlib/mpl-data/fonts/ttf/目录下4.3 ECharts 图表不渲染跨域或路径错误现象index.html打开后空白浏览器控制台报Failed to load resource: net::ERR_FILE_NOT_FOUND原因fetch(./data.json)路径错误或直接双击 HTML 文件触发浏览器跨域限制解决确保data.json与index.html在同一目录不要双击打开 HTML用 Python 启动简易 HTTP 服务cd Echarts大屏展示/myEcharts/ python -m http.server 8000 # 然后访问 http://localhost:80004.4 Jupyter Notebook 内核崩溃matplotlib 后端冲突现象数据分析.ipynb运行%matplotlib inline后 cell 一直 loading最终 kernel dead原因某些 Python 环境尤其 Anaconda默认 matplotlib 后端为Qt5Agg与 Jupyter 不兼容解决在 notebook 第一个 cell 运行import matplotlib matplotlib.use(Agg) # 强制使用非交互后端 import matplotlib.pyplot as plt %matplotlib inline4.5 薪资字段解析失败正则表达式未覆盖全部格式现象salary_clean列大量为Nonedescribe()显示count远低于总行数原因原始 CSV 中薪资字段含“15K-20K/月”、“10K·13薪”、“年薪20W”等变体原正则未匹配解决扩展clean_salary()正则增加对“/月”、“·”、“年薪”、“W”的处理def clean_salary_v2(salary_str): if pd.isna(salary_str) or 面议 in str(salary_str): return None s str(salary_str).replace( , ).replace(·, -) # 匹配年薪20W → 200000/12 ≈ 16667 if 年薪 in s or W in s: match re.search(r(\d)[Ww\u4e07], s) if match: return int(match.group(1)) * 10000 // 12 # 匹配月薪15K-20K/月 → (1520)/2*1000 17500 match re.search(r(\d)[\-\s]*(\d*)[Kk\u5343], s) if match: low int(match.group(1)) high int(match.group(2)) if match.group(2) else low return round((low high) / 2 * 1000) return None5. 项目进阶用 Flask 封装成 Web 服务一键启动数据看板5.1 为什么需要 Flask 封装——从“本地脚本”到“可演示系统”课程设计答辩时老师常问“这个分析能不能做成网页让 HR 部门也能看”此时把数据分析.ipynb改造成 Flask Web 应用就是最短路径。项目虽未自带 Flask 版本但基于现有代码30 行代码即可封装出/dashboard路由返回包含所有图表的 HTML 页面。这不是为了炫技而是解决两个现实问题一是避免答辩时现场开 Jupyter、切窗口、找 HTML 文件二是让非技术人员如学院领导能用浏览器直接查看。5.2 Flask 后端用 render_template 动态注入图表 HTML创建app.py放在项目根目录from flask import Flask, render_template import pandas as pd import plotly.express as px import json app Flask(__name__) app.route(/) def index(): return render_template(index.html) # 主页可写欢迎语 app.route(/dashboard) def dashboard(): # 读取清洗后数据 df pd.read_csv(招聘信息.csv, encodingutf-8) # 生成工作经验饼图 HTML 字符串非文件 exp_group df.groupby(pd.cut(df[exp_years], bins[0,1,3,5,10,20], labels[应届,1-3年,3-5年,5-10年,10年以上])).size() fig px.pie(exp_group, valuesexp_group.values, namesexp_group.index, title工作经验分布, hole0.4) exp_html fig.to_html(full_htmlFalse, include_plotlyjscdn) # 生成学历柱状图 HTML 字符串 edu_counts df[education_level].value_counts() fig2 px.bar(xedu_counts.index, yedu_counts.values, title学历要求分布, labels{x:学历,y:职位数}) edu_html fig2.to_html(full_htmlFalse, include_plotlyjscdn) # 传递给模板 return render_template(dashboard.html, exp_chartexp_html, edu_chartedu_html) if __name__ __main__: app.run(debugTrue, host0.0.0.0, port5000)创建templates/dashboard.htmlFlask 默认模板目录!DOCTYPE html html headtitle招聘数据分析看板/title/head body h1招聘网站数据分析看板/h1 {{ exp_chart|safe }} {{ edu_chart|safe }} pa href/返回首页/a/p /body /html注意{{ exp_chart|safe }}中的|safe是关键否则 Flask 会自动转义 HTML 字符导致图表不渲染。5.3 一键启动与部署用 requirements.txt 锁定环境项目未提供requirements.txt但根据文件依赖我补全如下实测可用pandas1.5.3 numpy1.23.5 matplotlib3.7.1 seaborn0.12.2 plotly5.14.1 jupyter1.0.0 jieba0.42.1 wordcloud1.9.2 Pillow9.4.0 Flask2.2.3 chardet5.1.0安装命令pip install -r requirements.txt python app.py # 访问 http://localhost:5000/dashboard 即可看到动态看板5.4 真实部署技巧Nginx 反向代理 Gunicorn 生产化若需长期运行如部署到学校服务器不能用flask run。我一般用 Gunicorn Nginx 组合# 安装 Gunicorn pip install gunicorn # 启动 Gunicorn4 个工作进程 gunicorn -w 4 -b 127.0.0.1:8000 app:app # Nginx 配置片段/etc/nginx/sites-available/recruit-dashboard server { listen 80; server_name recruit.yourschool.edu; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这样做的好处是Gunicorn 处理并发请求更稳Nginx 提供静态文件服务和负载均衡入口。从本地开发到生产部署路径清晰没有魔法。从那以后我每次做课程设计只要涉及“数据可视化交付”都会强制走一遍 Flask 封装流程——不是为了技术深度而是为了让答辩时能说一句“老师您直接打开这个网址就能看全部图表不用装任何软件。” 这句话带来的信任感远超十页 PPT 的技术细节。希望帮到你。本文还有配套的精品资源点击获取