简介:这是一款面向办公与档案管理人员的批量双层PDF生成工具,可自动识别文件夹内多个PDF文件并完成OCR转换。软件基于Paddle识别模型,对中文、手写体均有较好的识别效果,适合需要批量构建可检索PDF索引库的场景。压缩包共1075个文件,大小约129.99MB,主要包含exe主程序、dll与pyd动态库、pdmodel/pdiparams模型文件、py与tcl脚本等,安装或配置后可独立运行。已有237人学习下载。资源内含完整运行环境与模型依赖,开箱即用,可帮助用户将扫描版或图像型PDF快速转为保留原始版面且支持文字检索的双层PDF文件,有效提升资料数字化管理效率。
1. 项目概述:批量为啥要转双层PDF
先说说我为什么要做这个工具。手头有大量纸质合同、档案、论文扫描件,都是纯图片PDF。这种文件最大的问题是文字不能搜索、不能复制,需要引用某句话只能手动重新敲一遍。更要命的是,几百页的扫描件要归档到知识库,没法全文检索,等于把一堆资料变成了死档。
如果只是偶尔一两份,用Adobe Acrobat或者ABBYY手动跑一下还好说。但一旦上了量,几十份、几百份堆在一起时,手动操作就是灾难,鼠标点来点去能点到怀疑人生。我花了一个周末写了个"批量转双层PDF工具v1.0",把整条流程串成一行命令,丢进去等结果就行。
所谓双层PDF,就是用OCR技术把扫描件识别出来的文字层叠加在原图像上。你可以把它形象地理解为"图片打底、文字隐形覆盖":视觉上看到的还是原来的扫描图像,但鼠标一划就能选中文字,Ctrl+F也能直接搜索,复制出来的是干净可编辑的文本。底层是图,顶层是不可见的文字,两层合一,文件体积相比原始扫描件不会膨胀太多,便携又不失真。
这个工具解决的就是"扫描件不可搜索、不可复制"的痛点,核心能力是把批量PDF/图片自动完成OCR识别、文字层嵌入、压缩输出。适合档案数字化、合同归档、论文扫描整理、老书电子化这些场景。凡是需要长期留存、频繁查阅检索的扫描文档,都值得转一遍。
顺便说一句,最近总看到有人在问"wps批量转图公式",其实WPS的批量图片转PDF功能配合宏命令也能做出类似效果。这个我在后面的实操章节里会展开讲,包括怎么用WPS写一个批量合并图片的公式,再配合我们的工具直接走通全流程。
2. 技术方案选型:为什么是OCRmyPDF + Tesseract
2.1 先聊转双层PDF的几种常见路子
转双层PDF的方案市面上不少,我简单梳理一下,大家心里有个谱。
第一种是商业软件方案,比如ABBYY FineReader、Adobe Acrobat Pro。识别精准度确实高,中文版面还原一流,但问题很现实:贵。一套授权几百上千,而且命令行批量处理的能力弱,想做自动化流水线基本没门。
第二种是开源命令行方案,核心是OCRmyPDF和Tesseract的组合。OCRmyPDF专门干"把OCR文字层嵌入PDF"这件事,Tesseract负责真正的文字识别。两个都是开源的,免费,支持脚本调用,批量处理起来非常顺手,而且识别精度在参数调好之后完全不逊色于商业软件。我做的工具v1.0就是基于这条路线。
第三种是国产方案,比如PaddleOCR。它的中文识别能力很强,尤其是在复杂版式下效果优于Tesseract。但它在做"双层PDF"时比OCRmyPDF要绕一些,需要自己写文字层嵌入逻辑,工程量偏大。不过如果你手头的扫描件版式极其复杂、表格特别多,PaddleOCR值得考虑,作为备用识别引擎来切换。
综合来看,对于"批量、免费、可自动化、中文够用"这四个需求,OCRmyPDF + Tesseract是当前最稳的性价比选择。
2.2 工具依赖的完整清单与版本说明
我这套工具的实际运行环境是Windows 10,Python 3.9,下面这些组件一个都不能少。对Linux/macOS用户,思路完全一致,只是安装命令稍有不同。
| 组件 | 版本 | 作用 |
|---|---|---|
| Python | 3.9+ | 批处理脚本运行环境 |
| OCRmyPDF | 14.x | 核心工具,负责OCR和文字层嵌入 |
| Tesseract OCR | 5.x | 文字识别引擎 |
| Ghostscript | 9.5x | PDF底层处理依赖,OCRmyPDF离不开它 |
| pdf2image | 1.16+ | 把PDF页转成图片做预处理用的辅助库 |
| PyMuPDF | 1.21+ | PDF元数据读取、分页操作 |
上述版本我只写了最低要求,往上兼容基本没问题。特别提醒,Tesseract通过pip没法装,它是个独立的原生程序。Windows下推荐用UB-Mannheim的安装包,装的时候记得勾选中文语言包(chi_sim),否则中文识别直接摆烂。
2.3 文件结构设计:一个脚本串起全流程
工具v1.0的目录结构是这样的:
batch_ocr_pdf/ ├── input/ # 待处理的PDF/图片全部丢这里 ├── output/ # 处理完成的双层PDF输出目录 ├── done/ # 处理完的源文件归档,防止重复处理 ├── logs/ # 日志目录,记录每次运行详情 ├── batch_ocr.py # 主脚本 └── config.json # 配置文件,参数都放这为什么要把源文件移到done而不是直接删掉?这是我在处理重要文档时养成的习惯。万一输出文件有问题,源文件还能找回重跑。等确认结果没问题后,再手动清空done目录也不迟。几百份文件批量处理最怕的就是跑了一半挂了,源文件又没了,那种欲哭无泪的感觉我经历过,你们就别踩了。
3. 核心实操:从安装到跑通全流程
3.1 环境安装的完整步骤
照着我下面的步骤走,半小时内能把环境全部配好。
第一步,安装Python依赖库。打开终端,执行:
pip install ocrmypdf pdf2image pymupdf pillow第二步,安装Ghostscript。Windows直接去官网下载安装包,装完后把安装目录下的bin路径加到系统环境变量PATH里,比如默认路径是C:\Program Files\gs\gs9.5x\bin。不配置的话,后面跑起来会报"Ghostscript not found"。
第三步,安装Tesseract。下载UB-Mannheim的安装包,安装时勾选Additional Language Data里的Chinese (Simplified)。安装路径最好记一下,后面配置要用,比如C:\Program Files\Tesseract-OCR。
安装完成后验证一下:
tesseract --version ocrmypdf --version两个命令都能正常回显版本号,说明基础环境就绪了。
3.2 配置文件说明:参数全解析
我在config.json里放了几个关键参数,每个参数都解释一下,这样你调整的时候心里有数:
{ "language": "chi_sim+eng", "deskew": true, "rotate_pages": true, "clean_final": true, "output_type": "pdfa", "jobs": 4, "optimize": 1, "skip_text": true, "threshold": 0.7 }各参数含义如下:
| 参数 | 取值 | 作用说明 |
|---|---|---|
| language | chi_sim+eng | 识别语言,中英混合优先 |
| deskew | true | 自动纠偏,扫描放歪的页面会被修直 |
| rotate_pages | true | 自动旋转方向,竖版横版混着的文档也能处理 |
| clean_final | true | 用图像清洁算法去噪点、去黑边 |
| output_type | pdfa | 输出PDF/A格式,适合长期归档保存 |
| jobs | 4 | 同时处理几个文件,视CPU核心数而定 |
| optimize | 1 | 压缩级别,0不压缩,1平衡,3最高压缩 |
| skip_text | true | 已有文字层的PDF直接跳过不重复处理 |
| threshold | 0.7 | 图片页码相似度阈值,用于跳过图片型页面 |
其中threshold这个参数我要展开聊聊。它是配合"跳过已有文字层的页面"策略用的。有些PDF本身前几页就是原生文字版,只是后面混了扫描页。OCRmyPDF默认会对整个文档做处理,但如果我们开skip_text,它会先检测哪些页面已经是数字原生文字,如果页面已有文字的比例超过这个阈值,就直接跳过该页面,只处理真正需要OCR的页面。这样处理出来速度更快,文件也不会因为重复嵌入文字层而变大。
3.3 主脚本逻辑拆解:几百行代码的核心就这点事
我的batch_ocr.py主脚本,核心逻辑不复杂,就是把一堆重复劳动自动化了。完整脚本涉及到的关键部分我拆开来解释一下。
首先是文件遍历逻辑。调用Path.glob把input目录下所有.pdf、.jpg、.png、.tif文件全部找出来,每找到一个文件就丢进处理队列。这里有个小细节:图片文件会先被合并成一个临时多页PDF再交给OCRmyPDF,不然一张一张输出太零碎了。
处理流程的关键代码段如下:
import ocrmypdf import subprocess from pathlib import Path def process_single_pdf(input_path: Path, output_path: Path, cfg: dict): """处理单个PDF的完整流程""" try: ocrmypdf.ocr( input_path, output_path, language=cfg["language"], deskew=cfg["deskew"], rotate_pages=cfg["rotate_pages"], clean=cfg["clean_final"], output_type=cfg["output_type"], jobs=cfg["jobs"], optimize=cfg["optimize"], skip_text=cfg["skip_text"], progress_bar=False ) return True except ocrmypdf.exceptions.PriorOcrFoundError: # 已经有文字层的PDF,直接复制过去 shutil.copy2(input_path, output_path) return True except Exception as e: print(f"[失败] {input_path.name}: {e}") return False如果你的原始PDF里有部分页是文字页、部分是扫描页,必须把skip_text参数开起来,并用好PriorOcrFoundError这个异常捕获分支。这个异常的意思是"检测到整个文件都带文字层了",这种情况下不需要重新OCR,直接把原文件复制过去就行,省掉大量CPU时间。
然后是多线程并发控制。我用的concurrent.futures.ThreadPoolExecutor,这里的核心是设置max_workers。OCRmyPDF本身在单文件内会开多线程,所以文件级别的并发不宜太高,否则内存直接打满。实测8核机器,jobs=4、文件级并发2,是比较均衡的配置。如果机器内存只有8GB,文件级并发建议1,宁愿多等一会儿也别把机器跑死。
最后是日志记录。每次运行的详细输出写进logs目录,文件按日期命名。处理失败的路径写进一个failed.txt,方便跑完再补一次。
3.4 实际跑批效果记录
我拿手头一批45份合同扫描件做了实测,总共680页,其中有部分页面是歪的,还有几页是横版表格。配置文件用的就是上面那套参数,单文件并行数4个,跑完用时约27分钟,输出文件总大小从2.1GB降到860MB,而且全部页面都能正常搜索文字、复制文本。
最让我意外的是deskew参数的效果。原本扫描时放歪了几度的页面,处理后被自动纠偏了,肉眼几乎看不出原来歪过。过去用商业软件,这种纠偏功能多半是需要手动逐页调整的,现在全自动搞定。这在批量处理时省下的时间,比OCR本身还要多。
4. 常见问题与排查技巧实录
4.1 安装配置阶段的翻车现场
第一批报错基本都是环境问题,下面这些都是我实际踩过的坑:
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
报错Ghostscript not found | Ghostscript没装或不在PATH | 检查PATH里是否有gs路径,重装后重启终端 |
报错pytesseract.pytesseract.TesseractNotFoundError | Tesseract路径未配置 | 在config里指定tesseract_path到tesseract.exe全路径 |
| 中文识别出来是乱码 | 没装中文语言包 | 重装Tesseract,勾选Chinese (Simplified),重新下载tessdata |
| 处理完成后输出文件比原来大好几倍 | 优化参数没设 | 把optimize调到1或2,开启clean可减轻体积膨胀 |
最搞的是第一次跑的时候,75页的PDF出来一个400多MB的文件,把我吓一跳。后来查了文档才知道,OCRmyPDF默认会把扫描页重新编码为无压缩的TIFF,文件自然膨胀得离谱。开了optimize=1之后,体积立刻回到正常水平,甚至比原文件还小。
4.2 处理过程中的疑难杂症
处理过程中最常见的报错,我总结了三个典型场景。
第一个,PriorOcrFoundError。这个在上面代码里已经处理过,但很多人不知道这个异常的存在,导致处理一批文件时中途抛错就停了。官网说明里明确写了这个异常场景,捕获后跳过即可,不是真正的失败。
第二个,纯图片PDF处理时内存爆掉。如果你一次性丢进去一个300页的大文件,并且把jobs设到8,16GB内存也很容易被吃满。解决办法是把jobs降到2,或者在批处理脚本里把超过100页的文件拆分成多个临时小PDF,分别处理后再合并。PyMuPDF提供了Document.insert_pdf方法,分治策略非常适合大文件。
第三个,扫描质量太差导致识别率感人。这个不是工具问题,是源文件本身太差。建议预处理,用clean_final配合deskew能在一定程度上修复,但如果是模糊到根本看不清的那种,再强的OCR也只能靠猜。更靠谱的思路是扫描时用300dpi以上,识别率会大幅提升。
4.3 批量处理中的隐藏坑
批量处理多份文件时,要特别注意同名文件覆盖问题。我的input目录里曾经出现过扫描件(1).pdf和扫描件(2).pdf这种微信传输后自动改名的文件,输出时如果命名逻辑没处理,后者会直接覆盖前者。我在脚本里有段名字过滤逻辑,把所有非ASCII字符替换成下划线,再确保输出目录里文件名唯一:
from pathlib import Path import re def safe_output_name(filepath: Path) -> str: raw = filepath.stem cleaned = re.sub(r'[^\w\u4e00-\u9fff]+', '_', raw) if len(cleaned) > 50: cleaned = cleaned[:50] final_name = f"{cleaned}.pdf" return final_name另外日志里一定要记录每个文件的处理状态。45份文件跑了27分钟,人不可能一直盯着屏幕,跑完后看日志和failed.txt就能精准知道哪几个文件出了问题,针对性修复重跑即可。
5. 补充工具:WPS批量转图公式的联动玩法
5.1 WPS能做什么
这个工具v1.0发布后,有朋友问我"手里的素材全是图片,怎么快速变成一个多页PDF然后送给工具处理"。这时就要用到WPS的批量转图公式了。步骤非常简单:
在WPS里新建一个空白文档,点击"插入→图片",选中所有需要转换的图片文件,一次性插入。WPS会自动把图片按文件名顺序排列,每张图片单独占一页。然后另存为PDF,一个多页PDF就生成了。这活用公式逻辑来理解就是:把图片文件当成一个个单元格值,WPS的插入功能就是按顺序填充这些值。
这个功能其实胜在"顺手",不需要额外安装软件,也不用写代码。对于临时把几十张照片扫进PDF的场景,非常够用。生成的PDF虽然没有文字层,但可以直接丢给我们上面做的批量转双层PDF工具,自动完成OCR嵌入。两者配合,WPS负责"把图变成PDF",工具负责"把PDF变成能搜索的双层PDF",各干各的活。
5.2 WPS宏命令实现图片名批量生成
如果你的图片文件特别多、而且文件名有规律,还可以借用一个Excel公式技巧来批量生成文件名序列。比如你的图片命名依次是scan_001.jpg到scan_045.jpg,在Excel里任意单元格输入:
="scan_"&TEXT(ROW(A1),"000")&".jpg"下拉填充45行,就能得到全部文件名。再用这个序列配合WPS的=HYPERLINK()公式或者VBA代码,就能自动插入对应图片。原理很简单,就是用Excel的文本公式批量生成文件名,再把生成的列表复制出来配合脚本或宏使用。
这套公式我是在整理一批老照片时琢磨出来的。当时有几百张图片需要按顺序合并,靠手动输文件名不得累死。有了公式生成文件名列表,再配合一个十行左右的VBA宏,把列表逐行插入WPS文档,全程自动化,省了不少功夫。
6. 工具v1.0的局限与后续扩展思路
当前版本v1.0解决了70%的需求,但还有几个明显的短板。第一,Tesseract对复杂版面的识别效果一般,特别是多栏排版、密集表格,偶尔会有文字错位。第二,批量合并图片时,如果图片本身方向混乱,rotate_pages虽然能自动转正,但不保证100%准确。第三,对于超大PDF(1000页+),全流程耗时较长,虽然能跑完,但效率有待优化,v2.0准备引入分页并行处理来提速。
后续扩展我目前想好了三个方向。把PaddleOCR作为可切换的OCR引擎,用它的版面分析模型来处理多栏、表格场景,识别率和结构还原度都会上一个大台阶。再加一个Web UI界面,让不懂命令行的同事也能通过浏览器拖文件进来直接批量转换。在输出端增加目录书签生成,对扫描版书籍尤其有用,方便按章节跳转。
这套工具目前已经稳定跑了好几个月,我自己的文件归档库、办公室同事的合同扫描件,都在用它的输出结果。如果有朋友需要批量转双层PDF,建议直接按我上面的步骤抄作业,环境装好、参数一配,把文件丢进去等结果就行。遇到报错也别慌,日志里都有明确的线索,按排查表逐项对照基本都能解决。
最后再分享一个我个人的习惯:跑完批处理之后,不要急着删源文件,先抽查三五份输出文件,确认文字层正常、页面顺序正确,再清理中间文件。批量工具做得再顺手,复核这个动作永远不能省,尤其处理的是重要合同、档案这类不容有失的材料。
本文还有配套的精品资源,点击获取