ARTICLE DETAIL

资讯详情

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

98分毕设拆解:Python招聘数据分析可视化系统全链路实战

98分毕设拆解:Python招聘数据分析可视化系统全链路实战 简介这是一套面向计算机、通信、人工智能、自动化等相关专业学生与教师的毕业设计级招聘数据分析可视化系统采用Python语言开发配套完整源码、数据库与文档说明可直接用于毕业设计、期末课程设计或课程大作业也适合基础较好的学习者在此基础上二次修改以扩展功能。压缩包共161个文件约7.11MB以Java后端代码、Vue前端页面、JavaScript脚本、SCSS样式、SVG图标及SQL建表脚本为主另含少量Python脚本、Markdown说明文档与Maven构建配置前后端分离结构清晰便于按模块阅读与调试。目前已有336人学习下载。项目经调试测试可正常运行答辩评审分达98分读者可从中获取完整的数据采集、清洗、统计分析与可视化实现思路以及数据库设计、接口分层与前端图表渲染的参考写法对理解招聘数据业务与全栈开发流程具有较高的借鉴价值。1. 从一份 98 分毕设拆起这套 Python 招聘数据分析可视化系统到底能跑出什么如果你正在为毕业设计发愁或者想找一个能直接跑起来、代码结构清晰、还带数据库和文档说明的完整项目那这套基于 Python 的招聘数据分析可视化系统值得花时间拆一遍。它不是那种只丢几个 py 文件、跑起来满屏报错的“半成品”而是包含数据采集、清洗入库、后端接口、前端可视化展示的完整链路配套数据库脚本和说明文档答辩评审分达到 98 分代码经过调试测试确保可以运行。这套资源解决的核心问题是把招聘网站上散落的岗位信息变成能按城市、薪资、学历、经验等维度筛选和统计的可视化图表。适合计算机、通信、人工智能、自动化等相关专业的同学拿来做毕业设计、期末课程设计或课程大作业也适合刚入门 Python 想找一个有前后端、有数据库的真实项目练手的人。基础能力强的可以在它基础上改字段、换数据源、加图表改成自己的题目。我拿到这份源码后第一件事不是急着跑而是先看目录结构和依赖关系因为招聘数据分析这类项目坑往往不在算法而在环境、编码和数据库连接上。下面按“先看懂它怎么组织再动手跑通最后避开常见翻车点”的顺序把这份资源拆开讲清楚。2. 拆开源码包目录结构、技术栈与数据流走向2.1 从文件清单反推项目分层项目正文里列出的文件很有代表性mvnw.cmd、maven-wrapper.jar说明构建工具用的是 Maven Wrapper意味着你不需要本机预装 Maven用项目自带的包装器就能拉依赖chromedriver.exe直接暴露了数据采集环节用的是 Selenium 驱动 Chrome 抓招聘网站index.html和favicon.ico是前端入口和图标JobServiceImpl.java、JobController.java、AdminServiceImpl.java是典型的 Controller-Service 分层RedisCache.java说明引入了 Redis 做缓存AnalyseApplicationTests.java是 Spring Boot 的测试入口。这里有个容易让人困惑的点标题写的是“基于 Python”但文件清单里全是 Java 类。实际拆下来会发现这类毕设常见做法是 Python 负责数据采集和清洗Java 负责后端接口和可视化服务两边通过数据库或接口对接。所以不要看到 Java 文件就以为下错了包它恰恰说明这是一个前后端分离、多语言协作的完整项目。你如果只想用 Python 部分可以单独把爬虫和数据分析脚本拎出来如果想跑完整系统就需要同时配好 Python 环境和 Java 环境。2.2 技术栈选型与各自职责把技术栈按职责拆开看逻辑就清楚了层级技术/文件职责数据采集Python Selenium chromedriver.exe抓取招聘网站岗位列表、薪资、公司、学历要求数据存储MySQL数据库脚本随包提供存岗位原始数据和分析结果缓存Redis RedisCache.java缓存高频查询结果减少数据库压力后端服务Java Spring Boot Maven Wrapper提供 REST 接口处理筛选、统计、分页前端展示HTML 可视化图表库渲染城市分布、薪资区间、学历要求等图表文档说明文档环境配置、启动步骤、数据库导入说明常见做法是Python 脚本定时或手动跑一次把数据写进 MySQLJava 服务启动后读 MySQL通过接口把聚合结果吐给前端前端用 ECharts 或类似库画图。Redis 在这里不是必须的但加上它能让你在答辩时多讲一个“性能优化”的点比如“相同筛选条件 5 分钟内直接走缓存”。2.3 数据从抓取到图表的完整链路理解数据流后面排错才有方向。整条链路大致是Python 爬虫启动Selenium 驱动 Chrome 打开目标招聘网站按关键词和城市翻页抓取。抓到的原始字段岗位名、公司、薪资文本、城市、学历、经验先落到本地 CSV 或直接写 MySQL 临时表。清洗脚本处理薪资文本比如“15-25K·13薪”要拆成最低 15、最高 25、薪资月数 13城市字段统一成“北京”“上海”这种标准名。清洗后的数据写入正式业务表Java 后端通过 MyBatis 或 JPA 读取。前端请求接口拿到按城市分组计数、按薪资区间分组计数等结果渲染成柱状图、饼图、地图。这条链路里最容易断的是第 2 步和第 3 步网站结构一变Selenium 选择器就失效薪资文本格式一乱清洗规则就漏。所以下面动手部分我会把重点放在环境配置和清洗脚本的写法上。3. 把系统跑起来环境配置、数据库导入与启动顺序3.1 Python 侧环境与依赖安装先确认本机 Python 版本。这类毕设项目通常用 Python 3.8 到 3.10 之间比较稳太新的版本有些库还没跟上。装依赖时不要直接pip install一堆先看项目里有没有requirements.txt有就按它来。# 查看 Python 版本建议 3.8-3.10 python --version # 创建独立虚拟环境避免污染全局包 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS/Linux 激活 source venv/bin/activate # 安装依赖优先用项目自带的 requirements.txt pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple逻辑说明虚拟环境是为了把这份项目的依赖和你本机其他项目隔开避免版本冲突。-i指定国内镜像源是因为 Selenium、pandas 这些包体积不小直连官方源容易超时。参数上如果你机器上 Python 是 3.11 以上遇到numpy或pandas装不上就退回 3.10 的虚拟环境这是血泪经验。Selenium 还需要 Chrome 浏览器和对应版本的chromedriver.exe。项目包里已经带了chromedriver.exe但你要确认它的版本和你本机 Chrome 大版本一致。不一致的话Selenium 启动时会直接报SessionNotCreatedException。查看 Chrome 版本在地址栏输入chrome://version然后去驱动仓库下对应版本替换即可。3.2 MySQL 数据库导入与连接配置数据库是这套系统的核心导入不成功后面全白搭。常见做法是先用 Navicat 或命令行建库再执行项目里的.sql文件。# 登录 MySQL mysql -u root -p # 创建数据库字符集用 utf8mb4否则中文岗位名会乱码 CREATE DATABASE job_analyse DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 退出后导入 sql 文件 mysql -u root -p job_analyse job_analyse.sql逻辑说明字符集必须用utf8mb4不能用utf8因为招聘数据里可能出现生僻字或特殊符号utf8存不下会报错或变问号。导入完成后去 Java 项目的配置文件里改数据库连接通常是application.yml或application.propertiesspring: datasource: url: jdbc:mysql://localhost:3306/job_analyse?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0参数说明serverTimezoneAsia/Shanghai不加的话MySQL 8 以上会报时区错误characterEncodingutf8配合库的utf8mb4使用Redis 如果本机没装可以先注释掉相关配置或把RedisCache的注入改成可选否则启动会报连接拒绝。3.3 启动顺序与验证接口是否通启动顺序错了前端会白屏或接口 404。正确顺序是先确保 MySQL 和 Redis 在跑再启动 Java 后端最后打开前端页面。# Windows 下用项目自带的 Maven Wrapper 启动不需要预装 Maven mvnw.cmd spring-boot:run # macOS/Linux ./mvnw spring-boot:run逻辑说明mvnw会自动下载项目指定版本的 Maven 和依赖第一次跑会比较慢耐心等它拉完。启动成功后控制台会打印端口默认一般是 8080。验证接口是否通可以直接浏览器访问# 测试岗位列表接口看是否返回 JSON http://localhost:8080/job/list?page1size10 # 测试统计接口看城市分布数据 http://localhost:8080/job/cityStats如果返回一串 JSON 数据说明后端和数据库通了。如果报 500先看控制台异常栈大概率是数据库字段对不上或 Redis 没启动。前端index.html如果直接双击打开接口请求会跨域常见做法是把它放到后端静态资源目录或者用 Nginx 起一个静态服务再或者后端加跨域配置。这一步不做图表就是空的。4. 数据采集与清洗Selenium 抓取和薪资字段拆解4.1 Selenium 抓取招聘列表的稳定写法招聘网站的反爬策略不算特别狠但对 Selenium 有基本识别。直接裸奔很容易被跳验证页。常见做法是加一些启动参数降低被识别概率同时控制抓取频率。from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By import time import random options Options() # 禁用自动化提示条减少被识别特征 options.add_argument(--disable-blink-featuresAutomationControlled) # 无头模式调试阶段建议注释掉方便看页面 # options.add_argument(--headless) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) driver webdriver.Chrome(optionsoptions) # 覆盖 navigator.webdriver 属性 driver.execute_script(Object.defineProperty(navigator, webdriver, {get: () undefined})) driver.get(目标招聘网站搜索页URL) time.sleep(random.uniform(2, 4)) # 随机等待模拟人工 jobs [] for item in driver.find_elements(By.CSS_SELECTOR, .job-list-item): try: title item.find_element(By.CSS_SELECTOR, .job-name).text salary item.find_element(By.CSS_SELECTOR, .salary).text city item.find_element(By.CSS_SELECTOR, .city).text jobs.append({title: title, salary: salary, city: city}) except Exception as e: # 单条解析失败不影响整体记录后跳过 print(f解析失败: {e}) continue driver.quit()逻辑说明excludeSwitches和useAutomationExtension是为了去掉 Chrome 启动时的自动化标志execute_script覆盖navigator.webdriver是常见反检测手段。random.uniform(2, 4)是随机等待固定sleep(1)反而容易被识别出机器节奏。选择器.job-list-item只是示例实际要按目标网站 DOM 改改之前先在浏览器 F12 里确认类名。4.2 薪资文本清洗从“15-25K·13薪”到结构化字段薪资字段是招聘数据分析里最脏的格式五花八门“15-25K”“15-25K·13薪”“1.5-2万”“200元/天”“面议”。不拆成数字后面没法做区间统计。import re def parse_salary(salary_text): 返回 (最低薪资, 最高薪资, 薪资月数)单位元/月 解析失败返回 (None, None, None) if not salary_text or 面议 in salary_text: return None, None, None # 提取薪资月数默认 12 months 12 month_match re.search(r·(\d)薪, salary_text) if month_match: months int(month_match.group(1)) # 统一单位万 - 元 text salary_text.replace(万, 0000).replace(K, 000).replace(k, 000) # 匹配 15000-25000 这种区间 match re.search(r(\d)-(\d), text) if match: low int(match.group(1)) high int(match.group(2)) # 处理“1.5-2万”被替换成“1.50000-20000”的异常 if low 1000 and high 10000: low int(low * 10000) return low, high, months # 匹配单个数字如“200元/天” single re.search(r(\d), text) if single: val int(single.group(1)) return val, val, months return None, None, None逻辑说明先处理“面议”直接返回空再提取“·13薪”里的月数然后把“万”和“K”统一替换成数字后缀方便正则匹配。这里有个坑1.5万直接替换会变成1.50000所以加了一个判断当最低值小于 1000 而最高值大于 10000 时把最低值乘 10000 修正。参数上months默认 12遇到 13 薪、14 薪会覆盖后面算年薪时用(lowhigh)/2 * months。清洗完的数据建议先写 CSV 看一眼确认没有明显异常再入库。常见异常是“薪资倒挂”最低值比最高值还大那是正则匹配顺序问题需要调整。4.3 数据入库与去重策略抓取和清洗完成后写 MySQL 时要去重否则重复跑几次数据量翻倍统计结果失真。常见做法是用岗位链接或“公司岗位名城市”做唯一键。-- 建表时加唯一索引 CREATE TABLE job_info ( id INT PRIMARY KEY AUTO_INCREMENT, job_name VARCHAR(255), company VARCHAR(255), city VARCHAR(64), salary_low INT, salary_high INT, salary_months INT, education VARCHAR(32), experience VARCHAR(32), job_url VARCHAR(512), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_job (company, job_name, city) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;入库时用INSERT IGNORE或ON DUPLICATE KEY UPDATE避免重复插入报错中断。import pymysql conn pymysql.connect(hostlocalhost, userroot, password你的密码, databasejob_analyse, charsetutf8mb4) cursor conn.cursor() sql INSERT IGNORE INTO job_info (job_name, company, city, salary_low, salary_high, salary_months, education, experience, job_url) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) for job in jobs: low, high, months parse_salary(job[salary]) cursor.execute(sql, (job[title], job[company], job[city], low, high, months, job.get(education), job.get(experience), job.get(url))) conn.commit() cursor.close() conn.close()逻辑说明INSERT IGNORE遇到唯一键冲突会跳过而不是报错适合反复跑采集脚本。charsetutf8mb4必须和建库一致。参数上salary_low和salary_high允许为 NULL因为“面议”岗位解析出来就是空统计时用WHERE salary_low IS NOT NULL过滤掉即可。5. 可视化接口与缓存Java 后端怎么把统计结果吐给前端5.1 统计接口的 SQL 聚合写法前端要的图表数据本质是几组 GROUP BY 查询。以城市分布和薪资区间为例-- 城市岗位数量 Top 10 SELECT city, COUNT(*) AS num FROM job_info WHERE city IS NOT NULL GROUP BY city ORDER BY num DESC LIMIT 10; -- 薪资区间分布按 5K 一档 SELECT FLOOR(salary_low / 5000) * 5000 AS salary_range, COUNT(*) AS num FROM job_info WHERE salary_low IS NOT NULL GROUP BY salary_range ORDER BY salary_range;逻辑说明第一条按城市分组计数LIMIT 10只取前十个避免地图上城市太多看不清。第二条用FLOOR(salary_low / 5000) * 5000把最低薪资归到 0-5K、5-10K 这样的区间参数 5000 就是档位宽度想改成 3K 一档就把 5000 换成 3000。注意salary_low为 NULL 的“面议”岗位要过滤掉否则区间统计会少一块但看不出原因。在 Java 侧JobController暴露接口JobServiceImpl调 Mapper 执行上面 SQL返回ListMapString, Object给前端。常见做法是接口路径设计成/job/cityStats、/job/salaryStats、/job/educationStats前端按需请求。5.2 Redis 缓存命中与失效策略RedisCache.java的存在说明项目对高频查询做了缓存。招聘数据不是实时变化的缓存几分钟完全可接受。// 伪代码示意实际按项目里的 RedisTemplate 写法调整 public ListMapString, Object getCityStats() { String key job:cityStats; // 先查缓存 Object cached redisTemplate.opsForValue().get(key); if (cached ! null) { return (ListMapString, Object) cached; } // 缓存没有查数据库 ListMapString, Object result jobMapper.selectCityStats(); // 写入缓存过期时间 10 分钟 redisTemplate.opsForValue().set(key, result, 10, TimeUnit.MINUTES); return result; }逻辑说明先查 Redis命中直接返回没命中查库再写缓存。过期时间设 10 分钟是因为招聘数据一天更新一次都算频繁10 分钟足够保证图表不滞后。参数上如果答辩时被问“数据更新了缓存怎么办”可以答“采集脚本跑完后手动删 key或者把过期时间调短”。注意 Redis 里存的对象要可序列化否则会报SerializationException常见做法是配置GenericJackson2JsonRedisSerializer。5.3 前端图表对接与跨域处理前端index.html里通常用 ECharts 或 Chart.js通过fetch或axios请求后端接口。直接双击打开 HTML 会跨域因为file://协议和http://localhost:8080不同源。// 前端请求示例 fetch(http://localhost:8080/job/cityStats) .then(res res.json()) .then(data { const cities data.map(item item.city); const nums data.map(item item.num); // 传给 ECharts 渲染柱状图 chart.setOption({ xAxis: { data: cities }, series: [{ data: nums, type: bar }] }); });逻辑说明data.map把后端返回的对象数组拆成两个数组分别喂给 x 轴和系列。跨域问题有三种常见解法后端加CrossOrigin注解把前端文件放进src/main/resources/static目录通过http://localhost:8080/index.html访问用 Nginx 反代。第一种最快但生产环境不推荐第二种最适合毕设演示启动一个服务就能看全部。6. 避坑与排查环境、编码、驱动和缓存的五条血泪记录6.1 启动报数据库连接失败但密码明明是对的现象Spring Boot 启动时抛Access denied for user rootlocalhost或Communications link failure。原因一种是 MySQL 8 的驱动类名变了老配置写的是com.mysql.jdbc.Driver新驱动要写com.mysql.cj.jdbc.Driver另一种是连接串没加时区MySQL 8 默认时区不是东八区会拒绝连接。解决检查application.yml里驱动类名和连接串加上serverTimezoneAsia/Shanghai。如果还不行用命令行mysql -u root -p确认密码能登录排除密码本身错误。6.2 中文岗位名入库变成问号现象数据库里查出来的岗位名、公司名全是???。原因建库时用了utf8而不是utf8mb4或者连接串没指定characterEncodingutf8。解决删库重建DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci连接串加useUnicodetruecharacterEncodingutf8Python 侧pymysql.connect也要带charsetutf8mb4。三处都对齐才不会乱码。6.3 Selenium 报 SessionNotCreatedException现象Python 爬虫一启动就报SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version XX。原因chromedriver.exe版本和本机 Chrome 大版本不一致。项目包里带的驱动不一定匹配你机器上的 Chrome。解决地址栏输入chrome://version看版本号去驱动下载页找对应大版本的驱动替换。如果不想手动管可以用webdriver-manager自动匹配但毕设环境建议手动固定版本避免自动下载失败。6.4 Redis 没启动导致后端起不来现象启动日志里一堆RedisConnectionFailureException或者Unable to connect to Redis。原因项目里注入了RedisCache但本机没装 Redis 或没启动。解决要么装一个 Redis 并启动要么在配置文件里把 Redis 相关配置注释掉同时把RedisCache的注入改成Autowired(required false)并在使用处判空。答辩演示时如果不想多开一个服务第二种更省事。6.5 前端图表空白接口返回 200 但没数据现象页面能打开图表区域是空的F12 看接口返回{code:200,data:[]}。原因数据库里没数据或者统计 SQL 过滤条件把数据全滤掉了。最常见的是salary_low IS NOT NULL而采集进来的数据薪资字段全是 NULL因为清洗脚本没跑或解析全失败。解决先直接查库SELECT COUNT(*) FROM job_info;确认有数据再查SELECT COUNT(*) FROM job_info WHERE salary_low IS NOT NULL;确认薪资解析成功。如果第二条为 0回去检查parse_salary函数拿几条原始薪资文本单独跑一遍看正则哪里没匹配上。7. 进阶改造换数据源、加图表和答辩演示的稳招把系统跑通只是第一步真正让这份资源发挥价值的是按自己题目改造。我一般会从三个方向动手换数据源、加分析维度、做演示兜底。换数据源是最容易出彩的。这套项目的采集层是独立的 Python 脚本你完全可以把目标从招聘网站换成另一类岗位数据比如把关键词从“Python 开发”换成“数据分析师”或者把城市从全国换成某个省。改的时候只需要动 Selenium 的搜索 URL 和翻页逻辑清洗和入库部分基本不用大改。但要注意不同网站的 DOM 结构不同选择器必须重新确认别直接套用原来的类名。加分析维度是答辩加分项。现有系统通常有城市、薪资、学历、经验几个维度你可以加“公司规模分布”“岗位关键词词云”“薪资与经验交叉分析”。词云用jieba分词加wordcloud库交叉分析用 SQL 的GROUP BY experience, salary_range就能出数据。加图表时前端 ECharts 的配置项照着现有图表复制一份改series.type和接口路径即可不用重写渲染逻辑。答辩演示最怕现场环境出问题。我的习惯是提前做两套准备一套是正常启动的完整系统另一套是把数据库导出成 SQL 文件、把前端图表截图存成 PDF。万一现场 MySQL 或 Redis 起不来直接切到截图讲逻辑评委看的是你的分析思路和代码结构不是看你现场敲命令。另外采集脚本不要现场跑招聘网站响应慢或弹验证会很难看提前跑好数据入库演示时只展示查询和可视化。从那以后我每次交付这类毕设项目都强制走一遍“换一台干净机器从零配环境”的流程因为只有这样才能暴露那些藏在application.yml和驱动版本里的坑。希望帮到你。本文还有配套的精品资源点击获取
返回列表