Skip to content

zhongxinghong/PKUAutoElective

Repository files navigation

PKUAutoElective

北大选课网 补退选 阶段自动选课小工具 v6.0.0 (2021.03.12)

目前支持 本科生(含辅双)研究生 选课

停更说明

感谢大家两年多来对这个项目的支持!我已经大四了,这学期结束后我将毕业,那之后我将不再更新这个项目。由于测试较为困难,这个项目一直以来基本都没有接受过 PR,如果有更改和扩充功能的需要,建议你 fork 一个自己的分支。停更后该项目不会 archive,有任何问题仍然可以在 Issue 里共同讨论,如果你想宣传自己的改版后的分支,也可以在 Issue 里分享

小白兔 写于 2021.03.12

注意事项

特地将一些重要的说明提前写在这里,希望能得到足够的重视

  1. 不要使用过低的刷新间隔,以免对选课网服务器造成压力,建议时间间隔不小于 4 秒
  2. 选课网存在 IP 级别的限流,访问过于频繁可能会导致 IP 被封禁

特点

  • 运行过程中不需要进行任何人为操作,且支持同时通过其他设备、IP 访问选课网
  • 利用专门训练的 CNN 模型自动识别验证码,识别准确率为 99.16%,详细见 PKUElectiveCaptcha2021Spring
  • 具有较为完善的错误捕获机制,程序鲁棒性好
  • 提供额外的监视器线程,开启后可以通过端口监听进程运行状况,为服务器上部署提供可能
  • 支持多进程下的多账号/多身份选课
  • 可以自定义额外的选课规则,目前支持互斥规则和延迟规则

安装

Python 3

该项目至少需要 Python 3,可以从 Python 官网 下载并安装(项目开发环境为 Python 3.6.8)

例如在 Linux 下运行:

$ apt-get install python3

Repo

下载这个 repo 至本地。点击右上角的 Code -> Download ZIP 即可下载

对于 git 命令行:

$ git clone https://github.com/zhongxinghong/PKUAutoElective.git

Packages

安装 PyTorch 外的依赖包(该示例中使用清华镜像源以加快下载速度)

$ pip3 install requests lxml Pillow opencv-python numpy flask -i https://pypi.tuna.tsinghua.edu.cn/simple

安装 PyTorch,从 PyTorch 官网 中选择合适的条件获得下载命令,然后复制粘贴到命令行中运行即可下载安装 (CUDA 可以为 None),PyTorch 版本必须要大于 1.4.x,否则无法读取 CNN 模型

示例选项:

  • PyTorch Build: Stable (1.8.0)
  • Your OS: Windows
  • Package: Pip
  • Language: Python
  • CUDA: None

复制粘贴所得命令在命令行中运行:

pip3 install torch==1.8.0+cpu torchvision==0.9.0+cpu torchaudio===0.8.0 -f https://download.pytorch.org/whl/torch_stable.html

该项目不依赖 torchvision 和 torchaudio,因此你可以只安装 torch

pip3 install torch==1.8.0+cpu -f https://download.pytorch.org/whl/torch_stable.html

PyTorch 安装时间可能比较长,需耐心等待

验证码识别模块测试

这个测试旨在检查与验证码识别模块相关的依赖包是否正确安装,尤其是 PyTorch, OpenCV

$ cd test/
$ python3 test_cnn.py

Captcha('er47') True
Captcha('rskh') True
Captcha('uesg') True
Captcha('skwc') True
Captcha('mmfk') True

基本用法

  1. 复制 config.sample.ini 文件,所得的新文件重命名为 config.ini
    • 直接复制文件,不要新建一个文件叫 config.ini,然后复制粘贴内容,否则可能会遇到编码问题
  2. 用文本编辑器打开 config.ini (建议用代码编辑器,当然记事本一类的系统工具也可以)
  3. 配置 [user],详细见注释
    • 如果是双学位账号,设置 dual_degreetrue,同时需要设置登录身份 identity,非双学位账号两者均保持默认即可
  4. 在选课网上,将待选课程手动添加到选课网的 补退选选课计划 中,并确保它们处在 补退选选课计划 列表的 同一页
    • 如果想刷的课处在不同页,可以参考 多进程选课
    • 该项目无法事前检查选课计划的合理性,只会根据选课的提交结果来判断某门课是否能够被选上,所以请自行 确保填写的课程在有名额的时候可以被选上,以免浪费时间。选课失败引发的常见错误可参见 异常处理
  5. 配置 [course] 定义待选课程,详细见注释
  6. 如果有需要,可以配置 [mutex],[delay],详细见注释。如要使用,请务必仔细阅读 自定义选课规则
  7. 配置 [client],详细见注释(如果不理解选项的含义,建议不要修改)
    • supply_cancel_page 指定实际刷新第几页,确保这个值等于 (4) 中待选课程所处的页数
    • refresh_interval / random_deviation 设置刷新的时间间隔(如果有需要),切记 不要将刷新间隔改得过短,以免对选课网服务器造成太大压力
  8. 进入项目根目录,运行 python3 main.py,即可开始自动选课。

