Python PDF转Excel全攻略:pdfplumber与openpyxl实战详解

Python PDF转Excel全攻略:pdfplumber与openpyxl实战详解 办公场景里PDF 表格和文本批量转 Excel 的需求一直不少。月初对账、年报汇总、财务报表归档、产品参数整理都会遇到“数据明明清晰可见却没法制成表格”的尴尬。手动录入效率低复制粘贴又容易把排版弄乱。很多学习 Python 的同学在这一步会卡住到底该用什么库为什么extract_text()拿不到内容怎么把读取到的结果稳定写入 Excel这一篇就来完整梳理pdfplumber的基础用法并用带表格、带文本的 PDF 文件做两个实战案例最终把数据写入 Excel。文章按“入门概念 → 环境准备 → API 拆解 → 实战案例 → 常见问题 → 工程建议”的顺序展开既有可复制的代码也有能直接对照排查的避坑清单。如果你曾用正则表达式硬抠 PDF 文本或者手动复制过多张 PDF 表格这篇文章很值得收藏。1. pdfplumber 能解决什么问题1.1 为什么不是所有 PDF 都能直接读取很多初学者刚接触 PDF 时会有一个疑问PDF 里的文字明明能被鼠标选中为什么用代码读取这么费劲这和 PDF 的文件结构有关。PDF 更像一份“打印排版稿”文件内部保存的是文字、图形的坐标位置信息而不是像 Word、Markdown 那样天然带有段落层级。页码、页眉、页脚、表格线、文字框都是基于坐标绘制的。pdfplumber做的事就是把这种“基于坐标的绘制内容”重新抽取为 Python 可以处理的文本、表格、图片对象。如果你直接把 PDF 文件重命名成.txt再读取看到的内容往往是乱码原因就在这里。1.2 pdfplumber 能提取哪些内容pdfplumber是 Python 生态中比较成熟的开源 PDF 解析库底层依赖pdfminer.six。它既可以提取文本也可以识别表格线、图片、矩形还能拿到每个字符在页面上的精确坐标。常用能力包括功能说明常用方法提取文本获取整页或指定区域的纯文本page.extract_text()提取表格根据页面表格线重组出行列数据page.extract_table()提取单词获取单词文本及坐标位置page.extract_words()提取图片获取页面中的图片对象page.images裁剪页面按矩形区域局部解析page.crop()对比PyPDF2这类偏“PDF 文件操作”的库pdfplumber更专注于“内容解析”所以它在读取表格、带上坐标信息时优势明显。1.3 它不擅长处理什么要注意pdfplumber不是万能的。它读取的是 PDF 内部的文字和矢量线条如果 PDF 本身是扫描件也就是“每页都是一张图片”的那种它拿不到可编辑文本extract_text()可能返回空内容或者乱码。这种情况需要先接入 OCR 识别组件例如Tesseract属于另一套技术路线。另外pdfplumber也不是 PDF 编辑器。它不会自动矫正乱排版更不会把一张图片里的表格变成 Excel 表格。2. 环境准备与安装2.1 安装 pdfplumber 和 openpyxl本文涉及两个 Python 第三方库pdfplumber负责 PDF 内容解析。openpyxl负责将数据写入.xlsx文件。如果你电脑上还没有安装 Python建议先安装 Python 3 版本。安装完成后打开命令行工具执行pip install pdfplumber openpyxl如果机器上同时存在多个 Python 版本可能需要用pip3代替pippip3 install pdfplumber openpyxl安装完成后可以运行下面的代码验证import pdfplumber import openpyxl print(pdfplumber 版本, pdfplumber.__version__) print(openpyxl 版本, openpyxl.__version__)如果能看到版本号说明环境已经就绪。我建议你顺手新建一个项目目录例如pdf_to_excel_demo后续代码都放在这个目录里。示例结构如下pdf_to_excel_demo/ ├── input/ # 存放测试 PDF ├── output/ # 存放生成的 Excel └── demo.py # Python 脚本2.2 准备一份测试 PDF代码实战前需要准备一份测试文件推荐使用带清晰表格线的 PDF例如月度销售汇总表、成绩单、库存清单。本文演示中使用的文件名为月度报表.pdf放在input目录下。后面写代码时你可以把它替换成自己的真实文件路径。假设月度报表.pdf第一页内容大致如下商品名称数量单价金额苹果105.0050.00香蕉53.5017.50橙子86.0048.00这里的数据并不来自任何真实企业只是为了演示代码输出。3. pdfplumber 核心概念与用法拆解3.1 打开 PDF用with管理文件在pdfplumber中推荐使用上下文管理器打开 PDF 文件import pdfplumber # 文件路径改成自己的 PDF 路径 pdf_path input/月度报表.pdf with pdfplumber.open(pdf_path) as pdf: print(PDF 总页数, len(pdf.pages)) first_page pdf.pages[0] text first_page.extract_text() print(text)运行后第一页的文字会按阅读顺序输出。通过pdf.pages可以拿到页面列表pdf.pages[0]表示第一页。页面编号和 PDF 一样从 0 开始所以第一页对应索引 0。这里使用with好处很明显解析完成后文件会自动关闭避免句柄占用尤其在批量处理大量 PDF 时很关键。3.2extract_text()提取文本extract_text()是最常用的方法。它会把页面中的文字按顺序组合成字符串并尽量保留换行。示例import pdfplumber with pdfplumber.open(input/月度报表.pdf) as pdf: page pdf.pages[0] text page.extract_text() # 输出前 500 个字符避免内容过长 print(text[:500])这里有两点需要说明。第一extract_text()对中英文混排的文本支持度尚可但 PDF 内部没有“段落”概念所以读到什么换行位置、文字顺序取决于 PDF 内容本身的绘制顺序。某些 PDF 分栏排版、文本框悬浮文本顺序可能和视觉顺序不完全一致。第二如果 PDF 页面是空白扫描图片这个方法返回的值可能是None或者空字符串。3.3extract_table()提取表格表格提取是pdfplumber的拿手好戏。它会根据页面中的垂直线和水平线把表格区域切分成行列结构并返回一个二维列表。import pdfplumber with pdfplumber.open(input/月度报表.pdf) as pdf: page pdf.pages[0] table page.extract_table() # 输出表格 for row in table: print(row)输出结果类似[商品名称, 数量, 单价, 金额] [苹果, 10, 5.00, 50.00] [香蕉, 5, 3.50, 17.50] [橙子, 8, 6.00, 48.00]table是一个列表列表中的每个元素又是列表对应表格中的一行。这种结构非常接近 Python 中的二维数组也很方便后续写入 Excel。如果表格跨页extract_table()一次只提取当前页面内的表格。跨页表格需要逐页提取后再合并后面会专门演示。3.4extract_words()提取单词与坐标有些场景下你不只需要文字还需要文字的位置信息。例如只需要某一列、判断某个单元格属于表头还是正文都可以用到extract_words()。import pdfplumber with pdfplumber.open(input/月度报表.pdf) as pdf: page pdf.pages[0] words page.extract_words() for word in words[:5]: print(word)每个单词对象的字段通常包括字段含义text单词文本x0单词左边界距离top单词顶部距离x1单词右边界距离bottom单词底部距离page_number所在页码这些坐标通常以点为单位。页面左上角是坐标原点向右是 x 轴正方向向下是 y 轴正方向。如果以后要实现“只提取某个区域内的文本”这些坐标会非常有用。3.5chars字符对象pdfplumber在页面中提供了更细粒度的page.chars每个字符都可以提取出来包括字体名、字号、颜色等信息。import pdfplumber with pdfplumber.open(input/月度报表.pdf) as pdf: page pdf.pages[0] first_char page.chars[0] print(first_char)当你想判断某个标题字体是否加粗、字号是否大于正文可以通过chars实现。在很多“批量整理 PDF 目录”的任务中先根据字体大小定位标题再提取标题下正文就是比较常见的做法。3.6 常见误区表格线和文字的关系使用extract_table()时很多新手会误以为只要 PDF 长得像表格就一定能提取成功。实际上pdfplumber主要依赖“可见的表格线”来切分表格。如果 PDF 中的表格没有边框线或者表格线是扫描图片里的图像而不是矢量线提取效果就不理想。此时可尝试调整table_settings例如设置vertical_strategytext让程序尝试通过文字 x 坐标对齐来推测表格列边界table_settings { vertical_strategy: text, horizontal_strategy: text } table page.extract_table(table_settings)如果仍然失败通常只能考虑先对扫描图片做 OCR。4. 实战一读取 PDF 文本并写入 Excel4.1 需求说明假设有一份纯文本型 PDF每行是一条报告记录内容如下工号 部门 姓名 出勤天数 缺勤次数 1001 销售部 张三 22 0 1002 市场部 李四 21 1 1003 技术部 王五 23 0需求是读取 PDF 全部页面的文本拆分成行后写入 Excel。这种场景常见于把“文字报告”转成结构化工作簿方便二次筛选。4.2 完整示例代码创建一个新文件text_to_excel.py# -*- coding: utf-8 -*- import pdfplumber from openpyxl import Workbook pdf_path input/考勤报告.pdf excel_path output/考勤报告.xlsx wb Workbook() ws wb.active ws.title 考勤记录 with pdfplumber.open(pdf_path) as pdf: target_page pdf.pages[0] text target_page.extract_text() # 按换行拆分成列表 lines text.splitlines() # 逐行写入 Excel for row_idx, line in enumerate(lines, start1): # 如果原始内容用空格分隔先拆分 columns line.split( ) # 去掉空字符串避免因为多个空格产生空列 columns [col for col in columns if col ! ] for col_idx, value in enumerate(columns, start1): ws.cell(rowrow_idx, columncol_idx, valuevalue) wb.save(excel_path) print(已保存, excel_path)4.3 代码解释先通过text.splitlines()把整页文本切成行但 PDF 里的空格不一定是单个字符有可能会出现连续多个空格或全角空格因此拆列前先按空格拆分再过滤空字符串能有效减少“空列错位”现象。不过这种处理方式仍然依赖 PDF 本身的行列格式。如果实际内容中的字段是用 Tab 分隔的可以改用line.split(\t)如果字段间距是通过多个空格实现的过滤空格后效果会更好。真正规范的数据报告建议先输出拆分结果检查一遍。4.4 运行验证命令行执行python text_to_excel.py如果输出目录下出现考勤报告.xlsx打开后表格内容与原 PDF 对应说明第一个实战完成了。5. 实战二提取 PDF 表格并写入 Excel5.1 需求说明这是 PDF 转 Excel 的高频场景。业务部门发来一份带边框线的 PDF 表格需要把它中的数据导入 Excel 进行透视分析或数据清洗。这里用input/月度报表.pdf作为输入文件内容是最常见的三行商品数据。5.2 技术方案步骤拆解如下用pdfplumber读取 PDF 页面。调用extract_table()提取二维表格数据。遍历二维列表并写入 openpyxl 工作表。保存为.xlsx文件。5.3 表格提取并写入 Excel新建table_to_excel.py# -*- coding: utf-8 -*- import pdfplumber from openpyxl import Workbook from openpyxl.styles import Font, Alignment pdf_path input/月度报表.pdf excel_path output/月度报表.xlsx # 创建 Excel 工作簿 wb Workbook() ws wb.active ws.title 月度报表 with pdfplumber.open(pdf_path) as pdf: # 这里先处理第一页多页场景见下一节 page pdf.pages[0] # 提取表格返回二维列表 table page.extract_table() if table: # 第 1 行通常是表头 for row_idx, row in enumerate(table, start1): for col_idx, cell_value in enumerate(row, start1): ws.cell(rowrow_idx, columncol_idx, valuecell_value) else: print(当前页面未提取到表格请检查 PDF 是否存在清晰表格线) # 简单美化表头 for col_cell in ws[1]: col_cell.font Font(boldTrue) col_cell.alignment Alignment(horizontalcenter, verticalcenter) # 保存文件 wb.save(excel_path) print(表格写入完成, excel_path)5.4 预期结果运行后Excel 内容如下商品名称数量单价金额苹果105.0050.00香蕉53.5017.50橙子86.0048.00代码中ws[1]表示第一行也就是表头所在行。这里将字体加粗表头居中显示方便识别。有一点要注意extract_table()返回的单元格中某些cell_value可能是None通常是因为该单元格没有文字内容或合并单元格导致。openpyxl 写入None时会生成空单元格不会导致程序崩溃因此可以先写入后续再统一清洗。5.5 单元格数字被 Excel 认为是文本怎么办pdfplumber提取出的单元格内容默认全部是字符串。比如10在 Python 中是字符串10写入 Excel 后显示为文本。如果后续需要做数值求和可能需要在 Excel 里手动转格式。如果希望写入时自动转换成数字可以在代码里做一个安全转换函数def to_number_if_possible(value): if value is None: return value text_value str(value).strip() # 去掉千分位符号 text_value text_value.replace(,, ) try: # 试用整数转换 return int(text_value) except ValueError: pass try: # 再试用浮点数转换 return float(text_value) except ValueError: return text_value在写入单元格前调用这个函数ws.cell(rowrow_idx, columncol_idx, valueto_number_if_possible(cell_value))这样可以尽量避免“看起来像数字实际却无法求和”的问题。6. 进阶多页 PDF 转换多个 Sheet实际业务中一份 PDF 往往不止一页每页都有一张结构相同的表格。手工一页页复制效率很低。此时可以让 Python 逐页提取表格每个页面写入 Excel 的一个 Sheet也可以用 Sheet 名区分页码。6.1 完整代码多页转多 Sheet新建multi_page_to_excel.py# -*- coding: utf-8 -*- import pdfplumber from openpyxl import Workbook from openpyxl.utils import get_column_letter pdf_path input/多页库存报表.pdf excel_path output/多页库存报表.xlsx # 创建新的工作簿 wb Workbook() # 删除默认的空白 Sheet稍后按页新建 wb.remove(wb.active) with pdfplumber.open(pdf_path) as pdf: for page_idx, page in enumerate(pdf.pages, start1): # 提取当前页表格 table page.extract_table() # 如果页面没有表格跳过该页 if not table: continue # 以页码作为 Sheet 名称注意 Excel Sheet 名不能重复 sheet_name f第{page_idx}页 ws wb.create_sheet(titlesheet_name) # 写入表格数据 for row_idx, row in enumerate(table, start1): for col_idx, cell_value in enumerate(row, start1): ws.cell(rowrow_idx, columncol_idx, valuecell_value) # 简单自适应列宽 if table and table[0]: for col_idx in range(1, len(table[0]) 1): column_letter get_column_letter(col_idx) ws.column_dimensions[column_letter].width 16 wb.save(excel_path) print(多页转换完成, excel_path)6.2 代码中的关键点第一个关键点是wb.remove(wb.active)。Workbook()默认会生成一个名为Sheet的空的表如果不删除它后面又多创建了 SheetExcel 文件里就会出现一个无用空白表。当然你也可以选择把第一个页面写到默认 Sheet 里这是一个习惯问题。第二个关键点是get_column_letter(col_idx)。它可以把数字列号转成 Excel 字母列号例如1变成A2变成B。手动设置列宽时会用到。第三个关键点是if not table: continue它能自动跳过没有表格的页面。如果 PDF 中有封面页、目录页后面没有表格的页面不会生成空 Sheet。6.3 跨页表格合并问题如果一张表格跨越多页像“表头在第一页第二页继续显示数据”上述写法会把它保存为两个 Sheet。如果需要合并成一张连续表做法是遍历所有页面使用extract_table()。第一页的返回值直接作为表格开头。后续页的返回值去掉表头后追加到前一个列表。思路可以用伪代码表达为all_rows [] for page in pdf.pages: table page.extract_table() if not table: continue if all_rows: # 去掉后续页的重复表头 table table[1:] all_rows.extend(table)随后再一次性写入 Excel就能得到一个跨页合并后的完整表格。7. 常见问题与排查思路PDF 解析过程中问题很多下面把最高频的错误场景整理成表格方便查阅。问题现象常见原因解决思路extract_text()返回空或NonePDF 是扫描图片无法直接提取文本先做 OCR或确认源 PDF 是否有文字层中文提取成乱码PDF 字体编码特殊或缺少字体资源尝试extract_text()后另存为 txt 查看部分场景需自行做字符映射表格提取结果为None页面没有可见表格线调整table_settings改用 text 策略表格提取后列错位有合并单元格、斜线表头、跨行单元格人工清洗后处理根据实际情况设计规则单元格数据是小数却显示成一长串浮点数格式化问题使用round()或字符串格式控制写入 Excel 时提示PermissionErrorExcel 文件正在打开或目标目录无权限关闭 Excel 文件后重试或换输出路径提取到的数字无法求和全部被当作字符串写入转换数值后写入或 Excel 中批量转数值提取 PDF 时速度很慢页面数量大、页面包含复杂图形按页切片处理减少单次解析范围页码越界pdf.pages[1]但 PDF 只有 1 页先判断len(pdf.pages)再访问报错KeyError因 PDF 内部结构异常导致对象缺失升级库或先尝试解析失败页复杂文件可用Uncertain容忍模式7.1 典型报错补充报错页面被跳过批量处理大量 PDF 时最忌讳“一份文件报错整个程序中断”。可以在循环里加入try / except把解析失败的页面或文件记录下来继续处理后面的内容。import pdfplumber pdf_path input/问题文件.pdf try: with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: text page.extract_text() # 这里继续处理业务逻辑 except Exception as e: print(f解析文件失败{pdf_path}) print(错误信息, e)被except捕获后程序不会因一个页面失败而停止。不过要注意异常捕获范围不宜过大否则会把“代码逻辑 bug”也一并掩盖掉。对真实项目来说失败文件需要写入日志方便后续人工排查。8. 最佳实践与工程建议当你把pdfplumber真正用到生产环境或日常自动化脚本时下面这些经验非常值得注意。8.1 使用上下文管理器避免文件句柄泄漏操作 PDF 时建议始终使用with pdfplumber.open(pdf_path) as pdf:。如果不使用上下文管理器退出时没有主动调用pdf.close()在 Windows 下很容易造成文件占用导致后续删除、移动文件失败。8.2 优先按页解析不要一次性读全部内容如果 PDF 有几百页一次性把所有页面文本读入内存速度慢且占用高。建议只解析需要的页面# 只解析第 3 页到第 5 页 for page_idx in range(2, 5): page pdf.pages[page_idx] text page.extract_text() # 处理 text范围下标的具体写法需要根据业务需求调整。遇到非常大的 PDF可以考虑把整份文件拆分成多个任务处理。8.3 批量处理时保留失败日志批量目录转换时一个健壮的程序应当输出“成功的文件列表”和“失败的文件列表”。不要在控制台只打印一堆异常时间久了根本找不到问题源头。建议记录文件名、页码、异常信息。8.4 备份原始 Excel避免覆盖如果脚本每天定时跑而且输出 Excel 会被其他人打开千万不要直接覆盖原文件。更稳妥的做法是生成新文件文件名加入日期后缀from datetime import datetime today datetime.now().strftime(%Y%m%d) excel_path foutput/月度报表_{today}.xlsx这样可以保留历史文件避免数据覆盖后无法找回。8.5 正则表达式后处理必不可少pdfplumber提取出的文本很难百分之百干净尤其遇到“商品名称苹果”这种带标签的数据正则表达式可以帮你统一清洗。例如提取金额字段import re text 订单金额1,234.56元 match re.search(r金额[:]?\s*([\d,]\.?\d*), text) if match: number_str match.group(1).replace(,, ) amount float(number_str) print(amount)需要注意正则表达式用于 PDF 结构化提取时要先看清 PDF 内部文本规律避免误提取其它相似字段。8.6 路径中的中文与特殊字符在 Windows 环境中如果路径包含中文只要 Python 源码文件保存为 UTF-8 编码通常没问题。但要注意不要让脚本依赖于 cmd 的 GBK 编码打印中文。如果控制台出现编码报错可以临时设置环境变量或在代码开头声明编码。8.7 对 PDF 质量做预检批量处理之前可以先对 PDF 做一个“健康检查”总页数是否合理、某个页面文字是否为空、表格是否存在。把预检结果输出成一个汇总文件能极大提升自动化任务成功率。9. 总结与下一步学习建议通过这篇教程你应该已经掌握了pdfplumber的几个核心动作用extract_text()读文本用extract_table()提取表格用extract_words()获取坐标信息再结合openpyxl把二维数据写成 Excel 文件。如果你只需要处理少量带表格的 PDF直接使用上面的extract_table()openpyxl方案基本能覆盖大多数日常工作。但真实项目往往没有这么理想PDF 里可能出现合并单元格、只读加密、扫描图片、复杂表头等情况。这时候我建议按“检查是否有文字层 → 逐页尝试 → 清洗数据 → 人工兜底复核”的顺序逐步推进不要指望一个函数解决所有文件。下一步可以尝试这些方向把上面几段代码封装成一个pdf_table_to_excel(pdf_path, excel_path)函数方便复用。学习用page.crop()裁剪页面特定区域只提取左上角或右下角的数据。研究chars中的字体大小实现自动识别标题和正文。对扫描版 PDF 接入 OCR 工具把图片中的文字识别出来后再做表格清洗。技术可以解决重复劳动但 PDF 格式本身并没有统一标准。脚本处理完的结果建议在关键场景下增加一轮人工校验。如果这篇文章解决了你的一两个实际问题或者让你对pdfplumber的边界更清楚别忘了收藏备用。后续遇到批量 PDF 处理需求按本文的目录顺序翻一翻就能快速上手。