最佳实践
面向真实使用场景的推荐做法,帮助更稳定、更轻量地接入 SDK。
复用会话,避免频繁登录
UserClient 内部持有 requests.Session,登录一次即可多次查询;SchoolClient 无状态,可为多个用户复用:
from school_sdk import SchoolClient
school = SchoolClient("jw.example.edu.cn")
# 同一个 school 实例可为多个用户签发会话
user_a = school.user_login("account_a", "password_a")
user_b = school.user_login("account_b", "password_b")
# 同一个用户实例可多次查询, 无需重复登录
course = user_a.get_schedule(year=2022, term=1)
score = user_a.get_score(year=2022, term=1)
info = user_a.get_info()
Warning
每次业务请求前都重新 user_login() 容易触发教务系统风控,应尽量避免。
用 Cookie 登录做调试
开发调试阶段可从浏览器复制 Cookie,跳过账密与验证码链路:
# 带 account 标识的 cookie 登录
user = school.user_login_with_cookies("JSESSIONID=xxxx", account="tester")
# 或者纯开发模式
user = school.init_dev_user("JSESSIONID=xxxx")
Note
Cookie 会话缺少账密,失效后无法自动重登,仅适合调试与短生命周期任务。 完整示例见仓库 cookie_login_example.py。
长任务的会话保活
定时任务等长生命周期场景,在业务请求前调用 check_session(),失效时自动重登:
from school_sdk.client.exceptions import LoginException
user.check_session()
try:
course = user.get_schedule(year=2022, term=1)
except LoginException as e:
print(f"会话异常: {e}") # 兜底: 重新 user_login 或告警
Warning
check_session() 重登录后,之前创建的业务对象仍指向旧会话;
重登后应通过 user.get_xxx() 让 SDK 重新惰性创建业务对象,不要复用旧引用。
用 proxy_request 扩展未封装的业务
SDK 只封装了课表、成绩、个人信息三类接口,其余业务(考试安排、空教室等)可抓包后用 proxy_request() 复用登录态:
resp = user.proxy_request("POST", "/kscx/xskscx_cxXsksxxIndex.html", data={
"xnm": "2022", "xqm": "3",
})
print(resp.json())
kwargs 会原样透传给 requests.request,响应需自行解析。更多示例见仓库 proxy_request_examples.py。
统一的异常处理
建议按异常类型分层处理:
from school_sdk.client.exceptions import LoginException, RTKException
try:
user = school.user_login("account", "password")
score = user.get_score(year=2022, term=1)
except LoginException as e:
# 登录失败 / 会话失效, e 中带教务系统原始提示
...
except RTKException:
# 滑块验证码 rtk 解析失败, 一般是教务系统改版, 可提 issue
...
except ValueError as e:
# 参数缺失, 如未指定学年
...
except KeyError as e:
# 自定义 schedule_time 缺少节次
...
传入本校作息表
SDK 内置作息表并不适用所有学校,节次时间只影响课表中的 time 字段;对时间精度有要求时传入本校作息:
course = user.get_schedule(year=2022, term=1, schedule_time={
"1": [8, 0], "2": [8, 55], "3": [10, 10], "4": [11, 5],
# 需覆盖课表中出现的全部节次, 否则抛 KeyError
})
Note
返回的 time.last 是末节的上课时间而非下课时间,计算课时长度时请注意。
校外通过 VPN 访问
部分学校的教务系统仅在校内网络可访问,校外需要通过 VPN。推荐先连 VPN 再运行 SDK,也可以为 Session 配置代理:
school = SchoolClient("jw.vpn.czjtu.edu.cn", ssl=True)
user = school.user_login("account", "password")
# 设置 HTTP 代理(如果 VPN 提供代理地址)
user._http.proxies = {
"http": "http://vpn-proxy:port",
"https": "http://vpn-proxy:port",
}
Tip
也可以通过系统环境变量 HTTP_PROXY / HTTPS_PROXY 设置代理,对所有请求透明生效。
详细排查思路见常见问题 - 校外访问需要 VPN。
云服务器部署
在存储空间受限的云服务器上部署时,只需核心依赖(~10-20 MB),避免安装可选的 PyTorch:
# 轻量安装(不含图形验证码依赖)
pip install school-sdk
# 使用 uv 安装(更快、更可靠)
uv add school-sdk
Warning
不要安装 school-sdk[kaptcha],PyTorch + torchvision 体积超 700MB,
除非确认你的学校使用图形验证码。大多数学校不需要。
选课高峰期抢课
选课高峰期教务系统响应慢,可结合以下策略提高成功率:
import time
# 1. 增大超时时间
school = SchoolClient("jw.example.edu.cn", timeout=30)
# 2. 提前登录并预热会话
user = school.user_login("account", "password")
user.get_info() # 预热会话
# 3. 定时重试目标接口
while True:
try:
resp = user.proxy_request("POST", "/xkcx/xk_cxXkSave.html", data={
# 选课参数需抓包获取
})
result = resp.json()
if result.get("status") == "success":
print("选课成功!")
break
except Exception as e:
print(f"请求失败: {e}, 1秒后重试...")
time.sleep(1) # 避免触发风控
Warning
请求间隔不低于 1 秒,过于频繁可能触发教务系统封禁。
版本升级指南
从旧版本升级到最新版时,注意以下兼容性变化:
| 版本 | 变化 | 影响 |
|---|---|---|
| v1.9.0 | PyTorch 改为可选依赖 ([kaptcha]) |
不需要图形验证码的用户无需安装 PyTorch |
| v1.7.x | UserClient 内部结构重构 | 必须重新创建 UserClient 实例,不要复用旧对象 |
升级步骤:
# 升级到最新版
pip install --upgrade school-sdk
# 如果遇到问题,清除缓存重装
pip cache purge
pip install --force-reinstall school-sdk
Tip
升级后务必重新创建 SchoolClient 和 UserClient 实例,不要复用旧进程中的对象。