部门季度汇总第八次卡壳的那天下午我握着鼠标又完成了一场“重复劳动马拉松”四十八份 Word 巡检报告逐份打开、点文件、另存为 PDF再打开 PDF 编辑器按顺序把四十八个文件拖到一块调整顺序、检查页码足足忙到六点才吃上晚饭。要说这活有多难真不难要说有多烦那是真烦。后来我实在忍不了就自己动手做了一个 Word 批量转 PDF 合并工具一路迭代到 v1.3现在办公室里好几个部门都在用。这篇不是什么官方文档就是我个人的折腾记录。工具解决的核心问题一句话就能说清把指定目录下的一批 Word 文档批量转成 PDF再按文件名或标题顺序合并成一个整本 PDF期间自动处理书签、排序、字体预检这些麻烦事。适合谁用经常处理标书、合同、会议材料、报告归档的办公室人员、运维、产品经理、项目助理只要你有“一批 Word 文档要转格式、要合成一个文件”的需求就能直接套用这套思路。下面我按从动机到实践的顺序把技术选型、版本迭代和踩坑经验一次性讲清楚你可以直接拿去用也可以只挑踩坑部分看少走点弯路。1. 先从最痛的地方说起多份 Word 转 PDF 再合并的日常噩梦1.1 手动流程到底差在哪我见过不少团队的“标准流程”是这样的打开第一份 Word 文档点文件另存为 PDF关闭再打开第二份……重复四十多次然后打开 PDF 编辑器把每个 PDF 拖进去排序检查页眉页脚最后导出一个合并文件。这套流程所有人都能干但只有文件数量多了才能真正体会到它有多磨人。第一是时间账。单份文档如果只是点击操作几秒钟就能完成可实际上你每打开一份文档都要先看它有没有未保存的修改、有没有弹窗拦截、有没有格式校验提示转完还要回文件夹确认文件名有没有冲突。我实测过四十八份中等复杂度的文档全手动操作平均要二十到三十分钟中途被打断两次还能拖到四十分钟。这个时间干别的什么不好第二是排序和返工的风险。文档交晚了或者领导说“第七章要放到最前面”你在 PDF 编辑器里已经排好的顺序就得全部重拖。拖完还得重新检查页码和页眉因为合并后的文件很难一眼看出页眉跳的是哪一节的。第三是漏转漏合并人的注意力不可能在重复操作里持续高度集中我有一次漏掉了一份文档交上去之后被同事指出来整本只能重做。从那以后我才下定决心搞自动化。1.2 为什么 Word 转 PDF 必须在“Word 阶段”完成有人可能会问最终要合并成 PDF那直接把 Word 文档扔进某些“万能转换器”不就行了问题在于PDF 是“最终印刷态”的文档Word 是“可编辑文稿态”的文档。字体、公式、表格边框、页眉页脚、分页符这些信息在 Word 里由排版引擎动态计算在 PDF 里则完全固化。想让 PDF 长得和 Word 里一模一样就必须在 Word 排版引擎还可用的时候把文档“打印”成 PDF。这个“打印”动作本质上是一次完整的页面渲染。很多在线转换工具都是先解析 docx 的 XML再自己写一套布版逻辑小文档看不出毛病一遇到文本框锚点、艺术字、页眉里面的域代码就会丢排版、错页码。这也是我没走“用 Java POI 或 OpenXML SDK 直接解析 Word 再生成 PDF”这条路的原因。POI 对 docx 内容的读写能力很强甚至能往文档里插表格和图表但它不是排版引擎绝大多数真实场景下还原不了 Word 原版式。搜索的时候看到很多人问“Java POI 能生成图表吗”能但如果你要的是“保持原版式转 PDF”那 POI 并不是正确的工具。选择让成熟的办公软件来做渲染自己只负责调度和合并是最稳妥的方案。1.3 从 v1.0 到 v1.3这个工具的功能演进这个工具不是一步到位的。v1.0 的最初形态只是一个批处理脚本扫描目录下所有 docx逐个调用转换引擎输出 PDF。它能解决“不用手工另存为”的问题但转出来的 PDF 散落一堆后续合并还得人工操作自动化程度远远不够。用了大概一周我就发现必须把转换和合并连成一条线。于是 v1.1 加了合并功能转完后自动按文件名排序合并成一个 PDF同时保留单份 PDF 是否保存的开关。v1.2 又补了两个高频需求一是文件名按“自然排序”而不是“字典序”排序二是合并后的 PDF 能按 Word 文档的一、二级标题自动生成书签目录这个版本开始接近“办公神器”的样子了。v1.3 则是对稳定性和细节的大打磨调整并发转换调度修复批量作业时的进程锁和资源冲突处理 Word 公式、特殊字体、宏安全提示等场景还引入了失败恢复机制转失败的文档会被记录到日志不会让整个任务白跑。说到一键自动化其实这种思路在 Excel 场景也一样。网上很多人找“xls 批量合并工具”核心逻辑和我这个工具是镜像关系——把多个工作簿合并成一个汇总表本质都是把重复劳动拆成流水线先采集、再合并、最后输出。万变不离其宗。2. 技术选型为什么定在 LibreOffice 无头模式 PDF 合并库2.1 候选方案对比“批量转 PDF”听起来简单真正选型时我发现每条路都有不省心的地方。我把认真比过的方案摆出来供你参考方案保真度环境依赖我的评价Word COM 自动化最高必须装完整版 Office要处理宏安全本机少量转换可以批量不稳LibreOffice 无头模式高安装 LibreOffice跨平台最终选择免费且稳定Java POI / OpenXML SDK中JDK 和一堆依赖库适合改内容不适合保全版式pandoc / LaTeX 模板低需要 LaTeX 环境纯文本报告可以复杂文档会崩Word COM 自动化方案我一开始很想用因为它保真度最高毕竟是 Office 自己渲染自己。但它在批量场景下有个硬伤需要系统里有完整的 Office 许可证还要跟 Word 的宏安全策略斗智斗勇更烦的是Word 进程偶尔会弹一个“文档已被占用”或“是否保存默认模板”的窗口调度脚本就卡死在那里。LibreOffice 无头模式虽然保真度比 Office 略低但胜在稳定、免费、跨平台而且命令行接口设计得很好适合程序化调用所以最终落在了它身上。2.2 LibreOffice 无头模式的具体用法所谓无头模式就是不弹窗、不开可视化界面只在后台调用 LibreOffice 的排版引擎。核心命令长这样soffice --headless --convert-to pdf:writer_pdf_Export --outdir /输出目录 /输入目录/文档.docx--convert-to指定目标格式pdf:writer_pdf_Export是 Writer 组件专门的 PDF 导出过滤器--outdir指定输出目录。命令行可以一次接多个文件但我强烈建议批量任务别一次性塞几十个文件给同一个进程内存容易爆而且一个文件卡住整批都得跟着卡。这里有个官方文档不会仔细讲、但批量场景必须知道的细节LibreOffice 无头模式默认使用用户配置目录里的配置如果同时启动多个进程会出现配置文件锁冲突轻则转换任务挂起重则进程直接退出。解决办法是给每个进程指定独立的用户配置目录用-env:UserInstallation参数soffice --headless -env:UserInstallationfile:///tmp/lo_profile_01 \ --convert-to pdf:writer_pdf_Export --outdir /tmp/output /tmp/input/report.docx给每个 worker 分配独立的 profile 之后并发问题基本绝迹。这个经验我写进了 v1.3 的配置项叫worker_profiles默认按并发数自动创建临时配置目录。2.3 PDF 合并为什么用 pypdf转换完的多个 PDF 需要合并。合并逻辑自己写纯属自讨苦吃——PDF 内部有页树、资源对象、字体子集、页面坐标系统手写就等于重新发明一个轮子。我直接用了开源的 pypdf 库它对合并场景支持得非常成熟。核心代码其实就是循环添加页面from pypdf import PdfReader, PdfWriter writer PdfWriter() for pdf_path in sorted_pdf_list: reader PdfReader(str(pdf_path)) for page in reader.pages: writer.add_page(page) with open(合并结果.pdf, wb) as f: writer.write(f)pypdf 支持保留原 PDF 书签也支持在合并后重新生成书签。这里提醒一句选型问题早期我用的是 PyPDF2后来这个库改组更名成了 pypdf更新更勤对新版 Python 兼容更好。你如果要自己搭直接用 pypdf别在 PyPDF2 上浪费时间官网文档现在也基本都迁过去了。2.4 让转换与合并成为一条流水线v1.3 最核心的架构变化是把“先全部转换、再统一合并”改成“边转换、边合并”的流水线。早期版本的逻辑是等所有 Word 全部转完 PDF 才开始合并中间任何一个文档出问题整个流程卡在那里不动。现在改成生产者-消费者模式转换 worker 每产出一个 PDF就送进一个待合并队列合并 worker 按全局序号从队列里取页面边转边合并首份文档转完就能看到后续进度在跑。这样做有三个实际好处第一不用把几十份 PDF 全部落盘再重新读一遍临时文件占用大幅减少第二单份文档失败不会阻塞整体日志记录好异常其他文档照常合并第三从用户体感上来讲进度是持续滚动的不会再出现“等最后那根加载条”的焦虑感。自己写流水线时最容易翻车的点是队列入队顺序不等于文档最终顺序。转换是并发的先完成的可能不是文件夹里排第一的文档如果不对全局序号排序合并端拿到的页面顺序就会错乱。我的做法是在扫描目录时给每个文件分配一个全局序号合并端按序号排队取页面稳得很。3. v1.3 版本的升级细节我用真实项目数据验证的成果3.1 测试样本与对比数据工具做出来自己用是一回事给同事用是另一回事。自从我说“这工具能省半小时”之后办公室几个部门的人都来要我干脆拿真实项目做了一轮验证。测试样本是一份季度巡检报告四十八份 Word 文档包含两套封面、六份带图表的数据明细、三份带公式的技术说明总页数一百四十七页文件总大小约 86 MB。测试机器是中端办公笔记本8 代 i516 GB 内存。结果如下场景转换耗时合并耗时失败文档备注手动操作约 22 分钟约 9 分钟1 份漏转中途被电话打断v1.2约 5 分 20 秒约 2 分 10 秒4 份公式文档异常公式字体变方块v1.3约 3 分 15 秒约 1 分 30 秒0一次跑通这个数据让我挺满意但真正让我在意的是 v1.3 把失败文档从 4 份压到了 0。这四份问题文档的根因不是转换引擎而是系统里没装数学字体。LibreOffice 转换时会用系统字体渲染字体缺失它不会报错只是用替代字体顶替公式里的符号就变成方块或者错位。v1.3 专门做了“字体预检”扫描文档用到的字体清单和系统已安装字体对比缺哪个直接弹提示问题从这里开始就被拦下来了。3.2 并发转换的窗口逻辑v1.3 另一个改动是并发窗口的调整。本机转换是 CPU 密集加内存密集的双重压力四十八份文档全开并发16 GB 内存能被直接吃满系统卡到鼠标都跟着抖开单线程又太慢效率上不去。我最后把默认并发数设为 4给每个 worker 分配独立临时目录和独立 LibreOffice profile实测 CPU 占用在 60% 到 80% 之间浮动内存稳定在 4 GB 左右既能快速跑完也不影响我同时开着浏览器查资料。这个 4 不是随手拍的我用同一批文档做了压测并发数为 2 时耗时约 6 分钟并发数 4 时约 3 分 15 秒并发数 8 时内存冲到 11 GB耗时反而只降到 2 分 50 秒性价比很低。所以默认配置写死 4并允许高级用户在配置文件里改。3.3 失败恢复与日志设计批量任务最怕的就是“跑到底发现一个文件坏了”。v1.3 引入了失败恢复机制每个文件转换前先在日志里写“开始”成功后更新状态为“完成”异常时记录错误原因并继续执行。任务结束后日志里标记为“失败”的文档会单独列出来我可以用命令一键重跑这些失败项省去整个任务从头再来的时间。日志格式我用了每文件一行 JSON方便人眼扫也方便脚本解析。字段包括文件名、全局序号、开始时间、结束时间、耗时、状态、错误摘要。有一次同事递过来一份加密文档LibreOffice 打开时要求输密码转换直接挂掉日志里清楚写着PDF_ENC_ERROR排查起来省了非常多时间。3.4 如何验证“转换保真度”而不是只看文件能打开跑通了不意味着合格我最担心的是转换后的 PDF 和原 Word 长得不像。验证保真度我用了三种办法。第一是页数对比脚本统计 Word 文档的分页数量再统计输出 PDF 的页数不一致必然是排版差异。测试那批文件里出现过三份页数对不上根因是字体缺失导致行数变多内容被挤到下一页。第二是文本层抽查从 PDF 里抽取文本内容和预设关键词对比确认没有丢字、乱码、缺行对表格数据尤其有效。第三是渲染对比把 Word 和输出 PDF 都转成图片按像素比对关键页面这个方法比较重我通常只抽查封面和复杂公式页。综合结果四十八份文档里有四十六份差异值为零两份公式文档存在几像素的轻微差异人工核对后在可接受范围。保真度过了关我才敢正式把工具分享给同事。4. 高频踩坑现场与解决办法公式、空白页、排序规则4.1 Word 公式与特殊字体在 PDF 里变成色块这个坑是大家问得最多的因为很多人手里的文档都带 Word 公式编辑器或者 MathType 对象。网上经常看到“Word 公式转 Latex”的需求我这边恰好是反过来的——文档已经用 Word 公式写好了我需要它在转 PDF 时不坏掉。第一类是 MathType 写的 OLE 对象。LibreOffice 对这类对象支持有限处理不好会变成一张难看的内嵌图片或者直接报错。我的处理办法是在转换前跑一个预处理脚本把 OLE 公式对象替换成 Word 内置公式。操作可以在 Word 里通过查找替换完成也可以写成 VBA 宏批量处理——熟悉“VBA Word 删除空白页”这类操作的朋友应该能理解本质都是对文档对象做脚本化处理。第二类是字体缺失。公式里的希腊字母和运算符需要对应字体比如 OpenSymbol、STIX 等数学字体必须安装到操作系统里。没装全转换时就会用默认字体顶替看起来就是公式变方块。现在工具里已经有字体预检同事遇到公式问题基本不用问我看提示就能去装字体。4.2 末尾空白页不是“删除段落”那么简单很多人处理 Word 末尾空白页第一反应是跑到最后一页按退格键。这个问题的根源往往是分节符布局——Word 文档最后一段结束之后如果有一个分节符且分节符后的“节”设置了不同页边距或纸张方向哪怕没有任何内容也会在 PDF 里生成一页空白。我在合并工具里没法手动去删 PDF 空页因为那样会破坏后续页的页码连续性所以只能源头治理推荐用户先把文档末尾多余的分节符删掉再丢给工具批量转换。如果文档已经转成 PDF 才发现空白页可以用 PDF 编辑器删页面但要额外注意页码是不是由 Word 域生成的删页之后页码可能不会自动更新。工具里还加了一个“空白页检测”辅助提示合并前统计每个 PDF 的页数和文本字符量如果某一页文本字符量几乎为零就把它标记出来提醒用户。大部分空白页能在合并前被发现。4.3 文件名排序自然排序和字典序相差 10 名合并顺序按文件名来那排序算法就很关键。系统默认的字典序是按字符逐位比较的所以10.docx会排在9.docx前面因为字符串比较时1比9小。如果你的文件叫“第一章、第二章……第十一章”字典序会把第十一章排到第二章前面整个 PDF 的章节顺序全乱。所以必须用自然排序算法把文件名里的数字部分按整数值比较而不是按字符串比较。这样处理完第2章才会排在第10章前面符合直觉。这个坑在 v1.1 就踩过后来写进了排序模块。现在同事都会主动把文件名规范成001-第一章.docx这种格式靠文件名就能确定顺序。如果你有自定义顺序比如“卷首语要放最后”工具支持在配置里写一个顺序映射表排序时优先按映射表映射表里没出现的文件再按自然排序落到后面。4.4 页面大小不一致合并后的观感问题同一批文档里经常既有 A4 又有 A3转出来的 PDF 单页尺寸不一样合并后就会出现部分页面特别大、部分特别小的观感。这在标书和技术方案里很常见因为有些图纸横排、有些大表格要放 A3。工具里加了一个“页面尺寸统计”合并前列出所有文档中出现的页面尺寸及出现次数。如果结果显示混用了多种尺寸工具会提示三种处理方式一是按原样合并接受尺寸不一二是统一调整为 A4 并缩放内容三是在页面间插入空白补偿页让装订时页码位置对称。实际办公场景里需求最多的是“原样合并”其次是“统一调整为 A4”。缩放我不推荐它会把表格线和图片弄模糊验收材料上容易出问题。4.5 宏安全与只读文档的边界处理批量转换时LibreOffice 后台打开 Word 文档虽然没有界面但仍会触发宏安全策略。如果文档包含 VBA 宏且系统策略禁止自动运行LibreOffice 可能弹一个确认框挂起转换任务从外面看就是“卡住不前进也不报错”。v1.3 做了折中处理默认打开文档时禁用宏运行不弹确认框。这个策略对绝大多数正规文档没有影响带恶意宏的文档也不会因为执行宏而出事。如果确实有文档必须跑宏逻辑建议单独执行宏生成最终文档再用工具做格式转换不要让批量转换工具承担运行宏的职责。只读文档也遇到过有些文档被设置成“建议只读”转换引擎不会因此拒绝但如果文档正被其他用户编辑锁定比如存在~$xxx.docx临时文件转换可能读到旧版本。所以批量转换前最好先确认本机没有打开着同一个文档。5. 半小时上手配置一键批量转 PDF 合并工具的实操流程5.1 环境准备与依赖清单想搭一套自己用的需要准备的东西并不多LibreOffice 7.3 以上版本安装时选“完整安装”确保无头模块完整Python 3.9 以上用来跑批量调度和合并脚本pypdf 库纯 Python 的 PDF 操作库安装完成后在命令行执行soffice --version确认 LibreOffice 可用再执行python -c import pypdf确认 pypdf 装好了。环境准备大概五分钟就能搞定。5.2 核心配置项解析v1.3 的配置文件用的 YAML 格式结构如下input_dir: D:/batch_in output_file: D:/output/合并报告.pdf keep_split_pdf: true split_pdf_dir: D:/output/split worker_count: 4 sort_mode: natural bookmark_from_heading: true temp_dir: D:/output/tmp font_check: true逐项解释一下input_dirWord 文档所在目录工具只处理 docx 文件output_file合并后的 PDF 输出路径keep_split_pdf是否保留每个 Word 转换后的独立 PDF标书拆分包时会用到worker_count并发转换进程数默认 4机器内存小于 8 GB 建议改成 2sort_modenatural自然排序lex字典序一般用自然排序bookmark_from_heading根据 Word 标题生成 PDF 书签如果文档标题样式不统一建议关掉font_check转换前做字体预检发现缺失字体先提示配置写好之后执行一行命令python word_pdf_merge.py --config config.yaml工具会先做配置校验检查输入目录存在、输出目录可写、文件能正常读取然后开始批量转换。5.3 三种典型办公场景的实践第一个场景是标书汇编。拿到分章节的 Word 文档按章节顺序放进一个文件夹运行工具得到一本带书签目录的 PDF。书签层级来自 Word 标题样式二级标题会变成 PDF 里的二级书签评标人拿过去直接点书签跳转体验和原生排版的电子书一样。第二个场景是合同归档。合同文件往往带签章页面和附件页页码要求从正文开始算。我的做法是在合同正文里用 Word 分节符区分封面和正文正文那节的页码重新从 1 开始然后交给工具合并输出 PDF 的页码结构就是标准的。如果一份合同本身有多个 Word 附件我把附件当作独立文件放进同一个目录统一跑省去后续手拼的麻烦。第三个场景是月度运营报告。这类文档更新频繁我建了一个固定目录放“原始素材”把当月文档往里一扔跑完工具自动输出 PDF再通过 Windows 任务计划定时执行。月度报告的产出时间从原来的半天压缩到了十五分钟。经常做周报月报的人很值得试试把“攒素材—跑工具—发结果”固定成流程人只负责审核。5.4 接入定时任务与团队共用时的注意点想把工具交给不熟悉命令行的同事用建议把命令封装成一个.bat或.sh脚本双击就能运行。脚本里做两件事先检查目录里有没有 Word 文档尚未关闭比如存在~$前缀的临时文件就提示用户先关文档再调用工具。团队共用还有一个容易踩的点输出目录如果已经存在同名 PDF工具默认按时间戳生成新文件名不直接覆盖旧文件避免某个人误操作覆盖掉上一版。确实要覆盖旧文件时可以在配置里显式打开overwrite: true。Linux 服务器上部署也支持核心就是 LibreOffice 无头模式加 Python 调度日志写好、目录权限配好晚上睡觉前丢一批文档进去第二天早上直接收结果。“web 页面打印 PDF”之类的问题原理类似浏览器后台打印也是无头渲染只不过引擎从 LibreOffice 换成了浏览器内核思路是通的。到这里v1.3 的关键细节就说得差不多了。我自己的体会是这类“小而实用”的办公工具最值钱的部分其实不是代码而是对业务流程的拆解够不够细批量转换和合并为什么要放一起文件顺序按什么排公式字体缺失时怎么兜底这些问题的答案才是真正被每天用到、真正省下了时间的东西。最后再分享一个小技巧我现在把文件命名全部固定成“序号-标题.docx”配合工具的自然排序基本不用改任何配置扔进目录就跑。如果你打算自己动手搭一套建议从一批真实的、带格式复杂度的文档开始测试别拿空白文档试——空白文档看起来全跑通了一上真实项目还得返工。