ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python PDF办公自动化指南:文本提取、页面合并拆分与表格导出

Python PDF办公自动化指南:文本提取、页面合并拆分与表格导出 在日常办公自动化开发中PDF 是一个绕不开的“硬骨头”。很多人习惯把 Word、Excel 当作文本源数据处理但只要换成 PDF问题就立刻变多有的 PDF 能复制出文字有的 PDF 复制出来全是乱码还有的 PDF 里面明明有表格但用最直接的解析方式却怎么都拿不到。随着企业内部合同、凭证、技术文档、报表越来越多地以 PDF 形式流转掌握用 Python 处理 PDF 的基本思路已经成为办公自动化脚本开发里非常实用的一项能力。这篇内容是 py100 办公自动化系列中 PDF 了解篇的第二篇目标是帮大家把 PDF 处理的技术边界梳理清楚先弄明白 PDF 和普通文本格式到底差在哪里再熟悉常用处理库的选型逻辑最后通过“提取关键词页面、批量拆分合并、提取表格写入 Excel”三组实战把办公场景里最高频的几个需求完整跑通。适合刚入门办公自动化、又想系统学习 PDF 处理的开发者如果你的项目已经比较成熟也可以重点看后文的常见问题和工程经验部分用来排查脚本上线后的边界情况。1. PDF 办公自动化的真正难点从文件格式说起1.1 办公场景里的真实痛点回想一下日常办公里的典型场景财务报销需要核对大量发票 PDF行政归档需要把多份通知合并成一本文件技术部门需要从产品手册中提取带关键参数的页面HR 手里则有成百上千份简历 PDF。这些场景的共同特点是人工作业太慢、容易漏看、操作重复度高。很多初学者接触 Python 办公自动化时都希望几行代码就能把 PDF 里的文字、表格一次性原样拿出来但实际动手后会发现四个关卡第一个关卡是“文本到底能不能读出来”同样是 PDF电脑生成的 PDF 与扫描仪生成的 PDF 处理方式完全不同。第二个关卡是“文字读出来了顺序对不对”PDF 的文字经常存在少量乱序、换行不合理、多余空格的情况。第三个关卡是“表格明明可见代码却提取不到”这往往和表格是否有完整边框、页面是文字还是图片有很大关系。第四个关卡是“合并拆分、加密解密、加水印”这类需要保持版式稳定的操作不能简单按文本追加处理。这些关卡并不是代码库不够好而是 PDF 本身的格式特性决定的。所以我们先从格式层面说清楚为什么 PDF 能成为办公文件的通用标准又为什么它会比 HTML、Word 更“难以直接解析”。1.2 PDF 与 Word 等文档最本质的区别Word、Markdown、HTML 这类格式属于“流式文档”内容会随着页面大小自动重排。作者关心的是“一个标题下面有一段文字”至于这段文字具体显示在哪一页往往由渲染工具决定。读取这类文件时程序拿到的是一个相对清晰的内容结构。PDF 属于“固定版式文档”它更像是把一页纸上的每个视觉元素都“钉死”在坐标上。读者在屏幕上看到的内容就是打印机实际输出的内容不会因为换了一台电脑就重排。PDF 内部保存的是一系列图形对象、文字对象和资源信息即使某段文字从语义上属于同一个段落也可能因为排版需要被拆成多个小块散布在同一页的不同位置。所以 PDF 本质上是在模拟一张“电子纸张”。你可以把它理解成“把一张完成的纸质打印稿搬到屏幕上”而不是一份保留了标题、正文、表格逻辑的源文件。也因为这个原因Python 解析 PDF 时很少能像openpyxl读取 Excel 单元格那样直接按“行”和“列”读取内容大多数解析库都需要根据坐标、字号、间距来重新推断文档结构。很多朋友会问那 PDF 里到底能不能复制文字如果这份文件打开后可以选中并复制说明它至少包含一层文字对象如果整页像图片一样不可选中说明文件底层主要是位图或矢量图形必须先经过 OCR光学字符识别才能变成可搜索文本。这个判断在后面的实战中会反复用到建议先记住。1.3 办公自动化中处理 PDF 的常见需求清单从办公脚本的实际使用频率来看PDF 处理需求大致可以分为三类需求类别常见场景Python 实现难度提取类读取文字、提取表格、提取图片、读取元数据文字和元数据简单图片和复杂表格中等操作类合并 PDF、拆分 PDF、调整页序、加密解密简单接口成熟风险较低转换类PDF 转 Word、PDF 转 Excel、网页打印成 PDF版式还原难度高通常依赖专用工具库本次文章聚焦“提取类”和“操作类”中的高频入门需求。对于转换类我们会在实战三中演示“PDF 表格转为 Excel”的场景因为表格数据进入 Excel 后才方便继续做统计、透视或汇总。至于“PDF 转 Word 后是否保真”这类问题涉及版式还原工程上需要根据实际文件质量评估不能盲目相信自动转换结果。针对上述需求下面我们先准备好 Python 环境和常用库。这里我不会把 PDF 处理写得黑科技化每天工作中的真实要求其实很简单库要稳定、代码要能读懂、出问题时要快速定位到具体文件。2. 准备工作Python 环境与 PDF 库选型2.1 环境检查与安装建议本文示例使用 Python 3.9 之后的常见版本操作系统不限Windows、macOS、Linux 都可以运行。建议为每个办公自动化项目单独创建虚拟环境避免不同项目之间互相污染依赖。命令如下python --version pip --version如果电脑上同时存在 Python 2 或 Anaconda 等多个版本建议先使用where python或which python确认当前命令指向哪个解释器。创建并激活虚拟环境python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate如果公司网络对 PyPI 下载有限制可以配置为内部镜像源或者带--timeout参数重试。本文需要安装的第三方库如下pip install pdfplumber pypdf openpyxl安装完成后可以在 Python 交互式环境中验证导入是否正常import pdfplumber import pypdf import openpyxl print(pdfplumber, pdfplumber.__version__) print(pypdf, pypdf.__version__) print(openpyxl, openpyxl.__version__)这里不指定精确版本号因为办公项目通常依赖公司内部的统一版本策略读者按自己项目锁定版本即可。如果之前习惯使用 PyPDF2也可以迁移到 pypdf两者 API 比较接近pypdf 的维护更新更活跃新项目建议直接考虑 pypdf。2.2 常用 PDF 处理库的功能比较把几个主要库放在一张表里会更清晰库名主要用途适合场景注意点pdfplumber读取文本、提取表格、分析字符坐标文字型 PDF 的精细解析对扫描件无效复杂页面需要调试pypdf页数、元数据、合并拆分、加密解密、页面的旋转裁剪对 PDF 页面做结构操作文本提取能力简单不适合复杂版面PyMuPDF高性能解析、提取图片、渲染页面为图片、添加水印需要把 PDF 可视化或做批量渲染包的体积较大需要额外了解 APIpdf2docxPDF 转 Word把有清晰结构的内容转成可编辑的 Word转出结果不一定完美复杂版式需要人工复核reportlab从零生成 PDF 文件把报表、合同等动态内容写入新 PDF生成排版比读取更复杂建议按需学习如果是第一次接触 PDF 处理建议先专注掌握两个库pdfplumber负责“读懂内容”pypdf负责“整理页面”。等遇到“读取图片”或者“把 PDF 页面转成图片做 OCR”的需求时再引入 PyMuPDF 即可不需要一开始把所有库都学完。2.3 演示用的目录结构为了便于后续实战演示建议创建如下目录结构。路径名称可以根据实际情况调整保持统一即可pdf_office/ ├── pdfs/ # 存放原始 PDF 文件 ├── output/ # 存放脚本输出结果 ├── search_pdf_pages.py # 实战一脚本 ├── merge_pdfs.py # 实战二合并脚本 ├── split_pdf.py # 实战二拆分脚本 └── table_export.py # 实战三脚本在下面的代码里我会统一用Path来拼接路径避免 Windows 和 Linux 上的斜杠问题。3. 了解 PDF 读取的基本逻辑提取文字与元数据3.1 第一个最小可运行示例先用pdfplumber读取一个简单 PDF 的全部文本。假设当前目录下存在一份sample.pdf可以从任意一个文本型 PDF 中截取一页做测试import pdfplumber pdf_path pdfs/sample.pdf with pdfplumber.open(pdf_path) as pdf: print(页数:, len(pdf.pages)) first_page pdf.pages[0] text first_page.extract_text() print(第一页文本预览:) print(text[:500])这段代码会打开pdfs/sample.pdf读取第一页并打印前 500 个字符。需要注意extract_text()在没有任何可提取文本时会返回None所以正式代码里建议写成text first_page.extract_text() or 原因很简单扫描后的 PDF 没有文字层解析结果为空是很常见的情况。如果直接当成字符串处理None可能引发后续的AttributeError。pdfplumber在提取文本时会分析页面上的字符坐标尽量按阅读顺序组装。但 PDF 文件在制作时可能把页眉、页脚、页码、正文分成不同区域所以输出的顺序不一定完全等于人的阅读顺序。举个例子如果页面上有左右两栏文字解析结果可能会先把左栏读完再读右栏也可能穿插读取取决于 PDF 内部对象顺序。因此输出后的文本需要做二次清洗例如去掉多余空行、统一空格、过滤无意义字符。3.2 读取页数与文档元数据办公自动化经常需要确认 PDF 是否完整页数是否对得上、类型是否准确、文件里有没有残留的作者或公司信息。使用pypdf可以方便地读取这些信息from pypdf import PdfReader pdf_path pdfs/sample.pdf reader PdfReader(pdf_path) print(页数:, len(reader.pages)) print(文档信息:, reader.metadata)如果reader.metadata返回的不是None通常会包含标题、作者、创建时间等信息。有些公司发送重要文件时会通过代码自动清洗掉这些元数据。这里我们不展开写写入元数据的操作但在办公场景里“发送 PDF 前先检查元数据是否泄露隐私”是一个值得养成的习惯。pypdf同样可以判断页面是否加密reader PdfReader(pdfs/encrypted.pdf) print(文件已加密:, reader.is_encrypted) if reader.is_encrypted: # 仅限处理自己有权限的文件 reader.decrypt(your_password)对于已加密的 PDF直接读取页面可能抛出异常。合法的做法是先尝试解密如果忘记密码只能通过业务方或文件创建者找回。千万不要把“破解他人 PDF 密码”作为自动化目标这既违反授权边界也可能在法律上带来风险。办公自动化脚本处理的一定是自己有权限处理的内容。3.3 理解“文本型 PDF”和“扫描型 PDF”的差异有一类 PDF 是直接用编辑软件导出的文字可以选择、可以搜索我们称为“文本型 PDF”另一类是把纸质文件通过扫描仪或手机拍照生成的页面本质上是图片我们称为“扫描型 PDF”。后者如果需要提取文字必须首先经过 OCR 识别直观的体验就像“把图片中的文字变成可复制文本”。判断一份 PDF 属于哪一类最简单的方法是打开文件后按Ctrl F随便搜索一个页面上肉眼可见的词如果能搜到就是文本型搜不到则很可能只是图片或看起来像文字但无文字层。代码判断时可以提取一页文字如果长度接近 0 或远小于肉眼可见的字符量说明文件很可能需要走 OCR 流程。py100 系列后续如专门讲解 OCR可以从 Tesseract、PaddleOCR 等方向继续深入学习本篇文章中我们重点处理的是可以直接提取内容的文本型 PDF。4. 实战一批量提取 PDF 指定页面内容并汇总4.1 需求拆解办公场景中常遇到这样的任务在几十份 PDF 合同或验收单中找到包含“合同编号”的页面把文件名称、页码和该页第一行内容汇总成一个清单方便后续人工查看。如果手工一份份打开查找很容易漏看使用脚本则可以快速生成一个可检索的结果文件。功能拆解如下遍历指定目录下的所有 PDF 文件。逐页提取文本判断是否包含目标关键词。如果命中记录文件名、页码和页面首行片段。把结果写入 UTF-8 编码的文本文件方便在 Excel 或编辑器里查看。这里的关键点不是代码本身而是异常处理。办公环境中 PDF 文件来源多样难免有个别文件损坏、加密或包含扫描页脚本不能因为一个文件报错就中断整批任务所以我们需要把单文件异常捕获住并继续处理。4.2 完整代码新建search_pdf_pages.py保存以下代码# -*- coding: utf-8 -*- # 文件名search_pdf_pages.py # 说明批量搜索 PDF 页面中包含指定关键词的文件与页码 import logging from pathlib import Path import pdfplumber INPUT_DIR Path(pdfs) KEYWORD 合同编号 OUTPUT_FILE Path(output/search_result.txt) def main(): logging.basicConfig(levellogging.INFO, format%(levelname)s | %(message)s) pdf_files sorted(INPUT_DIR.glob(*.pdf)) if not pdf_files: logging.warning(目录中没有找到 PDF 文件: %s, INPUT_DIR) return results [] for pdf_path in pdf_files: try: with pdfplumber.open(pdf_path) as pdf: total_pages len(pdf.pages) logging.info(开始处理 %s共 %d 页, pdf_path.name, total_pages) for page_index, page in enumerate(pdf.pages, start1): text page.extract_text() or if KEYWORD in text: # 取页面第一行非空内容作为片段便于人工核对 lines [line.strip() for line in text.splitlines() if line.strip()] snippet lines[0][:80] if lines else text[:80] results.append(f{pdf_path.name}\t第{page_index}页\t{snippet}) logging.info(命中%s 第 %d 页, pdf_path.name, page_index) except Exception as exc: logging.error(处理 %s 时出错: %s, pdf_path.name, exc) OUTPUT_FILE.parent.mkdir(parentsTrue, exist_okTrue) with open(OUTPUT_FILE, w, encodingutf-8) as f: f.write(\n.join(results)) logging.info(处理完成共命中 %d 条结果已写入 %s, len(results), OUTPUT_FILE) if __name__ __main__: main()代码里为什么用logging而不是print因为在批量处理大量 PDF 时如果脚本运行时间较长我们更希望看到带级别的日志也方便以后把日志写入文件。OUTPUT_FILE.parent.mkdir(parentsTrue, exist_okTrue)保证输出目录即使不存在也能自动创建避免手动新建文件夹。4.3 运行与验证命令行执行python search_pdf_pages.py预期日志类似INFO | 开始处理 合同A.pdf共 3 页 INFO | 命中合同A.pdf 第 1 页 INFO | 开始处理 项目资料_v2.pdf共 12 页 INFO | 处理完成共命中 1 条结果已写入 output/search_result.txt打开output/search_result.txt每一行由三部分组成用制表符分隔合同A.pdf 第1页 销售合同编号LX-2025-0098这样的结果文件可以直接复制到 Excel 里按制表符分列后形成表格。如果以后需要输出成 Excel也可以把results改存到openpyxlWorkbook思路是相通的。4.4 需要注意的坑搜索结果出现漏报最常见的原因是目标 PDF 是扫描件或图片型 PDFextract_text()提取不到文字。出现这种情况时可以先打印该页提取到的文本长度如果一直都是 0尽早提示用户该文件需要走 OCR 路线不要继续在文本提取上浪费时间。搜索出现误报常见原因是关键词是通用词比如搜索“合同”可能命中页眉、页脚、表单说明等位置。解决方案是让关键词更加精确例如搜索“合同编号LX-”而不是“合同”。另外如果关键词包含中文请确保 Python 源码文件编码为 UTF-8在Windows控制台遇到编码问题时可以临时把输出文件编码统一写成 UTF-8并在 Excel 中通过“数据-自文本”导入。5. 实战二批量合并与拆分 PDF5.1 页面的操作为什么适合用 pypdf办公场景中的“合并 PDF”和“拆分 PDF”本质上是把 PDF 中的页面对象重新组合成一个新文件。这种操作不关心页面内部文字的完整语义只关心“哪几页应该出现在新文件的什么位置”因此使用pypdf非常合适。试想一个例子你手上有四份 PDF分别是“报名表封面”“技术方案正文”“报价清单”“附件说明”需要合并为一个完整标书或者你收到了一个 80 页的合同扫描附件但真正需要提交给同事的只有第 3 到第 10 页。针对前者合并脚本可以替代手动打开多个文件再另存为的操作针对后者拆分脚本可以避免一页页打印或复制。5.2 批量合并多个 PDF新建merge_pdfs.py代码如下# -*- coding: utf-8 -*- # 文件名merge_pdfs.py # 说明把 pdfs 目录下的多个 PDF 按文件名顺序合并为一个 PDF import re from pathlib import Path from pypdf import PdfReader, PdfWriter INPUT_DIR Path(pdfs) OUTPUT_FILE Path(output/merged.pdf) def natural_key(path: Path): 自然排序文件名中的数字按数值而不是字符串排序。 return [int(part) if part.isdigit() else part.lower() for part in re.split(r(\d), path.stem)] def merge_pdfs(pdf_dir: Path, output_file: Path) - None: writer PdfWriter() # sorted 默认按字符串排序10 会排在 2 前面所以这里用 natural_key pdf_files sorted(pdf_dir.glob(*.pdf), keynatural_key) if not pdf_files: print(目录中没有 PDF 文件) return for pdf_file in pdf_files: reader PdfReader(pdf_file) # 如果 PDF 加密且无权限continue 或记录日志 for page in reader.pages: writer.add_page(page) print(f已加入: {pdf_file.name}页数: {len(reader.pages)}) output_file.parent.mkdir(parentsTrue, exist_okTrue) with open(output_file, wb) as f: writer.write(f) print(f合并完成: {output_file}) if __name__ __main__: merge_pdfs(INPUT_DIR, OUTPUT_FILE)代码中最容易被忽视的是natural_key函数。如果目录下包含1.pdf、2.pdf、10.pdf使用默认的字符串排序结果是1.pdf、10.pdf、2.pdf这显然不符合日常预期。通过把文件名中的数字段转成整数可以保证1, 2, 10的顺序。运行python merge_pdfs.py得到output/merged.pdf它的页面顺序和源码文件顺序一致。在正式办公场景中建议先预览合并后的文件重点关注页序、文件是否为最新版本。脚本可以快速执行但“文件选错版本”这种问题脚本是无法判断的。5.3 按页码范围拆分 PDF新建split_pdf.py代码如下# -*- coding: utf-8 -*- # 文件名split_pdf.py # 说明把指定 PDF 的某一段页码范围拆出来另存为新 PDF from pathlib import Path from pypdf import PdfReader, PdfWriter INPUT_FILE Path(pdfs/大文件.pdf) OUTPUT_DIR Path(output/split) START_PAGE 3 # 结束页码从 1 开始 END_PAGE 10 # 结束页码包含该页 def split_pdf(file_path: Path, output_dir: Path, start_page: int, end_page: int) - None: reader PdfReader(file_path) total_pages len(reader.pages) if start_page 1 or end_page total_pages or start_page end_page: print(f页码范围不合法共 {total_pages} 页请输入 1-{total_pages} 之间的页码) return writer PdfWriter() # 将人类习惯的“第 3 页”转为从 0 开始的索引 for page_index in range(start_page - 1, end_page): writer.add_page(reader.pages[page_index]) output_dir.mkdir(parentsTrue, exist_okTrue) output_file output_dir / f{file_path.stem}-pages_{start_page}-{end_page}.pdf with open(output_file, wb) as f: writer.write(f) print(f拆分成功: {output_file}) if __name__ __main__: split_pdf(INPUT_FILE, OUTPUT_DIR, START_PAGE, END_PAGE)这里有一个非常容易让初学者出错的点PDF 页面索引从 0 开始代码里reader.pages[0]实际是第一页而用户习惯说的“第 1 页”对应索引 0。在上面的实现里用户输入的START_PAGE3会被转换为索引 2再通过range连续取页目标页码都会被包含在内。如果需求是“保留第 3 到第 10 页”上面的写法是正确的如果你的需求是“从第 3 页开始拆 10 页”则应该用range(start_page - 1, start_page - 1 page_count)。两种语义很容易混淆写脚本前先和需求方确认清楚。5.4 加密、解密与权限边界拆分或合并加密 PDF 时pypdf的常见处理流程是reader PdfReader(pdfs/encrypted.pdf) if reader.is_encrypted: reader.decrypt(password_you_have) # 仅限有权限的文件如果调用decrypt后仍然无法读取页面说明当前密码为空或权限不足。办公自动化的前提是操作者对自己手中的文件有合法处理权限例如公司内部文档管理允许运维人员为了归档而解密未经授权破解他人文件密码属于越权操作绝对不能写入脚本。如果遇到同事发来的加密文件正确做法是请对方提供密码或直接让对方解密后重发。同样的道理也适用于文件覆盖。拆分输出文件建议使用新的文件名或独立的输出目录不要直接覆盖原始 PDF避免误操作后无法恢复。6. 实战三把 PDF 表格提取并写入 Excel6.1 为什么表格提取与文本提取不是一回事在办公中PDF 里的表格数据往往是最有利用价值的内容例如对账单、人员名单、项目进度表。按普通文本方式提取表格行列关系会被完全打散因此我们需要使用具备表格识别能力的解析库。pdfplumber对表格的提取逻辑大致是识别页面上的横线和竖线坐标把页面区域划分成单元格再把落在单元格内的字符组装起来。因此如果 PDF 中的表格有比较完整的边框提取效果通常非常好如果没有边框但用空格分层extract_tables()的准确性就会下降。另一种常见情况是扫描 PDF 中表格本身是图片此时pdfplumber也无法直接识别表格必须先做 OCR 或使用专门的 OCR 表格识别方案。在实际处理表格之前先用手工方式打开一个样例文件观察表格有几列、表头是什么、合并单元格情况如何再确定处理策略成功率和效率都会更高。6.2 单个 PDF 表格提取的基础代码先看单文件如何提取import pdfplumber with pdfplumber.open(pdfs/人员名单.pdf) as pdf: page pdf.pages[0] tables page.extract_tables() for table in tables: for row in table: print(row)page.extract_tables()返回的是一个列表每个元素是一张表每张表又是由多行组成的嵌套列表。打印输出大概是[姓名, 部门, 联系电话] [张三, 技术部, 13800000001] [李四, 市场部, 13800000002]如果打印结果是[None]或空列表说明当前页面没有检测到表格边框或者表格结构太复杂需要先看看页面中的表格是否由图片构成。6.3 批量写入 Excel 的完整代码这里做一个小型批量处理把pdfs目录下所有 PDF 中能识别出的表格都写入一个 Excel 文件每个 PDF 占一个工作表。如果 PDF 是一个销售报表多张表结构相同我们也可以再额外增加汇总逻辑把相同表头的数据统一追加到一个工作表里。为控制文章篇幅我们先实现“每个 PDF 一个 sheet”的版本。新建table_export.py# -*- coding: utf-8 -*- # 文件名table_export.py # 说明批量把 PDF 中能提取到的表格写入 Excel import logging from pathlib import Path import pdfplumber from openpyxl import Workbook INPUT_DIR Path(pdfs) OUTPUT_EXCEL Path(output/table_output.xlsx) def sanitize_sheet_name(name: str) - str: Excel 工作表名不能包含 \\ / ? * [ ] : 等字符 for ch in \\/?*[]: name name.replace(ch, _) return name[:31] or Sheet def extract_tables_from_pdf(pdf_path: Path): 返回列表元素为 (页码, 表格二维列表) result [] with pdfplumber.open(pdf_path) as pdf: for page_index, page in enumerate(pdf.pages, start1): try: tables page.extract_tables() except Exception as exc: logging.warning(%s 第 %d 页提取表格失败: %s, pdf_path.name, page_index, exc) continue for table in tables: if table: result.append((page_index, table)) return result def main(): logging.basicConfig(levellogging.INFO, format%(levelname)s | %(message)s) pdf_files sorted(INPUT_DIR.glob(*.pdf)) if not pdf_files: logging.warning(目录中没有 PDF 文件: %s, INPUT_DIR) return wb Workbook() wb.remove(wb.active) # 移除默认空白 sheet for pdf_file in pdf_files: tables extract_tables_from_pdf(pdf_file) if not tables: logging.warning(%s 未提取到表格, pdf_file.name) continue sheet_name sanitize_sheet_name(pdf_file.stem) ws wb.create_sheet(titlesheet_name) current_row 1 for page_index, table in tables: # 不同页码之间加一个小标题行便于阅读 ws.cell(rowcurrent_row, column1, valuef--- 第 {page_index} 页表格 ---) current_row 1 for row in table: # 去除完全为 None 的空行 if not any(cell is not None and str(cell).strip() for cell in row): continue for col_index, cell in enumerate(row, start1): if cell is not None: ws.cell(rowcurrent_row, columncol_index, valuestr(cell).strip()) current_row 1 current_row 1 # 每页表格之间空一行 logging.info(%s 已写入共 %d 段表格, pdf_file.name, len(tables)) OUTPUT_EXCEL.parent.mkdir(parentsTrue, exist_okTrue) wb.save(OUTPUT_EXCEL) logging.info(Excel 已保存: %s, OUTPUT_EXCEL) if __name__ __main__: main()这段代码做了几件事使用openpyxl.Workbook创建 Excel并移除默认空白 sheet避免留下无用的“Sheet”。对工作表名做了清洗。Excel 中 sheet 名称不能包含\ / ? * [ ] :并且长度不能超过 31 个字符否则保存时会直接报错。对每个 PDF 创建同名工作表便于对照原始文件。写入时过滤掉整行为空的行因为pdfplumber返回的表格经常包含一些只有None的空白行。每一页表格之间加一行说明文字方便肉眼分页查看。这里把每个 PDF 放一个工作表在实际办公中还有一个扩展思路如果所有 PDF 的结构一样例如多个销售明细表通常更应该把数据行追加到同一个 sheet之后再使用 Excel 数据透视表做统计。实现思路是把第一张表的表头作为主表头后续文件只追加数据行。如果想在自动化脚本里动态判断表头是否一致可以先读取第一个文件的第一行再与后续文件第一行做字符串比较。6.4 运行与验证python table_export.py日志输出类似INFO | 人员名单.pdf 已写入共 1 段表格 INFO | 费用明细.pdf 已写入共 3 段表格 INFO | Excel 已保存: output/table_output.xlsx打开output/table_output.xlsx可以看到每个 PDF 对应一个工作表工作表内部是按页标记的连续表格区域。若打开后发现某些单元格内容不是预期列例如文字多出来或列错位需要回到源 PDF 分析该表格是否有合并单元格、跨页断行等问题。Excel 的“分列”功能有时可以补救一部分文本导出后的拆列问题但最好还是在源 PDF 阶段就尽量保证解析规则准确。7. 办公自动化高频问题与排查思路7.1 常见问题汇总问题现象常见原因解决思路extract_text()返回空或NonePDF 是扫描件/图片型 PDF没有文字层确认文件是否为扫描件后续考虑 OCR提取出来的文字中文乱码PDF 内嵌字体不含 Unicode 映射或源文件本身有问题先用阅读器搜索关键词测试再输出字符编码信息表格提取不到或列对不齐表格没有清晰边框页面可能被转成图片手动预处理表格使用有边框的排版或视觉化识别方案合并后第 10 个文件排在 2 前面文件名按字符串排序使用自然排序函数把数字段转成整数后再排序打开加密 PDF 报错文件使用了密码保护确认拥有合法密码后调用decrypt保存 Excel 报“工作表名称无效”sheet 标题包含\ / ? * [ ] :或长度超过 31清洗工作表名后再写入脚本处理几十个文件时中断某个文件损坏或页面格式特殊增加 try-except每处理一个文件记录日志跳过问题文件7.2 如何排查“读不出来的 PDF”当某个 PDF 无法提取文本时可以按下面的步骤定位用 PDF 阅读器打开文件按Ctrl F搜索一个页面上肉眼可见的中文词。如果搜不到把这个页面导出成图片再使用一个具备 OCR 能力的工具识别确认是否扫描件。在 Python 中只提取首页文本并打印repr(text)观察是否有不可见字符或乱码。如果某些页面能提取、某些页面不能说明 PDF 是混合型文件脚本中需要按页判断渲染方式。如果文本提取能拿到内容但顺序错乱检查该 PDF 是否包含多栏排版、页眉页脚或文本框必要时用pdfplumber提供的高级参数extract_text(layoutTrue)或手动控制page.crop区域。7.3 从“能跑”到“稳定”的调试建议很多办公自动化脚本在样例文件上运行顺利一放到真实目录里就问题百出几乎都是因为没有处理边界情况。我的建议是先在待处理目录里保留少量小文件进行调试并设置一个只处理 3 到 5 个文件的测试模式确认脚本逻辑没问题后再放开全量目录。不要一开始就去处理上千份文件那样只会增加排障成本。输出结果最好带上源文件名称与页码字段这样一旦发现异常可以快速回溯到原始 PDF。如果处理的是敏感文档脚本输出的临时文本也要及时清理避免在服务器或共享目录中留下不必要的明文缓存。8. 工程建议让 PDF 脚本真正落地办公场景8.1 文件与路径方面的工程建议在实际办公项目中文件处理需要比教程示例更谨慎。建议将原始文件统一放到一个可读目录把脚本输出和中间结果放到另一个目录脚本运行前先统计目标 PDF 数量和总页数便于和预期结果核对。通过Path对象操作路径可以避免写出../pdfs/这类硬编码也便于后续在不同操作系统中迁移。批量操作有覆盖风险的场景建议先复制一份原文件到备份目录。例如拆分或加水印时如果脚本因为某个 bug 把页面重复写入原始文件至少不会被破坏。输出文件命名可以附上时间戳例如merged_20250201_1530.pdf这样即使多次运行也不会覆盖上一次结果方便版本对比。8.2 编码与日志建议办公文件大量包含中文编码问题集中体现在文本文件写入、Excel 表头、控制台输出三个位置。Python 3 中源码默认 UTF-8现在基本不需要在文件开头加coding: utf-8声明但如果团队仍使用旧编辑器或 Windows 老环境统一 UTF-8 仍然是最省事的方案。在多文件任务中print可以用于简单调试生产级脚本建议使用logging。通过日志能同时记录成功、跳过和失败的文件名方便事后核对。例如import logging logging.basicConfig( levellogging.INFO, format%(asctime)s | %(levelname)s | %(message)s, handlers[ logging.FileHandler(pdf_process.log, encodingutf-8), logging.StreamHandler() ] )这样错误信息同时输出到屏幕和文件即使脚本在后台运行之后也能追查日志。8.3 安全与合规边界涉及 PDF 加密、密码恢复、文档内容抽取时边界非常简单只处理业务上授权你处理的文件。办公自动化是为了提升效率不是为了绕过权限。明文包含身份证号、手机号、银行卡等敏感信息的 PDF在生成中间文件时要注意最小化数据落盘并设置合理的访问权限。另外PDF 处理结果不能完全替代人工复核。合同类文档建议输出 PDF 预览或生成偏差报告不会因为“代码能跑”就直接跳过关键内容审核。9. 总结与后续学习方向写到这里py100 办公自动化的 PDF 了解篇第二部分就告一段落了。本篇从 PDF 的“固定版式”特性出发解释了为什么 PDF 不能像 Word 一样直接解析也区分了文本型 PDF 与扫描型 PDF 两条技术路线。环境方面我们完成了pdfplumber、pypdf、openpyxl的安装与库选型实战方面分别实现了“按关键词搜索页面并输出汇总清单”“批量合并与按页码范围拆分 PDF”“把 PDF 表格提取并写入 Excel”三个典型场景。对整个办公自动化项目来说这三组实战基本覆盖了日常高频的 PDF 操作。你不需要把 PDF 领域的所有细节都一次学完先把本次的脚本组装成自己的“通用工具箱”遇到信息抽取就想到pdfplumber遇到页面重组就想到pypdf遇到表格落盘就想办法先校验再写入 Excel。在此基础上再去看图像提取、水印定制、把 PDF 页面渲染成图片做 OCR就都只是 API 层面的扩展了。在动手测试时建议先用几页的简单文件跑通流程再逐步投入复杂文件。这样做的好处是能把格式问题、编码问题和代码逻辑问题分开排查也更容易建立起对 PDF 处理边界的直观感受。真正在办公环境运行前务必在测试目录中保留原始 PDF 的副本并检查脚本是否带有意外覆盖或输出错乱的风险。希望这篇文章能帮你把 PDF 办公自动化从“看着复杂”变成“能复用、能落地”为后续更高阶的 py100 实战题目打下基础。
返回列表