☰
PDF批量压缩实战:Ghostscript与Python脚本详解
2026/10/23 9:26:11 网站建设 项目流程

在日常办公、资料归档、邮件附件上传以及网盘同步这些场景里,PDF 体积过大是很多人都会遇到的一个痛点。一份文件动辄几十兆,不仅占空间,上传下载也慢;如果手里积压了成百上千个 PDF,一个一个用编辑器另存去压缩,工作量会非常可怕。本文以“批量压缩 PDF”为目标,从压缩原理讲起,给出命令行方案和 Python 脚本方案,并提供完整可复制的代码、运行步骤、常见报错排查和工程建议,适合有一定办公自动化需求的学生、HR、财务、档案管理员和开发者阅读。

先提醒一点:批量压缩和“批量降低图片画质”不是一回事。如果你只是粗暴地把 PDF 的每一页转成低分辨率图片再重新生成 PDF,页面上原本可选的文本会丢失,打印效果也会明显变差。正确的做法应该是有选择地去重采样图片、清理无用对象、压缩内容流,并保留文字层和基本结构。下面,我们围绕这套思路逐步展开。

1. 背景与核心概念

1.1 PDF 体积为什么越来越大

PDF 并不是一种“越存越小”的格式。很多 PDF 在生成时为了兼容性,会嵌入整套字体;通过 Word、WPS 或设计软件导出的 PDF 往往还保留了大量编辑信息;扫描仪生成的 PDF 更是直接把每一页保存为高分辨率图片。这些因素叠加在一起,文件体积就会快速膨胀。

从根本上说,PDF 是一个容器,它内部至少包含四类东西:

  • 文本内容和字体:文本本身不大,但嵌入的完整字体文件可能有好几兆。
  • 图片对象:扫描件、截图、商品图、流程图,这些是 PDF 体积的大头。
  • 内容流:描述页面布局、绘制指令的代码,通常是可压缩的。
  • 元数据和辅助对象:书签、缩略图、属性信息、冗余对象。

如果你的 PDF 是从 CAD、PS 或高分辨率扫描设备导出的,图片部分占比通常超过 90%。这时候,压缩 PDF 的核心问题,就变成了在“视觉可接受”和“文件体积”之间做平衡。

1.2 批量压缩的典型应用场景

批量压缩绝不是“把单个文件多处理几次”那么简单。常见的应用场景包括:

  1. 电子档案归档:大批量扫描件转存的 PDF 需要长期保存,体积过大会浪费存储空间。
  2. 邮件附件发送:多数邮箱附件限制在 20MB 到 50MB 之间,超大 PDF 需要降体积后才能发送。
  3. 网盘同步与知识库搭建:压缩后能显著减少上传时间,也便于检索和分享。
  4. 电子书整理:从网上下载或自行排版的 PDF 书刊,压缩后可存入阅读器,节省设备空间。
  5. 业务流程处理:比如人力资源部门批量处理简历、合同,财务部门批量归档电子发票。

这些场景里,如果只靠人工打开软件另存为,效率极低且容易漏处理。批量压缩的价值在于:一次写脚本,永久复用。

1.3 压缩不是简单降低分辨率

很多新手会对“压缩 PDF”产生一个误解:把每页导出成图片,缩小后再拼回 PDF。这种思路只适合打印质量要求极高且不需要文本层的场景。绝大多数办公场景下,我们要的是:

  • 尽量保留文字和矢量信息的可编辑性;
  • 尽量保留页面的排版效果;
  • 对内嵌图片做适度重采样;
  • 只删除冗余对象和无效元数据。

所以,批量压缩应该理解为“基于 PDF 内部结构优化”,而不是“把 PDF 当图片重绘”。

2. 环境准备与版本说明

PDF 压缩可以用不同技术栈实现:Java 、Python、命令行工具都可以。本文选择“Python + Ghostscript”的组合,原因是它跨平台、安装简单、脚本适合批量化,且 Ghostscript 的 PDF 优化引擎足够成熟,压缩效果明显。

