上个月我接了个相当折磨人的活儿。当时需要批量修改几百个报表文件的文档标签,调整打印边界后,再全部输出成多页横版的PDF文件。
这还不算完。生成完这批PDF后,我需要把它们按特定的规则合并,并且还要从另外一批参考资料里精准提取文字和图片。起初我用了平时常用的那几个老牌PDF库,结果不仅解析速度慢得让人想砸键盘,提取出来的文本排版更是惨不忍睹,全糊成了一锅粥。
请在微信客户端打开
直到我受不了去翻开源社区,重新捡起了 PyMuPDF。用完之后的感受只有一个:相见恨晚。
今天就和大家聊聊这个绝对被低估的PDF处理利器。
为什么是 PyMuPDF?
在Python生态里,处理PDF的库其实不少,比如PyPDF2、pdfplumber、PDFMiner等。但如果你追求极致的执行效率,同时又需要极高的底层控制力(比如操作像素级的图像、处理复杂的文档树),PyMuPDF 绝对是首选。
它的底层是基于C语言编写的MuPDF引擎,所以速度快得惊人,甚至在处理几百兆的扫描版PDF时,也不会出现内存溢出的情况。
需要特别注意的是,你在终端里安装时敲的是 pip install PyMuPDF,但在代码里导入时,它的名字叫 fitz。这是因为早期的历史遗留原因,大家习惯就好。
核心工作流解析
不管是提取、拆分还是加水印,PyMuPDF的操作逻辑都非常线性,完全符合直觉。我们可以看下它的基本数据流向:
[本地/内存 PDF 文档]│▼(fitz.open 载入) ──> 获取全局文档对象 (Document)│▼(遍历或指定加载页面 load_page)│┌───────────────┼───────────────┐▼ ▼ ▼文本/图片提取 插入/修改内容 合并/拆分页面(get_text/pixmap) (insert_text) (insert_pdf)│ │ │└───────────────┼───────────────┘▼(save 保存)│▼[输出最终 PDF]
痛点直击:实战代码演示
下面我们直接上代码,看看它在几个日常高频痛点场景下是怎么大显身手的。
场景一:极速提取文本(不丢排版)
以前用其他库提取文本,经常会出现段落错位或者行距丢失的问题。PyMuPDF 提供了一个非常实用的 blocks 模式,它可以按照物理排版的块(比如段落)来提取文字,这对我们后续做正则匹配或者自然语言处理非常友好。
import fitzdef extract_text_by_blocks(pdf_path):# 打开文档doc = fitz.open(pdf_path)# 假设我们只处理第一页page = doc.load_page(0)# 以“块”的形式提取文本,返回一个列表# 每个块包含文本坐标、内容、块编号等信息blocks = page.get_text("blocks")for block in blocks:# block[4] 是具体的文本内容text_content = block[4].strip()if text_content:print("--- 段落开始 ---")print(text_content)print("--- 段落结束 ---\n")doc.close()# extract_text_by_blocks("sample.pdf")
场景二:暴力扒出所有高清图片
很多时候我们需要把PDF里的插图或者扫描件原封不动地扒下来。PyMuPDF 可以直接读取PDF底层的交叉引用表,把原画质的图片直接 dump 到本地,完全不需要像其他库那样做二次渲染。
import fitzimport osdef extract_images_from_pdf(pdf_path, output_folder):if not os.path.exists(output_folder):os.makedirs(output_folder)doc = fitz.open(pdf_path)image_count = 0for page_num in range(len(doc)):page = doc.load_page(page_num)# 获取当前页面的所有图片对象image_list = page.get_images(full=True)for img in image_list:xref = img[0] # 获取图片的交叉引用号# 提取图像的原始像素数据base_image = doc.extract_image(xref)image_bytes = base_image["image"]image_ext = base_image["ext"] # 获取扩展名,如png, jpeg# 保存到本地image_filename = f"{output_folder}/page_{page_num}_img_{image_count}.{image_ext}"with open(image_filename, "wb") as f:f.write(image_bytes)image_count += 1print(f"提取完成,共获取了 {image_count} 张图片。")doc.close()# extract_images_from_pdf("report.pdf", "./extracted_images")
场景三:像搭积木一样合并PDF
回到我文章开头提到的那个任务,当我把一堆Excel转换成了几百个横版的PDF后,如何快速把它们整合成一个汇总报告?用 PyMuPDF 的 insert_pdf 方法,几行代码就能搞定。
import fitzdef merge_pdfs(pdf_list, output_path):# 创建一个新的空白文档作为母本merged_doc = fitz.open()for pdf in pdf_list:# 打开需要合并的子文档sub_doc = fitz.open(pdf)# 将子文档的所有页面插入到母本的末尾# 也可以通过 from_page 和 to_page 参数指定只合并某几页merged_doc.insert_pdf(sub_doc)# 养成好习惯,随手关文档释放内存sub_doc.close()# 保存最终文档# garbage=4 参数会最大程度优化并压缩生成的PDF体积merged_doc.save(output_path, garbage=4, deflate=True)merged_doc.close()print(f"合并成功,文件已保存至:{output_path}")# my_pdfs = ["part1.pdf", "part2.pdf", "part3.pdf"]# merge_pdfs(my_pdfs, "final_report.pdf")
写在最后
这几年用Python处理了海量的数据和文档,踩过的坑不计其数。很多时候,选择一个对的工具,真的能省下大把掉头发的时间。
PyMuPDF 的功能远不止今天展示的这些,它还支持高亮文本标注、直接在PDF上画图、加密解密,甚至能把PDF直接转换成高清图片流。如果你平时的工作或科研中,经常需要和各种令人头大的文档打交道,强烈建议你花半个小时把这个库吃透。
下一次的推送,我会和大家聊聊在处理大规模数据集时的一些踩坑经验,我们不见不散。
编辑:余文彬
审校:余雨馨

夜雨聆风