跳转至

最佳实践

面向真实使用场景的推荐做法,帮助更稳定、更轻量地接入 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,跳过账密与验证码链路:

# 带 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

升级后务必重新创建 SchoolClientUserClient 实例,不要复用旧进程中的对象。