kita / notes

约 3 分钟

想自动抓课表,先卡在了登录页

课表插件的用法一直是让用户自己导出 ics 文件再上传。这步对普通用户来说太麻烦——得知道课表在哪导、怎么导。

我想做成:机器人发个链接,用户点进去输学号密码,后台自己去教务系统抓课表,抓完写进插件。用户只做"输账号密码"这一件事。

想法很顺,登录这一步就不顺了。

密码不是明文提交的

教务系统用的是正方那套。先在服务器上打登录页,看表单:

base = "http://<教务系统域名>"

拿到的 HTML 里,密码框不是普通的 input,提交前会被 JS 处理。往前端 JS 里找,果然有 RSA 相关的东西——密码要先做 RSA 加密再提交,公钥是页面里给的。

Python 这边用 Crypto.PublicKey.RSA 按公钥构造、再对密码加密,这段不难,难的是搞清楚加密前拼什么格式——有的版本还要带上 csrftoken 或者时间戳一起加密。

真正的麻烦:提交逻辑藏在 JS 里

我知道表单字段,但不知道浏览器点了"登录"之后到底发什么请求。

于是开始啃 login.js。打印出来看,找 submit 相关的东西:

#dl 的点击绑定
login_slogin

来来回回好几轮:先是找 #dl 的点击事件,找到之后发现它不直接 submit;又去找表单的 submit 钩子;再翻到 login_slogin 这个函数名。

这段是纯体力活。 前端 JS 经过打包压缩,变量名短得没法读,只能靠关键字一点点试。

最后拼出来的流程是:csrftoken(从页面或 cookie 拿)→ RSA 加密密码 → POST 到登录接口。

怎么知道登进去了

登录这种接口,成功了返回什么、失败了返回什么,都得实测。

我用了两个办法:

一是拿错密码试。 用错的账号密码打一次,看服务器返回什么——有的系统返回一个带错误码的 JSON,有的直接跳登录页。知道"失败长什么样",才能判断成功。

二是对比。 正确账号和错误账号打同一个接口,把两边的响应并排看差异。这个方法看着笨,但对没有文档的老系统最有效。

中间还确认了一件事:这个系统没有统一身份认证和扫码登录,就是最基础的账号密码表单。如果有统一认证,流程会完全不一样。

课表接口

登录态拿到之后的课表接口是 xskbcx_cxXsKb,返回的字段是这套:

xqmc   学期名称
xqj    星期几
jcs    节次
zc     周次

四个字段拼起来就能定位"第几周、星期几、第几节、什么课"。这就是 ics 日历需要的最小信息。

开学日期还得另外拿——课表里只有"第几周",没有"第几周是哪天"。所以又去打了校历接口,从里面抠出学期起止日期。这一步是后来补的,第一次做出来的 ics 时间全是错的。

一个容易忽略的环境坑

抓教务系统的脚本在服务器上跑,一直连不上。

原因是服务器环境里有 HTTP 代理环境变量,httpx 默认会读取它,于是请求被代理走了——而代理到不了学校的教务系统(那是个内网偏外的地址)。

修法是构造 client 的时候显式禁用环境代理:

httpx.AsyncClient(trust_env=False)

这个坑不显眼:代码逻辑全对,网络库"贴心地"帮你走了代理,结果全错。

现在的样子

抓取脚本 course_fetcher.py 加一个 Web 服务(/make_token 生成带 token 的绑定链接)跑在服务器上,用 systemd 管着,另占一个端口。机器人在群里发链接,用户在网页上输账号密码,后台登录抓课表、生成 ics,再写进课程表插件。

← 回到全部笔记