Icve Exam Extract
用 Kimi WebBridge 从 icve/zjy2 的 viewExam 考试回看页(如 https://zjy2.icve.com.cn/study/viewExam?...)提取所有"红色背景=做错"的题目,并整理成可直接粘贴进 Word 的结构化格式。 触发场景:用户给出 icve 的 viewExam 类 URL,要求"提取错题/做错的题/红题",或要整理考试错题本。From its SKILL.md
npx -y skills add BitsAstro/Icve-Exam-ExtractAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.3 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it
icve viewExam 错题提取(Kimi WebBridge)
通过 Kimi WebBridge 操控用户已登录的浏览器(含隐私窗口),读取答题卡红色(错)题号并抓取每题完整内容。
前置条件
- Kimi WebBridge daemon 运行在
http://127.0.0.1:10086(二进制C:\Users\a\.kimi-webbridge\bin\kimi-webbridge.exe)。 - 浏览器扩展已连接(
curl http://127.0.0.1:10086/status看extension_connected)。 - 用户已在浏览器(隐私窗口需扩展开启"隐私模式时启用")打开目标 viewExam 页面并登录。
调用约定(Windows)
- 每个请求体写成 JSON 文件(避免 PowerShell 行内传参把中文变
?),用curl.exe -s -X POST http://127.0.0.1:10086/command -H "Content-Type: application/json" --data-binary "@<文件>"发送。 - 请求体必须带顶层
"session"字段(同一任务固定一个名字,如exam-extract)。
步骤
1. 借用用户当前标签(必须传真实 URL)
find_tab 即使 active:true 也要求 url 匹配已打开的标签——直接传用户给的 URL + active:true:
{"action":"find_tab","args":{"url":"<用户给的viewExam完整URL>","active":true},"session":"exam-extract"}
2. 合并脚本:扫红色错题号 + 抓题目数据
用 evaluate 执行下面 JS(一次性返回结构化 JSON):
- 红色(错) vs 蓝色(对) 背景色挂在答题卡
.tagItem的 computed style 上:错=rgb(159,19,18)、对=rgb(21,51,152)。(注意:链接本身 background 是 transparent,要查.tagItem元素!) - 每题是
.subjectDet,字段:题号/题型/分值.xvhao;题干.seeTitle .htmlP.ql-editor;用户得分.titleBox .el-tag;选项.optionList下.el-radio__label(单选)/.el-checkbox__label(多选);正确答案.answer;解析.analysis .htmlP.ql-editor。
(() => {
const tags = Array.from(document.querySelectorAll('.tagItem'));
const wrong = [];
for (const tag of tags) {
const txt = (tag.textContent || '').trim();
const n = Number(txt);
if (!Number.isInteger(n) || n < 1 || n > 500) continue;
const cs = getComputedStyle(tag);
const bg = cs.backgroundColor;
const m = bg.match(/\d+/g);
const p = m ? [+m[0], +m[1], +m[2]] : null;
if (p && p[0] > 120 && p[1] < 100 && p[2] < 100) wrong.push(n);
}
const wset = new Set(wrong);
const subs = Array.from(document.querySelectorAll('.subjectDet'));
const out = [];
for (const sub of subs) {
const xvhao = sub.querySelector('.xvhao');
if (!xvhao) continue;
const mm = (xvhao.textContent || '').match(/(\d+)/);
if (!mm) continue;
const num = parseInt(mm[1], 10);
if (!wset.has(num)) continue;
const header = (xvhao.textContent || '').trim();
const stemEl = sub.querySelector('.seeTitle .htmlP.ql-editor');
const stem = stemEl ? stemEl.textContent.trim() : '';
const tagEl = sub.querySelector('.titleBox .el-tag');
const userScore = tagEl ? tagEl.textContent.trim() : '';
const optLabels = sub.querySelectorAll('.optionList .el-radio__label, .optionList .el-checkbox__label');
const options = Array.from(optLabels).map(o => o.textContent.trim());
const ansEl = sub.querySelector('.answer');
const correctAnswer = ansEl ? ansEl.textContent.trim() : '';
let analysis = '';
const anaEl = sub.querySelector('.analysis .htmlP.ql-editor');
if (anaEl) analysis = anaEl.textContent.trim();
if (!analysis) { const a2 = sub.querySelector('.analysis'); if (a2) analysis = a2.textContent.replace('解析:','').replace('解析:','').trim(); }
out.push({num, header, stem, userScore, options, correctAnswer, analysis});
}
out.sort((a,b)=>a.num-b.num);
return JSON.stringify({wrongCount: wrong.length, count: out.length, questions: out});
})()
写 JSON 文件时注意:JS 里的
/\d+/g、/(\d+)/在 JSON 字符串中反斜杠要写成\\d。
3. 格式化输出(Python)
curl 返回包在 {"ok":true,"data":{"type":"string","value":"<内层JSON>"}},先取 data.value 再 json.loads。
- 题头解析坑:Python 原始字符串
r'...'里的\u3010不会被解释成【。改用header.find('【')按括号切分最稳:def parse_header(header): i1=header.find('【'); i2=header.find('】'); i3=header.find('('); i4=header.find(')') if i1>=0 and i2>i1 and i3>i2 and i4>i3: return header[:i1].strip().rstrip('.'), header[i1+1:i2].strip(), header[i3+1:i4].strip() return None,None,None - 输出模板(每题间空一行):
[题号].【[题型]】([分值]) [题干] [用户得分] 分 [选项A内容] [选项B内容] ... 正确答案: [答案] 解析:[解析] - 写出
<阶段>测试_错题整理.txt(UTF-8)。
收尾
- 删掉中间临时 JSON(请求体、data 文件),保留格式脚本和最终 txt。
- 若多阶段,各自成文件;可后续合并或导出 Word/PDF。
What ships with it: 3 files
11.2 KB alongside SKILL.md
- .gitignore4.5 KB
- LICENSE1.0 KB
- README.md5.7 KB
Gives 0 of the 12 instructions most education skills give in ~1.6k tokens
Counted across 169 of the 171 authors here whose files we hold, read 2026-08-07
- Search for existing resources before creating new onesin 7 of 169, across 3 files
- Check tool responses for errors before proceedingin 7 of 169, across 3 files
- Reduce request frequency on rate limit errorsin 7 of 169, across 3 files
- Confirm connection status is ACTIVE before running workflowsin 7 of 169, across 3 files
- Execute prerequisite steps first in workflowsin 7 of 169, across 3 files
- Handle pagination by fetching until exhaustedin 7 of 169, across 3 files
- Re-authenticate if the connection expiredin 6 of 169, across 2 files
- Always call RUBE_SEARCH_TOOLS first to get schemasin 6 of 169, across 2 files
- Pass strictly schema-compliant tool argumentsin 6 of 169, across 2 files
- Run the skill generator if the shared file is missingin 6 of 169, across 3 files
- Create the coursein 5 of 169, across 3 files
- List enrolled studentsin 5 of 169, across 3 files
Said here and by no other author read
- check extension connection status via curl
- use the provided url to find the active browser tab
- keep the same session name for all requests
- write each request body to a json file
- send requests using curl.exe with the data binary flag
- inject javascript to extract all red questions
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.