Python提取PDF表格:从pdfplumber到camelot的完整指南
2026/9/2 11:02:44 网站建设 项目流程

为什么 PDF 中的表格提取总是“翻车”?很多同学拿到一份带表格的 PDF,第一反应是复制粘贴到 Excel,结果发现表格完全错位、换行丢失、单元格内容粘成一坨。换了几个在线工具,要么要付费,要么导出的表格在 Excel 里根本没法用。其实用 Python 提取 PDF 表格并没有想象中那么难,选对库、用对参数,大部分场景都能稳定输出。

本文会从 PDF 表格提取的核心难点讲起,整理一套基于 pdfplumber、camelot 等 Python 库的完整实操方案,包含从环境搭建到复杂表格处理的代码示例、常见报错排查思路和工程化建议。无论你是刚入门 Python 的测试、数据分析师,还是已经在业务中对接 PDF 数据流的后端开发,都可以在这篇文章里找到能直接落地的方案。

1. 背景与核心概念

1.1 为什么 PDF 表格提取这么难

要理解 PDF 表格提取,得先弄清楚 PDF 文件本身的底层结构。PDF 本质上是“描述页面排版的容器”,里面记录的是文本字符、线条、矩形的位置坐标,而不是像 Excel 那样有清晰的“行”和“列”概念。也就是说,PDF 里的表格对程序来说只是一堆文字片段和线框坐标,需要算法把这些碎片重新组合成结构化表格。

举个例子,同样一个“姓名、年龄、城市”三列表格,在 Excel 里是单元格,而在 PDF 里可能只是:

  1. 文字对象 “姓名” 出现在坐标 (100, 200) 处;
  2. 文字对象 “张三” 出现在坐标 (100, 185) 处;
  3. 若干条线段组成表格边框。

提取程序要做的事情,就是根据这些坐标和线框,反向还原出“哪几个文字属于同一行、同一列”的规则。一旦原 PDF 排版不规范,比如文字间距过大、表格线缺失、单元格合并、同一行文字高度不一致,还原难度就会指数上升。

1.2 主流的 Python 表格提取方案

目前 Python 生态中处理 PDF 表格提取的主流库主要有三个:pdfplumber、camelot 和 tabula-py。三者各有侧重:

原理优点局限
pdfplumber基于 pdfminer.six,按坐标解析文字与线框,提供extract_table()方法轻量、易安装、对普通有线表格效果好,能高度自定义提取逻辑对无边框表格和复杂合并单元格支持较弱
camelot通过 OpenCV 识别表格线,支持 lattice 和 stream 两种模式对有线表格识别准确率高,可输出 DataFrame、CSV、JSON 等格式依赖较多,安装稍复杂,大量扫描件 PDF 需先 OCR
tabula-py封装 Java 版 tabula,内部调用 Apache PDFBox对规范表格效果稳定,支持 JDBC 导出数据库需要 Java 环境,版本兼容问题偶尔让人头疼

从维护活跃度和社区使用量来看,pdfplumber 是目前最常被推荐的方案,本文主要围绕它展开,同时会补充 camelot 处理复杂线框表格的经验。

1.3 常见应用场景

PDF 表格提取在现实业务中很有价值,典型场景包括:

  • 从银行账单、财务凭证中提取交易明细;
  • 从科研论文、行业报告中抓取数据表;
  • 从政府公告、物流单据中采集字段信息;
  • 从扫描版 PDF 中批量获取报价单、订单数据;
  • 配合 pandas、Excel 进行数据分析与数据清洗。

掌握这项技能,等于多了一条“数据搬运管道”,能极大减少人工录入手工核对的时间。

2. 环境准备与版本说明

2.1 Python 环境

本文示例基于 Python 3.8 以上版本,操作系统可以是 Windows 10/11、macOS 或 Linux。如果你还没有安装 Python,建议到 Python 官网下载稳定版,安装时勾选 “Add Python to PATH”。装完后可以在命令行确认版本:

python --version

正常情况下会输出类似Python 3.11.2的信息。

2.2 安装 pdfplumber

