下载国家中小学教育平台教材

SKILL研究检索
研究检索 未标注权限 许可证不明确
安装这个技能

下载国家中小学智慧教育平台(basic.smartedu.cn)教师教材详情页。上半讲公共源(免 Token,批量抓预览图合成 PDF);下半讲私有源(带 access token 直拉原生分辨率 PDF)。当用户给出 basic.smartedu.cn/tchMaterial/detail 链接,或要求"下载教材 / 教材转 PDF / 批量抓教材图片"时使用。

一句话选型结论

按资料字段整理

下载国家中小学教育平台教材是研究检索类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类研究检索 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_a3ed06bc/smartedu-download
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

研究检索
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_a3ed06bc仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验2026-11-06上游最后核验时间 · 数据更新 2026-11-06
正文体量15.7 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
查资料并整理出处

让模型按主题查资料,并把引用来源整理成清单。

网页与文献要点提炼

把长文、论文、网页压成能直接看的要点。

竞品与行业汇总

按关键词汇总竞品动态与行业信息。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在研究检索场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
下载国家中小学教育平台教材当前 未标注权限 未标注 未标注 未标注 有
寻找技能 浏览器控制 GitHub 仓库 3120.0 万 Codex、Claude、Cursor 有
自我改进 Agent 接触敏感数据 ClawHub 市场 1646.4 万 OpenClaw 有
灵魂追踪 联网访问 GitHub 仓库 758.9 万 Codex、Claude、Cursor 有
自我完善 联网访问 ClawHub 市场 682.9 万 OpenClaw 有
微软代工厂 调用外部服务 GitHub 仓库 661.4 万 Codex、Claude、Cursor 有

SKILL.md 全文

15.7 KB
模型实际读到的说明书原文,来自技能仓库

智慧教育平台教材下载

本 skill 分两半:

  • 上半:公共源下载方法 —— 免 Token,从公开 JSON 拉预览图合成 PDF。零依赖、可自动化,首选。
  • 下半:私有源获取方法 —— 需 access token,直拉平台原生 A4 矢量 PDF(体积小、文字可选)。想要原生分辨率时用。

两条路线的数据源都是同一个公开 JSON 接口(无需登录),区别只在最终资源落在「非私有 host 的图片」还是「私有 host 的 source PDF」。


上半:公共源下载方法(免 Token,图片合成 PDF)

适用场景

用户只想把整本教材抓成 PDF,不在乎它是否原生矢量。此路线不需要任何登录态,全程可自动化。

核心原理(关键坑已标出)

  1. 详情页是 SPA:直接抓页面只能拿到空壳。不要解析 HTML。
  2. 真实数据源是公开 JSON 接口,无需登录、无需 Token:
    https://s-file-2.ykt.cbern.com.cn/zxx/ndrv2/resources/tch_material/details/{contentId}.json
    {contentId} 取自 URL 的 contentId 参数(UUID 格式)。
  3. preview 只是预览子集,≠ 全书页数(最易踩的坑)。
    custom_properties.preview 是 dict:SlideN → 该页 jpg 的完整 URL。但它的条目数通常远少于真实页数(实测一本 167 页的书,preview 只有 49 条)。绝不能拿 len(preview) 当总页数。
  4. 真实页数在 ti_items 里:找到 ti_file_flag=="image" 的项,其 custom_properties.requirements 中有 {"name":"pagesize","value":"167"} —— 这才是全书真实页数。脚本用这个值驱动下载。
  5. 图片 URL 规律:非私有 host 上每页一张 jpg,按页码顺序可直接遍历:
    https://r1-ndr.ykt.cbern.com.cn/edu_product/esp/assets/{cid}.t/zh-CN/{ts}/transcode/image/{N}.jpg
    {ts} 与时间戳相关,从任意 preview URL 里原样抠出即可;{N} 从 1 顺序递增到 pagesize。第 pagesize+1 页会返回 403,可据此验证边界。
  6. 镜像容灾:主机 r1-ndr / r2-ndr / r3-ndr.ykt.cbern.com.cn 三个等价镜像,失败自动切换。
  7. Referer 必需:请求头带 Referer: https://basic.smartedu.cn/,否则 CDN 拒回。

工作流程

Step 1:提取 contentId

从用户链接的 contentId 查询参数取 UUID。只给 contentId 也行。

Step 2:拉取 JSON、确认真实页数(先确认,避免误下)

python download_textbook.py <contentId> --dry-run

打印书名、contentId、真实页数(来自 ti_items.pagesize)。确认无误再去掉 --dry-run。

