Flask + ECharts 就业岗位数据分析可视化大屏实战
Flask + ECharts 就业岗位数据分析可视化大屏实战:8,016 条真实招聘数据的完整毕业设计
摘要(先说结论):本文完整复盘一个基于 Flask + ECharts + MySQL 的岗位数据分析与可视化大屏系统——从 Requests 爬虫采集 8,016 条真实岗位数据,到 pandas 数据清洗入库,再到 16 个 RESTful API 与 12 个可视化模块的三栏式 1920×1080 大屏,最后通过 64 并发压力测试(成功率 100%,平均响应 481ms)。文中给出可复用的项目结构、核心代码(含 MySQL 并发连接竞态的线程锁解决方案)、以及三个踩坑实录。适合做毕业设计选型、就业数据可视化项目参考。
关键词:Flask 可视化大屏、ECharts 数据分析、就业岗位分析、Python 毕业设计、数据可视化、MySQL 并发、数据爬虫
本文数据+代码:https://pan.baidu.com/s/13zhkvQpVnJqK3BXkjmOUlA 提取码: 46y8

一、这个项目是什么?能解决什么问题?
岗位数据分析可视化大屏是一个面向高校毕业生就业决策的数据系统:它采集国家大学生就业服务平台的公开招聘数据,经过清洗、结构化后存入 MySQL,再通过 Flask 提供的 16 个 RESTful API,在前端以 ECharts 大屏形式呈现全国岗位的区域分布、薪资水平、学历要求、企业规模等 12 个维度的分析结果,并支持多维条件查询与交互式钻取。
1.1 它解决的三个核心问题
| 问题 | 传统做法 | 本系统做法 |
|---|---|---|
| 岗位信息分散,逐页浏览效率低 | 人工翻页对比 | 8,016 条数据聚合为 12 个图表维度,一屏总览 |
| 薪资/学历/区域缺乏交叉视角 | 只能单点查看 | 学历-薪资交叉分析、区域-薪资联动地图 |
| 数据时效性差,无法追溯来源 | 截图留存 | 数据来源弹窗展示采集→清洗→入库全链路 |
1.2 数据规模(真实实测,非估算)
- 有效岗位数据:8,016 条(Requests 爬虫全量采集、去重后)
- 招聘单位:2,789 家
- 覆盖区域:33 个省级行政区(含港澳台)
- 招聘总人数:138,713 人
- 全国平均招聘月薪:约 7.2 千元
这组数据是 2026 年从国家大学生就业服务平台实际采集清洗后的统计结果,可作为高校就业市场分析的参考样本。
二、技术栈如何选型?一张表说清楚
| 层次 | 技术 | 版本 | 选型理由 |
|---|---|---|---|
| 后端框架 | Flask | 3.x | 轻量微框架,路由即 API,适合中小型数据服务 |
| 数据库 | MySQL | 8.x | 关系型存储,SQL 聚合分析能力成熟 |
| 数据库驱动 | mysql-connector-python | 8.x | 官方 C 扩展驱动,性能优于纯 Python 实现 |
| 数据处理 | pandas | 2.x | Excel 读取、缺失值处理、字段派生 |
| 中文分词 | jieba | 0.42 | 专业要求文本分词,支撑词云统计 |
| 前端图表 | ECharts | 5.x | 国产开源图表库,地图/词云/矩形树图一站支持 |
| 爬虫 | requests | 2.x | 平台接口为公开 JSON,无需浏览器自动化 |
为什么选 Flask 而不是 Django? 本系统的后端职责单一——对外提供只读的统计分析 API,没有后台管理、ORM 复杂模型、用户认证等需求。Flask 的"一个 app.py + 路由函数"模式让 16 个 API 的实现保持在 355 行代码内,答辩时代码走查负担小;Django 在此场景下属于"杀鸡用牛刀"。
为什么用 ECharts 而不是 D3.js? ECharts 声明式配置项(option)的开发效率远高于 D3 的命令式 DOM 操作,且原生支持中国地图(需配合 GeoJSON)、词云(echarts-wordcloud 扩展)、矩形树图等 12 种本系统用到的图表类型,对中文场景的字体渲染和 tooltip 格式化支持也更完善。
三、项目结构怎么组织?
flask_project/
├── app.py # Flask 主应用:16 个 API 路由(355 行)
├── config.py # MySQL 连接配置
├── requirements.txt # 依赖清单
├── data/
│ ├── 招聘信息.xlsx # 爬虫采集的原始数据
│ └── cleaned_data.csv # 清洗后的标准化数据
├── scripts/
│ ├── 数据爬取.py # Requests 爬虫
│ ├── data_clean.py # 数据清洗与入库
│ └── tools/ # 截图采集与文档构建脚本(复现用)
├── templates/
│ └── index.html # 大屏单页(原生 JS + ECharts)
├── static/
│ ├── js/china.js # 中国地图 GeoJSON(含九段线)
│ ├── js/echarts.min.js # 本地化图表库
│ └── js/wordcloud.min.js # 本地化词云扩展
├── docs/ # 论文 docx 与答辩 pptx
├── screenshots/ # 系统截图
└── blog/ # 博文 markdown 源文件
结构设计的两个原则:
- 数据、脚本、静态资源、文档四分离——
data/存数据、scripts/存离线流程、static/存前端资源、docs/存交付文档,根目录只保留入口文件。 - 路径全部用
os.path动态解析,脚本从根目录或scripts/目录执行都能正确找到config.py与数据文件(下文代码会展示)。