2.1 推荐技术路线

推荐路线如下:

  • 操作系统:Windows 10/11、macOS、Linux 均可,本文示例以通用命令说明为主。
  • 脚本语言:Python 3.8 及以上版本,低版本请留意pathlib和subprocess的兼容性。
  • 压缩引擎:Ghostscript,这是开源的 PostScript 和 PDF 解释器,内置pdfwrite输出设备,专门用于 PDF 重建和优化。
  • Python 库:pikepdf和PyMuPDF作为备选方案,用于兼容加密 PDF、清理无用对象等场景。

版本需要根据你的实际环境调整,本文重点是配置和代码思路。比如你使用的是 Python 3.12 或 3.13,依赖安装方式不变,但个别库的底层版本可能影响运行结果,建议先用小样本测试。

2.2 安装 Ghostscript

Ghostscript 的安装方式按系统区分:

  • Windows:从官方渠道下载安装包(如 Ghostscript 的 Windows 安装程序),安装时可以选择“添加到系统 PATH”。如果没勾选,安装后在命令行中找不到gswin64c,脚本就需要写绝对路径。
  • macOS:执行brew install ghostscript,安装后命令名一般是gs。
  • Linux(Debian/Ubuntu):执行sudo apt-get install ghostscript;CentOS/RHEL 上可以使用sudo yum install ghostscript。

安装完成后,在终端执行:

gs --version

如果能输出版本号,说明安装成功。Windows 用户如果遇到“不是内部或外部命令”,说明 Ghostscript 没有加入 PATH,后面会单独说解决方法。

2.3 安装 Python 依赖

Python 侧建议新建一个虚拟环境,避免依赖冲突:

python -m venv pdf_compress_env

激活环境后安装依赖:

# Windows pdf_compress_env\Scripts\activate # macOS / Linux source pdf_compress_env/bin/activate

然后安装库:

pip install pikepdf pymupdf

如果网络较慢,可以加国内镜像源:

pip install pikepdf pymupdf -i https://pypi.tuna.tsinghua.edu.cn/simple

安装完成后,可以进入 Python 交互环境做一个导入验证:

import pikepdf import fitz print(pikepdf.__version__) print(fitz.__doc__)

能正常打印版本信息,说明环境就绪。

3. 批量压缩的核心原理

3.1 PDF 里的体积来源分析

要写出真正有效的压缩脚本,首先要大概知道 PDF 的体积来源。文本 PDF 和扫描件 PDF 的压缩策略完全不同:

  • 文本型 PDF(Word 导出、LaTeX 生成的论文、公众号排版导出的文档):体积主要来自嵌入字体和内容流,压缩重点在于字体子集化和流压缩,Ghostscript 能很好处理。
  • 图片型 PDF(扫描仪生成、PS 导出、产品图册):体积主要来自内嵌图片,压缩重点在于图片重采样。如果图片是 300 DPI 的彩色扫描件,改成 150 DPI 往往体积能减少一半以上。
  • 混合型 PDF:既有大量文字,又有插图、照片、封面图,需要综合处理。

如果你不确定 PDF 的体积类型,可以先开 Python 看一眼页面里的图片数量、颜色空间和分辨率,再决定压缩参数。

3.2 三种主流压缩手段

常见的批量压缩手段可以归纳为三类:

第一类:内容流压缩。PDF 内部的页面内容流可以使用 Flate 等算法压缩,compress_streams就属于这类。它适合对已经解压过的 PDF、或是由某些工具生成时未开启压缩的 PDF。这类方法不改变画面,无损程度最高,但压缩率有限。

第二类:图片重采样。把高分辨率的位图降到合理分辨率,比如从 300 DPI 降到 150 DPI,并统一图片颜色空间。这是大幅减小体积的最有效手段,但会造成一定的细节损失。

第三类:移除冗余对象。PDF 重新生成时,可以丢弃未引用的对象、过时的元数据、重复字体。garbage、clean参数就是做这一类清理。