Step 3:批量下载图片

  • 按 ti_items 的 pagesize 驱动,从 1 遍历到真实页数(而非 preview 条目数)。
  • r1 → r2 → r3 镜像轮换容灾 + 带 Referer。
  • 断点续传:已存在且大小正常的文件跳过(重跑自动补缺失页)。
  • 支持 --start/--end 分段、--workers 并发。
  • 保存到 textbook_{contentId}/page_{N:03d}.jpg。
  • 结尾打印 done: ok/total,失败页列出 MISSING=[...]。

> 网络注意:部分沙盒/服务器到 *.ykt.cbern.com.cn 的 TLS 会被封锁。若下载全失败是网络层限制,非代码问题——需在本机(能访问该域的环境)执行,本机只需 pip install Pillow。

Step 4:按页码升序合成 PDF

python make_pdf.py <contentId>

默认不缩放;图片极大导致 PDF 过大时加 --max 2000 把最长边限制在 2000px。
合成后用 pdfinfo 教材_<contentId>.pdf | grep Pages 核对页数 == 真实页数。

脚本 A:download_textbook.py(标准库 urllib,仅 Pillow 为外部依赖)

import os, re, sys, json, argparse, urllib.request
from concurrent.futures import ThreadPoolExecutor

UA = "Mozilla/5.0"
REFERER = "https://basic.smartedu.cn/"
MIRRORS = ["r1", "r2", "r3"]
JSON_TMPL = "https://s-file-2.ykt.cbern.com.cn/zxx/ndrv2/resources/tch_material/details/{cid}.json"


def fetch_json(content_id):
    req = urllib.request.Request(
        JSON_TMPL.format(cid=content_id),
        headers={"User-Agent": UA, "Referer": REFERER},
    )
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.loads(r.read().decode("utf-8"))


def build_url_template(d):
    """从 preview 任一 URL 抽取 image/{N}.jpg 之前的固定前缀。"""
    prev = d.get("custom_properties", {}).get("preview", {})
    sample = None
    if isinstance(prev, dict):
        sample = next(iter(prev.values()), None)
    elif isinstance(prev, list):
        sample = prev[0] if prev else None
    if isinstance(sample, dict):
        sample = sample.get("url")
    if not sample:
        raise RuntimeError("preview 中找不到可用 URL 样本")
    return re.sub(r"/image/\d+\.jpg$", "/image/{n}.jpg", sample)


def get_total_pages(d):
    """真实页数取自 ti_items 中 image 项的 requirements.pagesize(非 preview 子集)。"""
    for it in d.get("ti_items", []):
        if it.get("ti_file_flag") == "image":
            for req in it.get("custom_properties", {}).get("requirements", []):
                if req.get("name") == "pagesize":
                    return int(req["value"])
    prev = d.get("custom_properties", {}).get("preview", {})
    nums = []
    items = prev.items() if isinstance(prev, dict) else enumerate(prev)
    for key, _ in items:
        m = re.search(r"Slide(\d+)", str(key))
        if m:
            nums.append(int(m.group(1)))
    return max(nums) if nums else 0


def swap_mirror(url, idx):
    return re.sub(r"r[123]-ndr", f"{MIRRORS[idx % len(MIRRORS)]}-ndr", url, count=1)


def download_one(args):
    page, url, outdir = args
    path = os.path.join(outdir, f"page_{page:03d}.jpg")
    if os.path.exists(path) and os.path.getsize(path) > 1000:
        return page, True
    for i in range(len(MIRRORS) * 3):
        u = swap_mirror(url.format(n=page), i)
        try:
            req = urllib.request.Request(u, headers={"User-Agent": UA, "Referer": REFERER})
            data = urllib.request.urlopen(req, timeout=30).read()
            if len(data) > 1000:
                with open(path, "wb") as f:
                    f.write(data)
                return page, True
        except Exception:
            continue
    return page, False


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("content_id")
    ap.add_argument("--start", type=int, default=1)
    ap.add_argument("--end", type=int, default=9999)
    ap.add_argument("--workers", type=int, default=8)
    ap.add_argument("--dry-run", action="store_true", help="只打印元数据,不下载")
    args = ap.parse_args()

    d = fetch_json(args.content_id)
    total = get_total_pages(d)
    tpl = build_url_template(d)
    print(f"[{d.get('title','')}] content_id={args.content_id} 真实页数={total}")
    if args.dry_run:
        return
    outdir = f"textbook_{args.content_id}"
    os.makedirs(outdir, exist_ok=True)
    sel = list(range(args.start, min(args.end, total) + 1))
    print(f"downloading {len(sel)} pages ...")
    with ThreadPoolExecutor(max_workers=args.workers) as ex:
        results = list(ex.map(download_one, [(n, tpl, outdir) for n in sel]))
    ok = sum(1 for _, s in results if s)
    miss = [n for n, s in results if not s]
    print(f"done: {ok}/{len(sel)}" + (f"  MISSING={miss}" if miss else ""))


