整理一批历史资料的时候,最烦的往往不是文件多,而是格式杂。我手上有一批从旧硬盘里翻出来的 .doc 文档,目录里混着报表、说明、会议记录,打开一个弹一次兼容模式提示,改完保存还要被问"是否保留原格式"。折腾了两天之后,我干脆把使用 Microsoft Word 批量将 .doc 文件转换为 .docx 文件这一套流程跑通了:一次选中上百个文件,无人值守跑完,还带日志和失败清单。这篇文章讲的就是这套流程从选型到落地、再到验收和踩坑的全过程,手上有一批存量旧文档、又不想一个个另存为的人,都可以直接照着做。
需要说明的是,我这里说的"批量转换"只处理格式,不改动内容,源文件保持原样不动,输出到独立目录。这个前提很重要,后面所有的方案设计都围绕它展开。文章里涉及的两条主路线分别是 Word 自带的 VBA 宏和 PowerShell 调用 Word 的 COM 接口,前者适合本机操作,后者适合放进计划任务里定时跑。我会把参数为什么这么设、坑在哪里、怎么验收都写清楚,代码可以直接抄,但参数建议按自己的实际情况再核一遍。
1. 为什么老文档还得过这一道:.doc 与 .docx 的底子差异
在动手之前,得先搞清楚这两种后缀到底差在哪。很多人以为 .docx 只是 .doc 换了个后缀、省了点空间,其实完全不是一回事。理解这一点,后面遇到"转换后排版变了""图片位置跑了""文件大小反而变大"这些现象时,就不会慌。
1.1 一个存的是二进制流,一个存的是压缩包
.doc 用的是二进制复合文档格式,可以把它的内部结构想象成一个老式的档案柜:文件头之后是一堆按固定偏移量排列的扇区,文字、样式表、图片、宏、摘要信息分别塞在不同的"抽屉"里,谁在哪个位置、占多长,全靠内部的索引表约定。这种结构的好处是读写快、对老软件友好;坏处是任何一个小地方出错,整个文件可能就打不开了,而且外部程序想读里面的一段内容非常困难,只能靠 Word 自己的解析器。
.docx 走的是另一条路,它本质上是一个 ZIP 压缩包,把 document.xml、styles.xml、图片资源、关系定义文件分别打包进去,再用一套开放的标准把它们组织起来。你可以直接把 .docx 的后缀改成 .zip 然后解压,里面全是看得懂的 XML 和资源文件。这个设计带来的直接结果有两个:一是体积通常更小,因为 XML 文本压缩率很高;二是外部工具可以不依赖 Word 就读到内容,各种文档处理库、在线预览服务基本都是冲着这个结构来的。
我用一个 3MB 左右、带十几张插图的老报表实测过:转成 .docx 之后变成 2.1MB 左右,缩减大概三成。但这不是铁律,如果原文档里塞了大量重复的矢量图形或者嵌入对象,转完之后反而可能变大,因为 XML 描述这些对象时会引入额外开销。所以别把"转换后一定更小"当成判断成功的标准。
1.2 转换能拿到什么,又会丢掉什么
先说收益。第一是兼容性,新版本的 Word 打开 .docx 不再弹兼容模式提示,功能区里那些新特性也能正常用;第二是体积和打开速度,尤其是图片多的长文档,差别比较明显;第三是可被其他系统消费,比如文档归档平台、全文检索、在线预览这类场景,基本都优先吃 .docx;第四是损坏后的可修复性,ZIP 结构坏了往往还能解压出部分 XML 抢救内容,二进制格式一旦结构性损坏,恢复难度高得多。
再说代价,这部分才是真正要留意的。第一,带宏的 .doc 不能直接转成 .docx,因为 .docx 从设计上就不允许存放 VBA 工程,强行保存会丢宏。正确做法是存成 .docm,或者先确认这批文档确实不需要宏。第二,域代码、交叉引用、目录这类动态内容,转换时会被重新计算一次,如果原文档里的域本来就指向了不存在的书签,转换后会显示成错误提示。第三,早期版本里的一些特殊排版(比如文本框的环绕方式、艺术字、公式编辑器 3.0 对象)在新格式下的渲染引擎不同,可能出现几个像素的位移,长文档里会表现为分页位置变化。
注意:如果这批 .doc 是合同、标书、财务报表这类对版式要求严格的文档,转换后必须逐份目视检查分页和页眉页脚,不能只看"转换成功"的日志。
理解了这层差异,就能明白为什么后面所有的方案都不是"改个后缀"那么简单,而是必须真正让 Word 打开、重新排版、再按新格式写出。这也解释了为什么所有可靠的批量方案都绕不开 Word 本体。
2. 三条路线怎么选:手工、VBA、PowerShell
批量转换这件事,网上流传的方案很多,但真正靠谱的就那么几条。我把它们按文件数量和使用场景排了一下,你可以对号入座,不用一上来就写代码。
2.1 文件少于二十个,别写代码
说句实在话,如果只是十几个文件,写脚本加调试的时间比手工点还长。Word 自带一个很实用的入口:打开"文件"菜单里的"打开"对话框,按住 Ctrl 多选一批 .doc,然后统一另存为。更省事的做法是用"文件 - 信息 - 转换"按钮,它会把当前文档直接升级为新格式。
不过手工路线有两个硬伤:一是每次都逃不掉兼容模式提示和"是否保留格式"的询问,批量操作时这些弹窗会不断打断你;二是文件一多,人一定会走神,漏掉几个、覆盖掉几个都是常有的事。所以我的经验阈值是二十个:低于二十个手工干,高于二十个就上脚本。
2.2 VBA 宏:本机操作最顺手的批量方案
VBA 的优势在于它跑在 Word 进程内部,不需要跨进程通信,也不涉及 COM 注册这类玄学问题。你在 Word 里按 Alt+F11 打开编辑器,贴一段代码,按 F5 就开跑,全程不超过五分钟。对于"我就在这台电脑上、我现在就要转"这种需求,VBA 是最短路径。
它的局限也很明确:只能在本机 Word 里跑,没法做成无人值守的定时任务;代码存在文档或者模板里,换台电脑就得重新贴一遍;一旦代码里出现死循环或者未处理的错误,Word 可能卡死,只能去任务管理器结束进程。所以我在 VBA 版本里会特意加上错误处理和日志,跑完能一眼看出哪几个失败了。
2.3 PowerShell 调用 Word:适合无人值守和定时跑
PowerShell 的思路是用 New-Object 创建一个 Word 的 COM 实例,然后驱动它打开、另存、关闭。它的好处是脚本可以存在文件里、进版本管理、参数化,也可以挂到计划任务上半夜跑;日志和结果处理用 PowerShell 的原生能力写起来很舒服,导出 CSV、统计成功失败数都是几行的事。
代价是 COM 这套东西比较脆:Word 进程残留、弹出对话框卡住脚本、权限不足导致创建实例失败,这些都得提前防。我一般会在脚本里显式设置 DisplayAlerts 和 Visible,跑完强制 Quit 并释放引用,再补一个"检查是否有残留 WINWORD.EXE"的收尾步骤。
把这三条路线放在一起对比,选择就清楚了:
| 方案 | 适用文件量 | 是否需要写代码 | 能否无人值守 | 主要风险 |
|---|---|---|---|---|
| 手工另存为 | 1 至 20 个 | 否 | 否 | 漏转、误覆盖 |
| Word VBA 宏 | 20 至 2000 个 | 是(约 40 行) | 否 | 弹窗阻塞、Word 卡死 |
| PowerShell + COM | 20 个以上 | 是(约 60 行) | 是 | 进程残留、权限问题 |
我自己现在的习惯是:日常零散文件用宏,成批归档用 PowerShell 挂计划任务。两套代码我都留着,需要哪个用哪个。
3. 动手之前:环境、备份、目录约定
批量操作最怕的不是转不成功,而是转坏了还把原件弄没了。所以在敲代码之前,有几个准备工作必须先做完,这几步花不了十分钟,但能救你一整天的返工。
3.1 环境自检清单
先把这几项确认一遍,很多"脚本跑不起来"的问题其实都出在这一步:
- Word 版本:SaveAs2 方法需要 Word 2010 及以上。2010 之前的版本只有 SaveAs,参数少一些但也能用。我测试环境是 Microsoft 365 版的 Word,2016、2019 也都验证过没问题。
- 位数一致:如果走 PowerShell 路线,PowerShell 的位数最好和 Office 一致。64 位 Office 配 64 位 PowerShell 最稳;混搭有时能跑,但出问题时排查起来很费劲。
- 受信任位置:把源目录和输出目录加到"信任中心 - 受信任位置"里,否则文件会以受保护视图打开,自动化会直接失败或者卡在提示上。这一步是新手最容易漏的。
- 磁盘空间:输出目录至少留出源目录总大小的 1.5 倍。转换过程中 Word 会生成临时文件,空间不够时会在中途报错。
- 关闭同步盘实时同步:如果目录挂在云同步上,批量写入会触发大量上传,既慢又容易冲突。跑之前先暂停同步。
3.2 备份与目录结构
我的规矩是三条:源目录只读不写、输出目录独立、失败文件单独归集。
具体做法是建三个平级目录:src放原始 .doc,out放转换后的 .docx,failed放转换失败的原件副本。源目录在转换期间设为只读(右键属性里勾上"只读",或者在权限里去掉写权限),这样即使代码写错了路径,也不会把原件覆盖掉。
目录结构建议保持单层,不要嵌套太深。原因有两个:一是 Word 打开深层路径的文件时容易触发路径长度限制,尤其文件名本身就很长的时候;二是脚本里做递归遍历会增加复杂度,出错时不好定位。如果你的文件本来就分散在多层目录里,先用资源管理器搜出来、全部复制到src平铺,转完再按需归位。
输出文件名默认和源文件同名、只换后缀。如果担心重名覆盖,可以在文件名后加一个批次号,比如报告A_20240115.docx。这个细节看着小,但批量处理几千个文件时,重名是很常见的,尤其是从不同目录汇总过来的文件。
4. VBA 批量转换的完整实现
这条路线我用了很久,代码不长,但几个参数设不对就会出各种怪问题。下面按"怎么进编辑器、代码怎么写、每个参数为什么这么设"的顺序讲。
4.1 进 VBA 编辑器与引用设置
打开任意一个 Word 文档,按 Alt+F11 进入 VBA 编辑器。左侧工程窗口里右键,插入一个模块,代码就贴在这个模块里。这样代码跟着文档走,下次打开还能用;如果你希望所有文档都能调用,可以把它放进 Normal.dotm 模板,但我不太建议这么做,模板里塞代码容易在升级时出问题。
代码里用到 FileSystemObject 来遍历目录和取文件名,这个对象来自 Microsoft Scripting Runtime,不过用 CreateObject 后期绑定方式创建就不需要手动勾引用了,省事而且换机器不会因为引用丢失报错。这一点很关键,很多人贴了代码跑不起来,就是卡在"缺少引用"上。
4.2 核心代码
下面这段是我现在在用的版本,处理单层目录、带日志、失败不中断:
Sub BatchDocToDocx() Dim fso As Object, srcFolder As Object, f As Object Dim doc As Document Dim srcPath As String, dstPath As String Dim okCount As Long, failCount As Long Dim logText As String Dim tStart As Double ' ===== 只需改这两行 ===== srcPath = "D:\BatchConvert\src\" dstPath = "D:\BatchConvert\out\" ' ======================== tStart = Timer Set fso = CreateObject("Scripting.FileSystemObject") If Not fso.FolderExists(srcPath) Then MsgBox "源目录不存在:" & srcPath, vbCritical Exit Sub End If If Not fso.FolderExists(dstPath) Then fso.CreateFolder dstPath Set srcFolder = fso.GetFolder(srcPath) Application.DisplayAlerts = wdAlertsNone Application.ScreenUpdating = False For Each f In srcFolder.Files If LCase(fso.GetExtensionName(f.Name)) = "doc" Then On Error Resume Next Set doc = Documents.Open( _ FileName:=f.Path, _ ReadOnly:=True, _ AddToRecentFiles:=False, _ Visible:=False) If Err.Number = 0 Then doc.SaveAs2 _ FileName:=dstPath & fso.GetBaseName(f.Name) & ".docx", _ FileFormat:=wdFormatXMLDocument, _ AddToRecentFiles:=False If Err.Number = 0 Then okCount = okCount + 1 logText = logText & "OK," & f.Name & "," & vbCrLf Else failCount = failCount + 1 logText = logText & "FAIL," & f.Name & "," & Err.Description & vbCrLf End If doc.Close SaveChanges:=False Else failCount = failCount + 1 logText = logText & "FAIL," & f.Name & "," & Err.Description & vbCrLf End If On Error GoTo 0 End If Next f Application.ScreenUpdating = True Application.DisplayAlerts = wdAlertsAll ' 写日志 Dim ts As Object Set ts = fso.CreateTextFile(dstPath & "_convert_log.csv", True, True) ts.WriteLine "result,file,message" ts.Write logText ts.Close MsgBox "完成。成功 " & okCount & " 个,失败 " & failCount & _ " 个,耗时 " & Format(Timer - tStart, "0.0") & " 秒。", vbInformation End Sub4.3 关键参数逐个说清
代码短,但每一行都有讲究,我挑几个容易出错的讲透。
FileFormat:=wdFormatXMLDocument,这个常量的值是 12,对应的就是 .docx。很多人会把 wdFormatDocumentDefault(值 16)和它混用,两个写出来都是 .docx,区别在于 12 是明确的"Word 2007 及以后的无宏文档格式",16 是"当前 Word 默认格式"。日常用两者结果一样,但如果你的 Word 默认保存格式被人改过(比如改成 .docm),用 16 就会写出 .docm,后面校验就通不过。所以我固定用 12,语义明确。
ReadOnly:=True,打开时只读,这一步是保命用的。万一后面代码写错了调用 Save 而不是 SaveAs,因为是只读打开的,Word 会直接报错而不是把原件覆盖掉。只读打开不影响另存为,Word 读进内存之后照样能按新格式写出去。
AddToRecentFiles:=False,不加这个参数,每打开一个文件就往"最近使用的文档"里塞一条,转两百个文件之后你的最近列表就被彻底污染了。这个参数在打开和保存时都要设。
Visible:=False,让文档在后台打开。批量处理时不显示窗口能明显提速,也能避免你误点。但要注意,某些包含复杂图形的文档在不可见模式下打开可能出异常,如果你遇到某几个文件老失败,可以先把它改成 True 单独跑一次看看。
DisplayAlerts = wdAlertsNone,这一行是整个脚本能无人值守的关键。它会抑制"文件已存在,是否替换""是否保留格式"之类的弹窗。注意这段代码只在宏内部生效,脚本结束时必须还原成 wdAlertsAll,否则你后续手动操作时该有的提示全没了,容易误覆盖文件。
On Error Resume Next 配 On Error GoTo 0,这对组合是为了"单个文件失败不中断整批"。错误处理打开后,文件打开失败就跳到判断 Err.Number;处理完一个文件立刻用 On Error GoTo 0 关掉,避免错误状态延续到下一个文件造成误判。这个细节我在早期版本里漏了,结果一个坏文件导致后面所有文件都被记录成失败。
4.4 运行与日志核对
按 F5 运行,跑完会弹一个统计框,同时输出目录里多出一个_convert_log.csv。日志第一列是结果,第二列是文件名,第三列是错误信息,直接双击用表格软件打开就能筛。
我的习惯是跑完之后先看失败数。如果失败数是零,再抽三到五份内容最复杂的文档打开看一眼版式。如果不是零,按错误信息分类处理,常见的几类我在第 7 节列了速查表。
提示:第一次跑的时候,强烈建议先把 src 里的文件复制十来个到一个测试目录,把脚本路径指过去跑一遍,确认日志格式和输出都符合预期,再对全量动手。
另外提醒一句,转换过程中别去操作 Word,也别在 Word 里开别的文档。ScreenUpdating 关掉之后界面本来就不刷新,你如果强行切窗口,可能出现界面卡住、你以为死了其实是还在跑的情况。
5. PowerShell 脚本实现与无人值守
需要定时跑、或者需要处理成批不同目录的文件时,我会换成 PowerShell。核心逻辑和 VBA 一样,难点在于 COM 对象的创建和释放。
5.1 脚本骨架
$src = 'D:\BatchConvert\src' $out = 'D:\BatchConvert\out' $failed = 'D:\BatchConvert\failed' $logPath = Join-Path $out ('_log_' + (Get-Date -Format 'yyyyMMdd_HHmmss') + '.csv') New-Item -ItemType Directory -Force -Path $out, $failed | Out-Null $word = $null $results = @() try { $word = New-Object -ComObject Word.Application $word.Visible = $false $word.DisplayAlerts = 0 # wdAlertsNone $word.Options.SaveInterval = 0 # 关闭自动恢复,减少临时文件 $files = Get-ChildItem -Path $src -Filter '*.doc' -File | Where-Object { $_.Name -notlike '~$*' } $i = 0 foreach ($f in $files) { $i++ $target = Join-Path $out ($f.BaseName + '.docx') $doc = $null try { $doc = $word.Documents.Open( $f.FullName, $false, $true, $false) $doc.SaveAs2($target, 12) # 12 = wdFormatXMLDocument $doc.Close(0) # 0 = wdDoNotSaveChanges $doc = $null $results += [pscustomobject]@{ Result = 'OK'; File = $f.Name; Msg = ''; Seconds = 0 } } catch { if ($doc) { try { $doc.Close(0) } catch {} ; $doc = $null } Copy-Item $f.FullName (Join-Path $failed $f.Name) -Force $results += [pscustomobject]@{ Result = 'FAIL'; File = $f.Name; Msg = $_.Exception.Message } } # 每处理 100 个重启一次 Word,防止内存膨胀 if ($i % 100 -eq 0) { $word.Quit() [System.Runtime.InteropServices.Marshal]::ReleaseComObject($word) | Out-Null Start-Sleep -Seconds 2 $word = New-Object -ComObject Word.Application $word.Visible = $false $word.DisplayAlerts = 0 } } } finally { if ($word) { try { $word.Quit() } catch {} [System.Runtime.InteropServices.Marshal]::ReleaseComObject($word) | Out-Null [GC]::Collect() } $results | Export-Csv -Path $logPath -NoTypeInformation -Encoding UTF8 }5.2 COM 对象释放与进程残留
PowerShell 这条路最容易出问题的地方就是 Word 进程没关干净。表现是脚本跑完了,任务管理器里还躺着好几个 WINWORD.EXE,占着内存不说,下次再跑时可能因为文件被占用而失败。
处理办法有三层。第一层是每处理一百个文件主动 Quit 重启一次,这个阈值是我实测出来的:处理两三百个中等大小的文档之后,Word 的内存占用会明显上涨,超过一千个不重启基本必卡。第二层是 finally 块里保证 Quit 一定执行,哪怕中间抛异常。第三层是收尾时检查并清理残留进程,我一般会在脚本末尾加一段提示,如果检测到还有 WINWORD 进程就打印出来人工确认,不自动杀进程,因为你自己可能还开着别的文档。
[System.Runtime.InteropServices.Marshal]::ReleaseComObject加[GC]::Collect()这套组合是清理 COM 引用的标准做法。不加的话,PowerShell 的垃圾回收时机不确定,Word 可能一直挂在后台。
5.3 挂计划任务时要注意的三件事
第一,计划任务默认以"只在用户登录时运行",这个模式下 COM 调用 Word 通常没问题。如果选"不管用户是否登录都运行",Word 这种带界面的应用在无交互会话里可能初始化失败,不建议这么配。
第二,任务要勾上"如果任务运行时间超过以下时间,停止任务",给个上限比如两小时。真出现弹窗卡住的情况,至少不会一直挂着。
第三,脚本里所有路径都写绝对路径,别依赖当前工作目录。计划任务启动时的工作目录和你手动运行时不一样,用相对路径必然找不到文件。
6. 转换结果的验收:怎么确认真的成了 docx
日志写着"OK"不代表真的成功。我遇到过日志全绿、结果打开一看是空文档的情况,也遇到过后缀是 .docx 但内部还是二进制格式的情况(极少数,通常是写文件过程中断了)。所以验收这步不能省。
6.1 三步验收法
第一步,看文件头。.docx 是 ZIP 包,前两个字节是50 4B(也就是 ASCII 的 PK);.doc 是复合文档,前八个字节固定是D0 CF 11 E0 A1 B1 1A E1。写几行脚本批量读一下文件头,能瞬间筛出"假 docx"。
第二步,看文件数量和名称对应关系。输出目录里的 .docx 数量应该等于源目录 .doc 数量减去失败数。名称一一对应,没有多余的临时文件(以~$开头的那些要清掉)。
第三步,抽查内容。按文件大小排序,抽最大的三个、最小的两个,外加随机两个,打开确认正文完整、图片在位、页眉页脚正常。这一步靠人眼,但只花两分钟,性价比很高。
6.2 批量校验脚本
$out = 'D:\BatchConvert\out' Get-ChildItem $out -Filter '*.docx' -File | ForEach-Object { $fs = [System.IO.File]::OpenRead($_.FullName) $buf = New-Object byte[] 2 $null = $fs.Read($buf, 0, 2) $fs.Close() [pscustomobject]@{ File = $_.Name Header = ('{0:X2}{1:X2}' -f $buf[0], $buf[1]) Valid = ($buf[0] -eq 0x50 -and $buf[1] -eq 0x4B) KB = [math]::Round($_.Length / 1KB, 1) } } | Where-Object { -not $_.Valid } | Format-Table -AutoSize这段脚本只输出校验不通过的文件,正常的话什么也不打印,很省心。如果打印出一堆,说明写文件环节出了问题,去检查磁盘空间和权限。
另外还可以顺手比一下体积,转换后体积小于源文件 5% 的,很可能是内容没转全,值得单独打开看一眼。这个经验阈值不绝对,但能帮我捞出过几次真问题。
7. 常见问题与排查实录
这一节是我踩过的坑的汇总,按现象分类,遇到了直接对号入座。
7.1 弹窗类问题:脚本跑着跑着不动了
最典型的现象是脚本运行到一半停住,Word 界面上挂着一个对话框。原因通常是三种:文件有密码、文件已损坏、文档里引用了外部链接需要更新。
带密码的文档,自动化打开时会弹输入框,无人值守的情况下就永远卡在那。处理办法是提前识别:可以在循环里先尝试以只读方式打开,设置Documents.Open的参数PasswordDocument:="******"(如果你知道密码),或者干脆用异常捕获配合超时机制跳过。我一般会先用一段小脚本扫一遍所有文件,把有密码的挑出来单独处理。
损坏文件的表现是打开时报"文件已损坏,无法打开",这个反而好办,错误捕获就能跳过,归到 failed 目录。
外部链接的问题更隐蔽。文档里如果有链接到其他文件的图片或者 Excel 对象,打开时 Word 会问"是否更新链接"。解决办法是在脚本开头设Application.Options.UpdateLinksAtOpen = False,或者在 Open 方法里加UpdateLinks:=0参数。
7.2 内容类问题:转完打开发现不对劲
宏没了。这是最常见的一类。源 .doc 里带 VBA 工程,存成 .docx 时 Word 会丢弃宏,而且可能弹一个提示。如果你的文档确实需要保留宏,目标格式要改成wdFormatXMLDocumentMacroEnabled(值 13),输出后缀用 .docm,而不是 .docx。这个必须在跑批之前确认清楚,转完再发现丢宏,只能从备份重来。
图片位置偏移。老版本 Word 的环绕方式和现在的渲染引擎有细微差别,尤其是"浮于文字上方"配合精确坐标定位的图。转换后可能出现图片挪了几毫米、长文档里表现为分页错位。这个没法完全避免,只能靠转完抽查来发现。如果这批文档对版式极敏感,考虑先输出 PDF 做留档,再转 docx 供编辑。
目录和页码变了。.docx 打开时会重新计算域,目录页码自然跟着变。这不叫出错,是正常现象。但如果你需要固定页码,转完之后要在 Word 里全选按 F9 更新一次域,再手动核对目录。
文本框变成方框或者消失。极老的文档用了 Word 95 时代的文本框实现,新格式下不认。这类文档数量一般不多,挑出来手工处理更划算。
中文显示成方框。说明源文档用的字体在当前系统里没有。批量处理前先确认系统里装了文档用到的字体,尤其是那些用了特殊中文字体的公文。
7.3 性能与稳定性:几千个文件怎么跑得稳
一次别跑太多。我试过一口气跑三千多个,跑到一千五左右 Word 就明显变慢,最后直接无响应。后来改成每批五百个,批间重启 Word 并等两秒,全程稳定。总耗时反而更短,因为避免了后期的卡顿。
别开并行。有人会想开多进程同时转,速度翻倍。实测下来收益很小,因为 Word 是单实例应用,多开进程会互相抢资源,还容易出现文件占用冲突。老老实实串行跑。
把杀毒软件的实时扫描暂时排除输出目录。这一条效果很明显,我这边加了排除之后整体速度快了将近四成。批量写入大量新文件时,实时扫描的开销非常大。当然,跑完记得把排除去掉。
关掉自动恢复。Word 的自动恢复功能默认每隔十分钟存一次临时文件,批量处理时这些临时文件既占空间又拖速度。脚本开头设Options.SaveInterval = 0可以关掉。
7.4 问题速查表
| 现象 | 最可能的原因 | 处理方式 |
|---|---|---|
| 脚本卡住不动,Word 挂弹窗 | 文件加密 / 外部链接提示 | 预先识别加密文件,设 UpdateLinksAtOpen=False |
| 日志全部成功但打开是空的 | 磁盘空间不足或写入中断 | 检查剩余空间,校验文件头 |
| 转换后宏丢失 | 目标格式选成了 .docx | 改用 wdFormatXMLDocumentMacroEnabled,后缀 .docm |
| 部分文件打开报损坏 | 源文件本身损坏 | 归入 failed 目录,从备份恢复原件 |
| 图片或文本框错位 | 新旧渲染引擎差异 | 转后抽查,重要文档保留 PDF 存档 |
| 运行越来越慢,最后卡死 | Word 内存膨胀 | 每 100 至 500 个重启一次 Word |
| 输出目录出现大量 ~$ 文件 | 异常退出留下的临时文件 | 循环时过滤,跑完统一清理 |
| 提示找不到文件或路径过长 | 路径超过 260 字符 | 缩短目录层级,或平铺文件 |
| 受保护视图导致失败 | 目录不在受信任位置 | 把源目录和输出目录加入受信任位置 |
还有一个小细节值得单独说:输出目录里如果混进了~$开头的文件,一定要过滤掉。这些是 Word 的锁文件,本身是二进制垃圾,如果被当成 .doc 处理,会一直失败,还会在日志里刷屏。在 VBA 里判断扩展名之前加一句If Left(f.Name, 2) <> "~$" Then,在 PowerShell 里用Where-Object { $_.Name -notlike '~$*' }过滤掉就行。
最后分享一个我自己的习惯:每批转换跑完,我都会把源目录和输出目录的文件数、总大小记在一个小表格里,附上当次的日志路径。批量处理这种事,出问题往往不是当场发现,而是几周后有人要找某份文档时才发现它没转成功或者转坏了。有了这份记录,回溯起来是几分钟的事,没有的话就得整批重来。
另外,转换完之后别急着删源文件。我的做法是至少保留一个完整的备份周期,等到确认所有下游系统都能正常读取转换后的文档,再考虑清理。硬盘便宜,重来一次的时间可不便宜。