使用 pip 安装 pdfplumber 和后续需要的 pandas、openpyxl(用于 Excel 导出):

pip install pdfplumber pandas openpyxl

这里简单说明每个库的作用:

  1. pdfplumber:负责解析 PDF 并提取表格;
  2. pandas:生成 DataFrame,方便后续处理和输出;
  3. openpyxl:pandas 导出 Excel 文件的底层引擎。

2.3 安装 camelot(可选)

如果需要尝试 camelot,建议安装带 cv 依赖的版本,并确保系统里有 Ghostscript:

pip install camelot-py[cv]

安装后可以用以下代码验证是否正常:

import camelot print(camelot.__version__)

如果你的网络环境安装受限,可以先跳过 camelot,本文核心案例以 pdfplumber 为主。

3. pdfplumber 核心用法拆解

3.1 打开 PDF 并读取页面

pdfplumber 的基本操作逻辑是:打开 PDF 文件 → 获取指定页面 → 对该页面提取文字、表格等元素。来看最简单的例子:

import pdfplumber # 打开 PDF 文件 with pdfplumber.open("example.pdf") as pdf: print("总页数:", len(pdf.pages)) # 获取第一页 first_page = pdf.pages[0] # 提取该页全部文本 text = first_page.extract_text() print(text)

这里有两个关键点:

  1. 使用with上下文管理器,能确保 PDF 文件被自动关闭,避免资源占用;
  2. pdf.pages[0]对应第一页,PDF 页码从 0 开始计数。

3.2 extract_table 与 extract_tables 的区别

这是新手最容易混淆的两个方法。

  • page.extract_table():提取页面中“第一个”表格,返回一个列表,列表的一行对应表格的一行,每行是一个单元格列表。
  • page.extract_tables():提取页面中“所有”表格,返回一个外层列表,里面每个元素是一个表格。

请看示例:

import pdfplumber with pdfplumber.open("table_demo.pdf") as pdf: first_page = pdf.pages[0] # 提取第一个表格 table = first_page.extract_table() print("第一个表格:") for row in table: print(row) # 提取所有表格 tables = first_page.extract_tables() print("\n总共有", len(tables), "个表格")

extract_table()返回的表格数据是二维列表,每一行是列表,每一个单元格可能是字符串或NoneNone通常表示该位置没有文字内容,比如合并单元格产生的空白区域。

3.3 常用参数说明

extract_table方法支持几个常用参数,理解它们能显著提升提取准确率:

page.extract_table( table_settings={ "vertical_strategy": "lines", "horizontal_strategy": "lines", "intersection_tolerance": 10, "text_tolerance": 3, } )

参数含义如下:

参数默认值作用
vertical_strategy"lines"用竖线确定表格列边界,可选值还有 "text"、"explicit"
horizontal_strategy"lines"用横线确定表格行边界,可选值同上
intersection_tolerance5判定两条线段是否相交的容差,单位像素
text_tolerance3文字与表格线的最小间距容差

如果 PDF 的表格线不完整,可以尝试把策略改成"text",让 pdfplumber 根据文字位置猜测行列:

table_settings = { "vertical_strategy": "text", "horizontal_strategy": "text", }

这种方式对无边框表格更有效,但误判率会稍高。

3.4 从提取结果到 DataFrame

实际业务中,我们通常希望把表格转换成 pandas DataFrame 继续处理。代码如下:

import pdfplumber import pandas as pd with pdfplumber.open("table_demo.pdf") as pdf: first_page = pdf.pages[0] table = first_page.extract_table() # 第一行作为表头 header = table[0] data_rows = table[1:] df = pd.DataFrame(data_rows, columns=header) print(df)

这段代码假设表格第一行是列名。如果 PDF 表格没有表头,直接pd.DataFrame(table)即可。

4. 完整实战案例

为了让你更有体感,这里从一个模拟业务场景展开:现在有一份季度销售报表 PDF,里面有多张表格,需要把所有表格提取出来并导出为 Excel 文件。

4.1 创建项目结构

在本地创建一个项目目录,结构如下:

pdf_table_extract/ ├── data/ │ ├── sales_report.pdf ├── extract_tables.py ├── requirements.txt

