ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python招聘数据分析系统:Flask+MySQL实战搭建

Python招聘数据分析系统:Flask+MySQL实战搭建 简介本资源是一套完整的基于Python的招聘数据可视化分析系统毕业设计项目面向计算机、数据科学及相关专业本科生解决招聘数据采集、清洗、分析与可视化落地的实际问题兼具课程设计与毕业设计双重适用性。压缩包共59个文件含9个核心Python源码如爬虫handle_crawl_tencent.py、Flask后端run.py、2个MySQL建表与测试数据SQL脚本、1个PPTX演示文稿、7个PNG/JPG图表素材及静态资源JS/CSS/HTML总大小9.93MB其中tencentflask为Flask后端模块mysql数据库文件夹提供可直接导入的结构化招聘数据README.md与演示文稿清晰呈现系统架构与操作逻辑。目前已有50人学习下载读者可直接部署运行获得从腾讯/前程无忧双源爬取、本地MySQL存储、多维度统计分析到交互式图表展示的全链路实现方案包含城市坐标映射、职位热力图、行业分布饼图等典型可视化案例代码规范、模块解耦清晰适合作为工程实践范例深入学习与二次开发。1. 用 Python 搭建招聘数据可视化分析系统不是做一张热力图就完事——它得能连真实 MySQL 库、跑通 Flask 接口、支持岗位关键词动态筛选且新手照着命令就能在本地跑起来招聘数据可视化分析系统常被误认为是“用 pandas 读个 CSV matplotlib 画几条折线”的练习项目。但真实业务场景里它必须对接持续更新的招聘数据库比如企业自建的 MySQL提供 Web 界面供 HR 实时查看岗位热度、薪资分布、技能词云和地域聚类前端要能点选城市/行业/经验要求实时刷新图表后端得处理 SQL 查询参数注入防护、异步加载大表、缓存高频查询结果。本系统以 Python 为核心栈明确依赖 Flask 作为轻量 Web 框架、MySQL 为持久化存储、PlotlyDash 或 MatplotlibJinja2 为可视化路径——不引入 Vue/React 等前端框架降低部署门槛不依赖云服务或 SaaS API所有组件均可在本地 Linux/macOS/WindowsWSL环境离线安装配置。适合刚学完 Python 基础、接触过 SQL 和简单 Web 开发的工程师用 2 小时完成从环境搭建到首页图表渲染的完整闭环。后续可无缝接入爬虫模块自动抓取主流招聘平台公开数据或对接企业内部 HRM 系统导出的 Excel/CSV。2. 为什么选 Flask MySQL 组合对比 FastAPI/Django/SQLite 的实际约束与落地成本2.1 Flask 的轻量性直接决定开发效率和部署灵活性招聘分析系统核心诉求是快速验证数据逻辑与交互流程而非高并发用户认证或复杂权限体系。Flask 无内置 ORM、无强制项目结构、路由定义即函数一个app.py文件即可启动服务from flask import Flask, render_template, request, jsonify import mysql.connector from mysql.connector import Error app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/api/salary_dist) def salary_dist(): conn create_db_connection() cursor conn.cursor() cursor.execute(SELECT avg_salary, city FROM jobs WHERE avg_salary 0 ORDER BY avg_salary DESC LIMIT 50) data cursor.fetchall() cursor.close() conn.close() return jsonify({data: data})提示此处create_db_connection()需封装连接池逻辑避免每次请求新建连接。Flask 自带开发服务器足够支撑单机分析场景生产环境只需gunicorn app:app启动无需 Nginx 反向代理即可对外提供 HTTP 接口。2.2 MySQL 是招聘数据关系型建模不可替代的选择招聘数据天然具备强关联性岗位jobs→ 公司companies→ 行业industries→ 技能标签skills→ 地域cities。SQLite 虽免配置但无法支持多用户并发写入HR 同时导出报表会锁表、缺乏远程访问能力无法让 BI 工具直连、不支持存储过程实现复杂统计如按城市经验学历三维度交叉计算平均薪资。而 MySQL 8.0 提供窗口函数ROW_NUMBER() OVER (PARTITION BY city ORDER BY avg_salary DESC)可精准提取各城市 Top3 高薪岗位其 JSON 类型字段能灵活存储技能列表[Python, SQL, TensorFlow]避免传统多对多中间表带来的 JOIN 性能损耗。对比 PostgreSQLMySQL 在 Windows 下安装包体积更小官方 MSI 安装器仅 400MB、Navicat 连接兼容性更好、社区教程更聚焦于业务场景而非地理空间扩展。2.3 放弃 FastAPI 的根本原因同步阻塞式数据查询更贴合分析场景FastAPI 默认异步但招聘数据查询本质是 CPU-bound聚合计算而非 I/O-bound网络请求。SELECT COUNT(*), AVG(avg_salary) FROM jobs GROUP BY city这类语句在 MySQL 中由存储引擎直接执行Python 层无需await。强行套用 async/await 反而增加调试复杂度——当需要调用pandas.read_sql()加载数据时该函数本身不支持异步必须用loop.run_in_executor()包装代码可读性断崖下降。实测在 10 万条岗位记录下Flask 同步视图响应时间稳定在 120ms 内满足内部系统“秒级反馈”要求。2.4 Django 过重ORM 自动生成的迁移文件反而阻碍快速迭代招聘分析需求变化频繁本周要加“远程办公标识”下周要拆分“工作经验”为“应届/1-3年/3-5年”枚举值。Django 的makemigrations机制要求每次字段变更都生成新迁移文件而 MySQL 中ALTER TABLE jobs ADD COLUMN remote BOOLEAN DEFAULT FALSE一行 SQL 即可完成。更关键的是Django Admin 后台虽强大但招聘数据需定制化图表如用 Plotly 绘制技能共现网络图其模板系统与 Jinja2 兼容性差强行集成会导致 CSS 样式冲突。保留原生 SQL 查询 Jinja2 渲染使前端工程师能直接修改templates/chart.html中的 JavaScript 代码调整图表交互逻辑。3. 从零初始化 MySQL 数据库与 Flask 项目结构6 条命令完成基础环境搭建3.1 在本地安装并启动 MySQL 8.0以 Ubuntu 22.04 为例# 添加 MySQL 官方 APT 仓库避免 Ubuntu 自带版本过旧 sudo apt update sudo apt install -y wget gnupg wget https://dev.mysql.com/get/mysql-apt-config_0.8.24-1_all.deb sudo dpkg -i mysql-apt-config_0.8.24-1_all.deb # 安装时选择 mysql-8.0 sudo apt update sudo apt install -y mysql-server # 启动服务并设置 root 密码首次运行会提示交互式输入 sudo systemctl start mysql sudo mysql_secure_installation # 按提示禁用匿名用户、移除测试库等 # 创建招聘分析专用数据库与用户比 root 权限更安全 sudo mysql -u root -p -e CREATE DATABASE recruitment_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER recruiterlocalhost IDENTIFIED BY Recruit2024; GRANT SELECT, INSERT, UPDATE ON recruitment_db.* TO recruiterlocalhost; FLUSH PRIVILEGES;注意utf8mb4字符集支持 emoji 和中文四字节字符如某些公司名含特殊符号recruiter用户仅授予必要权限避免 Web 应用因漏洞导致全库删除。3.2 初始化 Flask 项目目录与依赖管理mkdir recruitment-analyzer cd recruitment-analyzer python3 -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate.bat pip install --upgrade pip pip install flask mysql-connector-python pandas plotly openpyxl python-dotenv提示mysql-connector-python是 Oracle 官方驱动兼容性优于 PyMySQL尤其在处理DATETIME字段时不易报错openpyxl用于后续导入 Excel 格式招聘数据python-dotenv从.env文件加载数据库配置避免密码硬编码。3.3 创建标准项目结构含配置分离与蓝图划分recruitment-analyzer/ ├── app.py # 主应用入口注册蓝图与配置 ├── config.py # 数据库连接参数、调试开关等 ├── .env # 存放 DB_USERDB_PASS 等敏感信息 ├── requirements.txt # pip freeze requirements.txt 生成 ├── templates/ │ ├── base.html # 基础 HTML 模板含导航栏与图表容器 │ └── dashboard.html # 主页嵌入 Plotly 图表与筛选表单 ├── static/ │ ├── css/ │ │ └── style.css # 自定义样式解决 Python 画图横坐标太密集问题见 5.2 节 │ └── js/ │ └── filters.js # 前端筛选逻辑监听城市/行业下拉框变更 └── models/ └── database.py # 封装连接池与常用查询方法3.4 编写config.py实现环境隔离与连接池复用import os from dotenv import load_dotenv load_dotenv() class Config: SECRET_KEY os.environ.get(SECRET_KEY) or dev-key-change-in-prod # 从 .env 读取数据库配置 MYSQL_HOST os.environ.get(MYSQL_HOST, localhost) MYSQL_USER os.environ.get(MYSQL_USER, recruiter) MYSQL_PASSWORD os.environ.get(MYSQL_PASSWORD, Recruit2024) MYSQL_DB os.environ.get(MYSQL_DB, recruitment_db) MYSQL_PORT int(os.environ.get(MYSQL_PORT, 3306)) # 连接池参数关键避免频繁创建销毁连接 MYSQL_POOL_SIZE 10 MYSQL_POOL_NAME recruitment_pool MYSQL_POOL_RESET_SESSION True逻辑说明MYSQL_POOL_SIZE10表示最多维持 10 个空闲连接当并发请求超过 10 时后续请求将排队等待POOL_RESET_SESSIONTrue确保每次从连接池获取连接时重置会话变量如SET NAMES utf8mb4防止字符集污染。3.5 在models/database.py中实现安全查询封装from flask import g from config import Config import mysql.connector from mysql.connector import Error def get_db(): 获取数据库连接使用 Flask 的 g 对象实现请求内单例 if db not in g: try: g.db mysql.connector.connect( pool_nameConfig.MYSQL_POOL_NAME, pool_sizeConfig.MYSQL_POOL_SIZE, hostConfig.MYSQL_HOST, userConfig.MYSQL_USER, passwordConfig.MYSQL_PASSWORD, databaseConfig.MYSQL_DB, portConfig.MYSQL_PORT, charsetutf8mb4, autocommitTrue ) except Error as e: raise RuntimeError(f数据库连接失败: {e}) return g.db def close_db(error): 请求结束时关闭连接 db g.pop(db, None) if db is not None: db.close() def query_jobs_by_filters(cityNone, industryNone, min_salaryNone): 参数化查询防止 SQL 注入 conn get_db() cursor conn.cursor(dictionaryTrue) # 构建动态 WHERE 条件注意None 值不参与过滤 conditions [] params [] if city: conditions.append(city %s) params.append(city) if industry: conditions.append(industry %s) params.append(industry) if min_salary: conditions.append(avg_salary %s) params.append(min_salary) where_clause AND .join(conditions) if conditions else 11 query f SELECT job_title, company_name, avg_salary, experience_req, skills FROM jobs WHERE {where_clause} ORDER BY avg_salary DESC LIMIT 100 cursor.execute(query, params) results cursor.fetchall() cursor.close() return results参数说明dictionaryTrue让cursor.fetchall()返回字典列表如[{job_title:Python工程师,avg_salary:15000}]便于 Jinja2 模板直接遍历autocommitTrue关闭事务自动提交因分析查询无需事务回滚提升性能。4. 实现核心可视化功能用 Plotly 渲染薪资分布直方图与技能词云支持前端动态筛选4.1 在 MySQL 中创建招聘数据表并导入示例数据-- 执行前确保已创建 recruitment_db 数据库 USE recruitment_db; CREATE TABLE jobs ( id INT PRIMARY KEY AUTO_INCREMENT, job_title VARCHAR(100) NOT NULL, company_name VARCHAR(100), city VARCHAR(50) DEFAULT 北京, industry VARCHAR(50) DEFAULT 互联网, avg_salary DECIMAL(10,2) DEFAULT 0.00, experience_req ENUM(应届,1-3年,3-5年,5年以上) DEFAULT 1-3年, skills JSON, -- 存储 [Python,SQL,Docker] created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 插入 500 条模拟数据实际可用 Python 脚本批量生成 INSERT INTO jobs (job_title, company_name, city, industry, avg_salary, experience_req, skills) VALUES (Python开发工程师, 科技先锋有限公司, 深圳, 互联网, 18500.00, 3-5年, [Python,Django,MySQL]), (数据分析专员, 数据洞察咨询公司, 上海, 金融, 12000.00, 1-3年, [SQL,Python,Tableau]), (Java后端开发, 云服务集团, 杭州, 互联网, 22000.00, 5年以上, [Java,Spring Boot,Redis]); -- 为高频查询字段创建索引解决 MySQL 排序慢问题 CREATE INDEX idx_city_salary ON jobs(city, avg_salary); CREATE INDEX idx_industry_exp ON jobs(industry, experience_req);注意skills字段用 JSON 类型而非 TEXT后续可用JSON_CONTAINS(skills, Python)快速筛选含 Python 技能的岗位比LIKE %Python%更精准且支持索引优化。4.2 在app.py中注册路由并传递 Plotly 图表数据from flask import Flask, render_template, request, jsonify from models.database import query_jobs_by_filters import plotly.express as px import plotly.utils import json import pandas as pd app Flask(__name__) app.config.from_object(config.Config) # 注册数据库关闭钩子 from models.database import close_db app.teardown_appcontext(close_db) app.route(/) def dashboard(): # 默认加载北京地区数据生成初始图表 jobs_data query_jobs_by_filters(city北京) df pd.DataFrame(jobs_data) # 生成薪资分布直方图解决 python 画图横坐标太密集问题用 plotly 自动缩放 fig_salary px.histogram( df, xavg_salary, nbins30, title北京地区岗位薪资分布, labels{avg_salary: 月薪元}, templateplotly_white ) fig_salary.update_layout( xaxisdict(tickmodelinear, tick05000, dtick5000), # 强制 X 轴刻度间隔 margindict(l20, r20, t50, b20) ) # 生成技能词云数据统计 skills JSON 数组中各技能出现频次 all_skills [] for skill_list in df[skills]: if skill_list: all_skills.extend(json.loads(skill_list)) from collections import Counter skill_counter Counter(all_skills) # 转为 DataFrame 供 Plotly 绘制条形图词云用 SVG 文本渲染更佳此处简化 skill_df pd.DataFrame(skill_counter.most_common(10), columns[skill, count]) fig_skills px.bar( skill_df, xcount, yskill, orientationh, titleTop 10 技能需求, labels{count: 出现次数, skill: 技能} ) # 将图表转为 JSON 传给前端 graphJSON_salary json.dumps(fig_salary, clsplotly.utils.PlotlyJSONEncoder) graphJSON_skills json.dumps(fig_skills, clsplotly.utils.PlotlyJSONEncoder) return render_template( dashboard.html, graphJSON_salarygraphJSON_salary, graphJSON_skillsgraphJSON_skills, cities[北京, 上海, 深圳, 杭州, 广州], # 传递城市列表供前端下拉框使用 industries[互联网, 金融, 制造业, 教育] ) app.route(/api/update_charts, methods[POST]) def update_charts(): 接收前端筛选参数返回新图表数据 data request.get_json() city data.get(city) industry data.get(industry) min_salary data.get(min_salary) jobs_data query_jobs_by_filters(citycity, industryindustry, min_salarymin_salary) if not jobs_data: return jsonify({error: 无匹配数据}), 404 df pd.DataFrame(jobs_data) # 重新生成两个图表逻辑同上省略重复代码 fig_salary px.histogram(df, xavg_salary, nbins20, titlef{city or 全部}地区薪资分布) fig_skills px.bar(pd.DataFrame(Counter([s for skills in df[skills] for s in json.loads(sills) if skills]).most_common(10)), xcount, yskill, orientationh) return jsonify({ salary_chart: json.dumps(fig_salary, clsplotly.utils.PlotlyJSONEncoder), skills_chart: json.dumps(fig_skills, clsplotly.utils.PlotlyJSONEncoder) })4.3 在templates/dashboard.html中嵌入交互式图表与筛选表单!DOCTYPE html html head title招聘数据可视化分析系统/title script srchttps://cdn.plot.ly/plotly-latest.min.js/script link relstylesheet href{{ url_for(static, filenamecss/style.css) }} /head body div classcontainer h1招聘数据可视化分析系统/h1 !-- 筛选表单 -- div classfilters label城市/label select idcity-select onchangeapplyFilters() {% for c in cities %}option value{{ c }}{{ c }}/option{% endfor %} /select label行业/label select idindustry-select onchangeapplyFilters() {% for i in industries %}option value{{ i }}{{ i }}/option{% endfor %} /select label最低月薪元/label input typenumber idmin-salary value0 min0 onchangeapplyFilters() /div !-- 图表容器 -- div classcharts div idsalary-chart stylewidth:100%;height:400px;/div div idskills-chart stylewidth:100%;height:400px;/div /div /div script src{{ url_for(static, filenamejs/filters.js) }}/script script // 页面加载时渲染初始图表 Plotly.newPlot(salary-chart, {{ graphJSON_salary | safe }}); Plotly.newPlot(skills-chart, {{ graphJSON_skills | safe }}); /script /body /html4.4 编写static/js/filters.js实现无刷新图表更新function applyFilters() { const city document.getElementById(city-select).value || null; const industry document.getElementById(industry-select).value || null; const minSalary parseInt(document.getElementById(min-salary).value) || 0; fetch(/api/update_charts, { method: POST, headers: { Content-Type: application/json, }, body: JSON.stringify({ city, industry, min_salary: minSalary }) }) .then(response response.json()) .then(data { if (data.error) { alert(data.error); return; } // 更新两个图表 Plotly.react(salary-chart, JSON.parse(data.salary_chart), {}, {responsive: true}); Plotly.react(skills-chart, JSON.parse(data.skills_chart), {}, {responsive: true}); }) .catch(error console.error(图表更新失败:, error)); } // 页面加载完成后绑定事件 document.addEventListener(DOMContentLoaded, function() { // 初始化时触发一次筛选确保默认值生效 applyFilters(); });逻辑说明Plotly.react()替代Plotly.newPlot()避免重复创建 DOM 元素导致内存泄漏{responsive: true}使图表随窗口缩放自动适配解决响应式布局下图表变形问题。5. 解决招聘分析中的典型技术难点MySQL 存储过程加速聚合、Plotly 中文显示与坐标轴优化5.1 用 MySQL 存储过程预计算高频统计指标规避 Python 层循环瓶颈当岗位数据量超 100 万行时pandas.groupby().agg()在 Python 中执行耗时显著。此时应将聚合逻辑下沉至 MySQLDELIMITER $$ CREATE PROCEDURE GetCitySalaryStats(IN p_city VARCHAR(50)) BEGIN SELECT city, COUNT(*) as job_count, ROUND(AVG(avg_salary), 0) as avg_salary, MIN(avg_salary) as min_salary, MAX(avg_salary) as max_salary, STDDEV(avg_salary) as salary_stddev FROM jobs WHERE city p_city AND avg_salary 0 GROUP BY city; END$$ DELIMITER ; -- 在 Python 中调用存储过程 def get_city_stats(city): conn get_db() cursor conn.cursor(dictionaryTrue) cursor.callproc(GetCitySalaryStats, [city]) # 注意参数为列表 for result in cursor.stored_results(): return result.fetchall() cursor.close()优势MySQL 的AVG()STDDEV()函数经 C 语言优化百万行数据聚合耗时通常 200ms而 Pandas 在 16GB 内存机器上处理同等数据需 1.2 秒以上。存储过程结果集可直接映射为字典无需额外解析。5.2 强制 Plotly 使用思源黑体显示中文解决图表标题乱码Plotly 默认字体不支持中文需在app.py图表生成处注入字体配置fig_salary.update_layout( fontdict(familySource Han Sans SC, sans-serif, size12), # 思源黑体 SC 版本 title_fontdict(size16), xaxis_title_fontdict(size14), yaxis_title_fontdict(size14) )注意Linux/macOS 需提前安装思源黑体sudo apt install fonts-noto-cjkWindows 用户需确认系统已安装该字体。若部署到 Docker应在Dockerfile中添加字体安装指令。5.3 针对“python 画图横坐标太密集”问题的三重优化策略招聘数据中城市/岗位名称常超 20 字Plotly 默认渲染会导致 X 轴标签重叠。解决方案分层实施优化层级具体操作适用场景前端自动缩放fig.update_xaxes(tickmodeauto, nticks10)标签数量 50自动选取最优刻度数文本旋转fig.update_xaxes(tickangle-45)标签长度中等10-15 字倾斜显示后端截断Tooltipdf[job_title] df[job_title].str[:12] ...hover_data[job_title_full]标签超长如“某大型央企下属人工智能研究院高级算法工程师”主图显示缩略名悬停显示全称# 在生成技能条形图时应用截断 skill_df[skill_short] skill_df[skill].str[:10] ... fig_skills px.bar( skill_df, xcount, yskill_short, orientationh, hover_data[skill], # 悬停显示完整技能名 titleTop 10 技能需求 ) fig_skills.update_layout(yaxis_title技能悬停查看全称)5.4 利用 MySQL 创建索引加速ORDER BY与GROUP BY查询招聘分析中SELECT * FROM jobs ORDER BY avg_salary DESC LIMIT 20是高频操作若无索引MySQL 需扫描全表排序。执行以下语句建立复合索引-- 覆盖查询WHERE city北京 AND avg_salary0 ORDER BY avg_salary DESC CREATE INDEX idx_city_salary_desc ON jobs(city, avg_salary); -- 覆盖查询GROUP BY industry, experience_req CREATE INDEX idx_industry_exp ON jobs(industry, experience_req);验证索引是否生效在 MySQL 命令行执行EXPLAIN SELECT * FROM jobs WHERE city北京 ORDER BY avg_salary DESC LIMIT 20;观察key列是否显示索引名rows列是否显著减少如从 100000 降至 200。5.5 在 Flask 中启用 Gzip 压缩减少 Plotly 图表 JSON 传输体积Plotly 图表 JSON 通常达 200KB开启压缩可提速 60%# 在 app.py 顶部添加 from flask_compress import Compress app Flask(__name__) Compress(app) # 自动压缩响应体 # 配置压缩选项可选 app.config[COMPRESS_MIMETYPES] [ text/html, text/css, text/xml, application/json, application/javascript, application/octet-stream ] app.config[COMPRESS_LEVEL] 6注意需pip install flask-compress且 Nginx/Apache 生产环境也应开启 Gzip形成双重压缩保障。本文还有配套的精品资源点击获取
返回列表