从 优学院 (DGUT) 的「题库训练(Question Train / Practice)」页面导出题目,并整理为 佛脚刷题 所需的 JSON 格式(格式定义见 tmpl.jsonc)。
说明(关于“官方站/学校专线”的差异):
- 本仓库默认适配的是 东莞理工(DGUT)专属线路:
https://lms.dgut.edu.cn/utest/(页面)与https://lms.dgut.edu.cn/utestapi/(接口)。- 优学院官方站(
www.ulearning.cn)与其他学校通常属于“同一套产品、多租户/多实例部署”。接口的业务逻辑大概率相似(例如同样存在answerSheet/questionList/answer),但 域名、路径前缀、鉴权 cookie 名称 可能不同。- 因为我无法保证官方站与 DGUT 的路径一模一样,所以 README 里把“怎么定位 base URL 与关键接口”的方法写得更详细,方便你或其他学校同学进行二次开发。
- 应该不是因为我懒得写
本项目提供两条使用路线:
- 路线 A:本地 Python 导出器
- 适合:可重复运行、自动化、归档、批量导出。
- 路线 B:油猴脚本(Tampermonkey)一键导出
- 适合:你已经在题目页面,直接点击按钮导出并下载 JSON,无需本地 Python 环境。
免责声明:本项目仅用于导出你自己账号在平台上可访问的数据,并进行格式整理。请遵守学校与课程平台的使用规范。
目标格式在 tmpl.jsonc 中定义,整体是一个数组(题目按顺序排列)。示例:
- 选择题:
- 字段:
题型、题干、选项、答案、解析
- 字段:
- 判断题:
- 字段:
题型、题干、答案、解析
- 字段:
- 填空题:
- 字段:
题型、题干(答案用{}括起来嵌入题干)、解析
- 字段:
- 问答题:
- 字段:
题型、题干、答案、解析
- 字段:
当前实现规则(与 Python 和 Userscript 的逻辑一致):
- 选择题:
- 选项会输出成
"A. xxx"、"B. xxx"… - 答案会合并成字符串,例如
"AC"。
- 选项会输出成
- 判断题:
- 若平台答案为
A/B,会尽量映射成正确/错误。
- 若平台答案为
- 填空题:
- 会尝试把题干中的空(
____、()、【】、[])按顺序替换成{答案}。 - 若题干找不到空位,会把
{答案}追加到题干末尾。
- 会尝试把题干中的空(
- 解析:
- 当前输出为空字符串(因为在我们抓到的接口响应里没有解析字段;后续若发现解析 API,可扩展)。
你在浏览器里看到的题目,表面上像在页面 HTML 里,但其实平台是 SPA:
- 前端页面加载后会调用后端接口拿到 JSON 题目数据。
- 再由前端把 JSON 渲染成你看到的题干/选项。
因此:最稳定的导出方式不是去“扒 HTML”,而是复用浏览器已经请求过的 API。
使用 F12 刷新一下,在网络活动中可以看到题库训练页面的关键接口都在:
https://lms.dgut.edu.cn/utestapi/
核心需要两个接口:
- 答题卡(答案/总题数/题目 ID 列表)
GET /questionTraining/student/answerSheet- 作用:拿到
total(总题数)以及每道题的id -> answer映射。
- 题目详情列表(分页)
GET /questionTraining/student/questionList- 作用:分页拿到题干、选项等信息。
这两个接口都需要参数:
qtId:题库训练 IDocId:课程/班级 IDqtType:训练类型(通常为1)traceId:用户 ID(通常是 cookieUSERINFO.userId)pn/ps:页码/每页数量(仅questionList需要)
还需要登录态:
Authorization请求头(通常来自 cookieAUTHORIZATION或token)- 同域 cookie(浏览器里天然具备;Python 版本通过 cookie 文件/环境变量提供)
如果你使用的不是 lms.dgut.edu.cn(比如官方站 www.ulearning.cn 或其他学校域名),建议按下面流程迁移:
- 打开题库训练页面,按
F12-> Network,刷新页面。 - 在 Network 里搜索关键词:
utestapi或answerSheet或questionList
- 记录你看到的接口基址(base URL),通常形态类似:
https://<your-domain>/utestapi/- 或
https://<your-domain>/<prefix>/utestapi/
- 验证以下接口是否存在(路径可能一致/也可能需要微调):
GET /questionTraining/student/answerSheetGET /questionTraining/student/questionListPOST /questionTraining/student/answer(用于拿result.correctAnswer)
- 验证鉴权方式:
- 请求头里是否需要
Authorization(以及是否需要Bearer xxx) - cookie 里是否仍然有
USERINFO.userId(我们用它当traceId)
- 请求头里是否需要
只要你能在 Network 里看到这些请求成功(HTTP 200 + JSON code 正常),就基本可以把本项目的 API_BASE 与 cookie 解析逻辑迁移过去。
- 从页面/配置中拿到:
qtId / ocId / qtType / traceId / Authorization - 调用
answerSheet:- 得到
total与题目答案映射(按题目id关联)
- 得到
- 根据
total计算页数,循环调用questionList:- 每页返回
trainingQuestions - 把答案映射合并到题目对象上
- 每页返回
- 把平台题目 JSON 转成佛脚刷题 JSON
- 写出
questions.json/ 或浏览器触发下载
平台返回的题干/选项是富文本(HTML),例如:
...重大命<br />题...
如果导出时原样保留,阅读与搜索体验都会很差。
因此我们在导出时做一次“HTML -> 纯文本”清洗:
<br>/<br/>/<br />:转换成换行\n</p>/</div>/</li>/</tr>:转换成换行\n- 其余标签:直接移除
等 HTML 实体:解码成正常字符
性能方面:清洗是一次性线性扫描(O(n)),相对网络请求耗时可以忽略;并且文本更干净后通常更利于搜索。
为了让用户尽量少操作,本项目支持 三种来源 的信息合并,优先级从高到低:
- cookie 文件(
cookie.json/cookie.jsonc)(最高优先级)
建议在浏览器的拓展商店搜索"cookie editor", 然后 Export Json - 提供: -
AUTHORIZATION(从 cookieAUTHORIZATION或token) -USER_ID(从 cookieUSERINFO/USER_INFO解析userId) - 若 cookie 和.env冲突:以 cookie 为准。 - 推荐用户手动在根目录放置:cookie.json(纯 JSON)。 -cookie.jsonc仅作为演示文件(jsonc:json+comment(允许注释(coomment)))。
-
.env文件- 用户可以手动填写:
AUTHORIZATION / USER_ID / QT_ID / OC_ID / QT_TYPE。
- 用户可以手动填写:
-
练习链接
PRACTICE_URL- 只负责从链接中解析:
QT_ID / OC_ID / QT_TYPE。 - 示例:
https://lms.dgut.edu.cn/utest/index.html?v=1765875491045#/questionTrain/practice/2674/134202/1
- 只负责从链接中解析:
自动探测 cookie 文件(当你不显式指定时):
- 先找根目录
cookie.json - 再找根目录
cookie.jsonc
.
├── main.py # 根目录启动入口(建议使用)
├── python/
│ ├── client.py # 调用 utestapi
│ ├── config.py # 合并 cookie/.env/url 配置
│ ├── formatter.py # 转佛脚刷题 JSON
│ └── exporter.py # 写文件导出
├── .env.example # 环境变量示例
├── tmpl.jsonc # 目标 JSON 格式说明
└── UserScript/_userscript.js # 油猴脚本
本项目使用 uv 管理依赖:
uv sync- 浏览器导出 cookie 为
cookie.json(推荐)
- 放到项目根目录(与
main.py同级)
-
复制你当前题目页面的链接(practice URL)
-
运行:
uv run python main.py --url "https://lms.dgut.edu.cn/utest/index.html?v=1765875491045#/questionTrain/practice/2674/134202/1" --txt通过 一些答题时候的网络活动 我们确认:提交答题接口会在响应中返回 correctAnswer(标准答案)。
因此本项目将“标准答案导出”作为默认行为:程序会对每道题提交一个“随便答案”,从响应中收集 correctAnswer,并将导出的 答案 字段替换为标准答案。
重要提示:
--correct会写入你的答题记录(平台会认为你做过这些题,正确率可能变化)。- 如果你只想先验证功能,可以用
--correct-limit限制只提交前 N 题。
示例:
# 仅测试前 20 题
uv run python main.py --url "https://lms.dgut.edu.cn/utest/index.html?v=...#/questionTrain/practice/2674/134202/1" --correct-limit 20
# 全量导出标准答案(会对全部题目提交一次答案)
uv run python main.py --url "https://lms.dgut.edu.cn/utest/index.html?v=...#/questionTrain/practice/2674/134202/1" --txt如果你想导出用户作答答案(不提交答案,不保证有值/不保证正确),可以使用 --user-answer 参数。
示例:
uv run python main.py --url "https://lms.dgut.edu.cn/utest/index.html?v=...#/questionTrain/practice/2674/134202/1" --user-answer --txt- 复制
.env.example为.env - 填写
AUTHORIZATION / USER_ID / QT_ID / OC_ID / QT_TYPE - 运行:
uv run python main.py --txt当你希望 .env 作为“默认值”,但又希望 cookie 更新后优先用 cookie:
- 保留
.env - 放置/指定
cookie.json - 运行时 cookie 的
AUTHORIZATION/USER_ID会覆盖.env
默认输出到 output/:
output/questions.json:佛脚刷题 JSON(主产物)output/questions.txt:可读文本(需要--txt)output/questions_raw.json:原始平台 JSON(需要--raw)
文件:UserScript/_userscript.js
- 安装 Tampermonkey
- 新建脚本,把
UserScript/_userscript.js内容粘贴进去保存 - 打开题库训练页面(practice 页面)
页面右下角会出现按钮:Export Questions JSON
点击后脚本会:
- 从当前浏览器 cookie 自动拿
Authorization和userId - 调用
answerSheet+questionList分页拉题目 - 转成佛脚刷题 JSON
- 触发下载文件:
ulearning_{qtId}_{ocId}_{qtType}_questions.json
- 按钮出现但报错 Missing Authorization cookie
- 说明你当前未登录或登录态失效;重新登录后刷新页面。
- 报错 Not on practice page
- 说明当前页面 hash 不是
#/questionTrain/practice/...;请确保在题目训练页面。
- 说明当前页面 hash 不是
AUTHORIZATION和 cookie 相当于登录凭证,不要提交到仓库、不要发给他人。- 本仓库的
.gitignore已忽略:.env、(优学院的信息暴露就暴露吧,能帮我写作业就再好不过了)、导出结果等。cookie.jsonc
- cookie + 链接导出:
uv run python main.py --url "https://lms.dgut.edu.cn/utest/index.html?v=...#/questionTrain/practice/2674/134202/1" --txt- 手动 .env 导出:
uv run python main.py --txt