高级用法

命令行参数

输入 python3 main.py -h 查看帮助

$ python3 main.py -h

Usage: main.py [options]

PKU Auto-Elective Tool v6.0.0 (2021.03.12)

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -c FILE, --config=FILE
                        custom config file encoded with utf8
  -m, --with-monitor    run the monitor thread simultaneously

多进程选课

如果你有多个账号需要选课,那么可以为每一个账号单独配置一个 config.ini 然后以不同的配置文件运行多个进程,即可实现多账号同时刷课

例如你为 Alice 和 Bob 同学创建了这两个文件,假设它们处在 config/ 文件夹中(手动创建)

$ ls

config  main.py

$ ls config/

config.alice.ini  config.bob.ini

接下来在两个终端中分别运行下面两个命令,即可实现多账号刷课

$ python3 main.py -c ./config/config.alice.ini
$ python3 main.py -c ./config/config.bob.ini

由于选课网单 IP 下存在会话数上限,开启多进程时还需更改 [client] 中的 elective_client_pool_size 项,合理分配各个进程的会话数。同一 IP 下所有进程的会话总数不能超过 5。建议值: 单进程 3~4; 两进程 2+2; 三进程 1+1+2 ... (保留一个会话给浏览器正常访问选课网)

如果教学网的 选课计划 列表很长,想刷的课处在不同页,也可以通过类似的方法实现多页选课。例如:要同时刷第 1 页和第 2 页的课程,那么分别将两页的课配置成两个 config.ini,修改相应的 supply_cancel_page,然后按照上法运行即可。

$ ls config/

config.p1.ini  config.p2.ini

监视器

如果你拥有可以同时连上 elective.pku.edu.cniaaa.pku.edu.cn 的服务器,你可以在服务器上部署这个项目,并且开启监听线程,配置相应的路由,之后就可以通过外网访问服务器来查看当前运行状态。

示例:

  1. 配置 [monitor],修改需要绑定的 host/port
  2. 在运行时指定额外 -m 参数,即 python3 main.py -m
  3. 利用 nginx 进行反向代理。假设监视器线程监听 http://127.0.0.1:7074,相应的配置示例如下:
# 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  /              查看该路由规则
GET  /rules         同 /
GET  /stat          同 /
GET  /stat/course   查看与选课相关的状态
GET  /stat/error    查看与错误相关的状态
GET  /stat/loop     查看与 loop 线程相关的状态

例如,请求 http://10.123.124.125:12345/stat/course 可以查看与选课相关的状态

自定义选课规则

互斥规则

假设你有多个备选方案,它们在选课规则上并不矛盾,可以同时被选上。例如你在考虑选 A 院的概率统计(B)或是 B 院开的概率统计(B),你希望在选上其中两者其一时就不再考虑选另一门,那么你可以定义这两门课为互斥的,之后在上述情境发生时,另一门课会被程序自动忽略,这样就不会发生两者同时被选上的问题。详细见 Issue #8

务必注意的是,如果互斥的几门课在同一回合内同时出现空位,优先级高的课会被首先提交,而低优先级的课会被忽略(关于课程优先级的概念,参看 config.ini[course] 下的相关注释),虽然多门课同时出现空位的情况比较罕见,但是为了确保在这种情况发生的时候你能选到更加心仪的课,如果你使用了互斥规则,记得将你更希望选上的课定义成更高的优先级

另外,没有必要为本身就是互斥的课定义互斥规则,比如学校一学期只能选一门体育课,如果你现在有十几门体育课候选,你不必额外声明一个互斥规则来指定这些体育课互斥,因为一旦有一门体育课选上,选课网就会拒绝你提交另一门体育课的选课请求,然后其他的体育课在自然的选课失败后就会被自动忽略

延迟规则

假设你有一门课(不妨设为马原)有多个班可以选,但是每个班的受欢迎程度不一样,你希望选择某个比较火爆的班(比如林锋老师的班,不妨设为 A 班),但是它已经被选满了,而另外一个冷门的班(不妨设为 B 班)仍然有很多的名额,你只希望把它作为你的第二志愿,因此你想尝试抢 A 班。但是你发现当你选了 B 班后,出于某些原因(比如 A 班与 B 班时间冲突、不够学分),你将无法再选 A 班,因此你只好把 B 班先撂在一边,去抢 A 班,但是你并不知道 B 班什么时候会被选满,为此你还得不时地监控 B 班当前的选课情况。这个时候你就可以考虑同时添加 A 班和 B 班到选课计划中,并为 B 班定义一个延迟规则。延迟规则使得触发 B 班选课请求的提交必须要满足 B 班的当前空余人数小于等于某个特定的阈值,我们不妨假设这个阈值是 10,那么程序将会在 B 班剩余的空位小于等于 10 的时候,才会提交 B 班的选课请求,否则它将对此视而不见。详细见 Issue #28

