
一个扫描版合同,原文件 38MB,邮件附件限制 20MB。
我第一次拿 Python 处理时,脚本运行正常,输出文件也生成了。再看文件大小:37.6MB。
这类结果最烦。代码没报错,PDF 也能打开,但基本等于没压。
PDF 不是普通文本文件,里面可能同时塞着文字、字体、矢量图、JPEG 图片和扫描页。单纯重新保存一遍,通常只能清掉少量冗余对象。真正占空间的,十有八九还是图片。
我现在处理这类需求,一般让 Python 负责参数校验、批量执行和结果统计,真正的重新采样交给 Ghostscript。这个组合不算花哨,但稳定。
先安装 Ghostscript。
Linux 可以直接执行:
sudo apt install ghostscript
macOS:
brew install ghostscript
Windows 安装后,确认命令行能找到 gswin64c.exe。这一步别跳,很多所谓的 Python 压缩失败,最后只是系统环境变量没配。
先写一个能实际放进脚本目录里用的版本:
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
def find_ghostscript() -> str:
candidates = ("gs", "gswin64c", "gswin32c")
for command in candidates:
executable = shutil.which(command)
if executable:
return executable
raise RuntimeError(
"未找到 Ghostscript,请先安装并确认 gs 命令已加入环境变量"
)
def readable_size(byte_count: int) -> str:
value = float(byte_count)
for unit in ("B", "KB", "MB", "GB"):
if value < 1024 or unit == "GB":
return f"{value:.2f}{unit}"
value /= 1024
return f"{byte_count}B"
def compress_pdf(
source_file: str | Path,
target_file: str | Path,
quality: str = "ebook",
) -> None:
source = Path(source_file).expanduser().resolve()
target = Path(target_file).expanduser().resolve()
if not source.is_file():
raise FileNotFoundError(f"输入文件不存在:{source}")
if source.suffix.lower() != ".pdf":
raise ValueError(f"只处理 PDF 文件:{source.name}")
if source == target:
raise ValueError("输出文件不能覆盖原文件,先保留一份退路")
presets = {
"screen": "/screen",
"ebook": "/ebook",
"printer": "/printer",
"prepress": "/prepress",
}
if quality not in presets:
raise ValueError(f"不支持的压缩等级:{quality}")
target.parent.mkdir(parents=True, exist_ok=True)
gs = find_ghostscript()
command = [
gs,
"-sDEVICE=pdfwrite",
"-dCompatibilityLevel=1.4",
f"-dPDFSETTINGS={presets[quality]}",
"-dNOPAUSE",
"-dQUIET",
"-dBATCH",
"-dDetectDuplicateImages=true",
"-dCompressFonts=true",
f"-sOutputFile={target}",
str(source),
]
result = subprocess.run(
command,
capture_output=True,
text=True,
timeout=300,
)
if result.returncode != 0:
target.unlink(missing_ok=True)
error = result.stderr.strip() or "Ghostscript 未返回错误详情"
raise RuntimeError(f"PDF 压缩失败:{error}")
if not target.is_file() or target.stat().st_size == 0:
raise RuntimeError("命令执行结束,但输出文件为空")
before = source.stat().st_size
after = target.stat().st_size
reduced = (1 - after / before) * 100
print(f"原文件:{readable_size(before)}")
print(f"新文件:{readable_size(after)}")
print(f"缩小比例:{reduced:.1f}%")
if after >= before:
print("压缩后没有变小,这个 PDF 可能已经做过图片压缩")
if __name__ == "__main__":
compress_pdf(
source_file="合同扫描件.pdf",
target_file="合同扫描件_压缩版.pdf",
quality="ebook",
)
这里我默认用 ebook,不是因为它名字好听,而是它在文件大小和可读性之间相对稳妥。
几个等级大概这样选:
screen 文件最小,图片损失明显,适合临时预览
ebook 日常发送、上传系统,通常先试这个
printer 更偏向打印质量,压缩幅度会小一些
prepress 保留质量优先,不适合单纯追求小文件
我不建议一上来就用 screen。身份证、合同小字、发票编号这些内容,一旦压糊了,文件再小也没用。压缩不是只看最后那行“缩小 80%”,至少要抽查首页、印章页和文字最密集的页面。
批量处理也不用重新写一套逻辑:
from pathlib import Path
source_dir = Path("待压缩")
target_dir = Path("压缩结果")
for pdf_file in source_dir.glob("*.pdf"):
output_file = target_dir / f"{pdf_file.stem}_small.pdf"
try:
compress_pdf(pdf_file, output_file, quality="ebook")
except Exception as exc:
print(f"[失败] {pdf_file.name}:{exc}")
else:
print(f"[完成] {pdf_file.name}")
实际跑批量任务时,我还会盯两个地方。
一个是超时。异常 PDF、超大扫描件或者损坏文件,可能长时间卡住,所以代码里加了 timeout=300。五分钟还没处理完,先停下来查文件,别让一个坏文件堵住整个任务。
另一个是压缩后的体积。文字型 PDF 本身就不大,压缩比例可能很低;扫描版 PDF 每页都是大图,通常才有明显空间。要是输出文件反而更大,不要急着继续调参数,很可能原文件已经优化过,再编码一次只是在折腾。
还有个坑容易漏:加密 PDF。
碰到设置了权限或密码的文件,Ghostscript 可能直接失败。脚本里不要把异常吞掉,更不要生成一个空文件还当成功。保留错误日志和原文件,比强行输出重要。
PDF 压缩这件事,Python 代码并不难。难的是别把“文件生成了”当成任务完成。
我一般只认三个结果:文件能打开,关键页面看得清,体积确实变小。缺一个,这次压缩都不算完。
以上就是“Python 压缩 PDF,别只盯着“压缩成功”!”的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。
扫码二维码 获取免费视频学习资料

- 本文固定链接: http://phpxs.com/post/14391/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料