尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Python+MySQL招聘数据分析可视化:从数据清洗到简历项目实战

发布时间:2026/9/5 5:22:05

资讯中心
01
ARTICLE

Python+MySQL招聘数据分析可视化:从数据清洗到简历项目实战

Python+MySQL招聘数据分析可视化:从数据清洗到简历项目实战
又是一年秋招季很多同学在准备数据分析、数据开发方向简历时最头疼的不是算法题而是“拿不出一个有说服力的项目”。网上的电商销售分析、超市订单分析已经快被写烂了面试官一眼就知道是抄的。换一个与求职场景强相关的方向效果会完全不一样用 Python MySQL 对招聘网站的数据做清洗、存储、分析和可视化既贴近真实业务又能完整覆盖“数据获取 → 数据治理 → 数据存储 → 数据分析 → 可视化展示”整条链路。这套项目放进简历无论是投数据分析、数据运营还是 Python 后端岗位都能成为清晰的加分项。本文将围绕“基于 Python MySQL 的 BOSS 直聘招聘数据分析可视化”展开从环境准备、MySQL 表设计、pandas 清洗、pymysql 入库到 pyecharts / Matplotlib 可视化再到简历面试话术手把手带大家跑通一个可用于毕设、课设的真实项目。文章配有完整源码、数据集合课件文档属于保姆级教程哪怕你现在对 pandas 还不太熟跟着章节一步步做也能完成。文末还会整理高频报错和项目优化方向方便碰到问题时快速排查。1. 项目背景与价值1.1 为什么选择“招聘数据分析”作为练手项目先思考一个问题数据分析项目最怕什么最怕数据是“假”的。很多练习数据是随机生成的分析出来的结论自然站不住脚。而招聘数据本身贴近真实人才市场BOSS 直聘、拉勾、前程无忧等平台上的职位信息包含大量结构化字段例如岗位名称、薪资范围、城市、学历要求、工作经验、公司类型、融资阶段、技能标签等。这些字段来源于真实的招聘需求能回答很多有价值的问题哪个城市的 Python 数据分析岗位需求量最大不同学历要求对应的薪资范围是怎样的数据分析岗位对工作经验的要求集中在哪个范围哪些技能关键词出现频率最高说明当前市场最看重哪些能力数据岗在不同行业的薪资分布是否存在规律对求职者来说这是“以数据反哺求职”对面试官来说这是一个完整的数据分析闭环不是单纯抛几张图表而是能从数据清洗到可视化解释清楚每个环节。这也是这类项目能在简历中胜出的原因它不是“Hello World”而是“真实问题 完整流程 可复用代码”。1.2 项目核心功能拆解一套完整的招聘数据分析可视化项目需要拆解成以下几个模块模块核心任务涉及的技术数据获取从招聘网站采集职位信息或使用已有脱敏数据集requests / 已有数据集数据清洗处理缺失值、去重、解析薪资字段、标准化城市/学历/经验pandas、正则表达式数据存储将处理后的结构化数据写入 MySQL形成可查询的数据表pymysql / SQLAlchemy数据分析基于 SQL 和 pandas 进行聚合统计、分组对比、相关性探索MySQL、pandas数据可视化将分析结果通过图表展示Matplotlib、pyecharts报告输出归纳结论形成项目说明文档Markdown / PPT本教程重点放在第 2 到第 6 步因为这也是大家在练习中最容易遇到问题的部分。真实场景中爬虫采集需要遵守目标网站的 Robots 协议和平台规定不建议拿正式账号去暴力请求尤其是招聘平台的反爬策略非常多稍不注意就可能触发封禁或法律风险。更保险的做法是先拿一份合法、脱敏的历史数据集或用自己模拟构造的数据跑通全链路后续再在导师、公司授权范围内采集真实数据。1.3 项目适合准备到什么程度如果你是零基础转行只要按本文步骤能把项目跑起来并理解每段代码是做什么的简历上就可以写“熟悉数据分析常规流程、能使用 pandas 完成数据清洗并使用 MySQL 存储”。如果你已经有 Python 和 SQL 基础建议重点思考可视化选型原因和数据表设计细节这些内容在小厂技术面和二面中很容易被追问。2. 技术栈与整体架构图2.1 技术栈版本说明本项目涉及的技术栈整体如下开发语言Python 3.8推荐使用 Anaconda 自带 Python 环境可避免很多包管理问题。数据处理pandas、numpy、re。数据库MySQL 8.x版本不是重点5.7 也可以使用注意字符集统一为 utf8mb4。数据库连接pymysql如果使用 SQLAlchemy 的to_sql写库需要额外安装sqlalchemy。可视化Matplotlib 3.x静态图、pyecharts 2.x交互式 HTML 图表。IDEPyCharm、VS Code、Jupyter Notebook 均可。Jupyter 比较适合分段演示写成项目推荐使用 PyCharm。版本要根据自己的实际环境调整本文不会把某个版本号写死。例如 pyecharts 的 API 在 1.x、2.x 之间有变化如果你是更早的版本导入方式和链式调用可能需要微调。重点是先理解流程再处理版本兼容问题。2.2 架构思路本项目的数据流可以表示如下本地数据集(csv/excel) ↓ pandas 读取 → 数据清洗 → 字段标准化 ↓ 写入 MySQL 数据库招聘信息表 ↓ SQL 聚合查询 / pandas 分组统计 ↓ Matplotlib 静态图 pyecharts 交互图 ↓ 输出 html 报告 / 可视化大屏 / 简历项目描述所有文件的建议结构如下方便后续维护job_analysis/ │ ├── data/ │ └── recruitment_demo.csv # 演示数据集 ├── sql/ │ └── create_table.sql # MySQL建表语句 ├── src/ │ ├── config.py # 数据库配置统一管理 │ ├── clean_data.py # 数据清洗模块 │ ├── write_db.py # 写数据库模块 │ ├── analysis_sql.py # SQL分析模块 │ └── visualization.py # 可视化模块 ├── output/ │ └── charts/ # 图表输出目录 ├── README.md └── requirements.txt代码文件拆分不要过于复杂。把“配置”和“业务逻辑”分开把“清洗”“入库”“分析”“可视化”放在不同文件中是工程项目最基本的模块化思想。面试官在看你的 GitHub 地址时能一眼看出项目组织能力。2.3 关于数据集的说明由于平台数据合规和数据动态性问题本文用一份结构类似 BOSS 直聘字段的脱敏演示数据集来讲解。数据集字段不会与线上完全一致但具有代表性。你完全可以把自己的爬虫数据替换成相同字段来完成同样的流程。注意真实爬虫数据仅限本人学习、且不超过平台合理访问频率下获得。切勿大规模采集后商用。3. 环境准备3.1 Python 环境安装要点如果你已经安装了 Python可以在命令行输入下面命令检查版本python --version pip --version保证 Python 为 3.8 及以上即可。如果还没有安装 Python建议直接安装 Anaconda 发行版。Anaconda 自带 conda、pandas、numpy、matplotlib、jupyter 等常用库能省去很多单独安装依赖的时间。Linux 服务器上也可以使用 pyenv 或 conda 管理 Python 环境。3.2 MySQL 环境安装MySQL 的安装方式在不同操作系统上差别不大。Windows 安装时需要注意选择字符集或安装完成后在配置文件中指定 utf8mb4macOS 可以用 Homebrew 安装brew install mysql brew services start mysqlUbuntu/Debian 系列 Linux 可以使用sudo apt update sudo apt install mysql-server sudo systemctl start mysql安装完成后使用 root 用户登录 MySQL创建数据库和专用账号。生产环境绝对不要直接用 root 远程连接本地学习项目为了方便可以暂时使用 root但代码中建议配置专用账号这是好习惯。mysql -u root -p3.3 创建 Python 虚拟环境并安装依赖Python 依赖通过 requirements.txt 管理。先创建一个干净的虚拟环境python -m venv venv source venv/bin/activate # Windows 为 venv\Scripts\activate pip install --upgrade pip然后在项目根目录创建 requirements.txtpandas1.5.0 numpy1.23.0 pymysql1.0.2 sqlalchemy2.0.0 openpyxl3.0.0 matplotlib3.5.0 pyecharts2.0.0 python-dotenv1.0.0安装依赖pip install -r requirements.txt如果只想先快速体验至少安装这几个包pip install pandas pymysql sqlalchemy matplotlib pyecharts openpyxl python-dotenv3.4 演示数据集字段说明建议把数据文件放在data/目录下。为了使代码可独立运行下面给出一份模拟数据集的核心字段定义文件格式为 CSV分隔符为英文逗号编码为 UTF-8。字段名含义示例值job_name岗位名称Python数据分析师company_name公司名称某科技有限公司salary原始薪资字段15-25K·14薪salary_min最低月薪单位K15salary_max最高月薪单位K25salary_months年薪月数14city工作城市北京education学历要求本科experience经验要求3-5年industry公司所属行业互联网tags职位标签Python,SQL,数据分析publish_date发布日期2025-10-12实际招聘平台的字段远远不止这些还可以有公司规模、融资阶段、福利标签、岗位详情等。为了演示方便我们抽取出最能体现岗位特点的字段。到这里项目整体环境和数据目录就准备好了。下面开始设计 MySQL 表。4. MySQL 数据库设计与建表4.1 根据分析需求设计表结构在设计表之前先想清楚要回答什么问题这样才不会被字段拖累。本项目需要分析城市岗位分布、学历要求、经验要求、薪资区间、技能关键词因此至少需要一张主表来存储岗位基本信息CREATE DATABASE IF NOT EXISTS job_analysis DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE job_analysis; CREATE TABLE t_job_info ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, job_name VARCHAR(128) NOT NULL COMMENT 岗位名称, company_name VARCHAR(128) DEFAULT NULL COMMENT 公司名称, salary_min DECIMAL(10, 1) DEFAULT NULL COMMENT 最低月薪(K), salary_max DECIMAL(10, 1) DEFAULT NULL COMMENT 最高月薪(K), salary_months DECIMAL(10, 1) DEFAULT NULL COMMENT 发放月数, city VARCHAR(64) DEFAULT NULL COMMENT 城市, education VARCHAR(64) DEFAULT NULL COMMENT 学历要求, experience VARCHAR(64) DEFAULT NULL COMMENT 经验要求, industry VARCHAR(128) DEFAULT NULL COMMENT 所属行业, tags VARCHAR(500) DEFAULT NULL COMMENT 技能标签, publish_date DATE DEFAULT NULL COMMENT 发布日期, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 入库时间, KEY idx_city (city), KEY idx_job_name (job_name), KEY idx_education (education) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4 COMMENT 招聘岗位信息表;为什么salary_min和salary_max用DECIMAL而不是直接存字符串因为后续要做AVG(salary_min)、MAX(salary_max)等聚合运算数值类型才能被 SQL 直接处理。DECIMAL(10,1)能存小数比 INT 更灵活。为什么要设置create_time字段数据入库时记录时间既能追溯数据批次后续如果要跑“增量更新”也能用这个时间字段过滤当日或当批新增数据。4.2 索引设计背后的考虑很多初学者建表时完全不建索引等数据量变大后一句WHERE city 北京就会全表扫描。招聘数据量如果只有几万条问题不大但如果是几十万甚至百万条索引差异会很明显。本表中我们对三个高频查询字段建了索引idx_city城市维度是最常做的分组条件。idx_job_name岗位名称过滤例如查“Python数据分析师”。idx_education学历维度做统计时使用。不是说索引越多越好。索引会占用磁盘空间并且在写入时增加维护成本。对于演示项目来说建 2 到 4 个关键索引即可不用把所有字段都建上。4.3 数据表设计常见问题在实际项目答辩中很容易被问到“为什么不用salary原始字段直接分析”因为原始薪资是一个字符串比如15-25K·14薪或者20-35K·13薪甚至有的是面议。字符串无法直接求平均、比较大小。SQL 里虽然可以用函数解析但每次查询都要解析一遍性能很差也容易出错。更合理的做法是在 Python 清洗阶段解析一次再把结构化字段存库。“为什么还要存salary_min和salary_max两个字段直接存平均薪资不行吗”招聘平台通常显示的是薪资范围。真实求职过程中HR 看待薪资范围会考虑下限、上限和期望区间。只存平均值会丢失上限和下限的信息不利于分析高薪区间。保留两个字段后续可以灵活计算“最低薪资均值”“最高薪资均值”“薪资区间中位数”等。5. Python 数据清洗与数据入库5.1 读取数据集并查看基本情况在src目录下创建一个clean_data.py文件先使用 pandas 读取 CSV 文件# 文件路径src/clean_data.py import pandas as pd def load_data(file_path): 读取招聘数据 csv 文件 df pd.read_csv(file_path, encodingutf-8) print(数据形状:, df.shape) print(字段列表:, df.columns.tolist()) print(df.head(3)) return df if __name__ __main__: df load_data(../data/recruitment_demo.csv)运行效果如下根据数据集不同会有差异数据形状: (5000, 12) 字段列表: [job_name, company_name, salary, city, education, ...]5.2 处理缺失值和重复数据拿到的数据第一件事不是直接建模而是看质量。常用操作包括用df.isnull().sum()检查每列缺失值数量。用df.duplicated().sum()检查是否存在完全重复的行。对关键字段缺失太多且无法填充的行直接删除。# 剔除关键字段为空的行 df df.dropna(subset[job_name, company_name, city]) # 完全重复的数据去重 df df.drop_duplicates() # 对非关键字段做缺失值填充例如行业写“未知” df[industry] df[industry].fillna(未知)实际项目里education偶尔会出现“学历不限”“硕士及以上”这样的描述如果要做学历体系归类还需要手工清洗成标准值。例如学历不限保留为不限硕士及以上可以规整为硕士。5.3 薪资字段解析Salary 原始字段格式一般是15-25K·14薪。我们通过正则表达式提取最低和最高薪资同时提取月数import re def parse_salary(salary_str): 将形如 15-25K·14薪 的薪资字符串解析为 (salary_min, salary_max, salary_months) 解析失败时返回 None if not isinstance(salary_str, str): return None, None, None # 匹配 15-25 的区间单位为 K range_match re.search(r(\d(?:\.\d)?)[-~](\d(?:\.\d)?)K, salary_str, re.IGNORECASE) if not range_match: return None, None, None salary_min float(range_match.group(1)) salary_max float(range_match.group(2)) months_match re.search(r(\d)\s*薪, salary_str) months float(months_match.group(1)) if months_match else 12.0 return salary_min, salary_max, months这里用正则而不是字符串切片是因为薪资格式可能出现15-25K、20-30K·14薪、13薪等多种形态人工写死切片位置并不稳健。正则表达式虽然有一定学习成本但能兼容格式变化。实际薪资字段有时候还会写25K以上或面议这两类情况在 parse 函数中会返回空值。不要直接删除这些行否则会损失样本量。可以把salary_min和salary_max都置空后续统计薪资时再过滤。清洗代码中加入列转换df[[salary_min, salary_max, salary_months]] df[salary].apply( lambda x: pd.Series(parse_salary(x)) )注意apply返回 Series 后赋值到新列是一种常见的 pandas 写法但性能中等。对于几万行数据完全没问题百万行级别建议用列表推导式 一次性赋值避免多次创建 Series。5.4 编写数据库连接模块数据库密码不要直接硬编码。我们使用python-dotenv读取.env文件既方便本地开发也能避免源码泄露密码。在项目根目录创建.env文件DB_HOST127.0.0.1 DB_PORT3306 DB_USERroot DB_PASSWORD123456 DB_NAMEjob_analysis然后在src/config.py中编写读取配置的代码# 文件路径src/config.py import os from dotenv import load_dotenv load_dotenv() DB_CONFIG { host: os.getenv(DB_HOST, 127.0.0.1), port: int(os.getenv(DB_PORT, 3306)), user: os.getenv(DB_USER, root), password: os.getenv(DB_PASSWORD, ), database: os.getenv(DB_NAME, job_analysis), charset: utf8mb4, }连接 MySQL 的模块写在src/db.py# 文件路径src/db.py import pymysql def get_connection(): return pymysql.connect( hostDB_CONFIG[host], portDB_CONFIG[port], userDB_CONFIG[user], passwordDB_CONFIG[password], databaseDB_CONFIG[database], charsetDB_CONFIG[charset], cursorclasspymysql.cursors.DictCursor, # 返回字典形式更方便后续处理 )5.5 批量写入 MySQL写入数据库最直观的方法是循环遍历 DataFrame逐行执行 INSERT。数据量小时没问题但几千行后速度会明显变慢。正确做法是使用executemany批量插入一次执行多条语句减少网络往返和 MySQL 解析开销。示例代码# 文件路径src/write_db.py import pandas as pd from db import get_connection def insert_job_data(df): 将清洗后的 DataFrame 批量写入 MySQL 表 conn get_connection() sql INSERT INTO t_job_info (job_name, company_name, salary_min, salary_max, salary_months, city, education, experience, industry, tags, publish_date) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) # 只保留需要的列并把 NaN 转为 None df_to_insert df[[ job_name, company_name, salary_min, salary_max, salary_months, city, education, experience, industry, tags, publish_date ]].copy() # pandas 的 NaN 不能直接写入数据库需要转成 None df_to_insert df_to_insert.astype(object).where(pd.notnull(df_to_insert), None) # 转成元组列表 data_list [tuple(row) for row in df_to_insert.itertuples(indexFalse)] try: cursor conn.cursor() cursor.executemany(sql, data_list) conn.commit() print(f成功写入 {cursor.rowcount} 条数据) except Exception as e: conn.rollback() print(写入失败已回滚:, e) raise e finally: cursor.close() conn.close()这里面有几个值得反复看的细节astype(object).where(pd.notnull(df_to_insert), None)pandas 的NaN是浮点类型MySQL 驱动无法把它正确识别为 NULL所以转成 None。conn.rollback()一旦执行失败需要把事务回滚防止只插入了一部分数据破坏数据表完整性。先 commit 再关闭连接顺序不能反。为了形成完整脚本我们还可以在src/clean_data.py中加入__main__流程读取文件 → 清洗 → 建表或确认表存在→ 写入。这里不再重复粘贴最终项目源码中会有完整脚本。6. 数据分析SQL 与 pandas 结合使用6.1 城市岗位需求 TOP10数据入库后可以使用 MySQL 做聚合统计。打开 MySQL 客户端或使用 Python 执行查询。先看第一个问题哪个城市的数据分析类岗位数量最多SELECT city, COUNT(*) AS job_cnt FROM job_analysis.t_job_info WHERE job_name LIKE %数据分析% GROUP BY city ORDER BY job_cnt DESC LIMIT 10;这个查询会将岗位名中包含“数据分析”的职位按城市分组计数最后取前 10。之所以使用LIKE %数据分析%是因为很多岗位名称不叫“数据分析师”而是“数据分析专员”“资深数据分析师”等。如果数据量大这种模糊查询无法走索引性能会下降。更稳妥的做法是在建表阶段增加一个 category 分类字段或者在分析时精确匹配job_name但演示数据量不大采用LIKE完全没问题。6.2 学历与经验要求分布统计各学历要求下的岗位数量SELECT education, COUNT(*) AS job_cnt, ROUND(COUNT(*) / (SELECT COUNT(*) FROM job_analysis.t_job_info) * 100, 2) AS rate FROM job_analysis.t_job_info GROUP BY education ORDER BY job_cnt DESC;这里用了子查询来计算总岗位数并计算占比。ROUND(..., 2)的目的是将百分比保留两位小数。实际业务中学历字段如果不统一可以先在 Python 中规整成不限/大专/本科/硕士/博士几类再入库。6.3 不同学历对应的薪资中位数薪资字段不是正太分布直接用AVG求平均值容易受极值影响。使用中位数更稳健。MySQL 8.x 有窗口函数可以计算中位数如果没有窗口函数也可以用多次查询模拟。这里展示一个按学历分组求最低薪资平均值的简单 SQLSELECT education, ROUND(AVG(salary_min), 2) AS avg_min_salary, ROUND(AVG(salary_max), 2) AS avg_max_salary FROM job_analysis.t_job_info WHERE salary_min IS NOT NULL GROUP BY education ORDER BY avg_max_salary DESC;注意加WHERE salary_min IS NOT NULL因为清洗阶段已经说明了“面议”等数据无法解析薪资若不排除统计结果会出现空档。6.4 技能词频统计思路如果tags字段保存的是逗号分隔的技能标签如“Python,SQL,数据分析”那么单个 SQL 里直接按标签分组会比较麻烦。更常见的方法是取出所有 tags在 Python 中先用逗号拆分再统一统计词频from collections import Counter from db import get_connection import pandas as pd def get_all_tags(): conn get_connection() sql SELECT tags FROM t_job_info WHERE tags IS NOT NULL df pd.read_sql(sql, conn) conn.close() return df df_tags get_all_tags() counter Counter() for tag_str in df_tags[tags]: for tag in str(tag_str).split(,): tag tag.strip() if tag: counter[tag] 1 top_tags counter.most_common(20) print(top_tags)技能词频统计最大的意义在于给求职者提供方向参考如果数据岗里Python、SQL出现次数远高于其他技能简历中应重点强调这两种能力。这个分析结果同样可以作为可视化图表里的词云数据。6.5 用 pandas 还是用 SQL 分析这两种方式不是互斥的数据已入 MySQL且查询涉及多表关联、分组汇总使用 SQL 最直接。例如聚合后再连接业务表SQL 能利用索引。如果数据已经读取到 DataFrame并且需要做比较复杂的前处理比如自定义函数解析字段、多步骤清洗后统计使用 pandas 更灵活。如果后续要做可视化写 SQL 查询后可以再通过pd.read_sql(sql, conn)读成 DataFrame。在项目开发中建议采用“复杂过滤、分组交给 SQL统计运算和可视化前处理交给 pandas”的分工方式。本例数据量不大两种方式都能胜任但面试时能够说出选择理由会显得更有经验。7. 可视化展示数据分析如果没有可视化阅读者很难快速抓住规律。本节会实现静态图和交互式图表两类静态图适合嵌入 Word 论文、PPT交互式 HTML 适合展示在个人主页、毕设系统或者 GitHub Pages 中。7.1 Matplotlib 中文乱码解决方案Matplotlib 默认字体不支持中文很容易出现方框。解决思路是设置中文字体。在 Windows 中常用SimHei或Microsoft YaHeimacOS 中是Arial Unicode MS或PingFang SCLinux 需安装中文字体。统一在可视化脚本顶部设置# 文件路径src/visualization.py import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False # 解决负号显示问题这段代码虽然只有两行但在中文图上非常关键。如果设置后仍然乱码可以通过下面的代码查看系统到底有哪些可用字体from matplotlib import font_manager font_list [f.name for f in font_manager.fontManager.ttflist if Hei in f.name or YaHei in f.name] print(set(font_list))7.2 Matplotlib 绘制城市岗位 TOP10 条形图下面提取城市岗位数前 10 的数据并绘制成横向条形图import pandas as pd import matplotlib.pyplot as plt from db import get_connection def query_city_top(limit10): conn get_connection() sql SELECT city, COUNT(*) AS job_cnt FROM t_job_info GROUP BY city ORDER BY job_cnt DESC LIMIT %s df pd.read_sql(sql, conn, params(limit,)) conn.close() return df def plot_city_bar(df): plt.figure(figsize(10, 6)) plt.barh(df[city], df[job_cnt], color#4C72B0) plt.xlabel(岗位数量) plt.ylabel(城市) plt.title(数据分析岗位城市分布 TOP10) # y轴倒序让数量最高的城市显示在最上方 plt.gca().invert_yaxis() # 在条形右侧添加数量标签 for index, value in enumerate(df[job_cnt]): plt.text(value 0.3, index, str(value), vacenter) plt.tight_layout() plt.savefig(../output/charts/city_top10.png, dpi300) plt.show() if __name__ __main__: city_df query_city_top() plot_city_bar(city_df)代码中演示了两个常用处理plt.gca().invert_yaxis()横向条形图会把数值小的放在轴底部数值大的放在顶部一般我们希望柱状图从高到低排列因此把 y 轴反转。plt.text()在柱子右侧显示具体数字避免读者只看柱子长度却无法准确读出数字。7.3 pyecharts 交互式可视化Matplotlib 的图是静态图片。如果想做可视化大屏或者希望鼠标悬停时能看到具体数值、图例可以动态隐藏推荐使用 pyecharts。它生成的是 HTML 页面基于 ECharts 渲染页面效果远比静态图现代。安装 pyecharts 后先绘制一个薪资与学历关系条形图# 文件路径src/visualization.py 片段 from pyecharts.charts import Bar from pyecharts import options as opts def plot_education_salary_bar(): conn get_connection() sql SELECT education, ROUND(AVG(salary_min), 2) AS avg_min, ROUND(AVG(salary_max), 2) AS avg_max FROM t_job_info WHERE salary_min IS NOT NULL GROUP BY education ORDER BY avg_max DESC df pd.read_sql(sql, conn) conn.close() # 学历字段可能顺序混乱这里按平均最高薪资排序 education_list df[education].tolist() avg_min_list df[avg_min].tolist() avg_max_list df[avg_max].tolist() bar ( Bar() .add_xaxis(education_list) .add_yaxis(最低薪资均值, avg_min_list, color#5470C6) .add_yaxis(最高薪资均值, avg_max_list, color#91CC75) .set_global_opts( title_optsopts.TitleOpts(title不同学历下的薪资均值), yaxis_optsopts.AxisOpts(name月薪(K)), xaxis_optsopts.AxisOpts(name学历) ) ) bar.render(../output/charts/education_salary_bar.html) print(图表已生成output/charts/education_salary_bar.html)pyecharts 的链式调用主要是做配置。上例中add_xaxis添加 x 轴数据add_yaxis添加柱状图系列数据set_global_opts统一配置图标题、坐标轴名称和位置。这样会生成一个带动态交互的 HTML 文件双击即可在浏览器打开。绘制横向条形图或饼图类似只需要把Bar换成Pie并把加载数据的方式改成add_dataset或add。例如from pyecharts.charts import Pie def plot_education_pie(): conn get_connection() sql SELECT education, COUNT(*) AS cnt FROM t_job_info GROUP BY education df pd.read_sql(sql, conn) conn.close() data_pair list(zip(df[education], df[cnt])) pie ( Pie() .add(学历分布, data_pair, radius[35%, 70%]) .set_global_opts(title_optsopts.TitleOpts(title岗位学历分布)) .set_series_opts(label_optsopts.LabelOpts(formatter{b}: {d}%)) ) pie.render(../output/charts/education_pie.html)radius[35%, 70%]代表制作环形饼图。label_opts.formatter中的{b}表示名称{d}表示百分比。实际演示时可根据需求调整。7.4 生成可视化大屏的简易方案如果毕设需要“大屏效果”可以基于 pyecharts 提供的Page组件把多张图拼在同一页面from pyecharts.charts import Page page Page(layoutPage.SimplePageLayout) page.add(city_bar) # 城市岗位条形图 page.add(education_pie) # 学历饼图 page.add(salary_scatter) # 薪资散点图 page.render(../output/dashboard.html)这种方案不需要前端基础能快速将所有交互图表整合在一个 HTML 文件中再配合页面自动布局。若想达到更复杂的“可视化大屏”效果比如一行多列、不同颜色背景可以自定义 HTML 模板用 pyecharts 的Page底层改成完整页面。但毕设答辩和简历展示中清晰比华丽更重要所以不必过度追求大屏动效。8. 简历写法与面试高频知识点8.1 简历中的项目描述模板很多同学做完项目后简历上只会写“实现了招聘网站数据分析和可视化”太单薄。建议使用“项目背景 技术栈 个人职责 项目难点”四段式结构项目名称基于 Python与MySQL的招聘数据分析系统 项目描述采集/整理某招聘平台数据类岗位信息数据集完成数据清洗、MySQL建模、多维度统计分析与可视化输出交互式可视化报告用于求职方向辅助决策。 技术栈Python、pandas、MySQL、pyecharts、Matplotlib 个人职责 - 使用 pandas 完成数据去重、缺失值处理、薪资字段正则解析与标准化清洗 5000 条招聘记录 - 设计 MySQL 表结构和索引使用 pymysql 批量写入并维护数据一致性 - 基于 SQL 与 pandas 完成城市、学历、经验、薪资等多维度聚合分析 - 使用 pyecharts 制作城市岗位分布、学历薪资对比等交互图表生成可视化报告。 项目难点 - 招聘平台薪资字段格式不统一需要利用正则表达式提取结构化薪资区间 - 不同来源数据存在中文编码问题需统一使用 utf8mb4 编码并规范化学历、经验等字段 - 大批量数据使用循环插入效率低改为 executemany 批量写入并配合事务处理。这样写的好处是每个量化细节都能在面试中被追问时从容回答。数据量可以如实写如果只是几百条那就写“3000 条样本”不要虚构成百万级经不起深挖。8.2 面试官常问的问题这类项目在初筛和一面中比较容易被追问为什么选择 MySQL 而不是直接把数据存在 CSV 里 答CSV 文件适合单机分析但搜索、分组、去重效率低MySQL 支持索引、事务和 SQL 聚合在生产环境中更利于多用户和后续建模。薪资字段是怎么处理的 答原始字段是类似“15-25K·14薪”的字符串用正则表达式拆出salary_min、salary_max、salary_months。遇到“面议”“薪资范围不完整”等行则保留在表中但把薪资置空并在统计中过滤。数据有没有做去重 答使用drop_duplicates()去除完全重复的记录同时结合岗位名称、公司、发布时间三个字段进行逻辑去重避免同一职位被重复采集多次。索引为什么建了city、education字段而不是每个字段都建索引 答索引能加速查询但会增加写操作成本。针对高频分组和过滤字段建索引即可例如城市、岗位、学历。全表扫描在小数据量级下不慢但索引设计是生产环境必须考虑的问题。8.3 项目还能怎么扩展完成基础功能后下面几个方向可以让项目更有深度加入“薪资预测模型”使用岗位名称、城市、学历、经验等字段预测薪资范围。使用 Flask 搭建简易 Web 系统让用户选择城市或岗位关键词后动态展示图表。增加定时采集任务用APScheduler每天增量采集一次存入 MySQL形成历史趋势分析。使用 ECharts 地图展示不同省份的岗位数量分布在视觉上比柱状图更直观。但要注意不是扩展得越多越好。简历项目和毕设项目重点是完整性和逻辑自洽而不是炫技。做一个功能能讲透 30 分钟比堆砌 8 个功能但每个都讲不清要好得多。9. 常见问题与排查清单实际运行中遇到最多的问题按“现象 原因 解决”整理如下问题现象常见原因解决思路ModuleNotFoundError: No module named pymysql虚拟环境中没有安装 pymysql执行pip install pymysql确认当前使用的 Python 是 venv 环境连接 MySQL 报Access denied for user rootlocalhostroot 密码或认证方式不正确检查.env中的账号密码MySQL 8 默认使用 caching_sha2_password如果驱动版本过旧需升级 pymysql中文写入 MySQL 后显示乱码客户端连接字符集不是 utf8mb4或建表字符集不是 utf8mb4连接参数设置charsetutf8mb4建表时指定DEFAULT CHARSETutf8mb4确认终端也使用 UTF-8executemany写入失败DataFrame 中存在NaN或日期字段格式不正确将NaN转为None日期统一使用strftime(%Y-%m-%d)转成字符串pandas.to_sql报错ValueError: dialect $ not supportedSQLAlchemy 未安装执行pip install sqlalchemy并将连接方式改为create_engine(mysqlpymysql://...)运行时显示“文件编码错误”CSV 可能不是 UTF-8 编码用记事本或 VS Code 将文件另存为 UTF-8读取时指定encodingutf-8也可以尝试encodinggbkMatplotlib 中文显示为方框系统没有匹配到中文字体使用plt.rcParams[font.sans-serif]设置为SimHei/Microsoft YaHei/PingFang SC并重载字体缓存pyecharts 生成的 HTML 打开后图表空白浏览器设置或脚本文件路径错误检查是否在本地用浏览器直接打开 HTML路径中不要有中文如果仍有问题使用默认浏览器和最新 pyecharts 版本插入数据后查询字段出现None源数据字段缺失写入时转为 NULL先做dropna(subset[...])删除必要字段为空的行非必要字段用fillna填充后再入库批量插入速度非常慢循环逐条 insert未使用事务改成executemany并使用conn.commit()一次性提交排查问题时推荐顺序是先确认 Python 环境和依赖版本。再检查 MySQL 服务是否启动账号权限是否正常。检查.env文件中的数据库配置是否和本地一致。用小样本数据比如只取前 100 行单独执行清洗和入库定位问题。将报错信息复制到搜索引擎优先看官方文档和 Stack Overflow不要盲目改配置。每次修改代码后手动查看 MySQL 表中的前几条记录确认写入结果符合预期。10. 工程化与合规建议10.1 数据获取合规性关于“BOSS直聘招聘数据分析”这个话题需要特别强调数据合规。如果你是自己练习建议使用公开的脱敏数据集或模拟数据。如果你确实想采集真实招聘数据务必做到只抓取公开页面中与职位相关的内容不采集个人敏感信息。控制请求频率避免对目标平台造成压力不进行并发爆破式访问。遵守网站 Robots 协议、服务条款和相关法律法规。数据仅用于个人学习、学术研究等非商业用途不对外公开原始数据。不以绕过反爬机制为技术亮点去介绍。在简历项目描述中建议写“使用公开脱敏数据集”或“基于学习研究目的下获取的数据”而不是把“爬虫绕过策略”作为核心卖点。合规是工程底线也是技术人应具备的基本素养。10.2 配置文件管理密码、API Key 这类信息不要硬编码到.py文件中更不要把数据库密码传到公开 GitHub 仓库。使用.env文件配合python-dotenv管理并在.gitignore中加入.env venv/ output/ __pycache__/ *.pyc在团队协作场景下.env.example可以提交到仓库用于告诉其他人需要配置哪些变量真实.env则保留在本地。10.3 异常处理写入库和查询代码时不要偷懒不写异常处理。长期运行的脚本网络中断、字段格式变化、数据库连接断开都是常态。建议至少在入口函数中增加try...except...finally并把错误日志输出到本地文件import logging logging.basicConfig( filename../logs/app.log, levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s ) logger logging.getLogger(__name__) try: insert_job_data(df) except Exception: logger.exception(数据写入失败)logger.exception会自动记录完整的堆栈信息。这样脚本半夜跑挂了第二天看日志就能定位是哪条数据、哪个环节出了问题。10.4 编码与命名规范项目文件命名统一使用小写字母 下划线类名使用驼峰命名函数名使用小写字母 下划线。MySQL 表名和字段名建议统一使用小写 下划线。不要因为代码能跑就不重视可读性。在面试中一段命名清晰、职责单一的函数比几百行“能跑”的流水账代码更有说服力。10.5 生产环境与本地开发隔离本文示例直接用 root 连接 MySQL。在实际项目部署时应该遵循最小权限原则为项目创建一个独立数据库账号只授予该数据库的 SELECT、INSERT、UPDATE、DELETE 权限避免误操作破坏其他库表。CREATE USER dev_userlocalhost IDENTIFIED BY your_password; GRANT SELECT, INSERT, UPDATE, DELETE ON job_analysis.* TO dev_userlocalhost; FLUSH PRIVILEGES;在涉及到删除表、清空表或批量更新前一定先在测试库上验证。使用DELETE或UPDATE时务必写清WHERE条件并提前备份数据。11. 总结与下一步学习建议到这里你已经把整个项目的数据表设计、清洗、入库、分析、可视化链路跑通了。很多同学做到这一步会发现技术点并不复杂难点在于把每个环节串起来并且在遇到问题时能快速定位。把项目跑通后你可以把代码和数据集上传到 GitHubREADME 里写清楚项目背景、表结构说明和运行步骤。这本身也是一种工程能力的体现。下一个进阶方向建议优先掌握这三点把 pandas 清洗流程改成参数化脚本输入不同格式的 CSV 都能自动处理学习 Flask 基础将图表演示页面包装成一个小型 Web 应用接触更完整的数据库设计例如建事实表和维度表学习星型模型尝试把两张以上的业务表进行 JOIN 分析比如岗位表 公司表分析公司规模对薪资的影响。这个项目的上限并不局限于毕业设计。如果你后续想在数据方向走得更远可以把同样的项目迁移到 Spark、Flink 等分布式框架处理更大规模的数据也可以引入更规范的数据仓库分层设计。不过那是另一个阶段需要解决的问题。现阶段最关键的是先把 Python MySQL 可视化这条基础链路吃透遇到报错不要怕数据的乐趣就藏在一点点排错和看到最终图表那一刻。动手把项目跑起来比只看教程有价值得多。如果这篇教程对你有帮助欢迎收藏备用也欢迎在评论区交流你在跑项目中遇到的问题。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。