一份成熟的压缩方案通常三管齐下。Ghostscript 就是通过组合这些策略,把原始 PDF 重建为一个更精简的新 PDF。

3.3 主流工具横向对比

下面这个表格能帮你理解三种常用工具各自的定位:

工具/库定位优点局限
Ghostscript命令行压缩引擎压缩层度深,支持 DPI 重采样,批量处理稳定需要额外安装,参数较多,不同版本参数细节有差异
pikepdfPython PDF 库无损清理、压缩流,适合加密 PDF,API 清晰不做图片重采样时体积下降有限
PyMuPDFPython PDF 库可清理无用对象,支持渲染和图片操作轻量级清理为主,复杂图像压缩需配合其他手段

在实际工程中,推荐把 Ghostscript 作为主压缩引擎,把 pikepdf 作为“打不过 Ghostscript 时”的兜底工具,比如遇到损坏 PDF 或加密 PDF,可以先用 pikepdf 尝试修复和重建。

4. 完整实战:命令行批量压缩 PDF

命令行方案适合临时处理、服务器环境或不需要写完整 Python 程序的场景。它的核心是 Ghostscript。

4.1 使用 Ghostscript 压缩单个 PDF

先看单个文件的压缩命令。假设输入文件是input.pdf,输出文件是output.pdf:

gs \ -sDEVICE=pdfwrite \ -dCompatibilityLevel=1.7 \ -dPDFSETTINGS=/ebook \ -dNOPAUSE \ -dQUIET \ -dBATCH \ -sOutputFile=output.pdf \ input.pdf

参数含义如下:

  • -sDEVICE=pdfwrite:告诉 Ghostscript 使用 PDF 输出设备,目标是生成一个新的 PDF 文件。
  • -dCompatibilityLevel=1.7:输出的 PDF 兼容 1.7 版本,绝大多数阅读器都支持。
  • -dPDFSETTINGS=/ebook:选用“电子书”优化级别,适合普通办公文档。
  • -dNOPAUSE、-dBATCH:不暂停、不交互,适合批量处理。
  • -dQUIET:减少不必要的输出,日志更干净。

执行完成后,对比input.pdf和output.pdf的大小,就能看出压缩效果。

4.2 用批处理脚本压缩文件夹

如果只有几十个 PDF,可以用终端循环。Windows CMD 里可以写:

@echo off set INPUT_DIR=D:\pdf_input set OUTPUT_DIR=D:\pdf_output mkdir %OUTPUT_DIR% 2>nul for %%f in (%INPUT_DIR%\*.pdf) do ( gswin64c -sDEVICE=pdfwrite -dPDFSETTINGS=/ebook -dNOPAUSE -dBATCH -dQUIET -sOutputFile=%OUTPUT_DIR%\%%~nf.pdf %%f ) echo batch compress done

macOS 或 Linux 的 Bash 脚本可以写:

#!/bin/bash input_dir="pdf_input" output_dir="pdf_output" mkdir -p "$output_dir" for pdf in "$input_dir"/*.pdf; do name=$(basename "$pdf") gs -sDEVICE=pdfwrite -dPDFSETTINGS=/ebook -dNOPAUSE -dBATCH -dQUIET \ -sOutputFile="$output_dir/$name" "$pdf" done echo "batch compress done"

命令行方案的优点是简单直接,缺点是:

  • 无法自动跳过已经压缩过的文件;
  • 无法自动记录每个文件的压缩率;
  • 无法处理文件名带空格或者路径特殊符号时产生的小问题;
  • 用户对整个流程的透明度不高。

所以当你打算把压缩归档变成一个稳健的生产工具时,应该转用 Python 脚本。

5. 完整实战:Python 批量压缩脚本

这一章是文章的核心。我会给出一个可以直接复制运行的 Python 脚本,它不仅能批量压缩 PDF,还会输出压缩日志,跳过不需要处理的文件,并在压缩失败时自动降级处理。

5.1 项目结构

建议按照下面的结构组织文件:

pdf_batch_compress/ ├── pdf_input/ # 放原始 PDF ├── pdf_output/ # 放压缩后的 PDF ├── logs/ # 保存运行日志 └── batch_pdf_compress.py

提前把pdf_input、pdf_output、logs三个目录创建好。脚本会自动创建目录,但手动创建便于你理解文件路径关系。

5.2 核心代码:调用 Ghostscript 批量压缩

下面是一个完整的 Python 脚本,文件路径为pdf_batch_compress/batch_pdf_compress.py:

""" 批量压缩 PDF 脚本 依赖: Python 3.8+, Ghostscript 用法: python batch_pdf_compress.py \ --input pdf_input \ --output pdf_output \ --settings ebook \ --min-size-kb 100 \ --skip-existing """ import argparse import logging import shutil import subprocess import time from pathlib import Path logger = logging.getLogger("pdf_compress") # Ghostscript 四档预设 SETTINGS_MAP = { "screen": "/screen", # 屏幕预览,体积最小 "ebook": "/ebook", # 电子阅读与办公文档 "printer": "/printer", # 打印机分辨率 "prepress": "/prepress", # 印刷输出级,体积较大 } def find_ghostscript(): """查找系统中的 Ghostscript 可执行文件。""" candidates = ["gs", "gswin64c", "gswin32c"] for name in candidates: path = shutil.which(name) if path: return path raise FileNotFoundError( "未找到 Ghostscript,请先安装并配置 PATH," "或者修改脚本中 candidates 为绝对路径。" ) def compress_pdf( gs_path: str, input_pdf: Path, output_pdf: Path, settings: str = "/ebook", ) -> bool: """使用 Ghostscript 压缩单个 PDF。""" output_pdf.parent.mkdir(parents=True, exist_ok=True) cmd = [ gs_path, "-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.7", f"-dPDFSETTINGS={settings}", "-dNOPAUSE", "-dBATCH", "-dQUIET", "-sOutputFile", str(output_pdf), str(input_pdf), ] # 捕获标准输出和标准错误,便于排查问题 result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: logger.error("压缩失败: %s", input_pdf.name) logger.error(result.stderr[-1500:] if result.stderr else "无错误信息") return False return output_pdf.exists() and output_pdf.stat().st_size > 0 def batch_compress( input_dir: Path, output_dir: Path, settings: str, min_size_kb: int, skip_existing: bool, use_pikepdf_fallback: bool, ) -> None: """批量遍历输入目录下的 PDF 并压缩。""" gs_path = find_ghostscript() # 只处理 PDF 文件 pdf_files = list(input_dir.rglob("*.pdf")) if not pdf_files: logger.warning("输入目录下没有找到 PDF 文件。") return logger.info("共发现 %d 个 PDF 文件", len(pdf_files)) success_count = 0 skip_count = 0 fail_count = 0 total_size_before = 0 total_size_after = 0 for index, input_pdf in enumerate(pdf_files, start=1): size_before = input_pdf.stat().st_size # 跳过过小文件 if size_before < min_size_kb * 1024: logger.info("[%d/%d] 跳过过小文件: %s (%.1f KB)", index, len(pdf_files), input_pdf.name, size_before / 1024) skip_count += 1 continue # 输出路径:保持相对目录结构是更稳妥的选择 relative_path = input_pdf.relative_to(input_dir) output_pdf = output_dir / relative_path if skip_existing and output_pdf.exists(): logger.info("[%d/%d] 跳过已存在文件: %s", index, len(pdf_files), output_pdf.name) skip_count += 1 continue logger.info("[%d/%d] 正在压缩: %s (%.1f MB)", index, len(pdf_files), input_pdf.name, size_before / 1024 / 1024) ok = compress_pdf(gs_path, input_pdf, output_pdf, settings) if not ok and use_pikepdf_fallback: # 如果是 Ghostscript 无法处理,则尝试用 pikepdf 兜底 ok = compress_with_pikepdf(input_pdf, output_pdf) if ok: size_after = output_pdf.stat().st_size ratio = (1 - size_after / size_before) * 100 if size_before else 0 total_size_before += size_before total_size_after += size_after success_count += 1 logger.info(" 压缩后: %.1f MB,压缩率 %.1f%%", size_after / 1024 / 1024, ratio) else: fail_count += 1 logger.info("处理完成: 成功 %d 个,跳过 %d 个,失败 %d 个", success_count, skip_count, fail_count) if total_size_before: logger.info("总体积从 %.1f MB 降至 %.1f MB,减少 %.1f%%", total_size_before / 1024 / 1024, total_size_after / 1024 / 1024, (1 - total_size_after / total_size_before) * 100) def compress_with_pikepdf(input_pdf: Path, output_pdf: Path) -> bool: """如果 Ghostscript 失败,使用 pikepdf 进行无损清理压缩。""" try: import pikepdf with pikepdf.open(input_pdf) as pdf: pdf.save( output_pdf, compress_streams=True, object_stream_mode=pikepdf.ObjectStreamMode.generate, ) return output_pdf.exists() and output_pdf.stat().st_size > 0 except Exception as exc: logger.error("pikepdf 兜底失败: %s", exc) return False def main(): parser = argparse.ArgumentParser(description="批量压缩 PDF 文件") parser.add_argument("--input", type=Path, default=Path("pdf_input"), help="输入 PDF 目录") parser.add_argument("--output", type=Path, default=Path("pdf_output"), help="输出 PDF 目录") parser.add_argument("--settings", type=str, default="ebook", choices=list(SETTINGS_MAP.keys()), help="压缩预设等级") parser.add_argument("--min-size-kb", type=int, default=100, help="小于该体积的文件跳过,单位 KB") parser.add_argument("--skip-existing", action="store_true", help="输出目录已有同名文件时跳过") parser.add_argument("--fallback", action="store_true", help="Ghostscript 失败时使用 pikepdf 兜底") args = parser.parse_args() # 日志配置:同时输出到控制台和文件 log_dir = Path("logs") log_dir.mkdir(exist_ok=True) log_file = log_dir / f"compress_{time.strftime('%Y%m%d_%H%M%S')}.log" logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ logging.StreamHandler(), logging.FileHandler(log_file, encoding="utf-8"), ], ) settings_value = SETTINGS_MAP[args.settings] batch_compress( input_dir=args.input, output_dir=args.output, settings=settings_value, min_size_kb=args.min_size_kb, skip_existing=args.skip_existing, use_pikepdf_fallback=args.fallback, ) if __name__ == "__main__": main()

这段代码中,以下几个设计点值得注意:

  1. find_ghostscript()自动识别不同系统上的 Ghostscript 命令名称,避免 Windows 和 macOS/Linux 命令名不一致的问题。
  2. 利用subprocess.run(capture_output=True, text=True)捕获 Ghostscript 的标准输出和错误,方便后续排错。
  3. 输出路径使用input_pdf.relative_to(input_dir)保留原有目录层级,适合多级文件夹批量处理。
  4. 增加--min-size-kb参数,避免对体积很小的文件做无意义压缩。
  5. 增加--skip-existing参数,重复运行脚本时不会重复处理已经生成的同名文件。
  6. 日志写入logs目录,文件名带时间戳,方便审计。

5.3 备选方案:PyMuPDF 压缩

如果某些 PDF 不需要 Ghostscript 那样激进的重采样,可以单独写一个基于 PyMuPDF 的版本:

""" 另一个轻量方案:使用 PyMuPDF 清理无用对象并重新压缩内容流。 文件路径:pdf_batch_compress/compress_pymupdf.py """ from pathlib import Path import fitz def compress_with_pymupdf(input_pdf: Path, output_pdf: Path): doc = fitz.open(input_pdf) # garbage=4: 清除未引用的对象、去除重复对象 # deflate=True: 重新压缩内容流 # clean=True: 清理并规范化 PDF 结构 doc.save(output_pdf, garbage=4, deflate=True, clean=True) size_before = input_pdf.stat().st_size size_after = output_pdf.stat().st_size print(f"{input_pdf.name}: {size_before / 1024:.1f} KB -> {size_after / 1024:.1f} KB") doc.close() if __name__ == "__main__": input_dir = Path("pdf_input") output_dir = Path("pdf_output") output_dir.mkdir(exist_ok=True) for pdf_path in input_dir.glob("*.pdf"): out_path = output_dir / pdf_path.name compress_with_pymupdf(pdf_path, out_path)

PyMuPDF 胜在轻量,不需要额外安装 Ghostscript,适合 PDF 文件本身结构比较规范、压缩需求不是特别高的场景。但对图片密集型 PDF,它的压缩率往往不如 Ghostscript 明显。

5.4 运行与验证

把需要压缩的 PDF 放入pdf_input,然后执行:

python batch_pdf_compress.py \ --input pdf_input \ --output pdf_output \ --settings ebook \ --min-size-kb 100 \ --skip-existing \ --fallback

预期日志输出类似于:

2025-01-15 10:00:01,123 [INFO] 共发现 5 个 PDF 文件 2025-01-15 10:00:02,456 [INFO] [1/5] 正在压缩: 扫描合同.pdf (15.2 MB) 2025-01-15 10:00:04,789 [INFO] 压缩后: 4.3 MB,压缩率 71.7% 2025-01-15 10:00:05,012 [INFO] [2/5] 正在压缩: 产品手册.pdf (42.8 MB) 2025-01-15 10:00:07,345 [INFO] 压缩后: 18.1 MB,压缩率 57.7%

注意,压缩率和具体文件内容强相关,不同文件的体积缩减比例可能差异很大,上面只是演示效果。

运行完,打开pdf_output里的文件,随机抽几页检查文字是否可选、图片是否可接受,再决定是否上调或下调压缩档位。

6. 进阶参数与质量平衡

6.1 四档 PDFSETTINGS 详解

Ghostscript 的-dPDFSETTINGS参数有四个常用预设,理解这些预设,才能在不同场景下选对档位:

  • /screen:最低质量,适合电脑屏幕预览、快速预览。图片会被压到 72 DPI 左右,体积最激进,但打印效果通常不可接受。
  • /ebook:电子设备阅读,约 150 DPI,是多数办公文档的首选,兼顾清晰度和体积。
  • /printer:面向打印机输出,约 300 DPI,适合需要打印但图片质量要求不极端的场景。
  • /prepress:面向印刷出版,体积压缩有限,适合需要保真输出的场景。

如果你是财务或人力资源人员,需要把一堆合同、发票压缩后归档,/ebook足够。如果这些 PDF 之后可能被拿去打印、签章、作为正式材料,建议用/printer,避免因过度压缩影响法律效力或者可读性。

6.2 手动控制图片分辨率

预设档位不一定满足所有需求。某些扫描件只需要彩色 100 DPI、黑白 200 DPI,但/ebook会统一处理。你也可以手动增加参数:

gs \ -sDEVICE=pdfwrite \ -dPDFSETTINGS=/ebook \ -dColorImageResolution=120 \ -dGrayImageResolution=150 \ -dMonoImageResolution=200 \ -dColorImageDownsampleType=/Bicubic \ -dGrayImageDownsampleType=/Bicubic \ -dNOPAUSE \ -dBATCH \ -dQUIET \ -sOutputFile=output.pdf \ input.pdf

参数说明:

  • -dColorImageResolution:彩色图片的目标分辨率。
  • -dGrayImageResolution:灰度图片的目标分辨率。
  • -dMonoImageResolution:黑白图片的目标分辨率。
  • -dColorImageDownsampleType=/Bicubic:重采样算法,双三次插值比平均采样能保留更多细节。

使用手动分辨率参数时,建议先取 3 到 5 个代表性文件做测试,用肉眼确认文字清晰度,再批量处理。

6.3 处理加密 PDF 与元数据保留

批量压缩时经常会遇到加密 PDF。这里的“加密”要区分两种:

  • 打开密码:打开文件就需要输入密码。
  • 权限密码:可以打开,但禁止打印、复制、编辑。

对于有合法权限的文件,在pikepdf.open()时传入密码即可:

import pikepdf # 打开密码模式下,需要传 password with pikepdf.open("encrypted.pdf", password="123456") as pdf: pdf.save( "output.pdf", compress_streams=True, object_stream_mode=pikepdf.ObjectStreamMode.generate, )

需要强调的是:处理加密 PDF 必须在拥有合法授权的前提下进行。切勿绕过他人设置的访问控制,或用这类技术处理没有权限的文件。

如果希望压缩后保留基础元数据,Ghostscript 默认会保留大部分文档信息。你也可以在脚本里追加-dPreserveMetadata=true参数,但要注意部分版本的实现差异。

7. 常见问题与排查思路

问题现象常见原因解决思路
运行脚本时提示gs: command not foundGhostscript 未安装,或不在 PATH 中安装 Ghostscript;Windows 把安装目录加入 PATH;或脚本里写绝对路径
Windows 提示gswin64c 不是内部或外部命令安装时未勾选加入 PATH手动配置系统环境变量,或使用shutil.which难以找到时直接改成绝对路径
压缩后文件反而变大原 PDF 已经高度压缩;Ghostscript 重新嵌入了字体或重采样效果不明显先统计原始 PDF 的类型;改用compress_streams=True这类无损清理方案,或降低 DPI
压缩后字体变成方块或乱码字体未正确嵌入检查原 PDF 字体嵌入情况,Ghostscript 参数增加-dEmbedAllFonts=true -dSubsetFonts=true
压缩后图片模糊明显使用了/screen档位,或 DPI 设置过低提升到/ebook或/printer,减小重采样强度
某个 PDF 直接压缩失败PDF 文件损坏、加密或结构异常用pikepdf尝试打开并修复,或先转成标准 PDF 再压缩
批量处理中途中断磁盘空间不足、权限不足清理磁盘,确保输出目录可写,使用日志定位最后处理到哪个文件
输出文件包含多余空白页原 PDF 有空白页或扫描件包含空白页压缩前先做空白页检测,或用 PDF 编辑工具删除空白页
压缩后文本无法搜索原 PDF 本来就是扫描图片,没有 OCR 文本层这类文件压缩前可以考虑加 OCR,或保留原文件

排查这类问题有一个通用套路:先处理单文件,再处理批量。先用命令行压缩单个 PDF,观察是否成功;失败则查看标准错误输出的末尾部分,定位具体报错信息;成功后再跑到脚本里,排除“批量逻辑”和“单文件本身问题”两方面的原因。

8. 最佳实践与工程建议

8.1 保留原始文件,压缩结果先输出到新目录

批量压缩脚本默认把结果写入pdf_output,不会覆盖pdf_input中的原始文件。这是一个非常重要的安全边界。在实际生产中,建议不要一开始就原地覆盖文件,而是先保持“输入目录”和“输出目录”分离,等确认压缩效果、阅读体验、打印质量都符合预期后,再由人工决定是否替换原文件。

如果是需要长期运行的自动化流程,还可以把“原始文件”和“压缩文件”之间的对应关系记录成 CSV,包括文件名、大小、压缩率、时间戳,方便审计。

8.2 压缩前先做小样本验证

无论你使用 Ghostscript、pikepdf 还是 PyMuPDF,拿到一个新的 PDF 集合时都应该先抽 3 到 5 个代表性文件测试。代表性文件应该包含最小体积、最大体积、图片最多、文字最多的文件。确认压缩参数对这批文件有效之后,再放开批量运行。

这样做的原因很简单:PDF 是高度异构的格式,没有一种参数能通吃所有文件。只有通过小样本验证,才能避免几百个文件处理完后才发现图片质量不可接受。

8.3 记录日志,确保可重复执行

批量压缩脚本需要输出日志。我建议至少记录以下几点:

  • 原始文件名和路径;
  • 压缩前大小、压缩后大小、压缩率;
  • 使用的 Ghostscript 参数;
  • 处理是否成功;
  • 失败时的错误信息。

日志目录最好按天或者按时间戳归档,防止多次运行相互覆盖。可重复执行是脚本工具与手工操作最大的区别。

8.4 性能优化:并行处理时的资源边界

批量压缩大量 PDF 时,如果机器 CPU 核心较多,并行处理可以显著提速。但要注意,Ghostscript 是 CPU 密集型进程,如果同时启动十几个实例,不仅 CPU 会占满,磁盘 I/O 也会成为瓶颈,反而可能拖慢整体速度,甚至导致系统卡顿。

一个相对稳妥的思路是:先用串行脚本跑通流程,统计每个文件的平均耗时;在 CPU 核数允许的情况下,再用concurrent.futures.ThreadPoolExecutor控制并发数,比如设置为 2 到 4,而不是无脑开满所有线程。

8.5 隐私与合规边界

很多 PDF 包含个人隐私、合同编号、身份证号、财务报表等信息。使用在线 PDF 压缩工具虽然方便,但文件会被上传到第三方服务器,存在泄密风险。对于敏感材料,强烈建议本地脚本处理,不要上传到不信任的在线平台。

另外,对加密 PDF、受限文档的压缩必须遵守合法授权和合规要求。如果没有处理权限,不要尝试绕过限制,也不要对他人资料做未授权操作。

8.6 把压缩纳入归档流程

从工程角度看,PDF 压缩不应该是一次性的“补作业”,而应该纳入归档流程。比如:

  • 文件上传前自动压缩;
  • 扫描件归档时统一使用/ebook档位;
  • 每季度检查一次旧归档文件,对未压缩的 PDF 执行批量压缩;
  • 将压缩脚本和参数写入运维手册或项目文档,保证后续接手的人能理解每个参数的作用。

这样,批量压缩就不再是一个临时脚本,而是稳定可维护的工具。

9. 总结与学习路线

本文围绕“如何批量压缩 PDF 文档”这一需求,讲解了 PDF 体积过大的原因、压缩的核心原理,以及三种可行方案。其中最推荐的做法是“Python 脚本调用 Ghostscript”,兼顾压缩率、批量处理能力和可维护性。脚本中包含了自动查找 Ghostscript、跳过小文件、跳过已存在文件、日志输出、pikepdf 兜底等设计,你可以直接复制使用,也可以按自己的目录结构微调。

读完这篇文章,你应该能够完成以下几件事:

  • 判断一个 PDF 的体积来源是文本、图片还是混合型;
  • 根据使用场景选择/screen、/ebook、/printer或/prepress四档压缩参数;
  • 写出自己的批量压缩脚本,并妥善处理日志和异常;
  • 排查常见问题,比如压缩后体积变大、字体乱码、工具找不到等。

下一步如果你想继续深入,建议学习三个方向:一是用pikepdf做更精细的 PDF 对象级分析,比如导出图片列表、统计每张图片分辨率;二是结合 OCR 技术,把扫描版 PDF 转换成可搜索文件同时压缩体积;三是把这些脚本封装成带界面的小工具,供团队里不熟悉命令行的同事使用。

批量压缩 PDF 本质上就是一个“分析文件构成、选择合适参数、批量执行、验证结果”的工程问题。动手跑一次脚本,比对压缩前后的文件质量和体积,你会很快找到最适合自己业务场景的参数组合。如果本文对你有帮助,可以先收藏备用,后续压缩其他 PDF 时可以直接套用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询