对应的数据流程:爬虫采集(requests)→ 数据清洗(pandas + jieba)→ MySQL 存储 → Flask API 聚合 → ECharts 前端渲染,五个环节单向流转,每个环节可独立复现。
四、数据从哪来?爬虫与数据清洗
4.1 爬虫:requests 采集公开接口
国家大学生就业服务平台的岗位列表为公开 JSON 接口,爬虫核心逻辑是分页请求 + 字段抽取 + 去重落盘:
# scripts/数据爬取.py(核心片段)
import requests, time
import pandas as pd
all_rows = []
for page in range(1, MAX_PAGE + 1):
resp = requests.post(API_URL, json={
'pageIndex': page, 'pageSize': 20,
'keyword': '', # 全量采集
}, headers=HEADERS, timeout=10)
data = resp.json()['data']
rows = data['list'] if data else []
if not rows:
break
for r in rows:
all_rows.append({
'职位名称': r['jobName'],
'公司名称': r['companyName'],
'最高月薪': r.get('salaryMax'),
'最低月薪': r.get('salaryMin'),
'地区': r.get('workPlace'),
'学历要求': r.get('education'),
'专业要求': r.get('major'),
'招聘人数': r.get('recruitNum'),
})
time.sleep(0.8) # 限速,避免对平台造成压力
df = pd.DataFrame(all_rows).drop_duplicates(subset=['职位名称', '公司名称'])
df.to_excel('data/招聘信息.xlsx', index=False)
采集要点:每页请求间隔 0.8 秒做礼貌性限速;以"职位名称 + 公司名称"组合去重,最终得到 8,016 条有效记录。
4.2 清洗:pandas 标准化 + 派生字段
清洗脚本解决四类问题:缺失薪资、月薪区间取均值、地区归一化为省份、学历档位标准化:
# scripts/data_clean.py(核心片段)
df = pd.read_excel(EXCEL_PATH)
# 1. 派生平均月薪:区间中值
df['平均月薪'] = (df['最高月薪'] + df['最低月薪']) / 2
df.loc[df['平均月薪'].isna() | (df['平均月薪'] <= 0), '平均月薪'] = np.nan
# 2. 地区 → 省份:取前两字匹配(如"浙江省杭州市"→"浙江")
def to_province(loc):
if not isinstance(loc, str):
return None
loc = re.sub(r'[省市区自治区特别行政壮族回族维吾尔]', '', loc[:3])
return loc[:2]
df['省份'] = df['地区'].apply(to_province)
# 3. 学历标准化:映射到统一档位
EDU_MAP = {'博士研究生': '博士及以上', '硕士研究生': '硕士及以上',
'本科': '本科及以上', '大学专科': '专科及以上'}
df['学历要求'] = df['学历要求'].map(EDU_MAP).fillna('不限')
# 4. 入库 MySQL
cursor.executemany(INSERT_SQL, df[COLS].itertuples(index=False))
清洗后的数据进入 job_positions 单表,utf8mb4 字符集,17 个字段覆盖职位、公司、薪资、地域、学历、规模、标签等维度。
五、16 个 RESTful API 如何设计?
API 分四类,全部返回 JSON,统一通过 query_db 执行参数化 SQL:
| 类别 | 端点 | 功能 |
|---|---|---|
| KPI | /api/overview | 总岗位数、平均月薪、招聘总人数、省份数、企业数 |
| 地图 | /api/salary_by_region | 各省平均月薪 |
| 地图 | /api/job_count_by_region | 各省岗位数量 |
| 地图 | /api/headcount_by_region | 各省招聘需求(人数) |
| 图表 | /api/salary_distribution | 薪资区间直方图分桶 |
| 图表 | /api/education_requirements | 学历要求占比 |
| 图表 | /api/education_salary | 学历-薪资交叉分析 |
| 图表 | /api/company_size_analysis | 企业规模分布与薪资 |
| 图表 | /api/job_type_distribution | 职位性质(全职/实习)分布 |
| 图表 | /api/profession_wordcloud | jieba 分词统计的专业词云 |
| 图表 | /api/top_positions | 招聘量 Top 岗位榜 |
| 图表 | /api/title_keywords_rank | 职位名称关键词排行 |
| 溯源 | /api/data_source_analysis | 采集来源、时间范围、清洗统计 |
| 查询 | /api/jobs | 多条件组合查询 + 分页 |
| 查询 | /api/filters | 筛选选项(省份/学历/性质) |
| 轮询 | /api/latest_jobs | 最新岗位滚动播报 |
5.1 核心:并发安全的数据库访问层
16 个 API 共用一个连接函数,这里有一个实战中最有价值的坑——MySQL 官方驱动的 C 扩展在多线程同时建连时会竞争认证插件导致报错。解决方案是给建连过程加线程锁:
# app.py
import threading
import mysql.connector
_db_lock = threading.Lock()
def get_db_connection():
with _db_lock: # 串行化建连,规避并发认证插件竞争
return mysql.connector.connect(**MYSQL_CONFIG)
def query_db(sql, params=None):
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
cursor.execute(sql, params or ())
results = cursor.fetchall()
cursor.close()
conn.close()
return results
5.2 多维组合查询 API(含 SQL 注入防护)
/api/jobs 支持 关键词 + 省份 + 学历 + 职位性质 + 薪资区间 五个维度的动态组合,用参数化查询拼接 WHERE 子句:
# app.py:动态条件拼接(全部参数化,杜绝 SQL 注入)
where, params = [], []
if keyword:
where.append("(职位名称 LIKE %s OR 公司名称 LIKE %s)")
params.extend([f"%{keyword}%", f"%{keyword}%"])
if province:
where.append("省份 = %s")
params.append(province)
if salary_range:
ranges = {'0-5k': (0, 5), '5-8k': (5, 8), '8-12k': (8, 12),
'12-20k': (12, 20), '20k+': (20, 10000)}
if salary_range in ranges:
low, high = ranges[salary_range]
where.append("平均月薪 >= %s AND 平均月薪 < %s")
params.extend([low, high])
rows = query_db(
f"""SELECT 职位名称, 公司名称, 地区, 学历要求, 职位性质, 平均月薪, 招聘人数
FROM job_positions {where_clause}
ORDER BY (平均月薪 IS NULL), 平均月薪 DESC
LIMIT %s OFFSET %s""",
params + [page_size, (page - 1) * page_size]
)
page_size 强制收敛到 [1, 50] 区间,防止恶意大分页拖垮数据库。
六、可视化大屏如何实现?12 个图表模块
大屏采用三栏式 1920×1080 单屏布局(不滚动),深空科技风配色(背景 #060D1F + 青色 #2EDEFF 强调色):

6.1 12 个图表模块清单
| 位置 | 模块 | 图表类型 | 数据要点 |
|---|---|---|---|
| 顶栏 | KPI 核心指标 | 数字滚动卡片 | 8,016 岗位 / 7.2k 均薪 / 2,789 企业 / 33 省份 |
| 左栏 | 学历要求分布 | 玫瑰图 | 专科及以上占比最高(3,171条) |
| 左栏 | 学历-薪资分析 | 分组柱状图 | 学历与薪资正相关 |
| 左栏 | 薪资区间分布 | 直方图 | 6-9k(2,724条)与 3-6k(2,253条)最集中 |
| 中栏 | 全国分布地图 | 涟漪散点地图 | 北京、广东、江苏、四川、上海领先 |
| 中栏 | 热门岗位榜 | 动态榜单 | 招聘量 Top 排行 |
| 中栏 | 最新岗位播报 | 无缝滚动列表 | 轮询 /api/latest_jobs |
| 右栏 | 专业需求词云 | 词云 | jieba 分词,"自动化、技术、管理"高频 |
| 右栏 | 职位性质分布 | 环形图 | 全职/实习占比 |
| 右栏 | 企业规模分布 | 矩形树图 | 100-499 人规模企业最多 |
| 右栏 | 职位名称关键词 | 条形图 | "工程师/专员/教师"等高频词 |
左栏三块分析面板的实际效果:


6.2 3 个交互功能
- 岗位查询中心:点击顶部按钮弹出模态框,五维条件组合筛选 + 分页浏览,点击地图省份自动填入查询条件(联动钻取);
- 图表放大查看:每个面板带放大按钮,点击后模态框全屏呈现图表细节,适合答辩演示;
- 数据来源分析:点击 KPI 卡片弹出数据链路弹窗,展示采集来源、时间范围、清洗规则,保证数据可溯源。


6.3 词云:jieba 分词统计的实现
后端对 8,016 条岗位的"专业要求"文本做分词与词频统计:
# app.py:/api/profession_wordcloud
counter = Counter()
for (text,) in query_db("SELECT 专业要求 FROM job_positions WHERE 专业要求 IS NOT NULL"):
for word in jieba.cut(text):
if len(word) >= 2 and word not in STOPWORDS:
counter[word] += 1
return jsonify([{'name': w, 'value': c} for w, c in counter.most_common(80)])
统计结果显示:词频 Top5 为"自动化(1,776 次)、技术(1,662 次)、管理(1,547 次)、工程(1,432 次)、计算机(779 次)",直观反映了就业市场的技能导向。

6.4 数据洞察:8,016 条岗位告诉我们什么?
以下结论均来自系统 API 的实测统计(2026 年采集数据):
- 岗位数量 Top5 省份:北京(1,246 条)、广东(1,131 条)、江苏(764 条)、四川(632 条)、上海(547 条)——岗位高度集聚于京津冀、长三角、珠三角与成渝四大经济圈;
- 薪资最高地区:香港(9.6k/月)、北京(8.3k/月)、贵州(8.0k/月)、湖南(7.9k/月)、上海(7.6k/月);
- 学历-薪资正相关:博士及以上平均 25.4k、硕士及以上 10.9k、本科及以上 7.0k、专科及以上 6.6k——学历每上一个台阶,平均薪资接近翻倍;
- 学历要求主力:专科及以上(3,171 条)与本科及以上(2,989 条)合计占比约 77%,是就业市场绝对主力;
- 企业规模:100-499 人规模企业发布岗位最多(1,327 条),5000 人以上大型企业紧随其后(1,249 条)。

七、三个踩坑实录(含解决方案)
坑 1:MySQL 并发建连报错(2013 Lost connection)
现象:单线程调试一切正常,一旦用线程池压测,随机抛出认证插件相关异常。
原因:mysql-connector-python 的 C 扩展在多线程同时调用 connect() 时,认证插件的加载过程存在竞争条件。
解决:见 5.1 节的 threading.Lock() 方案——锁只包住建连(毫秒级),不包住查询执行,因此不损伤并发吞吐。实测 64 并发下成功率 100%。
坑 2:localhost 连接慢(每次建连延迟 1 秒+)
现象:API 首次访问明显卡顿,MySQL 建连耗时异常。
原因:Windows 下 localhost 会先尝试 IPv6(::1)解析,而 MySQL 只监听 IPv4(127.0.0.1),产生解析超时后回退。
解决:config.py 中 host 明确写 127.0.0.1,杜绝 IPv6 回退延迟:
MYSQL_CONFIG = {
'host': '127.0.0.1', # 不要写 localhost:避免 IPv6 解析回退
'port': 3306,
...
}
坑 3:ECharts 中国地图缺九段线与南海诸岛
现象:网上流传的部分 china.js GeoJSON 裁掉了南海诸岛与九段线,地图不完整(在学术与发布场景属于严重问题)。
解决:使用完整的中国地图 GeoJSON,并在地图配置中显式加入九段线要素与"南海诸岛"标注:
// index.html(核心配置)
geo: {
map: 'china',
zoom: 1.02,
center: [104.3, 29.6],
regions: [{
name: '九段线',
itemStyle: { areaColor: 'rgba(46, 222, 255, 0.95)' },
tooltip: { show: false }
}],
graphic: [{
type: 'text',
left: '60%', top: '89%',
style: { text: '南海诸岛', fill: '#9CC3F0', font: '12px sans-serif' }
}]
}
八、并发压力测试结果
用 Python ThreadPoolExecutor(requests + concurrent.futures)对主要数据接口做三轮递增压测,多线程模拟并发用户、每用户循环请求不同接口:
| 测试轮次 | 并发数 | 请求总数 | 成功率 | 平均响应 | P95 响应 |
|---|---|---|---|---|---|
| 第一轮 | 16 线程 | 128 | 100% | 141 ms | 218 ms |
| 第二轮 | 32 线程 | 256 | 100% | 301 ms | 629 ms |
| 第三轮 | 64 线程 | 512 | 100% | 481 ms | 889 ms |
结论:在 64 并发(远超毕业答辩演示场景)下系统仍保持 100% 成功率与亚秒级平均响应,"每次请求建连-查询-关连"的短连接模式配合建连锁在此数据量级下完全够用;若追求更高吞吐,可引入数据库连接池(如 DBUtils.PooledDB)。
九、FAQ:常见问题
Q1:Flask 开发服务器能用于生产环境吗?
不能。Flask 内置服务器是单进程调试服务器,仅适合开发与答辩演示。生产部署应使用 Gunicorn(Linux)或 Waitress(Windows)等 WSGI 服务器,配合 Nginx 反向代理。本系统 64 并发压测结果是在开发服务器上取得的,仅用于验证代码逻辑层面的并发正确性。
Q2:ECharts 大屏如何保证图表在数据返回前不报错?
初始化图表时先渲染空 option(空坐标轴),fetch 数据回来后再 setOption 填充。所有 API 调用包在 try/catch 中,失败时图表区域展示占位提示而非 JS 异常,保证大屏"永不白屏"。
Q3:8,016 条数据为什么不用缓存?
单表 8,016 行的聚合查询(GROUP BY 省份/学历)在 MySQL 中执行时间在 10ms 级,加缓存属于过度设计。当数据量达到 10 万级以上时,再考虑 Redis 缓存聚合结果或预计算汇总表。
Q4:这个项目适合作为本科毕业设计吗?
适合。它覆盖了完整的数据链路:爬虫采集 → pandas 清洗 → MySQL 建模 → Flask API → ECharts 可视化 → 压力测试,工作量饱满且每一环都有可答辩的技术点(如并发锁、SQL 注入防护、地图合规)。按此路线扩展时间序列预测(ARIMA/Prophet)或推荐模块可进一步扩充为硕士级工作量。
Q5:数据可以商用吗?
本系统采集的是公开页面数据,仅用于学术研究与教学演示。商用需评估平台 robots 协议与相关法律法规,并控制采集频率。
十、总结与展望
本项目交付了一个数据链路完整、经过并发验证的就业岗位分析可视化大屏:8,016 条真实招聘数据、16 个 RESTful API、12 个可视化模块、3 项交互功能,以及一篇数据可溯源的论文与答辩 PPT。三个实战踩坑(MySQL 并发建连锁、IPv6 解析延迟、地图 GeoJSON 合规)都具有直接的复用价值。
可扩展方向:
- 时间维度:定时增量采集,引入岗位数量与薪资的时间序列趋势图与预测模型;
- 推荐系统:基于专业标签的岗位-学生画像匹配;
- 性能:DBUtils 连接池 + Redis 聚合缓存,支撑万级并发;
- 部署:Docker 容器化 + Gunicorn + Nginx,一键部署。
完整代码结构见第三节项目结构图。如果本文对你有帮助,欢迎点赞收藏;评论区欢迎交流 Flask 大屏与就业数据分析的实践问题。
版权声明:本文为原创技术复盘,数据采集自公开渠道仅用于学术研究,转载请注明出处。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)