ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python pdfplumber实战:从PDF中高效提取表格并写入Excel

2026/9/5 22:21:51 拓冰建站 浏览量
Python pdfplumber实战:从PDF中高效提取表格并写入Excel 很多人拿到一份带表格的 PDF第一反应往往是找在线转换网站。文件小、格式简单时确实很快可一旦页数多、表格跨页、单元格内容自动换行转换结果就变成一种很典型的“能看不能用”文字全出来了行列结构却对不上。做到第 42 天的 Python 练习如果还在靠手工复制粘贴去整理几十页表格效率明显不够用。pdfplumber 是我处理 PDF 文本和表格时最常用的起点它不把 PDF 当成“一张图”而是按字符、坐标、线条和表格边界去读取内容。这篇文章不是把 pdfplumber 的 API 念一遍而是按真实使用顺序拆开讲先搞清楚它为什么能解决这类问题再把环境、文本、表格、写 Excel、批处理和排查路径一条条跑通。我的核心判断是pdfplumber 的价值不在于让你少复制粘贴一次而在于把 PDF 提取从一次性的手工活变成可复用、可维护的数据流程。1. 为什么 PDF 数据不能靠“复制粘贴”处理1.1 PDF 里的表格是“画”出来的不是结构化数据先理解一个前提PDF 的版面并不是像 Excel 那样天然按行列存储的。你看到的一行表格在 PDF 内部往往不是“一张表”而是一堆字符、坐标、横向线条和纵向线条的组合。如果一个 PDF 是常规报表系统导出、字体也完整嵌入你在阅读器里确实可以选中文字。但把整段文字粘贴到 Excel 后问题会很常见同一行内容被拆成多行不同列的字符挤进同一格甚至带空格的数字被 Excel 自动当成日期。这不能全怪 Excel。PDF 本身只描述“这些字符应该放在页面的哪个位置”并没有告诉你“这属于第几行第几列”。在线转换工具能处理的大多是规则简单、有边框、无合并单元格的 PDF。一旦页面复杂它的识别结果就会开始“自由发挥”。pdfplumber 之所以有价值是因为它保留了 PDF 页面的对象级信息。它会把每个字符、每条线、每个矩形和每个表格候选区域找出来。相对于直接提取纯文本它更贴近页面布局本身相对于把 PDF 当成图片去 OCR它又能利用 PDF 内部已经存在的文本数据减少识别不确定性。这也是我第一次用 pdfplumber 时最大的认知转变处理 PDF 表格不是在“读文字”而是在“重建版式”。理解这一点后很多参数的意义就清楚了。1.2 pdfplumber 真正改变的是工作方式在线转换工具适合做一次性任务。你转一次文件拿到结果不关心过程也不需要重复执行。但只要你遇到过下面任何一种情况脚本方式的优势就会出来每周都要从同一份系统导出的 PDF 提取订单表。一个 PDF 有 50 页每页结构相同需要合并成一个 Excel。提取完之后还要做字段校验、类型转换、去重和写入数据库。你想保证下一次跑的结果和这次一致而不是依赖转换网站的心情。pdfplumber 能提供的是一条可固定的处理链路打开 PDF → 定位页码 → 提取文本或表格 → 清洗结果 → 输出 Excel。因为每一步都在脚本里所以可以加参数、加日志、加异常处理。今天跑通了明天换一批文件还能接着跑。当然它也有明确边界。pdfplumber 更适合处理“本身有文本层”的 PDF。如果文件是扫描件页面本身只是图片那它并不能直接做 OCR。这个问题我会在第 6 部分展开但你要先建立这个基本预期避免后面踩坑时误判工具能力。2. 别急着解析先把环境边界和最小流程跑通2.1 安装前先确认两件事很多人拿到教程第一件事是pip install pdfplumber但安装完后直接打开一份“扫描版 PDF”发现什么都提取不出来于是以为工具不行。建议安装前先做一个简单判断打开 PDF用阅读器试着选中一段文字。如果不能选中说明它可能没有文本层pdfplumber 的extract_text()大概率返回空或者异常碎片。如果确实需要处理这类文件后续要接 OCR 流程而不是只靠 pdfplumber。安装依赖不复杂但养成虚拟环境习惯会省很多事。用下面的命令创建一个临时虚拟环境并安装两个库python -m venv .venv # macOS / Linux source .venv/bin/activate # Windows PowerShell .venv\Scripts\Activate.ps1 # Windows CMD 则使用 .venv\Scripts\activate.bat pip install --upgrade pip pip install pdfplumber openpyxl这里同时安装openpyxl是因为我们稍后要把结果写入 Excel。如果你打算用 pandas 做后续分析也可以把 pandas 一并装好但从最小可运行流程来看pdfplumber openpyxl 已经足够。注意安装完成后先运行python -c import pdfplumber; print(pdfplumber.__version__)确认能正常导入。如果看到 ModuleNotFoundError第一步不是重装而是确认当前终端是否真的在虚拟环境里。2.2 最小代码打开 PDF 并读取第一页文本pdfplumber 的 API 设计相对友好。打开文件用pdfplumber.open()文件对象可以作为上下文管理器使用出作用域后会自动关闭避免占用文件句柄。下面是最小读取流程import pdfplumber pdf_path sample.pdf with pdfplumber.open(pdf_path) as pdf: print(页面数量:, len(pdf.pages)) first_page pdf.pages[0] text first_page.extract_text() if text: print(text[:500]) else: print(没有提取到文本请检查 PDF 是否为扫描版或字体是否嵌入。)这段代码涉及三个关键概念pdf.pages是页面列表第一页是pdf.pages[0]不是pdf.page。extract_text()返回的是该页文本可能带换行符也可能有空格缺失。页面不一定能提取到完整文本返回空不代表代码错需要先判断输入文件的类型。2.3 “能读出文字”不等于“能读准内容”第一次跑通后你会遇到一种情况代码确实输出了文字但文字顺序和你想象的不一样或者一段话被拆碎。原因通常是 PDF 内部字符顺序和阅读顺序不一致。有的 PDF 由排版软件生成字符存储顺序是“先画字再调坐标”而不是“按人眼阅读顺序排列”。pdfplumber 会尽力按位置排序但遇到多栏布局、复杂嵌套表格时仍会不稳定。我的建议是不要一开始就追求“所有 PDF 全自动正确”。先选定一份结构固定的文件在脚本里把页数、第一页前 200 个字符打印出来确认抽样结果符合预期后再继续批量逻辑。很多后续问题其实在这一步就能筛掉一大半要么是输入文件不对要么是页面结构比想象中复杂。3. 真正要处理的核心是表格结构不是文字抽取3.1extract_text()和extract_table()走的是两套思路如果你只需要 PDF 里的普通段落文字用extract_text()就能得到近似结果。但如果需要表格数据extract_text()往往不够用因为到了文本层面“第几行第几列”信息已经被摊平了。相比之下extract_table()或extract_tables()会尝试把页面里的文本按坐标对齐到行列网格里。它返回的结果通常是一个“列表的列表”也就是二维表结构。看一段基础用法import pdfplumber with pdfplumber.open(table.pdf) as pdf: page pdf.pages[0] # 如果页面只有一个表格可以直接用 extract_table() table page.extract_table() if table: for row in table: print(row) else: print(当前页面没有识别到表格)输出结果大致长这样[[产品编号, 产品名称, 数量], [A001, 螺丝, 200], [A002, 螺母, None]]其中None表示单元格为空。pdfplumber 不会自动填成空字符串这在你后续写 Excel 时要注意处理。3.2 先写一个清洗函数再处理表内容PDF 表格单元格的内容经常包含换行。尤其单元格宽度不够时一个词会被拆到两行然后extract_table()返回的字符串里可能带有\n。如果直接把这个带换行的字符串写入 Excel视觉上会很难看也影响后续筛选。所以我在处理表格前会先定义一个简单的清洗函数def clean_cell(cell): if cell is None: return # 去掉多余换行、前后空格并把连续空白合并成一个空格 return .join(str(cell).split())这个函数针对普通字段通常够用。比如螺丝\nA001会变成螺丝 A001。但要注意如果你的业务需要保留单元格内的真实换行比如地址、备注、多行明细那就不应该用这个函数把换行全部去掉。是否需要清洗要看下一页数据要进什么系统。如果进 Excel 只是做展示和二次加工那么把换行转成空格往往是更安全的选择。3.3 无边框表格调整表格识别策略很多表格不是用直线画出来的而是靠空格分隔列。pdfplumber 默认会先找页面里的横线和竖线用交点构成单元格。如果页面没有明显边框线条默认策略可能识别为空。遇到这种情况可以告诉 pdfplumber 改用文本对齐策略table_settings { vertical_strategy: text, horizontal_strategy: text, } with pdfplumber.open(no_border.pdf) as pdf: page pdf.pages[0] table page.extract_table(table_settingstable_settings) print(table)这里的思路是当表格没有线条时用字符的横纵坐标来判断哪些文字属于同一列、哪些文字属于同一行。这个策略不是万能的但对常见的“空格分列”表格有比较好的效果。另外还有两个参数可以一起试table_settings { vertical_strategy: text, horizontal_strategy: text, snap_tolerance: 3, join_tolerance: 3, }snap_tolerance控制容错距离join_tolerance控制线段合并容错。不同版本 PDF 效果不同如果你正在处理复杂无框表可以按 1、3、5 这样的小步长去调整不要一次拉太大。不要一看到extract_table()返回空就认为是工具问题。先用阅读器打开 PDF看这一页到底有没有表格线。如果它只是看起来像表格、实际靠空格排版优先尝试text策略。4. 把解析结果写入 Excel 时哪些操作容易被忽略4.1 最简单的“PDF 多页 → Excel 工作表”流程拿到了表格二维数组写入 Excel 反而是最直接的一步。用 openpyxl 创建一个工作簿循环页面的表格把每一行追加到工作表import pdfplumber from openpyxl import Workbook def clean_cell(cell): if cell is None: return return .join(str(cell).split()) pdf_path report.pdf xlsx_path report.xlsx wb Workbook() ws wb.active ws.title PDF提取结果 with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: table page.extract_table() if table is None: continue for row in table: ws.append([clean_cell(cell) for cell in row]) wb.save(xlsx_path) print(已写入:, xlsx_path)这段代码的优点是简单、容易读。缺点是不区分页面如果一个 PDF 里有多个不同结构的表所有内容都会被拼到同一个 sheet 里。所以写作的时候我先建议你用它跑通流程等确认数据没问题后再根据表结构决定是全部合并到一个 sheet还是每个页面单独一个 sheet。4.2 空值、数字格式化、表头重复写 Excel 前要想清楚写 Excel 之前有几个点看起来小但会影响后续使用第一空单元格最好在代码里统一处理。None会被 openpyxl 写成空单元格这本身没问题。但如果你在清洗函数里把它转成空字符串后续做len(cell)或字符串操作会更安全否则容易出现NoneType报错。第二数字和文本要区分清楚。pdfplumber 提取出来的数字通常是字符串比如1,200、00123。如果直接写进 ExcelExcel 可能把它当文本也可能当你手动点一下时自动转成数字。这里不建议贸然执行int()或float()因为业务编号、订单号、快递单号这类字段一旦转成数字前面的零就会丢失。第三重复表头要单独处理。很多打印版 PDF 每一页顶部都有同样的表头。如果直接把所有页的表格逐行追加最终 Excel 里会出现几十个重复的表头行。要么提取前用页码判断跳过要么写完后做一次去重。最简单的方式是只保留第一页表头后续页从第二行数据开始追加。4.3 先写数据后调外观不要本末倒置刚开始接触 openpyxl 的人容易一上来就设置列宽、字体、背景色、边框。这在少量数据上没问题但批量处理时单元格样式会显著拖慢写入速度而且会掩盖真正的数据问题。我更推荐的顺序是先用最小代码把数据原样写入 Excel。打开 Excel随机抽几行和 PDF 原文对比。确认列顺序、空值、表头、跨页都正常后再考虑样式。如果你每天都跑可以抽象出一个“写数据”函数和一个“写样式”函数分开调用。样式的价值是让最终结果更好看但一个数据错位的 Excel即使样式再精美也不能用。5. 批量处理从单页文件改造成可复用脚本的关键节点5.1 三级放大先单页再单文件再多文件解析 PDF 最忌一上来就写“扫描整个目录”的代码。批量任务出错时错误会被埋在很多文件里排查成本会成倍增加。我习惯把流程分成三级第一级只解析一个 PDF 的第一页打印结果。第二级解析同一个 PDF 所有页确认跨页表头、多表结构。第三级用Path.glob()循环处理整个目录下的多个 PDF。第三级的代码示例from pathlib import Path import pdfplumber from openpyxl import Workbook def clean_cell(cell): if cell is None: return