if __name__ == "__main__":
    main()

脚本 B:make_pdf.py(合成 PDF,支持可选缩放)

import glob, os, sys, argparse
from PIL import Image


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("content_id", nargs="?", default="*")
    ap.add_argument("--max", type=int, default=0, help="最长边上限(px),0=不缩放")
    args = ap.parse_args()
    cid = args.content_id
    pattern = f"textbook_{cid}/page_*.jpg" if cid != "*" else "textbook_*/page_*.jpg"
    files = sorted(glob.glob(pattern))
    imgs = []
    for f in files:
        im = Image.open(f).convert("RGB")
        if args.max and max(im.size) > args.max:
            im.thumbnail((args.max, args.max))
        imgs.append(im)
    out = f"教材_{cid}.pdf" if cid != "*" else "教材_合成.pdf"
    if imgs:
        imgs[0].save(out, save_all=True, append_images=imgs[1:])
        print(f"saved {out}: {len(imgs)} pages, {os.path.getsize(out)//1024}KB")
    else:
        print("no images found")


if __name__ == "__main__":
    main()

使用方式(公共源)

本机执行(仅需 pip install Pillow):

python download_textbook.py 9085151a-b698-4b28-8c00-2c4aaf0c91ad --dry-run   # 先看真实页数
python download_textbook.py 9085151a-b698-4b28-8c00-2c4aaf0c91ad               # 全量下载
python make_pdf.py 9085151a-b698-4b28-8c00-2c4aaf0c91ad                        # 合成 PDF
pdfinfo 教材_9085151a-b698-4b28-8c00-2c4aaf0c91ad.pdf | grep Pages             # 核对=167

分段 / 换教材:改 contentId,或加 --start 10 --end 30;PDF 过大加 --max 2000。


下半:私有源获取方法(需 access token,原生 PDF)

适用场景

用户要原生分辨率的教材 PDF:A4 页面、体积更小(约为图片合成版的一半)、文字可选中、更清晰。图片合成版(上半)是 1488×2104 像素图,非真矢量。此路线需用户提供一份有时效的 access token。

核心原理(HTTP MAC 鉴权)

  1. 源 PDF 在哪:ti_items 里 ti_file_flag=="source" 的项,其 ti_storages 含形如
    https://r1-ndr-private.ykt.cbern.com.cn/edu_product/esp/assets/{cid}.pkg/pdf_xxx.pdf
    的地址,落在 -private host。该 host 的 HEAD 常返回 200,但 GET 返回 401 Authorization Required——实体下载才强制鉴权。
  2. 鉴权是 HTTP MAC 方案,最简单可用的是 URL 拼 ?accessToken=<token>,已实测直达 200 / 完整体积。
  3. token 来自登录态:浏览器登录后,UC_TOKEN-<appId>-ncet-xedu cookie 是一段 base64 JSON,用 base64 解码后含:
    - access_token —— 即 MAC 的 id,直接当 ?accessToken= 的值;
    - mac_key + mac_algorithm:"hmac-sha-256" —— 用于构造 x-nd-auth 请求头(见下);
    - expires_at —— 约 7 天有效,过期需重新取。
  4. 取 token 的两种方式:
    - 方式一(推荐,最简单):浏览器打开教材页 → F12 → Application → Cookies → 找 UC_TOKEN-*-ncet-xedu → 复制整段值 → base64 解码 → 取 access_token。
    - 方式二(看网络包):F12 → Network → 找 viewer.html?file=<私有PDF> 或 .pdf 的 XHR 请求 → 复制其 x-nd-auth 头里的 MAC id="<token>",取引号内即为 access_token。
  5. 等效请求头写法(防 query 参数被拦时兜底):
    x-nd-auth: MAC id="<access_token>",nonce="<毫秒时间戳:随机串>",mac="<HMAC-SHA256(mac_key, 归一化串)>"
    归一化串格式:timestamp\nnonce\nMETHOD\n/path?query\nhost\nport\n\n。绝大多数情况用 ?accessToken= 即可,无需手搓 HMAC。
  6. 平台实际用法:前端用 basic.smartedu.cn/pdfjs/2.15/web/viewer.html?file=<私有PDF> 加载,子请求带上述鉴权。

工作流程

Step 1:提取 contentId(同上)

从用户链接的 contentId 参数取 UUID。

Step 2:让用户提供 access_token

明确要求用户从浏览器按上面的「方式一 / 方式二」拷一份新鲜的 access_token(base64 JSON 里的 access_token 字段值)。不要把明文 token 落盘到交付文件,用完即弃。