data/sales_report.pdf放需要提取的 PDF 文件,requirements.txt记录依赖。

4.2 编写依赖清单

requirements.txt内容:

pdfplumber==0.11.0 pandas==2.1.4 openpyxl==3.1.2

版本号可以根据你的环境调整,如果提示找不到对应版本,直接去掉版本号重新安装。

4.3 编写核心提取脚本

extract_tables.py完整代码:

import datetime import pdfplumber import pandas as pd from pathlib import Path def extract_all_tables(pdf_path: str, output_excel: str) -> None: """ 提取 PDF 中的所有表格,并导出为 Excel 文件。 每个页面单独写入一个工作表,同一页有多个表格时依次命名。 """ pdf_path = Path(pdf_path) output_excel = Path(output_excel) if not pdf_path.exists(): raise FileNotFoundError(f"PDF 文件不存在:{pdf_path}") # 记录每个页面的表格 all_tables = [] with pdfplumber.open(pdf_path) as pdf: for page_idx, page in enumerate(pdf.pages): tables = page.extract_tables() print(f"第 {page_idx + 1} 页找到 {len(tables)} 个表格") for table_idx, table in enumerate(tables): all_tables.append({ "page": page_idx + 1, "table_index": table_idx + 1, "data": table, }) if not all_tables: print("未提取到任何表格,请检查 PDF 是否包含可识别的表格结构。") return # 写入 Excel with pd.ExcelWriter(output_excel, engine="openpyxl") as writer: for item in all_tables: sheet_name = f"P{item['page']}_T{item['table_index']}" df = pd.DataFrame(item["data"]) df.to_excel(writer, sheet_name=sheet_name, index=False) print(f"完成!共导出 {len(all_tables)} 个表格,文件保存为:{output_excel}") if __name__ == "__main__": extract_all_tables("data/sales_report.pdf", "data/sales_tables.xlsx")

这里有几个值得注意的设计点:

  1. 函数参数使用类型注解,让代码可读性更好;
  2. 每个表格用“页码_表格序号”命名工作表,避免重复;
  3. 使用Path处理路径,兼容不同操作系统;
  4. 打印日志方便确认提取进度。

4.4 运行脚本

在命令行进入项目目录,执行:

python extract_tables.py

预期输出类似:

第 1 页找到 2 个表格 第 2 页找到 1 个表格 完成!共导出 3 个表格,文件保存为:data/sales_tables.xlsx

打开生成的 Excel 文件,就能看到按页面拆分好的表格数据。

4.5 结果说明

如果你的 PDF 表格带有合并单元格,extract_table()提取出来的二维列表里对应位置可能是None,pandas 会把它转成空值。后续处理时可以用fillna("")dropna()按需清洗:

df = pd.DataFrame(item["data"]).fillna("")

5. 进阶实战:处理复杂表格

现实中的 PDF 表格往往不如示例中那么规整,接下来看几种常见复杂场景。

5.1 无边框表格

有些 PDF 表格没有完整边框线,只有空格排版。此时需要调整vertical_strategyhorizontal_strategy"text"

import pdfplumber with pdfplumber.open("no_border_table.pdf") as pdf: page = pdf.pages[0] table_settings = { "vertical_strategy": "text", "horizontal_strategy": "text", } table = page.extract_table(table_settings) if table: for row in table: print(row)

注意,这种模式依赖于文字间距形成的“视觉列”,如果原 PDF 文字间距不均匀,提取结果可能错列,需要人工校验。

5.2 跨页连续表格

当一张表格跨越多页时,直接逐页提取会在每一页都出现表头,且中间数据会被拆断。一种思路是把每页的表头去掉,只保留数据行,再拼接成一个 DataFrame。示例逻辑如下:

import pdfplumber import pandas as pd all_rows = [] header = None with pdfplumber.open("multi_page_table.pdf") as pdf: for page in pdf.pages: table = page.extract_table() if not table: continue if header is None: header = table[0] # 第一页第一行作为表头 all_rows.extend(table[1:]) # 去掉表头 else: # 检查当前页第一行是否与表头相同(跨页重复表头) if table[0] == header: all_rows.extend(table[1:]) else: # 如果页首不是表头,说明这一页没有重复表头 all_rows.extend(table) df = pd.DataFrame(all_rows, columns=header) print(df)

