智慧教育平台教材下载器 (smartedu-downloader)
从 国家中小学智慧教育平台(basic.smartedu.cn)下载教材资源:
- 听力音频(MP3,如英语教材的 Listening)
- 教材电子版(PDF 本体)
背景 / 为什么需要登录
- 平台的资源清单 API 无需登录即可访问,但 2024 年 8 月后,实际文件 CDN(
r*-ndr-private.ykt.cbern.com.cn)需要X-Nd-Auth鉴权头才能下载。 - 该鉴权头由登录后 token 里的
access_token+mac_key通过 HMAC-SHA256 生成。 - 登录会触发腾讯滑块验证码,无法自动破解,因此采用「脚本自动填账号密码 + 用户手动拖动滑块 + 脚本自动取 token 并下载」的混合方式。
使用流程
1. 收集参数(向用户询问)
必需:
- 登录手机号(账号)
- 登录密码
- 一个或多个教材链接(https://basic.smartedu.cn/tchMaterial/detail?...contentId=<CID>...)
从每个链接的 URL 中提取 contentId 参数值作为 cid。
2. 先探测教材类型(无需登录,推荐先做)
在真正下载前,先用文件服务器镜像确认每个教材是「听力音频」还是「纯PDF」,以及标题,便于给用户预告:
node -e '
const CID="<contentId>";
(async()=>{
for (const s of ["s-file-1","s-file-2","s-file-3"]) {
try {
const r = await fetch(`https://${s}.ykt.cbern.com.cn/zxx/ndrv2/resources/tch_material/details/${CID}.json`);
if(!r.ok) continue;
const d = await r.json();
const hasPdf=(d.ti_items||[]).some(it=>it.ti_format==="pdf"||it.lc_ti_format==="pdf");
console.log("标题:", (d.global_title&&d.global_title["zh-CN"]), "| PDF:", hasPdf);
break;
} catch(e){}
}
const a = await (async()=>{for(const s of ["s-file-1","s-file-2","s-file-3"]){try{const r=await fetch(`https://${s}.ykt.cbern.com.cn/zxx/ndrs/resources/${CID}/relation_audios.json`);if(r.ok)return await r.json();}catch(e){}}return[];})();
console.log("听力数量:", Array.isArray(a)?a.length:0);
})();
'
3. 确保依赖就绪
脚本依赖 playwright(需已安装 chromium 浏览器)。检查:
node -e "require('playwright'); console.log('playwright OK')"
若缺失,需在隔离的 node workspace 安装:npm i playwright && npx playwright install chromium(按运行环境的隔离规则调整路径与 NODE_PATH)。
4. 运行下载脚本
所有参数通过环境变量传入 scripts/smartedu_downloader.js:
| 变量 | 说明 |
|---|---|
SMARTEDU_PHONE |
登录手机号(必需) |
SMARTEDU_PASS |
登录密码(必需) |
SMARTEDU_JOBS |
JSON 数组 [{"cid":"<contentId>","name":"<可选人类可读名>"}](必需) |
SMARTEDU_OUTPUT_DIR |
输出根目录(默认 ./smartedu_downloads) |
SMARTEDU_MODE |
audio|pdf|auto(默认 auto:有听力则下听力,否则下PDF) |
name 省略时脚本会自动用教材官方标题命名。
示例(一次登录批量下载多个教材):
SMARTEDU_PHONE="手机号" SMARTEDU_PASS="密码" \
SMARTEDU_MODE="auto" \
SMARTEDU_OUTPUT_DIR="/输出/目录" \
SMARTEDU_JOBS='[
{"cid":"b7ca9536-ccd7-a4db-56b3-971645c54515"},
{"cid":"540ac93d-67fc-4353-9e49-1ef20d02b5a4"}
]' \
NODE_PATH="<node_modules 路径>" node scripts/smartedu_downloader.js
重要:脚本以前台方式运行(headless:false),会弹出真实浏览器窗口。运行后要提示用户:
> 浏览器已打开,账号密码已自动填好。请你:1. 点击蓝色「登录」按钮 → 2. 拖动滑块完成验证码 → 3. 等待自动跳转。之后脚本会自动取 token 并下载全部教材。
脚本最多等待 6 分钟让用户完成登录+验证码。不要用后台方式运行,否则无法观察进度、任务易丢失。
5. 校验结果
下载后校验文件完整性:
- MP3:文件头为 ID3(494433) 或帧头 fffb/fff3,大小 > 1KB
- PDF:文件头 %PDF-,末尾 %%EOF
然后用 present_files 把输出目录展示给用户。
关键技术细节(供排错)
- token 存储位置:登录后写入 localStorage,key =
ND_UC_AUTH-e5649925-441d-4a53-b525-51a2f1c4e0a8&ncet-xedu&token - token 是嵌套结构:
{value:"<json字符串>", expire:...},真正的access_token/mac_key在value里,需先JSON.parse外层再解包.value(脚本已处理)。 - X-Nd-Auth 生成:
MAC id="{access_token}",nonce="{ts}:{rand}",mac="{HMAC_SHA256(mac_key, "{nonce}\n{GET}\n{host}\n{path}\n")}"(mac 为 hex) - 登录页表单:手机号
#username,密码#tmpPassword,协议勾选#agreementCheckbox - 常见坑1:轮询时若用户还在 auth 域名(登录页),不能强制
page.goto跳转,否则验证码界面被冲掉,用户无法操作。脚本仅在离开 auth 域名后才检查/跳转。 - 常见坑2:有的教材(非英语学科,如语文/数学/道法/历史/地理)没有听力音频(
relation_audios为 0),此时应下 PDF 本体。auto模式会自动处理。 - 资源清单 API:
- 教材详情(含PDF):
https://s-file-{1,2,3}.ykt.cbern.com.cn/zxx/ndrv2/resources/tch_material/details/{CID}.json - 听力清单:
https://s-file-{1,2,3}.ykt.cbern.com.cn/zxx/ndrs/resources/{CID}/relation_audios.json - 文件 CDN:
r{1,2,3}-ndr-private.ykt.cbern.com.cn(需鉴权) - token 有效期内可连续下载多个资源,无需重复登录。
安全与隐私
- 账号密码仅用于当次登录,通过环境变量传入,不要写入任何文件或日志。
- 下载所得为教材公开资源,供用户个人学习使用。