Python实战:自动批量下载国家中小学智慧教育平台教学资源 (含MAC Token鉴权破解思路)
Python实战:自动化获取国家中小学智慧教育平台公开资源 (含MAC Token鉴权解析)
声明:本文仅供 Python 网络编程、API 交互技术学习交流使用。请尊重平台资源版权,合理合规使用,切勿用于高并发请求、影响服务器运行或任何商业牟利行为。
一、项目背景
国家中小学智慧教育平台是广大中小学教师备课的宝库,里面有大量优秀的公开教学设计、课件(PPT)和学习任务清单。在日常备课中,老师们往往需要逐个点击预览再进行保存,过程相对繁琐。
为了提高备课效率,我们可以利用 Python 编写一个自动化辅助工具:输入课程链接,即可按顺序将“PPT、教学设计、学习任务清单”等公开备课资源整理到本地。

二、数据接口与网络请求分析
在编写代码之前,我们需要了解浏览器是如何与服务器交互并获取这些 PDF 文件的。打开浏览器的开发者工具(F12),切换到 Network 面板进行观察。
1. 寻找资源的元数据 JSON
当我们在平台上打开一个特定的课程活动(例如:https://basic.smartedu.cn/syncClassroom/classActivity?activityId=xxx...)时,前端会发起一个 XHR 请求获取当前课程的数据。
接口结构大概如下:https://s-file-1.ykt.cbern.com.cn/zxx/ndrv2/national_lesson/resources/details/{activityId}.json
返回的是一个 JSON 对象,里面包含了这个课程下的所有资源信息。在 ti_storages 字段里,我们可以找到云存储的访问链接。
2. 跨域与权限验证
拿到云存储的 URL 后,如果直接在浏览器开个新标签页访问,或者用最简单的 requests.get() 去请求,会收到 401 Unauthorized 或 403 Forbidden。
观察浏览器的正常请求,发现请求真实 PDF 链接时,带有几个特殊的 Request Header:
Origin:https://basic.smartedu.cnReferer:https://basic.smartedu.cn/x-nd-auth:MAC id="7F93...",nonce="1784105167:AbCdEfGh",mac="xxxx..."
前两个是常规的跨域来源校验。而 x-nd-auth 则是核心的鉴权字段。
3. 解析 MAC Token 鉴权机制
查阅相关开放文档,发现平台使用的是 OAuth MAC Token 机制(RFC draft-ietf-oauth-v2-http-mac)。
这是一种标准的鉴权方式:
- 用户登录后,服务器下发一对密钥对:
access_token(身份标识 id)和mac_key(签名密钥)。 - 当客户端去请求云盘文件时,需要在本地用
mac_key对 当前时间戳、请求的URL路径 等信息进行HMAC-SHA256摘要计算。 - 计算结果即为
x-nd-auth里的mac字段。服务器收到请求后,会采用同样的算法验证签名的有效性。
这意味着:直接复制抓包里的 x-nd-auth 是无效的,因为时间戳和随机数会很快过期。我们需要在 Python 脚本中按规范实现这套签名算法。
三、获取核心凭证 (Token)
在实现签名算法前,我们需要获取自己的 access_token 和 mac_key,它们通常存储在浏览器的 localStorage 中。
为了方便提取,我编写了一小段辅助 JavaScript 脚本。
操作步骤:
- 登录 basic.smartedu.cn
- F12 打开控制台(Console)
- 粘贴并回车运行以下 JS 代码:
(function(){function f(o){if(o==null)return null;if(typeof o==='string'){if(o.length>10&&(o[0]==='{'||o[0]==='[')){try{return f(JSON.parse(o))}catch(e){return null}}return null}if(typeof o!=='object')return null;if(o.access_token&&o.mac_key)return{access_token:o.access_token,mac_key:o.mac_key,expires_at:o.expires_at||''};for(var k in o){var r=f(o[k]);if(r)return r}return null}function s(st){for(var i=0;i<st.length;i++){var r=f(st.getItem(st.key(i)));if(r)return r}return null}var r=s(localStorage)||s(sessionStorage);if(r){console.log('%c==== 复制下面这一行 JSON ====','color:green;font-size:14px;font-weight:bold');console.log(JSON.stringify(r));console.log('%c==== 复制上面那一行 ====','color:green;font-size:14px;font-weight:bold')}else{console.log('%c未找到 token','color:red;font-size:14px')}})();
运行后,控制台会输出一行 JSON,里面正好包含我们需要的两把钥匙。保存好备用。
四、Python 代码实现 (核心解析)
环境准备非常简单,只需要安装 requests:pip install requests
为了方便理解,我将核心逻辑分成了三个模块。
模块 1:MAC 签名生成器 (关键)
这是请求成功的核心。我们需要把 nonce、HTTP方法、请求路径、域名、端口号按照固定顺序拼接,最后用 HMAC-SHA256 计算。
import base64, hashlib, hmac, random, string, time
from urllib.parse import urlparse
def build_mac_auth(url: str, access_token: str, mac_key: str, method: str = "GET") -> str:
# 1. 解析URL
parsed = urlparse(url)
host = parsed.hostname or ""
port = parsed.port or (443 if parsed.scheme == "https" else 80)
path = parsed.path or "/"
if parsed.query:
path = f"{path}?{parsed.query}"
# 2. 构造 nonce (时间戳:随机串)
ts = str(int(time.time()))
rand = "".join(random.choices(string.ascii_letters + string.digits, k=8))
nonce = f"{ts}:{rand}"
# 3. 构造基础签名串 (严格按顺序,末尾加换行)
base = f"{nonce}\n{method}\n{path}\n{host}\n{port}\n\n"
# 4. HMAC-SHA256 加密并 Base64 编码
mac = base64.b64encode(
hmac.new(mac_key.encode("utf-8"), base.encode("utf-8"), hashlib.sha256).digest()
).decode("utf-8")
# 5. 拼装为 header 要求的格式
return f'MAC id="{access_token}",nonce="{nonce}",mac="{mac}"'
模块 2:深度遍历寻找资源
资源的 JSON 结构存在较深嵌套。我们可以编写一个递归扫描函数:只要节点里有 ti_format == 'pdf' 和 ti_storages(云盘地址),我们就提取它。
def find_pdf_items(activity_json: dict) -> list:
results = []
def walk(node):
if isinstance(node, dict):
fmt = str(node.get("ti_format", "")).lower()
storages = node.get("ti_storages")
# 找到目标
if fmt == "pdf" and isinstance(storages, list) and storages:
results.append(node)
# 继续向子节点递归
for v in node.values():
walk(v)
elif isinstance(node, list):
for v in node:
walk(v)
walk(activity_json)
return results
模块 3:保存文件
利用生成好的鉴权信息,携带 Origin 和 Referer 去请求文件,以流(stream=True)的方式写入本地,防止文件过大占用过多内存。
import requests
import os
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"
def download_pdf(resource_url: str, save_path: str, access_token: str, mac_key: str) -> None:
headers = {
"User-Agent": UA,
"Origin": "https://basic.smartedu.cn",
"Referer": "https://basic.smartedu.cn/",
"x-nd-auth": build_mac_auth(resource_url, access_token, mac_key, "GET"),
"Accept": "*/*",
}
with requests.get(resource_url, headers=headers, stream=True, timeout=60) as r:
if r.status_code == 401:
raise RuntimeError("请求被拒 (401),token 无效或已过期")
r.raise_for_status()
os.makedirs(os.path.dirname(save_path), exist_ok=True)
with open(save_path, "wb") as f:
for chunk in r.iter_content(chunk_size=64 * 1024):
if chunk:
f.write(chunk)
完整组合
最后,只需将上述模块按流程组装:
- 请求 API 拿到元数据。
- 调用
find_pdf_items找出资源。 - 提取所需的备课资源。
- 循环调用
download_pdf保存。
(大家可以基于上面的三大核心模块自由发挥编写 main 函数和交互逻辑。)
五、打包为 EXE 便捷使用
为了方便分享给同事使用,我们可以用 PyInstaller 将它打包成独立的程序:
pip install pyinstaller
pyinstaller --onefile --name "教育资源下载器" --clean crawler.py
打包完成后,dist 文件夹下会生成一个可执行文件,双击即可运行,无需配置环境。
六、总结
通过这次实践,我们完成了以下技术要点:
- 标准鉴权协议的本地化实现(基于 HMAC 的 MAC Token 签名过程)
- 复杂 JSON 数据结构解析(深度遍历算法寻找目标节点)
- Python 脚本打包与交互设计
github源码:https://github.com/lhjgyhj/Education_Resource_Downloader
希望这篇文章能帮你巩固网络请求分析与协议实现的技巧。如果你在学习过程中有任何疑问,欢迎在评论区留言交流!
更多推荐


所有评论(0)