两个数组直接比较table[0] == header在 Python 中是逐元素比较,会自动判断列表内容是否相等,逻辑上是成立的。

5.3 扫描版 PDF

如果 PDF 是扫描图片生成的整体图像,文字并未嵌入文件内,pdfplumber 无法直接提取文字内容。这时需要先做 OCR。

常用的 OCR 工具是 Tesseract,配合 Python 的pytesseract使用。流程是:先用 pdf2image 把 PDF 页面转成图片,再用 Tesseract 识别文字,最后根据识别出的坐标信息尝试拼表。这种方式实现成本较高,且准确率取决于扫描质量。对于扫描版 PDF,优先建议考虑商业 OCR 方案或专业的 PDF 表格识别工具,而不是单纯依赖开源库硬扛。

5.4 合并单元格的处理

pdfplumber 对合并单元格的处理结果是:合并区域中的首个单元格有值,其余位置返回None。例如一个“姓名”单元格跨两行,提取结果可能如下:

['姓名', '年龄', '城市'] ['张三', 25, '北京'] [None, 26, '上海']

这里None表示该行对应的位置继承上一行的“姓名”字段。处理时,可以采用向前填充的方法:

import pandas as pd df = pd.DataFrame(table) df = df.fillna(method="ffill") print(df)

fillna(method="ffill")会用上一行同列的值填充空值,这是合并单元格场景下常用的清洗手段。

6. 使用 camelot 提取有线表格

当表格线框复杂、pdfplumber 效果不理想时,可以试试 camelot。它的lattice模式专门针对带框线表格设计,识别效果通常更好。

6.1 基础用法

import camelot # lattice 模式:适合有边框表格 tables = camelot.read_pdf("lattice_table.pdf", flavor="lattice", pages="1") print("表格数量:", len(tables)) print(tables[0].df) # 导出 CSV tables[0].to_csv("output.csv")

6.2 与 pandas 联动

camelot 的df属性就是一个 pandas DataFrame,可以直接继续操作:

import pandas as pd tables = camelot.read_pdf("sales_report.pdf", flavor="lattice", pages="1,2") all_df = [] for table in tables: df = table.df all_df.append(df) result = pd.concat(all_df, ignore_index=True) print(result)

6.3 安装注意事项

camelot 依赖 Ghostscript,Windows 环境还需要安装对应的可执行文件,并将gs.exe所在目录加入系统 PATH。如果安装后运行报错GhostscriptNotFound,说明 Ghostscript 未配置成功。

依赖项较多的环境下,camelot 的安装体验确实不如 pdfplumber 平滑。如果你只是想快速提取普通表格,建议先从 pdfplumber 入手。

7. 常见问题与排查思路

掌握了基础用法,实际应用时还会遇到各种细节问题。这里整理一份高频问题排查表。

问题现象常见原因解决思路
extract_table()返回None页面表格结构无法被算法识别,或表格线严重缺失尝试extract_tables()查看页面所有表格;调整vertical_strategyhorizontal_strategy
提取出的列错位单元格间距不均匀,合并单元格干扰增大intersection_tolerance;改用"text"策略;对结果做人工清洗
中文乱码字体编码解析问题升级 pdfplumber 到最新版;必要时对 PDF 进行 OCR 预处理
读取加密 PDF 报错PDF 设置了解密口令使用pdfplumber.open(path, password="xxx")传入密码
提取速度很慢PDF 页数多、表格复杂只提取目标页面,用pages参数限定范围
导出 Excel 时提示缺失 openpyxl未安装 Excel 导出引擎执行pip install openpyxl
camelot 报Ghostscript错误Ghostscript 未安装或未配置环境变量安装 Ghostscript 并配置 PATH

7.1 加密 PDF 处理示例

加密 PDF 需要提供密码才能打开,pdfplumber 支持在open方法中传入密码:

import pdfplumber with pdfplumber.open("encrypted.pdf", password="your_password") as pdf: page = pdf.pages[0] table = page.extract_table() print(table)

需要注意,该方式只适用于打开权限密码,不适用于需要打印或编辑权限限制的 PDF,后者仍依赖于 PDF 文件本身的权限设置。

7.2 指定页面提取

如果 PDF 有几十页,每次全量提取会浪费大量时间。可以只提取特定页:

with pdfplumber.open("big_report.pdf") as pdf: # 页码从 0 开始,pages[3:5] 表示第 4 到第 5 页 for page in pdf.pages[3:5]: tables = page.extract_tables() print(f"页码 {page.page_number}: 发现 {len(tables)} 个表格")

8. 最佳实践与工程建议

有了代码和排错思路,最后提几条工程化建议,帮助你在真实项目中少踩坑。

8.1 先做页面级探索,再写全量脚本

拿到 PDF 后不要急着写提取逻辑,先手动打开 PDF,观察表格形态:是有完整边框、只有部分横线,还是完全无边框?不同形态对应不同参数组合。可以先写一段脚本,把每页的表格数量和数据片段打印出来,确认参数设置有效后再跑全量。

import pdfplumber with pdfplumber.open("unknown_report.pdf") as pdf: for idx, page in enumerate(pdf.pages): tables = page.extract_tables() for t in tables[:1]: print(f"第 {idx + 1} 页首个表格前 3 行:") for row in t[:3]: print(row)

8.2 输出文件做好命名和目录规划

批量处理场景下,建议把输出文件按“输入文件名_提取时间”的规则命名,避免覆盖和混乱。同时把原 PDF、中间结果、最终 Excel 分目录存放。

output/ ├── raw/ # 原始 PDF ├── temp/ # 中间提取结果 └── final/ # 最终交付文件

8.3 保留原始数据与清洗结果分离

提取结果先保留一份未清洗的原始版本,再做清洗操作。表格提取本身可能出错,清洗逻辑也可能有 bug,保留原始版本能让你在数据出问题时快速回溯,而不是返工查看日志。

8.4 用校验机制提升可靠性

提取后可以自动检查每个表格的列数是否一致,不一致往往说明某一行错位或合并单元格处理出了问题。

for row_index, row in enumerate(table): if len(row) != expected_cols: print(f"警告:第 {row_index} 行列数异常,期望 {expected_cols} 列,实际 {len(row)} 列")

8.5 权限与合规提醒

如果你在工作中处理的是客户报表、财务单据等敏感数据,需要注意:

  • 提取后的文件不要随意上传到第三方在线工具,避免数据泄露;
  • 对涉密或受版权保护的 PDF,务必确认自己拥有处理权限;
  • 自动化批量处理前,在测试环境用小范围样本验证,确认无误后再扩大范围。

9. 更进一步的学习方向

掌握基础提取后,相关内容还可以往这几个方向深入:

  1. TableMaster 等深度学习模型:对于复杂版式、不规则合并单元格,基于深度学习的表格结构识别模型比传统规则算法更鲁棒。
  2. OCR 与布局分析:扫描版 PDF 的表格提取离不开 OCR 和版面分析,建议了解 Tesseract、PaddleOCR 的表格识别能力。
  3. 数据质量验证:写数据校验规则,比如数值范围、列数、唯一性检查,确保提取后的数据具备可信度。
  4. 批量任务调度:如果每天都有新 PDF 需要处理,可以使用定时任务、消息队列等方式构建自动化管道。

实际项目中最优先关注的,仍然是提取准确率和异常样本覆盖率:先用几百份真实 PDF 验证方案,把出错样本分类,再针对高频问题调整参数或补充后处理逻辑。这样投入产出比最高。

如果这篇文章对你理解 Python 提取 PDF 表格有帮助,建议收藏备用。也欢迎动手拿几份不同样式的 PDF 练手,只有经历过“参数怎么调都不对”的阶段,才能真正掌握表格提取的思路。你在实际操作中如果遇到奇怪的表格结构,欢迎在评论区讨论,我看到后会尽量给出排查建议。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询