Python+MySQL招聘数据可视化分析项目:从爬虫到图表全流程

Python+MySQL招聘数据可视化分析项目:从爬虫到图表全流程 秋招季最头疼的问题之一就是简历上缺一个有数据链路、有业务场景、还能说清楚技术细节的项目。很多同学学完 Python 基础后不知道下一步做什么学完 MySQL 之后又发现只会写SELECT没真正和业务结合过。今天这个项目正好把整条链路补齐Python 采集招聘数据 - MySQL 存储 - pandas 清洗 - pyecharts 可视化最后形成一份能写进简历的“BOSS直聘数据可视化分析”项目。先看这个项目值不值得做它不是单纯调库画图而是把数据采集、数据入库、数据清洗、可视化分析、定时更新这一整条数据 pipeline 都走了一遍。对于后端岗位、数据分析岗位、Python 开发岗位的面试场景非常加分。项目成型后你可以向面试官讲清楚“数据从哪来、怎么存、怎么算、怎么展示”这比背面经更有说服力。这篇文章会按实际开发顺序拆解整个项目先给核心能力速览再讲环境准备和 MySQL 建库然后分别完成数据采集入库、数据清洗、可视化分析、批量更新最后给简历描述建议和常见问题排查。源码和数据集按标题约定整理在项目附件中建议配合本地环境逐步跑通。1. 核心能力速览在动手之前先用一张表看清这个项目的整体面貌能力项说明项目类型Python MySQL 数据采集与可视化分析项目主要功能招聘数据抓取入库、按岗位/城市/薪资维度聚合分析、图表可视化展示数据存储MySQL支持多表设计和 SQL 聚合统计采集方式requests BeautifulSoup / Selenium按目标平台反爬策略选择数据清洗pandas 处理空值、薪资统一、岗位标准化可视化方案pyecharts 或 matplotlib支持 HTML 动态图表批量更新支持定时任务重新采集、增量入库主要技术栈Python 3.x、MySQL 8.x、pandas、pyecharts、pymysql适合场景秋招简历项目、Python 数据分析练习、MySQL 实战练习合规提醒仅限个人学习研究采集前确认目标平台服务条款控制采集频率这个项目的核心价值在于它是一个“能讲完整链路”的项目不是只画一张图。面试时你可以从数据库表设计一直讲到可视化图表里的业务结论。2. 适用场景与使用边界这个项目适合三类人秋招学生缺少有业务背景的数据类项目需要一个能体现 Python、SQL、数据分析能力的完整案例。全栈开发入门者想练习 Python 操作 MySQL、用接口或爬虫获取数据、用图表组件做前端展示。数据分析学习者需要一份真实结构的数据集做分组聚合、清洗、透视分析。但要注意边界。BOSS直聘这类招聘平台有用户协议和反爬机制数据采集必须遵守以下原则仅采集公开可访问的页面信息不绕过登录验证、不破解验证码、不攻击接口。控制请求频率避免对目标服务器造成压力。采集数据仅用于个人学习、技术演示不用于商业用途不公开传播原始数据。如果项目要开源或写进公开简历建议对数据进行脱敏处理或使用项目提供的样例数据集代替实时采集。更稳妥的训练方式是先用项目附带的静态数据集完成 MySQL 建库、数据清洗和可视化跑通整条链路后再谨慎尝试实时采集。这样既不影响平台正常服务也能达到练习目的。3. 环境准备与前置条件建议使用以下环境版本不必完全一致但需要确保兼容软件建议版本备注Python3.9 或 3.103.11 对部分旧依赖包兼容性需要注意MySQL8.x5.7 也可以注意 SQL 语法差异pip最新版安装依赖需要IDEVS Code / PyCharm推荐 PyCharm Community调试方便需要安装的 Python 依赖pip install requests beautifulsoup4 pymysql pandas pyecharts如果使用 Selenium 方案还需要额外安装pip install seleniumMySQL 客户端工具可以用 Navicat、DBeaver 或者 MySQL Workbench。如果本机还没有 MySQL建议先完成 MySQL 的安装配置Root 账号和密码记好后面建库建表要用。环境检查清单[ ] Python 能正常执行pip install[ ] MySQL 服务已启动端口默认 3306[ ] 能通过命令行或客户端连接 MySQL[ ] 当前 Python 版本可以通过python --version查看4. MySQL 数据库设计与建表数据可视化分析的第一步不是画图而是设计好表结构。表结构决定后续聚合分析是否方便。这个项目建议设计两张核心表岗位信息表和公司信息表。岗位信息表job_infoCREATE DATABASE IF NOT EXISTS job_analysis DEFAULT CHARACTER SET utf8mb4; USE job_analysis; CREATE TABLE IF NOT EXISTS job_info ( id INT AUTO_INCREMENT PRIMARY KEY, job_name VARCHAR(255) COMMENT 岗位名称, city VARCHAR(50) COMMENT 工作城市, salary_min INT COMMENT 薪资下限单位K, salary_max INT COMMENT 薪资上限单位K, experience VARCHAR(50) COMMENT 经验要求, education VARCHAR(50) COMMENT 学历要求, company_name VARCHAR(255) COMMENT 公司名称, industry VARCHAR(255) COMMENT 行业, job_description TEXT COMMENT 岗位描述, fetch_date DATE COMMENT 采集日期, UNIQUE KEY uk_job_company_date (job_name, company_name, city, fetch_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT岗位信息表;公司信息表company_infoCREATE TABLE IF NOT EXISTS company_info ( id INT AUTO_INCREMENT PRIMARY KEY, company_name VARCHAR(255) UNIQUE COMMENT 公司名称, company_size VARCHAR(100) COMMENT 公司规模, financing_stage VARCHAR(100) COMMENT 融资阶段, industry VARCHAR(255) COMMENT 行业标签 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT公司信息表;表设计说明薪资字段拆成salary_min和salary_max方便后续做平均薪资、薪资区间分布分析。岗位唯一键使用(job_name, company_name, city, fetch_date)防止重复采集导致数据膨胀。fetch_date记录采集日期支持后续做 “某岗位薪资随时间变化” 的分析。建完表之后用SHOW TABLES;确认两张表都创建成功USE job_analysis; SHOW TABLES;5. 数据采集与入库数据采集是整个项目中门槛最高的一环。目标平台的页面结构、反爬策略、登录逻辑随时可能变化所以这一部分更要注重“思路可复用”。5.1 采集方式选择采集招聘数据有几种常见方式方式优点缺点适用场景静态页面 requests 请求速度快、代码简单容易被反爬拦截平台无登录限制Selenium 模拟浏览器可以处理动态渲染速度慢、占用资源高页面内容由 JS 异步加载官方开放 API合规、稳定多数平台不开放优先推荐但现实中很少静态数据集分析零风险、便于复现缺少实时性学习练手、简历讲解从项目教学角度最稳定的落地方式是“静态数据集为主requests 采集为辅”。先用项目提供的 CSV 或 JSON 数据集完成全流程再学采集逻辑。5.2 requests 采集通用模板如果目标页面是静态可访问的参考以下通用思路import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def fetch_page(url): try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() return resp.text except requests.RequestException as e: print(f请求失败{e}) return None def parse_job_html(html): soup BeautifulSoup(html, html.parser) # 实际选择器需要根据目标页面结构调整 job_items soup.select(.job-card) data [] for item in job_items: job_name item.select_one(.job-name).text.strip() if item.select_one(.job-name) else city item.select_one(.job-city).text.strip() if item.select_one(.job-city) else salary item.select_one(.job-salary).text.strip() if item.select_one(.job-salary) else company item.select_one(.company-name).text.strip() if item.select_one(.company-name) else data.append({ job_name: job_name, city: city, salary: salary, company: company }) return data if __name__ __main__: url https://example.com/jobs?querypython html fetch_page(url) if html: result parse_job_html(html) print(f解析到 {len(result)} 条数据)说明url和 CSS 选择器只是模板实际需要根据目标平台的页面结构调整。如果页面需要登录才能访问建议放弃实时采集改用静态数据集。5.3 数据写入 MySQL数据解析完成后使用 pymysql 写入 MySQLimport pymysql def save_to_mysql(data_list): connection pymysql.connect( host127.0.0.1, userroot, passwordyour_password, databasejob_analysis, charsetutf8mb4 ) try: with connection.cursor() as cursor: sql INSERT INTO job_info (job_name, city, salary_min, salary_max, company_name, fetch_date) VALUES (%s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE salary_min VALUES(salary_min), salary_max VALUES(salary_max) for item in data_list: salary_min, salary_max parse_salary(item.get(salary, )) cursor.execute(sql, ( item.get(job_name), item.get(city), salary_min, salary_max, item.get(company), item.get(fetch_date, 2026-01-01) )) connection.commit() finally: connection.close() def parse_salary(salary_text): # 20-35K - (20, 35) # 面议 - (None, None) if K not in salary_text: return None, None s salary_text.replace(K, ).replace(k, ) if - in s: parts s.split(-) return int(parts[0].strip()), int(parts[1].strip()) return None, None写入后验证SELECT COUNT(*) FROM job_info;如果行数大于 0说明入库成功。6. 数据清洗与薪资字段处理采集到的原始数据通常有噪声比如“薪资面议”、薪资单位不统一、岗位名为空、城市名带后缀等。清洗环节使用 pandas 完成。import pandas as pd import pymysql def load_data_from_mysql(): connection pymysql.connect( host127.0.0.1, userroot, passwordyour_password, databasejob_analysis, charsetutf8mb4 ) sql SELECT * FROM job_info df pd.read_sql(sql, connection) connection.close() return df def clean_data(df): # 1. 删除岗位名称为空的记录 df df.dropna(subset[job_name]) # 2. 删除没有薪资区间的记录 df df.dropna(subset[salary_min, salary_max]) # 3. 过滤异常数据 df df[(df[salary_min] 0) (df[salary_max] df[salary_min])] # 4. 计算平均薪资 df[salary_avg] (df[salary_min] df[salary_max]) / 2 # 5. 城市标准化去掉末尾的市字 df[city] df[city].str.replace(市, , regexFalse) return df df load_data_from_mysql() df clean_data(df) print(df[[job_name, city, salary_min, salary_max, salary_avg]].head())清洗的核心逻辑统一薪资单位全部换算成 K。删除薪资缺失的样本避免后续聚合出现空值。增加salary_avg列作为可视化分析的主要指标。对城市字段做标准化保证“北京”和“北京市”能正确聚合。这一步在面试中非常容易被问到重点考察你对脏数据的处理思路。不要只说“用 pandas 清洗”要能讲清每个字段的处理规则。7. 数据可视化分析与图表实现可视化是这个项目最直观的产出部分。使用 pyecharts 生成 HTML 图表优点是交互性好可以直接在浏览器里看也能嵌入到简历附带的演示项目中。7.1 岗位数量 Top10 柱状图from pyecharts.charts import Bar from pyecharts import options as opts def job_count_top10(df): top df[job_name].value_counts().head(10) bar ( Bar() .add_xaxis(top.index.tolist()) .add_yaxis(岗位数量, top.values.tolist()) .set_global_opts( title_optsopts.TitleOpts(title招聘岗位数量 Top10), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate30)) ) ) bar.render(output/job_count_top10.html)7.2 各城市平均薪资分析from pyecharts.charts import Bar def city_avg_salary(df): city_salary df.groupby(city)[salary_avg].mean().sort_values(ascendingFalse).head(15) bar ( Bar() .add_xaxis(city_salary.index.tolist()) .add_yaxis(平均薪资(K), city_salary.round(1).tolist()) .set_global_opts( title_optsopts.TitleOpts(title核心城市平均薪资对比), yaxis_optsopts.AxisOpts(name平均薪资(K)) ) ) bar.render(output/city_avg_salary.html)7.3 学历要求与薪资关系from pyecharts.charts import Boxplot def education_salary_box(df): edu_order [大专, 本科, 硕士, 博士] df df[df[education].isin(edu_order)] data [] labels [] for edu in edu_order: edu_data df[df[education] edu][salary_avg].tolist() data.append(edu_data) labels.append(edu) boxplot Boxplot() boxplot.add_xaxis(labels) boxplot.add_yaxis(薪资分布, boxplot.prepare_data(data)) boxplot.set_global_opts( title_optsopts.TitleOpts(title不同学历薪资分布), yaxis_optsopts.AxisOpts(name薪资(K)) ) boxplot.render(output/education_salary_box.html)7.4 经验要求与薪资散点图from pyecharts.charts import Scatter def experience_salary_scatter(df): x df[experience_year] y df[salary_avg] scatter ( Scatter() .add_xaxis(x.tolist()) .add_yaxis(平均薪资, y.tolist()) .set_global_opts( title_optsopts.TitleOpts(title经验年限与薪资关系), xaxis_optsopts.AxisOpts(name经验年限), yaxis_optsopts.AxisOpts(name平均薪资(K)) ) ) scatter.render(output/experience_salary_scatter.html)可视化结果不是画完图就结束。更合理的做法是每张图配一段分析结论比如从柱状图可以判断Java 开发、Python 开发、前端开发属于需求量最大的三个岗位。从城市薪资对比图可以看出热门岗位薪资中位数较高的城市通常是北京、上海、深圳、杭州。从学历箱线图可以发现硕士学历的薪资中位数明显高于本科但薪资下限和本科重合度较高说明学历不是唯一决定因素。这些结论才是简历中“数据分析”部分的关键素材。8. 批量更新与定时任务数据可视化项目最常见的进阶需求是定期更新数据让图表保持实时性。这时需要批量采集和定时任务。8.1 批量采集目录结构建议将采集任务按关键词拆分成多个岗位类型{ keywords: [Python开发, Java开发, 前端开发, 数据分析], city: [北京, 上海, 广州, 深圳], max_page: 3 }对应采集逻辑import time import json with open(config.json, r, encodingutf-8) as f: config json.load(f) for keyword in config[keywords]: for city in config[city]: for page in range(1, config[max_page] 1): url build_url(keyword, city, page) html fetch_page(url) items parse_job_html(html) save_to_mysql(items) time.sleep(2) # 控制访问频率避免对目标服务器造成压力注意time.sleep(2)不是可有可无的。任何采集任务都应该有频率控制这是合规底线。8.2 定时任务调度在 Windows 下可以用计划任务在 Linux 下可以用 crontab。Python 内部的轻量方案是schedule库pip install scheduleimport schedule import time def job(): print(开始采集更新...) run_collector() run_visualization() print(采集和可视化更新完成) schedule.every().day.at(09:00).do(job) while True: schedule.run_pending() time.sleep(60)定时任务跑起来后整个项目就从“一次性分析”变成了“可持续更新的数据服务”这在简历上是一个明显的加分项。9. 接口 API 封装与 Web 展示如果希望项目可演示性更强可以把可视化结果封装成 Flask 接口让图表通过网页访问。这会让项目的工程化程度更高面试时也更有的讲。9.1 安装 Flaskpip install flask9.2 创建可视化服务from flask import Flask, render_template app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/charts/job_count) def job_count_chart(): return render_template(job_count_top10.html) app.route(/charts/city_salary) def city_salary_chart(): return render_template(city_avg_salary.html) if __name__ __main__: app.run(host127.0.0.1, port8000, debugTrue)将 pyecharts 生成的 HTML 放在templates目录中启动服务后访问http://127.0.0.1:8000/即可看到图表导航页。有人可能会问这不就是一个静态页面吗为什么算加分项因为很多同学的简历项目只停留在 Jupyter Notebook 里而封装成 Web 服务之后你讲的是“数据采集 - 存储 - 分析 - 展示”的完整闭环这在工程思维上是一个明显的区别。10. 简历上怎么写这个项目项目做完了最终目标是写进简历。这部分用表格给出可复用的项目描述模板项目名称BOSS直聘数据可视化分析系统项目时间2026.01 - 2026.02项目描述基于 Python 和 MySQL 的招聘数据分析项目实现了招聘数据采集、清洗、存储、可视化分析全流程核心技术Python、requests、pandas、pyecharts、MySQL、Flask责任描述1. 使用 requests 和 BeautifulSoup 采集招聘岗位数据并对脏数据进行清洗和标准化处理2. 使用 pandas 完成数据聚合计算不同城市、岗位类型、学历要求的平均薪资分布3. 设计 MySQL 岗位表和公司表实现数据持久化存储和增量更新4. 使用 pyecharts 生成岗位需求 Top10、城市薪资对比、学历薪资分布等可视化图表5. 使用 Flask 封装可视化结果支持浏览器端展示和定时更新简历描述的关键是把“会什么”变成“做了什么”。例如“熟悉 pandas”只是一句技能列表“使用 pandas 完成 5000 条招聘数据的薪资字段拆解和城市标准化处理”才是项目经验。11. 常见问题与排查方法这个项目在本地跑的时候会有很多小坑列几个高频问题问题现象可能原因排查方式解决方案MySQL 连接失败服务未启动或密码错误检查 MySQL 服务、root 密码启动 MySQL 服务确认连接参数pip install 报错Python 环境冲突pip --version查看当前环境使用虚拟环境 venv 隔离依赖编码乱码数据源编码不统一chardet检测页面编码统一 utf-8 编码写入数据库数据重复唯一键缺失查询 job_info 表约束设置联合唯一键图表中文乱码pyecharts 未指定字体检查渲染日志在全局配置中指定中文字体请求被拒绝请求频率过高查看响应状态码增加 sleep 间隔降低采集频率Flask 页面打不开端口被占用检查 8000 端口占用情况更换端口如 8001遇到问题先看日志这是通用原则。Python 报错信息会直接指出脚本、行号和异常类型逐个处理即可。12. 最佳实践与工程化建议项目跑通之后建议再花一点时间把代码组织得更加工程化。下面几条是实际开发中最实用的习惯第一脚本启动入口保持简单。主程序只负责读取配置、调度模块不要让采集、清洗、可视化逻辑堆在同一个文件里。建议目录结构如下project/ ├── config.json # 采集参数配置 ├── requirements.txt # 依赖清单 ├── collector/ │ ├── fetcher.py # 页面请求 │ ├── parser.py # 页面解析 │ └── saver.py # MySQL 入库 ├── analysis/ │ ├── clean.py # 数据清洗 │ └── visualize.py # 图表生成 ├── web/ │ └── app.py # Flask 服务 ├── data/ │ └── sample_dataset.csv # 样例数据集 └── output/ └── *.html # 生成的图表第二模型文件、输入数据、输出结果分开目录管理。采集到的原始数据、清洗后的中间数据、最终可视化 HTML 分别存放在不同目录避免文件混乱。第三批量任务必须加日志和失败重试。采集过程中网络不稳定是常态建议封装一个带重试机制的请求函数import time def fetch_with_retry(url, retries3, delay2): for attempt in range(retries): try: return fetch_page(url) except Exception as e: print(f第 {attempt 1} 次请求失败{e}) if attempt retries - 1: time.sleep(delay) return None第四MySQL 写入建议使用事务保证数据一致性。pymysql 默认开启事务通过connection.commit()提交失败时调用connection.rollback()回滚防止数据半写状态。第五接口服务只监听127.0.0.1不直接暴露到公网。如果确实需要远程访问必须加访问控制和认证。第六涉及相关隐私和版权问题时务必谨慎。无论是招聘平台的数据还是其他第三方数据在项目演示和简历展示时都不要展示可识别到个人的敏感信息。本项目建议使用脱敏后的样例数据集进行展示。13. 总结与下一步这个项目最值得尝试的点就在于它不是一个只有单个知识点的 Demo而是把 Python 爬虫、MySQL 存储、pandas 清洗、pyecharts 可视化和 Flask Web 展示串成了一条完整的数据处理链路。对于秋招想积累项目经验的同学来说这一个项目能把好几个高频考点覆盖到。跑通之后建议最先验证的功能是从数据集到 MySQL 的入库链路因为这一步成功之后后面所有分析都有数据可依。最容易踩的坑也在这个环节MySQL 连接不成功、薪资字段清洗不彻底、中文编码乱码这三个问题占了初学者调试时间的一大半。下一步可以从三个方向继续深入一是把采集部分换成更稳定的官方 API 或合规数据源让数据源不受页面结构调整影响二是把可视化从静态 HTML 升级成关联交互的大屏页面三是加入更多业务分析维度比如岗位技能要求词云、薪资涨跌趋势、行业分布对比。每个方向都足够延伸成新的小项目进度允许的话建议优先做第三个方向因为它对数据分析面试的说服力最强。如果你正好在秋招准备期建议收藏备用按文章顺序一步一步搭出来。这个项目的工程量不大但技术链路完整值得投入两到三天时间。