北大选课网 补退选 阶段自动选课小工具 v1.0.4 (2019.02.22)
目前支持 本科生(含辅双)
和 研究生
选课,其中本科生辅双身份选课仅支持第 1 页的课程
- 运行过程中不需要进行任何人为操作,且支持同时通过其他设备、IP 访问选课网
- 利用机器学习模型自动识别验证码,具体参见我的项目 PKUElectiveCaptcha ,识别率测试值为 95.6%
- 具有较为完善的错误捕获机制,可以应对大部分常见的异常情况,确保程序有足够的强健性,不容易在运行时中断
输入文件提供 utf-8
, gbk
两种编码格式,并兼容 UTF-8 with BOM
编码,在 Linux
, Windows
, MacOS
操作系统中均能正常运行。目前已经在 Windows 7
, Debian 9
上做过测试。
该项目至少需要 Python 3 (项目开发环境为 Python 3.6.6),可以从 Python 官网 下载并安装
$ apt-get install python3
下载这个 repo 至本地
$ git clone https://github.com/zhongxinghong/PKUAutoElective.git
安装依赖包
$ pip3 install requests lxml Pillow numpy sklearn
可以改用清华 pip 源,加快下载速度
$ pip3 install requests lxml Pillow numpy sklearn -i https://pypi.tuna.tsinghua.edu.cn/simple
可选依赖包
$ pip3 install simplejson
- 根据当前系统类型选择合适的
course.csv
,以确保 csv 表格用软件打开后不会乱码,并修改config.ini
中的相应配置,使之与实际使用编码匹配。- Linux 若使用
utf-8
编码,可以用 LibreOffice 以UTF-8
编码打开,若使用gbk
编码,可以用 LibreOffice 以GB-18030
编码打开 - Windows 使用
gbk
编码,可以用 MS Excel 打开 - MacOS 若使用
gbk
编码,可以用 MS Excel 打开,若使用utf-8
编码,可以用 numbers 打开
- Linux 若使用
- 将待选课程手动添加到选课网的 “选课计划” 中,并确保所选课程处在 补退选页 中 “选课计划” 列表的 第 1 页 。
- 注:为了保证刷新速度,减小服务器压力,该项目不解析位于选课计划第 1 页之后的课程
- 注:该项目不会事前校验待选课程的合理性,只会根据选课提交结果来判断是否提交成功,所以请自行 确保填写的课程在有名额的时候可以被选上 ,以免浪费时间。部分常见错误可参看 异常处理 小节
- 将待选课程的
课程名
,班号
,开课单位
对应复制到course.csv
中(本项目根据这三个字段唯一确定一个课程),每个课程占一行,高优先级的课程在上(即如果当前循环回合同时发现多个课程可选,则按照从上往下的优先级顺序依次提交选课请求)- 注:考虑到 csv 格式不区分数字和字符串,该项目允许将课号
01
以数字1
的形式直接录入 - 注:请确保每一行的所有字段都被填写,信息填写不完整的行会被自动忽略,并且不会抛出异常
- 注:考虑到 csv 格式不区分数字和字符串,该项目允许将课号
- 修改
config.ini
- 修改
CSV_Coding
项,使之与所用course.csv
的编码匹配 - 填写 IAAA 认证所用的学号和密码
- 如果是双学位账号,则设置
DualDegree
项为true
,同时设置双学位登录身份Identity
,只能填bzx
,bfx
,分别代表主修
和辅双
;对于非双学位账号,则设置DualDegree
为false
,并跳过登录身份的设置 - 如有需要,可以修改刷新间隔
- 修改
- 进入项目根目录,利用
python3 main.py
命令运行主程序,即可实现自动选课。
如有需要,可以进行下面的部分测试,确保程序可以在 你的补退选页
中正常运行:
-
可以通过向课程列表中添加如下几种课程,测试程序的反应:
- 正常的可以直接补选上的课程
- 已经选满的课程
- 时间冲突的课程
- 相同课号的课程(其他院的相同课或同一门课的不同班)
- 性质互斥的课程(例如:线代与高代)
- 跨院系选课阶段开放的其他院专业课
-
可以尝试一下超学分选课会出现什么情况
- 之后手动退选的时候不要点错课噢 QvQ
- 研究生不能修改选课计划,请慎重测试,不要随便添加其他课程,以免造成不必要的麻烦!
.
├── LICENSE
├── README.md
├── autoelective
│ ├── __init__.py
│ ├── _compat.py
│ ├── captcha
│ │ ├── __init__.py 定义用于验证码识别的主类
│ │ ├── classifier.py 定义各模型对应的分类器
│ │ ├── feature.py 定义特征提取函数
│ │ ├── model
│ │ │ ├── KNN.model.f5.l1.c1.bz2
│ │ │ ├── RandomForest.model.f2.c6.bz2
│ │ │ └── SVM.model.f3.l1.c9.xz
│ │ └── preprocess.py 定义图像处理相关的函数
│ ├── client.py 定义客户端抽象基类
│ ├── config.py
│ ├── const.py
│ ├── course.py 定义 Course 课程类
│ ├── elective.py 定义用于 elective.pku.edu.cn 的客户端类
│ ├── exceptions.py
│ ├── hook.py 定义用于请求结果校验的 hooks
│ ├── iaaa.py 定义用于 iaaa.pku.edu.cn 的客户端类
│ ├── logger.py
│ ├── parser.py 定义解析 HTML 的和解析 csv 的函数
│ └── util.py
├── cache
├── config.ini 主配置文件
├── course.gbk.csv GBK 编码的 course.csv 待选课程表
├── course.utf-8.csv UTF-8 编码的 course.csv 待选课程表
├── log
├── main.py 项目主程序
└── requirements.txt
在 main.py
中,利用 iaaa.py
和 elective.py
定义的客户端类与服务器进行交互,hook.py
借助 parser.py
定义的函数解析 response,对每次请求结果进行校验,如果遇到错误,则抛出 exceptions.py
中定义的错误类,main.py
主程序通过捕获这些错误来把握客户端的运行情况,并及时做出相应的反应。
- 一次循环回合开始,打印候选课程的列表和已忽略课程的列表。
- 第一个循环回合会主动登录,刷新本地缓存,后续回合仅在
会话过期
或Token无效
的情况下重新登录(惰性登录),此时会结束当次回合。 - 获得补退选页的 HTML ,并解析 “选课计划” 列表和 “已选课程” 列表。
- 校验
course.csv
所列课程的合理性(即必须出现在 “选课计划” 或 “已选课程” 中),随后结合上一步的结果筛选出本回合有名额的课程。 - 依次提交选课请求,并在每次提交前先自动识别一张验证码。
- 根据提交请求的响应结果调整候选课程列表,并结束当次回合。
- 当次循环回合结束后,等待一个带随机偏量的
Refresh_Interval
时间(可在config.ini
中修改该值)。
各种异常类定义参看 exceptions.py
。每个类下均有简短的文字说明。
对应于 elective.pku.edu.cn 的各种系统异常页,目前可识别:
- 请不要用刷课机刷课: 请求头未设置
Referer
字段,或者未事先提交验证码校验请求,就提交选课请求(比如在 Chrome 的开发者工具中,直接找到 “补选” 按钮在 DOM 中对应的链接地址并单击访问。 - Token无效: token 失效
- 尚未登录或者会话超时: cookies 中的 session 信息过期
- 不在操作时段: 例如,在预选阶段试图打开补退选页
- 索引错误: 貌似是因为在其他客户端操作导致课程列表中的索引值变化
- 验证码不正确: 在补退选页填写了错误验证码后刷新页面
- 无验证信息: 辅双登录时可能出现,原因不明
- 你与他人共享了回话,请退出浏览器重新登录: 同一浏览器内登录了第二个人的账号,则原账号选课页会报此错误(由于共用 cookies)
对应于 “补退选页” 各种提交操作(补选、退选等)后的提示框反馈,目前可识别:
- 补选课程成功: 成功选课后的提示
- 您已经选过该课程了: 已经选了相同课号的课程(可能是别的院开的相同课,也可能是同一门课的不同班)
- 上课时间冲突: 待选课程与某已选课程的上课时间存在冲突
- 超时操作,请重新登录: 貌似是在 cookies 失效时提交选课请求(比如在退出登录或清空
session.cookies
的情况下,直接提交选课请求) - 该课程在补退选阶段开始后的约一周开放选课: 跨院系选课阶段未开放时,试图选其他院的专业课
- 您本学期所选课程的总学分已经超过规定学分上限: 选课超学分
- 选课操作失败,请稍后再试: 未知的操作失败,貌似是因为请求过快
- 只能选其一门: 已选过与待选课程性质互斥的课程(例如:高代与线代)
- 学校规定每学期只能修一门英语课: 一学期试图选修多门英语课
- 为了避免访问频率过快,每一循环回合,不管是正常结束还是因为遇到错误而异常结束,都会暂停一下,每两回合间保持适当的时间间隔,这个时间间隔不可以改得过短 ,否则有可能对服务器造成压力!(据说校方选课网所在的服务器为单机)
- 不要修改
course.csv
的文件名、文件编码、表头字段、文件格式,不要添加或删除列,不要在空列填写任何字符,否则可能会造成 csv 文件不能正常读取。 - 该项目通过统一设置 I/O 输入编码为
utf-8-sig
来兼容带 BOM 头的 UTF-8 编码的文件,包括config.ini
,course.csv
,如果仍然存在问题,请不要使用记事本 NotePad
进行文件编辑,而改用更加专业的编辑工具,例如NotePad ++
,Sublime Text
,进行文件修改,或者改用代码编辑器,例如PyCharm
,进行修改,并以无 BOM 的 UTF-8
编码保存文件。 - 该项目根据
预选页
和补退选页
进行设计,许多接口,例如elective.py
定义的ElectiveClient
下的接口,只适用于 补退选 阶段,不能保证能适用于其他阶段。 - 修改为第一个循环回合主动登录后,本地的 cookies 和 token 缓存机制相当于无用。
- 在 2019.02.22 下午 5:00 跨院系选课名额开放的时刻,有人使用该项目试图抢
程设3班
,终端日志表明,程序运行时发现程设3班
存在空位,并成功选上,但人工登录选课网后发现,实际选上了程设4班(英文班)
。使用者并未打算选修英文班,且并未将程设4班
加入到course.csv
中,而仅仅将其添加到教学网 “选课计划” 中,在网页中与程设3班
相隔一行。从本项目的代码逻辑上我可以断定,程序的网页的解析部分一定是不会出错的,对应的提交选课链接一定是程设3班
的链接。可惜没有用文件日志记录网页结构,当时的请求结果已无从考证。从这一极其奇怪的现象中我猜测,北大选课网的数据库或服务器有可能存在 线程不安全 的设计,也有可能在高并发时会偶发 Race condition 漏洞。因此,我在此 强烈建议: (1) 不要把同班号、有空位,但是不想选的课放在选课计划内; (2) 不要在学校服务器遭遇突发流量的时候拥挤选课。 否则很有可能遭遇 未知错误!
- 本项目仅供参考学习,你可以修改和使用这个项目,但请自行承担由此造成的一切后果
- 严禁在公共场合扩散这个项目,以免给你我都造成不必要的麻烦
- PKUElectiveCaptcha MIT LICENSE
- PKUAutoElective MIT LICENSE