乐于分享
好东西不私藏

别再折腾PDFMiner了,这才是Python处理PDF的真正神器

别再折腾PDFMiner了,这才是Python处理PDF的真正神器

上个月我接了个相当折磨人的活儿。当时需要批量修改几百个报表文件的文档标签,调整打印边界后,再全部输出成多页横版的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 = 0    for 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 += 1    print(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直接转换成高清图片流。如果你平时的工作或科研中,经常需要和各种令人头大的文档打交道,强烈建议你花半个小时把这个库吃透。

下一次的推送,我会和大家聊聊在处理大规模数据集时的一些踩坑经验,我们不见不散。

编辑:余文彬

审校:余雨馨