如果你要使用这个规则,你必须要注意以下几件事情,以预知一些可能存在的风险:

  1. 我已经对相关逻辑做了简单测试,但是考虑到这个规则出错的后果可能是十分严重的,如果你不放心,还是建议你先拿 B 班测试一下,以确保这个规则能够被程序正确实现

  2. 情况并不一定总和你想象得那么美好: B 班人少的时候,程序肯定会帮我补选上,因此我一定会有课上的。你必须要考虑一些特殊情况下程序可能会对此无能为力,例如在某个选课时间节点的时候,选课网访问量骤增,程序可能会连不上服务器,然后一直放着不选的 B 班一瞬间就被选满,那么这学期你可能就没有这个课上了。举一个我假想的案例:假设你是一名信科大一的学生,这学期想选程设,同理可以假设有上述情境中的 A 班和 B 班,你试着抢 A 班并给 B 班设置了延迟规则,但是在跨院系选课名额开放的那个下午 5 点,很多想要选修程设的外院系同学涌进选课系统,一瞬间把 B 班的名额抢完了,而你的程序一直卡着没法登录,完美错过了 B 班的选课,于是你这学期可能就没有程设课可以上了。对于这种风险,你必须要权衡一下,是否有必要在跨院系选课名额开放前就率先抢下 B 班。如果你打算见好就收,是不是下午 4:50 这样我就把 B 班给选了呢?并不是!因为这个时候系统已经在维护了,你需要在跨院系选课名额开放的那天的中午 12 点前,趁着系统还没开始维护,就把 B 班给选了,否则你还是有可能会错过 B 班的选课

  3. 不要以为空余人数的缩减速率一定是线性的。并不是说平时观察到 B 班每小时少 10 个名额,那么从 20 -> 10 和从 10 -> 0 都将花掉一小时。当延迟规则出现以后,阈值的设置有可能就会变成一次博弈。例如有 30 个程序都在抢 A 班,并均为 B 班设置了延迟规则,假设大家的阈值都是 10,那么当 B 班还剩 10 个名额的时候,它可能在接下来的一瞬间就没了,至少会有 20 个程序将抢不到 B 班。因此,爆发式抢课的情况仍然是有可能发生的

题外话:我个人觉得有课上就已经很好啦,所以最省心的事情是马上选下 B 班,然后当做 A 班不存在 :)

自定义 User-Agent 池

user_agents.txt.gz 中提供了默认的 User-Agent 池,可以通过 gzip 工具解压得到 user_agents.txt 加以查看

每次从 IAAA 登录时,都会从中随机选择一个 User-Agent 并设置到 IAAA 客户端和 Elective 客户端上,在下次重新登录前将不再更换

如果你需要自定义 User-Agent 池,可以在根目录下创建一个 user_agents.user.txt,在其中对其进行定义。格式为一行一条 User-Agent,具体格式可参考 user_agents.txt,需要确保文件为 utf-8 编码。程序会优先选择读入用户自定义的 User-Agent 池

DEBUG 相关

config.ini[client] 中:

  • debug_print_request 如果设置为 true,会将与请求相关的重要信息打印到终端
  • debug_dump_request 会用 pickle+gzip 保存请求的 Response 对象,如果发生未知错误,仍然可以重新导入当时的请求。关于未知错误,详见 未知错误警告

其他配置项

config.ini[client] 中:

  • iaaa_client_timeout IAAA 客户端的最长请求超时
  • elective_client_timeout Elective 客户端的最长请求超时
  • login_loop_interval IAAA 登录循环每两回合的时间间隔
  • elective_client_max_life 设置 Elective 客户端的存活时间。超过存活时间的 Elective 客户端会主动登出并自动重登
  • print_mutex_rules 是否在每次循环时打印完整的互斥规则列表。如果你定义了很复杂的互斥规则,你可以将这个值设为 False 以避免每次循环都将整个列表重复打印一遍

异常处理

系统异常 SystemException

