资讯动态

Python PDF自动化:文本提取、批量处理与扫描版OCR实战指南

发布时间:2026/9/4 21:44:45 来源:尧图企业网站定制
接到 50 个 PDF 需要提取文字做月度汇总里面既有合同扫描档也有系统导出的带目录 PDF。直接打开一个个复制粘贴一晚上基本就耗在鼠标上换用 Python 把这些文件按类型分开、批量读取文本、自动生成清单半小时可以跑完初筛。这就是办公自动化里很典型的 PDF 场景。PDF 自动化最值钱的地方不是帮你写一个复杂解析器而是帮你在不了解 PDF 内部结构的情况下把重复劳动转换成可批量、可复现的脚本流程。适合正在学 Python、想往办公自动化方向落地的开发者和运营人员也适合每天要整理大量电子文档的行政、科研助理。最值得先掌握的不是某个库的高级 API而是判断 PDF 能不能用代码直接处理以及不同任务应该用什么库。很多教程一上来就直接用 PyPDF2 读取页面文本结果遇到扫描版时读出来全是空的也有人只是想给 30 个 PDF 统一加水印结果先去学了全文解析。PDF 自动化的第一步不是选库而是把需求拆分清楚。1. PDF 自动化能处理哪些办公需求1.1 常见 PDF 任务和自动化价值日常办公中PDF 相关的自动化任务大致可以分成下面几类基本读取读取文件页数、标题、作者、创建时间等信息。页面操作合并、拆分、旋转、调整顺序、删除指定页。内容提取提取页面文字、图片、表格。格式转换PDF 转 Word、PDF 转图片、图片导出、网页生成 PDF。安全处理给自己生成的 PDF 加打开密码或处理自己有权处理文件的权限问题。批量归档把散落各处的 PDF 按规则命名、移动到统一目录生成汇总清单。不同任务对工具的要求完全不一样。合并拆分用轻量库就好未必需要完整解析页面文字表格抽取需要更细的坐标信息图片提取要能访问 PDF 内部对象而 PDF 转 Word 对版式还原要求最高。我建议先在纸上把需求写清楚输入是什么、输出是什么、文件量有多大、是单文件还是批量。只要输入文件数量和频率足够高脚本的收益就很大。比如一个月只转 3 个 PDF手动处理完全够用但如果每周都来几十个文件自动化才值得做。1.2 先学会区分文本型 PDF 与扫描版 PDFPDF 自动化实操中最容易踩的坑是把“能打开看”等同于“能用代码提取”。实际上 PDF 内部有非常大的形态差异。文本型 PDF 里面有真正的文字字符用阅读器选中文字后能复制脚本可以直接把内容抽出来。扫描版 PDF 本质是图片每一页都是页面照片代码直接提取文字时大概率拿到空内容或乱码。还有一类 PDF 用“字体路径”渲染文字阅读器显示正常但内部不包含标准 Unicode 映射抽取结果也会乱。判断方法很简单用 PDF 阅读器打开文件试着选中某段中文。能选中且复制出来不乱码说明有文字层选不中或者选中的是一整块图片基本就是扫描版。如果需要写代码判断可以在脚本里抽取第一页文本如果文本长度接近 0再看页面里有没有大尺寸图片对象两者结合基本能确定这是一份扫描 PDF。区分文本型和扫描版意义很大。如果目录中 80% 都是扫描版那处理思路要往 OCR 方向走如果都是系统导出的文本型 PDF那直接做文字抽取、合并拆分和格式转换就够用。盲目对所有文件套同一个解析脚本大概率会得到一堆空结果。2. 环境准备和库选型PDF 自动化第一关2.1 Python 环境和依赖安装建议PDF 自动化对 Python 版本没有特别苛刻的要求常用的 3.10、3.11、3.12 都可以。如果你的电脑还没有 Python建议从官方渠道下载安装包安装时勾选“Add Python to PATH”避免后面在命令行里敲python提示找不到命令。装完以后先确认环境python --version pip --version两个命令都有输出环境基本就绪。如果pip下载依赖很慢可以临时使用国内镜像源但不建议把镜像参数写死到全局配置避免以后需要私有源时产生干扰。然后安装 PDF 处理常用库pip install pypdf pdfplumber PyMuPDF pdf2docx这里说明一下PyMuPDF安装成功后在 Python 里导入的名称是fitz很多新手会以为装错了包。2.2 常用 PDF 库的选型对比先列一张选型表之后按场景选择就行主要场景建议库说明合并、拆分、旋转、加密码pypdfAPI 简单适合页面级文件操作文本抽取、表格抽取pdfplumber能拿到更多版面坐标信息全能批处理、图片提取、页面渲染PyMuPDF速度快功能综合导入名是 fitzPDF 转 Wordpdf2docx适合文本型和简单版式文件生成新 PDF、模板填充reportlab从零创建 PDF 时使用如果任务以“页面合并拆分”和“读取基本信息”为主pypdf 就够没必要引入重库。如果任务需要处理大量文本和表格pdfplumber 比 pypdf 的基础提取更细致速度和内存占用会大一些换来的定位信息更丰富。如果任务涉及把 PDF 页面渲染成图片、提取内部原始图片PyMuPDF 是首选。提醒一点PyMuPDF 的常见许可证需要根据项目情况评估公司内部项目使用前建议先确认合规性。学习阶段安装使用没问题但如果是后端服务或商业产品不能只看功能方便。2.3 多个库同时安装会不会冲突这几个库可以安装在同一套 Python 环境里它们之间没有强依赖冲突。实际安装报错如果出现多数原因是当前 Python 版本太老或者 pip 版本过旧与某个库的发布版本不兼容。处理方式不是去网上下载“整天候绿色版 Python 安装包”而是先升级 pippip install --upgrade pip再重新安装对应库并查看报错信息里提示的 Python 版本范围。办公自动化脚本通常跑在本地或公司内部服务器使用虚拟环境可以避免污染系统 Python。学习阶段用一个环境先跑通项目复杂之后再按虚拟环境拆分也来得及。3. 第一个实用脚本读取页数、元数据和批量文件清单3.1 从单文件元数据开始验证链路第一个脚本不需要做复杂解析先把一个 PDF 的基本信息读出来。这样能验证安装、路径、文件是否能正常打开为后面更深度的处理铺路。from pathlib import Path from pypdf import PdfReader file Path(sample.pdf) reader PdfReader(file) print(文件名称, file.name) print(页数, len(reader.pages)) if reader.metadata: print(标题, reader.metadata.title) print(作者, reader.metadata.author)这里有几个小提醒。PDF 的元数据不一定会完整填写很多文件metadata.title是空值打印前做判断更稳妥。另外页面索引从 0 开始后面做拆分和定位要注意。跑这个脚本的真正目的不是只看文件名而是确认“读取 PDF - 拿到对象 - 打印结果”这条路是通的。如果这一步都报错大概率不是代码问题而是 Python 环境、依赖版本或文件路径的问题。3.2 批量扫描目录并统计异常文件实际工作中很少只看单个 PDF通常是一个目录下几十个文件。这时可以把清单脚本做成一个小工具顺便把处理不了的文件标出来。import json from pathlib import Path from pypdf import PdfReader result [] for pdf_file in Path(pdf_folder).glob(*.pdf): try: reader PdfReader(pdf_file) item { 文件: pdf_file.name, 页数: len(reader.pages), 加密: reader.is_encrypted, 路径: str(pdf_file) } result.append(item) except Exception as exc: result.append({ 文件: pdf_file.name, 错误: f{type(exc).__name__}: {exc} }) with open(pdf_manifest.json, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse, indent2) print(处理完成, len(result))这段代码会扫描目录下所有.pdf文件并把页数、加密状态、能否正常读取写入 JSON 文件。加密文件没有密码时不必强行打开先标记为加密后面单独确认权限。跑完整理后再决定怎么做会比“直接对全部文件跑文本抽取”稳妥得多。因为用一个文件清单你就能发现有几份文件其实没权限打开、有几份页数可能异常、有几份根本不是 PDF 而是改后缀名的文件。3.3 路径和文件名是很多坑的来源Windows 命令行下经常遇到中文路径或者文件名里带空格、括号的情况。Python 的pathlib已经能比较好地处理这些问题但某些库在内部调用时仍可能因为编码和字符转义报错。我一般会建议批量处理前先把待处理的 PDF 放到一个纯英文目录中文件名尽量用字母、数字、下划线。这样做不是“Windows 不支持中文”而是为了减少排查成本。如果你愿意处理编码问题中文路径通常也能跑通但办公自动化追求的是稳定不是挑战环境上限。不推荐用str(Path)拼接路径更不建议在 Windows 上写死C:\xxx\pdf这种含反斜杠的路径跨平台脚本会受影响。统一用pathlib接管目录和文件路径是成本最低的规避方式。4. 文本抽取中文乱码、空格错乱和扫描版误判4.1 用 pdfplumber 抽取整页文本当清单确认文件是文本型 PDF 后可以进入文字抽取。推荐先用 pdfplumber因为它对版面信息的还原比 pypdf 的基础方法更细。import pdfplumber with pdfplumber.open(sample.pdf) as pdf: for page_index, page in enumerate(pdf.pages, start1): text page.extract_text() if text: print(f 第 {page_index} 页 ) print(text[:500]) else: print(f 第 {page_index} 页未提取到文本 )extract_text()返回的可能不是空字符串而是None所以不能直接用if not text判断要用if text is None或上面的方式。如果连续多页都返回空优先检查是不是扫描版 PDF而不是反复调参数。pdfplumber 的优点是能拿到每个单词的坐标、字体、大小等细节适合做“找到某一页某个区域的文字”这种精细化任务。缺点是处理大文件时比 PyMuPDF 慢。文件多、页数多、内容以全文为主时我更倾向于用 PyMuPDF 快速拿结果。import fitz doc fitz.open(sample.pdf) for page in doc: text page.get_text(text) if text.strip(): print(text[:500]) doc.close()批量脚本里会优先考虑这套逻辑先用 PyMuPDF 快速抽取全文遇到整页文本为空的再交给 pdfplumber 进一步看对象信息确认是图片页后标记成“待 OCR”。4.2 中文乱码和空格错乱的真实原因中文 PDF 提取乱码不一定是你代码写错。PDF 为了保证字体显示可能把字体子集嵌入但没有写入完整的文本映射表。阅读器在显示时用字形轮廓绘图所以你看不出异常但脚本提取时拿不到正常 Unicode就会输出乱码。另一种常见情况是内容能提取但汉字之间突然出现大量空格。这通常是因为 PDF 内部把每个文字当成独立文本块版式信息里记录了坐标却丢失了连续的文本游程。pdfplumber 抽取时可以试试带版面参数的读取方式text page.extract_text(layoutTrue)layoutTrue会尝试根据坐标还原原始排版关系多栏结构下效果更明显。如果依然不满意可以改用 PyMuPDF 的get_text(blocks)拿每一块的坐标和文字后自行重排for block in page.get_text(blocks): x0, y0, x1, y1, content, block_no, block_type block if block_type 0: print(round(x0, 1), round(y0, 1), content)这里block_type 0表示文本块图片块是 1。拿到坐标后就能按阅读顺序人工排序比只显示乱序内容更容易定位。遇到排版文件先不要认定“库不支持中文”。优先确认三件事文件能否选中文字、选中后复制是否正常、字体是否完整嵌入。“复制正常但脚本乱码”通常和字体映射有关不是换一个库就能彻底解决的。4.3 需要 OCR 时怎么快速判断如果一份 PDF 每一页都像扫描图片那文本抽取这条路走不通。继续调库参数、换提取模式只是在浪费时间。判断方法可以固化到脚本里提取当前页文本。如果文本为空或字数极少。再获取当前页的图片对象数量。如果图片对象数量很多标记为扫描页。用 PyMuPDF 获取页面图片对象非常方便page_images page.get_images(fullTrue)如果一页文字长度为 0同时有图片对象就可以判断这一页需要 OCR而不是文本抽取。OCR 本身是另一个主题涉及识别引擎、语言包、图片预处理和置信度判断适合作为 PDF 自动化系列的第二篇单独展开。本篇建议先把“扫描版需要走 OCR”这个判断埋进脚本把需要 OCR 的文件单独导出一份清单不要混在纯文本型 PDF 里继续处理。5. 页面级操作合并、拆分、旋转和加密码5.1 合并多个 PDF合并文件最常用的是 pypdf思路是创建一个合并器然后不断把源文件页面加入from pypdf import PdfReader, PdfWriter writer PdfWriter() file_list [file1.pdf, file2.pdf, file3.pdf] for file_name in file_list: reader PdfReader(file_name) for page in reader.pages: writer.add_page(page) with open(merged.pdf, wb) as f: writer.write(f)这里最容易被忽略的是文件顺序。如果目录里有很多按序号命名的 PDF用glob(*.pdf)拿到的顺序不一定是 1、2、3、4…… 而是 1、10、11、2、3 这种字典序。正确的做法是显式构造文件列表或者用自然排序file_list.sort(keylambda p: int(p.stem))如果文件名不是纯数字则按实际业务规则排序。合并之前建议先打印每个文件的页数避免合完以后才发现某一章节顺序放错。5.2 拆分提取指定页拆分相当于反向操作把源文件的若干页复制到新文件from pypdf import PdfReader, PdfWriter reader PdfReader(large.pdf) writer PdfWriter() start_page 2 end_page 5 for page_index in range(start_page - 1, end_page): writer.add_page(reader.pages[page_index]) with open(extract_pages.pdf, wb) as f: writer.write(f)这里容易错一页。代码里的range(2, 5)实际取的是索引 2、3、4对应第 3、4、5 页。如果你想把“从第 2 页到第 5 页”的内容抽出来要写range(1, 5)。建议统一用页码减一的方式写避免肉眼误判。如果要按照章节拆分一份几十页的 PDF可以事先从清单脚本里记录每个章节的起始页再循环生成多个输出文件。输出文件名不要用part.pdf这种固定写法新一批任务会直接覆盖旧结果最好带上原文名称或章节号。5.3 旋转页面和删除空白页扫描设备出来的 PDF偶尔会有个别页面方向不对。pypdf 可以按页码旋转for page_index in [1, 3]: page reader.pages[page_index] page.rotate(90)旋转前最好先打印每个页面的开头几个字符定位到真正需要旋转的页码。不要凭感觉猜测也不要在循环里把所有页面都旋转。大部分 PDF 阅读器自带的“自动旋转”功能是基于页面内容方向判断的脚本里没有这个自动判断能力需要自己根据实际方向写条件。删除空白页看起来简单其实有风险。有些书会保留空白页删除后页码和目录都会乱有些页面看着空白实际包含整页背景图片。如果一定要做建议先把所有“文本为空、图片数量为 0”的页面列出来人工确认后执行而不是写死“空页全部删掉”。5.4 给 PDF 加打开密码的合规操作办公流程中经常需要把自己生成的 PDF 加上打开密码再发出去。pypdf 支持设置用户密码from pypdf import PdfReader, PdfWriter reader PdfReader(source.pdf) writer PdfWriter() for page in reader.pages: writer.add_page(page) writer.encrypt(user_passwordyour-password) with open(protected.pdf, wb) as f: writer.write(f)这里要强调一个边界本文只讨论自己拥有合法处理权限的文件加密场景。如果收到一个打开密码未知的 PDF在未经授权情况下想办法跳过密码访问不在办公自动化的正常范围内。实际工作中有权限问题应该先通过与文件提供方确认来解决而不是写绕过脚本。另外要注意encrypt的签名在不同 pypdf 版本里略有差异写代码前可以用help(writer.encrypt)看一眼当前版本的参数。处理过程中准备一份“加密文件清单”标记哪些已处理、哪些跳过比手动记忆可靠。6. 图片提取、PDF 转 Word 和页面导出图的边界6.1 批量提取 PDF 内部的原始图片很多用户搜索“python 提取 pdf 中的图片”其实要两类不同的操作。一是把 PDF 页面转成图片这是截图渲染二是从 PDF 内部对象里取出原始嵌入图片这才更贴近“提取”。用 PyMuPDF 提取原始图片核心是拿到图片对象的交叉引用编号xref再转换成 Pixmap 保存import fitz doc fitz.open(sample.pdf) for page_index in range(len(doc)): page doc[page_index] images page.get_images(fullTrue) for img_index, img in enumerate(images): xref img[0] pix fitz.Pixmap(doc, xref) if pix.n - pix.alpha 4: pix fitz.Pixmap(fitz.csRGB, pix) output_name fpage{page_index 1}_img{img_index 1}.png pix.save(output_name) pix None doc.close()代码里pix.n - pix.alpha 4用来判断是否有透明通道或 CMYK 模式必要时转换成 RGB避免保存出来的图片颜色发黑或者格式异常。提取图片后要检查输出数量和图片质量。不是 PDF 里每个图片对象都有实际价值比如底纹、背景、logo 都会作为独立图片被提出大批量提取后通常需要再按分辨率或尺寸过滤一遍。6.2 PDF 转 Word 的效果取决于源文件“pdf 转 word”这个关键词搜索热度一直很高但很多效果问题不是工具能解决的。PDF 作为固定版式格式本身不保存 Word 的段落层级、样式和可编辑结构。PDF 转 Word 更像是“根据坐标反向推测版面结构”而不是“打开一份 Word 原稿”。pdf2docx 可以处理比较规整的文本型 PDFfrom pdf2docx import Converter converter Converter(sample.pdf) converter.convert(sample.docx) converter.close()这条链路对单栏、文字型、无复杂背景的 PDF 效果不错。但遇到这些情况会明显变差多栏报纸式排版。大量文本框悬浮、旋转。跨页表格。带水印或背景图。扫描版 PDF。扫描版 PDF 直接转 Word往往得到的是一份空白或几乎无文字的文档因为 pdf2docx 只会处理文本对象不会自动 OCR。正确顺序是扫描版 PDF 先做 OCR 或人工重新录入再进入 Word 整理。如果只是提取部分内容做初筛没必要追求完整还原 Word 版式。把文字和表格数据抽出来直接进 Excel 或数据库往往比转 Word 更实用。6.3 用 PyMuPDF 做页面导出和截图预览很多办公场景不是要转 Word而是要把 PDF 页面转成图片用于系统预览、网页展示、内部审核。PyMuPDF 在这个任务上几乎是首选import fitz doc fitz.open(sample.pdf) page doc[0] pix page.get_pixmap(dpi150) pix.save(preview.png) doc.close()dpi参数直接影响图片清晰度和文件大小。屏幕预览用 150 足够需要打印或存档可以提高到 300。如果把dpi调到 500输出会很清晰但处理时间和磁盘占用也会明显上涨批量跑之前一定要先拿一页做测试。浏览器里的“网页页面 PDF 打印”如果要做自动化一般不是直接用 PDF 处理库而是通过无头浏览器把网页渲染成 PDF这属于网页自动化方向。当前这篇偏 PDF 文件本身操作后续可以结合自动化测试工具单独展开。7. 把脚本放进工作流重点处理失败情况7.1 单个文件失败不能拖垮整批任务办公自动化脚本和课程 Demo 最大的区别是可以接受“中途有不少文件出错”。真实文件来自不同部门命名习惯、生成软件、版本参数完全不同很难保证每个文件都规范。批量处理时我建议把每个文件包在独立的异常处理里不要让异常中断整个循环success [] failed [] for pdf_file in file_list: try: process_pdf(pdf_file) success.append(pdf_file.name) except Exception as exc: failed.append({ file: pdf_file.name, error: f{type(exc).__name__}: {exc} }) print(成功数量, len(success)) print(失败数量, len(failed)) for item in failed: print(item[file], item[error])这样跑一轮后你能看到有多少文件失败、失败原因是什么。修复一批问题后重新跑已经成功的文件可以跳过。7.2 常见错误分类和应对顺序真实项目中报错类型大概可以按下面这张表排查| 报错或现象 | 常见原因 |

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价