ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PDF压缩与文档处理:BentoPDF、Hyper Compress与Kura工具链实践

PDF压缩与文档处理:BentoPDF、Hyper Compress与Kura工具链实践 平时处理 PDF 文档时相信不少人都遇到过两件烦心事一是几百 MB 的 PDF 文件体积太大发送、归档都麻烦二是 PDF 里的图片、字体、元数据互相混杂手动处理起来非常费劲。近期在开发者社区看到一组很有意思的工具集名为 BentoPDF、Hyper Compress 和 Kura有人把它当作一套轻量级文档处理方案来用。本文就围绕这三个名字展开结合真实可运行的示例聊一聊 PDF 批量加工、体积压缩以及批处理工作流编排的完整思路。这篇文章适合正在做文档管理、内容归档、自动化脚本或者只是想把大 PDF 压小一点的开发者。读完以后你能理解 PDF 文件为什么会被撑大能掌握常见的有损压缩和无损压缩方法也能用 Python 脚本把“读取 PDF → 处理图片 → 重新保存 → 控制压缩质量”这条链路跑通。文中涉及的命令和代码均以通用环境为例具体版本需要根据你本机的工具链做微调但核心思路是通用的。1. 背景与核心概念先说一下这项技术要解决的实际问题。PDF 本身是一种“版面固定”的文档格式它可以把文字、矢量图形、位图图片、字体、注释、表单等内容打包在一个文件里。这种结构带来了跨平台显示一致的优点但也带来了明显的副作用只要文档里有一张高清扫描图或者嵌入了一套完整字体文件体积就可能迅速膨胀到几十 MB、甚至上百 MB。BentoPDF、Hyper Compress 和 Kura 这三个名字从定位上看是围绕“文档处理 压缩 流程编排”的一组工具集合。BentoPDF 更接近 PDF 文件的批处理工具箱负责读取、拆分、合并和重写 PDFHyper Compress 负责把文件体积压缩到极致同时尽量保留可接受的视觉质量Kura 则像是一个调度层把前两者串成一条可重复执行的自动化流水线。实际开发中如果不想引入重型文档管理系统用这样一组轻量工具组合就能完成大部分文件处理需求。在深入示例之前有两个概念需要先理清无损压缩和有损压缩。压缩类型保留信息程度适用场景典型手段无损压缩不丢失任何数据解压后与原文完全一致合同扫描件、财务报表、存档文件重新编码流、清理无效元数据、字体子集化有损压缩丢弃部分视觉冗余文件体积显著下降网络预览、邮件附件、内部共享图片重采样、降低 JPEG 质量、降位深很多 PDF 压缩工具的底层逻辑本质上就是“先识别文档里的大块资源再对资源分别做有损或无损处理”。PDF 里的图片往往是体积大头所以压缩图片质量的效果通常最为明显。而字体、元数据、冗余对象则适合做无损清理。这也解释了为什么“压缩 PDF”并不能简单地套用通用的 zip 压缩——因为 PDF 内部的流对象本身可能已经经过某种编码直接压二进制流往往收益有限。2. 环境准备与版本说明这一节说明实验环境。本文示例不绑定某个具体操作系统以下环境组合是常见配置读者需要根据自己的实际环境调整版本。项目建议环境说明操作系统Windows 10/11 / macOS 12 / Ubuntu 20.04命令差别不大Python 跨平台Python3.9 及以上使用 f-string、类型注解等特性pip 管理工具pip / pip3安装第三方依赖PDF 处理库pikepdf无损重写、结构查看PDF 处理库PyMuPDFfitz图片提取、字体子集化、有损保存图片压缩库Pillow处理独立图片文件命令行工具pdfcpu / qpdf可选快速验证和批量操作使用以下命令安装本示例所需的核心依赖pip install pikepdf PyMuPDF Pillow这里需要特别提醒pikepdf 和 PyMuPDF 都是活跃维护的库但在不同版本中 API 会有细微差别。例如 PyMuPDF 从 1.23 版本开始提供了更便捷的ez_save()方法而旧版本没有。文中代码以常见 API 为主如果你发现某个方法不存在请先检查库版本再看对应版本的官方文档不要照搬线上代码而忽略版本差异。为了统一实验结构建议创建一个项目目录文件结构如下pdf-toolkit/ ├── scripts/ │ ├── bento_pdf.py │ ├── hyper_compress.py │ └── kura_workflow.sh ├── input/ │ └── sample.pdf ├── output/ │ ├── compressed.pdf │ └── preview.jpg └── requirements.txt其中input/sample.pdf放一个测试用 PDF如果没有现成文件可以用任意文档工具生成一份包含多张图片和文字的 PDF。3. 核心原理拆解PDF 为什么大压缩到底在压缩什么这部分不直接写代码而是先把原理讲清楚。只有理解了 PDF 的组成后面写压缩脚本时才知道该对什么下手。3.1 PDF 文件结构里藏了什么PDF 文件的物理结构可以粗略分成四个部分文件头、对象集合、交叉引用表xref、尾部信息。对象集合是重点常见的对象有以下几类目录对象Catalog描述页面树的根节点。页面对象Page记录每页包含的资源引用。内容流对象Content Stream保存页面显示指令。资源对象Resources包含字体、图片、色彩空间等。元数据对象Metadata包含作者、创建时间、标题等非必要信息。一份典型的 PDF 里体积最大的通常是图片对象和字体对象。图片对象保存的是嵌入式位图数据字体对象则可能包含完整的字形轮廓尤其是一些中文字体单个字体文件就可能达到 10 MB 以上。3.2 图片对象体积大头当你在 PDF 里插入一张 JPEG 图片时PDF 并不会把图片重新编码为 PDF 专用格式而是会把 JPEG 数据流作为一个嵌入对象放入文件。PDF 也支持 FlateDecode、DCTDecode、JPXDecode 等多种过滤器常见的有FlateDecode基于 zlib 的无损压缩适合文字内容流和部分图片。DCTDecodeJPEG 编码有损适合照片类图片。JPXDecodeJPEG 2000 编码有损/无损皆可适合大幅面图片。所以压缩 PDF 图片的实质是读取原始图像数据按需要缩放尺寸、降低质量、转换色彩空间再替换回 PDF 页面中的图片对象。3.3 字体对象被忽略的隐形体积很多人压缩 PDF 时只盯着图片忽略字体。其实中文字体动辄几十 MB如果 PDF 里嵌入了完整的 SimSun、Microsoft YaHei 字体即便文档只有几句话体积依然很大。针对字体常见的优化手段是“字体子集化Font Subsetting”只保留文档中实际用到的字形剔除不用的汉字和符号这样字体流可以被大幅缩小。PyMuPDF 的subset_fonts()方法就可以做字体子集化它在前置字体处理上做了大量封装使用起来很方便。3.4 元数据、书签与冗余对象PDF 里还可能包含大量冗余对象从未使用过的资源、过期的交叉引用记录、重复的元数据块、文档属性中的历史版本字段等。这部分虽然是“边角料”但在批量处理大量文档时清理元数据往往能节省 5%~15% 的体积。综合以上分析一套完整的 PDF 压缩策略应当是优先对图片做有损重采样对字体做子集化对内容流重新 FlateDecode 编码清理无效元数据重写交叉引用表和对象结构。4. BentoPDF 实战PDF 批处理脚本现在进入第一个实战环节。我们先实现一个“小而全”的 PDF 批处理脚本模拟 BentoPDF 工具的核心能力读取输入 PDF、统计资源、执行无损压缩、输出结果。脚本名为scripts/bento_pdf.py。4.1 基础读取与信息统计无论压缩还是拆分第一步都是读取 PDF 并了解它的基本状态。下面的代码片会输出 PDF 的页数、文件大小、包含的图片数量。# 文件路径scripts/bento_pdf.py import os import pikepdf def inspect_pdf(path: str) - None: if not os.path.exists(path): raise FileNotFoundError(fPDF 文件不存在: {path}) pdf pikepdf.open(path) size_bytes os.path.getsize(path) page_count len(pdf.pages) image_count 0 for page in pdf.pages: resources page.get(/Resources) if resources is None: continue xobjects resources.get(/XObject) if xobjects is None: continue for key, obj in xobjects.items(): if dict(obj).get(/Subtype) /Image: image_count 1 print(f页数: {page_count}) print(f文件大小: {size_bytes / 1024 / 1024:.2f} MB) print(f图片对象数量: {image_count}) pdf.close() if __name__ __main__: inspect_pdf(input/sample.pdf)这段代码使用 pikepdf 打开 PDF遍历每一页的/Resources资源再检查/XObject里的图片对象。注意dict(obj)这样的方式是因为 pikepdf 的对象是类字典结构想要取/Subtype需要先转成 Python 字典这是一种常见写法。4.2 无损压缩重写与重新编码pikepdf 的save()方法在保存时会重新组织对象流可以通过参数开启压缩。compress_streamsTrue表示压缩所有流对象object_stream_modepikepdf.ObjectStreamMode.generate则让 PDF 使用对象流来分组对象减少文件末尾交叉引用表的体积。# 继续写入 scripts/bento_pdf.py import pikepdf def compress_lossless(input_path: str, output_path: str) - None: pdf pikepdf.open(input_path, allow_overwriting_inputTrue) pdf.save(output_path, compress_streamsTrue, object_stream_modepikepdf.ObjectStreamMode.generate) pdf.close() if __name__ __main__: compress_lossless(input/sample.pdf, output/sample_lossless.pdf)注意在调用save()时不要直接覆盖原文件除非你使用allow_overwriting_inputTrue并且确认文件有备份。实际工程中强烈建议输出到独立目录保留原始文件不被破坏。4.3 合并与拆分除了压缩PDF 批处理最常见的需求是合并和拆分。pikepdf 的合并思路听起来简单但要注意页面的跨文件引用问题。下面这段代码把多个 PDF 按顺序合并成一个。# 继续写入 scripts/bento_pdf.py import pikepdf def merge_pdfs(file_list: list[str], output_path: str) - None: merged pikepdf.Pdf.new() for file_path in file_list: src pikepdf.open(file_path) merged.pages.extend(src.pages) src.close() merged.save(output_path) merged.close() def split_pdf(input_path: str, output_dir: str, start: int, end: int) - None: pdf pikepdf.open(input_path) total len(pdf.pages) if start 1 or end total or start end: raise ValueError(页范围无效请确保 start 和 end 在有效区间内) new_pdf pikepdf.Pdf.new() for page_num in range(start - 1, end): new_pdf.pages.append(pdf.pages[page_num]) new_pdf.save(f{output_dir}/pages_{start}_{end}.pdf) new_pdf.close() pdf.close() if __name__ __main__: # merge_pdfs([input/a.pdf, input/b.pdf], output/merged.pdf) split_pdf(input/sample.pdf, output, 1, 3)这里有一个容易踩的坑merged.pages.extend(src.pages)看似直接把页面对象复制过去但实际上 pikepdf 会维护对象引用关系源文件关闭后页面引用的资源仍然能正常访问。不过如果源文件结构复杂某些依赖外部文件的标签和注释可能丢失这是 PDF 处理的通用限制不是库的 Bug。4.4 有损压缩图片重写无损压缩的价值是安全但对以图片为主的 PDF 来说收益往往有限。真正能让文件“瘦身”的是有损压缩思路把页面里的图片对象提取出来缩放尺寸、降低质量再替换回去。这一过程用 PyMuPDF 实现会直观很多。# 文件路径scripts/hyper_compress.py import fitz def compress_pdf_lossy(input_path: str, output_path: str, zoom_factor: float 0.8, jpeg_quality: int 60) - None: doc fitz.open(input_path) # 字体子集化去掉未使用的字形 try: doc.subset_fonts() except Exception as exc: print(f字体子集化失败: {exc}) for page in doc: for img in page.get_images(fullTrue): xref img[0] pix fitz.Pixmap(doc, xref) # 如果原始图片不是 RGB先转成 RGB if pix.n - pix.alpha 3: pix fitz.Pixmap(fitz.csRGB, pix) # 按比例缩小 pix.shrink(int(1 / zoom_factor) if zoom_factor 1 else 1) # 替换回页面 page.replace_image(xref, pix.tobytes(jpeg, jpeg_quality)) pix None doc.set_metadata({}) doc.ez_save(output_path) doc.close() if __name__ __main__: compress_pdf_lossy(input/sample.pdf, output/sample_lossy.pdf, zoom_factor0.8, jpeg_quality60)这段代码里最容易被忽略的是pix.shrink()的行为。shrink(factor)的factor是整数表示将图像长宽除以几因此我这里做了一个简单的换算。如果你本机 PyMuPDF 版本较新也可以改用矩阵缩放方式例如scale_matrix fitz.Matrix(0.8, 0.8) new_pix pix.to_pdf_page(page, rectpage.rect, matrixscale_matrix, clipNone)这句话的语义是把当前 pix 对象按缩放矩阵渲染到页面的某个矩形区域内。不同版本的 API 稳定性略有差异建议结合官方示例微调。核心思路是先得到 Pixmap再转成 JPEG 字节流最后替换页面图片对象。新的 JPEG 字节流比原图小很多整个 PDF 的体积自然就下降。4.5 压缩质量的评估有损压缩不能只看输出体积还要盯着视觉质量。自动化脚本里建议输出压缩前后的对比信息并生成预览图# 继续写入 scripts/hyper_compress.py import os import fitz def preview_pdf(input_path: str, output_image: str, page_index: int 0) - None: doc fitz.open(input_path) if page_index len(doc): print(f页面索引超出范围共 {len(doc)} 页) return page doc[page_index] pix page.get_pixmap(matrixfitz.Matrix(2, 2)) pix.save(output_image) print(f预览图已保存: {output_image}) doc.close() def compare_ratio(before: str, after: str) - float: size_before os.path.getsize(before) size_after os.path.getsize(after) ratio (1 - size_after / size_before) * 100 print(f压缩前: {size_before / 1024:.1f} KB) print(f压缩后: {size_after / 1024:.1f} KB) print(f压缩率: {ratio:.1f}%) return ratio if __name__ __main__: compare_ratio(input/sample.pdf, output/sample_lossy.pdf) preview_pdf(output/sample_lossy.pdf, output/preview.jpg)预览图的意义在于人眼抽检。脚本自动评估体积比例但不能完全替代人工查看关键页面的可读性。对合同、身份证复印件这类敏感文件宁可压缩率低一点也要保证文字和印章可辨认。5. Hyper Compress 实战把压缩从 PDF 扩展到通用文件BentoPDF 解决的是 PDF 内部结构问题Hyper Compress 的思路则更通用它关注任意文件的压缩率和压缩方式。实际工作中我们经常需要把一批图片、日志、文本文件一起压成压缩包或者在 PDF 处理前先对图片素材做预压缩。这一节演示通用压缩技巧。5.1 图片预压缩如果你要处理的 PDF 是由扫描图片生成的那么更高效的做法是“先压缩图片再生成 PDF”。Pillow 是处理图片最常用的库下面是一个批量压缩 JPG 的示例。# 文件路径scripts/hyper_compress.py from pathlib import Path from PIL import Image, ImageOps def compress_image_folder(input_dir: str, output_dir: str, quality: int 70, max_side: int 1600) - None: in_path Path(input_dir) out_path Path(output_dir) out_path.mkdir(parentsTrue, exist_okTrue) for img_path in in_path.glob(*.jpg): with Image.open(img_path) as img: # 转换到 RGB避免 CMYK 或 RGBA 导致导出问题 img img.convert(RGB) # 限制最长边等比缩放 img.thumbnail((max_side, max_side), Image.Resampling.LANCZOS) output_file out_path / img_path.name img.save(output_file, JPEG, qualityquality, optimizeTrue, progressiveTrue) print(f已处理: {img_path.name}) if __name__ __main__: compress_image_folder(input/images, output/images, quality70, max_side1600)thumbnail()方法会保持宽高比只缩小不放大。optimizeTrue会让编码器在扫描图像后优化 Huffman 表通常还能再省一点体积。如果图片带有 EXIF 方向信息最好先用ImageOps.exif_transpose()矫正方向否则压缩后可能出现旋转丢失问题。5.2 通用文件压缩对于非图片类文件Python 标准库的zipfile和lzma可以完成基础压缩。前者适合兼容性要求高的场景后者适合追求更大压缩率的场景。# 文件路径scripts/hyper_compress.py import zipfile from pathlib import Path def make_zip(input_dir: str, output_zip: str, compress_level: int 9) - None: dir_path Path(input_dir) with zipfile.ZipFile(output_zip, w, zipfile.ZIP_DEFLATED, compresslevelcompress_level) as zf: for file_path in dir_path.rglob(*): if file_path.is_file(): zf.write(file_path, file_path.relative_to(dir_path)) print(f压缩包已创建: {output_zip}) if __name__ __main__: make_zip(output/images, output/images.zip, compress_level9)compresslevel9是 zlib 的最高压缩级别但注意压缩时间也会变长。如果你处理的是几千个几 KB 的小文件压缩率反而可能不如先合并再压缩。针对日志类文本可以先用通用压缩工具压出.xz文件进一步降低体积。但.xz的兼容性不如 zip跨平台传输时要考虑对方是否能解压。5.3 “超压缩”的边界“Hyper Compress”这个名字听起来很吸引人但在工程中必须明确一个边界压缩不是越高越好。我用一个简单表格说明压缩率、时间、可用性三者的权衡压缩策略代表方式优点风险低压缩zip level 1JPEG quality 85速度快质量高体积可能仍然偏大中压缩zip level 6JPEG quality 70平衡体积与质量需要观察细节损失高压缩zip level 9JPEG quality 40体积最小处理慢图片出现明显伪影所谓“超压缩”更适合用于预览版、临时文件、归档副本不建议直接覆盖原始文件。优秀的方案是在流水线里同时保留“原稿”和“压缩稿”让下游按需选择。6. Kura 实战把它们编排成一条工作流BentoPDF 负责文档处理Hyper Compress 负责体积优化Kura 则负责把这两者围绕业务场景串联起来。为便于演示我们把 Kura 定义为一个轻量任务编排器它可以通过命令行、配置文件或简单的 Shell 脚本把“图片预压缩 → PDF 生成 → PDF 压缩 → 预览图生成”变成一个一键命令。6.1 先写一个可重复执行的 Shell 工作流在真正的项目管理中脚本化比手动点击更可靠。下面是一个kura_workflow.sh示例它把上文中的 Python 脚本作为步骤按顺序执行。#!/bin/bash # 文件路径scripts/kura_workflow.sh set -euo pipefail INPUT_DIR./input OUTPUT_DIR./output TMP_IMG_DIR./output/images echo [Kura] 1/4 清理输出目录 rm -rf $OUTPUT_DIR mkdir -p $OUTPUT_DIR $TMP_IMG_DIR echo [Kura] 2/4 预处理图片并生成 PDF python scripts/hyper_compress.py # 内部包含图片处理和 PDF 压缩示例 echo [Kura] 3/4 执行 PDF 无损和有损压缩 python scripts/bento_pdf.py echo [Kura] 4/4 生成预览图 python -c import sys; sys.path.insert(0, scripts); from hyper_compress import preview_pdf; preview_pdf(output/sample_lossy.pdf, output/preview.jpg) echo [Kura] 工作流执行完成这里用set -euo pipefail保证脚本在第一个命令出错时立即停止避免多个错误叠加导致不可预期的结果。实际项目中建议把可复用的步骤写成带参数的函数例如run_bento() { python scripts/bento_pdf.py $ }6.2 用 Python 字符串拼接完整流水线有些团队不想依赖 Shell 脚本而是希望在同一套 Python 代码里完成所有操作。我们可以用subprocess调用命令行工具也可以把前面写的函数封装成一个Workflow类这是更工程化的做法。# 文件路径scripts/kura_workflow.py from pathlib import Path class KuraWorkflow: def __init__(self, input_dir: str, output_dir: str): self.input_dir Path(input_dir) self.output_dir Path(output_dir) self.output_dir.mkdir(parentsTrue, exist_okTrue) def run(self) - None: # 为了保证演示可独立运行这里直接调用 previous 模块 from bento_pdf import compress_lossless from hyper_compress import compress_pdf_lossy, preview_pdf sample self.input_dir / sample.pdf lossless self.output_dir / sample_lossless.pdf lossy self.output_dir / sample_lossy.pdf preview self.output_dir / preview.jpg print(步骤 1: 无损压缩) compress_lossless(str(sample), str(lossless)) print(步骤 2: 有损压缩) compress_pdf_lossy(str(sample), str(lossy), zoom_factor0.8, jpeg_quality60) print(步骤 3: 生成预览) preview_pdf(str(lossy), str(preview)) print(全部完成) if __name__ __main__: KuraWorkflow(input, output).run()这里有几个工程细节值得说明类名KuraWorkflow只是一种命名约定不代表官方对象模型。每个步骤都打印日志方便在大量批处理任务中定位耗时点。输出文件统一放在output/目录避免与输入目录混淆。在实际项目中可以把任务参数放进一个config.json让 Kura 按配置文件运行实现“同一个脚本、不同参数、不同输入目录”的复用。{ input_dir: ./input, output_dir: ./output, zoom_factor: 0.8, jpeg_quality: 60, generate_preview: true }7. 常见问题与排查思路在实际处理 PDF 压缩和批量任务时难免会遇到各种报错。下面把高频问题整理成表并附上排查建议。问题现象常见原因解决思路PDF 打开报错“文件损坏”输入文件本身加密或受 DRM 保护先解密受控文档确认授权后再处理pikepdf.Pdf.open抛错PDF 版本过旧或结构损坏尝试用 qpdf 修复qpdf --check input.pdfpage.replace_image找不到图片页面图片以内联形式存储不在 XObject 中改用page.get_images适配不同存储方式有损压缩后文字模糊图片缩放比例过大或 JPEG 质量过低提高zoom_factor将jpeg_quality调到 70 以上输出 PDF 比输入还大原始 JPEG 被重新编码为 PNG 或无损流检查输出过滤器确保图片以 JPEG/DCTDecode 写入中文字体显示乱码字体子集化后保留字形不完整更新 PyMuPDF 版本或对含复杂文字的页面关闭字体子集化批量任务运行到一半崩溃某个输入文件格式异常在循环中加 try-except逐文件记录错误日志脚本路径报错相对路径在不同工作目录下表现不同优先使用绝对路径或基于Path(__file__).parent拼接针对“输出 PDF 比输入还大”这个现象我再展开说一点。很多 PDF 里嵌入的本身就是高压缩率的 JPEG 图无损重写并不会让它变得更小。此时如果脚本又把图片转成不压缩的位图流重新嵌入体积必然反弹。正确的做法是先检测图像编码方式如果原图已经是 JPEG 且质量较高应直接复用原始数据流而不是重新编码。8. 最佳实践与工程建议这些建议来自实际批处理项目的经验不是泛泛而谈。8.1 永远保留原始文件无论是无损还是有损压缩都不建议直接覆盖源文件。压缩是一个有损过程即使是无损方案也可能因为库版本差异而改变 PDF 内部结构。建议目录结构如下input/ # 原始文件只读 output/ # 生成结果 archive/ # 定期归档的压缩稿8.2 在批量任务中加入校验和批处理上百个 PDF 时不应只看文件大小是否变小还要验证文件是否完整。可以在压缩后重新打开 PDF统计页数是否与原始文档一致。如果页数对不上说明处理过程中有页面资源丢失需要立即标记为失败文件。import pikepdf def verify_pdf(path: str, expected_pages: int) - bool: try: with pikepdf.open(path) as pdf: return len(pdf.pages) expected_pages except Exception: return False8.3 日志必须包含输入输出路径与耗时自动化任务的日志不能只在控制台打印建议写入文件。每条日志至少要包含以下内容时间戳 | 输入文件路径 | 输出文件路径 | 处理结果 | 耗时(ms) | 压缩率(%)这样当任务数量增长到几千个文件时才能快速定位失败项和最慢项。8.4 安全边界与权限问题如果这些脚本部署到服务器上要特别注意文件权限和路径穿越风险。例如不要把用户输入的目录直接作为路径前缀拼接否则可能读到服务器上的任意文件对上传的 PDF 应先检查文件头和文件大小再放进隔离目录处理删除临时文件时要基于白名单目录避免误删业务数据。涉及敏感文档应当遵循最小权限原则只让任务账号访问必要的目录。8.5 选择合适的处理顺序最优的压缩顺序不是先压缩再合并而是“先合并后统一处理”。因为合并操作会复制多个 PDF 的资源引用如果先分别压缩再合并可能会在合并时引入重复的字体和图片对象体积反而更大。正确做法是先合并成一个 PDF再统一压缩、字体子集化、清理元数据。9. 收尾从脚本到可维护工具链围绕 BentoPDF、Hyper Compress 和 Kura 这套思路本文实际上完成了一条完整的工具链设计BentoPDF 负责 PDF 结构层面的批量读写与无损压缩Hyper Compress 负责图片和文件的深度压缩Kura 把前两者编排成可重复执行的流水线。即使你最终不使用这三个名字对应的具体程序文中涉及的 pikepdf、PyMuPDF、Pillow 和 Shell 编排方法也可以直接迁移到自己的文档处理项目里。下一步可以对照你手头的真实文档先跑一遍inspect_pdf()看看自己的 PDF 里图片和字体占用多少比例然后再决定采取哪种压缩方案。对以扫描件为主的 PDF优先调高图片压缩强度对以文字排版为主的 PDF优先做字体子集化和无损重写。最后记得把“验证页数 生成预览图 保留原始文件”这三件事固化进你的自动化流程它们能在长期运行中帮你避开大量隐蔽问题。
返回列表