Python 与 VBA 的领地划分
问题的起点
word-mcp-live 把 AI 的能力延伸到了 Word 实时编辑层。Python 现在能用自然语言驱动 Word 做跟踪更改、批注、格式调整,所有修改实时可见、Ctrl+Z 即撤。
那问题来了:有了 MCP 之后,VBA 还需要吗?
答案是:不但需要,而且两者的关系不是替代,是互补。
Python 的领地:外部世界
连接能力
HTTP 请求、数据库读写、JSON/XML 解析、正则匹配、Pandas 数据分析、文件批量转换——这些是 Python 的主场。VBA 每一样都要靠 Win32 API 或外部库拼凑,成本高一大截。
批处理
处理 50 份报告、从 200 个 docx 里提取预算金额、把 Excel 数据库写进 Word 模板——Python 脚本一轮跑完。VBA 当然也行,但开 50 个 Word 实例的稳定性和速度是硬伤。
MCP 的宿主
word-mcp-live 本身是 Python 写的。MCP 生态下,Python 是控制面的语言。未来扩展 MCP 工具、写自定义工具函数、连其他 API,全在 Python 一侧。
自然语言驱动的实时编辑
"帮我把这份标书所有'供应商'改成'投标人',用跟踪更改"——这件事 VBA 要写几十行代码,MCP 一句话。
VBA 的领地:Word 内部
深度对象模型访问
Python 通过 COM(pywin32 或 word-mcp-live 封装)调 Word,本质上是"外部程序远程控制"。VBA 是住在 Word 里的原住民。有些 COM 操作在外部调用会有延迟、超时、并发问题,VBA 不存在这些。
事件驱动
Document_Open、Document_BeforeSave、Window_SelectionChange——这些钩子只有 VBA 能用。你想让文档打开时自动检查格式、保存时自动备份到指定路径,Python 做不到(除非常驻进程轮询)。
可分发的自包含模板
.dotm 文件里嵌好 VBA,发给同事或客户,他们打开就能用,零安装。Python 方案需要对方有 Python 环境 + 依赖包 + MCP 服务器,分发成本完全不同。
自定义对话框
UserForm 做参数输入窗、进度条、交互式格式选择——VBA 原生支持。Python 做不到(除非开外部 GUI 窗口)。
只有渲染引擎才知道的事
| 能力 | python-docx | VBA |
|---|---|---|
| 图片实际显示DPI和尺寸 | ❌ 只能拿到原始像素 | ✅ InlineShape.Width/ScaleWidth |
| 表格跨页断行检测 | ❌ 不保留分页信息 | ✅ 每行的实际分页位置 |
| 跟踪更改的详细统计 | ❌ 支持很弱 | ✅ Revisions 集合完整 |
| 域值(DocProperty、StyleRef等)解析 | ❌ 不解析域 | ✅ Fields 集合原生 |
| 打印布局溢出检测 | ❌ 不模拟渲染 | ✅ PageSetup + 实际渲染状态 |
MCP 当前的能力边界
结合实测,几个明确的缺口:
| 缺口 | 影响 | VBA 能不能填 |
|---|---|---|
| 不能读文档内嵌图片 | 审阅时看不到资质证书扫描件的内容 | ✅ VBA 能提取图片并做 OCR |
| 无事件钩子 | 做不到"打开文档自动执行" | ✅ VBA 事件模型覆盖 |
| 不能做自定义 UI | 不能给文档加按钮/菜单/对话框 | ✅ UserForm + Ribbon |
| 无分发能力 | 对方必须有完整 MCP 栈 | ✅ .dotm 单文件分发 |
| COM 兼容缺口 | WPS 下部分工具报错 | ⚠️ VBA 也有,但至少能捕获 |
桥接方案:Python 调 VBA
核心机制:COM 的 Application.Run
VBA 的所有 Public 过程和函数,都挂在 Word 的 COM 对象模型上。Python 通过 COM 拿到 Word Application 对象后,直接 Run("宏名", 参数),就像在 VBA 里按了 F5。
import win32com.client
word = win32com.client.GetActiveObject("Word.Application")
# 调用 VBA 函数,获取返回值——字符串/数组/布尔值直接拿到
result = word.Run("CheckCertificateValidity", "C:\\资质\\城乡规划乙级.pdf")
print(result)
三种通信模式
模式1:直连调用(推荐)
Python ──COM(Run)──▶ VBA 函数 ──return──▶ Python
适用:VBA 函数有明确输入参数和返回值。限制:只能传简单类型(字符串、数字、数组),不能传对象引用。
模式2:文档侧通道
Python ──写入──▶ 文档内容/文档变量/自定义属性 ◀──读取── VBA
适用:异步、不需要返回值。比如 Python 把 50 个文件路径写进文档属性,用户打开文档时 VBA 自动生成目录。
模式3:文件/剪贴板中转
Python ──写JSON──▶ 临时文件 ◀──读JSON── VBA
最笨但最可靠的方案,没有任何版本兼容问题。适用:数据量大、复杂嵌套结构。
实操:Python 指挥 VBA 检查文档图片 DPI
VBA 侧(嵌在 .dotm 里):
Public Function GetImageInfo(docPath As String) As String
Dim doc As Document, shape As InlineShape
Dim result As String
Set doc = Documents.Open(docPath, Visible:=False)
For Each shape In doc.InlineShapes
If shape.Type = wdInlineShapePicture Then
Dim dpi As Long
dpi = shape.Width / shape.ScaleWidth * 100
result = result & shape.AlternativeText & "|" & dpi & vbCrLf
End If
Next
doc.Close SaveChanges:=False
GetImageInfo = result
End Function
Python 侧:
word = win32com.client.Dispatch("Word.Application")
word.Documents.Add("C:\\模板\\国土工具.dotm")
info = word.Run("GetImageInfo", "D:\\标书\\河津市.docx")
for line in info.strip().split("\n"):
alt, dpi = line.split("|")
print(f"图片「{alt}」: {dpi} DPI")
与 word-mcp-live 集成
word-mcp-live 通过 SSE 守护进程已经在跟 Word COM 通信。加一个 word_live_run_macro 工具,就能在 AI 对话里直接调 VBA:
"调一下文档里的 CheckFormat 宏,看看有没有标题编号跳号的问题"
MCP 拿到结果,AI 解读并告诉你哪几处标题有问题。
国土行业的实战决策
| 场景 | 主力工具 | 理由 |
|---|---|---|
| 单份报告审阅/修改 | MCP(Python) | 自然语言驱动,实时可见 |
| 批量文档处理 | Python 脚本 | 批处理碾压 |
| 模板分发给同事/客户 | VBA .dotm | 单文件,零安装 |
| 定期报告生成(数据库→Word) | Python 后端 | 数据连接+模板填充 |
| 一键排版宏 | VBA | 只在 Word 内运行,无需外部进程 |
| 表格填写(几十格) | Python SSE 直连 | 实测 41 格 0 失败 |
| 文档内容检查 | MCP | 先读全文,AI 分析,再批注标记 |
| 图片DPI/分页/域值检查 | Python 调 VBA | 只有 VBA 能拿到渲染信息 |
实测:Python 调王帅工具箱的所有宏
环境
- WPS Office(ProgID:
KWPS.Application,COM 接口报告名称为Microsoft Word 12.0) - Python 3.11 + pywin32(注意:QClaw 自带的 Python shim 没有 pywin32,要指定路径
C:\Users\Administrator\AppData\Local\Programs\Python\Python311\python.exe) - 工具箱文件:
王帅工具20260311.docm,含一个标准模块m(830 行代码)+ 一个 UserForm + ThisDocument
读取 VBA 源码
Python 能读取 .docm 里的全部 VBA 代码,不需要打开 VBA 编辑器:
import win32com.client
wps = win32com.client.Dispatch('KWPS.Application')
doc = wps.Documents.Open(r'D:\wangshuaibiji\tools\其他工具\王帅工具20260311.docm')
code = doc.VBProject.VBComponents('m').CodeModule
for i in range(code.CountOfLines):
print(code.Lines(i + 1, 1))
doc.Close(False)
工具箱已有的宏(共 18 个 Public Sub)
| 宏名称 | 功能 | 有无对话框 | Python 可调? |
|---|---|---|---|
插文号 |
选中文本转规范文号格式 〔xxx〕 |
❌ 无 | ✅ 已测通 |
插表号 |
光标处插入带章编号的表格标题 | ❌ 无 | ✅ 已测通 |
插图号 |
光标处插入带章编号的图片标题 | ❌ 无 | ✅ 已测通 |
域代码切换 |
切换选中域的代码/结果显示 | ❌ 无 | ✅ 未测 |
选中区域自动编号转普通文本 |
把自动编号变成硬文本 | ❌ 无 | ✅ 未测 |
设置图片首行不缩进位置居中 |
全文图片段落居中 | ❌ 无 | ✅ 未测 |
删除全部域 |
清空所有自定义文档属性 | ⚠️ MsgBox 确认 | ⚠️ 需改掉 MsgBox |
域转为普通文档 |
全文档域解除链接(不可逆) | ⚠️ MsgBox 确认 | ⚠️ 需改掉 MsgBox |
批量设置表格字体样式 |
表格统一格式+列宽+标题行重复 | ⚠️ InputBox | ⚠️ 需改掉 InputBox |
检查山西省其他地名 |
扫描文档给山西地名加批注 | ⚠️ InputBox | ⚠️ 需改掉 InputBox |
检查叠词 |
检测重复词语加批注 | ⚠️ InputBox | ⚠️ 需改掉 InputBox |
添加文本属性 |
选中文本→自定义文档属性+插入域 | ❌ 无 | ✅ 未测 |
查找替换域 |
逐个确认替换文本为域 | ⚠️ InputBox+MsgBox | ⚠️ 需大改 |
批量word转pdf |
文件夹内全部 doc/docx→PDF | ⚠️ 文件夹选择框 | ⚠️ 需改掉对话框 |
普通文本编号删除 |
按分隔符去掉编号前缀 | ⚠️ InputBox | ⚠️ 需改掉 InputBox |
插入多图 |
多选图片文件插入 | ⚠️ 文件选择框 | ⚠️ 需改掉对话框 |
多图改尺寸 |
统一图片高度 | ⚠️ InputBox | ⚠️ 需改掉 InputBox |
plxgy |
打开 UserForm1 | ⚠️ 模态窗体 | ❌ 会阻塞 COM |
实测:Application.Run 调用
测试1:插文号(WPS 中实测通过)
wps = win32com.client.Dispatch('KWPS.Application')
doc = wps.Documents.Open(r'D:\wangshuaibiji\tools\其他工具\王帅工具20260311.docm')
doc.Activate()
doc.Content.Text = '测试文号转换 [2026] 看看效果'
# 定位并选中 [2026]
rng = doc.Content
rng.Find.Execute('[2026]')
rng.Select() # 选中: '[2026]'
# 调用宏
wps.Run('插文号')
# 结果:'测试文号转换 〔2026〕 看看效果'
print(doc.Content.Text)
关键细节:
Run必须在包含该宏的文档是活动文档时调用(.Activate()不能省)- 跨文档调用(在测试文档上选中文字,调宏文档的宏)会报
pywintypes.com_error: (-2147352567, '发生意外。'),WPS COM 不允许 插文号的逻辑:正则匹配[]/【】/()/()四种括号并统一转为〔〕,括号内需是年份+数字才合理([abc]也会被转但无实际意义)
测试2:插表号 / 插图号(WPS 中实测通过)
doc.Content.Text = '第一章 测试\n这是一个段落。'
wps.Selection.EndKey(6) # 光标移到末尾
wps.Selection.TypeParagraph()
wps.Run('插表号')
# 输出: 表 文档中未找到关联章节样式-1
wps.Selection.TypeParagraph()
wps.Run('插图号')
# 输出: 图 文档中未找到关联章节样式-1
提示"未找到关联章节样式"是因为测试文档没用内置标题样式(Heading 1/2/3),VBA 依赖章号链接但找不到对应样式的章节——逻辑完全正确,正常文档中会产出 表 1-1 这种编号。
发现的坑
-
WPS 的 COM ProgID 是
KWPS.Application,不是WPS.Application也不是Word.Application。GetActiveObject('ET.Application')/GetActiveObject('KWPS.Application')只在 WPS 已经运行时有效,否则用Dispatch启动新实例。 -
PC 上的 Python shim 没装 pywin32。 QClaw 自带的 Python 环境不含 com 库,必须显式用全路径 Python 解释器。
-
跨文档调宏不可靠。 如果宏存在文档 A,你想在文档 B 的选区上执行该宏,必须在调用
Run前把宏文档设为活动文档,然后通过Activate或显式设置 Range 确保操作目标正确。最好的做法是:直接在宏文档上操作,或者在 B 文档也引用同名宏。 -
Sub 没有返回值。 工具箱里的宏全是
Sub(非Function),Python 通过Run调用拿不到返回值,只能通过副作用(改文档内容、加批注、插域)或文档侧通道(把结果写入自定义属性、书签、隐藏文字)来获取结果。 -
InputBox/MsgBox 阻塞 COM 调用。 工具箱里 18 个宏中有 10 个含对话框。从 Python 调这些宏时,对话框弹出来但 Python 看不到(因为 Word 进程在等用户操作),Python 进程也在等 Run 返回——双向死锁。要批量调用这些宏,必须先改掉对话框,改成从文档属性或 Selection 里读参数。
总结
Python 是外脑,VBA 是内脏。
两者不是替代关系。有了 MCP,VBA 原来扛的某些活可以卸下来(审阅、格式化、替换),但 VBA 自己的地盘(模板分发、事件驱动、对话框、渲染状态检查)Python 进不去也没必要进。
最优架构:VBA 做前端传感(拿渲染状态、事件响应),Python 做后端大脑(数据处理、AI 判断、MCP 控制面),COM 的 Application.Run 做桥梁。
一句话:能用 VBA 一行搞定的,没必要上 Python socket。反过来,需要 AI 理解和判断的事,VBA 完全没戏。各打各的柜台,COM 把手牵。