
1. 先把这个工具能干什么、不能干什么讲透本地文档格式转换工具说白了就是一套跑在自己机器上的服务把 PDF、Word、Excel、PPT 这四类文件互相倒来倒去核心引擎用的是 LibreOffice。PDF/Word/Excel/PPT 高保真互转这件事本质上不是写个脚本调一下那么简单真正难的是排版、字体、分页、表格宽度这些看起来琐碎、错了却一眼就能看出来的东西。我做过好几个需要在离线环境里批量处理文档的活儿最深的体会是转换命令本身五分钟就能跑通剩下的时间全花在为什么这份 Word 转成 PDF 之后页脚跑到第二页去了这类问题上。这篇内容适合三类人看。第一类是需要在内部系统里集成文档转换能力、又不能把文件传到外部服务的开发者第二类是经常批量处理报表、合同、课件的运营和行政岗想给自己做一个顺手的本地小工具第三类是想搞清楚 LibreOffice 命令行到底能干什么、边界在哪里的技术爱好者。我会把架构怎么拆、参数怎么调、字体怎么配、并发怎么扛、出问题怎么查一条线讲完代码和配置都能直接抄。1.1 本地转换的真实价值到底在哪把文档传到在线转换网站最直接的顾虑是内容外泄。合同、财务报表、内部汇报材料这些文件一旦离开本地风险就不是可能而是什么时候的问题。本地转换把整条链路收进自己的机器文件不出网卡日志不出磁盘这是最硬的价值。第二个价值是批量能力在线站点一次一个文件、还要等广告和验证本地工具可以挂一晚上跑几千份第二天早上直接看结果目录。第三个容易被忽略的价值是可复现。在线服务的版本、参数、字体都在别人手里今天转出来是这样明天可能就变了。本地工具你可以把 LibreOffice 版本、字体包、转换参数全部固定下来同一批文件任何时候转出来的结果都一样这对需要归档、需要审计留痕的场景非常关键。我在做文档归档的时候就把整个运行环境打成了一个固定镜像两年后重新跑旧文件输出还能对得上。第四个价值是可控的错误处理。在线接口失败了你只能重试或者放弃本地工具可以做到超时杀进程、失败进重试队列、结果做二次校验、异常文件单独隔离这套机制是批量任务的命脉。1.2 LibreOffice 在链路里的位置和它的天花板LibreOffice 在这套工具里承担的是渲染与序列化的角色。它内部有一套完整的文档模型能把 docx、xlsx、pptx 解析成自己的对象树再按照目标格式重新序列化输出。所以它不只是个文件后缀转换器而是真的在重新排版。这也是它比很多轻量库强的地方——纯文本提取库只能拿到字拿不到分页和样式。但它有明确的天花板这一点必须提前说清楚否则后面一定会失望。第一PDF 转 Word 的还原度很低。LibreOffice 导入 PDF 走的是 Draw 的 PDF 导入器结果是一个绘图文档文字能提取、图片能保留但段落结构、表格、分栏基本还原不了转出来的 docx 大概率是一堆文本框。这不是配置问题是能力边界。第二复杂排版的 Word 转 PDF 会有细微漂移尤其是用了特殊字体、复杂的图文混排、域代码、公式的地方。第三Excel 转 PDF 默认不处理打印区域超出页宽的部分会被切掉。所以我在设计工具的时候第一件事不是写代码而是把承诺划清楚Office 转 PDF 承诺高保真PDF 转 Office 只承诺文本可编辑、不承诺排版还原。这个边界一旦明确后面的产品设计和用户预期都好办。2. 整体架构一个能扛住批量的转换器长什么样单文件转换和质量保证是两件事批量转换和单文件转换又是两件事。我见过太多人写了个 for 循环遍历目录、挨个调 soffice跑十个文件没问题跑一千个就卡死、内存爆掉、进程僵在那不动。问题出在架构上不是出在命令上。所以这一节先把结构讲清楚再往下钻参数细节。2.1 三层拆分调度层、转换层、校验层我把整个工具拆成三层。调度层负责扫描输入目录、生成任务、控制并发数、管理重试队列转换层是一个个独立的 soffice 子进程每个进程只干一件事干完就退校验层在转换完成后检查输出文件是否存在、大小是否合理、页数是否对得上不合格的打回重试或者标记失败。这么拆的理由很实际。转换层是黑盒LibreOffice 内部出什么问题你没法干预唯一能做的就是隔离和兜底。把转换层做成一次性进程好处是内存泄漏、状态污染、崩溃僵死这些问题都只影响单个任务不会拖垮整个服务。调度层和校验层是纯 Python 逻辑好测试、好改。三层之间通过文件系统和任务状态表通信不搞复杂的消息队列。文件系统就是最朴素的任务队列输入目录里的文件就是待办输出目录里存在的文件就是已完成失败目录里的就是需要人看的。这套设计在几千份文件的量级上完全够用而且调试极其方便——出问题直接看目录就知道卡在哪。2.2 为什么走子进程而不是常驻 UNO 服务LibreOffice 提供了 UNO 接口可以用 Python 连上一个常驻的 soffice 进程通过 API 直接操作文档对象。这条路理论上更优雅不用反复启动进程冷启动开销省了还能精细控制导出参数。我试过结论是单机小批量、对参数要求极细的场景可以用批量转换不建议。原因有三个。第一常驻进程的状态会累积。转了几百个文件之后内存占用一路上涨偶尔还会莫名其妙地卡住你没法优雅地重启它而不影响正在跑的任务。第二UNO 的连接会断。进程崩了、网络套接字出问题客户端这边就是一堆超时异常恢复逻辑比子进程模式复杂得多。第三也是最关键的并发。一个 soffice 进程同时处理多个文档内部是单线程模型实际上还是排队想真并发就得多开进程那又回到了子进程模式。所以我选的是一任务一进程的子进程模式用独立的用户配置目录做隔离用超时强杀做兜底。代价是每个任务多几百毫秒到几秒的启动开销换来的是稳定性和可预测性。批量场景下这个交换非常值。2.3 任务模型与目录约定目录结构我固定成四个inbox放待转换文件outbox放成功结果failed放失败文件加一份同名.logtmp放运行时的临时文件和独立的 profile 目录。这套约定看起来土但用起来顺手运维同事不需要看代码就知道怎么补文件、怎么捞失败件。命名上有一个坑必须提前处理soffice 的输出文件名默认跟输入文件同名只改扩展名。如果inbox里同时有报告.docx和报告.xlsx都转 PDF输出就撞名了后转的会覆盖先转的而且不报错。我的做法是在调度层给每个任务分配一个唯一 ID 前缀转换完成后再改回可读的名字或者在输出名前拼上源文件的完整名字加哈希短码。还有一点任务状态我写在 SQLite 里字段就四个文件路径、状态、尝试次数、错误摘要。不上 Redis不上消息队列单机场景 SQLite 足够还能直接 SQL 查哪些文件重试超过三次了。3. 决定成败的核心参数与细节命令行的部分看着简单soffice --headless --convert-to pdf 文件.docx一行就能跑。但真要做到高保真和稳定参数层面的细节非常多。这一节把关键的几个点摊开讲包括过滤器名称、配置目录隔离、字体、PDF 导出选项。3.1 命令行是最小可用面但每个参数都有讲究最基础的调用长这样soffice --headless --norestore --invisible \ --convert-to pdf \ --outdir /data/outbox \ /data/inbox/季度汇报.docx--headless是不弹界面服务器上没有 X 服务必须加。--norestore是崩溃后不要弹恢复对话框不加的话某些情况下进程会挂在那里等交互批量任务直接卡死。--invisible是老参数新版可以不加加上也无害。--outdir指定输出目录不指定就输出到当前工作目录容易找不到文件。有一个行为要注意soffice 转换成功时退出码是 0但转换失败时退出码也可能是 0。我踩过这个坑脚本里判断returncode 0就当成功结果一批文件全是 0 字节的 PDF 或者压根没生成文件。所以判断成功的唯一可靠依据是目标文件存在且大小大于一个阈值我一般用 1KB 作为下限正常文档不可能更小。退出码只用来辅助判断进程是不是正常结束。另一个细节是文件路径里有空格和中文。子进程调用时不要用 shellTrue 拼字符串直接传列表参数让操作系统处理转义中文路径在 Linux 下只要 locale 是 UTF-8 就没问题Windows 下要把控制台编码设成 UTF-8 或者用宽字符接口。3.2 过滤器名称对照表不写清楚就会转错格式--convert-to后面跟的不只是扩展名完整写法是格式:过滤器名。只写扩展名的时候 LibreOffice 会猜一个默认过滤器大部分情况能猜对但有几个地方会翻车尤其是同一种扩展名对应多个过滤器的时候。常用的对照我整理成表目标格式完整写法说明PDF文档类pdf:writer_pdf_ExportWord/ODT 转 PDFPDF表格类pdf:calc_pdf_ExportExcel/ODS 转 PDFPDF演示类pdf:impress_pdf_ExportPPT/ODP 转 PDFdocxdocx:MS Word 2007 XML必须带引号含空格xlsxxlsx:Calc MS Excel 2007 XML表格工作簿pptxpptx:Impress MS PowerPoint 2007 XML演示文稿htmlhtml:HTML (StarWriter)文档转网页txttxt:Text (encoded):UTF8纯文本编码必须指定这里最容易错的点是转 PDF 的时候三种文档类型用的是三个不同的过滤器。如果你用writer_pdf_Export去转一个 xlsxLibreOffice 会先把它当作文档打开结果可能是只有第一张表、或者整个表格被压成一页。反过来用一个通用写法--convert-to pdf让程序自己猜大部分时候是对的但批量场景下我强烈建议显式指定因为猜错的时候往往是静默的你不会收到任何报错。引号那块也要注意。过滤器名里有空格在 shell 里必须用引号包住整段在 Python 的 subprocess 里则是作为一个完整字符串传进去不要自己拆。写成--convert-to和docx:MS Word 2007 XML两个列表元素前者是参数名后者是参数值。3.3 用户配置目录必须隔离这是并发的前提LibreOffice 启动的时候会在用户目录下建一个 profile路径大概是~/.config/libreoffice/4。这个目录里有锁文件、缓存、最近文档列表、扩展配置等等。同一个 profile 同时被两个 soffice 进程使用第二个进程要么直接退出要么卡住等锁现象就是我明明发了十个任务只有一个在跑。解决办法是每个进程指定独立的 profilesoffice --headless --norestore \ -env:UserInstallationfile:///tmp/lo_profile_7f3a \ --convert-to pdf --outdir /data/outbox /data/inbox/a.docx注意-env:是单横线不是双横线这是 LibreOffice 的扩展参数语法写错了会被当成文件路径。路径必须用file://开头的 URI 形式相对路径和裸绝对路径都可能失败。隔离之后还有两个现实问题。第一新 profile 首次启动会做一堆初始化冷启动时间明显变长大概多出两到三秒。第二profile 目录会占磁盘一个空 profile 大概几十兆跑一千个任务就是几十 G必须做清理。我的做法是给每个任务建一个临时 profile任务结束后整个目录删掉用tempfile.mkdtemp()管理生命周期异常时在 finally 里清理。还有一个可优化点可以预先准备一个干净模板 profile里面已经装好了必要的扩展和字体配置任务开始时直接复制一份比从零初始化快得多。这个技巧在任务量大的时候能省不少时间。3.4 字体是高保真的第一道生死线我见过最典型的问题一份 Word 文档在本地打开好好的转成 PDF 之后全是方框或者中文变成了宋体、英文变成了 Times New Roman行距全乱。原因几乎都是字体缺失。LibreOffice 找不到文档里声明的字体就用替代字体顶上替代字体的字宽、字高、基线都跟原字体不一样整个排版就跟着移位。解决方案分三步。第一步查清文档用了什么字体。docx 本质是个 zip解压后看word/fontTable.xml和word/document.xml里面的w:rFonts节点就写着字体名。批量场景下可以写个脚本把所有文档的字体名扫一遍做一次统计你就知道该装哪些字体了。第二步装字体。Linux 下把 ttf/otf 文件丢进/usr/share/fonts/truetype/下的一个自建目录然后跑fc-cache -fv刷新缓存。这一步不能省不刷缓存 fontconfig 不认识新装的字体。装完用fc-list | grep 字体名确认能被识别到。中文文档一般至少需要一套宋体、一套黑体、一套楷体、一套仿宋再加上常用英文字体。第三步做映射。有些商业字体没法装就得用fontconfig的别名机制做替代。在/etc/fonts/conf.d/下加一个配置文件把缺失的字体名映射到已装的近似字体上比如把某个宋体变体映射到思源宋体。这样至少在字宽上接近排版漂移会小很多。提示字体这件事没有捷径只能老老实实做需求统计。我在一个项目里因为漏装了一套等线字体导致几百份 PPT 转出来的标题全部换行位置错位返工重跑花了大半天。装字体成本极低不装代价极高。3.5 PDF 导出参数的精细调节LibreOffice 7.x 之后支持用 JSON 形式传过滤器参数这就打开了精细调节的口子。写法是把 JSON 直接拼在--convert-to的值后面soffice --headless \ --convert-to pdf:writer_pdf_Export:{SelectPdfVersion:{type:long,value:1},EmbedStandardFonts:{type:boolean,value:true},UseTaggedPDF:{type:boolean,value:true},ExportBookmarks:{type:boolean,value:true},MaxImageResolution:{type:long,value:300}} \ --outdir /data/outbox /data/inbox/a.docx几个参数值得单独说。SelectPdfVersion设成 1 是对应 PDF/A-1适合归档场景好处是强制嵌入字体、不允许加密保证十年后还能打开。EmbedStandardFonts打开后会把标准字体也嵌进去文件会大一些但换机器打开绝对一致做交付件的时候我都开着。UseTaggedPDF生成带标签的 PDF无障碍阅读和后续用工具提取结构都用得上代价是文件略大、转换略慢。MaxImageResolution控制图片压缩后的最大 DPI设 300 基本能保住印刷质量设 150 能把文件压到原来的一半以下。还有一个和 PDF 相关的隐藏坑文档里的超链接、书签、交叉引用默认导出行为在不同版本里不一样。如果你的 PDF 是给外部看的ExportBookmarks记得开如果文档里有大量内部链接导出后点不动是很常见的投诉原因就是参数没配。4. 从零跑起来实操全流程前面讲的是为什么这一节讲怎么做。我会按顺序走一遍环境准备、单文件脚本、批量并发、结果校验每一步都给可直接用的代码或命令。4.1 环境准备与自检Linux 下装 LibreOffice 建议装完整包不要只装 core。命令是apt install libreoffice libreoffice-writer libreoffice-calc libreoffice-impress加上fonts-noto-cjk把中文字体一起带上。装完之后先跑一遍自检soffice --version fc-list :langzh | head -20第一条确认版本第二条确认系统中文字体情况。如果fc-list一条中文都没有那后面所有转换都会出问题先解决字体再说。Windows 下路径是C:\Program Files\LibreOffice\program\soffice.exe注意程序目录下有个soffice.com那是控制台版本做子进程调用时用.com更容易拿到输出.exe是窗口版本在某些情况下不返回 stdout。还有一点服务器上如果没有图形环境--headless是必须的但有些老版本仍然会尝试连接显示服务。稳妥的做法是装一个libreoffice-headless包或者设置环境变量SAL_USE_VCLPLUGINsvp强制用无头后端。4.2 单文件转换的最小可用脚本先写一个能跑通的最小版本把所有关键参数都带上import os import subprocess import tempfile import shutil SOFFICE /usr/bin/soffice def convert(src, outdir, fmtpdf:writer_pdf_Export, timeout180): profile tempfile.mkdtemp(prefixlo_profile_) cmd [ SOFFICE, --headless, --norestore, --invisible, f-env:UserInstallationfile://{profile}, --convert-to, fmt, --outdir, outdir, src, ] try: proc subprocess.run( cmd, stdoutsubprocess.PIPE, stderrsubprocess.PIPE, timeouttimeout, start_new_sessionTrue, ) detail (proc.stdout proc.stderr).decode(utf-8, ignore) return proc.returncode, detail except subprocess.TimeoutExpired: return -1, timeout finally: shutil.rmtree(profile, ignore_errorsTrue)这里每一个参数都有来历。start_new_sessionTrue是关键它让 soffice 在独立进程组里跑超时的时候可以整组杀掉。如果不开这个subprocess的 timeout 只会杀掉父进程soffice 派生的子进程会变成孤儿继续占着内存和 CPU跑几百个任务之后机器就废了。超时值 180 秒是给大文件留的余量。一般几十页的 Word 转 PDF 在 5 到 15 秒之间上百页的、图片特别多的可能要 30 到 60 秒。设太短会误杀正常任务设太长又会让卡死的任务占着并发槽。我的经验值是 180 秒超过这个时间的文件基本都有问题值得人工看一眼。4.3 并发池与超时强杀并发数的选择要按机器配置来。每个 soffice 进程转换时占 100 到 300MB 内存CPU 主要吃在首次启动和渲染阶段。我的经验公式是并发数 min(CPU 核心数 / 2, 内存GB / 0.5)。八核十六 G 的机器开四个并发比较稳开八个也能跑但内存容易打满遇到大文件就 OOM。并发实现用concurrent.futures.ThreadPoolExecutor就够了因为真正的并行发生在子进程里线程只是负责发起和等待。每个线程内部再调上面的convert函数。超时强杀这一段要单独写清楚因为默认的 kill 行为不够import os, signal, subprocess proc subprocess.Popen(cmd, stdoutsubprocess.PIPE, stderrsubprocess.PIPE, start_new_sessionTrue) try: out, err proc.communicate(timeout180) except subprocess.TimeoutExpired: os.killpg(os.getpgid(proc.pid), signal.SIGTERM) try: proc.communicate(timeout10) except subprocess.TimeoutExpired: os.killpg(os.getpgid(proc.pid), signal.SIGKILL) raise先发SIGTERM让进程有机会清理临时文件等十秒还不退再SIGKILL。直接上SIGKILL的问题是可能在 profile 目录里留下锁文件虽然我们已经做了目录隔离、用完就删但有的版本的 soffice 会在系统临时目录留东西日积月累也是麻烦。4.4 结果校验怎么判断转成功了前面说过退出码不能信所以要自己做校验。校验分三层从轻到重。第一层看文件存在和大小。目标路径存在、大小超过 1KB基本可以认为生成了有效文件。第二层看文件能否被解析PDF 可以用pdfinfo读页数或者用 PyPDF2 打开一次读不出来就是坏的。第三层看内容合理性比如源文档有 30 页转出来的 PDF 只有 2 页那肯定是渲染出错需要人工介入。from PyPDF2 import PdfReader def check_pdf(path, min_pages1): if not os.path.exists(path) or os.path.getsize(path) 1024: return False, 文件不存在或过小 try: reader PdfReader(path) n len(reader.pages) except Exception as e: return False, f解析失败: {e} if n min_pages: return False, f页数异常: {n} return True, fok, {n} 页页数校验的参考值不好自动获取我的做法是把同批次的文档做一个中位数统计页数明显偏离中位数的比如只有中位数的十分之一标黄人工抽查。这套规则拦下了不少问题比如扫描版 PDF 被误当作可编辑文档处理后变成空白。5. 各格式互转的真实效果与边界这一节讲实际体验。工具做出来能不能用取决于你对每种转换路径的预期是否合理。我按 Office 转 PDF、PDF 转 Office、以及 Excel/PPT 的特殊处理三条线讲。5.1 Office 转 PDF最稳的一条路但要盯住排版漂移Word 转 PDF、Excel 转 PDF、PPT 转 PDF是这套工具里质量最高的三条路径也是主要使用场景。正常情况下还原度能到九成以上字体装齐、页边距标准、没有复杂图形的文档基本可以做到肉眼无差。漂移主要出现在几个地方。一是分页位置。Word 的分页依赖于渲染引擎的度量LibreOffice 和 Word 在行高、字间距的计算上有细微差别长文档的最后一页可能差一两行。二是表格宽度。用了自动调整的表格转出来可能某一列被挤得很窄。三是页眉页脚的域代码比如自动页码、总页数一般能正常渲染但用了复杂域的地方可能显示异常。四是图表和公式简单的柱状图饼图没问题带复杂格式的、用了 Office 独有公式编辑器的转出来会变形。应对办法是建立一份回归样本库。我这边的做法是攒二十个有代表性的文档带页眉页脚的长文档、多级列表、复杂表格、图文混排、带图表的报表、带公式的技术文档每次调整参数或者升级 LibreOffice 版本都拿这批文件跑一遍人工看一遍输出。这个习惯能挡住九成的版本升级事故。5.2 PDF 转 Office只承诺文本可编辑别承诺排版前面提过这条路还原度很低但需求又特别旺盛所以要把能力和话术都设计好。PDF 导入 LibreOffice 走的是 Draw 的导入器结果是绘图文档每一行文字可能是一个独立的文本框图片是独立对象页面上看起来跟原 PDF 一模一样但你打开编辑的时候就会发现全是散件。我的处理策略是分两档。第一档是提取可编辑文本把 PDF 转成 txt 或者 docx接受段落结构丢失只保证文字内容完整可用。第二档是保留视觉外观转成 odg 或者直接渲染成图片再嵌进 docx文字不可编辑但外观一致。用户需要哪一档界面上让他选别指望一个按钮解决所有问题。如果确实需要高保真的 PDF 转 Word那得引入版面分析能力识别文字块、段落、表格、图片位置再重新构建 docx。这已经超出 LibreOffice 能做的范围属于另一个技术栈。我的建议是把这个需求明确排除在工具的核心范围之外避免做出一个什么都能转但什么都转不好的东西。5.3 Excel 和 PPT 的特殊坑Excel 转 PDF 最大的问题是打印区域和缩放。默认情况下 LibreOffice 按表格的实际内容分页宽表会被横向切成好几页右边的内容跑到第二页去了看起来很怪。解决办法有两个一是在转换前用宏或者 UNO 脚本设置页面属性为横向、缩放到一页宽二是在转换命令里指定 calc 的 PDF 过滤器参数控制分页行为。用脚本设置页面属性的方式更可控。思路是先用 Python 的 UNO 接口打开文件设置PageStyle的ScaleToPagesX 1、IsLandscape True保存后再转 PDF。这条路要引入常驻进程但对于表格类文档质量提升是明显的。折中方案是只对宽度超过阈值的表格做这个处理正常表格直接转。PPT 转 PDF 相对干净主要问题是动画、切换效果、演讲者备注。转成 PDF 之后动画自然没有了这个要提前告知。另一个问题是字体替换PPT 里炫技用的特殊字体最容易被替换一旦替换文本框宽度不够就会换行整个版式就歪了。所以字体检查在 PPT 场景下比在文档场景下更重要。还有一个细节是页面尺寸宽屏 16:9 的 PPT 转成 PDF 之后如果 PDF 导出参数里没指定页面尺寸可能变成 A4 比例两侧留白或者内容被压缩最好在导出参数里显式指定跟源文件一致的尺寸。6. 常见问题排查实录这一节是我这些年踩过的坑汇总按现象分类每条都给排查路径。遇到问题先查这张表能省掉大量试错时间。6.1 中文乱码、方框、字体替换现象是转换后的 PDF 里中文全是方框或者变成了完全不像的字体。排查顺序是这样的先跑fc-list :langzh确认系统里有多少中文字体再用fc-match 文档里声明的字体名看实际匹配到了哪个字体如果返回的是 DejaVu 之类的西文字体那就说明没匹配上最后检查fc-cache有没有刷新。还有一种情况是字体装了但 LibreOffice 没认。这是因为 LibreOffice 有自己的字体缓存位置在 profile 目录里。做目录隔离的时候每个新 profile 都要重新扫描字体第一次启动会慢一点。如果发现装了字体还是方框可以试试删掉 profile 目录重新跑或者干脆用系统默认 profile 跑一次让它刷新缓存。Windows 下的乱码原因不太一样多半是编码问题。源文件是 GBK 编码的 txt或者文件路径里有非 ASCII 字符子进程调用时没有正确传递。解决办法是统一用 UTF-8Python 里显式给encodingutf-8路径用os.fsencode处理。6.2 进程僵死与并发冲突现象是任务提交上去之后一直不返回CPU 占用为零也没有任何输出。九成是 profile 冲突。检查方法很简单看命令行里有没有-env:UserInstallation路径是不是每个进程都不一样。如果两个进程用了同一个 profile第二个会卡在等锁上永远不会超时退出只能手动杀。第二种僵死是 LibreOffice 崩溃后留下的恢复对话框。虽然加了--norestore但某些异常情况下还是可能弹出。彻底的解决办法是在 profile 里预置配置关掉恢复功能或者用SAL_DISABLE_OPENCL1之类的环境变量规避一些已知的崩溃路径。第三种是内存耗尽。多个大文件同时转换每个进程吃几百兆机器 swap 打满之后所有进程都变慢看起来像僵死。监控内存占用把并发数调低或者给单个任务加内存上限用 cgroup 或者ulimit都是办法。现象最可能的原因处理方式输出 0 字节过滤器名写错 / 源文件损坏显式指定过滤器先用 GUI 打开源文件验证中文变方框中文字体缺失装字体fc-cache -fv刷新进程卡住不退出profile 冲突每个进程独立 UserInstallation 目录宽表被切断未设置打印区域转换前设置横向、缩放一页宽部分文件无输出但退出码 0静默失败用文件存在性和大小做校验不信任退出码转换极慢首次启动 / 图片过多预热 profile压缩图片分辨率内存持续上涨常驻进程泄漏改为一任务一进程用完即退6.3 性能数据与调优方向实测数据放在这里方便你对号入座。八核十六 G 的机器单进程冷启动转换一份 5 页的 Word 到 PDF大约 3 到 5 秒其中启动占 2 秒左右。同样文件热启动profile 已存在大约 1.5 到 2.5 秒。一份 50 页带图的文档转换时间 10 到 20 秒。一份 20 张幻灯片的 PPT5 到 10 秒。一份 1000 行的 Excel 转 PDF3 到 8 秒如果设置了缩放到一页宽会慢一些。并发四个进程时整体吞吐大概是单进程的三倍左右不是四倍因为启动阶段的 IO 和 CPU 会互相争抢。并发八个的时候吞吐提升有限反而容易出现内存压力。所以我的建议是并发数不要超过核心数的一半。优化方向按收益排序第一是预热 profile能省掉每个任务的初始化开销第二是减少不必要的格式转换中间不要绕道第三是控制图片分辨率超过 300 DPI 的图片对屏幕阅读毫无意义第四是给输出做缓存同一个文件的哈希值算出来如果之前转过且参数没变直接复用结果。缓存这一条在重复处理场景下收益巨大尤其是那种每天跑一遍全量归档的任务。7. 几条我反复验证过的硬经验做这类工具最值钱的不是代码是那些踩过之后才明白的常识。第一条不要相信任何转换成功的信号只相信输出文件本身。退出码、标准输出里的 convert ... - ... 提示、甚至文件的修改时间都可能骗你。我现在的校验逻辑是先删掉可能存在的旧输出文件转换完再检查文件是否重新出现这样能排除掉这次失败了但上次的残留还在的误判。第二条把字体问题当成项目的一等公民来对待。开工第一件事就是统计需求文档用到的所有字体列一个清单缺什么补什么能拿到授权的装原字体拿不到的做映射。这件事花半天能省掉后面无数次的返工和解释。我见过最惨的情况是转换服务上线两周后才发现所有对外文档的标题字体都被换掉了因为这个错误不会报错只有设计师一眼看出来。第三条给每一种转换路径都准备一份责任声明。Office 转 PDF 承诺排版一致PDF 转 Office 明确说明只做文本提取扫描件需要走 OCR 且不在本工具范围内带宏的文件会丢失宏带外部链接的表格链接会断开。这些话提前写清楚比事后解释一百遍都管用。第四条日志要记到能复现的程度。我的日志里记源文件路径、目标格式、完整命令行、耗时、退出码、输出文件大小、profile 路径。出问题的时候能拿着命令行原样重跑一次比翻代码快十倍。日志按天切分保留三十天体积不大但够用。最后分享一个小技巧。如果你要批量处理历史文档别一股脑全塞进去跑。先按文件大小和页数做分层小的先跑一批看看质量中等的一批大的单独跑。因为大文件最可能出问题混在一起跑的时候一个大文件卡住会拖慢整个队列你分不清是普遍问题还是个别问题。分层跑出问题的范围一眼就清楚了。这套流程我在几个不同规模的归档任务里都用过每次都能在早期发现配置问题避免了几千份文件全部重跑的灾难。