简介:这是一套面向计算机、通信、人工智能、自动化等相关专业学生与教师的毕业设计级招聘数据分析可视化系统,采用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 脚本定时或手动跑一次,把数据写进 MySQL;Java 服务启动后读 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.properties:
spring: datasource: url: jdbc:mysql://localhost:3306/job_analyse?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0参数说明:serverTimezone=Asia/Shanghai不加的话,MySQL 8 以上会报时区错误;characterEncoding=utf8配合库的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?page=1&size=10 # 测试统计接口,看城市分布数据 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-features=AutomationControlled") # 无头模式,调试阶段建议注释掉,方便看页面 # options.add_argument("--headless") options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_experimental_option("useAutomationExtension", False) driver = webdriver.Chrome(options=options) # 覆盖 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 薪会覆盖,后面算年薪时用(low+high)/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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;入库时用INSERT IGNORE或ON DUPLICATE KEY UPDATE,避免重复插入报错中断。
import pymysql conn = pymysql.connect(host="localhost", user="root", password="你的密码", database="job_analyse", charset="utf8mb4") 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遇到唯一键冲突会跳过而不是报错,适合反复跑采集脚本。charset="utf8mb4"必须和建库一致。参数上,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,返回List<Map<String, Object>>给前端。常见做法是接口路径设计成/job/cityStats、/job/salaryStats、/job/educationStats,前端按需请求。
5.2 Redis 缓存命中与失效策略
RedisCache.java的存在说明项目对高频查询做了缓存。招聘数据不是实时变化的,缓存几分钟完全可接受。
// 伪代码示意,实际按项目里的 RedisTemplate 写法调整 public List<Map<String, Object>> getCityStats() { String key = "job:cityStats"; // 先查缓存 Object cached = redisTemplate.opsForValue().get(key); if (cached != null) { return (List<Map<String, Object>>) cached; } // 缓存没有,查数据库 List<Map<String, 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 'root'@'localhost'或Communications link failure。
原因:一种是 MySQL 8 的驱动类名变了,老配置写的是com.mysql.jdbc.Driver,新驱动要写com.mysql.cj.jdbc.Driver;另一种是连接串没加时区,MySQL 8 默认时区不是东八区,会拒绝连接。
解决:检查application.yml里驱动类名和连接串,加上serverTimezone=Asia/Shanghai。如果还不行,用命令行mysql -u root -p确认密码能登录,排除密码本身错误。
6.2 中文岗位名入库变成问号
现象:数据库里查出来的岗位名、公司名全是???。
原因:建库时用了utf8而不是utf8mb4,或者连接串没指定characterEncoding=utf8。
解决:删库重建,DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;连接串加useUnicode=true&characterEncoding=utf8;Python 侧pymysql.connect也要带charset="utf8mb4"。三处都对齐才不会乱码。
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和驱动版本里的坑。希望帮到你。
本文还有配套的精品资源,点击获取