智慧教育平台教材下载
所有操作都通过 scripts/textbook.py 完成(只依赖 Python 3 标准库):
S=~/.qoder/skills/smartedu-textbook-download/scripts/textbook.py
python3 $S search 小学 数学 四年级 # 检索目录
python3 $S resolve <contentId|预览页链接> # 只解析地址
python3 $S download <contentId...> [--out ~/Desktop]
python3 $S auth check # 检查凭据是否可用
python3 $S auth set --clipboard # 保存用户从浏览器复制的凭据(主用)
Windows(PowerShell)下把 python3 $S 换成 python $S,路径用 $env:USERPROFILE。auth set --clipboard 已按系统自动适配剪贴板(macOS pbpaste / Windows Get-Clipboard / Linux wl-paste·xclip·xsel),download 默认 --out ~/Desktop 也会经 expanduser 正确展开到 Windows 桌面:
$S = "$env:USERPROFILE\.qoder\skills\smartedu-textbook-download\scripts\textbook.py"
python $S search 小学 数学 四年级 # 检索目录
python $S resolve <contentId|预览页链接> # 只解析地址
python $S download <contentId...> # 默认下载到桌面
python $S auth check # 检查凭据是否可用
python $S auth set --clipboard # 保存剪贴板里的凭据(跨平台)
> Linux 若无 wl-paste/xclip/xsel,可改用 python3 $S auth set 从标准输入粘贴凭据。
工作流程
- [ ] 1. 检查凭据:auth check
- [ ] 2. 检索教材:search
- [ ] 3. 确认下载范围(多版本时必须列出让用户选,勿默认)
- [ ] 4. 下载:download
- [ ] 5. 汇报结果
1. 检查凭据:运行 auth check。
- 输出“凭据有效”→ 继续。
- 提示未保存、401 或 400 → 走下方「获取凭据(操作链路)」:提示用户手动从浏览器取凭据 → 用户复制到剪贴板并回复“已复制” → 运行 auth set --clipboard 保存校验 → 继续下载。
2. 检索教材:把用户的话拆成关键词,传给 search。
- “下载小学数学四年级教材” → search 小学 数学 四年级
- “人教版数学四年级上册” → search 数学 四年级 上册 人教版
- 关键词会同时匹配标题和学段、学科、年级、册次、版本,全部命中才算结果;关键词越多越窄,0 结果时先减关键词或去掉版本。
- 注意版本名:语文、道德与法治、历史全国统一用“统编版”(部编版),搜“人教版”会 0 结果;数学、英语等才分人教版/北师大版/苏教版。
- 默认排除盲校、聋校、培智等特殊教育教材;用户明确需要时加 --include-special。
- 目录缓存 24 小时;结果明显过旧时加 --refresh。
3. 确认下载范围:
- 只有 1 本,或用户已指定版本和册次 → 直接下载。
- 有多本(如五年级数学有人教版、北师大版、苏教版等,每版又分上下册)而用户没指定版本 → 必须先列出版本和册次让用户选择,等用户确认后再下载;不要默认人教版、也不要自动挑任一版本直接下载。可在列表里把人教版标为「推荐」,但选择权交给用户。
- 用户明确说“全部下载”→ 全部下载。
4. 下载:download <id1> <id2> ...,默认保存到桌面。
- 文件名格式:{版本}_{教材名}.pdf,如 人教版_义务教育教科书·数学四年级上册.pdf。
- 已有完整文件会跳过;中断会留下 .part 文件,重新执行同一命令即可续传。
- 下载后会校验文件大小、%PDF- 文件头,元数据给出标准 MD5 时也会校验。
5. 汇报:用 Markdown 链接列出每个文件的路径和大小;有失败时如实说明原因。
获取凭据(操作链路)
PDF 存储需要登录凭据。主链路是引导用户手动从浏览器取凭据:agent 不替用户登录、不绕过平台风控。完整链路:
1. auth check → 有效则直接下载
2. 无效/未配置 → 提示用户按下面步骤手动获取凭据
3. 用户复制到剪贴板,回复“已复制”
4. auth set --clipboard → 保存并自动校验
5. 校验通过 → download 自动下载
步骤 2 给用户的提示(二选一)
A. Network 面板复制 X-Nd-Auth(最简单,无需控制台)
1. 浏览器登录 https://basic.smartedu.cn 。
2. 按 F12 → Network(网络)→ 刷新页面。
3. 过滤框输入 set_edu_stage(或任意 API 请求)并点开。
4. 在 Request Headers 找到 X-Nd-Auth,复制冒号后的整段值(MAC id="...",nonce="...",mac="...")。
B. Console 脚本取 access_token/mac_key/diff(更稳,按 URL 自动签名)
1. 浏览器登录 https://basic.smartedu.cn 。
2. 按 F12 → Console,粘贴运行:
(() => {
const key = Object.keys(localStorage).find(k => /^ND_UC_AUTH-[^&]+&[^&]+&token$/.test(k));
if (!key) return console.error("未找到登录凭据,请先登录");
const { access_token, mac_key, diff } = JSON.parse(JSON.parse(localStorage.getItem(key)).value);
copy(JSON.stringify({ access_token, mac_key, diff }));
console.log("已复制登录凭据,回对话说“已复制”");
})();
两种方式都把凭据复制到剪贴板后,让用户回复“已复制”,agent 运行 auth set --clipboard:任何非 JSON 的整段头值原样保存(不限定前缀),JSON 三字段则走逐 URL 签名。
关键:登录被拦 ≠ 下载被拦
平台登录要先从 transport-crypto-api.ykt.eduyun.cn 取 SM2 公钥,该域名挂在中国移动云 WAF 后,对很多网络出口(含真实浏览器)返回 418,用户在自己浏览器里登录也可能卡在 获取公钥失败 / Cannot read properties of undefined (reading 'timer')——这不是密码错,换个不被拦的网络再登录即可。
但登录加密域名(eduyun.cn)和下载域名(cbern.com.cn)是两套基础设施。即使登录被 WAF 拦,只要手里有一个仍有效的 X-Nd-Auth(之前登录留下的,实测可跨天、跨文件复用),download 依然能下。所以 auth check 失败时,先问用户有没有现成的 X-Nd-Auth,再决定是否要重新登录。
安全约束
- 凭据保存在
~/.config/smartedu-textbook/credentials.json(Unix 权限 600;Windows 上用icacls收紧为仅当前用户可访问)。不要读取或打印它的内容,也不要把凭据写进命令行参数、对话或仓库。 - 让用户通过剪贴板传递凭据,不要让用户把凭据粘贴到对话里;如果用户已经发在对话中,提醒对方用完后退出登录,让凭据失效。
- 只下载平台公开提供的教材;用途、版权与责任见下方「合规与免责」。
合规与免责
本 skill 只是检索与下载的技术工具,不托管、不分发、不修改任何教材内容。 使用即视为同意以下条款:
- 版权归属:所有教材版权归原作者、出版单位及国家中小学智慧教育平台(basic.smartedu.cn)所有,下载的 PDF 仅是供你个人使用的一份副本。
- 仅限个人非商业学习:只能用于本人或家庭的学习、备课、研究等非商业用途;严禁任何商业目的、二次分发、公开传播、售卖、出租,或批量爬取、镜像整站。
- 凭据合法:只能使用你本人合法登录获得的凭据,不得盗用、买卖、共享他人凭据,不得访问无权访问的资源;凭据的保管与使用后果由你自行承担。
- 遵守平台与法律:须遵守平台服务条款、接口约定及你所在地区的法律法规,不得绕过或破坏平台的技术保护与风控(本 skill 不替用户登录、不破解、不规避风控)。
- 责任自负:因使用本 skill 产生的任何直接或间接后果(含账号受限、法律责任、数据丢失)均由使用者自行承担,作者与维护者不承担任何责任。
- 及时清理:建议完成个人学习后按需删除下载文件,不长期囤积或传播。
给 agent 的要求:默认用户为个人学习用途;一旦用户表达出商业分发、批量爬取、共享或售卖他人凭据等意图,应停止协助并提示上述合规风险。
错误处理
| 现象 | 处理 |
|---|---|
401 |
凭据失效 → 走「获取凭据(操作链路)」重新取 |
登录页 获取公钥失败 / reading 'timer' / 418 |
登录加密接口被 WAF 拦(IP 级,真实浏览器也中招)→ 换网络登录,或用现成 X-Nd-Auth 直接下载 |
400 InvalidArgument |
签名被拒(多见于整段 X-Nd-Auth 模式)→ 改用 access_token + mac_key |
403 AccessDenied |
文件地址不存在;用 resolve 从元数据重新取地址,不要手动拼文件名或时间戳 |
search 无结果 |
减少关键词、去掉版本,或加 --refresh |
| 网络中断 | 重新执行同一 download 命令续传 |
平台接口(排查用)
- 目录:
https://s-file-1.ykt.cbern.com.cn/zxx/ndrs/resources/tch_material/version/data_version.json→ 各part_*.json - 元数据:
https://s-file-1.ykt.cbern.com.cn/zxx/ndrv2/resources/tch_material/details/{contentId}.json,从ti_items中取源文件的ti_storage,把cs_path:${ref-path}替换为https://r1-ndr-private.ykt.cbern.com.cn - 签名:
HMAC-SHA256(mac_key, "{nonce}\nGET\n{解码后的 path}\n{host}\n")再做 Base64;nonce 为{当前毫秒+diff}:{8 位随机字符} - 预览页链接里的
contentId通常与 PDF 地址中的*.pkgID 相同