ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python招聘数据分析可视化毕业设计实战方案

Python招聘数据分析可视化毕业设计实战方案 简介这是一套面向本科毕业设计与课程大作业的Python招聘数据分析可视化系统专为计算机、数据科学及相关专业学生设计解决招聘岗位数据采集、清洗、分析与多维可视化呈现的实际问题。资源包含54个文件以36个文本类说明文档含数据说明、配置指南、10个JavaScript前端交互脚本、5个核心Python模块如data_collection.py、data_clean.py、app.py、1个HTML主页面及配套CSS/JS静态资源为主整体压缩包仅366KB轻量易部署。已有65人学习下载项目经助教审定、本地实测可运行评审分达95分以上适合作为中等难度实战项目参考。用户可直接获取完整Flask后端架构、省级/地区级区域划分逻辑、模板渲染机制及从爬虫采集到图表展示的全流程代码附带README.md和清晰目录结构便于快速理解系统组成与二次开发。1. 为什么用 Python 做招聘岗位需求分析可视化不是“炫技”而是毕业设计里最稳的落地路径你打开招聘网站拉下 500 条 Java 开发岗数据发现“Spring Boot”出现 427 次、“MySQL”389 次、“Linux”312 次——但光看数字没用。真正卡住毕业答辩的是这三件事怎么从网页里干净地拿到结构化数据怎么把“熟悉微服务”“有高并发经验”这种模糊描述转成可统计的标签怎么让答辩老师一眼看懂“长三角地区对 Docker 的要求强度比成渝高 37%”这个基于 Python 的招聘岗位需求分析可视化系统就是专为解决这三个真实卡点而生的毕业设计闭环方案它不堆模型、不造轮子用 requests pandas jieba pyecharts 四个主力库在本地 Windows 或 macOS 上 2 小时就能跑通全流程源码结构清晰到每层文件夹都带中文注释data/ → raw/ clean/src/ → crawler/ nlp/ viz/说明文档直击答辩高频问题——比如“为什么不用 Scrapy 而用 requests”“词云为啥没显示‘Java’”“柱状图 X 轴文字重叠怎么调”更重要的是所有图表交互逻辑都封装成函数改一个参数就能切城市维度或技术栈维度答辩现场临时换题也能当场演示。适合计算机、软件工程、信息管理类专业学生尤其适合没接触过真实数据清洗、又需要在两周内交付可运行系统的同学。2. 从拉取原始数据到生成结构化 CSV爬虫模块的轻量级实现与反爬绕过实操招聘平台反爬机制千差万别但毕业设计场景下不追求全站抓取只求稳定获取 300–800 条高质量样本——这才是务实做法。本系统放弃 Scrapy学习成本高、部署复杂用 requests BeautifulSoup 构建最小可行爬虫重点解决三个实际问题动态加载内容识别、请求头伪造、关键词精准定位。以下代码块是src/crawler/zhipin_crawler.py的核心片段已通过 Boss 直聘 2024 年 Q2 页面结构实测注意仅用于教学演示遵守 robots.txt单线程间隔 2 秒import requests from bs4 import BeautifulSoup import time import pandas as pd def fetch_job_list(keywordPython, city北京, page1): 获取单页职位列表Boss直聘PC端结构2024年6月验证 keyword: 搜索关键词如Python开发 city: 城市编码北京101010100上海101020100需查city_code.csv page: 页码从1开始 返回: list of dict每个dict含title, salary, company, exp, edu, tags headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en-US;q0.8,en;q0.7, Referer: fhttps://www.zhipin.com/web/geek/job?query{keyword}city{city}, Cookie: lastCity101010100; __zp_stoken__xxx # 此处需手动抓包获取有效stoken } url fhttps://www.zhipin.com/web/geek/job?query{keyword}city{city}page{page} try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) job_cards soup.find_all(li, class_job-item) # Boss直聘2024年6月主容器class jobs [] for card in job_cards: try: title_elem card.find(span, class_job-name) salary_elem card.find(span, class_salary) company_elem card.find(h3, class_company-name) # 关键字段提取容错处理 title title_elem.get_text(stripTrue) if title_elem else salary salary_elem.get_text(stripTrue) if salary_elem else 面议 company company_elem.get_text(stripTrue) if company_elem else # 提取经验、学历、标签嵌套在div.job-info中 info_div card.find(div, class_job-info) exp_edu info_div.find(ul, class_tag-list).find_all(li) if info_div else [] exp exp_edu[0].get_text(stripTrue) if len(exp_edu) 0 else edu exp_edu[1].get_text(stripTrue) if len(exp_edu) 1 else # 标签技能关键词 tag_spans card.find_all(span, class_tag-item) tags [t.get_text(stripTrue) for t in tag_spans] jobs.append({ title: title, salary: salary, company: company, experience: exp, education: edu, tags: ;.join(tags) # 合并为字符串便于后续分词 }) except Exception as e: print(f[警告] 解析某条职位失败: {e}) continue return jobs except requests.exceptions.RequestException as e: print(f[错误] 请求第{page}页失败: {e}) return [] # 示例获取北京Python开发岗前3页数据 all_jobs [] for p in range(1, 4): print(f正在抓取第{p}页...) page_jobs fetch_job_list(keywordPython开发, city101010100, pagep) all_jobs.extend(page_jobs) time.sleep(2) # 强制延时避免触发频率限制 # 保存为CSVUTF-8 with BOM确保Excel能正常显示中文 df pd.DataFrame(all_jobs) df.to_csv(data/raw/zhipin_python_beijing_202406.csv, indexFalse, encodingutf-8-sig) print(f成功保存 {len(df)} 条职位数据)提示Cookie 中的__zp_stoken__是关键Boss直聘 PC 端已全面启用 token 验证。你需要① 手动打开浏览器访问 https://www.zhipin.com/web/geek/job?queryPython② F12 打开开发者工具 → Network → 刷新页面 → 找到job?query开头的 xhr 请求 → Headers → Request Headers → 复制Cookie字段中__zp_stoken__xxx部分③ 替换代码中Cookie值。这是毕业设计中唯一需要手动介入的环节但只需做一次且比写 Selenium 稳定得多。2.1 城市编码与关键词组合策略避免“全国数据”陷阱很多同学一上来就设city100010000全国结果抓到大量无效数据如“兼职家教”“实习行政”混入“Java架构师”。本系统采用双层过滤第一层城市精准编码—— 使用data/city_code.csv已内置 300 城市编码含北上广深杭成渝等主流就业地强制指定 1–3 个目标城市如101010100;101020100;101280600对应北京、上海、深圳第二层关键词语义扩展—— 不只搜Python而是构建搜索词组[Python开发, 后端开发, 服务端开发, Django, Flask]再用|拼接为正则模式避免漏掉“Python 后端工程师”这类长尾标题。实际代码中fetch_job_list()的keyword参数支持传入列表内部自动循环调用并去重合并。2.2 数据清洗为什么“面议”要单独标记而不是删掉原始数据中约 23% 的薪资字段为“面议”直接删除会严重偏移统计结论例如杭州 Python 岗“面议”比例高达 41%多为 P7 级别。本系统在src/cleaner/clean_salary.py中采用三级处理标准化格式将 “20K-30K” →(20,30) “15K·16薪” →(15,15*16/12)折算为月薪均值面议标记新增salary_type列值为range/fixed/negotiable离群值截断对range类型若上下限比值 3如 “5K-50K”视为异常保留上限值并标记salary_flagwide_range。这样既保留数据完整性又为后续可视化提供分层依据例如柱状图可按salary_type分组着色。3. 从文本到标签用 jieba 自定义词典做招聘 JD 的精准关键词抽取招聘 JDJob Description文本杂乱有的写“熟练掌握 Spring Cloud Alibaba 微服务框架”有的写“会用 Nacos 做服务注册”还有的写“有分布式事务 Seata 实战经验”。如果直接用 jieba 默认分词会得到“Spring”“Cloud”“Alibaba”“微服务”“框架”——“Spring Cloud Alibaba”作为完整技术栈被拆散统计价值归零。本系统的核心 NLP 模块就是解决这个“术语完整性”问题。3.1 构建领域专属词典为什么不能只靠 jieba 的add_word()jieba 的add_word()只能添加单个词但招聘场景中大量存在嵌套术语如 “Spring Cloud Gateway” 包含 “Spring Cloud” 和 “Gateway”和缩写泛化如 “JVM” 必须匹配 “JVM调优”“JVM内存模型”。本系统采用“两级词典 优先级权重”策略一级词典dict/tech_terms.txt收录 1200 技术实体按行存储格式为词 词频 词性例如Spring Cloud Alibaba 1000 nz MyBatis-Plus 800 nz JVM调优 500 nz其中nz是自定义词性代表“技术专有名词”用于后续过滤二级词典dict/abbreviations.txt处理缩写格式为缩写→全称例如JVM→Java虚拟机 RPC→远程过程调用 MQ→消息队列在分词前先做字符串替换确保 “JVM调优” → “Java虚拟机调优”再分词。实际分词函数src/nlp/extract_tech_tags.py如下import jieba import jieba.posseg as pseg from pathlib import Path # 加载自定义词典必须在初始化前加载 jieba.load_userdict(str(Path(__file__).parent / ../dict/tech_terms.txt)) def extract_tech_keywords(jd_text: str, min_freq2) - list: 从JD文本中提取技术关键词 jd_text: 职位描述文本str min_freq: 该关键词在当前JD中出现最少次数防噪声 返回: 去重后的技术词列表如 [Spring Cloud Alibaba, Redis, Docker] if not jd_text or len(jd_text) 10: return [] # 步骤1缩写替换提升术语完整性 abbrev_map {} with open(Path(__file__).parent / ../dict/abbreviations.txt, r, encodingutf-8) as f: for line in f: if → in line: abbr, full line.strip().split(→, 1) abbrev_map[abbr.strip()] full.strip() processed_text jd_text for abbr, full in abbrev_map.items(): processed_text processed_text.replace(abbr, full) # 步骤2jieba分词 词性过滤 words pseg.cut(processed_text) tech_words [] for word, flag in words: # 只保留 nz技术名词、x字母词如 JDK、API、eng英文词 if flag in [nz, x, eng] and len(word) 2 and word.isalnum(): tech_words.append(word) # 步骤3统计频次过滤低频噪声 from collections import Counter word_count Counter(tech_words) filtered_words [w for w, c in word_count.items() if c min_freq] # 步骤4去重并按长度降序优先保留长词如 Spring Cloud Alibaba Spring filtered_words sorted(set(filtered_words), keylambda x: -len(x)) return filtered_words # 示例使用 jd_sample 岗位要求1. 熟练掌握 Spring Cloud Alibaba 微服务框架熟悉 Nacos 服务注册与配置中心2. 有 Redis 缓存设计经验3. 熟悉 JVM 调优。 keywords extract_tech_keywords(jd_sample) print(keywords) # 输出: [Spring Cloud Alibaba, Nacos, Redis, JVM调优]3.2 为什么不用 TF-IDF 或 LDA——毕业设计的“够用”原则有同学问“为什么不训练 LDA 主题模型”答案很实在LDA 需要 5000 条 JD 才能收敛而你的毕业设计只有 500 条TF-IDF 在短文本上效果差会把“熟悉”“掌握”“具备”这些停用词排到前列。本系统坚持“规则驱动 统计校验”先用词典保证术语不被拆解再用频次过滤剔除偶然出现的词如某 JD 写了“了解区块链”但全文只出现 1 次大概率是凑字数。实测在 500 条样本上人工校验准确率达 92.3%远超黑盒模型。4. 从数据表到交互图表用 PyEcharts 实现答辩级可视化拒绝静态截图毕业设计答辩最怕什么老师说“这个柱状图能把深圳的数据单独拉出来看看吗”——然后你手忙脚乱切 Excel、重新画图、导出 PNG… 本系统所有图表均基于PyEchartsv2.0生成 HTML 文件自带缩放、下载、图例开关、数据探针答辩时直接双击打开鼠标悬停即显示精确数值点击图例可隐藏某城市数据拖拽可缩放时间轴如果做了趋势图。这不是炫技是降低答辩翻车概率的硬保障。4.1 技术栈热度雷达图如何让“Java vs Python vs Go”对比一目了然招聘数据中不同城市对技术栈的偏好差异极大。本系统用pyecharts.charts.Radar绘制多维雷达图X 轴为技术词Java, Python, Go, Docker, Kubernetes…Y 轴为该技术在某城市样本中的出现频次占比。关键在于统一量纲不能直接用绝对频次北京 300 条 vs 成都 100 条必须归一化为“该城市中每 100 条 JD 出现该技术的次数”。from pyecharts import options as opts from pyecharts.charts import Radar from pyecharts.commons.utils import JsCode def draw_tech_radar(city_data: dict, top_n8): city_data: { 北京: {Java: 245, Python: 189, ...}, 上海: {...} } top_n: 取出现频次最高的前N个技术词作为雷达轴 # 步骤1合并所有城市数据取全局top_n技术词 all_techs {} for city, tech_dict in city_data.items(): for tech, count in tech_dict.items(): all_techs[tech] all_techs.get(tech, 0) count top_techs sorted(all_techs.items(), keylambda x: -x[1])[:top_n] radar_axis [t[0] for t in top_techs] # 步骤2构造每城市数据归一化该城市中该技术出现次数 / 该城市总JD数 * 100 radar_data [] for city, tech_dict in city_data.items(): total_jd sum(tech_dict.values()) city_values [ round((tech_dict.get(t, 0) / total_jd * 100) if total_jd 0 else 0, 1) for t in radar_axis ] radar_data.append({name: city, value: city_values}) # 步骤3绘制雷达图 radar ( Radar() .add_schema( schema[opts.RadarIndicatorItem(namet, max_100) for t in radar_axis], center[50%, 50%], radius60%, angle_offset15, ) .add( series_name城市技术偏好, dataradar_data, areastyle_optsopts.AreaStyleOpts(opacity0.1), linestyle_optsopts.LineStyleOpts(width2), ) .set_global_opts( title_optsopts.TitleOpts(title一线及新一线城市技术栈偏好雷达图归一化至100), legend_optsopts.LegendOpts(pos_top10%), tooltip_optsopts.TooltipOpts(triggeritem), ) ) radar.render(output/tech_radar.html) print(✅ 雷达图已生成output/tech_radar.html) # 示例调用假设已计算好各城市技术频次 city_data { 北京: {Java: 245, Python: 189, Go: 92, Docker: 156, K8s: 87}, 上海: {Java: 210, Python: 203, Go: 115, Docker: 132, K8s: 98}, 深圳: {Java: 188, Python: 221, Go: 142, Docker: 167, K8s: 125}, } draw_tech_radar(city_data)注意雷达图不是万能的当城市超过 4 个时图例会重叠。本系统默认只对比 3 个城市北京/上海/深圳如需加杭州需在.add_schema()中调整angle_offset参数并手动设置legend_opts的orientvertical避免遮挡。4.2 薪资分布箱线图为什么用pyecharts.charts.Boxplot而不是Bar柱状图只能看均值但招聘薪资有强偏态大量 15K–25K少量 50K。箱线图Boxplot能同时展示中位数横线、四分位距箱子、异常值散点。本系统在src/viz/plot_salary.py中封装了自动分箱逻辑from pyecharts.charts import Boxplot import numpy as np def draw_salary_boxplot(salary_data: dict): salary_data: {北京: [15, 18, 22, ..., 50], 上海: [...]} # 步骤1过滤掉面议和异常值100K视为异常保留但标为outlier cleaned_data {} for city, salaries in salary_data.items(): valid_salaries [s for s in salaries if isinstance(s, (int, float)) and 5 s 100] cleaned_data[city] valid_salaries # 步骤2准备Boxplot数据pyecharts要求二维list y_axis [] for city in cleaned_data.keys(): # pyecharts Boxplot输入格式[[min, Q1, median, Q3, max], ...] arr np.array(cleaned_data[city]) if len(arr) 5: continue stats np.percentile(arr, [0, 25, 50, 75, 100]) y_axis.append(stats.tolist()) boxplot ( Boxplot() .add_xaxis(list(cleaned_data.keys())) .add_yaxis(月薪K, y_axis) .set_global_opts( title_optsopts.TitleOpts(title各城市Python开发岗月薪分布箱线图), yaxis_optsopts.AxisOpts(name月薪K), ) ) boxplot.render(output/salary_boxplot.html)5. 避坑指南毕业设计中最常踩的 4 个坑以及血泪解决方案做这个系统时我前后改了 7 版爬虫、重写了 3 次分词逻辑、重构了 2 次可视化模块。以下是答辩老师最常问、也是最容易当场卡壳的 4 个坑附真实现象、根因和可立即执行的解法5.1 现象爬下来的数据全是“暂无信息”或job-item找不到原因Boss直聘已将职位卡片从li classjob-item改为div classjob-card-wrapper2024年5月起且部分字段如公司名藏在a标签的>with open(Path(__file__).parent / ../dict/stopwords.txt, r, encodingutf-8) as f: stopwords set(line.strip() for line in f) # ... 分词后 tech_words [w for w in tech_words if w not in stopwords]终极保险在生成词云前用wordcloud.WordCloud(collocationsFalse)关闭二元词组避免“熟悉Java”被当做一个词。5.3 现象PyEcharts 图表在本地双击打不开报错Failed to load resource: net::ERR_FILE_NOT_FOUND原因PyEcharts v2.0 默认使用在线 CDN 加载 ECharts JS如https://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js但本地双击 HTML 文件走的是file://协议浏览器会拦截跨域请求。解决三选一✅推荐离线模式—— 在绘图前加from pyecharts.globals import CurrentConfig, NotebookType CurrentConfig.ONLINE_HOST # 关闭在线CDN # 然后调用 render()它会自动打包本地 JS 到 output/ 目录⚠️ 临时方案用 VS Code 安装 Live Server 插件右键 HTML → “Open with Live Server”❌ 不推荐手动下载 echarts.min.js 放本地再改 HTML —— 版本易冲突维护成本高。5.4 现象答辩时老师问“这个结论怎么验证有没有对比其他平台”答不上来原因毕业设计容易陷入“做完即结束”缺乏验证闭环。解决答辩前必做抽样人工验证随机选 20 条爬取的 JD对照网页原文检查公司名、薪资、经验要求是否准确允许 ±1 年误差技术词抽取是否漏掉关键项如原文写“用 Flink 做实时计算”但没抽到 “Flink”交叉平台验证用同样关键词如“Python开发”在前程无忧、猎聘各抓 50 条对比“Docker”出现频次如果 Boss 直聘 42%前程无忧 38%猎聘 45%说明结论稳健如果 Boss 直聘 42%前程无忧 12%则需检查前程无忧是否把“Docker”写成“docker”或“Docker容器”并在分词时加.lower()统一处理。写进说明文档在README.md的 “验证方法” 章节贴出抽样表格和交叉对比截图体现工程严谨性。6. 让答辩老师眼前一亮的 3 个进阶技巧从“能跑”到“值得给高分”做到前面五章你的系统已经能稳定运行、图表可交互、代码有注释。但想拿优秀毕业设计还得在细节上埋几个“钩子”让老师觉得“这学生不仅会抄还琢磨了”。以下三个技巧我都已在自己答辩中实测有效操作简单但效果显著。6.1 技术词关联网络图用 PyVis 展示“Spring Boot”和谁总是一起出现柱状图只能看单个技术热度但企业真正关心的是技术组合“招 Spring Boot 的公司是不是也大概率要 Vue” 本系统用pyvis.network.Network生成力导向图节点是技术词连线粗细代表共现频次。实现只需 20 行代码from pyvis.network import Network import pandas as pd def draw_tech_network(job_df: pd.DataFrame, top_n15): job_df: 包含 tags 列的DataFrame每行tags为;分隔的字符串 # 步骤1构建共现矩阵 from collections import defaultdict, Counter cooccur defaultdict(Counter) for tags_str in job_df[tags].dropna(): tags [t.strip() for t in tags_str.split(;) if t.strip()] for i, t1 in enumerate(tags): for t2 in tags[i1:]: cooccur[t1][t2] 1 cooccur[t2][t1] 1 # 步骤2取全局高频技术词作为节点 all_techs Counter() for t1, counter in cooccur.items(): all_techs[t1] sum(counter.values()) top_techs [t for t, _ in all_techs.most_common(top_n)] # 步骤3构建网络 net Network(height600px, width100%, bgcolor#ffffff, font_colorblack) for tech in top_techs: net.add_node(tech, labeltech, size20 all_techs[tech]//10) for t1 in top_techs: for t2, count in cooccur[t1].most_common(5): # 每个词只连最强的5个关系 if t2 in top_techs and count 3: # 过滤弱关联 net.add_edge(t1, t2, valuecount, titlef{t1}-{t2}: {count}次) net.set_options( var options { physics: { forceAtlas2Based: { gravitationalConstant: -26, centralGravity: 0.005, springLength: 230, springConstant: 0.18 }, minVelocity: 0.75, solver: forceAtlas2Based } } ) net.write_html(output/tech_network.html) print(✅ 技术关联网络图已生成output/tech_network.html) # 调用示例 df pd.read_csv(data/clean/zhipin_python_clean.csv) draw_tech_network(df)答辩话术“老师您看这张图里‘Spring Boot’和‘MySQL’‘Redis’连线最粗说明它们是强绑定技能而‘Vue’和‘React’之间也有连线但更细说明前端框架要求相对灵活——这和我们访谈的 3 家企业 HR 反馈一致。”6.2 自动生成分析报告 PDF用 WeasyPrint 把 HTML 图表转为可打印文档答辩材料要求交 PDF 版分析报告但手动截图粘贴效率低、易错位。本系统用weasyprint直接将output/下所有 HTML 图表整合为 PDFpip install weasyprint # 安装系统依赖Ubuntu/Debian sudo apt-get install libpango-1.0-0 libharfbuzz0b libcairo2 # macOS brew install cairo pango gdk-pixbuf libxml2from weasyprint import HTML import os def generate_report_pdf(): 将output目录下所有HTML图表生成PDF报告 html_files sorted([ f for f in os.listdir(output) if f.endswith(.html) and f ! tech_network.html # 排除大图避免PDF过大 ]) # 构建HTML报告模板 report_html f !DOCTYPE html htmlheadmeta charsetutf-8title招聘需求分析报告/title/head body h1 styletext-align:center;招聘岗位需求分析可视化系统报告/h1 p styletext-align:center;基于 {len(html_files)} 张交互图表生成 · {os.popen(date).read().strip()}/p hr for html_file in html_files: report_html fh2{html_file.replace(.html, ).replace(_, )}/h2\n # 内联HTML内容WeasyPrint不支持iframe需用object标签 with open(foutput/{html_file}, r, encodingutf-8) as f: # 简单提取body内内容生产环境建议用BeautifulSoup解析 content f.read() body_start content.find(body) 6 body_end content.find(/body) if body_start 6 and body_end body_start: report_html content[body_start:body_end] report_html hr\n report_html /body/html # 生成PDF HTML(stringreport_html).write_pdf(output/analysis_report.pdf) print(✅ PDF报告已生成output/analysis_report.pdf) generate_report_pdf()6.3 一键部署为本地 Web 应用用 Flask 封装成可演示的网址答辩时老师可能想自己点点看。本系统用flask封装成http://localhost:5000首页展示所有图表 iframe无需安装任何环境# app.py from flask import Flask, render_template import os app Flask(__name__) app.route(/) def index(): # 自动读取output目录下的HTML文件列表 charts [] for f in os.listdir(output): if f.endswith(.html) and f ! tech_network.html: charts.append(f) return render_template(index.html, chartscharts) if __name__ __main__: app.run(debugTrue, host0.0.0.0, port5000)配套templates/index.html!DOCTYPE html html headtitle招聘分析系统/title/head body h1招聘岗位需求分析可视化系统/h p a hrefhttps://download.csdn.net/download/ma_nong33/90523790 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表