秉持着「能让机器完成的事情,绝不人工操作」的原则,早在入学军训之时,我就萌生了使用脚本打卡的念头,并且顺利地将其实现。时至今日,脚本也算是经历了几次迭代,现将技术细节全部公开出来,与大家一起交流讨论

校方已经不再信任由帐号密码登录的会话,先前的打卡方式全部失效

初号机

  初代打卡机采用了非常原始的实现方式,需要挂在 VPS 上定时运行,简单粗暴。纵观整个 web 登录打卡的过程,其实就只有 3 个步骤:

  在 web 端不像在微信中那样可以通过 oauth 直接登录,网页首先会请求:

https://fangkong.hnu.edu.cn/api/v1/account/getimgvcode

  这个 API(可以点击试试),得到一个 Token,然后拿着这个 Token 再次请求:

https://fangkong.hnu.edu.cn/imagevcode?token={{Token}}

  得到验证码图片,由于验证码内容是纯数字,比较简单,直接使用 Tesseract 强行识别,也可以达到勉强能用的识别率(验证码错误 10 次左右登录成功):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re
import subprocess as sp


def ocr(image):
proc = sp.Popen(
['tesseract', '-', '-', '-l', 'eng', '--psm', '7'],
stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE
)
proc.stdin.write(image)
proc.stdin.close()
result = re.sub('[^a-z0-9]', '', proc.stdout.read().decode())
if len(result) != 4:
return None
return result

二代机(GitHub Actions)

  初代机虽然已经能实现每日自动打卡,但实在是太不优雅,甚至出现过由于 VPS 欠费导致打卡中断的情况,况且把这么每日打卡和「一些其它服务」挂在同一台服务器上也很不安全,于是决定改用 GitHub Actions 定时打卡

  GitHub Actions 的配置非常简单,只需要增加一个配置文件,简单写几行 yml,确定 Actions 的触发方式即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
name: Auto Checkin
on:
schedule:
- cron: '*/30 20-21 * * *' # 北京时间 4:00、4:30、5:00、5:30 共尝试四次(GMT+8)
workflow_dispatch:

jobs:
checkin:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.8'

- name: Install modules
run: 'pip3 install -r requirements.txt'

- name: Auto checkin
env:
USER: ${{ secrets.USER }}
run: python main.py

  简简单单 20 行,声明了 Action 的触发方式(line 3 - 5),环境配置(line 11 - 14)和工作流程(line 16 - 22),非常地简单明了。此外 Actions 运行在 GitHub 的高性能容器上,脚本的稳定性能得到很好的保障

  除此之外,二代机使用了几层简单的全连接神经网络代替 Tesseract 完成验证码识别,准确率较一代机得到大幅提升,单张验证码四个数字全部识别正确的概率达到约 85%,具体实现可以参考我之前的 这篇文章

三代机(PyTorch)

  如果说一代到二代机的改动让脚本质量有了一个质的飞跃,那么三代机就比较偏向于练手作了,仅仅是用 PyTorch 替代了原来 numpy 手撕的神经网络来做的验证码识别,并且上传到 GitHub 让大家方便 fork 而已:

  神经网络的具体实现同样可以参考我的 这篇文章,这里就不再赘述

微信 WebView 抓包

  • 此法需要准备好一台 Android 手机,电脑上安装 Chromium 内核的浏览器

  基于 web 的打卡机被全面封杀之后,只能在微信中登录打卡,那么抓取微信网页中的数据包便成为了一项刚需,好在腾讯早就为我们留好了后门,只要在微信内置浏览器打开 http://debugx5.qq.com/ 这个链接,并在「信息」一栏中开启 inspector 功能即可轻松开启调试

  如果报错找不到 x5 内核,可以先打开 http://debugmm.qq.com/?forcex5=true 即可切换到 x5 内核。


  然后把手机开启 usb 调试并连接上电脑,在浏览器地址栏输入 chrome://inspect 打开远程调试工具,就可以看到在 Android 设备上打开的网页了:

  此时点击「inspect」按钮,就能像我们平常使用 DevTools 那样,对网页进行调试啦~

  还没结束!细心的你可能已经发现,手机和电脑上网页同步的速度非常慢,甚至根本加载不出来😰,因为默认的 inspect 调试方式是需要经过 Google 的服务器的,我们还需要对 chrome://inspect 这个网页本身做些手脚,才能将调试控制在本地进行

  在 inspect 页按下 F12 以调试其本身,并切换 UA,修改其中 Chrome 的版本号为 0.0.0.0,可以看到下方多出了一个「inspect fallback」的选项,点击它,就可以轻松打开 DevTools 而不经过 Google 的服务器了

  每次都打开 DevTools 切换 UA 还是有些麻烦,于是我CV了一个小插件来把「inspect」按钮替换成「inspect fallback],核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
!(function (origin) {
window.Object.assign = new Proxy(
origin, {
apply(target, thiz, args) {
let result = Reflect.apply(target, thiz, args)
if (window.populateTargets) {
window.Object.assign = origin
Function.prototype.bind = new Proxy(
Function.prototype.bind, {
apply(target, thiz, args) {
if (thiz.name == 'sendTargetCommand' && args[1] == 'inspect') {
args[1] = 'inspect-fallback'
}
return Reflect.apply(target, thiz, args)
}
}
)
}
return result
}
}
)
})(Object.assign)

  在 chrome://flags 中开启「Extensions on chrome:// URLs」选项即可使用

小技巧:本地覆盖

  我们在试图调试时,如果当天已经打卡,则会直接跳到日历页,给调试表单造成了很大的困扰。如果直接 block 掉查询请求,又会触发无限重试,把浏览器浓的非常卡顿。这个时候就可以巧妙利用本地覆写的技巧来避免被跳转

  来到 Sources 一栏,在下方找到 Overrides 的选项,勾选 Enable Local Overrides 后选择一个文件夹备用:

  按 Ctrl+Shift+F 打开搜索框,查找「今日已打卡」的提示信息,定位到对应的 js 文件:

  然后右键标头,选择 Save for overrides 一项,这样一来下次再请求该脚本时将会直接用本地保存的这份 js 文件作为响应:

  接下来在刚刚保存的这份 js 文件中搜索「今日已打卡」,找到一个回调函数,我们直接把它 return 掉,然后保存刷新:

  此时便不会再跳转到日历页,可以愉快地调试啦~