项目地址 GitHub: github.com/cbwang2016/PKUAutoElective2
目前支持 本科生(含辅双) 和 研究生 选课
特点
运行过程中不需要进行任何人为操作,且支持同时通过其他设备、IP 访问选课网
利用机器学习模型自动识别验证码,具体参见我的项目 PKUElectiveCaptcha ,识别率测试值为 95.6%
具有较为完善的错误捕获机制,不容易在运行中意外退出
可以选择性开启额外的监视器进程,之后可以通过端口监听当前的选课状况
支持多进程下的多账号/多身份选课
安装
该项目至少需要 Python 3 (项目开发环境为 Python 3.6.6),可以从 Python 官网 下载并安装
例如在 Debian-Linux 下运行:
$ apt-get install python3
下载这个 repo 至本地。点击右上角的 Clone or download 即可下载
对于 git 命令行:
$ 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 打开
将待选课程手动添加到选课网的 “选课计划” 中,并确保所选课程处在 补退选页 中 “选课计划” 列表的 第 1 页 。
注:为了保证刷新速度,减小服务器压力,该项目不解析位于选课计划第 1 页之后的课程
注:该项目不会事前校验待选课程的合理性,只会根据选课提交结果来判断是否提交成功,所以请自行 确保填写的课程在有名额的时候可以被选上 ,以免浪费时间。部分常见错误可参看 异常处理 小节
将待选课程的 课程名, 班号, 开课单位 对应复制到 course.csv 中(本项目根据这三个字段唯一确定一个课程),每个课程占一行,高优先级的课程在上(即如果当前循环回合同时发现多个课程可选,则按照从上往下的优先级顺序依次提交选课请求)
注:考虑到 csv 格式不区分数字和字符串,该项目允许将课号 01 以数字 1 的形式直接录入
注:请确保每一行的所有字段都被填写,信息填写不完整的行会被自动忽略,并且不会抛出异常
配置 config.ini
修改 coding/csv_coding 项,使之与所用 course.*.csv 的编码匹配
填写 IAAA 认证所用的学号和密码
如果是双学位账号,则设置 dual_degree 项为 true ,同时设置双学位登录身份 identity ,只能填 bzx, bfx ,分别代表 主修 和 辅双 ;对于非双学位账号,则设置 dual_degree 为 false ,此时登录身份项没有意义。注:以 双学位账号的主学位身份 进行选课仍然需要将 dual_degree 设为 true ,否则可能会遇到一直显示会话过期/尚未登录的情况。
如果待选的课程不在选课计划的第一页,并且无法将第一页的其他课程删除,你可以通过修改 supply_cancel_page 来指定实际刷新第几页。注:该项目一个进程只能刷新一页的选课计划,如果你需要选的课处于选课计划的不同页,则需要为每个页面分别开一个进程,详见 高级用法 中的 多账号设置 小节
如有需要,可以修改刷新间隔项 refresh_interval 和 random_deviation,但 不要将刷新间隔改得过短!
进入项目根目录,利用 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.csv config.bob.ini course.gbk.bob.csv main.py
接下来分别在两个终端中运行下面两个命令,即可实现多账号刷课
$ python3 main.py --config ./config.alice.ini --course-csv-utf8 ./course.utf-8.alice.csv
$ python3 main.py --config ./config.bob.ini --course-csv-gbk ./course.gbk.bob.csv
由于选课网存在会话数上限,开启多进程的时候还需要调整各进程的配置文件中的 client/elective_client_pool_size 项,合理分配各个进程的会话数。详见 其他配置项 。同一 IP 下所有进程的会话总数不超过 5 。建议值: 单进程 4; 两进程 2+2; 三进程 1+1+2 ......
开启监视器
假如你拥有一个可以连上 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 /errors 当前已捕获到的错误数
GET /goals 输出原始的选课计划(直接从 course.csv 中读取到的课程)
GET /ignored 已经被忽略的课程及相应原因(已选上/无法选)
GET /login_loop login-loop 当前循环数
GET /main_loop main-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 通用工具函数
运行流程
loop 进程
基本的思路是轮询服务器。利用 iaaa.py 和 elective.py 中定义的客户端类与服务器进行交互,请求结果借助 parser.py 中定义的函数进行解析,然后通过 hook.py 中定义的函数对结果进行校验,如果遇到错误,则抛出 exceptions.py 中定义的错误类,循环体外层可以捕获相应的错误。并判断应该退出还是进入下回合。
采用多 elective 客户端的机制,存在着可用的 elective 客户端池 electivePool 和需登录/重登的 elective 客户端池 loginPool,在 loop 进程内有 login-loop 和 main-loop 两个子线程。
login-loop 线程
该线程维护一个登录循环:
监听 loginPool ,阻塞线程,直到出现需要登录的客户端
就尝试对该客户端进行登录
登录成功后将该客户端放入 electivePool ,如果登录失败,则持有该客户端进入下一回合
结束循环,不管成功失败,等待 login_loop_interval 时间(可在 config.ini 中修改)
main-loop 线程
该线程负责轮询选课网及提交选课请求,运行流程如下:
一次循环回合开始,打印候选课程的列表和已忽略课程的列表。
从 electivePool 中获取一个客户端,如果 electivePool 为空则阻塞线程,如果客户端尚未登录,则立刻停止当前回合,跳至步骤 (8)
获得补退选页的 HTML ,并解析 “选课计划” 列表和 “已选课程” 列表。
校验 course.csv 所列课程的合理性(即必须出现在 “选课计划” 或 “已选课程” 中),随后结合上一步的结果筛选出当回合有选课名额的课程。
如果发现存在可选的课程,则依次提交选课请求。在每次提交前先自动识别一张验证码。
根据请求结果调整候选课程列表,并结束当次回合。
将当前客户端放回 electivePool ,下回合会重新选择一个客户端
当次循环回合结束后,等待一个带随机偏量的 refresh_interval 时间(可在 config.ini 中修改该值)。
monitor 进程
在运行时指定 --with-monitor 参数,可以开启 monitor 进程。此时会在主进程中开启 loop 和 monitor 两个子进程,它们通过 multiprocessing.Manager 共享一部分资源(计划选课列表、已忽略课程列表等)。monitor 本质是一个 server 应用,它注册了可以用于查询共享资源状态的路由,此时通过访问 server 所绑定的地址,即可实现对 loop 状态的监听。
DEBUG 相关
在 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 的客户端要长
client/elective_client_pool_size Elective 客户端池的最大容量。注:根据观察,每个 IP 似乎只能总共同时持有 5 个会话,否则会遇到 elective 登录时无限超时的问题。因此这个这个值不宜大于 5 (如果你还需要通过浏览器访问选课网,则不能大于 4)。
client/login_loop_interval IAAA 登录循环每两回合的时间间隔
异常处理
各种异常类定义参看 exceptions.py 。每个类下均有简短的文字说明。
系统异常 SystemException
对应于 elective.pku.edu.cn 的各种系统异常页,目前可识别:
请不要用刷课机刷课: 请求头未设置 Referer 字段,或者未事先提交验证码校验请求,就提交选课请求(比如在 Chrome 的开发者工具中,直接找到 “补选” 按钮在 DOM 中对应的链接地址并单击访问。
Token无效: token 失效
尚未登录或者会话超时: cookies 中的 session 信息过期
不在操作时段: 例如,在预选阶段试图打开补退选页
索引错误: 貌似是因为在其他客户端操作导致课程列表中的索引值变化
验证码不正确: 在补退选页填写了错误验证码后刷新页面
无验证信息: 辅双登录时可能出现,原因不明
你与他人共享了回话,请退出浏览器重新登录: 同一浏览器内登录了第二个人的账号,则原账号选课页会报此错误(由于共用 cookies)
只有同意选课协议才可以继续选课! 第一次选课时需要先同意选课协议
提示框反馈 TipsException
对应于 补退选页 各种提交操作(补选、退选等)后的提示框反馈,目前可识别:
补选课程成功: 成功选课后的提示
您已经选过该课程了: 已经选了相同课号的课程(可能是别的院开的相同课,也可能是同一门课的不同班)
上课时间冲突: 上课时间冲突
考试时间冲突 考试时间冲突
超时操作,请重新登录: 貌似是在 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) 不要在学校服务器遭遇突发流量的时候拥挤选课。 否则很有可能遭遇 未知错误!