Imported from luohuixi/xiaoya-automation (
SKILL.md). Install upstream withnpx skills add luohuixi/xiaoya-automation. Copyright stays with the author.
小雅·师说自动化操作指南
概述
本skill提供华中师范大学小雅·师说智能教学平台(https://ccnu.ai-augmented.com)的**登录态保存与作业抓取**自动化操作。
脚本执行完成后会在命名空间目录下生成 xiaoya_auth.json,供 Playwright MCP / Agent 后续直接加载,无需重复登录。
核心设计原则:
- 脚本优先:用户未明确声明"请一步步手动操作"时,直接运行打包好的Python脚本,无需通过Playwright MCP逐步点击
- 默认无头:脚本默认在后台运行(不弹出浏览器窗口),完成后自动关闭
- 命名空间隔离:以学号为命名空间,不同用户的数据完全隔离,避免冲突
- 保底手动:当脚本不可用时(如环境问题、需要人工确认)或对应步骤的脚本不存在,才回退到Playwright MCP逐步操作
命名空间(Namespace)
本 skill 使用命名空间机制隔离不同用户的数据,避免多用户场景下的文件冲突。
目录结构
{skill_base}/
├── config.json # 默认账号密码配置(所有脚本共享)
├── namespaces/
│ └── {namespace}/ # 学号
│ ├── xiaoya_auth.json # 登录态文件
│ ├── course_map.json # 课程映射
│ ├── mcp_auth_inject.js # MCP 注入代码
│ └── tasks/ # 作业详情与答案输出
│ └── {course_name}/
│ ├── {task_name}.md
│ ├── {task_name}_答案.md
│ └── {task_name}_无法完成说明.md
├── scripts/
│ ├── step1_login_and_store.py
│ ├── step2_get_course_map.py
│ ├── step3_get_tasks.py
│ ├── step4_answer_task.py # Step 4: 分析与生成答案
│ ├── step5_submit_task.py # Step 5: 提交作业
│ └── step6_resubmit_task.py # Step 6: 取消提交 / 重新提交
└── SKILL.md
命名空间规则
- 默认命名空间:使用学号
- 自定义命名空间:通过
--namespace参数指定 - 环境变量:设置
XIAOYA_NAMESPACE可覆盖默认命名空间 - 多用户支持:不同学号的数据完全隔离,互不影响
默认账号配置 (config.json)
所有脚本共享根目录下的 config.json 作为默认凭据来源,优先级如下:
命令行参数 (-u / -p) > 环境变量 (XIAOYA_*) > config.json
config.json 格式:
{
"username": "20240000",
"password": "123456"
}
- 修改此文件即可更改所有脚本的默认账号密码,无需逐个脚本修改
- 密码以明文存储在本地,请确保文件权限安全(仅自己可读写)
base_url和namespace保留在脚本内部,如需修改请直接编辑脚本
使用示例
# 使用默认命名空间
python scripts/step1_login_and_store.py
# 指定其他学号的命名空间
python scripts/step1_login_and_store.py --namespace 2024123456
# Step 2 和 Step 3 会自动使用同一命名空间
python scripts/step2_get_course_map.py --namespace 2024123456
python scripts/step3_get_tasks.py --course "数据库原理" --namespace 2024123456
默认值配置
- 学校: 华中师范大学(school=10511)
- 默认账号: 参考config.js
- 默认密码: 参考config.json
- 默认命名空间:
{学号} - 首页URL:
https://ccnu.ai-augmented.com/home - 课程页面URL:
https://ccnu.ai-augmented.com/app/jx-web/mycourse - 脚本路径:
{skill_base}/scripts/...
注意: 如果用户提供了自定义账号/密码,通过命令行参数传给脚本。仅在用户未声明时使用默认值。
Step 1: 保存登录状态
目标
完成小雅平台的登录流程,等待进入"我教的课"页面后,保存登录态到命名空间目录下的 xiaoya_auth.json。
保存的登录态(cookies + localStorage)供后续 Playwright MCP / Agent 直接加载,无需再次输入账号密码即可访问已登录页面。默认保存到 namespaces/{学号}/xiaoya_auth.json。
首选方式:直接运行脚本(默认)
当用户说"帮我登录小雅"、"保存小雅登录态"等,未明确要求"一步步来"或"用playwright手动操作"时,直接执行以下命令:
1. 进入skill目录并激活虚拟环境
cd {skill_base}
.venv\Scripts\activate # Windows PowerShell
# 或 source .venv/bin/activate # Linux/macOS/WSL
2. 执行脚本(使用默认账号密码,无头模式)
python scripts/step1_login_and_store.py
3. 执行脚本(用户指定了账号密码)
python scripts/step1_login_and_store.py -u 20240000 -p 123456
4. 执行脚本(指定命名空间)
python scripts/step1_login_and_store.py --namespace 2024123456
命名空间默认使用学号。指定后,登录态会保存到
namespaces/2024123456/xiaoya_auth.json。
5. 执行脚本(需要看到浏览器窗口,调试时)
python scripts/step1_login_and_store.py --headed
6. 执行脚本(完成后截图)
python scripts/step1_login_and_store.py -s result.png
8. 脚本输出文件
脚本成功执行后会生成以下文件(位于命名空间目录 namespaces/{namespace}/):
| 文件 | 说明 | 用途 |
|---|---|---|
xiaoya_auth.json |
Playwright storage_state 格式 |
Step 2/3 脚本复用登录态 |
mcp_auth_inject.js |
MCP 可直接执行的注入代码 | 在 MCP browser_evaluate 中执行以恢复登录态 |
xiaoya_step1.png |
登录成功后的页面截图 | 调试验证 |
注意:
mcp_auth_inject.js会自动生成。如果丢失,重新执行脚本即可重新生成。Step 2 生成的course_map.json同理。
脚本行为说明
| 行为 | 说明 |
|---|---|
| 默认模式 | 无头(headless),后台运行 |
| 超时处理 | 如无头模式加载页面超时,请添加 --headed 参数使用有头模式 |
| 登录态保存 | 成功后自动写入 xiaoya_auth.json(cookies + localStorage) |
| 完成后 | 自动保存截图到 xiaoya_step1.png(如指定了 -s) |
| 错误处理 | 失败时自动保存 xiaoya_error.png 用于调试 |
| 账号来源 | 命令行参数 > 环境变量 > 内置默认值 |
| 分辨率 | 自动设置 1920×1080,满足平台推荐 |
登录态文件 xiaoya_auth.json
脚本成功执行后,会在命名空间目录 namespaces/{namespace}/ 下生成 xiaoya_auth.json,内容格式如下:
{
"cookies": [{ "name": "JSESSIONID", "value": "...", "domain": "ccnu.ai-augmented.com" }],
"origins": [{ "origin": "https://ccnu.ai-augmented.com", "localStorage": [...] }]
}
使用方式(MCP / Agent):
# 启动浏览器时直接加载登录态(指定命名空间路径)
context = browser.new_context(storage_state="namespaces/20240000/xiaoya_auth.json")
page = context.new_page()
# 直接访问已登录页面,无需再次登录
page.goto("https://ccnu.ai-augmented.com/app/jx-web/mycourse")
注意:
storage_state只恢复登录态(cookies / storage),不恢复页面现场。如果需要回到特定页面或打开特定面板,MCP 需要重新导航并执行相应操作。默认路径在namespaces/{namespace}/xiaoya_auth.json。
MCP 注入代码 mcp_auth_inject.js
当需要通过 Playwright MCP 直接操作已登录页面时,使用此文件。
工作原理:
xiaoya_auth.json中部分 cookie 是HttpOnly(如SESSION),无法通过 JavaScriptdocument.cookie设置- 但小雅平台的核心鉴权 token(
HS-prd-access-token)和localStorage(user_sig、XY_GLOBAL_CONFIG)是非 HttpOnly 的 - 注入这些值后,页面刷新即可识别登录态
使用步骤:
// Step 1: 在 MCP 中执行注入代码
// 读取 mcp_auth_inject.js 文件内容,作为 browser_evaluate 的 function 参数
// 或直接复制文件中的 IIFE 代码执行
// Step 2: 刷新页面使登录态生效
await page.reload();
await page.waitForTimeout(3000);
// Step 3: 验证登录态
// 如果 URL 未跳转到登录页,且能看到课程列表,说明登录态恢复成功
完整示例(MCP workflow):
Tool: browser_navigate
URL: https://ccnu.ai-augmented.com/app/jx-web/mycourse
Tool: browser_evaluate
Function: (function() { document.cookie = 'HS-prd-access-token=...'; localStorage.setItem('user_sig', '...'); ... })()
Tool: browser_navigate
URL: https://ccnu.ai-augmented.com/app/jx-web/mycourse
Tool: browser_wait_for
Time: 3秒
提示:如果执行后仍被重定向到登录页,说明
HttpOnlycookie 缺失导致鉴权失败。此时需要重新执行step1_login_and_store.py生成新的登录态(session 可能已过期)。
备选方式:Playwright MCP逐步操作
仅在以下情况使用:
- 用户明确要求:"一步步来"、"手动操作"、"用playwright"
- 脚本执行失败(环境问题、页面结构变更、需要验证码)
- 需要与页面实时交互并查看中间状态
手动工作流
步骤 1.1: 导航到首页
Tool: browser_navigate
URL: https://ccnu.ai-augmented.com/home
预期页面结构:
- generic:
- generic:
- generic:
- img "logo" [cursor=pointer]
- generic:
- generic [cursor=pointer]: 首页
- generic [cursor=pointer]: 课程中心
- generic:
- button "登录" [cursor=pointer] ← 目标元素
- generic:
- img
- text: 中文
- img "down"
步骤 1.2: 点击右上角"登录"按钮
Tool: browser_click
Element: "登录按钮"
Target: 定位包含文本"登录"的button元素(首页右上角)
登录页结构:
- generic:
- generic:
- banner:
- img
- generic:
- generic: 中文
- img "down"
- generic:
- generic:
- generic:
- tablist:
- generic:
- tab "手机号/账号登录" [selected] ← 默认已选
- tab "统一身份认证"
- generic:
- generic:
- generic: 手机扫码,安全登录
- img
- generic:
- generic:
- generic [cursor=pointer]:
- generic:
- combobox [disabled]
- generic "华中师范大学" ← 学校已预填
- generic:
- img
- textbox "请输入手机号/账号" ← 账号输入框
- generic:
- img
- textbox "请输入密码" ← 密码输入框
- img "eye-invisible"
- button "登录" [cursor=pointer] ← 登录按钮
步骤 1.3: 处理分辨率提示弹窗(条件触发)
弹窗结构:
- generic:
- dialog:
- generic:
- generic:
- paragraph: 当前页面显示区域较小...
- paragraph: 1.将浏览器窗口设置为最大化显示
- paragraph: 2.在浏览器中调低浏览页面缩放比例
- paragraph: 3.在操作系统显示设置中调整显示分辨率或缩放比例
- generic [cursor=pointer]:
- checkbox "不再提示"
- generic: 不再提示
- button "知道了" [cursor=pointer] ← 点击关闭
Tool: browser_click
Element: "知道了按钮"
Target: 包含文本"知道了"的button元素
此弹窗在页面首次加载时可能出现。如果snapshot中没有此弹窗,跳过。
步骤 1.4: 输入账号和密码
账号输入:
Tool: browser_type
Target: 文本框占位符为"请输入手机号/账号"的元素
Text: {username} (默认值: 20240000)
密码输入:
Tool: browser_type
Target: 文本框占位符为"请输入密码"的元素
Text: {password} (默认值: 123456)
步骤 1.5: 点击登录按钮
Tool: browser_click
Element: "登录按钮"
Target: 包含文本"登录"的button元素(登录表单内)
预期结果: 先跳转至 auth/loginRedirect,再到 app/jx-web/mycourse。页面完全加载后,脚本自动保存 context.storage_state(path="namespaces/{namespace}/xiaoya_auth.json")。
Step 1 完成状态
完成上述步骤后,脚本已保存登录态到命名空间目录下的 xiaoya_auth.json,浏览器正常关闭。后续 MCP / Agent 可通过加载此文件直接访问已登录页面,无需再次登录。
错误处理
脚本执行失败
- 检查Python虚拟环境是否激活:
.venv\Scripts\activate - 检查playwright浏览器是否已安装:
.venv\Scripts\python.exe -m playwright install chromium - 检查网络连接是否正常
- 查看同目录下生成的
xiaoya_error.png调试截图
Playwright MCP手动方式失败
- 页面可能仍在加载中,增加
browser_wait_for等待时间 - 使用
browser_snapshot重新捕获当前页面结构 - 检查页面是否弹出新的对话框(如分辨率提示)
会话过期
- 如果 MCP 加载
namespaces/{namespace}/xiaoya_auth.json后访问页面仍被重定向到登录页,说明storage_state中的 session 已过期 - 重新执行 Step 1 脚本,生成新的登录态文件
清理残留进程
详见下文「资源清理(防止内存泄漏)」章节。
页面元素速查表
| 元素描述 | 定位方式 | 备注 |
|---|---|---|
| 首页登录按钮 | button包含文本"登录" | 页面右上角 |
| 账号输入框 | textbox占位符"请输入手机号/账号" | 登录表单内 |
| 密码输入框 | textbox占位符"请输入密码" | 登录表单内 |
| 登录表单提交按钮 | button包含文本"登录" | 表单底部 |
| 分辨率提示关闭按钮 | button包含文本"知道了" | 弹窗内,条件出现 |
资源清理(防止内存泄漏)
Playwright 浏览器进程在某些情况下(如脚本异常中断、Agent 强制终止子进程等)可能不会彻底退出,导致 chromium 僵尸进程残留并占用内存。
自动清理脚本
本 skill 提供了独立的清理脚本 {skill_base}/scripts/cleanup_playwright.py,用于扫描并终止残留的 Playwright / Chromium 进程。
1. 直接清理(推荐每次 skill 调用后执行)
python scripts/cleanup_playwright.py
2. 干跑模式(只查看会清理哪些进程,不实际终止)
python scripts/cleanup_playwright.py --dry-run
3. Agent 调用模式(完整流程示例)
# Step 1: 进入目录并激活环境
cd {skill_base}
.venv\Scripts\activate
# Step 2: 执行主脚本(使用 --no-wait 避免阻塞)
python scripts/step1_login_and_store.py -s xiaoya_result.png --no-wait
# Step 3: 清理残留进程(无论主脚本成功或失败都执行)
python scripts/cleanup_playwright.py
清理脚本的安全策略
- 只杀匹配的进程:进程名必须包含
chromium/chrome/msedge/firefox/playwright/node - 必须包含 Playwright 特征:命令行中出现
--remote-debugging-pipe、--headless、临时user-data-dir等标志 - 不杀用户浏览器:普通用户打开的 Chrome/Edge 因缺少 Playwright 特征,不会被误杀
- 不杀当前进程:清理脚本自身和正在运行主脚本的 Python 进程会被跳过
为什么需要这个脚本?
| 场景 | 风险 |
|---|---|
| Agent 超时强制终止子进程 | finally 块来不及执行,browser.close() 未调用 |
input() 阻塞时收到 EOFError |
异常发生在 finally 之前,资源未释放 |
| Playwright driver 崩溃 | Chromium 子进程成为孤儿进程 |
| 多次连续调用 skill | 每次调用新建浏览器进程,旧进程残留 |
建议:在 skill 的每次调用末尾,自动追加
cleanup_playwright.py的执行,形成"主脚本 + 清理"的闭环。
Step 2: 获取课程编号与课程名映射(按需执行)
目标
进入"我学的课"页面,遍历所有分页,提取每门课程的名称与编号 (courseId) 的映射关系,保存到 JSON 文件。
重要:此步骤仅在用户明确要求获取课程映射时执行,不是 Step 1 的自动后续。课程列表不会频繁变更,一次抓取可长期使用。
首选方式:直接运行脚本
脚本复用 Step 1 保存的登录态,无需再次输入账号密码。默认从命名空间目录读取/写入。
# 默认参数(从默认命名空间加载登录态,输出到同一目录)
python scripts/step2_get_course_map.py
# 指定命名空间(会自动读取 namespaces/2024123456/xiaoya_auth.json,输出到同一目录)
python scripts/step2_get_course_map.py --namespace 2024123456
# 完全自定义路径(覆盖命名空间默认值)
python scripts/step2_get_course_map.py --auth ./custom_auth.json --output ./custom_map.json --no-wait
脚本行为说明
| 行为 | 说明 |
|---|---|
| 登录态加载 | 自动读取 namespaces/{namespace}/xiaoya_auth.json,通过 browser.new_context(storage_state=...) 恢复 |
| 超时处理 | 如无头模式加载页面超时,请添加 --headed 参数使用有头模式 |
| 课程提取 | 遍历"我学的课"所有分页,逐页提取课程卡片 |
| ID 来源 | 从 React Fiber 内部状态提取 courseId(小雅前端不暴露 DOM data 属性) |
| 输出格式 | JSON,包含 name, courseId, url,保存到 namespaces/{namespace}/course_map.json |
| 去重策略 | 按 courseId 去重,防止分页重叠 |
输出文件 course_map.json 格式
{
"source": "xiaoya-automation",
"step": "course_map",
"total": 13,
"courses": [
{
"name": "机器学习",
"courseId": "6905007502274864505",
"url": "https://ccnu.ai-augmented.com/app/jx-web/mycourse/6905007502274864505/resource"
}
]
}
备选方式:Playwright MCP 手动提取
如果脚本执行失败,或需要在已有 MCP 会话中直接获取映射,可按以下步骤操作。
前提条件
已通过 Step 1 的 mcp_auth_inject.js 注入登录态,或 MCP 会话本身已完成登录。
手动工作流
步骤 2.1: 导航到课程页面
Tool: browser_navigate
URL: https://ccnu.ai-augmented.com/app/jx-web/mycourse
等待页面加载完成,确认显示"我学的课"课程列表。
步骤 2.2: 注入登录态(如需要)
如果页面被重定向到登录页,先执行 mcp_auth_inject.js 内容:
Tool: browser_evaluate
Function: (function() { document.cookie = 'HS-prd-access-token=...'; localStorage.setItem('user_sig', '...'); ... })()
然后重新导航到 mycourse 页面。
步骤 2.3: 提取当前页课程
Tool: browser_evaluate
Function: () => {
const cards = document.querySelectorAll('.aia_course_card');
const results = [];
for (const card of cards) {
const titleEl = card.querySelector('.ta_mainInfo');
if (!titleEl) continue;
const fullText = titleEl.textContent.trim();
const courseName = fullText.split('学院:')[0].trim();
// 从 React Fiber 提取 courseId
const reactKey = Object.keys(card).find(k => k.startsWith('__react'));
let courseId = null;
if (reactKey) {
try {
const fiber = card[reactKey];
let node = fiber;
for (let i = 0; i < 10 && node; i++) {
if (node.memoizedProps) {
const props = node.memoizedProps;
const id = props.courseId || props.id || (props.course || {}).id || (props.data || {}).courseId || (props.data || {}).id;
if (id) { courseId = String(id); break; }
}
node = node.return;
}
} catch(e) {}
}
if (courseName && courseId) {
results.push({ name: courseName, courseId: courseId });
}
}
return results;
}
步骤 2.4: 翻页并重复提取
Tool: browser_click
Element: "第2页按钮"
Target: 包含文本"2"且不是 tab 的分页按钮
等待 2-3 秒后,重复执行步骤 2.3,直到所有页码遍历完毕。
步骤 2.5: 合并并保存结果
将各页提取的课程列表合并,按 courseId 去重,保存为 JSON 文件供后续使用。
课程 URL 格式
获取到 courseId 后,可直接构造课程详情页 URL:
https://ccnu.ai-augmented.com/app/jx-web/mycourse/{courseId}/task
例如:
- 机器学习:
https://ccnu.ai-augmented.com/app/jx-web/mycourse/6905007502274864505/task - 计算机网络:
https://ccnu.ai-augmented.com/app/jx-web/mycourse/6905007502643963290/task
Step 3: 抓取课程作业详情
目标
进入指定课程的作业任务页面,抓取所有未完成作业(或用户指定的作业)的详情信息,包括:题目、提交格式、提交次数、截止时间等,保存为 Markdown 文件。
重要:此步骤仅在用户明确要求查看/抓取作业时执行,不是 Step 1/2 的自动后续。
Step 4 自动触发:Step 3 执行完毕后,默认自动进入 Step 4(完成作业)。如果用户明确声明"只抓取不完成"、"不要写作业"或"step3执行完不执行完成作业",则跳过 Step 4。
前提条件
- 已完成 Step 1:在命名空间目录生成有效的
xiaoya_auth.json登录态文件 - 已完成 Step 2:在命名空间目录生成
course_map.json课程映射文件(或知道 courseId)
首选方式:直接运行脚本
脚本复用已保存的登录态,自动完成以下流程:
- 从命名空间目录加载
xiaoya_auth.json登录态 - 进入指定课程的
/task页面 - 提取作业列表(默认所有"未完成"状态的作业)
- 逐个点击"进入任务",进入详情页
- 抓取:标题、状态、截止时间、允许提交次数、题目内容、卷面情况、答题说明
- 以作业名为文件名保存为 Markdown 到命名空间目录
# 抓取指定课程的所有未完成作业(默认命名空间)
python scripts/step3_get_tasks.py --course "数据库原理"
# 抓取指定课程的特定作业
python scripts/step3_get_tasks.py --course "数据库原理" --task "第5次 触发器实验"
# 使用 courseId 直接指定课程
python scripts/step3_get_tasks.py --course "6905007883017026816" --task "第3次实验"
# 指定命名空间
python scripts/step3_get_tasks.py --course "数据库原理" --namespace 2024123456
# 完全自定义路径(覆盖命名空间默认值)
python scripts/step3_get_tasks.py --course "数据库原理" --auth ./custom_auth.json --course-map ./custom_map.json --output ./custom_tasks --no-wait
脚本行为说明
| 行为 | 说明 |
|---|---|
| 登录态加载 | 自动读取 namespaces/{namespace}/xiaoya_auth.json |
| 超时处理 | 如无头模式加载页面超时,请添加 --headed 参数使用有头模式 |
| 课程解析 | 从 namespaces/{namespace}/course_map.json 查找课程名对应的 courseId,或直接接受 courseId |
| 作业筛选 | 默认处理状态不是"已完成"/"已提交"的作业;用户指定 --task 时处理匹配的作业 |
| 详情提取 | 进入详情页后提取:标题、状态、时间、题目、卷面情况 |
| 输出格式 | Markdown,保存到 namespaces/{namespace}/tasks/{course_name}/{task_name}.md |
| 注意 | 不勾选"仅关注待完成任务"(保持默认未勾选状态) |
输出文件格式
脚本默认在命名空间目录下创建 tasks/ 子目录,以课程名命名子目录,每个作业保存为一个 .md 文件:
namespaces/
└── 20240000/
└── tasks/
└── 数据库原理/
├── 第3次实验 数据库安全.md
├── 第5次 触发器实验.md
└── ...
每个 Markdown 文件结构:
# 第5次 触发器实验
## 基本信息
- **作业页面URL**: https://ccnu.ai-augmented.com/app/jx-web/course_paper/mycourse/6905007883017026816/6979856198174559548/6979856565905927196/6979856198166170939
- **状态**: 已截止
- **提交状态**: 状态:已提交
- **允许提交:2次**
- **开始时间**: 2026-05-14 08:48:55
- **截止时间**: 2026-05-21 23:59:59
- **结果公布**: 批阅后公布
## 卷面情况
- 共1题(2分)
- 附件题(1题)
## 题目
### 第 1 题
**[附件题 2分]**
实现触发器:实现before触发、after触发 两大类, ...
备选方式:Playwright MCP 手动操作
如果脚本执行失败,或需要在已有 MCP 会话中直接获取作业详情,可按以下步骤操作。
前提条件
已通过 Step 1 的 mcp_auth_inject.js 注入登录态,或 MCP 会话本身已完成登录。
手动工作流
步骤 3.1: 导航到课程任务页
Tool: browser_navigate
URL: https://ccnu.ai-augmented.com/app/jx-web/mycourse/{courseId}/task
等待页面加载完成,确认显示"全部任务"标题和作业列表表格。
步骤 3.2: 提取作业列表
Tool: browser_evaluate
Function: () => {
const rows = document.querySelectorAll('table tbody tr');
const tasks = [];
for (const row of rows) {
const cells = row.querySelectorAll('td');
if (cells.length < 8) continue;
const title = cells[0]?.textContent?.trim();
const status = cells[3]?.textContent?.trim();
const deadline = cells[8]?.textContent?.trim();
if (title) {
tasks.push({ title, status, deadline });
}
}
return tasks;
}
步骤 3.3: 筛选并点击"进入任务"
找到目标作业行,点击"进入任务"按钮(会打开新标签页):
Tool: browser_click
Element: "第5次 触发器实验的进入任务按钮"
Target: 包含"第5次"的行内的"进入任务"按钮
步骤 3.4: 切换到新标签页
Tool: browser_tabs
Action: select
Index: 1 // 新打开的标签页
步骤 3.5: 提取详情页信息
Tool: browser_evaluate
Function: () => {
return {
title: document.title.split('|')[0].trim(),
status: document.querySelector('main')?.innerText?.match(/已截止|进行中/)?.[0] || '',
deadline: [...document.querySelectorAll('aside p')].find(p => p.textContent.includes('截止时间:'))?.textContent?.replace('截止时间:', '').trim() || '',
questions: [...document.querySelectorAll('main span, main div')].filter(el => /^\d+\.$/.test(el.textContent.trim())).map(el => {
const parent = el.parentElement;
const title = [...parent.querySelectorAll('*')].find(c => c.textContent.length > 10 && !c.textContent.includes('['))?.textContent?.trim() || '';
const type = [...parent.querySelectorAll('*')].find(c => c.textContent.match(/^\[[^\]]+题\s*\d+分\]$/))?.textContent?.trim() || '';
return { number: el.textContent.trim().replace('.', ''), title, type };
})
};
}
步骤 3.6: 关闭详情页,回到任务列表
Tool: browser_tabs
Action: close
Index: 1
常见问题
所有作业状态都是"已完成"
如果课程的所有作业都已提交/完成,默认筛选会返回 0 个作业。此时可:
- 使用
--task参数指定特定作业名称,强制抓取该作业详情 - 或确认确实没有未完成的作业
点击"进入任务"后未检测到详情页
- 检查登录态是否过期(重新执行 Step 1)
- 增加等待时间:页面加载可能需要更长时间
- 查看调试截图
xiaoya_step3_error.png
Step 4: 完成作业(分析与生成答案)
执行时机
- 默认行为:Step 3 抓取完作业详情后自动执行
- 跳过条件:用户明确声明"只抓取不完成"、"不要写作业"、"step3执行完不执行完成作业"
目标
读取 Step 3 生成的作业详情 Markdown 文件,分析题目并判断 AI 能否独立完成。在生成答案前后,必要时向用户追问以获取缺失信息,确保答案准确可用。
注意:此步骤为纯 Markdown 内容生成,不需要浏览器自动化脚本。但鼓励与用户进行多轮对话以提高答案质量。
Step 5 执行策略:Step 4 完成后不会自动执行 Step 5(提交作业)。Step 5 涉及不可逆的提交操作,必须用户明确请求才会执行(如"提交作业"、"帮我交一下")。详见下文 Step 5 章节。
完整工作流
读取作业详情 → 预分析 → [追问用户] → 生成答案/说明 → [追问用户] → 定稿保存
阶段一:读取与预分析
在命名空间目录下找到 Step 3 生成的作业详情文件:
namespaces/{namespace}/tasks/{course_name}/{task_name}.md
读取后先做初步判断,按以下维度评估:
| 维度 | 能完成 ✅ | 不能完成 ❌ | 需要追问 ❓ |
|---|---|---|---|
| 题目清晰度 | 描述完整、要求明确 | 只有寥寥数语,如"完成实验三" | 部分清楚但缺细节 |
| 实验操作 | 纯理论、设计、代码题 | 需要实际操作、截图、拍摄 | 理论题但需要指定语言/工具 |
| 技能范围 | 在 AI 能力范围内 | 需要硬件/特定软件环境 | 能力边界,取决于用户补充 |
| 上下文依赖 | 独立可解 | 严重依赖课堂讲义/PPT | 可能依赖,可向用户确认 |
常见不能完成的场景:
- 缺乏上下文:题目只有寥寥数语,如"完成实验三的内容",但没有实验指导书
- 需要截图/照片:要求提交实验截图、运行结果截图、手写拍照等
- 实体操作:需要操作数据库、配置网络、焊接电路等物理操作
- 团队协作:需要小组讨论、分工合作完成的作业
- 主观评价:如"谈谈你的感受"、"结合亲身经历"等需要个人真实经历的题目
阶段二:完成作业前追问(Pre-Completion Questions)
以下情况必须在生成答案前向用户追问,不要直接猜测:
必须追问的场景
-
题目描述模糊/缺少上下文
- 题目只有一句话,如"完成第3章课后习题"
- 引用不存在的内容("根据课堂讲的案例")
- 需要实验指导书/PPT 但作业详情里没提供
追问模板:
这道题描述比较简略,提到"×××",但我没有看到具体的实验指导或详细要求。请问:
- 你有老师发的实验指导书/PPT 吗?可以发给我看看。
- 或者你能补充一下具体要完成哪些内容吗?
-
技术栈/工具不明确
- 编程题没指定语言(Python/Java/C/...)
- 数据库题没指定系统(MySQL/Oracle/SQL Server)
- 实验环境不确定(Windows/Linux、本地/远程服务器)
追问模板:
这道题需要写代码/操作数据库,但作业里没有指定用哪种语言/数据库。请问你们课程要求用:
- Python
- Java
- MySQL
- Oracle
- 其他:_____
另外,如果有学校提供的实验环境或特定版本要求,也请告诉我。
-
格式要求不清楚
- 需要手写拍照还是电子版?
- 代码需要截图还是贴文本?
- 文档有指定模板吗?
追问模板:
作业的提交格式我有些不确定:
- 是直接在网页文本框里填写,还是上传 Word/PDF 附件?
- 如果是附件,学校有统一的实验报告模板吗?
- 代码部分需要截图还是可以直接贴代码文本?
-
多解或有个人发挥空间
- "谈谈你的理解"、"结合实际分析"——需要知道用户的观点或立场
- 设计类题目有多种方案——需要确认用户倾向
追问模板:
这道题比较开放,需要结合你的个人理解/实际经历。为了给你写出更贴合你情况的答案,请问:
- 你对这个问题有没有什么初步想法?
- 或者你希望我从哪个角度展开?(技术角度/理论角度/案例分析...)
可选追问(提升答案质量)
- 如果用户之前提交过同类作业被退回,问清楚扣分原因
- 如果有参考答案/样例,可以问用户是否愿意分享
- 如果用户有课程群里的补充说明,可请用户转发
追问原则:
- 一次追问聚焦 1-2 个最关键的问题
- 给用户选择题(而非开放题)降低回答成本
- 如果用户表示"不知道"或"没有",基于现有信息尽量完成
- 如果用户长时间不回复,基于现有信息推进,不要阻塞
阶段三:生成答案或说明
如果能完成
在同一目录下创建 {task_name}_答案.md:
完整答案文件示例请参考 success_example/第6次 数据库编程_答案.md,该文件包含:
- 分隔线上方:完整解题思路、详细代码、注意事项(供人工审阅)
- 分隔线下方:`` 及上传文件/答题说明列表(供 Step 5 自动解析)
无法完成说明示例
# 第3次实验 数据库安全 — 无法完成说明
## 原因分类
- [ ] 缺乏上下文
- [x] 有实际实验要求(截图/操作)
- [ ] 缺少某种技能
## 具体说明
该作业要求实际操作 MySQL/Oracle 数据库并提交实验截图,包括:
1. GRANT/REVOKE 操作截图
2. 角色管理配置截图
AI 无法访问真实数据库环境,也无法生成实验截图。
## AI 能帮到什么
- 提供完整的 SQL 代码参考(见下文)
- 提供操作步骤文字说明
```sql
-- 任务1.1:基础权限授予
CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'pass123';
GRANT SELECT, INSERT ON school.* TO 'testuser'@'localhost';
## 建议
1. 本地安装 MySQL 8.0
2. 按上述 SQL 逐步执行并截图
3. 将截图整理到 Word 文档上传
阶段四:完成作业后追问(Post-Completion Questions)
答案生成并保存后,主动向用户确认以下事项:
必问项
-
答案是否符合预期
答案已生成并保存到:
{文件路径}请你看一下内容是否符合要求?如果有需要调整的地方(比如代码语言不对、缺了某部分、需要更详细),直接告诉我。
-
是否需要补充截图/操作指导
这份答案里如果有需要实际运行代码或截图的部分,我可以给你写一份"手动操作步骤指南",方便你照着做。需要吗?
选问项(根据作业类型)
-
如果是编程题:
代码里我用的是 {语言},如果你学校要求用其他语言,告诉我,我直接给你转写。
-
如果是论述题:
论述部分我写了大约 {字数} 字,如果你需要更简短/更详细的版本,或者想加入你自己的观点,告诉我。
-
如果涉及提交:
作业平台支持直接粘贴文本还是需要上传文件?需要我帮你整理成 Word/PDF 格式吗?
如果用户不回复
- 不要反复追问超过 2 次
- 告知用户:"答案已保存,你可以随时发消息让我修改。"
- 保持待命状态,用户反馈后立即修改
批量处理多个作业
当 Step 3 抓取了多个作业时:
-
逐个处理:对每个作业依次执行上述 4 个阶段
-
汇总汇报:全部处理完后,给用户一个汇总列表:
作业处理结果汇总: 1. 第5次 触发器实验 → ✅ 已生成答案 2. 第3次实验 数据库安全 → ❌ 需要手动完成(已生成操作指南) 3. 第2次实验 SQL查询 → ❓ 需要你确认编程语言... -
批量追问:如果有多个作业都需要用户补充信息,合并成一次追问,避免用户疲劳
Step 5: 提交作业
执行时机
- 默认行为:不自动执行。Step 4 完成后不会自动进入 Step 5
- 触发条件:用户明确请求提交作业,如:"提交作业"、"帮我把作业交一下"、"step5"、"交卷"
- 前置要求:必须已完成 Step 3(生成题目.md)和 Step 4(生成答案.md)
安全警告:Step 5 会实际向平台提交作业,消耗提交次数。脚本内置安全检查(剩余1次提交时停止),但仍建议用户先审阅答案内容再提交。
首选方式:直接运行脚本
脚本复用已保存的登录态,自动完成以下流程:
- 读取
题目.md提取:作业页面URL、允许提交次数 - 安全检查:如果只剩1次提交机会,立即停止并提醒用户
- 读取
答案.md提取:上传文件路径、答题说明内容 - 用 Playwright 打开作业提交页面
- 填写答题说明(如有)
- 上传附件(如有)
- 点击右下角【提交】按钮
# 提交指定课程的特定作业(默认需要用户确认)
python scripts/step5_submit_task.py --course "Java语言程序设计" --task "Access密码破解"
# 干跑模式:模拟操作但不实际提交(测试用)
python scripts/step5_submit_task.py --course "Java语言程序设计" --task "Access密码破解" --dry-run
# 跳过确认直接提交(危险!仅用于自动化场景)
python scripts/step5_submit_task.py --course "Java语言程序设计" --task "Access密码破解" --yes
# 显示浏览器窗口(调试)
python scripts/step5_submit_task.py --course "Java语言程序设计" --task "Access密码破解" --headed --dry-run
# 指定命名空间
python scripts/step5_submit_task.py --course "Java语言程序设计" --task "Access密码破解" --namespace 2024123456
脚本行为说明
| 行为 | 说明 |
|---|---|
| 登录态加载 | 自动读取 namespaces/{namespace}/xiaoya_auth.json |
| 超时处理 | 如无头模式加载页面超时,请添加 --headed 参数使用有头模式 |
| 输入文件 | 读取 tasks/{course_name}/{task_name}.md(题目)和 {task_name}_答案.md(答案) |
| 安全检查 | 剩余提交次数 ≤ 1 时强制停止,提醒用户手动检查 |
| 用户确认 | 默认提交前要求输入 yes 确认(可 --yes 跳过) |
| 答题说明 | 自动识别 textarea/contenteditable 输入框并填写 |
| 附件上传 | 自动识别上传按钮/文件输入框并上传 答案.md 中指定的文件 |
| 提交按钮 | 定位包含"提交"文本的按钮,点击并处理确认弹窗 |
答案文件格式要求
{task_name}_答案.md 必须以分隔线分为上下两部分:
- 分隔线上方:完整答案内容(供人工审阅)
- 分隔线下方:提交清单(供脚本自动执行)
分隔线格式:
================ 提交清单(Step 5 自动解析以下内容) ================
脚本只解析分隔线下方的内容,自动匹配:
上传文件
- 识别
## 上传文件/## 附件下方的文件路径列表 - 支持绝对路径或相对于答案文件目录的相对路径
- 文件不存在时跳过并提示
答题说明
- 识别
## 答题说明处填写/## 答案下方的文本内容 - 自动填入网页的答题说明文本框
示例(分隔线下方):
================ 提交清单(Step 5 自动解析以下内容) ================
## 上传文件
Main.java
report.pdf
## 答题说明处填写
密码:CCNUcs8318
步骤:从 MDB 文件固定位置读出...
备选方式:Playwright MCP 手动操作
如果脚本执行失败,或需要在已有 MCP 会话中直接提交,可按以下步骤操作。
前提条件
- 已通过 Step 1 加载登录态
- 已确认
题目.md中有有效的作业页面URL和允许提交次数 ≥ 2 - 已阅读
答案.md中分隔线(================ 提交清单)下方的内容,知道要上传什么文件、填写什么答题说明
手动工作流
步骤 5.1: 导航到作业提交页面
Tool: browser_navigate
URL: {从 题目.md 中提取的作业页面URL}
等待页面加载完成,确认显示题目内容和输入区域。
步骤 5.2: 填写答题说明
找到答题说明文本框(通常是 textarea 或 contenteditable 区域):
Tool: browser_type
Target: 答题说明文本框(placeholder 含"请输入"或"答题说明"的元素)
Text: {从 答案.md 中提取的答题说明内容}
步骤 5.3: 上传附件(如有)
Tool: browser_click
Element: "上传附件按钮"
Target: 包含文本"上传"的按钮或文件上传区域
在弹出的文件选择对话框中选择要上传的文件(MCP 可能不支持直接选择本地文件,此时需告知用户手动选择)。
注意:如果平台使用隐藏的文件 input 元素,可直接通过 browser_evaluate 设置其 files 属性。
步骤 5.4: 点击提交按钮
提交按钮通常位于页面右下角:
Tool: browser_click
Element: "提交按钮"
Target: 包含文本"提交"的按钮(通常在页面右下角)
处理可能的确认弹窗:
Tool: browser_click
Element: "确认提交"
Target: 弹窗中包含文本"确认"或"确定"的主按钮
步骤 5.5: 验证提交结果
Tool: browser_evaluate
Function: () => {
// 检查是否出现成功提示
const success = document.body.innerText.includes('提交成功') ||
document.body.innerText.includes('已提交');
return { success, url: window.location.href };
}
安全策略与风险防控
| 风险场景 | 防护措施 |
|---|---|
| 误提交错误答案 | 默认要求用户输入 yes 确认;支持 --dry-run 先模拟 |
| 耗尽提交次数 | 剩余1次时强制停止,提醒用户手动检查 |
| 登录态过期 | 提交前检查页面是否被重定向到登录页 |
| 文件路径错误 | 上传前检查文件是否存在,不存在则跳过并提示 |
| 提交按钮找不到 | 截图保存 xiaoya_step5_error.png,供用户手动排查 |
Step 3-4-5 完整工作流示例
# Step 3: 抓取作业详情
python scripts/step3_get_tasks.py --course "Java语言程序设计" --task "Access密码破解"
# → 生成 namespaces/20240000/tasks/Java语言程序设计/Access密码破解.md
# Step 4: 分析并生成答案(Agent/用户操作)
# → 生成 namespaces/20240000/tasks/Java语言程序设计/Access密码破解_答案.md
# Step 5: 提交作业(用户明确请求后执行)
python scripts/step5_submit_task.py --course "Java语言程序设计" --task "Access密码破解" --dry-run
# → 确认模拟操作无误后,去掉 --dry-run 正式提交
Step 6: 取消提交 / 重新提交
执行时机
- 默认行为:取消已提交的作业(撤销提交状态,回到可编辑状态)
- 触发条件:用户明确请求"取消提交"、"重新答题"、"撤回作业"、"step6"
- 附加模式:添加
--resubmit参数可在取消后紧接着重新提交 - 前置要求:必须已完成 Step 3(生成题目.md),且作业当前处于已提交状态
注意:默认情况下,取消提交后页面会保留此前填写的内容(答题说明、附件),无需重新填写。如需清空内容,请添加
--clear参数。右下角按钮会从"重新答题"变为"第N次提交"。
首选方式:直接运行脚本
脚本复用已保存的登录态,自动完成以下流程:
- 读取
题目.md提取作业页面URL - 打开作业页面,智能检测右下角按钮状态:
- "重新答题" → 已提交,执行取消流程
- "提交" / "第N次提交" → 未提交,提示无需取消(如
--resubmit则直接提交)
- 点击"重新答题" → 等待确认弹窗
- 等待弹窗倒计时(通常3-5秒,按钮从
确定(X)变为确定) - 点击弹窗"确定",页面刷新回到可编辑状态
- 如指定
--resubmit,紧接着自动执行 Step 5 提交逻辑
# 默认:仅取消已提交的作业(保留内容)
python scripts/step6_resubmit_task.py --course "Java语言程序设计" --task "Access密码破解"
# 取消后清空内容(答题说明和附件)
python scripts/step6_resubmit_task.py --course "Java语言程序设计" --task "Access密码破解" --clear
# 取消后紧接着重新提交(保留原内容)
python scripts/step6_resubmit_task.py --course "Java语言程序设计" --task "Access密码破解" --resubmit
# 取消 + 清空 + 重新提交(完全重置后提交新答案)
python scripts/step6_resubmit_task.py --course "Java语言程序设计" --task "Access密码破解" --clear --resubmit
# 显示浏览器窗口(调试)
python scripts/step6_resubmit_task.py --course "Java语言程序设计" --task "Access密码破解" --headed
# 指定命名空间
python scripts/step6_resubmit_task.py --course "Java语言程序设计" --task "Access密码破解" --namespace 2024123456
脚本行为说明
| 行为 | 说明 |
|---|---|
| 登录态加载 | 自动读取 namespaces/{namespace}/xiaoya_auth.json |
| 超时处理 | 如无头模式加载页面超时,请添加 --headed 参数使用有头模式 |
| 状态检测 | 通过右下角按钮文本判断:"重新答题"=已提交,"提交"/"第N次提交"=未提交 |
| 弹窗处理 | 自动等待倒计时结束(轮询检查按钮从 disabled → enabled) |
| 倒计时容错 | 最多等待10秒,超时仍尝试点击 |
| 清空内容 | --clear 时,取消提交后清空 textarea 和已上传附件(默认保留) |
| 重新提交 | --resubmit 时在同一浏览器会话中继续执行填写/上传/提交 |
| 提交按钮兼容 | 支持"提交"、"第2次提交"、"第3次提交"等变体 |
备选方式:Playwright MCP 手动操作
如果脚本执行失败,可按以下步骤手动操作。
手动工作流
步骤 6.1: 导航到作业页面
Tool: browser_navigate
URL: {从 题目.md 中提取的作业页面URL}
步骤 6.2: 检测右下角按钮
确认页面右下角显示 "重新答题" 按钮(绿色,表示已提交)。
步骤 6.3: 点击"重新答题"
Tool: browser_click
Element: "重新答题按钮"
Target: 页面右下角包含文本"重新答题"的按钮
步骤 6.4: 等待弹窗倒计时
弹窗会出现确认提示和两个按钮:
- 确定(3) / 确定(2) / 确定(1) —— 倒计时中,disabled
- 取 消 —— 可随时点击取消
等待倒计时结束,按钮变为 "确定" 并变为可用状态。
步骤 6.5: 点击弹窗"确定"
Tool: browser_click
Element: "弹窗确定按钮"
Target: 弹窗中包含文本"确定"的主按钮(绿色)
步骤 6.6: 等待页面刷新
页面会自动刷新,状态从"已提交"变为"正在答题...",右下角按钮变为"第N次提交"。
步骤 6.7: (可选)重新提交
如需重新提交,按 Step 5 手动工作流执行填写、上传、提交。
Step 3-6 完整工作流示例
# Step 3: 抓取作业详情
python scripts/step3_get_tasks.py --course "Java语言程序设计" --task "Access密码破解"
# Step 4: 分析并生成答案(Agent/用户操作)
# → 生成 Access密码破解_答案.md
# Step 5: 首次提交
python scripts/step5_submit_task.py --course "Java语言程序设计" --task "Access密码破解"
# Step 6: 发现答案有误,取消后重新提交
python scripts/step6_resubmit_task.py --course "Java语言程序设计" --task "Access密码破解" --resubmit