ARTICLE DETAIL

资讯详情

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

Python就业分析平台:Flask+MySQL+机器学习实现就业预测

Python就业分析平台:Flask+MySQL+机器学习实现就业预测 简介这是一份基于Python的大学生就业情况分析平台完整项目实例适合具备Python基础的高校学生、研究人员、软件开发者以及从事教育数据分析、人力资源管理等相关工作的从业者学习参考。项目聚焦高校就业数据的智能化管理与人才培养质量评估同时也服务政府就业形势监控、企业精准招聘与学生职业规划等现实场景覆盖数据采集、清洗、特征工程、建模预测、可视化展示与智能推荐全流程。压缩包为单个docx文档体积仅84KB内含项目背景、目标与意义、挑战及解决方案、系统架构、功能模块、数据库设计、API接口规范、前后端代码实现与部署方案等完整章节并附有可运行的代码示例、数据库脚本以及GUI设计说明。目前已有181人学习浏览文档目录层级清晰读者可按照章节逐步搭建Flask后端、Tkinter前端与MySQL数据库环境深入理解数据预处理、模型构建与API交互逻辑并可在现有基础上尝试扩展知识图谱、深度学习等功能进一步提升平台的智能化水平。1. 基于 Python 的大学生就业情况分析平台把就业预测从“玄学”变成可复现的工程每年毕业季高校就业办最头疼的不是“统计就业率”而是“预测明年哪个专业会凉、哪些学生需要提前干预”。这套基于 Python 的大学生就业情况分析平台把这件事从“凭感觉”变成了可复现的工程它用 Flask 做后端服务Tkinter 做桌面 GUIMySQL 管数据机器学习算法负责就业预测与岗位推荐最后用可视化图表把结果直观摆出来。简单说这是一套完整的学生就业数据管理、分析、预测与展示系统而不是散装脚本。适合有 Python 基础的高校学生、研究人员、软件开发者以及教育数据分析、人力资源方向的从业者新手可以照着文档一步步搭环境跑起来熟手可以直接复用它的数据库设计、API 接口分层和模型训练流程。2. 系统架构与数据库设计Flask Tkinter MySQL 是怎么拼起来的2.1 五个核心模块的职责边界从需求分析可以看出这个平台把整个业务拆成了五个核心模块数据采集与预处理、特征工程与数据挖掘、建模预测与评估、可视化分析与交互展示、模型优化与平台扩展。这个拆分方式直接对应数据流先拿到原始数据再清洗加工再喂给模型最后输出结果展示同时留出优化迭代的口子。我在接手这类项目时也会坚持这个顺序否则后面换模型或加数据源时改一处就要牵连一片。下面用一张表把各模块的职责、输入输出和技术选型列清楚模块职责主要输入输出技术选型数据采集与预处理导入 Excel/CSV、清洗缺失值、统一字段格式教务导出表、就业办台账、企业岗位表干净的结构化 DataFramepandas、openpyxl特征工程与数据挖掘字段编码、构造派生特征、降噪预处理后的宽表可训练的特征矩阵pandas、scikit-learn建模预测与评估多模型训练、交叉验证、指标评估特征矩阵模型文件、评估报告scikit-learn、joblib可视化分析与交互展示报表、趋势图、画像展示预测结果与统计数据GUI 界面、图表、导出文件Tkinter、pyecharts模型优化与平台扩展增量训练、新数据源接入、API 扩展新样本与业务反馈更新后的模型与接口Flask、APScheduler为什么要这样拆核心原因是“数据边界清晰”。比如你后续要从招聘网站接入岗位数据只需要改“数据采集与预处理”这一层模型训练和 GUI 展示都不受影响。这是我个人用下来最舒服的分层方式。这里还要多说一句“多源异构数据集成”。平台接入的数据至少来自三处教务系统导出的成绩和学籍表就业办手工维护的毕业生去向表以及企业用户在平台上填写的岗位需求。这三类数据字段命名完全不同性别有的写“男”、有的写“M”专业有的存代码有的存名称如果不做清洗直接合并后面的模型训练基本就是垃圾进垃圾出。2.2 九张 MySQL 表从用户到岗位推荐的数据底座要支撑上述业务数据库需要完整覆盖“用户—学生—企业—岗位—就业状态—技能标签”这条链路。项目里一共设计了九张表用户表、学生信息表、企业/用人单位表、毕业生就业信息表、岗位/招聘信息表、职业能力标签表、学生技能关联表、报表统计日志表、系统管理操作日志表。其中核心表有三张。第一是用户表 sys_user管登录、角色和账号状态第二是毕业生就业信息表 graduate_employment管每个学生当前就业状态和去向第三是学生技能关联表 student_skill它决定了岗位智能推荐能不能跑起来——没有这张关联表模型就没法把“学生会什么”和“岗位要什么”匹配起来。报表统计日志表和系统管理操作日志表容易被忽略但我强烈建议保留。它们的作用是回答“这个报表是谁导出过”“这个学生的数据被哪个管理员改过”在高校这类有多角色参与的系统里没有操作日志会让排查问题变成噩梦。2.3 建表 SQL 与关键字段说明我按平台的数据库设计思路给出三张最有代表性的建表语句。第一张是用户表承担登录、鉴权与角色控制-- 用户表登录与角色鉴权 CREATE TABLE sys_user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, role ENUM(student, enterprise, admin) NOT NULL DEFAULT student, status ENUM(active, frozen) NOT NULL DEFAULT active, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;这张表里最关键的是 role 和 status 两个字段。role 区分学生、企业、管理员三种身份前端 Tkinter 界面会依据这个字段切换菜单后端 JWT 鉴权也会核对 role 来决定是否能调用管理员接口。status 表示账号是否被冻结登录模块每次校验都要先查它。密码字段存的是哈希值而不是明文常见做法是配合 Werkzeug 的 generate_password_hash 来生成。第二张是毕业生就业信息表-- 毕业生就业信息表核心业务表 CREATE TABLE graduate_employment ( id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, job_id INT, employment_status ENUM(已就业, 未就业, 灵活就业, 升学) NOT NULL, monthly_salary DECIMAL(10,2), city VARCHAR(50), company_type VARCHAR(50), employed_at DATE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (student_id) REFERENCES student_info(id), FOREIGN KEY (job_id) REFERENCES job_position(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;这里特别说明 monthly_salary 用 DECIMAL(10,2) 而不是 FLOAT。原因是薪资数据要参与均值、中位数统计和模型特征计算FLOAT 的二进制浮点误差会在累计均值时暴露出来DECIMAL 能保证展示和计算一致。employed_at 用 DATE 类型而不是 DATETIME因为就业日期不需要精确到时分秒查“今年 5 月就业人数”这类统计时 DATE 更干净。第三张是服务于岗位推荐的学生技能关联表-- 学生技能关联表岗位推荐的核心依据 CREATE TABLE student_skill ( id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, skill_tag VARCHAR(50) NOT NULL, skill_level ENUM(初级, 中级, 高级) NOT NULL DEFAULT 初级, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (student_id) REFERENCES student_info(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;这里的 skill_level 在推荐算法里一般会转换成分数比如初级 1、中级 2、高级 3用于计算学生与岗位的技能匹配度。注意示例 SQL 中外键依赖的 student_info 和 job_position 表在实际建库时需先创建否则外键约束会报错。我写代码的习惯是严格按“先主表、后从表”的顺序执行脚本。需要提醒的是所有表统一使用 utf8mb4 字符集。只用 utf8 的话遇到 Python 端写入生僻字或 Emoji 符号时会直接报错或变成乱码这是我早年踩过的坑。3. 数据采集、预处理与特征工程把“脏数据”洗干净再喂给模型3.1 多源数据读入Excel、CSV 与接口的取舍这套平台的数据来源可以分成三类Excel 台账、CSV 导出、以及平台前端页面收集的企业岗位数据。我在本地复现时最常用的做法是先把所有文件放进 data/ 目录统一用 pandas 读取。import pandas as pd # 教务系统导出的学生成绩表 df_score pd.read_excel(data/student_score.xlsx, sheet_name学生成绩, dtype{学号: str, 专业代码: str}) # 就业办维护的毕业去向表 df_employ pd.read_excel(data/graduate_status.xlsx, sheet_name毕业去向, parse_dates[毕业日期]) # 企业侧导出的岗位需求表 df_job pd.read_csv(data/job_demand.csv, encodingutf-8-sig)这里有两个参数值得专门说明。dtype{学号: str} 是为了防止 Excel 里的 15 位以上学号被 pandas 自动识别为科学计数法数值转换后会丢失精度。parse_dates[毕业日期] 是让 pandas 在读入阶段就把日期字段转成 datetime64 类型而不是留成字符串——字符串日期在后续按月份聚合时会出现排序错乱我在这上面翻过车。如果企业岗位数据是通过平台网页提交的那么这些数据会先落到 MySQL 表 job_position不需要走文件读取。读取数据库的方式也简单import pymysql import pandas as pd conn pymysql.connect(host127.0.0.1, userroot, passwordyour_password, databaseemployment_db, charsetutf8mb4) df_job pd.read_sql(SELECT * FROM job_position WHERE is_active 1, conn)这里 charset 必须写 utf8mb4与建表时的字符集保持一致否则读取带中文的数据可能出现 UnicodeEncodeError。连接用完记得关闭防止后续服务跑久后连接数被占满。3.2 缺失值与离群值处理按字段性质选择策略拿到多张表之后第一件要做的事不是急着合并而是先检查每一列缺失率。缺失率超过 60% 的字段通常直接丢弃因为强行填充只会往数据里注入大量噪音。剩下的字段按类型分别处理。# 检查缺失率 missing_ratio df.isnull().mean().sort_values(ascendingFalse) drop_cols missing_ratio[missing_ratio 0.6].index.tolist() df.drop(columnsdrop_cols, inplaceTrue) # 分类字段用众数填充 for col in [gender, major_code, company_type]: df[col].fillna(df[col].mode()[0], inplaceTrue) # 连续字段用中位数填充而不是均值 for col in [avg_score, monthly_salary]: df[col] df[col].fillna(df[col].median())为什么连续字段我不用均值因为就业薪资这类数据普遍右偏——少数高薪样本会把均值拉高用中位数填充更接近“普通学生”的水平。另外如果是消费水平、奖学金之类的字段可能还需要先做对数变换来压缩极端值影响但在就业数据集里先做中位数填充已经够用。离群值处理要更谨慎。比如月薪为 0 的记录很可能是未就业学生的占位值这实际上是有意义的业务信息不能直接删除。更合理的做法是构造一个“是否有稳定薪资”的派生特征或者单独标记# 月薪为 0 表示未就业或未领取薪资单独标记出来 df[has_salary] (df[monthly_salary] 0).astype(int) # 超过 99% 分位数的薪资按缺失处理避免极端值干扰模型 upper_limit df.loc[df[monthly_salary] 0, monthly_salary].quantile(0.99) df.loc[df[monthly_salary] upper_limit, monthly_salary] pd.NA注意这段代码的关键点先构造 has_salary 再处理极端值否则“无薪资”信息就丢失了。极端值只是修剪而不是删除保留行数避免样本量缩水。这里的 upper_limit 用的是只针对有薪资记录的 99% 分位数防止 0 值把上限拉低。3.3 特征编码让机器学习算法“咬得动”中文数据绝大多数机器学习算法只认数值不认字符串。所以性别、专业、企业性质这些字段都要编码。专业是最容易踩坑的字段因为专业名称的高基数导致直接独热编码One-Hot会生成几百列稀疏矩阵。from sklearn.preprocessing import LabelEncoder # 低基数字段直接映射 df[gender_encoded] df[gender].map({男: 0, 女: 1}) # 中高基数专业字段用标签编码同时保留专业大类 le_major LabelEncoder() df[major_encoded] le_major.fit_transform(df[major_name].astype(str)) # 派生特征按专业大类聚合同专业平均分 df[major_avg_score] df.groupby(major_encoded)[avg_score].transform(mean)这里我建议区分两种编码策略。性别、学历这类取值范围固定的字段直接 map 成 0/1 最直观专业这类数量多、且和就业率高度相关的字段单独做 LabelEncoder 再配合一个“专业平均分”的派生特征能比盲目 One-Hot 拿到更好的效果同时训练速度也快得多。如果你需要把键盘输入的中文专业名标准化常见的做法是建立一张专业代码映射表比如把“计算机科学与技术”“软件工程”“网络工程”都归一到“计算机大类”。这一步在特征工程里价值极高它能显著缓解稀疏编码问题也能让后续的岗位推荐在“大类”层面做匹配。4. 机器学习建模与 Flask 后端 API从“预测函数”到“可调用的服务”4.1 多模型自适应建模为什么同时跑两个分类器这个平台在建模模块里采用了“多模型自适应”的思路意思是同一个训练集上同时维护多个模型最后根据评估结果选择最优模型入库。我在复现时发现这个思路很务实——因为就业数据本身信噪比低单一模型容易翻车。实际上最稳妥的做法是分别训练逻辑回归和随机森林一个偏线性解释、一个偏非线性拟合。用同一组特征跑输出各自的准确率、召回率和 F1然后按业务口径选择。from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, recall_score, f1_score X df[[gender_encoded, major_encoded, major_avg_score, avg_score, intern_count, cert_count, has_salary]] y (df[employment_status] 已就业).astype(int) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy) models { logistic: LogisticRegression(max_iter1000, C1.0), random_forest: RandomForestClassifier(n_estimators200, max_depth6, random_state42) } for name, model in models.items(): model.fit(X_train, y_train) y_pred model.predict(X_test) print(name, acc:, round(accuracy_score(y_test, y_pred), 4), recall:, round(recall_score(y_test, y_pred), 4), f1:, round(f1_score(y_test, y_pred), 4))这里最关键的参数是 test_size0.2 和 stratifyy。test_size0.2 表示拿 20% 的数据做验证这是中小规模数据集上相对稳妥的训练验证比例stratifyy 则是让训练集和测试集里“已就业/未就业”的比例保持一致防止因为随机切分导致测试集里几乎没有未就业样本。这类数据集如果不做分层抽样模型评估结果会很飘。如果训练结果中随机森林的召回率显著高于逻辑回归我建议最终发布时就优先选随机森林。如果两者接近则选择逻辑回归因为它的 coef_ 系数可以直接解释“哪个特征对就业概率影响最大”方便后续给就业办写分析报告。4.2 评估指标就业预测关注召回率而不是准确率很多人在训练完模型后只看 accuracy但在就业预测场景里准确率往往具有欺骗性。假设数据集里 85% 的学生最终都就业了一个“无脑预测全部已就业”的模型准确率就有 85%但这个模型对业务毫无价值。就业办真正关心的是能不能提前识别出那 15% 可能就业困难的学生。所以这个场景下召回率recall的优先级要高于准确率。下面这张对比表能说明不同指标的业务含义指标计算公式业务含义准确率 accuracy(TPTN)/(TPTNFPFN)整体预测对了多少召回率 recallTP/(TPFN)就业困难学生被找回了多少精确率 precisionTP/(TPFP)预测为就业困难的学生里有多少是真困难F1 分数2*precision*recall/(precisionrecall)精确率和召回率的平衡实际操作中我会先看随机森林的 recall 是否达到 0.8 以上再回到预测结果中抽样检查被预测为“未就业”的学生画像确认模型不是只在机械模仿“实习少、成绩低”这类直观逻辑。如果发现模型只依据一两个特征做判断就要考虑增加特征或调整类别权重。4.3 Flask JWT把模型封装成带身份认证的 API模型训练完成后不能只停留在 jupyter notebook 里要部署成前后端都能调用的服务。平台后端采用 Flask用 joblib 加载训练好的模型文件同时通过 JWT 做接口身份认证。import joblib from datetime import datetime import jwt from flask import Flask, request, jsonify app Flask(__name__) app.config[SECRET_KEY] change-me-in-production model joblib.load(models/employment_random_forest.joblib) def token_required(func): def wrapper(*args, **kwargs): token request.headers.get(Authorization, ).replace(Bearer , ) try: payload jwt.decode(token, app.config[SECRET_KEY], algorithms[HS256]) except jwt.ExpiredSignatureError: return jsonify({msg: token expired}), 401 except jwt.InvalidTokenError: return jsonify({msg: invalid token}), 401 return func(*args, **kwargs) return wrapper app.route(/api/v1/predict, methods[POST]) token_required def predict(): data request.get_json() features [[ data[gender_encoded], data[major_encoded], data[major_avg_score], data[avg_score], data[intern_count], data[cert_count], data[has_salary] ]] prob model.predict_proba(features)[0][1] return jsonify({employment_prob: round(float(prob), 4), predicted_at: datetime.now().strftime(%Y-%m-%d %H:%M:%S)})这个接口接收前端 GUI 提交的学生特征返回就业概率。需要注意 predict_proba 和 predict 的区别predict 直接返回 0 或 1而 predict_proba 返回的是概率值。对业务方来说概率比硬分类更有用——比如 0.45 的阈值下系统可能判“未就业”但展示“就业概率 45%”远比直接说“你可能找不到工作”更能指导后续干预策略。JWT 鉴权逻辑是这套接口安全性的基础。前端拿到 token 后在请求头加 Authorization: Bearer 后端通过代理解析 token过期或伪造都直接返回 401。JWT 的有效期我一般设为 12 小时到期后前端要重新用用户名密码换新 token。生产环境的 SECRET_KEY 必须通过环境变量注入不能在仓库里写死。如果你要把预测接口暴露给企业端可以在同一套模型上增加一个匹配度计算接口用学生技能标签和岗位标签做重合度打分。平台正文里提到的“岗位智能推荐模块”就是这个接口的典型落地——它不改变模型本身只把预测结果与岗位库关联起来。5. 避坑与排查5 个让我翻车的问题与解决记录5.1 MySQL 中文数据全部变成问号现象往数据库写入中文姓名、专业名称后查询出来全是“???”但英文和数字正常。原因建表时没有指定 utf8mb4 字符集或者数据库连接字符串里 charset 参数缺失。MySQL 默认的 latin1 在大多数 Linux 环境下不支持中文写入时直接把字符截断成问号。解决统一在三个层面修正。建表语句里写 CHARSETutf8mb4 COLLATEutf8mb4_unicode_ciMySQL 连接串里加 charsetutf8mb4Python 端如果在 Windows 上写脚本读取文件时用 encodingutf-8-sig 避免 BOM 头问题。修改已有表用 ALTER TABLE sys_user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci 即可。5.2 pandas 读出的时间字段写回 MySQL 报错现象在 pandas 里处理好就业日期后用 to_sql 写回 MySQL 时报 DataError: Incorrect date value明明打印出来是“2024-06-20”。原因读取 Excel 时分了多张表其中一张表的“毕业日期”被 pandas 解析成 datetime另一张表解析成字符串。合并后字段类型是 objectto_sql 把字符串原样拼进 SQLMySQL 不认。解决合并前统一调用 pd.to_datetime() 并把缺失值转成 NaT写库前用 astype(str) 再手动转 DATE 格式。我一般会在数据合并后加一行断言代码assert pd.api.types.is_datetime64_any_dtype(df[graduation_date])确保类型是对的再进下一步。5.3 Tkinter 界面在高分屏下字体模糊、控件错位现象同一套 GUI 代码在 1080p 屏幕上显示正常换到 2K 或 4K 高分屏后控件挤成一团、中文文字发虚。原因Tkinter 默认按物理像素布局Windows 高分屏的缩放比例125%、150%没有正确传导给 Tk 控件。旧版 Python 的 Tk 也不能自动适配 DPI。解决在主窗口创建前调用 ctypes.windll.shcore.SetProcessDpiAwareness(1)然后再初始化 Tk。字体统一用 font(Microsoft YaHei, 10) 指定中文字体名不要依赖默认字体。5.4 Flask 并发访问时 MySQL 连接超时现象GUI 端多个用户同时登录、同时查询报表时后端日志报 MySQL server has gone away。原因Flask 的数据库连接是在模块导入时建立的长连接长时间空闲会被 MySQL 服务端断开再次使用时报错。这是每个新手都会遇到的连接池问题。解决不要在全局只建一个 connection而是用 PyMySQL 每次请求结束后关闭连接或者使用 SQLAlchemy 的连接池。推荐 SQLAlchemy 的 pool_pre_pingTrue 参数每次从池里取连接时先 ping 一下发现断线就重连。部署时再把服务端的 wait_timeout 调大比如改成 3600 秒。5.5 模型在训练集上“很准”上线后预测结果被业务方质疑现象交叉验证准确率 0.86但就业办反馈实际预测和当年真实情况偏差明显尤其是换了新一届学生后。原因数据分布漂移。训练集中“专业”“生源地”“是否挂过科”等特征的分布与真实新一届学生不同比如某一年某专业突然扩招样本结构改变旧模型自然失效。另外模型学到的是历史规律而就业市场本身在变化。解决建立“年度重训”机制。每年新一届学生数据录入后把新数据并入训练集重新评估模型而不是沿用历史模型。我推荐在平台后台留一个“模型再训练”按钮调用同一个训练脚本管理员每年点一次即可完成更新。平台里我已经提前留好了增量更新的接口路径实际使用体验会好很多。6. GUI 与部署技巧让 Tkinter 界面真正“接管”后端能力把 Flask 接口和 Tkinter 界面打通后这套系统才真正闭环。我的做法是让 Tkinter 只做两件事表单收集和结果展示所有业务逻辑都走 HTTP 请求。import tkinter as tk import requests def submit_predict(): payload { gender_encoded: gender_var.get(), major_encoded: major_var.get(), major_avg_score: float(score_var.get()), avg_score: float(avg_var.get()), intern_count: int(intern_var.get()), cert_count: int(cert_var.get()), has_salary: 1 if salary_var.get() else 0 } resp requests.post( http://127.0.0.1:5000/api/v1/predict, jsonpayload, headers{Authorization: fBearer {token}} ) result_label.config(textf预测就业概率: {resp.json()[employment_prob]:.2%})这个设计的关键是分离了界面层与业务层。以后模型升级、数据库迁移都不需要重写界面只要后端接口不变。部署时我习惯用 PyInstaller 打包 Tkinter 前端命令是 pyinstaller -F -w main.py配合 --hidden-import 把 joblib 和 sklearn 相关模块加进去。打包完要先在干净环境里跑一遍确认不依赖开发者机器上的 Python 环境。数据库脚本在部署前也最好完整执行一遍别像我之前那样只建核心表结果报表模块运行时缺字段。这套资源把 Flask 后端、九张 MySQL 建表脚本、Tkinter 前端工程和模型训练代码都整合好了建议拿到手后先按文档把环境搭起来再从数据导入到预测接口完整走一遍比只看代码要省力得多。从那以后我每次把这类项目交给别人之前都会强制把“环境依赖、数据库脚本、接口启动、GUI 联调”这四步完整走一遍。就业数据模型的准确率能不能再提升有时要等业务跑起来后才能看清但先把系统稳定交付、让使用者能点开界面看到不报错的结果永远是第一位的。希望帮到你。本文还有配套的精品资源点击获取
返回列表