北大选课网 补退选 阶段自动选课小工具 v2.0.1 (2019.09.09)
目前支持 本科生(含辅双)
和 研究生
选课,其中本科生辅双身份选课仅支持第 1 页的课程
- 运行过程中不需要进行任何人为操作,且支持同时通过其他设备、IP 访问选课网
- 利用机器学习模型自动识别验证码,具体参见我的项目 PKUElectiveCaptcha ,识别率测试值为 95.6%
- 具有较为完善的错误捕获机制,不容易在运行中意外退出
- 可以选择性开启额外的监视器进程,之后可以通过端口监听当前的选课状况
- 支持多进程下的多账号/多身份选课
该项目至少需要 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 flask
可以改用清华 pip 源,加快下载速度
$ pip3 install requests lxml Pillow numpy sklearn flask -i https://pypi.tuna.tsinghua.edu.cn/simple
可选依赖包
$ pip3 install simplejson
- 复制
config.sample.ini
文件,并将所复制得的文件重命名为config.ini
- 根据系统类型选择合适的
course.csv
,同理复制course.*.sample.csv
并将所得文件重命名为course.*.csv
,即course.utf-8.csv/course.gbk.csv
,以确保 csv 表格用软件打开后不会乱码- 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
- 修改
coding/csv_coding
项,使之与所用course.*.csv
的编码匹配 - 填写 IAAA 认证所用的学号和密码
- 如果是双学位账号,则设置
dual_degree
项为true
,同时设置双学位登录身份identity
,只能填bzx
,bfx
,分别代表主修
和辅双
;对于非双学位账号,则设置dual_degree
为false
,此时登录身份项没有意义 - 如有需要,可以修改刷新间隔,但 不要将刷新间隔改得过短!
- 修改
- 进入项目根目录,利用
python3 main.py
命令运行主程序,即可开始自动选课。
如有需要,可以进行下面的部分测试,确保程序可以在 你的补退选页
中正常运行:
-
可以通过向课程列表中添加如下几种课程,测试程序的反应:
- 正常的可以直接补选上的课程
- 已经选满的课程
- 上课时间/考试时间冲突的课程
- 相同课号的课程(其他院的相同课或同一门课的不同班)
- 性质互斥的课程(例如:线代与高代)
- 跨院系选课阶段开放的其他院专业课
-
可以尝试一下超学分选课会出现什么情况
- 之后手动退选的时候不要点错课噢 QvQ
- 研究生不能修改选课计划,请慎重测试,不要随便添加其他课程,以免造成不必要的麻烦!
自 v2.0.0
起,可以在程序运行时指定命令行选项。通过 python3 main.py --help
查看帮助。
$ python3 main.py --help
Usage: main.py [options]
PKU Auto-Elective Tool v2.0.1 (2019.09.09)
Options:
--version show program's version number and exit
-h, --help show this help message and exit
--config=FILE custom config file encoded with utf8
--course-csv-utf8=FILE
custom course.csv file encoded with utf8
--course-csv-gbk=FILE
custom course.csv file encoded with gbk
--with-monitor run the monitor process simultaneously
通过指定命令行参数,可以开启以下的功能:
可以为每一个账号单独创建一个配置文件和一个课程列表,在不同的进程中以不同的配置文件运行该项目,以实现多账号同时刷课
假如为 Alice 和 Bob 同学创建了如下的文件,填写好了相应配置。假设它们与 main.py
处于同一目录下
$ ls
config.alice.ini course.utf-8.alice.ini config.bob.ini course.gbk.bob.ini main.py
接下来分别在两个终端中运行下面两个命令,即可实现多账号刷课
$ python3 main.py --config ./config.alice.ini --course-csv-utf8 ./course.utf-8.alice.ini
$ python3 main.py --config ./config.bob.ini --course-csv-gbk ./course.gbk.bob.ini
假如你拥有一个可以连上 elective.pku.edu.cn
和 iaaa.pku.edu.cn
的服务器,你可以在服务器上运行这个项目,并开启监听进程,然后通过访问特定地址来查看当前的运行状态。具体的配置方法如下:
- 在
config.ini
中修改需要绑定的host/post
- 在运行时指定
--with-monitor
参数,即python3 main.py --with-monitor
- 请求相应的地址即可查看运行状态。例如按照默认设置,可以请求
http://127.0.0.1:7074
可以通过 nginx 进行反向代理,配置示例如下:
# filename: nginx.autoelective.conf
# coding: utf-8
server {
listen 12345;
server_name 10.123.124.125;
charset UTF-8;
location / {
proxy_pass http://127.0.0.1:7074;
}
}
在这个示例中,通过访问 http://10.123.124.125:12345
可以查看运行状态
该项目为这个监视器注册了如下路由:
GET / 同 /rules
GET /all 完整的状态
GET /current 当前候选的课程
GET /goals 输出原始的选课计划(直接从 course.csv 中读取到的课程)
GET /ignored 已经被忽略的课程及相应原因(已选上/无法选)
GET /loop 当前的运行到第几个循环,用来判断 loop 进程是否存活
GET /rules 输出这个路由列表
例如,请求 http://10.123.124.125:12345/all
可以查看完整的状态
autoelective/
目录结构如下
$ tree autoelective/
autoelective/
├── captcha 验证码相关
│ ├── classifier.py 模型导入与分类器类
│ ├── feature.py 与特征向量提取相关的函数
│ ├── __init__.py 验证码识别结果的模型和验证码识别类
│ ├── model 可用模型
│ │ ├── KNN.model.f5.l1.c1.bz2
│ │ ├── RandomForest.model.f2.c6.bz2
│ │ └── SVM.model.f3.l1.c9.xz
│ └── processor.py 验证码图像处理相关的函数
├── client.py 客户端的基类
├── config.py ini 配置文件的解析类及配置的模型声明
├── const.py 文件夹路径、URL 等常数
├── course.py 课程模型
├── elective.py 与 elective.pku.edu.cn 的接口通信的客户端类
├── exceptions.py 错误类
├── hook.py 对客户端请求结果进行校验的相关函数
├── iaaa.py 与 iaaa.pku.edu.cn 的接口通信的客户端类
├── __init__.py
├── _internal.py 内部工具函数
├── logger.py 日志类声明
├── loop.py 主循环进程的入口
├── monitor.py 监视器进程的入口
├── parser.py 网页解析相关的函数
└── utils.py 通用工具函数
基本的思路是轮询服务器。利用 iaaa.py
和 elective.py
中定义的客户端类与服务器进行交互,请求结果借助 parser.py
中定义的函数进行解析,然后通过 hook.py
中定义的函数对结果进行校验,如果遇到错误,则抛出 exceptions.py
中定义的错误类,循环体外层可以捕获相应的错误。每回合循环结束后,根据当回合的运行状况适当调整下回合的请求策略。
具体的流程如下:
- 一次循环回合开始,打印候选课程的列表和已忽略课程的列表。
- 判断 IAAA Token 是否过期(可以通过设置
config.ini
中的client/iaaa_relogin_interval
来定义 token 的有效时间),如果过期则先登录。 - 获得补退选页的 HTML ,并解析 “选课计划” 列表和 “已选课程” 列表。
- 校验
course.csv
所列课程的合理性(即必须出现在 “选课计划” 或 “已选课程” 中),随后结合上一步的结果筛选出当回合有选课名额的课程。 - 如果发现存在可选的课程,则依次提交选课请求。在每次提交前先自动识别一张验证码。
- 根据请求结果调整候选课程列表,并结束当次回合。
- 当次循环回合结束后,等待一个带随机偏量的
refresh_interval
时间(可在config.ini
中修改该值)。
在运行时指定 --with-monitor
参数,可以开启 monitor
进程。此时会在主进程中开启 loop
和 monitor
两个子进程,它们通过 multiprocessing.Manager
共享一部分资源(计划选课列表、已忽略课程列表等)。monitor 本质是一个 server 应用,它注册了可以用于查询共享资源状态的路由,此时通过访问 server 所绑定的地址,即可实现对 loop 状态的监听。
在 config.ini
中提供了如下的选项:
client/debug_print_request
如果你需要了解每个请求的细节,可以将该项设为true
,会将与请求相关的一些重要信息打印到终端。如果你需要知道其他的请求信息,可以自行修改hook.py
下的debug_print_request
函数client/debug_dump_request
会用pickle/gzip
记录该请求的Response
对象,如果发生未知的错误,仍然可以恢复出当时的请求。如有必要可以将该项设为True
以开启该功能。关于未知错误,详见 未知错误警告 小节。日志会被记录在log/request/
目录下,可以通过utils.py
中的pickle_gzip_load
函数重新导入
client/iaaa_client_timeout
IAAA 客户端的最长请求超时client/elective_client_timeout
Elective 客户端的最长请求超时,考虑到选课网在网络阻塞的时候响应时间会很长,这个时间默认比 IAAA 的客户端要长
各种异常类定义参看 exceptions.py
。每个类下均有简短的文字说明。
对应于 elective.pku.edu.cn
的各种系统异常页,目前可识别:
- 请不要用刷课机刷课: 请求头未设置
Referer
字段,或者未事先提交验证码校验请求,就提交选课请求(比如在 Chrome 的开发者工具中,直接找到 “补选” 按钮在 DOM 中对应的链接地址并单击访问。 - Token无效: token 失效
- 尚未登录或者会话超时: cookies 中的 session 信息过期
- 不在操作时段: 例如,在预选阶段试图打开补退选页
- 索引错误: 貌似是因为在其他客户端操作导致课程列表中的索引值变化
- 验证码不正确: 在补退选页填写了错误验证码后刷新页面
- 无验证信息: 辅双登录时可能出现,原因不明
- 你与他人共享了回话,请退出浏览器重新登录: 同一浏览器内登录了第二个人的账号,则原账号选课页会报此错误(由于共用 cookies)
- 只有同意选课协议才可以继续选课! 第一次选课时需要先同意选课协议
对应于 补退选页
各种提交操作(补选、退选等)后的提示框反馈,目前可识别:
- 补选课程成功: 成功选课后的提示
- 您已经选过该课程了: 已经选了相同课号的课程(可能是别的院开的相同课,也可能是同一门课的不同班)
- 上课时间冲突: 上课时间冲突
- 考试时间冲突 考试时间冲突
- 超时操作,请重新登录: 貌似是在 cookies 失效时提交选课请求(比如在退出登录或清空
session.cookies
的情况下,直接提交选课请求) - 该课程在补退选阶段开始后的约一周开放选课: 跨院系选课阶段未开放时,试图选其他院的专业课
- 您本学期所选课程的总学分已经超过规定学分上限: 选课超学分
- 选课操作失败,请稍后再试: 未知的操作失败,貌似是因为请求过快
- 只能选其一门: 已选过与待选课程性质互斥的课程(例如:高代与线代)
- 学校规定每学期只能修一门英语课: 一学期试图选修多门英语课
- 为了避免访问频率过快,每一个循环回合,不管是正常结束还是因为遇到异常而结束,都会暂停一下,确保每两回合间保持适当的间隔,这个时间间隔不可以改得过短 ,否则有可能对服务器造成压力!(据说校方选课网所在的服务器为单机)
- 不要修改
course.csv
的文件编码、表头字段、文件格式,不要添加或删除列,不要在空列填写任何字符,否则可能会造成 csv 文件不能正常读取。 - 该项目通过指定 I/O 相关函数的
encoding
参数为utf-8-sig
来兼容带 BOM 头的 UTF-8 编码的文件,包括config.ini
,course.csv
,如果仍然存在问题,请不要使用记事本 NotePad
进行文件编辑,应改用更加专业的编辑工具或者代码编辑器,例如NotePad ++
,Sublime Text
,VSCode
,PyCharm
等,对配置文件进行修改,并以无 BOM 的 UTF-8
编码保存文件。 - 该项目针对
预选页
和补退选页
相关的接口进行设计,elective.py
内定义的接口请求方法,只在 补退选 阶段进行过测试,不能保证适用于其他阶段。 - 该项目针对如下的情景设计:课在有空位的时候可以选,但是当前满人无法选上,需要长时间不断刷新页面。对于有名额但是网络拥堵的情况(比如到达某个特定的选课时间节点时),用该项目选课 不一定比手选快,因为该项目在每次启动前会先登录一次 IAAA ,这个请求在网络堵塞的时候可能很难完成。如果你已经通过浏览器提前登入了选课网,那么手选可能是个更好的选择。
- 在 2019.02.22 下午 5:00 跨院系选课名额开放的时刻,有人使用该项目试图抢
程设3班
,终端日志表明,程序运行时发现程设3班
存在空位,并成功选上,但人工登录选课网后发现,实际选上了程设4班(英文班)
。使用者并未打算选修英文班,且并未将程设4班
加入到course.csv
中,而仅仅将其添加到教学网 “选课计划” 中,在网页中与程设3班
相隔一行。从本项目的代码逻辑上我可以断定,网页的解析部分是不会出错的,对应的提交选课链接一定是程设3班
的链接。可惜没有用文件日志记录网页结构,当时的请求结果已无从考证。从这一极其奇怪的现象中我猜测,北大选课网的数据库或服务器有可能存在 线程不安全 的设计,也有可能在高并发时会偶发 Race condition 漏洞。因此,我在此 强烈建议: (1) 不要把同班号、有空位,但是不想选的课放在选课计划内; (2) 不要在学校服务器遭遇突发流量的时候拥挤选课。 否则很有可能遭遇 未知错误!
- 本项目仅供参考学习,你可以修改和使用这个项目,但请自行承担由此造成的一切后果
- 严禁在公共场合扩散这个项目,以免给你我都造成不必要的麻烦
- PKUElectiveCaptcha MIT LICENSE
- PKUAutoElective MIT LICENSE