Step 3:拉取源 PDF

python fetch_source_pdf.py 9085151a-b698-4b28-8c00-2c4aaf0c91ad --token "<access_token>"
  • 脚本从 JSON 的 ti_items 定位 source PDF 的私有地址,拼 ?accessToken= 直下。
  • 输出 教材_源PDF_<contentId>.pdf。
  • 合成后 pdfinfo 教材_源PDF_*.pdf | grep -E "Pages|Page size" 核对页数与尺寸(应为 A4 / 595×842pt)。

> 若返回 401:token 已过期或取自非本资源会话,请用户重新从浏览器取一份新鲜 token。

脚本 C:fetch_source_pdf.py(标准库 urllib,零外部依赖)

import sys, json, argparse, urllib.request

UA = "Mozilla/5.0"
REFERER = "https://basic.smartedu.cn/"
JSON_TMPL = "https://s-file-2.ykt.cbern.com.cn/zxx/ndrv2/resources/tch_material/details/{cid}.json"


def fetch_json(cid):
    req = urllib.request.Request(
        JSON_TMPL.format(cid=cid),
        headers={"User-Agent": UA, "Referer": REFERER},
    )
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.loads(r.read().decode("utf-8"))


def find_source_pdf(d):
    """从 ti_items 找 ti_file_flag=="source" 的私有 PDF 存储地址(含 -private host)。"""
    for it in d.get("ti_items", []):
        if it.get("ti_file_flag") == "source":
            for s in it.get("ti_storages", []):
                if s.endswith(".pdf"):
                    return s
    return None


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("content_id")
    ap.add_argument("--token", required=True,
                    help="浏览器 cookie (UC_TOKEN) 解码后的 access_token 字段值")
    ap.add_argument("--out", default=None, help="输出 PDF 路径")
    args = ap.parse_args()

    d = fetch_json(args.content_id)
    url = find_source_pdf(d)
    if not url:
        print("未在 ti_items 中找到 source PDF 存储地址")
        sys.exit(1)

    url += f"?accessToken={args.token}"
    out = args.out or f"教材_源PDF_{args.content_id}.pdf"
    req = urllib.request.Request(url, headers={"User-Agent": UA, "Referer": REFERER})
    with urllib.request.urlopen(req, timeout=60) as r:
        data = r.read()
    with open(out, "wb") as f:
        f.write(data)
    print(f"saved {out}: {len(data)//1024}KB")


if __name__ == "__main__":
    main()

使用方式(私有源)

# 1) 从浏览器取新鲜 access_token(UA-TOKEN 解码后的 access_token 字段)
# 2) 拉取原生 PDF
python fetch_source_pdf.py 9085151a-b698-4b28-8c00-2c4aaf0c91ad --token "<access_token>"
# 3) 校验
pdfinfo 教材_源PDF_9085151a-b698-4b28-8c00-2c4aaf0c91ad.pdf | grep -E "Pages|Page size"

两半对照与公共边界

维度 上半:公共源 下半:私有源
是否需登录/Token 否 是(access token,约 7 天有效)
资源位置 非私有 host transcode/image/N.jpg 私有 host pkg/pdf_xxx.pdf
产物 图片合成 PDF(1488×2104 像素图) 原生 A4 矢量 PDF(文字可选)
体积(167 页样例) ~53MB ~26MB
自动化程度 完全可自动化 需用户定期提供新鲜 token
脚本 download_textbook.py + make_pdf.py fetch_source_pdf.py

公共边界与风险提示:
- 页数陷阱:两半都要注意 preview 是预览子集,永远用 ti_items 的 pagesize 当总页数。
- 缺页核对:合成/下载后务必 pdfinfo 确认页数 == 真实页数;缺失页回到对应 Step 补抓重跑。
- 版权:仅用于个人学习、备课,请勿二次分发。
- 网络可达性:运行环境到 *.ykt.cbern.com.cn 不通时脚本会全部失败,是网络层限制,需换能访问该域的环境执行。
- token 安全:私有源路线拿到的 access token 等同登录凭据,不要写进脚本文件、不要提交到 git、不要落盘交付物;过期后让用户重新从浏览器取。
- 接口变动:JSON 结构与图片路径规律为逆向所得,平台改版需重新核对 ti_items 与 preview 字段。

常见问题

5 问
答案由本站依据条目字段整理
下载国家中小学教育平台教材怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_a3ed06bc/smartedu-download。安装方式为未标注。装完刷新技能列表或重开客户端。

下载国家中小学教育平台教材支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

下载国家中小学教育平台教材权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

下载国家中小学教育平台教材收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

下载国家中小学教育平台教材用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 2026-11-06。