File size: 7,898 Bytes
a30f196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e28c9e4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
---

title: Advanced SCU Course Catcher
emoji: 🐳
colorFrom: green
colorTo: yellow
sdk: docker
app_port: 7860
pinned: false
---


# Advanced SCU Course Catcher

这是一个面向 Hugging Face Space 的四川大学选课系统 Web 版抢课面板。当前版本已经从单机脚本改造成了可部署、可并行、可多用户管理的 Flask + Selenium 服务,支持普通用户端和管理员端分离运行。

## 当前能力

- 普通用户入口:`/login`
  用户使用 `学号 + 密码` 登录,只能看到自己的课程、任务状态和实时日志。
- 管理员入口:`/admin`
  管理员使用 `账号 + 密码` 登录。此入口不会在用户登录页展示。
- 多管理员体系
  支持多位普通管理员,支持 1 位超级管理员。超级管理员账号和密码由环境变量 `ADMIN``PASSWORD` 提供。
- 多用户管理
  管理员可以手动录入用户账号、重置密码、启用/禁用用户、查看所有课程目标。
- 课程录入
  用户自己填写 `课程号``课序号`,管理员可见全部内容,也可以代为录入和修改。
- 实时日志
  用户和管理员都可以实时看到程序日志,前端通过 SSE 持续推送。
- 并行调度
  多用户任务由后台任务调度器并行执行,管理员可以在后台动态设置并行数。
- Hugging Face Space 适配
  使用 Docker Space 运行,内置 Chromium、chromedriver、中文字体和无头浏览器配置。
- 登录验证码与提交验证码 OCR
  登录阶段和提交选课阶段都支持验证码 OCR 自动识别。
- Selenium 自恢复
  单个 Selenium 会话连续错误达到 5 次时,会自动重建浏览器;连续重建 5 个会话仍失败时,任务才会终止。

## 运行结构

当前实际运行入口和核心模块如下:

- `app.py`
  Hugging Face / gunicorn 使用的 WSGI 入口。
- `space_app.py`
  Flask 路由、页面渲染、登录鉴权、SSE 日志流。
- `core/config.py`
  运行配置与内部密钥管理。
- `core/db.py`
  SQLite 数据层。
- `core/task_manager.py`
  并行调度、任务生命周期管理。
- `core/course_bot.py`
  Selenium 抢课核心逻辑、验证码识别、重试与重建策略。
- `webdriver_utils.py`
  Chromium / chromedriver 初始化。
- `templates/` + `static/`
  用户端、管理员端和响应式前端页面。

## 环境变量

为了减少 Space 配置复杂度,当前只保留少量必要环境变量。

### 必填

- `ADMIN`
  超级管理员账号。
- `PASSWORD`
  超级管理员密码。

### 可选

- `DATA_DIR`
  数据目录,默认会使用仓库下的 `data/`。在 Hugging Face Space 中建议保持为 `/data`- `CHROME_BIN`
  自定义 Chromium 路径。默认会自动尝试 `/usr/bin/chromium`- `CHROMEDRIVER_PATH`
  自定义 chromedriver 路径。默认会自动尝试 `/usr/bin/chromedriver`### 不需要再手动配置的内容

以下内容已经改为自动生成或写入程序默认值,不需要再手工配置环境变量:

- Flask session secret
- 用户密码加密密钥
- 默认并行数
- 登录重试次数
- Selenium 会话错误阈值
- Selenium 会话重建阈值
- 提交验证码重试次数
- 页面超时时间
- 日志页容量

程序会在 `DATA_DIR/.app_secrets.json` 中自动持久化内部密钥。这样即使以后修改 `ADMIN``PASSWORD`,也不会导致历史用户密码全部失效。

## Hugging Face Space 部署

### 1. 创建 Space

在 Hugging Face 上创建一个新的 Space:

- SDK 选择 `Docker`
- 端口使用 `7860`
- 仓库内容直接推送本项目即可

本仓库根目录已经提供好 `README.md` 的 Space metadata 和 `Dockerfile`### 2. 设置 Space Secrets

