ARTICLE · 1285800
迭代篇:Office 三件套换心手术——Excel 体检 217 秒变 40 秒,Word 与 PPT 把 Python 读写、手搓 XML 一起换掉
上一轮给八套插件 Skill 换"知识底座",我在结尾写了句:底座换了,活儿还是那些活儿。 这一轮,手伸得更里面一点——Excel、Word、PPT 三套"实时操作"Skill,换的是引擎。 把 Python 生态里那套 openpyxl 的读写换掉,把从零手搓 XML 的代码整段删掉,把 COM 压到只剩非它不可的活,心脏换成微软官方的 OpenXML SDK。 一台手术做完:Excel 体检 217 秒变 40 秒,Word 的重建 184 秒变 0.04 秒,PPT 从 120 页到 400 页都是 1 秒出头。 但真正难的不是快,是快完之后,还敢不敢签字。
一、先交代:我动了最不该动的地方
先摆名字,免得对不上号:
- excel-expert:Excel 实时操作,版本 4.34.1;
- word-expert:Word 实时操作,版本 3.0.0;
- ppt-expert:PPT 实时操作,版本 3.0.0。
三套 Skill,从发布到现在,功能上没什么大动作。这一轮改的是一句更根本的话:AI 到底怎么读、怎么写这两个文件。
这件事,以前有三种干法,我都干过:
| 微软官方 OpenXML SDK |
说白了:前三种都在"模拟"文件,第四种是"按文件的定义"在读写文件。
这不是语言崇拜。不是"C# 比 Python 高级"这种话,我从来不信。
是三个硬指标:
1. 语义完整度:OpenXML SDK 是 schema 的实现者,DocumentFormat.OpenXml 里每一个类,都是文件规范里那一个元素;openpyxl 是第三方解释,解释不到的地方就不存在。
2. 回写保真度:SDK 保存时没被碰过的 part 字节不动;PRESENTATIONML 里那堆主题、版式、动画时间轴,能原样活着。openpyxl 一写回,图表和透视就拜拜了。
3. 速度:没有解释器启动,没有跨进程封送,SAX 流式一次过。同一份文件,量级差是真的。
顺手报个总账:这一轮三套 Skill 里,新引擎的 C# 代码 9,464 行(Excel 1,870 / Word 3,514 / PPT 4,080),加一套微软官方 SDK 全量 API 注释索引(4,314 个类型、33,945 条成员,外加 Excel 28 篇、Word 63 篇、PPT 60 篇官方 how-to)。
先说清楚:为什么要动这个?
二、这次不是"我觉得慢",是我自己被绊了一跤
这轮手术的起因很具体,不是技术洁癖。
我自己拿 excel-expert 去实操了一个真表:5.8MB、20 张工作表、18.5 万个公式格。
然后我盯着屏幕,看了 4 分半钟。
没有任何输出。没有进度,没有心跳,光标都不动。我一度以为它死了,去任务管理器看了一眼——进程活着,CPU 在跑,就是什么都不告诉我。
最后它出结果了:总分 0,等级 L0。
一张 20 张表的簿子,17 张是好表,3 张是历史遗留。它给我判了"全局崩坏"。
那一刻我不是生气,是羞愧。这东西是我写的,我拿它去给客户体检,客户会怎么想?他会觉得这个 AI 在胡说八道,然后关掉,再也不打开。
所以这一轮,两件事一起动:
- 读文件的方式(性能),
- 打分的标准(可信度)。
先说性能。因为性能不是参数,是耐心——你让用户等四分钟,他就不会再给你第二次机会。
病根找到了三个
第一个:COM 的"类型绑定税"。
pywin32 是动态绑定,每次遇到一个新的 Range 代理,都要重新问一遍"你这个对象有哪些成员",一次 5-15 毫秒。听起来不多,但你乘上几千次呢?
更阴的是,这个税不是固定的。同一台机器,Excel 忙的时候,单次往返能放大到 300-500 毫秒。也就是说,你的体检耗时,取决于 Excel 当时的心情。
第二个:SpecialCells 把一张表拆成了一地碎片。
我原来读公式,用 SpecialCells(xlCellTypeFormulas) 一次拿到所有公式格,然后按 Area 逐个读。听起来很聪明,对吧?
实测:一张表被拆成了 1,316 个碎 Area。每个 Area 读一次,光绑定税就是 44 秒。
改成"采样区一次性读回 Formula 网格,再用 = 前缀自己分类"——44 秒变 0.6 秒,而且算出来的公式格数、重复模式、混合列,逐字段和旧口径完全相等。
第三个:逐行探测隐藏行列。
400 次 COM 往返,2.7 秒。改成"整块探测 + 二分下钻 + 64 次预算"——0.5 秒。
三个病根,同一个性质:调用次数。不是算法不够聪明,是你不该跟另一个进程说那么多话。
三、第一刀:把 openpyxl 从主路上请下来
先给 openpyxl 一句公道话:它是好东西。改几个单元格的值、读一下表头、做个离线脚本,又快又省事。
但它不适合干"体检"这件事。
体检要看什么?值、公式、错误值、数字格式、样式用量、主题色、合并、超链接、数据验证、自动筛选、保护、智能表、定义名称、连接……这份清单里,一半以上 openpyxl 给你的是"半成品":主题色给你个索引,tint 要自己按 HLS 算;列级格式和格级格式的差别,它不告诉你;带图表的簿子一写回,图表就没了。
所以这一轮,Excel 侧立了一张三引擎编排表——注意,是编排,不是替换:
| cs-ooxml | ||
| COM | ||
.xls 转 .xlsx | COM | |
| COM 轻尾段 | ||
| cs-ooxml | ||
| openpyxl |
你看第 2 行和第 4 行——COM 没被赶走,它只是被赶回了它该待的地方。
以前让它干体检:一个格子一个格子地问,问到天荒地老。现在它只干两件别人干不了的活:替你现在动手,和看那些只有运行时才知道的事。
换完之后,账是这么算的
- 全量体检(5.8MB、20 表、18.5 万公式格):COM 全路径 217 秒 → 文件级 39-50 秒,4.4-5.5 倍;
- 格式扫描(大簿):40-77 秒 → 6-9 秒;
- 单表公式画像:44 秒 → 0.6 秒;
- 隐藏行列探测:2.7 秒 → 0.5 秒。
但快不是重点。快完之后,结果对不对,才是重点。
所以我做了一件事:两条路同时跑,逐条对拍。
同一份簿子,文件级引擎和原来的 COM 路径,各出一份体检报告:
- issues 总数:76 / 76,一致;
- 逐条内容:75 / 76 完全一致;
- 评分:同为 76 分、L1;
- 唯一的残差:一条 number_format_missing 的严重度,COM 判 P1,文件级判 P2。
为什么会有这一条?因为 COM 读的是"列级格式",文件级算的是"逐格多数票"。当某一列出现"列级格式 ≠ 格级格式"时,两种口径都对,但说的不是同一件事。
我没有把这条抹平。我在报告里把它写清楚了,还给了 --engine com 的降级通道。 一个敢交付的体检,不是"我的数一定对",是"我知道我的数从哪来、差在哪"。
四、第二刀:Word 和 PPT,把"手搓 XML"整段删掉
Excel 那边是"从 Python 换到 SDK",Word 和 PPT 这边更狠——从手搓 XML 换成 SDK,删掉的是代码,不是功能。
先看删了什么:
- Word:fl_docx_ops(读侧全部)、fl_edit(9 个写动作)、cs_par_compare(对拍脚本)——删了;
- PPT:fl_ooxml_ops、fl_ooxml_write、fl_timing_ops、om_smartart_file、fl_timing_frag、cs_par_compare——删了。
这些文件名你可能陌生,但它们的活你熟悉:改页边距、换字体、插目录、复制一页、改动画、注入 SmartArt、写节……以前全是 Python 字符串拼 XML,一个标签一个标签拼出来的。
为什么要删?
不是因为它不work——它work了好几个版本。是因为它每次都在赌。
手搓 XML 的死穴,我给你摆三个,全是真踩过的:
第一,属性改一个不够,得改三个。
你想把某段的字体改成宋体,写了:
<w:rPr>
<w:rFontsw:ascii="TimesNewRoman" w:hAnsi="TimesNewRoman" w:eastAsia="宋体"/>
</w:rPr>
看起来对。但原段落上还挂着 w:eastAsiaTheme="minorEastAsia"——主题引用优先级高于显式字体。
结果:文档里字体没变。你改了,它没生效,而且不报错。
这就是 Word 3.0 实测清单里的一条:改中文字体,必须把主题引用清掉。
第二,目录装域,不能替换段落。
老版本有个潜伏六版的 bug:插目录时,把整段 Range 交给 TOC.Add,Word 会用目录替换那一段——文档第一个标题,往往是文档标题或章标题,整段消失。
旧冒烟测试只断言"返回值里有 entries 键",六个版本都没抓到。后来加了一条"目录文本应含『绪论』",上线当天就把它逮住了。
新引擎里,这条教训变成了纪律:新插段落装域,绝不替换已有段落。
第三,PPT 里图表的那本"内嵌工作簿",手拼几乎不可能。
PPT 的图表数据存在哪?存在 chart part 里嵌着的一个 xlsx 包里。你要改图表数据,等于要在 PPT 包里面,再造一个合法的 xlsx 包——工作簿、工作表、共享字符串、样式,一样不能少。
以前是硬拼。现在:用 SpreadsheetDocument 的 DOM 重建。 该有的东西,SDK 一样给你补齐。
Word 的新心脏:读 3 个 op,写 20 族
WordLab.exe——net462 + DocumentFormat.OpenXml 3.1.1,预编译产物直接入库,你的机器零安装(.NET Framework 4.6.2 是 Win10/11 自带的)。
读侧就三个 op:
- inspect:包清单 + 逐 part SHA1(先知道这个文件里到底有什么);
- read:一次 DOM 遍历,出全画像(1,346 段:0.04 秒);
- validate:OpenXmlValidator 按 Office 2019 口径校验。
写侧 20 族:页边距、样式字体、样式段落、核心属性、文档设置、表格跨页重复表头、接受修订、清批注、页脚页码域、样式增删、编号定义/挂接/移除、域操作(目录/页码/引用/更新域)、分节、表格、批注、修订、页眉页脚、图片、文档属性、步骤串执行器。
每一族都不是"我拼一段 XML 试试",是三重自证(下一节讲)。
PPT 的新心脏:写 16 族,动画也能改
PptLab.exe,同一套 SDK。16 族写 op:主题设置、图表读取、图表数据、动画写入/读取、形状 ID、SmartArt 注入、文本替换、页面增删复制重排、图片替换(含 cover 裁切)、表格设置、形状几何批量、备注、批注、节、超链接、文档属性。
动画这块值得单独说一句:以前是手拼 timing 树的骨架,现在走"预设行 + mainSeq 骨架"的适配器,骨架来自 ppt-master 的 MIT 源码;改完 timing read 能读回来,validate 能过。
手搓 XML 不是错,是阶段。 当你只有几十个动作时,手搓最快;当你要交付给别人、要在别人的机器上开别人的文件时,就得让官方 SDK 接手,因为它会替你想那些你没想到的。
速度这块,量级差在哪
同一件事,两个引擎:
| 0.04 秒 | ||
| 0.05 秒 | ||
| < 0.01 秒 | ||
| 1.1 秒 / 1.2 秒 | ||
那两个 Word 重建的数字,我得诚实交代:不是同一天跑的。 184 秒 / 1,559 秒是历史账本里沉淀的真机数据,0.04 / 0.05 是这轮实测。段数相近(1,442 vs 1,346),但机器状态不同。
不过量级差摆在那儿——一个是你去泡杯茶,一个是你眨个眼。
PPT 那两行更值得看:120 页 1.1 秒,400 页 1.2 秒。 页数涨了三倍多,时间几乎不动。为什么?因为耗时大头是进程启动,不是页面处理。这就是"换引擎"和"优化代码"的区别——优化是把 100 次循环改成 10 次,换引擎是把循环这件事本身拿掉。
五、换了心脏,凭什么敢签字
这是整篇文章里我最想说的一节。
性能提升是爽的,但手术最危险的地方,是血管。心脏换好了,接错一根,人还是没了。
文件也一样。你把一个 20MB 的 docx 拆开、改一个属性、再存回去——你有没有碰坏别的 part?
所以我给每一族写 op 都上了三重自证:
第一重:integrity,逐 part SHA1 白名单对账。
改之前,把包里每个 part 的 SHA1 记下来;改之后,再算一遍。然后拿一份"允许变动"的白名单去核——白名单之外,一个字节都不许动。
这一条对 PPT 特别关键。SDK 保存时如果不整包重排,那些你没打算碰的主题、版式、媒体、动画,就能原样活着。实测下来,白名单外零改动。
第二重:validator,改前改后各跑一遍。
OpenXmlValidator 按 Office 2019 的口径检查包结构。改之前有 3 个既有告警,改之后还是那 3 个——说明我没引入新的。如果改完变成 4 个,这一 op 就不许交。
第三重:独立重开读回。
进程 A 写完,进程 B 重新打开,把值读出来对。为什么不用同一个进程?因为它可能拿着自己内存里的对象自说自话。
三关都过,才叫"写成功"。一关不过,就是没写。
边界我也照旧摆出来
- **PPT 的 reject-all(拒绝所有修订)**:明确报错,引导走 COM。不是做不到,是不该用文件级硬来——能力边界不是缺陷,是诚实。
- **Word 的重建 apply**:这一版还留着最后一段手写 XML 路径(重建引擎战功卓著,发布前不砍),排期 3.1 迁完。全项目只剩这一处,我写在这儿,不藏。
- Word 的页数、渲染类信号:文件画像里没有,跳过并披露,需要就走 COM。
- Excel 的透视/图表/形状明细:还是 COM 尾段采集。
- openpyxl 没被删:它退到"对拍基准 + 兜底"。ensure_built 发现 .NET 缺失,会自动装 SDK 自举(winget → 官方 dotnet-install.ps1 两条路),自举也失败,才落回 openpyxl,并且在回执里写明 reason。
最后一条我特意留着。一个引擎说自己"永远可用",通常意味着它在骗你。
六、还有一个坑:文件正开着呢
这是这三套 Skill 的立身之本——实时操作。用户把正在编辑的文件丢给你,你不能说"请先关闭"。
但这里有个矛盾:SDK 打开包时,只接受 Read / None 共享模式的文件流;而 Word、WPS 打开着的文件,是 deny-write 锁(不允许写,但允许读)。
怎么办?
用 FileShare.ReadWrite 把字节拷进内存,再在内存流上开包。 内存流是唯一通路。
于是现在:Word / WPS / PowerPoint 正开着的文件,cs 引擎的只读画像照常工作。 两个宿主都实测过。
Excel 那边多走了一步,叫 save-first:
体检默认不在未保存状态上操作。先查 wb.Saved——有未保存改动,就回执 needs_save,然后 AI 会来问你:
你这个表还有没保存的改动,我可以先帮你按一次保存(等价 Ctrl+S,不触发重算),还是你自己按?
你保存了,它走文件级,40 秒;你说不行,它走 COM,217 秒,慢但准。
这个交互不是啰嗦,是必须。 因为文件级读到的是磁盘上的值,你屏幕上那些没保存的,它看不见。看不见就得说看不见,不能猜。
七、后半台手术:打分标准,也重审了一轮
现在说第二根支柱。
前面那个"0 分 / L0",暴露的不只是性能问题,是评分体系的结构性问题。我把三套的评分从头审了一遍,先实测,再动规则。
病一:爆池——一条问题能把整本打死
Excel 的老算法是线性扣分:100 减掉所有问题的分。听起来合理,但它有个致命缺陷:问题数量和表数量无关。
20 张表的簿子,3 张历史遗留表贡献了 500 多分扣分,总分直接掉到 0,等级 L0。而那 17 张好表,一分不值。
修法:表均归一——100 - 总扣分 / 表数。同时保留 raw_score(线性合计)和 score_band(分档说明),让"为什么是这个分"能读出来。
效果:同一份簿子,0 分 / 线性 -555 → 76 分 / 线性 -377,L0 → L1,结构 96 / 数据 87 / 呈现 93 三个维度分开呈现。
"结构良好、数据有硬伤、呈现待改"——这句话,终于能从分数里读出来了。
病二:密度不可见——1% 和 95% 一个分
这条是压测压出来的。
Word 侧:300 段长文档,让同一个问题(T10)按 5% / 50% / 100% 三档密度命中。结果:1% 命中与 95% 命中,同分 82.9。
PPT 侧:50 页和 200 页的 deck,全篇命中与单页命中,只差 9.7 分。
什么意思?"一个页面有个小毛病"和"整本都烂了",分数差不多。 这种评分,用户看不懂,我也不敢用它下结论。
修法:占比分档。
- 命中占比 < 1%:×0.5(零星,降权);
- 1% - 10%:×1(正常);
- > 10%:×2(系统性,升权)。
再配三道护栏,防止"升档"变成新的误伤:
- 命中数 ≥ 3 才允许 ×2(单实例占 100%,不是系统性);
- 分母有效性下限:段落 < 50 / 标题域 < 10 / 表图节 < 5 的小样本,不给占比(避免干净的小文档被压 5.8 分);
- 内容页 < 5 的 PPT 不升档。
PPT 侧最后落成 27 个可重复触发码的占比白名单;Word 侧是 14 个发射点带 ratio。
分数因此动了:thesis_problem 的整体分 33.8 → 33.7(几乎不动),但结构变了——content 44.5 → 49.7(内容其实没那么差),format 23.1 → 17.6(排版是真的烂),total_penalty 279.5 → 302.5(该罚的罚到位了)。
总分不动、结构变清楚,这才叫"重审",不叫"调参数"。
病三:误报——规则在冤枉好文档
这一类最伤信任。举两个真例:
Word 的 S13(公文必备部件)。 它检查"主送机关 / 正文 / 发文机关署名 / 成文日期"有没有。听起来没问题?
问题是:真实公文里,这四个词永远不会字面出现。 主送机关写的是"XX 局:",署名是"XX 局"加日期。字面匹配的结果就是——每一份正常公文都必然误报。
修法:改结构化判定。主送机关=首行冒号收尾;署名日期=证据面出现"XXXX年"。同时加负向控制:正规文种版式不误报、T02 字体冒充防线、占比档行为锁、分层论证——新增 45 条断言。
PPT 的 SmartArt。 图示页上的 SmartArt,以前不被认作"视觉锚点 / 墨迹 / 重叠参与方",于是图示页被误报 N03 / P12,真正的 P04 反而漏检。
修法:三处白名单修掉,正反用例从 37 条加到 40 条。
病四:改了规则,谁保证分数不偷偷漂移
这条是工程纪律,不是算法。
我给两套都冻结了黄金基线:
- Word:干净画像 × 八文种 + thesis_problem,9 个用例;
- PPT:九型全量 + gov/report 配方面 + kpi/problem 问题面,13 个用例。
以后谁改评分规则,都得走基线对拍流程,分数动了就得解释为什么动。
还有一层:文档里的硬数字,和代码自动对拍。
SKILL.md 里写"正文 < 14pt"、"九型"、"50 条规则",代码里就真是这些;对不上就测试红。这一轮对拍抓到一个真实漂移——max_title_len 在文档里写 40,代码里早就改成 34 了。文档撒谎,是慢性病,得靠自动对拍治。
顺手补的可解释性
我把三套的评分回执都补成了"能讲给用户听"的:
- raw_score(占比档之前)与 total_penalty(进评分的)并列,不互相掩盖;
- score_band 点名 top3 扣分码 + 系统性码(命中 > 50% 点名);
- verdict 分层:局部重创 vs 全局崩坏,给修复优先级,不一刀切;
- 抽样、降级、引擎来源,全部在 read_source 里披露。
评分的价值不是给一个数,是让人知道下一步该动哪里。
八、那这套东西,你用起来是什么感觉
说人话,五个场景:
你说: 这个 6MB 的表帮我体检一下,看看哪里有问题。它做: 先问你有没有未保存改动 → 保存 → 40 秒出报告:76 条问题、76 分、L1,结构/数据/呈现三档分开,前三条扣分码点名。
你说: 这份论文目录不更新、章节号手打、图号乱,帮我改。它做: 先定型(这是学位论文)→ 读画像(1,346 段 0.04 秒)→ 出 W1-W5 处方 → 一档一档改 → 每改一族都有三重自证 → 最后 update fields + 导 PDF。
你说: 这个 PPT 全篇的产品名要换,14 页。它做: 一个
text_replace全 deck 1.3 秒,含完整性对账、validator 前后对比、重开读回。
你说: 我文件还开着呢,能看吗?它做: 能。拷进内存开包,只读画像照常,不动你的编辑。
你说: 这动画能不能统一改成淡入?它做: 走预设适配器,
timing set --effect fade,改完能读回、能过校验。
至于门槛,我照旧摆在这儿:
- Windows 专属(COM 是 Windows 的事,这是这套 Skill 的定位,不遮掩);
- .NET Framework 4.6.2+ —— Win10/11 自带,不用你装;
- 引擎缺失会自动装 SDK 自举构建(winget / 官方脚本两条路),全程最长 600 秒,有进度;
- 自举也失败,才落 openpyxl 兜底,并在回执里告诉你降级了、为什么降;
- 微软 Office 和 WPS 都能用。PPT 这边有个已知情况:Office Insider 16.0.20618 在 COM 自动化下不稳定(图表数据会话崩溃、关簿重开崩溃),是宿主侧的问题,WPS 同流程全绿,我把它写进了 CHANGELOG,不装作没发生。
九、写在最后:快,是给用户的耐心让路
回头看我这一轮的动机,其实挺朴素的。
不是"我要用更高级的技术",也不是"我要证明 C# 比 Python 快"。
是我坐在那儿,盯着一个 4 分半钟没有任何反应的屏幕,心里想的是:如果这是客户,他已经走了。
一个工具的能力,最终不是由"它能不能做"决定的,是由"人愿不愿意等它做完"决定的。
所以这一轮做的事,本质上是把"等"这件事,从用户身上拿走:
- 该流式扫的,别一个格子一个格子问;
- 该官方 SDK 干的,别自己拼字符串赌运气;
- 该 COM 干的,就让它干,但别让它干本来不该它干的活;
- 该说的边界,第一时间说清楚,别等用户踩了再解释。
性能是最大的用户体验,诚信是最大的技术能力。 这两句话,这一轮都写进代码里了。
顺手说一句:三套 Skill 都还是独立自包含的——把 OpenXML 官方 API 索引、检索器、预编译引擎全部放进包里,你单独拿走一套,也能完整跑,不依赖我别的 Skill。这个原则我守了半年,这一轮没破。
叫上身边的人,一起来把 AI 编程玩转。
想体验全套免费 Skill?欢迎加好友进入免费社群,关注公众号,后台回复「Skill」,不要理会AI自动回复,等我发联系方式,加好友进群自助获取。















































































