Python+MySQL招聘数据可视化分析项目:从爬虫到图表全流程
2026/8/31 12:00:19 网站建设 项目流程

秋招季最头疼的问题之一,就是简历上缺一个有数据链路、有业务场景、还能说清楚技术细节的项目。很多同学学完 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. 适用场景与使用边界

这个项目适合三类人:

  1. 秋招学生:缺少有业务背景的数据类项目,需要一个能体现 Python、SQL、数据分析能力的完整案例。
  2. 全栈开发入门者:想练习 Python 操作 MySQL、用接口或爬虫获取数据、用图表组件做前端展示。
  3. 数据分析学习者:需要一份真实结构的数据集做分组聚合、清洗、透视分析。

但要注意边界。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 selenium

MySQL 客户端工具可以用 Navicat、DBeaver 或者 MySQL Workbench。如果本机还没有 MySQL,建议先完成 MySQL 的安装配置,Root 账号和密码记好,后面建库建表要用。

环境检查清单:

  • [ ] Python 能正常执行pip install
  • [ ] MySQL 服务已启动,端口默认 3306
  • [ ] 能通过命令行或客户端连接 MySQL
  • [ ] 当前 Python 版本可以通过python --version查看

4. MySQL 数据库设计与建表

数据可视化分析的第一步不是画图,而是设计好表结构。表结构决定后续聚合分析是否方便。

这个项目建议设计两张核心表:岗位信息表和公司信息表。

岗位信息表job_info

CREATE 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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='岗位信息表';

公司信息表company_info

CREATE 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 '行业标签' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司信息表';

表设计说明:

  • 薪资字段拆成salary_minsalary_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, headers=headers, timeout=10) 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?query=python" html = fetch_page(url) if html: result = parse_job_html(html) print(f"解析到 {len(result)} 条数据")

说明:url和 CSS 选择器只是模板,实际需要根据目标平台的页面结构调整。如果页面需要登录才能访问,建议放弃实时采集,改用静态数据集。

5.3 数据写入 MySQL

数据解析完成后,使用 pymysql 写入 MySQL:

import pymysql def save_to_mysql(data_list): connection = pymysql.connect( host="127.0.0.1", user="root", password="your_password", database="job_analysis", charset="utf8mb4" ) 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( host="127.0.0.1", user="root", password="your_password", database="job_analysis", charset="utf8mb4" ) 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("市", "", regex=False) 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_opts=opts.TitleOpts(title="招聘岗位数量 Top10"), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=30)) ) ) 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(ascending=False).head(15) bar = ( Bar() .add_xaxis(city_salary.index.tolist()) .add_yaxis("平均薪资(K)", city_salary.round(1).tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="核心城市平均薪资对比"), yaxis_opts=opts.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_opts=opts.TitleOpts(title="不同学历薪资分布"), yaxis_opts=opts.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_opts=opts.TitleOpts(title="经验年限与薪资关系"), xaxis_opts=opts.AxisOpts(name="经验年限"), yaxis_opts=opts.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", encoding="utf-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 schedule
import 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 安装 Flask

pip install flask

9.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(host="127.0.0.1", port=8000, debug=True)

将 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, retries=3, delay=2): 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 升级成关联交互的大屏页面;三是加入更多业务分析维度,比如岗位技能要求词云、薪资涨跌趋势、行业分布对比。每个方向都足够延伸成新的小项目,进度允许的话,建议优先做第三个方向,因为它对数据分析面试的说服力最强。

如果你正好在秋招准备期,建议收藏备用,按文章顺序一步一步搭出来。这个项目的工程量不大,但技术链路完整,值得投入两到三天时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询