Python PDF神器PyMuPDF使用指南 (一)——安装和基础功能
系列文章:
Python PDF神器PyMuPDF使用指南 (一)——安装和基础功能
Python PDF神器PyMuPDF使用指南 (二)——文件和文本功能
Python PDF神器PyMuPDF使用指南 (三)——图像和注释功能
Python PDF神器PyMuPDF使用指南 (四)——绘图、多线程和OCR功能
Python PDF神器PyMuPDF使用指南 (五)——命令行使用
Python PDF神器PyMuPDF使用指南 (六)——Class和API详情1
正文:
PyMuPDF是一个高性能的Python库,用于PDF(和其他)文档的数据提取、分析、转换和操作。
Github地址为:pymupdf代码库
官方文档地址为:PyMuPDF文档
本文将介绍PyMuPDF的安装方式和基本的处理PDF的功能。
安装方式
基本安装方式(系统包含PyMuPDF的.whl文件)
以下所有示例假设您在一个 Python 虚拟环境中运行。详情请参见:https://docs.python.org/3/library/venv.html。同时假设您的 pip 是最新的。
例如:
Windows:
py -m venv pymupdf-venv
.\pymupdf-venv\Scripts\activate
python -m pip install --upgrade pip
Linux,MacOS:
python -m venv pymupdf-venv
. pymupdf-venv/bin/activate
python -m pip install --upgrade pip
通过pip安装PyMuPDF:
pip install --upgrade pymupdf
注意:这里是指系统平台有.whl文件,直接用.whl文件安装
如果系统平台没有.whl文件的安装方式
如果系统没有合适的.whl文件,pip 将自动从源代码构建,使用 Python sdist 文件。
这需要安装C/C++开发工具:
在Windows上:
该构建将自动下载并构建MuPDF。
安装后可能出现的问题
ImportError: DLL load failed while importing _fitz
有时会出现这个问题,通常是因为缺少 MSVCP140.dll,且这个问题出现在某些版本(2015-2017) 的 Microsoft Visual C++ Redistributables 中。
建议在 MSDN 中搜索 MSVCP140.dll,以查找如何重新安装它的说明。例如,您可以访问 Microsoft 支持页面 获取最新支持的版本链接。
更多详情请参考 PyMuPDF GitHub 问题页面。
ModuleNotFoundError: No module named 'frontend'
如果使用了 PyMuPDF 的旧名称 fitz
(例如 import fitz
而不是 import pymupdf
),且安装了一个名为 fitz
的无关 Python 包(pypi.org/fitz),就可能会发生这个问题。
fitz
包似乎不再维护(最后的版本是 2017 年发布的),而且遗憾的是无法将其从 pypi.org 删除。该包本身也无法正常工作,并且会破坏 PyMuPDF 使用旧名称的功能。
解决此问题有几种方法:
使用
import pymupdf
替代
import fitz
,并更新代码以匹配。
卸载
fitz
包并重新安装 PyMuPDF:
pip uninstall fitz
pip install --force-reinstall pymupdf
或者使用
import pymupdf as fitz
,但这种方式没有经过充分测试。
ImportError: /opt/conda/lib/python3.11/site-packages/pymupdf/libmupdf.so.24.4: undefined symbol: fz_pclm_write_options_usage
这似乎是 Jupyter Lab 中的一个问题,更多详情请参见:GitHub 问题链接。
注意事项
Windows 32-bit Intel.
Windows 64-bit Intel.
Linux 64-bit Intel.
Linux 64-bit ARM.
MacOS 64-bit Intel.
MacOS 64-bit ARM.
详细信息:
官网为上述平台发布了一个单独的.whl文件。
每个.whl文件使用当前最低支持的 Python 版本(目前是 3.9)的稳定 ABI,因此可以与所有较新的 Python 版本兼容,包括 Python 的新版本。
.whl文件已在当前 Python 版本列表中标记为“受支持”的版本上进行测试(参见:
https://devguide.python.org/versions/),目前是 3.9、3.10、3.11、3.12 和 3.13。
Pillow:用于
Pixmap.pil_save()
和
Pixmap.pil_tobytes()
方法。
fontTools:用于
Document.subset_fonts()
方法。
pymupdf-fonts:一组用于文本输出方法的漂亮字体。
Tesseract-OCR:用于图像和文档页面的光学字符识别(OCR)。Tesseract 是单独的软件,而不是 Python 包。要启用 PyMuPDF 的 OCR 功能,必须安装 Tesseract,并指定
tessdata
文件夹的位置,详情请参见下文。
注意:
您可以在安装 PyMuPDF 之前或之后安装这些额外的组件。PyMuPDF 在导入或使用相应功能时会自动检测它们的存在。
PDF基本处理功能介绍
注意1:这里提到的PDF文件都是标准的PDF,不是扫描版的PDF文件。扫描版的PDF文件操作方法会单独说明。
注意2:旧版的PyMuPDF在Python中import的是fitz,新版的改成了pymupdf
打开PDF文件
打开一个.pdf文件的具体方法:
import pymupdf
doc = pymupdf.open("a.pdf") # open a document
进一步了解:请查看
支持的文件类型列表以及
如何打开文件指南以获取更多高级选项。
从PDF中提取文本
从一个.pdf文件中提取文本内容的具体方法为:
import pymupdf
doc = pymupdf.open("a.pdf") # open a document
out = open("output.txt", "wb") # create a text output
for page in doc: # iterate the document pages
text = page.get_text().encode("utf8") # get plain text (is in UTF-8)
out.write(text) # write text of page
out.write(bytes((12,))) # write page delimiter (form feed 0x0C)
out.close()
这里不仅支持PDF格式,所有能支持的文档格式都能提取文本。
从PDF中提取图片
从一个.pdf文件中提取图片的具体方法为:
import pymupdf
doc = pymupdf.open("test.pdf") # open a document
for page_index in range(len(doc)): # iterate over pdf pages
page = doc[page_index] # get the page
image_list = page.get_images()
# print the number of images found on the page
if image_list:
print(f"Found {len(image_list)} images on page {page_index}")
else:
print("No images found on page", page_index)
for image_index, img in enumerate(image_list, start=1): # enumerate the image list
xref = img[0] # get the XREF of the image
pix = pymupdf.Pixmap(doc, xref) # create a Pixmap
if pix.n - pix.alpha > 3: # CMYK: convert to RGB first
pix = pymupdf.Pixmap(pymupdf.csRGB, pix)
pix.save("page_%s-image_%s.png" % (page_index, image_index)) # save the image as png
pix = None
进一步了解:后续会详细介绍提取特定区域的文本、表格等具体方法。
提取矢量图形
从.pdf中提取页面中所有的矢量图形,可以执行以下操作:
doc = pymupdf.open("some.file")
page = doc[0]
paths = page.get_drawings()
这将返回一个字典,其中包含页面上找到的所有矢量图形路径。
合并多个PDF文件
要合并多个.pdf文件,可以执行以下操作:
import pymupdf
doc_a = pymupdf.open("a.pdf") # 打开第一个文档
doc_b = pymupdf.open("b.pdf") # 打开第二个文档
doc_a.insert_pdf(doc_b) # 合并文档
doc_a.save("a+b.pdf") # 保存合并后的文档并使用新文件名
合并PDF文件与其他类型的文件
使用 Document.insert_file()
方法,可以将支持的文件类型与PDF合并。例如:
import pymupdf
doc_a = pymupdf.open("a.pdf") # 打开第一个文档
doc_b = pymupdf.open("b.svg") # 打开第二个文档
doc_a.insert_file(doc_b) # 合并文档
doc_a.save("a+b.pdf") # 保存合并后的文档并使用新文件名
进一步了解:
合并PDF非常简单,可以使用 Document.insert_pdf()
和 Document.insert_file()
方法。在打开PDF文档后,可以从一个文档中复制页面范围到另一个文档。可以选择插入页面的位置,还可以反转页面顺序,甚至改变页面的旋转角度。这个文章包含了详细的描述。
GUI脚本 join.py
使用此方法来合并文件列表,同时合并各自的目录内容。它看起来是这样的:
图片来源于PyMuPDF官网
处理坐标
在使用PyMuPDF时,有一个数学术语是您应该熟悉的——“坐标”。请快速查看坐标部分,了解坐标系统,帮助定位对象并理解文档空间。
向PDF添加水印
要向PDF文件添加水印,请执行以下操作:
import pymupdf
doc = pymupdf.open("document.pdf") # 打开文档
for page_index in range(len(doc)): # 遍历PDF页面
page = doc[page_index] # 获取页面
# 在页面边界处插入文件名为"watermark.png"的图片水印
page.insert_image(page.bound(), filename="watermark.png", overlay=False)
doc.save("watermarked-document.pdf") # 保存带有水印的文档
进一步了解
添加水印的本质就是在每个PDF页面的底部添加图像。应确保图像具有所需的透明度和纵横比,以便它按照需求显示。
在上面的示例中,每个文件引用都会创建一个新图像,但为了提高性能(节省内存和文件大小),应只引用一次此图像数据——请参考
Page.insert_image()
的代码示例和解释。
向PDF添加图像
要向PDF文件添加图像,例如添加一个logo,可以执行以下操作:
import pymupdf
doc = pymupdf.open("document.pdf") # 打开文档
for page_index in range(len(doc)): # 遍历PDF页面
page = doc[page_index] # 获取页面
# 在文档的左上角插入名为"my-logo.png"的图像logo
page.insert_image(pymupdf.Rect(0, 0, 50, 50), filename="my-logo.png")
doc.save("logo-document.pdf") # 保存带有logo的文档
旋转PDF页面
要旋转PDF页面,请执行以下操作:
import pymupdf
doc = pymupdf.open("test.pdf") # 打开文档
page = doc[0] # 获取文档的第一个页面
page.set_rotation(90) # 旋转页面
doc.save("rotated-page-1.pdf")
裁剪PDF页面
要将PDF页面裁剪为自定义的矩形,请执行以下操作:
import pymupdf
doc = pymupdf.open("test.pdf") # 打开文档
page = doc[0] # 获取文档的第一个页面
page.set_cropbox(pymupdf.Rect(100, 100, 400, 400)) # 设置页面裁剪框
doc.save("cropped-page-1.pdf")
附加文件
要将另一个文件附加到PDF页面,请执行以下操作:
import pymupdf
doc = pymupdf.open("test.pdf") # 打开主文档
attachment = pymupdf.open("my-attachment.pdf") # 打开要附加的文档
page = doc[0] # 获取文档的第一个页面
point = pymupdf.Point(100, 100) # 创建附加点
attachment_data = attachment.tobytes() # 获取文档的字节数据作为缓冲区
# 使用点、数据和文件名添加文件注释
file_annotation = page.add_file_annot(point, attachment_data, "attachment.pdf")
doc.save("document-with-attachment.pdf") # 保存文档
进一步了解:当使用
Page.add_file_annot()
方法添加文件时,第三个参数(文件名)应包括实际的文件扩展名。如果没有扩展名,附件可能无法被识别为可以打开的文件。例如,如果文件名为“attachment”,那么在查看结果PDF并尝试打开附件时,您可能会遇到错误。但如果文件名为“attachment.pdf”,PDF查看器可以将其识别为有效的文件类型并打开它。
嵌入文件
要向.pdf文档中嵌入文件,请执行以下操作:
import pymupdf
doc = pymupdf.open("test.pdf") # 打开主文档
embedded_doc = pymupdf.open("my-embed.pdf") # 打开要嵌入的文档
embedded_data = embedded_doc.tobytes() # 获取文档的字节数据作为缓冲区
# 使用文件名和数据进行嵌入
doc.embfile_add("my-embedded_file.pdf", embedded_data)
doc.save("document-with-embed.pdf") # 保存文档
进一步了解:与附加文件类似,当使用
Document.embfile_add()
方法添加文件时,注意第一个参数(文件名)应包括实际的文件扩展名。
删除页面
要从文档中删除页面,请执行以下操作:
import pymupdf
doc = pymupdf.open("test.pdf") # 打开文档
doc.delete_page(0) # 删除文档的第一个页面
doc.save("test-deleted-page-one.pdf") # 保存文档
要删除多个页面,请执行以下操作:
import pymupdf
doc = pymupdf.open("test.pdf") # 打开文档
doc.delete_pages(from_page=9, to_page=14) # 删除文档中的页面范围
doc.save("test-deleted-pages.pdf") # 保存文档
如果删除的页面被书签或超链接引用,会发生什么?
书签(目录中的条目)将变得无效,不再导航到任何页面。
超链接将从包含它的页面中删除,但该页面上的可见内容将不会发生其他变化。
注意:页面索引是基于零的,因此要删除文档的第10页,可以执行
doc.delete_page(9)
。
类似地,
doc.delete_pages(from_page=9, to_page=14)
将删除第10到第15页(包括)。
重新排列页面
要更改页面的顺序,即重新排列页面,请执行以下操作:
import pymupdf
doc = pymupdf.open("test.pdf") # 打开文档
doc.move_page(1, 0) # 将文档的第2页移动到文档的开头
doc.save("test-page-moved.pdf") # 保存文档
复制页面
要复制页面,请执行以下操作:
import pymupdf
doc = pymupdf.open("test.pdf") # 打开文档
doc.copy_page(0) # 复制第1页并将其放到文档的末尾
doc.save("test-page-copied.pdf") # 保存文档
选择页面
要选择页面,按照以下步骤操作:
import pymupdf
doc = pymupdf.open("test.pdf") # 打开文档
doc.select([0, 1]) # 选择第1和第2页
doc.save("just-page-one-and-two.pdf") # 保存文档
进一步操作
使用PyMuPDF,你可以执行所有的页面复制、移动、删除或重新排列操作。可以通过像Document.copy_page()
这样的方法来逐页操作。
或者,你还可以准备一个完整的新页面布局,形式为一个包含你需要的页码的Python列表,按你想要的顺序排列,并且可以重复多次。例如,下面的代码展示了可以用Document.select()
实现的功能:
doc.select([1, 1, 1, 5, 4, 9, 9, 9, 0, 2, 2, 2])
接下来,假设我们准备打印双面文件(如果打印机不支持双面打印):
文档的页数由len(doc)
(即doc.page_count
)给出。以下列表示奇数和偶数页号:
p_even = [p in range(doc.page_count) if p % 2 == 0]
p_odd = [p in range(doc.page_count) if p % 2 == 1]
这段代码创建了相应的子文档,可以用来打印文档:
doc.select(p_even) # 仅选择偶数页
doc.save("even.pdf") # 保存“偶数”PDF
doc.close() # 回收文件
doc = pymupdf.open(doc.name) # 重新打开
doc.select(p_odd) # 对奇数页执行同样的操作
doc.save("odd.pdf")
反转页面顺序
以下代码示例将所有页面的顺序反转(非常快速:对于756页的Adobe PDF文档,耗时不到一秒):
lastPage = doc.page_count - 1
for i in range(lastPage):
doc.move_page(lastPage, i) # 将当前最后一页移到最前面
这段代码将PDF复制一遍,使其包含从0到n页,再从0到n页(极其快速且几乎不增加文件大小):
page_count = len(doc)
for i in range(page_count):
doc.copy_page(i) # 复制当前页到文档末尾
添加空白页
要添加空白页,按以下步骤操作:
import pymupdf
doc = pymupdf.open(...) # 新建或打开现有PDF文档
page = doc.new_page(-1, # 插入位置:文档末尾
width=595, # 页面尺寸:A4竖向
height=842)
doc.save("doc-with-new-blank-page.pdf") # 保存文档
进一步操作
你可以使用此方法创建具有预定义纸张格式的页面:
w, h = pymupdf.paper_size("letter-l") # 'Letter'横向
page = doc.new_page(width=w, height=h)
便利函数paper_size()
支持40多种行业标准的纸张格式。要查看它们,检查字典paperSizes
。将所需的字典键传递给paper_size()
以获取纸张尺寸。支持大小写。如果在格式名称后附加"-L",则返回横向版本。
以下是一个创建具有一个空白页面的PDF的3行代码:
doc = pymupdf.open()
doc.new_page()
doc.save("A4.pdf")
插入文本内容的页面
使用Document.insert_page()
方法可以插入新页面,并且接受相同的宽度和高度参数。该方法还允许你向新页面插入任意文本,并返回插入的行数。
import pymupdf
doc = pymupdf.open(...) # 新建或打开现有PDF文档
n = doc.insert_page(-1, # 默认插入位置
text="The quick brown fox jumped over the lazy dog",
fontsize=11,
width=595,
height=842,
fontname="Helvetica", # 默认字体
fontfile=None, # 任何字体文件名
color=(0, 0, 0)) # 文本颜色(RGB)
进一步操作
text
参数可以是一个(字符串的)序列(假设是UTF-8编码)。文本插入将从页面顶部1英寸、左侧50点的位置开始。返回插入的文本行数。
拆分单个页面
这部分介绍如何将PDF的页面拆分成任意多个部分。例如,你可能有一个Letter格式的PDF页面,你希望将其放大四倍:将每个页面拆分成4个部分,每部分放到一个新的Letter格式页面中。
import pymupdf
src = pymupdf.open("test.pdf")
doc = pymupdf.open() # 空的输出PDF
for spage in src: # 对输入文档中的每一页进行操作
r = spage.rect # 输入页面矩形区域
d = pymupdf.Rect(spage.cropbox_position, # 如果没有则使用CropBox的位移
spage.cropbox_position) # 从(0, 0)开始
#----------------------------------------------------------------------
# 示例:将输入页面切割成2 x 2的部分
#----------------------------------------------------------------------
r1 = r / 2 # 左上角矩形区域
r2 = r1 + (r1.width, 0, r1.width, 0) # 右上角矩形区域
r3 = r1 + (0, r1.height, 0, r1.height) # 左下角矩形区域
r4 = pymupdf.Rect(r1.br, r.br) # 右下角矩形区域
rect_list = [r1, r2, r3, r4] # 将它们放入列表
for rx in rect_list: # 遍历矩形列表
rx += d # 添加CropBox位移
page = doc.new_page(-1, # 用rx尺寸的新页面
width=rx.width,
height=rx.height)
page.show_pdf_page(
page.rect, # 填充整个新页面
src, # 输入文档
spage.number, # 输入页面编号
clip=rx, # 使用输入页面的哪一部分
)
# 保存输出文件
doc.save("poster-" + src.name,
garbage=3, # 删除重复对象
deflate=True, # 尽可能压缩内容
)
例如:
合并单个页面
这部分处理将多个PDF页面合并成一个新PDF,每页合并两个或四个原始页面(也称为“2-up”,“4-up”等)。这可以用于创建小册子或缩略图式的概览。
import pymupdf
src = pymupdf.open("test.pdf")
doc = pymupdf.open() # 创建空的输出PDF
width, height = pymupdf.paper_size("a4") # A4纵向输出页面格式
r = pymupdf.Rect(0, 0, width, height)
# 定义每页的4个矩形区域
r1 = r / 2 # 左上矩形
r2 = r1 + (r1.width, 0, r1.width, 0) # 右上矩形
r3 = r1 + (0, r1.height, 0, r1.height) # 左下矩形
r4 = pymupdf.Rect(r1.br, r.br) # 右下矩形
# 将这些矩形放入列表中
r_tab = [r1, r2, r3, r4]
# 现在将输入页面复制到输出中
for spage in src:
if spage.number % 4 == 0: # 每4页创建一个新的输出页面
page = doc.new_page(-1, width=width, height=height)
# 将输入页面插入到正确的矩形区域
page.show_pdf_page(r_tab[spage.number % 4], # 选择输出矩形
src, # 输入文档
spage.number) # 输入页面编号
# 使用垃圾回收和压缩保存新文件
doc.save("4up.pdf", garbage=3, deflate=True)
例如:
PDF加密与解密
从版本1.16.0开始,PyMuPDF完全支持PDF的加密与解密(使用密码)。你可以执行以下操作:
Document.needs_pass
, Document.is_encrypted
)。Document.authenticate()
)。Document.save()
或Document.write()
来加密或解密内容。示例代码:
import pymupdf
text = "some secret information" # 保密信息
perm = int(
pymupdf.PDF_PERM_ACCESSIBILITY # 始终使用此权限
| pymupdf.PDF_PERM_PRINT # 允许打印
| pymupdf.PDF_PERM_COPY # 允许复制
| pymupdf.PDF_PERM_ANNOTATE # 允许注释
)
owner_pass = "owner" # 所有者密码
user_pass = "user" # 用户密码
encrypt_meth = pymupdf.PDF_ENCRYPT_AES_256 # 最强加密算法
doc = pymupdf.open() # 创建空PDF
page = doc.new_page() # 创建空页面
page.insert_text((50, 72), text) # 插入数据
doc.save(
"secret.pdf",
encryption=encrypt_meth, # 设置加密方法
owner_pw=owner_pass, # 设置所有者密码
user_pw=user_pass, # 设置用户密码
permissions=perm, # 设置权限
)
注意:
使用某些查看器(Nitro Reader 5)打开此文档反映了以下设置:
当没有提供加密参数时,解密将像以前一样在保存时自动发生。
要保留PDF的加密方法,请使用encryption=pymupdf保存它。PDF_ENCRYPT_KEEP。如果doc.can_save_incrementally()==True,则也可以进行增量保存。
要更改加密方法,请指定上述所有选项(加密、owner_pw、user_pw、权限)。在这种情况下,增量保存是不可能的。
从页面中提取表格
你可以在任何文档页面中找到并提取表格:
import pymupdf
from pprint import pprint
doc = pymupdf.open("test.pdf") # 打开文档
page = doc[0] # 获取文档的第一页
tabs = page.find_tables() # 查找并提取页面中的表格
print(f"{len(tabs.tables)} found on {page}") # 显示找到的表格数量
if tabs.tables: # 如果至少找到一个表格
pprint(tabs[0].extract()) # 打印第一个表格的内容
获取页面链接
可以从页面中提取链接并返回链接对象:
import pymupdf
for page in doc: # 遍历文档页面
link = page.first_link # 获取第一个链接对象或None
while link: # 遍历页面上的所有链接
# 对链接进行处理,然后:
link = link.next # 获取下一个链接,最后一个链接的next为None
获取文档中的所有注释
页面上的注释可以通过page.annots()
方法获取:
import pymupdf
for page in doc:
for annot in page.annots():
print(f'注释所在页面:{page.number} 类型:{annot.type} 区域:{annot.rect}')
从PDF中删除内容(加密)
加密标记是特殊类型的注释,表示文档中某些区域应该被安全删除。例如,删除文档中所有“Jane Doe”名称的实例:
import pymupdf
# 打开PDF文档
doc = pymupdf.open('test.pdf')
# 遍历文档中的每一页
for page in doc:
# 查找当前页面中的所有“Jane Doe”实例
instances = page.search_for("Jane Doe")
# 删除每一个“Jane Doe”实例
for inst in instances:
page.add_redact_annot(inst)
# 应用删除操作
page.apply_redactions()
# 保存修改后的文档
doc.save('redacted_document.pdf')
# 关闭文档
doc.close()
还可以通过设置参数来标记页面中的特定区域进行加密,但不影响其中的线条图形(例如矢量图形):
import pymupdf
# 打开PDF文档
doc = pymupdf.open('test.pdf')
# 获取第一页
page = doc[0]
# 标记要删除的区域
rect = [0,0,200,200]
# 添加加密注释,该注释将有红色填充
page.add_redact_annot(rect, fill=(1,0,0))
# 应用删除操作,但忽略矢量图形
page.apply_redactions(graphics=0)
# 保存修改后的文档
doc.save('redactied_document.pdf')
# 关闭文档
doc.close()
警告:一旦保存了加密文档,文档中的加密内容将无法恢复。因此,标记为加密的区域将完全删除该区域的文本和图形。
PDF文档转换
我们建议使用pdf2docx库,它结合了PyMuPDF和python-docx库,提供了从PDF到DOCX格式的简单文档转换。(后面会仔细介绍这个库)
总结
上面是PyMuPDF基本的安装和使用方法,后面会详细介绍这个神器的更多功能。
作者:塞大花