进入 `Settings -> Variables and secrets`,至少配置:

- `ADMIN=你的超级管理员账号`
- `PASSWORD=你的超级管理员密码`

通常不需要再设置其他环境变量。

如果你想显式指定数据目录,也可以增加:

- `DATA_DIR=/data`

### 3. 开启持久化存储

如果你希望以下数据在 Space 重启后仍然保留,建议在 Space 设置里开启 Persistent Storage:

- 用户账号
- 管理员账号
- 课程目标
- 任务历史
- 运行日志
- 自动生成的内部密钥文件

当前 Dockerfile 已按 `/data` 目录进行适配,并预先处理了目录权限,方便在 Space 中直接持久化。

### 4. 推送部署

如果你使用 git 推送到 Hugging Face Space,可以参考:

```bash

git remote add space https://huggingface.co/spaces/<your-name>/<your-space>

git push space main

```

### 5. 启动方式

容器启动命令已经写入 `Dockerfile````bash

gunicorn --bind 0.0.0.0:${PORT:-7860} --workers 1 --threads 8 --timeout 180 app:app

```

这里使用 `1` 个 gunicorn worker,是为了避免多进程情况下每个进程都各自启动一套本地任务调度器。并发能力由应用内部的线程调度和管理员可配置的任务并行数控制。

## Space 端建议配置

推荐的初始运行策略:

- 后台并行数先设置为 `1``2`
- 只有在 Space CPU / 内存足够稳定时,再逐步提升
- 在真实高峰前,先做一次小规模联调

对于 CPU 较小的 Space,同时拉起过多 Chromium 会明显增加失败率,因此管理员后台提供了并行数动态调整功能。

## 管理员联调清单

部署到 Space 后,建议按以下顺序联调:

1. 访问 `/admin`,使用 `ADMIN` / `PASSWORD` 登录超级管理员。
2. 在管理员后台创建 1 个普通管理员、2 个测试用户。
3. 为两个测试用户分别录入不同课程号和课序号。
4. 将并行数设置为 `2`5. 分别触发两个用户任务,确认两条任务都能进入队列,并按并行数启动。
6. 在管理员日志面板确认日志持续刷新,且可以看到不同学号的执行记录。
7. 使用用户账号访问 `/login`,确认用户只能看到自己的课程和日志。
8. 在任务运行过程中测试停止任务,确认状态能变为“停止中”并最终收敛。
9. 如果学校页面出现提交验证码,确认日志中能看到提交阶段验证码识别提示。

## 自动化测试

仓库当前已补充以下自动化测试:

- `tests/test_config.py`
  验证内部密钥在超级管理员账号变更后依然保持稳定。
- `tests/test_course_bot.py`
  验证 Selenium 会话错误累计后会触发浏览器重建,并验证提交阶段验证码分支。
- `tests/test_task_manager.py`
  验证任务调度器会严格遵守管理员设置的并行上限。
- `.github/workflows/linux-tests.yml``ubuntu-latest` 上自动运行上述测试,避免关键逻辑只在 Windows 本地验证。

本地运行测试:

```bash

python -m unittest discover -s tests -v

```

## 本地运行

### 直接运行

```bash

pip install -r requirements.txt

set ADMIN=admin

set PASSWORD=change-me

python main.py

```

### Docker 运行

```bash

docker build -t advanced-scu-course-catcher .

docker run --rm -p 7860:7860 \

  -e ADMIN=admin \

  -e PASSWORD=change-me \

  advanced-scu-course-catcher

```

## 重要说明

- 本项目当前的任务模型是“持续轮询直到成功、手动停止或达到错误上限”。
- 用户密码会以应用内部密钥加密后保存在数据库中,用于后台自动登录教务系统。
- 管理员可以看到全部用户、课程和日志,请在实际使用前明确权限边界。
- 如果学校选课页面 DOM 结构变化较大,需要同步更新 `core/course_bot.py` 中的选择器以及 `javascript/` 下的辅助脚本。
- `course_catcher/` 目录是仓库中保留的旧实现,当前运行链路以 `space_app.py + core/*` 为准。