一、结论先给
PDF 大的原因只有三类,先定位再动手,否则就是瞎压。
# 30 秒定位:看体积构成
pdfinfo input.pdf # 页数、页面尺寸
pdffonts input.pdf # 字体占多少
pdfimages -list input.pdf # 图片占多少、分辨率多少
| 成因 | 判断方法 | 压缩手段 | 效果 |
|---|---|---|---|
| 扫描/位图 | pdfimages -list 有大图 |
降采样 + 降 JPEG 质量 | 最大,常见 10-20 倍 |
| 嵌入字体 | pdffonts 有几百 KB 的全字库 |
字体子集化 | 中等,几百 KB 级 |
| 对象未压缩 | 老软件生成的 PDF | qpdf --linearize / 重压缩流 |
小,10-30% |
一句话结论:扫描件靠降采样,文字稿靠 Ghostscript 预设,剩下的边角靠 qpdf。
二、Ghostscript 四档预设:最快的一招
# 装
sudo apt install ghostscript
核心参数就一个:-dPDFSETTINGS
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 \
-dPDFSETTINGS=/ebook \
-dNOPAUSE -dQUIET -dBATCH \
-sOutputFile=out.pdf input.pdf
四档的含义与实测效果(50 页扫描件,原 48 MB,结果为估算):
| 预设 | 图像分辨率 | 适用场景 | 体积(估算) | 观感 |
|---|---|---|---|---|
/screen |
72 dpi | 屏幕看、邮件附件 | 3-6 MB | 文字边缘略毛躁,看图明显糊 |
/ebook |
150 dpi | 默认推荐,通用 | 8-15 MB | 屏幕清晰,打印勉强 |
/printer |
300 dpi | 要打印 | 25-40 MB | 打印没问题 |
/prepress |
300 dpi + 保留色彩 | 印刷 | 40+ MB | 几乎无损,体积基本没降 |
/screen 和 /ebook 是两个真正能用的档位。/printer 之后体积就没意义了。
⚠️ 注意:Ghostscript 重压缩会丢掉表单、注释、书签和部分元数据。有书签的文档压缩前先备份原文件。
三、手写参数:压得比预设更狠
预设不能满足时,手动控制图像参数:
gs -sDEVICE=pdfwrite \
-dCompatibilityLevel=1.5 \
-dDownsampleColorImages=true \
-dColorImageResolution=150 \
-dColorImageDownsampleType=/Bicubic \
-dDownsampleGrayImages=true \
-dGrayImageResolution=150 \
-dGrayImageDownsampleType=/Bicubic \
-dDownsampleMonoImages=true \
-dMonoImageResolution=300 \
-dMonoImageDownsampleType=/Subsample \
-dAutoFilterColorImages=false \
-dColorImageFilter=/DCTEncode \
-dJPEGQ=70 \
-dAutoFilterGrayImages=false \
-dGrayImageFilter=/DCTEncode \
-dNOPAUSE -dQUIET -dBATCH \
-sOutputFile=out.pdf input.pdf
参数解释(这几个是真正起作用的):
| 参数 | 作用 | 建议值 |
|---|---|---|
-dColorImageResolution |
彩色图降采样到多少 dpi | 100-150 |
-dGrayImageResolution |
灰度图 | 150 |
-dMonoImageResolution |
黑白图(文字/线条) | 300,别降,降了文字就毛 |
-dJPEGQ |
JPEG 质量 0-100 | 60-75 |
-dColorImageFilter=/DCTEncode |
彩色图用 JPEG 压缩 | 必加 |
-dDetectDuplicateImages=true |
重复图片只存一份 | 有重复 logo 时很有效 |
最容易忽略的一条:MonoImageResolution 一定要比彩色高。黑白图是文字主体,压到 150 dpi 会直接糊成一团——这是很多人压完觉得"字看不清"的根本原因。
四、扫描件的极限压缩:黑白二值化
扫描的文档类 PDF(合同、发票、书籍),最狠的一招是转成 1-bit 黑白:
# 先转图,再二值化,最后组回 PDF
pdftoppm -r 300 -gray input.pdf page
for f in page-*.pgm; do
magick "$f" -threshold 60% -compress Group4 "${f%.pgm}.tif"
done
magick page-*.tif -compress Group4 out.pdf
体积对比(估算,300 dpi A4 单页):
| 方案 | 单页体积 |
|---|---|
| 原扫描彩色 JPG | 500 KB - 1.5 MB |
| 灰度 + JPEG q75 | 150-400 KB |
| 黑白二值 + CCITT G4 | 30-80 KB |
适用范围:纯文字的黑白文档。有照片、有灰度图的不要二值化,会丢信息。
五、压到"指定大小":二分法脚本
需求场景很常见:"这个 PDF 必须小于 2 MB 才能上传"。一次压不准,用脚本自动找参数:
#!/usr/bin/env bash
# usage: ./fit.sh input.pdf 2048 (目标 2048 KB)
set -euo pipefail
IN=$1; TARGET_KB=$2
OUT="${IN%.pdf}.small.pdf"
lo=40; hi=300; best=""
for i in $(seq 1 8); do
mid=$(( (lo + hi) / 2 ))
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 -dNOPAUSE -dQUIET -dBATCH \
-dDownsampleColorImages=true -dColorImageResolution=$mid \
-dColorImageFilter=/DCTEncode -dJPEGQ=70 \
-sOutputFile="$OUT" "$IN"
size=$(( $(stat -c%s "$OUT") / 1024 ))
echo "res=${mid}dpi size=${size}KB"
if [ "$size" -le "$TARGET_KB" ]; then best=$mid; lo=$mid; else hi=$mid; fi
done
echo "---"
echo "最佳分辨率: ${best}dpi"
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 -dNOPAUSE -dQUIET -dBATCH \
-dDownsampleColorImages=true -dColorImageResolution=${best:-72} \
-dColorImageFilter=/DCTEncode -dJPEGQ=70 \
-sOutputFile="$OUT" "$IN"
echo "最终: $(( $(stat -c%s "$OUT") / 1024 ))KB"
思路:用分辨率当变量做二分查找,8 次迭代内收敛。如果压到 72 dpi 还不达标,说明页数太多,只能拆分成多个文件(用 PDF 拆分)或接受更低质量。
六、另一条路:qpdf(无损重排,不动图像)
sudo apt install qpdf
# 重排对象流 + 开启压缩(不改变图像质量)
qpdf --linearize --compress-streams=y input.pdf out.pdf
# 去掉没用的对象(有孤儿对象时有效)
qpdf --linearize --remove-unreferenced-resources=yes input.pdf out.pdf
# 加密 PDF 要先解密才能压
qpdf --decrypt --password=xxx input.pdf tmp.pdf && gs ... tmp.pdf out.pdf
qpdf 不改变图像,所以压缩率有限(10-30%),但完全无损,适合那些已经压过、不能再损的文档。
还有个更狠的第三方工具 pdfsizeopt(需要 Java + 一堆依赖),能自动做字体子集化、图像重编码,压缩率常高于 Ghostscript,但环境搭建麻烦,服务器上偶尔值得装一次。
七、压缩后必须做的校验
压完不看就交付,是这类操作最大的坑。
# 1. 页数对不对(Ghostscript 偶尔会丢页)
pdfinfo out.pdf | grep Pages
pdfinfo input.pdf | grep Pages
# 2. 能不能正常打开、文字能不能选(渲染引擎报错会静默产出坏文件)
pdftotext out.pdf - | head -20
# 3. 加密状态(gs 有时会带上权限设置)
pdfinfo out.pdf | grep -i encrypted
# 4. 体积对比
ls -lh input.pdf out.pdf
自动化脚本里建议加一句断言:页数不一致就直接失败退出,别继续往下走。
八、常见误区
1. 反复压同一个文件 压过的 PDF 再压一次,体积几乎不降,质量却再掉一档。永远从原文件压。
2. 用在线工具传合同 压缩要读整个文件,传到别人服务器上就等于交出去了。合同、身份证、病历这类,用本地工具处理。
3. 以为压缩等于加密 压缩只是变小,不设任何权限。要防打开得单独加密(见 PDF 加密篇)。
4. 只看总体积不看用途 给领导看的 PDF 压到 72 dpi,字体发毛,比体积大 20 MB 更糟。先定用途,再定参数。
5. 忘了算磁盘
批量压几百个文件时,中间产物能轻易吃掉几十 GB。开跑前用 磁盘容量计算 估一下,中间目录放 /tmp 定期清。
九、几条纪律
- 先
pdfimages -list+pdffonts定位,知道大头在哪再动手。 - 扫描件走降采样,黑白文档走二值化,其余走
/ebook预设。 - 黑白图分辨率保持 300,只降彩色和灰度。
- 永远从原文件压,不压二次。
- 压完必查页数和可选中文字,自动化脚本里写死断言。
- 敏感文件不上在线工具,走本地。