尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python用pdfplumber提取PDF表格并写入Excel的完整指南

Python用pdfplumber提取PDF表格并写入Excel的完整指南 PDF 文件大家平时经常遇到但要把它里面的文字、表格准确提取出来很多朋友第一反应是手动复制粘贴。遇到一页两页还好如果遇到几十页的合同、年报、论文手动整理既费时间又容易错行漏字。本文围绕 pdfplumber 这个 Python 库详细讲解从 PDF 中读取文本、提取表格并写入 Excel 文件的完整流程。文章会从安装环境开始逐步拆解 pdfplumber 的核心 API再通过实际案例把 PDF 表格内容导出到 Excel最后整理常见报错和最佳实践。无论你是刚接触 Python 的初学者还是需要批量处理 PDF 数据的开发人员都能在这篇文章里找到可以直接落地的方案。1. 为什么选择 pdfplumber 处理 PDF1.1 Python 解析 PDF 的痛点PDF 本质上是一种“固定版式”的文档格式。它保存的是文字、图形在页面上的精确位置信息而不是像 Word 或 HTML 那样保留段落、表格等结构化语义。这意味着当我们尝试用程序读取 PDF 时会遇到几个非常典型的问题同一个 PDF 里可能混合了文本、图片、表格、扫描件不同类型的内容提取方式完全不同。部分 PDF 中的文字是用矢量曲线绘制的本身没有文本层复制时显示为乱码或空白。表格的边框、列宽、换行逻辑多种多样按坐标简单分割容易错位。很多人首先会想到 PyPDF2、pdfminer 等库。它们确实能提取文本但遇到带格式的报表、复杂表格时输出结果往往需要大量二次加工。pdfplumber 在 pdfminer 的基础上做了更高层封装既能提取文本也能比较智能地识别页面中的线条、矩形和表格结构因此更适合“把 PDF 中的表格内容整理成 Excel”这类需求。1.2 pdfplumber 的核心定位pdfplumber 是一个基于 Python 的 PDF 文本和表格提取工具。它把每个页面抽象成Page对象我们可以从页面中获取纯文本内容表格结构和单元格坐标页面中的线条、矩形、曲线每个字符的详细位置信息。结合本文场景我们最常用的是extract_text()和extract_tables()两个方法。前者用于读取普通段落文字后者用于识别表格线并返回行列数据。提取结果是以二维列表list of list形式存在的正好方便转换为 Excel 的行列结构或者交给 pandas 处理。1.3 常见应用场景从实际项目经验来看pdfplumber 用得最多的场景包括银行对账单、财务报表的数据抽取论文、测试报告中的结果数据整理合同关键字段的定位与提取批量将 PDF 附件转换为 Excel 汇总表。本文的侧重点是“PDF 表格写入 Excel”但讲解过程中会把文本提取和表格提取一并覆盖让你在不同场景下都能找到对应的方法。2. 环境准备与安装2.1 Python 环境说明pdfplumber 是一个第三方库需要 Python 3.7 及以上版本。建议使用 Python 3.8 以上的稳定版本。如果你还没有安装 Python去 Python 官网下载安装包时注意勾选“Add Python to PATH”选项否则后续在命令行使用pip会出现找不到命令的情况。可以在命令行中先确认 Python 版本python --version输出示例Python 3.10.11然后升级 pip 并安装 pdfplumber建议一并安装 openpyxl用于后续写入 Excelpip install --upgrade pip pip install pdfplumber openpyxl如果你的网络环境访问默认 PyPI 源比较慢可以使用国内镜像源pip install pdfplumber openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple版本方面不需要刻意指定最新版pdfplumber 当前的主流稳定版本即可。如果后面遇到依赖冲突或报错可以查看已安装版本pip show pdfplumber2.2 验证安装是否成功安装完成后在 Python 交互环境或脚本中执行以下代码import pdfplumber print(pdfplumber.__version__)如果能看到版本号输出说明安装成功可以进入下一步。2.3 示例文件准备本文后面提到的所有示例会使用一个模拟的“月度销售报表.pdf”作为输入文件。你可以用任意一个包含规整表格的 PDF 代替例如从年报中截取的表格页面网上下载的测试 PDF本地 Excel 打印成 PDF 后生成的文件。为了便于描述后面统一将该文件命名为sales_report.pdf放在脚本同目录下。如果你准备的 PDF 是不带扫描内容的文本型 PDF示例结果会更稳定。如果 PDF 是扫描图片读者需要先 OCR 识别才能继续这部分内容不在本文讨论范围内。3. pdfplumber 核心用法拆解3.1 加载 PDF 文件使用 pdfplumber 打开 PDF 的基本写法如下import pdfplumber # 方法一with 语句自动关闭文件 with pdfplumber.open(sales_report.pdf) as pdf: print(f总页数: {len(pdf.pages)}) # 获取第一页 first_page pdf.pages[0]建议始终使用with语句打开文件这样可以确保资源被正确释放。pdf.pages是一个列表其中每个元素对应 PDF 中的一页索引从 0 开始。3.2 提取文本内容提取纯文本使用extract_text()方法with pdfplumber.open(sales_report.pdf) as pdf: first_page pdf.pages[0] text first_page.extract_text() print(text)如果 PDF 页面包含多栏文字extract_text()默认按从左到右、从上到下的顺序输出但复杂排版的顺序可能不理想。pdfplumber 还支持通过x_tolerance控制字符之间的容差用来调整分栏识别效果text first_page.extract_text(x_tolerance2)这里x_tolerance的值表示同一行中两个字符之间的最小间距超过该间距可能会被视为不同的文本块。数值越小越容易把间隔较大的字符拼回同一行数值越大越容易断行。具体参数需要根据 PDF 实际排版试调。3.3 提取表格数据对于带边框的规整表格调用extract_tables()就能获得表格数据with pdfplumber.open(sales_report.pdf) as pdf: first_page pdf.pages[0] tables first_page.extract_tables() print(tables)返回结果是一个三维列表。最外层是页面中的所有表格每个表格是一个二维列表二维列表的每一行对应表格的一行每个元素就是单元格中的文本。如果没有识别到表格返回值是空列表[]。实际打印出来的效果类似[ [ [产品名称, 销售数量, 单价, 销售额], [A产品, 120, 25.0, 3000.0], ... ] ]3.4 extract_text 与 extract_tables 的适用边界很多初学者会把这两个方法混用或者在一个方法提取失败后直接认为库不好用。其实它们的定位不同extract_text()适合读取文章、公告、说明性文字等连续段落。extract_tables()适合读取有线框围成的表格数据。如果表格没有边框extract_tables()可能识别不到这时可以调整table_settings参数例如增加vertical_strategy或horizontal_strategy的检测策略table_settings { vertical_strategy: lines, horizontal_strategy: lines } tables first_page.extract_tables(table_settings)vertical_strategy可选值包括lines、text、explicit等。lines表示只依赖可见线条text表示通过文字的间距推断列位置。对于无边框但本身是表格格式的内容后一种策略更有效。4. 实战案例PDF 表格读取并写入 Excel4.1 功能需求拆解假设我们有一份月度销售报表里面包含每个产品的名称、销售数量、单价、销售额。现在需要读取 PDF 第一页中的表格并将其写入 Excel 文件中。这个场景在财务对账、运营汇总中很常见。目标拆解读取 PDF定位到表格所在的页面。使用extract_tables()提取表格为二维列表。创建 Excel 工作簿将二维列表逐行写入工作表。保存 Excel 文件。4.2 编写基础代码创建一个 Python 文件例如pdf_to_excel.py写入以下代码# -*- coding: utf-8 -*- import pdfplumber from openpyxl import Workbook # 1. 打开 PDF 并提取表格 with pdfplumber.open(sales_report.pdf) as pdf: page pdf.pages[0] tables page.extract_tables() print(识别到表格数量:, len(tables)) # 这里假设第一页只有一个表格 table tables[0] # 2. 创建 Excel 工作簿 wb Workbook() ws wb.active ws.title 销售报表 # 3. 将表格数据写入工作表 for row in table: ws.append(row) # 4. 保存文件 wb.save(sales_report.xlsx) print(已保存为 sales_report.xlsx)运行代码python pdf_to_excel.py4.3 代码分步解释这段代码虽然不到 20 行但每一步都很关键。第 6 行到第 11 行使用with pdfplumber.open()打开 PDF取出第一页然后调用extract_tables()。因为返回结果是嵌套列表所以table变量本身也是一个二维列表。第 14 行创建一个新的工作簿。Workbook()来自 openpyxl 库默认自带一个工作表。第 17 行修改默认工作表的名称为“销售报表”。第 20 行到第 21 行循环遍历二维列表的每一行并通过append()写入 Excel。openpyxl 会按顺序把每一个单元格文本放到当前行的对应列。最后保存文件。4.4 执行结果说明如果一切正常会在当前目录下生成sales_report.xlsx。用 Excel 打开后你会看到 PDF 中表格的每一行都对应 Excel 中的一行。表头如果没有特殊处理也依然是文本状态方便继续用 Excel 公式处理。如果你把结果打印到控制台for row in table: print(row)输出可能如下[产品名称, 销售数量, 单价, 销售额] [A产品, 120, 25.0, 3000.0] [B产品, 85, 40.0, 3400.0] [C产品, 60, 15.5, 930.0]可以看到单元格的内容全部是字符串类型。这里的“120”“25.0”在 Excel 中也是文本需要后续通过 Excel 设置或 Python 转换才能参与数值计算。5. 进阶实战多页 PDF 合并写入同一个 Excel5.1 场景分析很多真实 PDF 不止一页比如年报中的销售明细可能分布在 5 页里。如果只取第一页显然不完整。更合理的做法是遍历全部页面过滤掉无表格的页面然后把页面上出现的数据逐行写入同一个 Excel 工作表。这里有一个比较容易踩坑的地方extract_tables()对每一页都会重新识别表格带表格的页面可能识别出 0 个表格也可能识别出多个表格。我们在代码中必须先判断表格数量避免索引报错。5.2 多页合并代码# -*- coding: utf-8 -*- import pdfplumber from openpyxl import Workbook wb Workbook() ws wb.active ws.title 销售汇总 pdf_path sales_report.pdf with pdfplumber.open(pdf_path) as pdf: for page_index, page in enumerate(pdf.pages): tables page.extract_tables() if not tables: print(f第 {page_index 1} 页没有识别到表格跳过) continue for table in tables: for row in table: # 去掉空行 if any(cell is not None and str(cell).strip() for cell in row): ws.append(row) print(PDF 所有页面的表格已合并写入 Excel) wb.save(sales_report_all.xlsx)5.3 细节说明enumerate(pdf.pages)用于同时获取页码索引和页面对象方便在日志中定位“哪一页识别失败”。if not tables:是空列表判断。没有任何一个识别到的表格时直接跳过避免后续table索引错误。any(cell is not None and str(cell).strip() for cell in row)用来过滤完全为空的行。因为有些 PDF 表格中存在拆分单元格、跨页产生的空行如果不做过滤Excel 里会出现很多空行影响数据质量。如果 PDF 第一页是封面第二页是目录这些页面往往没有明显表格线extract_tables()就会返回空列表代码会打印提示信息并继续执行不会中断报错。6. 处理边界文本型表格和无边框表格6.1 什么是文本型表格“文本型表格”指的是 PDF 页面中虽然用文字排列成了表格的样子但并没有绘制出完整的表格线。常见于某些导出系统或老式报告。这种表格用默认的extract_tables()可能找不到边框于是返回[]。pdfplumber 提供了一些可以调整的策略参数。我们可以根据页面中的文字位置来推断表格边界而不是依赖表格线。custom_settings { vertical_strategy: text, horizontal_strategy: text, snap_tolerance: 3, } tables page.extract_tables(custom_settings)这里的snap_tolerance表示在判断相邻文字是否在同一条竖直线上时的容差单位是像素。数值过大容易把其他列的文字误判进来数值过小可能漏掉列需要多试几次。6.2 混合提取表格中的图片或二维码有些 PDF 表格里还嵌入了二维码、条形码或图片。pdfplumber 不负责图片内容识别但可以通过page.images获取页面上的图片对象信息。例如with pdfplumber.open(sales_report.pdf) as pdf: page pdf.pages[0] if page.images: print(页面中包含图片:, len(page.images)) for img in page.images: print(图片位置:, img[x0], img[top], img[x1], img[bottom])这里获取的是图片在页面上的坐标而不是图片内容。如果需要识别图片里的文字应该配合 OCR 工具处理与 pdfplumber 的文本提取没有直接关系。6.3 扫描件 PDF 应该怎么处理如果你的 PDF 是从纸质文件扫描而来整页内容本质上是一张大图pdfplumber 无法直接提取文字或表格因为这种 PDF 根本没有文本层。这种情况下需要先用 OCR 引擎如 Tesseract、PaddleOCR识别文字后再结合坐标信息重建表格结构。这部分流程比较复杂需要在项目里单独设计。如果只是偶尔使用建议先用专业 PDF 编辑器进行 OCR再交给 pdfplumber 处理会省下不少调试时间。7. 常见问题与排查思路7.1 安装失败或 import 报错问题现象常见原因解决思路pip install pdfplumber报网络错误默认源不稳定换清华、阿里等镜像源重新安装import pdfplumber找不到模块包安装在另一个 Python 环境检查当前 Python 环境使用python -m pip install pdfplumber安装后运行报错提示缺少依赖pdfplumber 依赖未正确安装执行pip install --upgrade pdfplumber或卸载重装在 PyCharm 中导入失败解释器选错将 PyCharm 项目解释器切换为安装包的 Python 版本7.2 extract_text() 返回 Noneextract_text()返回None通常说明该页没有可提取的文本内容。可能原因有两种页面是纯扫描图片。文字被某些特殊字体编码为自定义 Unicode但不是标准文本层。排查步骤with pdfplumber.open(sales_report.pdf) as pdf: page pdf.pages[0] # 检查页面中是否有字符对象 print(page.chars)如果page.chars是空列表说明 pdfplumber 没有在页面上发现任何字符对象大概率需要 OCR 处理。如果chars中有字符数据但extract_text()仍然返回空可以尝试提取单个字符的文本text .join([char[text] for char in page.chars]) print(text)这个办法只能作为临时调试手段因为它忽略了排版换行常用于确认字符是否真的存在。7.3 extract_tables() 识别结果为空或不全表格识别受页面线条清晰度、标题行、合并单元格等因素影响。出现漏识别时可以按顺序排查检查当前页面是否有完整线条边框截图放大观察是否断线或被文本框遮盖。调整vertical_strategy和horizontal_strategy尝试text模式。尝试把大表拆成小表区域通过page.crop()裁剪局部范围后再提取。# 裁剪页面上半部分只对该区域提取表格 bbox (0, 0, page.width, page.height / 2) cropped_page page.crop(bbox) tables cropped_page.extract_tables()page.crop()接收一个包含四个数值的元组分别表示裁剪区域的左、上、右、下坐标。坐标体系以页面左上角为原点单位为像素但 pdfplumber 内部会根据 PDF 实际尺寸换算。7.4 单元格内容包含换行符有时单元格内存在换行提取出来会变成[产品名称\n型号]这样的字符串。写 Excel 时换行符会保留在单元格中。如果不需要保留换行可以统一替换掉cleaned_row [] for cell in row: if cell is None: cleaned_row.append() else: cell str(cell).replace(\n, ) cleaned_row.append(cell) ws.append(cleaned_row)这里不能直接对cell使用strip()因为换行符可能是单元格文本的一部分去掉后信息也许就不完整了。建议先替换换行为空格再判断两端空格。7.5 中文字体显示乱码或输出为空PDF 的中文字体编码方案相对复杂。如果extract_text()能提取到中文但乱码说明字体映射有问题如果提取出来是空白则说明页面中的字符没有标准文本映射。解决方案使用最新版 pdfplumber旧版本对某些字体支持较差。使用 PDF 阅读器检查该 PDF 是否支持“选择文字”操作。如果字符确实不能复制只能使用 OCR。8. 读取 PDF 后写入 Excel 的工程化建议8.1 使用 openpyxl 还是 pandas在本文示例中我们直接用 openpyxl 写入 Excel。如果后续你需要对提取结果做筛选、统计、透视可以先用 pandas 的DataFrame接收表格数据再调用to_excel()。示例import pandas as pd import pdfplumber with pdfplumber.open(sales_report.pdf) as pdf: table pdf.pages[0].extract_tables()[0] df pd.DataFrame(table) # 第一行作为表头 df.columns df.iloc[0] df df.drop(index0).reset_index(dropTrue) # 将数字列转为数值类型 df[销售数量] pd.to_numeric(df[销售数量], errorscoerce) # 写入 Excel df.to_excel(output.xlsx, indexFalse)使用 pandas 的好处是对数据清洗更顺手比如把“销售数量”“单价”“销售额”批量转成数值列避免 Excel 里全是文本格式坏处是引入了额外的库在轻量脚本里显得有点重。两种方式都可以关键是看项目里是否还有其他数据加工需求。8.2 对提取结果做数据校验PDF 表格提取得再顺利也难免出现个别行错位。生产环境落地时一定要写数据校验逻辑而不是直接把表写入 Excel。建议检查以下内容每行单元格数量是否一致。若某个表格行是 4 列但提取出来的行有 3 列或 5 列说明表格线被识别错误。数字列是否能正确转换为 float。如果期望是销售数量却得到“1,200”或“—”需要提前替换或标记。是否存在大量空行处理掉之后剩余行数是否与 PDF 页面中的记录数接近。表头是否重复。多页 PDF 在合并时每页可能都带一个表头行写 Excel 前要决定是保留一次还是全部保留。8.3 日志与异常记录处理大批量 PDF 时建议在关键位置打日志。例如import logging logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(pdf_extract.log, encodingutf-8), logging.StreamHandler() ] ) logger logging.getLogger(__name__) logger.info(开始处理 sales_report.pdf)每个 PDF 文件处理完成或失败时记录文件路径、页码、识别到的表格数量方便后续排查。直接在控制台 print 也可以但写日志文件在批量任务中更有价值。8.4 使用配置文件管理 PDF 路径不要把所有文件路径写死在代码里。可以使用简单的配置文件或命令行参数来传递输入输出路径比如import sys if len(sys.argv) 3: print(用法: python pdf_to_excel.py 输入PDF路径 输出Excel路径) sys.exit(1) pdf_path sys.argv[1] excel_path sys.argv[2]这样脚本就不需要每次修改源码也更适合接到自动化任务中。8.5 注意 PDF 文件来源合法性如果需要从网络下载 PDF 资料或从用户邮件附件中获取文件请确保你有权处理这些文件遵守相关数据合规要求。对包含个人信息的 PDF建议在本地脚本中完成处理不要将文件内容上传到公共服务平台。9. 综合案例从 PDF 中定位指定关键词并写入 Excel除了整表抽取还有一种常见需求PDF 正文是一段段文字我们需要把含有关键词的行提取出来整理成表格。比如从一篇公司公告中提取所有包含“金额”或“合计”的行。使用extract_text()配合splitlines()可以快速实现# -*- coding: utf-8 -*- import pdfplumber from openpyxl import Workbook keywords [金额, 合计] pdf_path company_report.pdf excel_path report_match.xlsx wb Workbook() ws wb.active ws.title 命中行 ws.append([页数, 命中文本]) with pdfplumber.open(pdf_path) as pdf: for page_index, page in enumerate(pdf.pages): text page.extract_text() if not text: continue for line in text.splitlines(): if any(k in line for k in keywords): ws.append([page_index 1, line]) wb.save(excel_path) print(匹配完成结果已保存到, excel_path)这个案例适用的场景很广例如从银行回单中寻找“手续费合计”从合同中定位“合同金额”从招标文件中抽取“项目名称、中标金额”所在行。但需要注意按行匹配无法处理关键词被 PDF 自动换行拆开的情况。若一行被拆成两端可以在收集所有文本后去掉换行再匹配但这样会丢失原始行号信息实际项目中需要根据效果取舍。10. 性能优化与批量处理经验10.1 批量处理多个 PDF如果手头有几十个 PDF 文件可以结合pathlib遍历目录from pathlib import Path pdf_dir Path(./pdf_files) for pdf_file in pdf_dir.glob(*.pdf): print(正在处理:, pdf_file.name) # 这里复用上面的单文件逻辑建议先处理 1 个文件确认表格结构正常再放开循环否则会在第 3 个文件就出现异常来回修改很浪费时间。10.2 降低内存占用pdfplumber 在打开页面时会把页面上的线条、字符信息都读到内存中。针对超大 PDF建议按页处理处理完一页后及时释放引用避免一次性保存所有页面对象with pdfplumber.open(large_pdf) as pdf: for page_index in range(len(pdf.pages)): page pdf.pages[page_index] # 处理该页 ...由于pdf.pages是一个懒加载列表实际使用某页时 pdfplumber 才会详细解析该页。循环内处理完当前页后续页面不再引用即可。10.3 并行处理是否可行Python 多线程在处理 PDF 时受 GIL 限制未必能带来很好的加速效果。更推荐使用多进程例如concurrent.futures.ProcessPoolExecutor让每个进程处理不同的 PDF 文件。配置文件时也要注意路径和日志写入的并发问题。如果只是几十个文件串行处理耗时通常也是可以接受的没有必要为了性能引入复杂的并发调度。11. 总结与下一步实践建议这篇文章从环境安装开始依次梳理了 pdfplumber 加载 PDF、提取文本、提取表格的核心方法并通过多个实战案例把 PDF 表格数据写入 Excel。重点内容可以概括为以下几点文本提取用extract_text()表格提取用extract_tables()。表格提取结果是一个二维列表非常适合转换为 Excel 的行列结构。多页合并时需要处理空页、空行、重复表头等问题。无边框表格需要调整table_settings策略。扫描版 PDF 没有文本层pdfplumber 无法直接处理需要额外引入 OCR。下一步建议你找一个自己工作或学习中真实接触的 PDF 文件先尝试用extract_text()观察输出再看表格识别结果最后结合 openpyxl 写成可复用的脚本。可以从 10 页以内的小文件练手逐步扩展到批量处理场景。如果后续遇到更复杂的合并单元格、跨页表格、图片表格混合排版还可以研究 pdfplumber 的page.crop()裁剪机制以及Table对象提供的原始坐标信息通过二次加工实现更精细的数据抽取。希望这篇文章能帮你节省一些处理 PDF 的时间也希望你在实践中积累出更顺手的处理流程。如果想了解 pdfplumber 结合 pandas 做数据清洗、批量生成 Excel 报表的进阶写法后续可以再继续深入。
返回列表