Skill 方案 S5 · 文档与表格批量转换
一句话:给一个目录,自动认格式、抽正文、提图片、转公式、保住表格结构,并用行列数与公式数做机器校验;5 次大批量转换消耗的 0.32 亿 token 可压到 0.05 亿。
一、为什么需要它
两个月里五类转换反复出现,单次成本都极高,因为每一次都是"边转边试、转完再逐页核对"。
| 任务 | 来源 | token | 主要难点 |
|---|---|---|---|
| D01 PDF→MD | 2026 考研数学(一)真题解析 | 651 万 | 公式有上下标、分式、跨页续表 |
| D02 CSV→可视化 | B 站 UP 主全量视频清单 | 915 万 | 播放量含"万"、分类靠语义判断 |
| D13 Word 表格→MD | 表格法.doc,202 次工具调用 | 2130 万 | 合并单元格、边框、图片混排 |
| 表格 grid↔Markdown | 笔记站编辑器双向转换 | 含在上面 | 对齐、转义竖线、代码围栏内不转 |
| 零散 docx/pdf | 各类资料 | 其余 | 格式识别靠猜 |
| 指标 | 数值 |
|---|---|
| 转换批次 | 5 次 |
| 累计 token | 0.32 亿 |
| 平均每批 | 640 万 |
| 平均工具调用 | 60–200 次 |
| 一次通过率 | 接近零 |
核心问题不是"转不出来",而是没有验收口径:D01 转完没有一处标记"这题公式可能识别错",用户只能自己逐题验;D13 的 21M token 里,很大一部分花在反复回读原文件比对表格边框。
二、触发词与边界
| 触发词 | 不适用场景 |
|---|---|
| 「把这个 PDF 转成 Markdown」「转 md」 | 只读不转、只要摘要 → 用 pdf skill 直读 |
| 「Word 表格转 Markdown」「表格提取」 | 要生成新 xlsx → 走 sheet-generation |
| 「CSV 做成可视化」「出图」 | 要改现有表结构 → 走 sheetagent |
| 「图片里的表格」「扫描件」 | 纯图片 OCR 且无文字层 → 先声明降级 |
| 「grid 转 md」「md 转 grid」 | 超大表(> 5000 行)→ 分片处理 |
边界:转换不改语义。数字、单位、题号一律原样保留;识别不确定的内容加 [待核对·第X页] 标记,绝不"顺手修正"。
三、输入与前置检查
先认格式,不要看扩展名。 改过后缀的 .doc 其实是 docx、.pdf 其实是图片扫描件,这两种在本机都出现过:
node -e "
const fs=require('fs');
for(const f of process.argv.slice(1)){
const b=Buffer.alloc(8); const fd=fs.openSync(f,'r'); fs.readSync(fd,b,0,8,0); fs.closeSync(fd);
const hex=b.toString('hex');
const kind = hex.startsWith('25504446')?'pdf' : hex.startsWith('504b0304')?'zip/docx/xlsx' : hex.startsWith('d0cf11e0')?'ole/doc(xls)':'unknown';
console.log(f, kind, hex);
}" "C:/Users/cag/xwechat_files/.../表格法.doc"
| 判定 | 处理 |
|---|---|
pdf | 先看有没有文字层;没有则按扫描件降级,逐页转图 + OCR |
zip/docx/xlsx | 用 docx / xlsx skill 抽取,按 XML 结构取表 |
ole/doc(xls) | 老格式,先另存为 docx/xlsx 再转,不要强解 |
unknown | 停下来问,不猜 |
三项环境检查:目标目录可写、可用磁盘 > 2 GB(图片提取最占空间)、notes 下的 TS 工具链可用(表格转换依赖 lib/md-tables.ts,跑 npx tsx --version 验证)。
四、执行步骤
步骤 1 · 建工作目录,固定产物结构。
mkdir -p out/<docname>/{assets,raw}
raw/ 放抽取的中间文本,assets/ 放提取的图片,最终 md 与 assets/ 同级,保证 Typora 打开即见。
步骤 2 · 抽取正文与图片。 用对应 skill 完成,不手写临时解析脚本:PDF 走 pdf skill,Word/Excel 走 docx / xlsx skill。抽取时同步导出图片到 assets/,命名统一为 img-<页码或序号>-<原文件名>,并在正文原位置插入 。
步骤 3 · 公式处理。 PDF 的文字层会打散公式结构,这是 D01 花掉 651 万 token 的直接原因。规则三条:
- 行内统一
$...$,独立公式统一$$...$$,不混用(用户原话给的是"$$或者$$$$",二选一等于没规定,必须收敛成一条)。 - 分式一律写
\frac{}{},不要用a/b冒充。 - 识别不出的公式原样保留文本并加
[待核对·第3页],不许编一个看起来合理的式子。
步骤 4 · 表格保真。 Markdown 管道表无法表达合并单元格,遇到就拆:跨行合并拆成重复值,跨列合并写成 列A / 列B 并在表下加一行说明。转换用现成函数,不要另写:
cd "C:/Users/cag/WorkBuddy/2026-10-11-06-00-16/notes" && npx tsx scripts/md-tables.test.ts
这个单测覆盖基本表、无首尾竖线、对齐、转义竖线、跳过代码围栏与引用块、多表偏移、grid 转换七类场景,跑通说明 scanPipeTables / pipeTableToGrid 可用。
步骤 5 · 机器校验。 转完必须用数字对账,不靠眼睛:
node -e "
const fs=require('fs'); const md=fs.readFileSync(process.argv[1],'utf8');
const rows=(md.match(/^\|.*\|$/gm)||[]).length;
const seps=(md.match(/^\|[\s:|-]+\|$/gm)||[]).length;
const inline=(md.match(/\\\$[^\\\$\\n]+\\\$/g)||[]).length;
const block=(md.match(/\\\$\\\$/g)||[]).length/2;
const imgs=(md.match(/!\\\[[^\\\]]*\\\]\\([^)]+\\)/g)||[]).length;
console.log(JSON.stringify({tableLines:rows, tables:seps, inlineMath:inline, blockMath:block, images:imgs}));
" out/<docname>/<docname>.md
把结果与原文件的表数、图数、公式数逐项对照;任何一项差超过 1,回到步骤 2 定位是哪一页丢的。
五、输出与验收
{"file":"D13.md","pages":42,"tables":17,"tableRows":386,"images":63,"inlineMath":0,"blockMath":0,"pendingCheck":4}
三条硬断言:
- 表格数与原文件一致,且每张表的列数一致;行列数不符必须标出具体表序号。
- 图片数 ==
assets/下实际文件数,正文里不得有指向不存在文件的链接。 [待核对]标记数必须写进最终报告;为 0 时要能说明"确实全部识别成功",不能默认没有。
六、失败处理与回滚
| 现象 | 根因 | 动作 |
|---|---|---|
| 转出来全是图 | PDF 无文字层 | 降级 OCR,并在报告首行声明 |
| 公式变成乱码 | 字体子集缺 ToUnicode | 该页转图,人工补,正文留 [待核对] |
| 表格错列 | 合并单元格 | 拆表 + 表下说明,不强行塞进一行 |
| 图片丢失 | 抽取时未导出 | 重跑步骤 2,只补图片不动正文 |
| 转换后打不开 | 编码不是 UTF-8 | 统一 -Encoding utf8 重写 |
回滚:转换是全量重生成,不留中间态;原始文件只读,永不修改。产物出错就删 out/<docname>/ 重跑——这里用 Move-Item 挪到 _trash,避开 safe-delete 钩子。
七、收益测算
| 项目 | 现状 | 改造后 |
|---|---|---|
| 单批 token | 640 万 | 100 万 |
| 批次 | 5 次 | 5 次 |
| 累计 | 0.32 亿 | 0.05 亿 |
| 核对方式 | 逐页人工比对 | 行列数/图数/公式数对账 |
| 返工 | 几乎每批都返 | 差异定位到页 |
省的是"回读原文件比对"这一层:D13 的 202 次工具调用里,超过一半是在反复读原文档确认表格边界。节省 0.27 亿,约 84%。