命令行工具,把在 三节课(sanjieke.cn) 已购买/已解锁的课程视频批量下载到本地做离线学习。
输入关键词或 course_id + 登录凭证,自动遍历课程目录树,逐节下载视频(HLS/M3U8,无 DRM)及字幕,按「课程/章节/小节.mp4」归档,支持断点续跑。
⚠️ 仅供个人已购课程的离线学习使用。请遵守平台服务条款,勿传播或用于商业用途。凭证与下载内容切勿外泄。
- 关键词搜索 + course_id 双入口:填关键词自动解析课程,或直接指定
course_id,两者合并去重。 - 两步取流:目录树取
nodeId→ 取详情拿短时效签名 m3u8 → 立即用yt-dlp下载合并。 - 清晰度选择:自动挑可下载的最高清晰度;
prefer_no_vip控制是否规避 VIP 专属流(1080p)。 - 字幕同步下载:中/英文
.vtt落在视频同目录。 - 会员免费课自动领取:未报名的会员免费课先调领取接口再取树。
- 递归目录树:兼容扁平 / 两级 / 多级嵌套结构。
- 断点续跑:
download_state.json记录整门课完成情况,课内再按「文件是否已存在」跳过已下小节。 - 容错不中断:单节失败记入失败清单继续;token 失效则安全停止本轮并提示更新凭证。
- 分批下载:
batch_size控制每轮课程数,降低风控风险。
- Python 3.10+
- 系统工具:
yt-dlp+ffmpeg(下载并合并 HLS 分片)
# macOS
brew install yt-dlp ffmpeg
# Python 依赖
pip install requests beautifulsoup4复制模板并填入真实凭证(切勿提交 git,.gitignore 已忽略 config.json):
cp config.example.json config.json凭证从浏览器登录 sanjieke.cn 后抓取:
| 字段 | 来源 |
|---|---|
token |
请求头 authorization: Bearer <JWT>,去掉 Bearer 前缀 |
apikey |
请求头 sjk-apikey 的值 |
cookies._sjk_jwt / cookies.PHPSESSID |
浏览器 Cookie |
config.json 里二选一或都填(结果去重合并):
{
"keywords": ["AI时代加速成长:从工具应用到效率飞跃"],
"course_ids": ["34009802"]
}python downloader.py重复运行会自动跳过已下载的课程与小节。若设了 batch_size,每次跑一批,建议隔开时间再跑下一批。
config.json 全部字段(参见 config.example.json):
| 字段 | 说明 | 默认 |
|---|---|---|
keywords |
关键词列表,逐个搜索解析成课程 | [] |
course_ids |
已知 course_id 直接下载,与 keywords 结果合并去重 | [] |
token |
JWT,去掉 Bearer 前缀 |
— |
apikey |
sjk-apikey 值 |
— |
cookies |
_sjk_jwt、PHPSESSID |
— |
user_agent |
浏览器 UA | — |
output_dir |
输出根目录(可指向外置盘,如 /Volumes/Disk/Sanjieke) |
downloads |
prefer_no_vip |
true=只在非 VIP 流里选最高清;false=全部候选选最高(VIP 下 1080p) |
true |
download_subtitles |
是否下载字幕 | true |
request_delay_sec |
接口调用间隔秒数(防风控) | 1 |
batch_size |
每轮下载课程数,0/缺省=一次全下 |
0 |
output_dir/
课程名/
第一章 .../
01_小节名.mp4
01_小节名.ZH_CN.vtt
...
另一门课/ # 扁平结构课程小节直接落在课程目录下
01_小节名.mp4
文件名以零填充序号前缀保证播放顺序,非法字符替换为 _,超长名截断到 80 字。
关键词 → GET sanjieke.cn/search?content=<kw> (服务端渲染 HTML)
→ 解析 a.course-list-item 得详情页 url
→ GET 详情页,正则抠 window.__NUXT__.productData.course_id
→ GET content/tree/{course_id} (course_id 即下载 id)
→ 逐节 content/{nodeId} 取 m3u8 → yt-dlp 下载 + 直连下字幕
改动代码前请务必先读 SPEC.md(权威文档),其中列出的约束违反后会跑飞:
- 两步取流:URL 路径用树的 nodeId,不是详情里的
contentId。 - m3u8 是短时效签名链接:取详情后必须立即下载,不能先批量收集再统一下。
- 多种 id 易混淆:下载用
course_id(如 34009802)≠ m3u8 里的class_id(如 37418645)。 - 清晰度:
vipFlag:true的流非会员会 403,选流规则受prefer_no_vip控制。 - 一个 section 可能含多个视频节点,需遍历,多 node 时文件名加序号后缀防覆盖。
- 会员免费课未报名需先
POST /cms-agg/api/vip/free_course领取再取树。
| 文件 | 说明 |
|---|---|
downloader.py |
主程序(单文件实现) |
SPEC.md |
完整需求与接口契约(权威文档) |
precheck.py |
下载前预检脚本 |
PROGRESS.md |
下载进度与运维经验记录 |
config.example.json |
配置模板 |
- token 失效:401 时立即停止整轮(
TokenRevoked),重新登录抓新凭证更新config.json后再跑,已完成的会跳过。 - 多客户端登录会封号:下载期间勿在别处登录同一账号。
- 单个视频失败(网络/token)不中断整体,末尾汇总失败清单。
- 搜索无结果或无精确匹配时打印候选,不静默瞎选。
MIT © libingtong