在日常办公和数据处理中,PDF 是出现频率极高的文件格式。无论是阅读合同、整理报价单、合并扫描件、提取报销发票信息,还是把系统导出的 PDF 报表转成 Excel 再汇总,都会涉及大量重复操作。手动处理这些文件不仅浪费时间,还容易出错。而 Python 恰好提供了一套比较成熟的 PDF 处理方案,借助几个第三方库,就可以把重活交给脚本完成。
本篇文章属于 Python 办公自动化系列中 PDF 主题的开篇,会从 PDF 的基础概念讲起,结合代码演示常见操作方法,包括读取页面信息、合并拆分 PDF、提取文本和表格,以及 PDF 转 Word 的思路。适合对 Python 语法有基本了解、想在办公场景中提升效率的读者,也适合刚接触办公自动化的新手对照练习。
1. PDF 文件格式与办公自动化场景
1.1 PDF 格式的基本特点
PDF 的全称是 Portable Document Format,中文一般叫“便携式文档格式”,由 Adobe 公司提出,现在由国际标准化组织 ISO 维护。它最大的特点是“版式固定”:无论在 Windows、macOS、Linux,还是在不同品牌的打印机上打开,字体、图片、表格位置都能保持一致。对需要跨平台流转、打印、存档的正式文件来说,这是很明显的优势。
但从另一个角度看,PDF 也更像“一张电子纸”。它内部保存的是文字、矢量图形、图片对象以及它们的坐标位置,而不是像 Word 那样按段落和样式保存。所以 PDF 中的文字可以被复制阅读,但想直接改段落布局就比较麻烦。日常办公中常见的“PDF 怎么编辑”“PDF 转 Word 后排版乱了”等问题的根源,往往就在这里。
1.2 办公自动化中的常见 PDF 需求
在办公自动化的实际项目里,PDF 相关的需求大致可以分成几类:
| 需求类型 | 典型场景 | 常用处理方式 |
|---|---|---|
| 读取信息 | 获取 PDF 页数、标题、作者、创建时间 | PyPDF2、pypdf |
| 内容提取 | 从简历、发票、合同中提取文本 | pdfplumber、PyMuPDF |
| 表格抽取 | 把 PDF 中的表格数据转为 Excel | pdfplumber + pandas |
| 合并拆分 | 多个 PDF 合并成一个,或按页拆分 | pypdf、PyPDF2 |
| 格式转换 | PDF 转 Word、图片转 PDF、HTML 打印 PDF | pdf2docx、img2pdf 等 |
| 批量处理 | 给 PDF 添加水印、加密、拆分成单页 | pypdf 等库组合使用 |
如果是做财务报销、人力资源简历筛选、招投标文件整理、订单单据归档这类工作,上面这些需求几乎每天都会遇到。用 Python 写一个脚本,就能把几分钟手动操作缩短到几秒钟。
1.3 为什么选择 Python 做 PDF 文件处理
选择 Python,主要是因为它的库生态完整,语法相对简洁。处理 PDF 时,不需要去理解 PDF 标准内部的复杂字节结构,只需要调用库提供的 API,就能读取页面、文字和表格。另外 Python 本身擅长文本处理和数据分析,提取出 PDF 内容后,可以很方便地和 Excel、CSV、数据库对接,容易形成一条完整的自动化流程。
不过需要注意,Python 并没有一个能解决所有 PDF 需求的“万能库”。不同库各有特长:有的擅长读取元数据,有的擅长定位表格坐标,有的擅长重新排版生成新 PDF。因此,本文会从了解和选型开始,帮大家建立正确的知识框架。
2. Python 处理 PDF 的常用库
2.1 pypdf / PyPDF2:PDF 基础操作
PyPDF2 是老牌 Python PDF 处理库,很多早期教程都会提到它。不过它的维护节奏不太稳定,后来社区推出了 pypdf,可以看作 PyPDF2 的延续版本。新项目建议优先使用 pypdf,API 兼容性较好,仍在持续更新。
pypdf 适合做以下操作:
- 读取 PDF 页数、作者、标题等元数据。
- 按页合并、拆分 PDF。
- 给 PDF 旋转页面。
- 给 PDF 添加水印。
- 对 PDF 设置密码或解密。
它的优点是安装简单、API 直观;不足是提取复杂版式文本时表现一般。
2.2 pdfplumber:文本与表格精确提取
pdfplumber 是一个专注于“从 PDF 中提取信息”的库。它会把页面中的每一个字符、线条、矩形都解析成对象,然后提供基于坐标的访问方式。因此,当需要从带边框表格、报销单、物流单中抽取数据时,pdfplumber 通常比 pypdf 更可靠。
pdfplumber 的典型用途:
- 提取纯文本和指定坐标区域的文字。
- 提取表格,每个单元格中的内容会以二维列表返回。
- 查看页面中的线条、矩形位置,定位图片。
缺点是对复杂排版的处理需要一定调试成本,而且提取速度会比简单读取页数慢一些。
2.3 reportlab:从零生成 PDF
前面几个库偏重“读”,reportlab 则偏重“写”。它可以根据代码动态生成 PDF,适合在办公自动化流程中创建对账单、报告、标签等文件。reportlab 功能强大,但上手曲线比 pypdf 高一些,因为需要理解 canvas、platypus 等概念。
2.4 PyMuPDF:高性能渲染与操作
PyMuPDF 是对 MuPDF 库的封装,特点是速度快、功能全。它可以把 PDF 页面渲染成图片,也可以提取文本、图片和矢量图形,还支持在 PDF 上做注释。它的 API 名称以 fitz 开头,所以代码中常常看到import fitz。如果觉得 pdfplumber 提取速度太慢,可以尝试 PyMuPDF。
2.5 库的选型建议
| 目标 | 推荐库 |
|---|---|
| 合并、拆分、读取页数 | pypdf |
| 提取带坐标的文本 | pdfplumber、PyMuPDF |
| 提取表格并转 Excel | pdfplumber + openpyxl/pandas |
| 从零生成 PDF | reportlab |
| 扫描版 PDF 文字识别 | OCR 库,如 PaddleOCR、Tesseract |
| PDF 转 Word | pdf2docx |
实际项目中可以根据需求组合使用,比如先用 pypdf 拆分 PDF,再用 pdfplumber 提取特定页面的表格。
3. 环境准备与基础概念
3.1 Python 与第三方库安装
本文示例使用 Python 3.8 以上版本。如果电脑还没有安装 Python,可以到 Python 官网下载安装包,安装时记得勾选“Add Python to PATH”。安装完成后,在命令行输入下面命令确认版本:
python --version接着安装本文需要的第三方库:
pip install pypdf pdfplumber pdf2docx如果你的项目同时需要操作 Excel,可以继续安装 openpyxl 或 pandas:
pip install openpyxl pandas安装过程如果提示 pip 版本较旧,可以先升级 pip:
python -m pip install --upgrade pip3.2 示例文件准备
为了便于后续练习,建议创建一个独立的项目目录,并放入几个测试 PDF。比如:
pdf_auto/ ├── file1.pdf ├── file2.pdf └── demo.py如果手头没有现成的 PDF,可以先用 WPS 或浏览器打印功能生成一个带表格的 PDF,也可以在 Word 里简单制作后另存为 PDF。为了方便验证表格提取,建议准备一个带有明显边框表格的 PDF 文件。
3.3 PDF 对象模型的基础认识
在开始写代码前,先简单了解 PDF 的内部逻辑。一个 PDF 文件通常由 4 类对象组成:
- 内容流(Content Stream):描述页面上的文字、图形如何绘制。
- 页面对象(Page Object):对应一页,里面包含该页的媒体框、旋转角度、内容流引用等。
- 字体和资源对象:描述页面使用的字体、图片资源。
- 元数据(Metadata):如标题、作者、创建时间,通常存储在 DocumentInfo 或 XMP 元数据中。
pypdf 读取 PDF 后,会把整个文档封装成一个 PdfReader 对象。这个对象里面有一个 pages 列表,每个元素代表一页。理解了这个结构,后面的代码就会显得很自然:先打开文件,再遍历页面或读取页面属性。
4. 实战一:读取 PDF 基础信息
4.1 需求描述
我们有多个 PDF 文件,需要查看每个文件的页数、作者、创建日期等基本信息。这是 PDF 办公自动化中最入门的操作,适合理解 pypdf 的 API 用法。
4.2 编写代码
创建read_pdf_info.py,代码如下:
# 文件路径:pdf_auto/read_pdf_info.py from pypdf import PdfReader def show_pdf_info(pdf_path): reader = PdfReader(pdf_path) meta = reader.metadata print(f"文件:{pdf_path}") print(f"页数:{len(reader.pages)}") if meta: print(f"标题:{meta.title}") print(f"作者:{meta.author}") print(f"创建时间:{meta.creation_date}") # 读取第一页的尺寸 first_page = reader.pages[0] width = first_page.mediabox.width height = first_page.mediabox.height print(f"第一页尺寸:{width} x {height}") print("-" * 60) if __name__ == "__main__": show_pdf_info("file1.pdf") show_pdf_info("file2.pdf")4.3 运行与输出
在项目目录下执行:
python read_pdf_info.py可能的输出效果如下:
文件:file1.pdf 页数:3 标题:产品报价单 作者:Administrator 创建时间:2024-01-15 10:24:00 第一页尺寸:595.0 x 842.0 ------------------------------------------------------------ 文件:file2.pdf 页数:1 标题:报销申请表 作者:None 创建时间:None 第一页尺寸:595.0 x 842.0 ------------------------------------------------------------4.4 代码关键点解释
上面的代码虽然短,但有三个关键点值得展开。
reader.metadata返回的是一组文档属性,不同软件导出的 PDF 携带的元数据不一样。比如 Word 导出的 PDF 可能有标题和作者,而某些网页打印生成的 PDF 可能没有这些信息。因此打印时要用if meta做判断,避免属性为 None 时程序报错。
len(reader.pages)用于获取总页数。pypdf 会把 PDF 的页面对象全部加载到 pages 列表中,所以后期想按页拆分或提取,也都会通过这个 pages 列表操作。
reader.pages[0]是第一个页面对象,类型为 PageObject。mediabox表示页面的媒体框,也就是整页在 PDF 坐标系中的范围。对于 A4 纸,常见尺寸是 595 磅 × 842 磅,这里使用的单位是“磅”而不是“厘米”。如果在办公场景需要把 PDF 页面尺寸和实际纸张对应,要注意单位换算。
在实际办公中,这个脚本的用途不仅是查看单个文件。可以配合os.listdir()遍历某个目录下所有 PDF,再把信息输出到 Excel,形成一份“PDF 档案清单”。这一步先理解单个文件处理,后面再扩展到批量会更稳。
5. 实战二:合并与拆分 PDF
5.1 合并场景
在办公场景里,经常需要把多个 PDF 合并成一个文件。比如:从不同部门收集了若干合同附件,需要汇总成一份完整档案;或者扫描了多页文件,每一页保存成了单独 PDF,最后要合并成一个文档。
使用 pypdf 的 PdfWriter 可以很轻松完成合并操作。
5.2 编写合并脚本
创建merge_pdfs.py,代码如下:
# 文件路径:pdf_auto/merge_pdfs.py from pypdf import PdfWriter def merge_pdf_list(pdf_list, output_path): writer = PdfWriter() for pdf_file in pdf_list: # append 方法会把整个 PDF 追加到输出文件末尾 writer.append(pdf_file) with open(output_path, "wb") as out_file: writer.write(out_file) print(f"合并完成,共生成 {len(pdf_list)} 个 PDF 的合并文件:{output_path}") if __name__ == "__main__": merge_pdf_list(["file1.pdf", "file2.pdf"], "merged.pdf")5.3 按页拆分脚本
创建split_pdfs.py,把一个 PDF 中指定的页面拆出来,保存成新的 PDF 文件。
# 文件路径:pdf_auto/split_pdfs.py from pypdf import PdfReader, PdfWriter def split_pdf_by_pages(pdf_path, page_numbers, output_path): """ 从 pdf_path 中抽取指定页码组成新 PDF。 page_numbers 使用从 1 开始的编号,例如 [1, 3, 5] """ reader = PdfReader(pdf_path) writer = PdfWriter() for page_num in page_numbers: # 用户输入的页码减 1,才是 pages 列表的索引 page = reader.pages[page_num - 1] writer.add_page(page) with open(output_path, "wb") as out_file: writer.write(out_file) print(f"已抽取 {len(page_numbers)} 页,生成:{output_path}") if __name__ == "__main__": # 抽取 file1.pdf 的第 1 页和第 3 页 split_pdf_by_pages("file1.pdf", [1, 3], "file1_part.pdf")5.4 运行与边界情况说明
执行命令:
python merge_pdfs.py python split_pdfs.py执行后,项目目录下会生成merged.pdf和file1_part.pdf。
这里有一个很容易踩的坑:页码从 1 开始还是从 0 开始?用户看到的 PDF 页码习惯从 1 开始,而 Python 列表索引从 0 开始。上面代码把参数定义为从 1 开始,内部再page_num - 1转换,更符合办公程序的使用习惯。如果写成从 0 开始,用户传页码时很容易出错。
另外,merge 场景需要关注失败回滚。如果中途某个文件损坏,脚本可能抛异常。生产环境建议先对文件做可读性校验,比如用 try-except 捕获 PdfReader 异常,或者把已经写入的临时文件放到临时目录,等所有文件处理成功后再覆盖最终文件。这样可以避免输出一个半成品 PDF。
6. 实战三:提取 PDF 文本与表格
6.1 提取纯文本
从 PDF 中复制文字是办公常事,但遇到多个 PDF 需要批量提取时,用脚本更高效。pdfplumber 的 extract_text 方法可以提取整页文字。
创建extract_text.py:
# 文件路径:pdf_auto/extract_text.py import pdfplumber def extract_all_text(pdf_path): with pdfplumber.open(pdf_path) as pdf: total_text = [] for page in pdf.pages: text = page.extract_text() total_text.append(text) return "\n".join(filter(None, total_text)) if __name__ == "__main__": result = extract_all_text("file1.pdf") print(result)说明:page.extract_text()返回该页文本;如果页面是扫描图片且没有 OCR,那 text 可能为 None,这时需要使用 OCR 工具识别。
6.2 提取表格
表格提取比纯文本更实用,也更有挑战。pdfplumber 是根据页面上的线条和文字位置判断表格结构的,因此要求表格必须有清晰边框或分隔线。
创建extract_tables.py:
# 文件路径:pdf_auto/extract_tables.py import pdfplumber def extract_tables_from_pdf(pdf_path, page_number): """ 提取指定页中的多个表格 """ with pdfplumber.open(pdf_path) as pdf: page = pdf.pages[page_number] tables = page.extract_tables() for idx, table in enumerate(tables, start=1): print(f"第 {page_number + 1} 页,第 {idx} 个表格:") for row in table: print(row) print("-" * 60) if __name__ == "__main__": # 0 表示第一页 extract_tables_from_pdf("file1.pdf", 0)6.3 把表格输出到 Excel
办公自动化项目往往不满足于在控制台打印表格,而希望把多个 PDF 表格汇总到 Excel。下面利用 pandas 和 openpyxl 完成写入。
创建pdf_table_to_excel.py:
# 文件路径:pdf_auto/pdf_table_to_excel.py import pdfplumber import pandas as pd def pdf_table_to_excel(pdf_path, excel_path): all_rows = [] with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: tables = page.extract_tables() for table in tables: for row in table: # 单元格为 None 时填空字符串,避免写入 Excel 报错 cleaned_row = ["" if cell is None else cell for cell in row] all_rows.append(cleaned_row) if not all_rows: print("未提取到任何表格数据") return df = pd.DataFrame(all_rows) df.to_excel(excel_path, index=False, header=False) print(f"已导出 {len(all_rows)} 行数据到 {excel_path}") if __name__ == "__main__": pdf_table_to_excel("file1.pdf", "table_output.xlsx")6.4 表格提取的常见坑
表格提取并不是“万能识别”。如果 PDF 中的表格是图片,或者通过扫描件生成,pdfplumber 看不到文字对象,这时需要先做 OCR。另外,如果表格是跨页显示的,不同页的列宽可能不一致,直接拼接会错位,需要指定提取边界并做后处理。
常见做法是先用page.find_tables()查看 pdfplumber 检测到的表格区域,再根据需求微调参数。例如:
tables = page.find_tables() for table in tables: print(table.bbox) # 输出 bbox 左上角和右下角坐标这样能更清楚表格的识别范围,便于调试。
7. 实战四:PDF 转 Word 的思路
7.1 使用 pdf2docx 实现转换
“把 PDF 转成 Word”是很多办公人员的高频需求,但也是技术上容易踩坑的需求。PDF 本身保存的是固定版式,转 Word 本质上是重新分析页面布局并生成可编辑段落。目前比较流行的方案是 pdf2docx 库。
安装库:
pip install pdf2docx转换脚本:
# 文件路径:pdf_auto/pdf_to_word.py from pdf2docx import Converter def pdf_to_word(pdf_path, docx_path): cv = Converter(pdf_path) cv.convert(docx_path) cv.close() print(f"转换完成:{docx_path}") if __name__ == "__main__": pdf_to_word("file1.pdf", "file1.docx")7.2 pdf2docx 的适用边界
pdf2docx 转换的效果取决于源文件质量。如果 PDF 本身是从 Word 导出的纯文字文档,转换后版式相对理想;如果 PDF 是复杂宣传画册、艺术字体、多层图片背景,转换结果可能会乱。扫描件转 Word 则需要先用 OCR 识别,再按识别结果重新排版。
因此,在办公自动化流程中,建议先明确需求:是真的需要“可编辑 Word”,还是只要“文字可复制”。如果只需要复制文字,直接用 pdfplumber 提取文本再写入 Word 会更稳定。
7.3 备选方案:用 python-docx 生成结构化 Word
如果只是把从 PDF 中清洗好的数据写入 Word 模板,建议使用 python-docx。它比 pdf2docx 更容易控制最终样式。
# 文件路径:pdf_auto/write_docx.py from docx import Document def write_text_to_word(text_lines, output_path): doc = Document() for line in text_lines: doc.add_paragraph(line) doc.save(output_path) print(f"Word 文件已生成:{output_path}") if __name__ == "__main__": write_text_to_word(["第一行内容", "第二行内容"], "output.docx")注意,使用前需要安装:
pip install python-docx很多办公自动化的报表生成、通知生成、会议纪要归档,核心思路都是:先用 pdfplumber 把 PDF 的内容读取为结构化数据,再用 python-docx 生成带指定格式的新文档。这个方向比“格式还原”更实用,也更可靠。
8. 常见问题与排查思路
8.1 问题列表
在实际操作中,读者容易遇到以下错误或异常。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| ModuleNotFoundError: No module named 'pypdf' | 未安装或安装到了其他 Python 环境 | 执行 pip install pypdf,用 pip list 检查安装位置 |
| PdfReader 读取文件报错 | PDF 文件损坏或加密 | 先用其他阅读器打开,有密码则先用工具解除密码 |
| extract_text() 返回 None | 页面为扫描图片,没有可提取的文字层 | 改用 OCR 工具识别 |
| pdfplumber 提取表格乱行 | 表格线条不清晰或跨页 | 使用 page.find_tables() 查看 bbox,调整 table_settings |
| 中文写入 Excel 乱码 | 缺少字体或者编码配置问题 | 使用 openpyxl/pandas 引擎时确认不是编码问题,Excel 打开时注意内容格式 |
| PDF 转 Word 后排版变化大 | PDF 版式复杂,转换算法无法完全还原 | 调整需求,改提取内容并使用模板生成 Word |
| 运行脚本后没有输出 | 代码路径写错,或文件在当前目录找不到 | 使用 os.path.abspath 查看路径,确认文件和脚本在同一目录 |
8.2 加密 PDF 的处理
办公中收到的 PDF 有时带有打开密码。此时,使用 pypdf 读取前需要先用密码解密:
from pypdf import PdfReader reader = PdfReader("encrypted.pdf") if reader.is_encrypted: reader.decrypt("your_password") print(reader.pages)需要提醒的是,处理他人文件前要确保有合法授权。密码破解或绕过他人设置的访问控制可能涉及法律和安全问题,在自动化项目中不要盲目操作。
8.3 中文乱码问题
从 PDF 提取中文文本时,如果控制台出现乱码,通常不是提取失败,而是终端编码问题。Windows 命令行可以设置 UTF-8 编码,或在代码中把文本写入文件后查看。如果是 pdfplumber 无法提取中文,则考虑是字体编码映射问题,可以尝试 PyMuPDF 提取对比。
9. 最佳实践与工程建议
9.1 善于使用备份和只读模式
PDF 处理脚本在办公中常常需要覆盖或生成新文件。稳妥做法是先备份原始文件,避免处理中途损坏。比如合并前把多个源 PDF 放到“待合并”目录,输出到“合并结果”目录,而不是在原目录上直接覆盖。这样即使脚本出现问题,也不会丢失原始资料。
9.2 日志与异常处理
处理一个文件可能很简单,但处理几百个文件时,某个单独文件报错会导致整个流程中断。建议写一个统一的函数,对每个文件做独立 try-except,把失败信息记录到日志或结果列表中,而不是直接让程序崩溃。
import traceback result_log = [] def safe_process_pdf(pdf_path): try: # 业务处理代码 pass except Exception: result_log.append({ "path": pdf_path, "error": traceback.format_exc() })这样可以在循环结束后统一查看哪些文件有问题,再针对异常文件检查。
9.3 批量文件遍历技巧
如果要处理一个目录下所有 PDF,常用的遍历方式是:
from pathlib import Path pdf_dir = Path("./pdfs") for pdf_file in pdf_dir.glob("*.pdf"): print(pdf_file)使用 Path 对象能避免 Windows 和 Linux 路径分隔符不一致的问题。glob 模式可根据文件名前缀精确匹配,比如finance_*.pdf。
9.4 提取结果尽量保留元数据
从 PDF 提取文本或表格时,建议在输出结果中保留来源文件名、页码、表格序号。这样在 Excel 汇总多份文件时,可以追溯每行数据来自哪个 PDF 的哪一页,方便后续核对。这一点很容易被忽略,但在正式办公交付中非常重要。
9.5 不要过度追求一步到位
初学办公自动化时,容易想写一个大脚本完成所有需求。更好的方式是先拆成小模块:PDF 读取模块、表格清洗模块、Excel 输出模块、日志模块。后续即使更换具体库,也能快速替换,不至于重写全部逻辑。PDF 相关库的迭代不算慢,模块化能降低维护成本。
10. 后续学习与实践建议
本篇文章完成了 PDF 办公自动化的“了解”阶段,覆盖了基础概念、库选型和 4 个实战脚本,算是入门 Python PDF 处理的完整闭环。
如果你在实践中已经顺利完成上面例子,下一步可以围绕以下方向继续练习:
- 用 schedule 库定时执行 PDF 归档脚本,把脚本升级为守护式工具。
- 将 pypdf、pdfplumber、pandas 组合,把批量 PDF 报表的页面提取成标准化 Excel。
- 把 pdf2docx、python-docx 结合,制作“PDF 信息抽取 → 术语清洗 → Word 报告生成”的流水线。
- 遇到扫描版 PDF 时,引入 OCR 流程,实现扫描文档文字识别。
- 将多个脚本打包成简单的 Web 工具或带界面的桌面小软件,方便不会 Python 的同事使用。
办公自动化的价值不在代码本身,而在于把重复劳动变成稳定可靠的自动化流程。只要在真实项目中把一个小环节打通,接下来就会越用越顺手。