多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

基于Django与Pandas的毕业生就业管理系统开发实践

基于Django与Pandas的毕业生就业管理系统开发实践 高校就业办的日常多半是从一堆Excel表格里开始的。学生发来的三方协议、签约单位信息、考研升学名单散落在不同的辅导员手里月底汇总时全是格式打架、数据对不上。我做的这个毕业生就业管理系统核心就是把这摊子事理顺用 Python 做业务逻辑Django 管 Web 界面和权限MySQL 存正式数据Pandas 负责把脏数据洗干净、把统计报表直接导出来。这篇文章把我从环境搭建到核心功能落地的完整过程拆开讲包括我在实际开发中踩过的坑和验证过的写法适合正在做课程设计、毕业设计或者想在企业内部搭一套轻量数据管理系统的朋友参考。1. 项目定位与整体技术选型1.1 高校毕业生就业管理的痛点就业管理表面上只是“记录学生去了哪”但落到系统里其实是三件事信息采集、过程跟踪、结果统计。信息采集阶段学生要填个人基础信息、联系方式、专业方向、求职意向过程跟踪阶段要记录简历投递、面试邀约、offer 情况结果统计阶段辅导员要按专业、班级、地域、单位性质做就业率分析。这中间还夹着用人单位库、校园招聘会、就业帮扶台账等附属数据。如果全靠 Excel最疼的是两件事一是数据分散辅导员手里一份、院办一份、学生自己填的一份三方对不上二是统计口径经常变比如“就业率”到底算不算灵活就业、考研升学算不算就业领导一句话就要重新拉数。系统要解决的就是把这些散落的数据收拢到统一的库里让所有角色在一个 Web 界面里操作让统计报表能在几分钟内重新生成而不是加班到半夜去核对几十个 sheet。1.2 技术栈为什么这么选选型的核心逻辑是“团队熟悉度 开发速度 数据处理能力”。Django 是最成熟的 Python Web 框架之一自带 Admin 后台、ORM、认证授权和表单处理这意味着我可以少写大量基础代码把精力集中在业务逻辑上。毕业生就业管理系统这类业务80% 的功能就是增删改查加统计Django 的脚手架方式非常契合。MySQL 是关系型数据库的标准选择稳定、文档多、运维资源丰富学校机房或者小公司服务器跑它都没压力。选它而不选 PostgreSQL纯粹是因为需求方更熟悉 MySQL出问题时能自己顶上去。Pandas 是这个项目里的点睛之笔。就业数据的源头非常脏学生填写的学院名称可能叫“计算机学院”也可能叫“计科院”单位性质有“国企”“私企”“民营企业”各种写法签约日期有文本格式有日期格式。Pandas 的 DataFrame 处理这些清洗、转换、聚合任务是天然的强项。更关键的是Pandas 可以直接读 MySQL 表分析完再写回整条数据链路打通得非常顺。Python 作为胶水语言把 Web 框架、数据库驱动、数据分析库粘在一起。整套系统没有引入消息队列、微服务这些重武器目的就是保持可维护性——一个普通 Python 开发者看懂这个项目不需要额外学习分布式概念。2. 环境准备与基础组件安装2.1 Python 版本与虚拟环境这个项目我推荐用 Python 3.10 或 3.11不要追求最新版本。Django 官方支持列表里3.10 和 3.11 的兼容性验证是最充分的Pandas 在这些版本下也不会有二进制包缺失的问题。装完 Python 之后第一件事是建虚拟环境这步省不得。我在项目里用 venv 创建独立环境防止不同项目之间依赖互相污染。命令很简单python -m venv employment_env source employment_env/bin/activate # Windows下用 employment_env\Scripts\activate激活后 pip 安装的 Django、Pandas、mysqlclient 都只存在于这个环境里以后整个项目目录拷到新机器只要重新激活环境就能跑不会出现“在我电脑上是好的”这种诡异问题。2.2 Django 安装与项目骨架安装 Django 时注意版本锁死。我当前用的是 Django 4.2 LTS这个版本支持时间长Bug 修复稳定社区资料也全。安装命令pip install django4.2.*创建项目和子应用是两步操作django-admin startproject employment_system cd employment_system python manage.py startapp job_manage这里有一个新手常犯的错创建完 app 之后立刻去写 models.py然后发现数据库迁移时找不到表——因为根本没把 app 注册到 INSTALLED_APPS 里。正确做法是先去settings.py的INSTALLED_APPS里添加job_manage再执行python manage.py makemigrations python manage.py migrate跑完 migrate 后 MySQL 里会出现 Django 内置的 session、auth 等表这代表 ORM 和数据库的连接已经通了。2.3 MySQL 的三种安装方式对比这个项目的数据要长期留存数据库得装成服务而不是临时进程。我在不同机器上试过三种安装方式各有适用场景。Windows 10 上用 MySQL Installer 装 8.0 最省心。下载安装包后选择 Server Only配置类型选 Development Machine端口默认 3306字符集务必要选 utf8mb4——否则后面存 emoji 表情或生僻字会报错。安装完在服务管理里确认 MySQL80 服务处于运行状态。Linux 服务器上用 RPM 包安装前提是先处理掉系统自带的 MariaDB两者会冲突rpm -qa | grep mariadb rpm -e --nodeps mariadb-libs rpm -ivh mysql-community-server-8.0.36-1.el7.x86_64.rpm mysqld --initialize --console # 初始化并生成临时密码临时密码会输出在控制台里一定要立刻记下来后续用mysql -uroot -p登录后马上改密码。Docker 安装适合临时测试环境。有一个常见坑直接用默认命令启动官方镜像后容器启动失败十有八九是 MySQL 初始化需要 root 权限、或者本地端口被占用。我会用如下命令并指定数据卷docker run -d --name mysql8 -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_ROOT_HOST% \ -v /my/own/datadir:/var/lib/mysql \ mysql:8.0MYSQL_ROOT_HOST%这行很关键允许 root 从任意地址连接否则 Django 从宿主连接容器内的 MySQL 时会报 Access denied。2.4 Pandas 安装与国内源加速Pandas 的安装本身不复杂但在国内直连官方 PyPI 经常超时最直接的解决办法是使用清华源镜像。在虚拟环境里执行pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple同样地装 numpy、mysqlclient 这些依赖也可以加上这个源。实测下来清华源的速度比官方源快一个数量级而且能避免“could not find a version that satisfies the requirement pandas”这类常见报错——那种报错基本不是包不存在而是网络根本没把索引数据拉回来。装完验证一下import pandas as pd print(pd.__version__)打印出 2.x 版本号就说明环境OK了。我的建议是装完 Pandas 顺手把 numpy、openpyxl 也装上后面读写 Excel 文件会用到。3. 核心业务实现与数据流转3.1 数据模型设计系统核心数据模型我拆成四张表学生信息表、用人单位表、就业记录表、操作日志表。设计时坚持一个原则凡是要做统计的字段必须用标准化的枚举或外键不用自由文本。学生表的简化模型长这样from django.db import models class Student(models.Model): student_no models.CharField(max_length20, uniqueTrue) name models.CharField(max_length50) gender models.CharField(max_length10, choices[(M, 男), (F, 女)]) major models.CharField(max_length100) class_name models.CharField(max_length50) phone models.CharField(max_length20) email models.EmailField() class Meta: db_table student就业记录表是核心它会关联学生和用人单位class EmploymentRecord(models.Model): student models.ForeignKey(Student, on_deletemodels.CASCADE) company models.ForeignKey(Company, on_deletemodels.CASCADE) position models.CharField(max_length100) salary models.DecimalField(max_digits10, decimal_places2, nullTrue, blankTrue) contract_date models.DateField() employment_type models.CharField( max_length20, choices[ (contract, 签约就业), (postgraduate, 考研升学), (study_abroad, 出国留学), (flexible, 灵活就业), ] )这里比起 Excel 表单的优势是枚举约束和静态检查不会出现“每个人对考研升学的描述都不一样”的情况后续统计时GROUP BY employment_type直接就能分组。3.2 Django ORM 查询与删除操作的要点Django ORM 是这个系统里最常写的东西但有几个操作要特别留意。删除对象是重灾区。Django 默认的Objects.delete()会立刻物理删除数据库记录而且通过外键关联的on_deletemodels.CASCADE会级联删掉就业记录。在实际管理中我不推荐直接物理删除学生数据否则就业台账就断了。我的做法是给模型加一个is_active布尔字段删除时只是标记为 Falsedef soft_delete_student(request, student_id): student get_object_or_404(Student, pkstudent_id) student.is_active False student.save()查询操作里最常用的是聚合统计。比如统计各二级学院的就业人数from django.db.models import Count, Sum result EmploymentRecord.objects.values(student__major) \ .annotate(totalCount(id))这里用双下划线跨表查询student__major表示从就业记录关联到学生表的专业字段。很多新手在这里会直接写JOINSQL但 ORM 的表达方式其实更直观生成的 SQL 也经过了优化。另外要注意values()返回的是一个字典列表不是模型对象模板直接渲染没问题。但如果你想再拿这些数据进 Pandas 做分析可以顺手转成 DataFrame数据接口天然兼容。3.3 Pandas 数据清洗与统计报表实践Pandas 在我的这个项目里主要干三件事数据导入清洗、口径转换、统计报表导出。数据清洗最常见的问题是“脏标签”。比如学生填的单位性质有“国企”“国有”乱写的我用 Python 写了一段映射逻辑进行归一化import pandas as pd df pd.read_sql(SELECT employment_type, company_nature FROM employment_record, conn) nature_map { 国有: 国有企业, 国企: 国有企业, 民营: 民营企业, 私企: 民营企业, 外资: 外资企业, } df[company_nature_clean] df[company_nature].map(nature_map).fillna(其他)数据类型转换是另一个高频需求。MySQL 里的DECIMAL字段读出来有时是Decimal对象Pandas 运算会报类型错误日期字段经常是字符串没法直接按月分组。我的标准处理方式df[salary] pd.to_numeric(df[salary], errorscoerce).fillna(0) df[contract_date] pd.to_datetime(df[contract_date], format%Y-%m-%d)要注意errorscoerce参数它会把你转换不了的脏数据变成 NaN而不是直接抛异常导致整个流程中断。NaN 用fillna(0)填充统计时就不会爆空值错误。按月生成就业趋势报表这段是我觉得 Pandas 最出彩的部分df[month] df[contract_date].dt.to_period(M) monthly_report df.groupby(month).agg( contracts(student_id, count), avg_salary(salary, mean) ).reset_index() monthly_report.to_excel(monthly_employment_report.xlsx, indexFalse)这一组代码替代了原先要手动写十几条 SQL 加 Excel 公式的工作。而且 Pandas 的to_excel依赖 openpyxl 库所以前面我建议装 openpyxl就是这个原因。4. MySQL 查询优化与高级特性实践4.1 索引与排序优化数据量到了几万条级别查询慢的问题就开始暴露。第一个要检查的是排序字段有没有索引。我遇到过一个实际案例就业记录表按签约日期contract_date排序数据量一万多查询要两秒多。EXPLAIN一看type是ALL全表扫描。加了索引后秒回ALTER TABLE employment_record ADD INDEX idx_contract_date (contract_date);还有一个容易踩的坑是多字段排序。比如要求“按学院排序学院内按签约时间倒序”MySQL 只有在索引顺序与排序顺序一致时才走索引。我用了联合索引ALTER TABLE employment_record ADD INDEX idx_major_date (major, contract_date DESC);SQL 里ORDER BY major ASC, contract_date DESC就能直接命中索引。如果反过来用DESC排序却建了ASC索引MySQL 8.0 可以支持降序索引但老版本只能走 filesort这点要注意。4.2 存储过程与锁机制落地存储过程在就业系统里最合适的场景是“每月定时算就业率”。Django 里可以写 cron 跑 Python 脚本但直接放在 MySQL 存储过程里减少了网络来回还方便 DBA 单独维护。一个简单的就业率统计存储过程CREATE PROCEDURE calc_employment_rate(IN target_month VARCHAR(7)) BEGIN INSERT INTO employment_rate_stats(month, rate) SELECT target_month, COUNT(CASE WHEN e.employment_type IN (contract,postgraduate,study_abroad) THEN 1 END) / COUNT(*) AS rate FROM student s LEFT JOIN employment_record e ON s.id e.student_id WHERE DATE_FORMAT(e.contract_date, %Y-%m) target_month; END;执行时直接CALL calc_employment_rate(2024-06)。整个过程把统计口径固化在数据库里逻辑清晰还避免了 Python 端与 SQL 端口径不一致的问题。MySQL 锁的分类也是实际开发绕不过的点。我简单总结InnoDB 支持行锁和表锁但行锁只有在查询走了索引时才生效否则 MySQL 会退化成锁全表。业务里修改学生记录时如果条件不加索引字段两个并发事务就会互相等待这就是“锁表”问题。悲观锁用SELECT ... FOR UPDATE适合并发量不高的管理后台乐观锁用版本号机制适合高并发写入场景。就业管理这类后台系统我用悲观锁居多因为共享数据的冲突概率其实不高加锁成本也低。4.3 数据同步扩展思路就业数据后续往往要对接学校的 BI 大屏或者领导驾驶舱直接让 Django 去拖大数据量实时查询不现实。一个常见扩展方案是用 Flink CDC 把 MySQL 数据实时同步到 ClickHouse专门服务分析类场景。这个方案在我项目里只做了预研原理很简单利用 MySQL binlog 的增量日志Flink 通过 CDC connector 监听表变化实时写入 ClickHouse。好处是 OLTP 业务完全不受影响分析报表随便怎么跑都不会卡住业务库。对于数据量暂时只有几万条的就业系统是杀鸡用牛刀但如果将来集成全校各院系的多年数据这是一条顺理成章的升级路径。5. 常见问题与排查技巧实录5.1 环境安装类问题Docker 安装 MySQL 失败是我被问过最多的问题。建议先去查容器日志docker logs mysql8。常见原因是宿主机 3306 端口被已有 MySQL 占用或者数据卷目录权限不对。把端口映射改成 3307 能快速避开端口冲突docker run -d -p 3307:3306 --name mysql8 ...连接到容器内 MySQL 时端口要写 3307而不是默认的 3306。rpm 安装 MySQL 报依赖错误大概率是系统自带的 MariaDB 没清干净。先rpm -qa | grep mariadb查询再强制卸载同时把/var/lib/mysql清空再初始化。Windows 10 安装 MySQL 后连接不上先看服务列表里 MySQL80 是否启动再看 3306 端口是否被占用netstat -ano | findstr 3306。如果进程不是 mysqld杀掉占用进程重启服务。5.2 依赖与数据包类问题pip install pandas 报 “could not find a version that satisfies the requirement pandas”这个报错看起来像版本不存在其实是网络问题。用清华源镜像安装绝对能解决这一点我实操验证过多次。如果换了源还不行检查 pip 版本需要更新python -m pip install --upgrade pipPython 连接 MySQL 乱码核心在连接参数。Django 的 DATABASES 配置里加上OPTIONSDATABASES { default: { ENGINE: django.db.backends.mysql, NAME: employment, USER: root, PASSWORD: 123456, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }数据库建库时也要用 utf8mb4否则 Django 写入中文没问题但读出来可能是乱码。mysqlclient 安装失败Windows 上经常因为没有 Microsoft C Build Tools 导致编译失败。直接下载对应 Python 版本的mysqlclient预编译 whl 包安装比在终端硬编译省太多时间。5.3 运行与性能类问题Django 迁移时报 “Table already exists”通常是半路中断导致迁移记录和数据表不一致。我的处理办法是如果表结构不重要直接进 MySQL 手动删掉残留表如果表里有数据用python manage.py migrate --fake让 Django 跳过这张表。Pandas 读取 MySQL 时数据类型对不上尤其时间字段。MySQL 的 DATETIME 在 Django 里是datetime对象但 Pandas 可能读出字符串。统一处理方案是读完立刻pd.to_datetime别等到运算时才发现。大报表导出慢如果一张表几十万数据Pandas 有内存溢出的风险。可以按月份分块读取比如每次只读一个月的数据然后pd.concat合并。我用pandas.read_sql时优先走 SQL 的 WHERE 条件缩小数据量而不是全表拉到内存再过滤。5.4 问题速查表问题现象大概率原因快速处理办法Pandas 安装报找不到版本PyPI 访问不稳定换清华源安装Django migrate 后表不存在app 未注册到 INSTALLED_APPS注册后重新 migrateMySQL 连接 Access deniedroot 没开远程权限执行GRANT ALL PRIVILEGES ON *.* TO root%; FLUSH PRIVILEGES;查询一排序就慢排序字段无索引ALTER TABLE 表名 ADD INDEX (字段名)并发更新死锁事务里用了无索引字段确保 WHERE 条件走索引中文乱码连接字符集错误配置 charsetutf8mb4Docker 容器无法启动端口占用或目录权限换映射端口、清理数据卷权限最后说点实在话这个系统让我最有成就感的不是界面有多漂亮而是就业办老师终于不用每个月花一周时间手动核对数据了。我在实际开发中最深的体会是不要一上来就写功能先把数据结构理顺。就业管理系统听起来简单但牵扯到学生、学院、单位、时间线、多种就业类型一张表设计错了后面所有统计都会跟着错。Pandas 和 Django 的配合是我觉得这个项目最大的亮点——Django 管业务Pandas 管数据MySQL 做存储各司其职整个开发周期压在了两周以内。如果后续你要扩展可以加招聘会管理、学生求职意向分析、或者对接学校统一身份认证这套框架都能扛得住。
返回列表