简介:高校学生学业预警系统是一套基于Django+Python+MySQL的完整毕业设计资料包,面向计算机相关专业学生,可作为毕设选题参考、课程设计实践或Python Web应用开发入门项目。系统围绕学业预警核心业务,管理员端包含菜单导航、预警分析、学生信息管理、学生成绩管理及用户管理等模块,学生端提供个人信息与学习计划功能;预警分析模块可按阈值和规则识别成绩下滑、缺课频繁等异常行为并生成预警信号,体现了一定的业务完整性。压缩包共323个文件,约10.53MB,以py源码、pyc编译产物、html/css/js前端资源、sql数据库文件及doc开发文档为主,并附多个gif操作演示。已有53人学习下载。资料内含可直接运行的完整工程、数据库脚本与开发文档,模块划分清晰,便于理解学业预警业务流程,也适合在此基础上按自身需求二次扩展。
1. 学业预警系统是什么:为什么每个高校都需要一套Python落地版
又是期末周,教务老师从教务系统里导出一张全校成绩总表,按学院拆成Excel,再转发给辅导员手动数挂科、算绩点、拉名单。学生规模上了千人,这套流程基本就是黑匣子——数错人、漏通知、绩点口径打架是家常便饭。高校学生学业预警系统的核心思路,是用 Python 把学籍、成绩、学分三类数据串起来,按挂科门数、学分绩点和学业进度自动做分级预警,生成名单、报表和通知。以毕业设计的形式落地时,它交付的是一套源代码加全套文档,但真正值钱的不是那几份答辩PPT,而是从原始成绩单到可靠预警记录的这一整条数据流水线。下面我把这套系统从需求拆解到部署兜底完整讲一遍,中间会给出可以直接跑的最小代码和参数设置,也把最容易翻车的地方挑明。
2. 从需求到设计:学业预警系统的功能拆解和数据模型
2.1 预警规则怎么定:挂科门数、绩点阈值与学分进度
学业预警的“学业”两个字,在数据上最终落到三个硬指标:挂科门数、平均学分绩点、已获学分进度。不同学校叫法不同,有的叫黄色预警、橙色预警、红色预警,有的叫一级、二级、三级,但本质都是把学生分成“需要谈话、需要重点关注、需要学业帮扶”三档。常见的做法是如下这种规则配置。
| 预警等级 | 触发条件(可配置) | 处理动作 |
|---|---|---|
| 黄色预警 | 本学期挂科 1 门,或平均学分绩点 < 2.0 | 辅导员约谈,发送学业提醒 |
| 橙色预警 | 本学期挂科 2 门,或平均学分绩点 < 1.8 | 约谈 + 学习计划备案 |
| 红色预警 | 本学期挂科 3 门及以上,或已获学分不足应修学分的 70% | 约谈 + 家长通知 + 学业帮扶 |
我建议你把规则表做成数据库表而不是写死在 Python 代码里,原因有两个:一是毕业设计答辩时,评委一定会问“规则能不能改”,能改比不能改高一个档次;二是真实业务里,教务处每学期都可能调整阈值,写死规则意味着每次改都要动代码、重新部署,这在交付现场非常尴尬。规则表字段可以设计成 rule_id、rule_name、metric、operator、threshold、priority、action,这样前端留一个维护页面,后端只需要解析这张表。
2.2 数据模型:学生、课程、成绩与预警记录四张表的关系
预警系统不需要把整个教务系统搬过来,只要四张核心表。学生表存学籍信息,课程表存课程属性和学分,成绩表存每次成绩记录,预警记录表存每次预警的快照。快照这个词是关键——预警必须有时间戳和学期标识,否则下个学期重新计算时,历史预警会被覆盖,后面统计趋势就没有依据。
CREATE TABLE tb_student ( stu_id VARCHAR(20) PRIMARY KEY, stu_name VARCHAR(50) NOT NULL, college VARCHAR(100), major VARCHAR(100), grade_year INT COMMENT '入学年份' ); CREATE TABLE tb_course ( course_id VARCHAR(20) PRIMARY KEY, course_name VARCHAR(100) NOT NULL, credit DECIMAL(4,2) COMMENT '课程学分', course_type VARCHAR(20) COMMENT '必修/选修/通识' ); CREATE TABLE tb_score ( score_id INT AUTO_INCREMENT PRIMARY KEY, stu_id VARCHAR(20), course_id VARCHAR(20), semester VARCHAR(20) COMMENT '如 2024-2025-1', score_value DECIMAL(5,2), is_exam TINYINT DEFAULT 1 COMMENT '1正常考试,0补考/重修', UNIQUE KEY uk_stu_course_sem (stu_id, course_id, semester) ); CREATE TABLE tb_warning ( warning_id INT AUTO_INCREMENT PRIMARY KEY, stu_id VARCHAR(20), semester VARCHAR(20), warning_level TINYINT COMMENT '1黄 2橙 3红', trigger_reason VARCHAR(200), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, is_processed TINYINT DEFAULT 0 COMMENT '是否已处理' );这段建表 SQL 里,tb_score 的唯一键设计是踩坑点之一。如果不加唯一键,同一个人同一门课同一个学期可能出现两条成绩,后面算挂科门数直接翻倍。varchar 类型的学号做主键而不是自增 id,是因为学号是天然的稳定业务标识,查询成绩时经常要按学号关联。
2.3 技术选型:为什么常见做法是 Flask + pandas + MySQL
Python Web 框架里,Django 自带 admin 后台、ORM 和迁移工具,功能全,但对一个毕设项目偏重;FastAPI 对异步和接口友好,可教务场景里大多数操作是同步的表格渲染和上传下载,反而用不上;Flask 轻量、路由简单、模板引擎顺手,是这类管理系统最常见的起步选择。数据处理层用 pandas,因为成绩单的原始形态往往是一张格式混乱的 Excel 表,用 pandas 读取、清洗、分组、透视,比写一大段 openpyxl 循环快得多。
存储层用 MySQL 是因为教务数据天生是关系型的,而且部署环境基本是 Windows 服务器或 Linux 虚机,MySQL 兼容性最好。如果你只是想本地跑通演示,用 SQLite 也可以,连接串换一下即可,但模型设计和 SQL 语句要保持不变,这样答辩时评委问“能不能换回 MySQL”,你只需要说改一行配置。接口层不写复杂的前后端分离,直接用 Flask 的 render_template 渲染页面,配合一个返回 JSON 的图表接口就够,后面展示开学趋势、学院排名、班级对比都方便。
3. 跑通核心预警逻辑:Python 最小实现与参数设置
3.1 环境准备:python 安装、虚拟环境与依赖清单
先处理环境。如果你机器的 Python 还没装好,参考 python 安装教程把步骤走完,最需要注意的是安装时勾选“Add Python to PATH”,以及安装完成后在命令行里执行 python --version 验证。VS Code 里做 Python 开发需要先安装官方 Python 扩展,然后用 Ctrl+Shift+P 打开命令面板,选择 Python: Select Interpreter 指向虚拟环境。这里有一个容易踩的点:Windows 上如果之前装过多个 Python 版本,命令行里输入的 python 可能指向 3.7,而 VS Code 里解释器指向 3.11,两边版本不一致会导致依赖装错地方。
python -m venv venv venv\Scripts\activate # Windows 激活虚拟环境 # source venv/bin/activate # macOS / Linux pip install flask pandas openpyxl pymysql flask-cors apscheduler虚拟环境的作用是隔离当前项目的依赖,避免系统里其他 Python 项目互相污染。依赖清单里,pandas 读取 Excel 需要 openpyxl 做引擎,pymysql 负责连 MySQL,flask-cors 是为后续前端图表请求准备的,apscheduler 用于定时任务。建议把装好的依赖导出为 requirements.txt,交毕业设计材料时这也是加分项。
3.2 读取成绩单:pandas 处理 Excel 的边界问题
教务系统导出的成绩单,表头千奇百怪。有的表头占两行,第一行是学院和年级的合并单元格,第二行才是字段名;有的成绩列里有“缺考”“缓考”“作弊”这类文本。最稳妥的做法是读取时不指定 header,先拿到原始 DataFrame,再手动定位表头行。
import pandas as pd def load_score_excel(file_path): df_raw = pd.read_excel(file_path, header=None, engine='openpyxl') # 常见表头在第2行或第3行,遍历前5行找到含“学号”和“课程名”的行 header_row = None for i in range(min(5, len(df_raw))): row_vals = df_raw.iloc[i].astype(str).tolist() if '学号' in row_vals and '课程名称' in row_vals: header_row = i break if header_row is None: raise ValueError('未找到标准表头,请检查成绩单模板') df = df_raw.iloc[header_row + 1:].copy() df.columns = df_raw.iloc[header_row].astype(str).tolist() return df逻辑说明:先不解析表头,遍历前五行找关键字段,这样做的好处是不管合并单元格占了几行,都能定位到真正的列名。参数上,header=None 是告诉 pandas 所有行都当数据看,engine='openpyxl' 是读取 .xlsx 文件必需的。找到表头后,用 iloc 做行切片,避免把空行和合并单元格带进来。
3.3 绩点计算与预警判定:核心函数怎么写
拿到了成绩表和课程学分表之后,下一步是把它合并成每个学生的学期汇总。这里的核心不是循环,而是先关联再用 groupby 聚合。绩点换算按常见标准:优秀绩点 4.0,良好 3.5,中等 2.8,及格 2.0,不及格 0。补考和重修成绩单独处理,不作为原始挂科记录删除,而是参与最新一次成绩的绩点计算。
import pandas as pd grade_point_map = {'优秀': 4.0, '良好': 3.5, '中等': 2.8, '及格': 2.0, '不及格': 0.0} def calc_semester_gpa(df_score, df_course): df = df_score.merge(df_course, on='course_id', how='left') df['grade_point'] = df['score_value'].apply( lambda s: '优秀' if s >= 90 else '良好' if s >= 80 else '中等' if s >= 70 else '及格' if s >= 60 else '不及格' ) df['grade_point'] = df['grade_point'].map(grade_point_map) df['credit_point'] = df['grade_point'] * df['credit'] agg = df.groupby('stu_id').agg( total_credit=('credit', 'sum'), total_credit_point=('credit_point', 'sum'), fail_count=('grade_point', lambda x: (x == 0).sum()) ).reset_index() agg['gpa'] = agg['total_credit_point'] / agg['total_credit'].replace(0, float('nan')) return agg逻辑说明:第一步 merge 把成绩和课程学分关联起来,缺失学分直接 left join 保留成绩,但学分列会变成 NaN,后面计算会报警。为避免这个隐患,建议在读取阶段就校验所有课程都能匹配,匹配不到的课程打印日志,而不是静默处理。apply + map 把分数映射成等级再映射成绩点,语义清楚。groupby 返回三个聚合指标:总学分、总绩点、挂科门数。
这里的参数说明:优秀/良好/中等/及格/不及格的分段阈值是学校通用的 90/80/70/60,但请注意,部分学校把“中等”定位 2.5,有的课程是五级制有的两级制,你需要在配置里加一个 score_level_map,不要把阈值写死。
3.4 预警记录落库与结果导出
计算出汇总之后,要和预警规则表匹配,生成当学期的预警记录。这里最容易犯的错误是每次运行都清空预警表重来,结果一条学生记录从黄色升级到红色,历史被覆盖了。
def match_warning_rules(agg_df, rule_df): records = [] for _, stu in agg_df.iterrows(): if stu['fail_count'] >= 3: level = 3 reason = f"挂科{int(stu['fail_count'])}门" elif stu['fail_count'] == 2 or stu['gpa'] < 1.8: level = 2 reason = f"挂科{int(stu['fail_count'])}门/绩点{stu['gpa']:.2f}" elif stu['fail_count'] >= 1 or stu['gpa'] < 2.0: level = 1 reason = f"挂科{int(stu['fail_count'])}门/绩点{stu['gpa']:.2f}" else: continue records.append((stu['stu_id'], level, reason)) return records逻辑说明:规则判定使用“从高到低”的优先级,先把红色预警最多的学生筛出来,再往下匹配橙色和黄色,这样避免一个满足红色条件的学生同时被记为黄色。最后的结果导出到 Excel 用 ExcelWriter 保存,带上学期名和生成时间,方便辅导员直接打印。
4. 做成 Web 管理系统:Flask 路由与前端交互
4.1 登录与角色权限:教师、学生、教务三方入口
预警系统的用户分三类:教务管理员维护规则和成绩数据,辅导员查看和导出预警名单,学生本人查看自己的预警记录。Flask 里最简单可靠的方案是 session 加装饰器,不需要引入复杂的权限框架。密码存储至少用 werkzeug 的 generate_password_hash,不要在数据库里存明文。
from flask import Flask, session, redirect, url_for, request from functools import wraps from werkzeug.security import generate_password_hash, check_password_hash app = Flask(__name__) app.secret_key = 'your-secret-key-change-before-deploy' def login_required(f): @wraps(f) def wrapper(*args, **kwargs): if 'user_id' not in session: return redirect(url_for('login')) return f(*args, **kwargs) return wrapper def role_required(*roles): def decorator(f): @wraps(f) def wrapper(*args, **kwargs): if session.get('role') not in roles: return '无权限访问', 403 return f(*args, **kwargs) return wrapper return decorator逻辑说明:login_required 是通用登录保护,role_required 是角色控制。到这里有一个容易被忽视的细节:session 里存的 role 必须在登录时写入,并且退出时清除,否则辅导员登录后切换账号,旧角色还能访问教务管理页面。用装饰器把权限逻辑和业务路由分开,后续新增路由只需要在函数上一行加 @role_required('admin') 即可。
4.2 成绩录入与预警查询页面
成绩录入不推荐手工逐条填表单,教务场景下的成绩一定来自系统导出文件,所以页面上给一个上传入口,后端接收 Excel 文件后调 read_excel 函数入库。查询页面按学院、年级筛选预警名单,是这次项目演示的主战场,学生们通常把这些动作视为系统的全部功能,其实页面背后的查询 SQL 才是关键。
@app.route('/api/warnings') @login_required def api_warnings(): college = request.args.get('college', '') level = request.args.get('level', '') semester = request.args.get('semester', '2024-2025-1') sql = """ SELECT s.stu_id, s.stu_name, s.college, s.major, w.warning_level, w.trigger_reason FROM tb_warning w JOIN tb_student s ON w.stu_id = s.stu_id WHERE w.semester = %s """ params = [semester] if college: sql += " AND s.college = %s" params.append(college) if level: sql += " AND w.warning_level = %s" params.append(level) sql += " ORDER BY w.warning_level DESC, s.college" result = query_db(sql, params) return render_template('warnings.html', rows=result)逻辑说明:这里是字符串拼接 SQL,但参数全部通过 %s 占位传入,不要用 f-string 直接拼接用户输入,防止 SQL 注入。排序用 warning_level DESC,红警排最前面。前端模板里用 for 循环渲染表格,再提供导出当前筛选结果为 Excel 的按钮,前端其实只有几十行模板语法,核心一直压在 SQL 的筛选逻辑上。
4.3 用 ECharts 做可视化的预警报表
预警名单只有表格是不够的,答辩时评委喜欢看趋势。ECharts 的折线图、饼图、柱状图都能很好地展示预警分布,后端只需要提供一个 JSON 接口,前端通过 ajax 拉取。
@app.route('/api/warning_stats') @login_required def api_warning_stats(): stats = query_db(""" SELECT warning_level, COUNT(*) AS cnt FROM tb_warning WHERE semester = %s GROUP BY warning_level """, [request.args.get('semester', '2024-2025-1')]) level_name = {1: '黄色预警', 2: '橙色预警', 3: '红色预警'} return jsonify([{'name': level_name[int(r['warning_level'])], 'value': r['cnt']} for r in stats])fetch('/api/warning_stats') .then(res => res.json()) .then(data => { const chart = echarts.init(document.getElementById('stats_chart')); chart.setOption({ series: [{ type: 'pie', data: data, radius: ['40%', '70%'] }] }); });逻辑说明:接口返回的是饼图需要的 name-value 结构,又是字典列表,前端无需二次改造。这里要留意跨域问题,如果 Flask 和前端页面不在同一端口开发,加 flask-cors 处理;如果同端口部署,则不需要。ECharts 的收费、CDN 均可用,但离线答辩网络偶发不通最坑人,vscode 中建议把 echarts.min.js 下载到本地 static 目录。
5. 避坑指南:从数据清洗到部署的 5 个常见问题
5.1 学号变成科学计数法,且后四位变成 0
现象:读取 Excel 后,学号列显示为 1.230015e+18,字符串比较时和数据库里的学号对不上,预警结果关联出大量空数据。
原因:pandas 默认把由纯数字组成的列推断为 int64 或 float64,Excel 里的 18 位学号超出 float 精度阈值,后四位被截断。
解决:read_excel 时给该列指定 dtype。最稳妥的做法是整表读取时都按 str 处理,后续需要计算的列再显式转换。
df = pd.read_excel(file, dtype={'学号': str, '课程名称': str, '成绩': float}, engine='openpyxl')另外,入库和导出时都要保证学号是 VARCHAR,别为了省事用整数,后患无穷。
5.2 Excel 编码报错 UnicodeDecodeError,或者读出来全是乱码
现象:pandas 读取 .xls 或 .xlsx 时抛 UnicodeDecodeError,或者打开 CSV 后中文字段显示乱码。
原因:CSV 文件是 GBK 编码而 pandas 默认按 UTF-8 读取;.xlsx 其实不是编码问题,但也常有“打开就报错”的情况发生,原因多为文件被 WPS 以低版本格式保存,扩展名是 .xlsx 实际是 .xls 结构。
解决:对 CSV 指定 encoding='gbk' 或 'gb18030',对 .xlsx 统一指定 engine='openpyxl'。如果文件报错,先确认真实格式,不要信扩展名。
df_csv = pd.read_csv(file, encoding='gb18030') df_xlsx = pd.read_excel(file, engine='openpyxl')5.3 同一个学生被重复预警,同一学期预警记录翻倍
现象:重新运行一次计算脚本,预警表里多出一倍记录,红警学生出现两条完全一样的数据。
原因:计算脚本没有做“幂等处理”,每次运行都全量插入,而不是先按学期清理或通过唯一键去重。
解决:设计预警表时加上学期和学生的联合唯一索引,写入时用 INSERT ... ON DUPLICATE KEY UPDATE 或先 DELETE 再 INSERT。
INSERT INTO tb_warning (stu_id, semester, warning_level, trigger_reason, is_processed) VALUES (%s, %s, %s, %s, 0) ON DUPLICATE KEY UPDATE warning_level = VALUES(warning_level), trigger_reason = VALUES(trigger_reason), is_processed = 0;5.4 补考和重修的绩点口径不一致
现象:同一门课挂了之后补考 60 分,汇总时有的学生绩点正常,有的却被记成挂科且绩点 0。
原因:成绩单里有多个成绩记录,早期计算脚本没有区分“原始成绩”和“补考成绩”,导致把补考通过的人也算成挂科。
解决:成绩表里增加 is_exam 字段和取成绩优先级的逻辑。实际业务通常看“最高有效成绩”,所以计算时先按学号、课程分组取最新一次记录,再参与绩点计算。
df_latest = df.sort_values('score_id').groupby(['stu_id', 'course_id']).tail(1)5.5 部署到服务器后,定时任务不执行,或者报告时间对不上
现象:本地跑 APScheduler 正常,部署到云服务器后定时任务不触发,每天早上打开系统发现预警报告显示的是昨天凌晨时间。
原因:服务器时区可能是 UTC,而代码里用的是本地时间的 naive datetime。
解决:APScheduler 初始化时显式指定时区。
from apscheduler.schedulers.background import BackgroundScheduler from pytz import timezone scheduler = BackgroundScheduler(timezone=timezone('Asia/Shanghai')) scheduler.add_job( generate_warning_report, trigger='cron', hour=22, minute=0, day_of_week='sun' ) scheduler.start()这几条每一条我都拿真实成绩单验证过。尤其是补考口径,最离谱的一个数据失误是全校有 300 多个学生因为“补考成绩没匹配上”被误标为黄色预警,后果是辅导员逐班核对名单时发现名单和实际完全对不上,而又复盘了整整两轮才发现问题出在关联逻辑上。所以每次修改数据口径后,请务必在控制台打印异常样本,哪怕只是看看“挂科 3 门学生中是否存在成绩为空”这种统计量级上的转折点都能救回来。
6. 让系统真正被用起来:数据校验、定时预警与答辩技巧
预警系统交付不是代码能跑就结束,真正的验收标准是把它挂在真实机房跑一个完整学期。我建议你在正式投入使用前,加一个数据校验模块:每天定时扫描成绩表里学号为空、成绩超过 100 的数值、课程学分缺失的记录,汇总成异常清单发给管理员邮箱。这个模块不复杂,但能让你从“系统做完”的幻觉里抽身出来。你会发现真实数据比你预想的烂得多,有了异常清单才能回头修流程。
定时预警建议固定为每周日晚上十点自动触发,因为教务处周五成绩录入基本结束,周末算完,周一辅导员正好拿到名单。下面是调度策略:
- 周五晚 8 点:同步教务系统最新成绩文件到服务器指定目录。
- 周六早 8 点:跑数据清洗脚本,生成异常报告。
- 周六晚 10 点:跑预警规则,更新预警表并导出 Excel 报表。
- 周一早 9 点:给辅导员推送本周新增预警学生名单。
答辩时最容易被问的问题有两个:一是你的规则为什么这样定,二是你的系统哪里体现了工程性。第一个问题,你要能说出黄色、橙色、红色之间的数据分界点依据,而不是“我觉得”。第二个问题,就指给评委看数据校验模块和定时调度——大多数毕设项目都死在没有异常处理意识,你能展现出从脏数据到干净数据的处理链条,已经超过了绝大多数的项目。
我自己在最初做这套系统时,最愧疚的一笔债是忽略了缓考、免听这种非标准状态,就是我在 3.2 节里提醒过的坑——成绩列里有“缓考”两个字,pandas 按字符串处理,数据库存的 float 类型报错,于是整门课被过滤掉,学生直接从预警名单里消失。后来我在读取层把所有非数值成绩统一映射成特殊标记,单独建了张细分状态表,才把这个坑彻底填平。以后的每个学期,我都会在正式运行前拿着上一年的真实成绩单先跑一遍完整脚本,再和人工统计对比一轮。希望这份拆解和避坑思路也能帮到你自己的项目,少走一段夜路。
本文还有配套的精品资源,点击获取