对应于 elective.pku.edu.cn 的各种系统异常页,目前可识别:

  • 请不要用刷课机刷课: 请求头未设置 Referer 字段,或者未事先提交验证码校验请求,就提交选课请求(比如在 Chrome 的开发者工具中,直接找到 “补选” 按钮在 DOM 中对应的链接地址并单击访问)
  • Token无效: token 失效
  • 尚未登录或者会话超时: cookies 中的 session 信息过期
  • 不在操作时段: 例如,在预选阶段试图打开补退选页
  • 索引错误: 貌似是因为在其他客户端操作导致课程列表中的索引值变化
  • 验证码不正确: 在补退选页填写了错误验证码后刷新页面
  • 无验证信息: 辅双登录时可能出现,原因不明
  • 你与他人共享了回话,请退出浏览器重新登录: 同一浏览器内登录了第二个人的账号,则原账号选课页会报此错误(由于共用 cookies)
  • 只有同意选课协议才可以继续选课! 第一次选课时需要先同意选课协议

提示框反馈 TipsException

对应于 补退选页 各种提交操作(补选、退选等)后的提示框反馈,目前可识别:

  • 补选课程成功: 成功选课后的提示
  • 您已经选过该课程了: 已经选了相同课号的课程(可能是别的院开的相同课,也可能是同一门课的不同班)
  • 上课时间冲突: 上课时间冲突
  • 考试时间冲突 考试时间冲突
  • 超时操作,请重新登录: 貌似是在 cookies 失效时提交选课请求(比如在退出登录或清空 session.cookies 的情况下,直接提交选课请求)
  • 该课程在补退选阶段开始后的约一周开放选课: 跨院系选课阶段未开放时,试图选其他院的专业课
  • 您本学期所选课程的总学分已经超过规定学分上限: 选课超学分
  • 选课操作失败,请稍后再试: 未知的操作失败,貌似是因为请求过快
  • 只能选其一门: 已选过与待选课程性质互斥的课程(例如:高代与线代)
  • 学校规定每学期只能修一门英语课: 一学期试图选修多门英语课
  • 学校规定每学期只能修一门体育课: 一学期试图选修多门体育课
  • 该课程选课人数已满: 试图选一门已经满人的课,这本是不允许的操作,但有的时候选课网会偶然出现某门课已选人数突然为 0 的情况,这时提交选课请求会遇到这个错误

补充说明

  1. 一直遇到 [310] 您尚未登录或者会话超时,请重新登录 错误,可能是因为你是双学位账号,但是没有在 config.ini 中设置 dual_degree = true
  2. 不要修改 config.ini 的编码,确保它能够以 utf-8-sig 编码被 Python 解析。如果遇到编码问题,请重新创建一个 config.ini,之后不要使用 记事本 Notepad 进行编辑,应改用更加专业的文本编辑工具或者代码编辑器,例如 NotePad ++, Sublime Text, VSCode 等,并以 无 BOM 的 UTF-8 编码保存文件
  3. 该项目适用于:课在有空位的时候可以选,但是当前满人无法选上,需要长时间不断刷新页面。对于有名额但是网络拥堵的情况(比如到达某个特定的选课时段节点时),程序选课 不一定比手选快,因为该项目每次启动前都会先登录一次 IAAA,这个请求在网络阻塞时可能很难完成,如果你已经通过浏览器提前登入了选课网,那么手动选课可能是个更好的选择
  4. 不要使用过低的刷新间隔,以免对选课网服务器造成压力,建议时间间隔不小于 4 秒
  5. 选课网存在 IP 级别的限流,访问过于频繁可能会导致 IP 被封禁

未知错误警告

  1. 在 2019.02.22 下午 5:00 跨院系选课名额开放的时刻,有人使用该项目试图抢 程设3班,终端日志表明,程序运行时发现 程设3班 存在空位,并成功选上,但人工登录选课网后发现,实际选上了 程设4班(英文班) 。使用者并未打算选修英文班,且并未将 程设4班 加入到 course.csv (从 v3.0.0 起已合并入 config.ini) 中,而仅仅将其添加到教学网 “选课计划” 中,在网页中与 程设3班 相隔一行。从本项目的代码逻辑上我可以断定,网页的解析部分是不会出错的,对应的提交选课链接一定是 程设3班 的链接。可惜没有用文件日志记录网页结构,当时的请求结果已无从考证。从这一极其奇怪的现象中我猜测,北大选课网的数据库或服务器有可能存在 线程不安全 的设计,也有可能在高并发时会偶发 Race condition 漏洞。因此,我在此 强烈建议: (1) 不要把同班号、有空位,但是不想选的课放在选课计划内; (2) 不要在学校服务器遭遇突发流量的时候拥挤选课。 否则很有可能遭遇 未知错误!

历史更新信息

详见 Realease History

版本迁移指南

详见 Migration Guide

责任须知

  • 你可以修改和使用这个项目,但请自行承担由此造成的一切后果
  • 严禁在公共场合扩散这个项目,以免给你我都造成不必要的麻烦

证书

About

北大选课网补退选阶段自动选课小工具

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •