Course selection CLI
Project description
csniper
csniper 是一个用 Typer 实现命令行的选课工具。它把“查课、导出、解析目标、抢课”统一成 CLI 功能,抢课只是其中一个子命令。
项目约定:CLI、YAML 配置、Python 内部模型都使用规范命名;教务系统原始字段只放在 HTTP 适配层,并记录在 命名对应.md。
特色功能
- 查课、解析、抢课一体化:
search用于临时查课,resolve用于验证courses.yaml是否能匹配到目标任务,snipe才负责真正提交。 - 服务端筛选优先:查询课程号时会尽量把课程代码、教师名传给教务接口筛选,减少全页签、全分页扫描。
- 开放时间感知:抢课前会读取教务系统页签规则里的开放时间;如果还没开放,首次提交会安排在开放时间之后 0.05 秒,而不是提前反复撞接口。
- 克制型重试:每门课最多尝试 5 分钟;遇到频率过高时,前 5 次保持短随机间隔,第 6 次开始指数退避,随机上限控制在 30-40 秒。
- 状态可见:等待目标时间、进入最后 10 秒、首次提交、频率限制、未开放重试、最终结果都会在终端中提示。
- 本地配置隔离:Cookie 写在
.env,目标课写在courses.yaml,仓库只保留样例文件,避免把个人运行配置提交出去。
安装与配置
推荐普通用户用 uv 安装 CLI:
uv tool install csniper
如果是从当前源码目录安装本地版本:
uv tool install --force .
创建一个用于放配置文件的目录,并生成配置样例:
mkdir csniper-config
cd csniper-config
csniper init
然后根据 .env.example 创建 .env,根据 courses.example.yaml 创建 courses.yaml。.env 和 courses.yaml 是本地运行配置,不建议提交到 git。
.env 配置
登录态:
CSNIPER_SESSION_ID=
CSNIPER_ROUTE_COOKIE=
学期参数:
CSNIPER_AUTO_TERM=true
CSNIPER_ACADEMIC_YEAR=
CSNIPER_SEMESTER=
CSNIPER_TERM_CODE=
CSNIPER_CURRENT_ACADEMIC_YEAR=
CSNIPER_CURRENT_SEMESTER=
CSNIPER_CURRENT_TERM_CODE=
默认建议保持 CSNIPER_AUTO_TERM=true。程序会在首次查询前调用教务系统的当前选课学期接口,自动填充这 6 个值。只有在自动识别不符合预期时,才把 CSNIPER_AUTO_TERM=false 并手动填写这些字段。
运行参数:
CSNIPER_TARGET_TIME=2026-01-10 13:00:00
CSNIPER_CONCURRENCY=1
CSNIPER_INTERVAL_MIN=2
CSNIPER_INTERVAL_MAX=4
CSNIPER_REQUEST_TIMEOUT=20
CSNIPER_VERIFY_SSL=false
CSNIPER_QUERY_DELAY=1.5
说明:
CSNIPER_SESSION_ID对应浏览器里的JSESSIONID。CSNIPER_ROUTE_COOKIE对应浏览器里的route。- 两个 Cookie 必须来自同一个有效浏览器会话。这两个必填,可以在浏览器开发者工具的 Cookie 中找到。
CSNIPER_TARGET_TIME只影响snipe,查询类命令不等待这个时间。csniper doctor会打印实际使用的选课学期。
目标课程配置
编辑 courses.yaml:
courses:
- course_code: ABC1234
teacher_name: ""
tabs: ["all"]
match_mode: "contains"
字段含义:
course_code:课程代码。teacher_name:教师名过滤;空字符串表示不限制教师。tabs:规范页签名列表,或["all"]表示扫描全部页签。match_mode:课程代码匹配方式,可选contains或exact。
常用写法:
tabs: ["all"]
tabs: ["compulsory"]
tabs: ["compulsory", "elective"]
内置规范页签名:
| 规范页签名 | 中文显示名 |
|---|---|
compulsory |
必修 |
elective |
限选 |
sports |
体育 |
writing_communication |
写作与沟通 |
innovation_entrepreneurship |
创新创业通选课 |
social_practice |
社会实践课 |
competition_guidance |
竞赛指导类课程 |
innovation_research |
创新研修 |
innovation_experiment |
创新实验 |
project_based |
项目制课程 |
retake |
重修 |
cross_major |
跨专业课程体系 |
liberal_arts |
文理通识 |
mooc |
MOOC |
optional |
任选 |
如果教务系统出现新页签,先用 csniper tabs --live --show-system-code 查看;确认后可以写入 courses.yaml 的 custom_tabs。
查看可用页签:
csniper tabs
需要查看教务系统编码时:
csniper tabs --show-system-code
使用当前 Cookie 从教务系统动态发现页签:
csniper tabs --live
常用命令
检查配置和登录态:
csniper doctor
查课:
csniper search ABC1234 --page-size 20
csniper search 水污染 --teacher 陈老师
csniper search ABC --tab cross_major
csniper search ABC1234 --all-matches
search 会尽量把课程代码、关键词和教师名传给教务接口做服务端筛选;查询类似 ABC1234 这种课程号时,默认在首次命中后停止扫描后续页签,以减少触发频率限制的概率。需要跨全部页签继续找同课程号时再加 --all-matches。默认不动态发现页签;确实需要刷新教务页签时使用 --live-tabs。
导出课程:
csniper export --output courses_export.csv
解析 courses.yaml 中的目标课,但不提交:
csniper resolve --page-size 20
模拟抢课流程,不提交:
csniper snipe --dry-run --page-size 20
真正提交:
csniper snipe
只有不带 --dry-run 的 snipe 会提交选课请求。其他命令都是只读。
snipe 会先解析目标课程,再等待 CSNIPER_TARGET_TIME。如果提交后教务系统返回“未开放 / 未到选课时间”一类提示,程序会刷新一次页签规则,读取教务系统返回的开放时间,并把下一次提交安排到开放时间之后 0.05 秒。
为了避免给教务系统造成过高压力,每门课最长尝试 5 分钟。遇到“查询请求频率过高”时,前 5 次仍使用 CSNIPER_INTERVAL_MIN 到 CSNIPER_INTERVAL_MAX 的随机间隔;第 6 次开始使用指数退避,并把随机等待上限控制在 30-40 秒之间。
开发
本项目使用 uv 管理开发环境和依赖:
uv sync
编译检查:
uv run python -m compileall csniper
开发环境中运行 CLI:
uv run csniper doctor
uv run csniper search ABC1234 --page-size 20
构建发行包:
uv build
uvx twine check dist/*
主要文件:
csniper/cli.py:Typer 命令入口。csniper/config.py:读取.env和courses.yaml。csniper/client.py:HTTP 适配层,集中处理教务系统原始字段。csniper/tabs.py:规范页签名和页签映射。csniper/courses.py:查课、搜索、导出、目标解析。csniper/sniper.py:等待时间、并发提交、响应判断。
注意事项
- 不要提交
.env,里面包含有效登录 Cookie。 - 不要提交
courses.yaml,里面是本地目标课程配置;仓库只保留courses.example.yaml。 - 普通使用优先
uv tool install csniper;开发本项目时使用uv sync和uv run ...。 - 如果浏览器能访问但
csniper doctor失败,通常是CSNIPER_SESSION_ID和CSNIPER_ROUTE_COOKIE不匹配或已过期。 - 遇到“查询请求频率过高”,优先缩小
--tab范围或降低分页大小;search内部会使用不低于 1.5 秒的查询间隔。
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file csniper-0.1.2.tar.gz.
File metadata
- Download URL: csniper-0.1.2.tar.gz
- Upload date:
- Size: 29.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
156677bf67747bb674649c914b6b11f5d33a0515b256534a917dc0aba8e51de5
|
|
| MD5 |
5beed1334df04d0228ba9ccfef7387d7
|
|
| BLAKE2b-256 |
39caed5461050726890eaa6ac0a8b1ecbff4cf8c48ba6bdc66aae008d1af5dd0
|
Provenance
The following attestation bundles were made for csniper-0.1.2.tar.gz:
Publisher:
python-publish.yml on WyattYuan/course-sniper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
csniper-0.1.2.tar.gz -
Subject digest:
156677bf67747bb674649c914b6b11f5d33a0515b256534a917dc0aba8e51de5 - Sigstore transparency entry: 1923796366
- Sigstore integration time:
-
Permalink:
WyattYuan/course-sniper@3ea6c2ed6d74f1b4cfcc206e939d844d93b440c2 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/WyattYuan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@3ea6c2ed6d74f1b4cfcc206e939d844d93b440c2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file csniper-0.1.2-py3-none-any.whl.
File metadata
- Download URL: csniper-0.1.2-py3-none-any.whl
- Upload date:
- Size: 22.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2160b74b15ac5dd46cea6875d1d28c7eeefd08416c40060d5ba7d7a72fdb209
|
|
| MD5 |
1c7a004d6c9960f8a8404c14f3244401
|
|
| BLAKE2b-256 |
966922902787a2da1cdf0d9f77787f139811980c5c81a6bf56f450a862e2fb7c
|
Provenance
The following attestation bundles were made for csniper-0.1.2-py3-none-any.whl:
Publisher:
python-publish.yml on WyattYuan/course-sniper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
csniper-0.1.2-py3-none-any.whl -
Subject digest:
d2160b74b15ac5dd46cea6875d1d28c7eeefd08416c40060d5ba7d7a72fdb209 - Sigstore transparency entry: 1923796452
- Sigstore integration time:
-
Permalink:
WyattYuan/course-sniper@3ea6c2ed6d74f1b4cfcc206e939d844d93b440c2 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/WyattYuan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@3ea6c2ed6d74f1b4cfcc206e939d844d93b440c2 -
Trigger Event:
release
-
Statement type: