Python实战:三步生成可搜索双层PDF文档
一、准备工作与环境搭建
在开始编写代码之前,必须先配置好运行环境。双层PDF的生成依赖于OCR识别引擎和图像处理库,缺少任何一个都会导致程序报错。请严格按照以下步骤操作。
1. 安装 Tesseract-OCR 引擎
Tesseract 是目前开源界最精准的OCR引擎,用于识别图片中的文字。
- Windows 用户:访问 https://github.com/UB-Mannheim/tesseract/wiki 下载最新的 Windows 安装包(例如 tesseract-ocr-w64-setup-5.x.x.exe)。安装时,务必勾选 "Additional language data",并在列表中找到 Chinese (Simplified) 和 English 进行勾选,否则无法识别中文。安装路径建议不要包含空格或中文字符,默认安装路径为 C:\Program Files\Tesseract-OCR。
- macOS 用户:打开终端,执行以下命令:
brew install tesseract tesseract-lang
- Linux 用户 (Ubuntu/Debian):打开终端,执行以下命令:
sudo apt-get update
sudo apt-get install tesseract-ocr tesseract-ocr-chi-sim
2. 安装 Poppler 依赖库
我们使用的 Python 库底层依赖 Poppler 来处理 PDF 转图片的操作,这是新手最容易忽略的步骤。
- Windows 用户:访问 https://github.com/oschwartz10612/poppler-windows/releases/ 下载最新的 Release 压缩包(例如 poppler-23.xx.0-0.zip)。解压后,将 bin 文件夹的路径(如 C:\poppler-23.01\Library\bin)添加到系统的 Path 环境变量中。
- macOS 用户:执行命令
brew install poppler。 - Linux 用户:执行命令
sudo apt-get install poppler-utils。
3. 安装 Python 依赖库
确保你的 Python 版本在 3.7 以上。在项目目录下打开终端,执行以下命令安装所需库:
pip install pytesseract pdf2image reportlab Pillow
- pytesseract:Tesseract 的 Python 封装包。
- pdf2image:将 PDF 页面转换为 PIL 图片对象。
- reportlab:用于生成新的 PDF 文件,支持绘制图片和文字层。
- Pillow:Python 图像处理标准库。
二、核心实现逻辑解析
所谓的“双层 PDF”,本质上是在原始图片层之上,覆盖了一层透明的文字层。这一层的文字坐标经过精确计算,与图片中的文字位置重合,但颜色设置为透明或白色(通常设置为白色即可,因为背景是图片),从而实现“选中复制”功能。
我们的操作流程如下:
- 输入:读取一个纯图片扫描版 PDF。
- 分页:利用 pdf2image 将 PDF 的每一页转换为图片。
- OCR识别:利用 pytesseract 识别图片中的文字,并获取每个文字块的坐标信息。
- 绘制:利用 reportlab 创建新 PDF 页面,先画入原始图片作为背景,再根据 OCR 返回的坐标,在对应位置画出文字。
- 输出:保存生成的新 PDF。
三、完整代码实现
新建一个文件名为 make_pdf_searchable.py,将以下代码完整复制进去。代码中已包含 Windows 环境下 Tesseract 路径的自动配置逻辑,Mac/Linux 用户会自动忽略。
```python import os import platform from pdf2image import convert_from_path from pytesseract import image_to_data, Output from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter def create_searchable_pdf(input_pdf_path, output_pdf_path, lang='chi_sim+eng'): """ 将扫描版PDF转换为可搜索的双层PDF :param input_pdf_path: 输入的PDF文件路径 :param output_pdf_path: 输出的PDF文件路径 :param lang: OCR语言包,默认中英文混合 """ 1. 配置 Tesseract 路径 (仅 Windows 需要) current_os = platform.system() if current_os == 'Windows': 请根据实际情况修改此路径,如果你安装时改了位置 tesseract_path = r'C:\Program Files\Tesseract-OCR\tesseract.exe' if os.path.exists(tesseract_path): os.environ['TESSDATA_PREFIX'] = r'C:\Program Files\Tesseract-OCR\tessdata' else: print(f"警告:未找到默认Tesseract路径,请确保已配置环境变量或手动修改代码中的路径。") print(f"正在处理文件: {input_pdf_path}") try: 2. 将 PDF 转换为图片列表 dpi=300 能保证较好的识别清晰度,但会增加处理时间 print("正在将PDF转换为图片...") images = convert_from_path(input_pdf_path, dpi=300) 创建一个 PDF 画布对象 这里使用 reportlab 的 canvas,单位是 point (1 inch = 72 points) 由于 convert_from_path 的 dpi 是 300,所以图片像素大小需要转换为 PDF 点单位 或者更简单的方法:让 PDF 页面大小等于图片大小 (在 72 dpi 下) 为了保证清晰度,我们通常保持图片原尺寸,通过画布大小适配 c = canvas.Canvas(output_pdf_path) total_pages = len(images) for i, image in enumerate(images): print(f"正在处理第 {i+1}/{total_pages} 页...") img_width, img_height = image.size 将图片尺寸从像素转换为 PDF 点 (假设输出PDF为 72 DPI 的逻辑尺寸,但图片本身是高清的) 实际上,为了让 PDF 里的图片清晰,我们直接用像素作为点单位,或者做比例换算 这里我们简化处理:将 PDF 页面设置为图片的像素尺寸(这会生成一个很大的 PDF 页面尺寸) 更好的做法是:将图片画在 A4 或 Letter 纸上,或者根据图片比例适配 本示例采用:直接适配图片尺寸,保证 1:1 还原 c.setPageSize((img_width, img_height)) 2.1 绘制底层图片 reportlab 需要文件路径来绘制图片,所以先临时保存一下 temp_img_name = f"temp_page_{i}.jpg" image.save(temp_img_name, "JPEG") c.drawImage(temp_img_name, 0, 0, img_width, img_height) 2.2 进行 OCR 识别并获取坐标 image_to_data 返回每个字符/单词的详细信息 data = image_to_data(image, output_type=Output.DICT, lang=lang) n_boxes = len(data['text']) for j in range(n_boxes): text = data['text'][j] conf = int(data['conf'][j]) 过滤掉置信度太低或为空的文本 if conf > 60 and text.strip(): x, y, w, h = data['left'][j], data['top'][j], data['width'][j], data['height'][j] 关键坐标转换: Tesseract 的坐标原点 (0,0) 在左上角,y 轴向下 Reportlab 的坐标原点 (0,0) 在左下角,y 轴向上 所以 reportlab 的 y 坐标 = 图片总高度 - tesseract 的 y 坐标 - 文字高度 注意:这里需要微调,因为 drawString 的 y 是文字基线,不是左下角 简单的转换公式: rl_y = img_height - y - h 设置字体属性 尝试根据 OCR 识别的高度动态调整字体大小,或者使用固定大小 这里使用固定大小 9 或 10,通常能较好匹配 font_size = max(9, int(h 0.8)) 设置中文字体,Windows 下通常用 SimSun,Mac 用 STSong 如果报错找不到字体,请确保系统中有对应字体或指定绝对路径 try: c.setFont("SimSun", font_size) except: 回退字体 c.setFont("Helvetica", font_size) 设置文字颜色为白色(透明效果需要复杂的 PDF 结构,白色覆盖在图片上最实用) 如果希望文字不可见但可选中,设置为白色即可 c.setFillColorRGB(1, 1, 1) 绘制文字 c.drawString(x, rl_y, text) 删除临时图片 if os.path.exists(temp_img_name): os.remove(temp_img_name) 完成本页,进入下一页 c.showPage() 保存 PDF c.save() print(f"成功!已生成可搜索 PDF: {output_pdf_path}") except Exception as e: print(f"处理过程中发生错误: {e}") if __name__ == "__main__": 输入文件名(请确保当前目录下有此文件,或修改为绝对路径) input_file = "scan_sample.pdf" output_file = "searchable_output.pdf" 为了演示,如果没有文件,创建一个假的提示 if not os.path.exists(input_file): print(f"错误:未找到输入文件 '{input_file}'") print("请将你需要处理的 PDF 文件重命名为 'scan_sample.pdf' 并放在本脚本同级目录下。") else: create_searchable_pdf(input_file, output_file) ```四、代码关键细节详解

为了确保你能顺利运行并理解原理,以下对代码中容易出错的三个核心点进行详细拆解。
1. 坐标系的转换
这是代码中最容易出错的地方。pytesseract 返回的坐标是基于图像的,原点在左上角;而 reportlab 的 Canvas 原点在左下角。
在代码中,我们使用了公式:
rl_y = img_height - y - h
其中 img_height 是图片总高度,y 是 OCR 识别到的文字块顶部距离,h 是文字块高度。如果不减去 h,文字会被绘制在基线以下,导致在生成的 PDF 中位置偏移,选中框会对不上图片里的字。
2. 字体设置与回退机制
在代码中,我们尝试调用 c.setFont("SimSun", font_size)。这是 Windows 的中文字体名。如果你的系统是 Linux 或 macOS,且没有安装该字体,程序会抛出异常。
代码中加入了 try...except 块。如果设置中文字体失败,会自动回退到 c.setFont("Helvetica", font_size)。虽然 Helvetica 不支持中文,但这能防止程序直接崩溃,让你能看到错误提示。对于生产环境,建议使用 pdfmetrics.registerFont 注册具体的 TTF 字体文件路径。
3. DPI 与 清晰度
在 convert_from_path 函数中,我们设置了 dpi=300。DPI(每英寸点数)越高,转换出的图片像素越多,OCR 识别率越高,生成的 PDF 体积也越大。
- 如果处理普通文档,300 DPI 是最佳平衡点。
- 如果处理速度太慢,可以尝试降到 200。
- 如果识别效果差,必须提升到 400 或 600。
五、实操验证步骤
代码和文件都准备好后,请按照以下步骤验证成果:
- 准备素材:找一份扫描好的 PDF 文件(内容最好是中文或中英混合),将其重命名为 scan_sample.pdf,放在 Python 脚本旁边。
- 运行脚本:在终端执行
python make_pdf_searchable.py。 - 观察日志:终端应显示“正在处理文件...”、“正在处理第 1/x 页...”等进度信息。
- 打开结果:运行结束后,目录下会生成 searchable_output.pdf。使用 Adobe Acrobat Reader 或浏览器打开它。
- 功能测试:
- 视觉测试:PDF 看起来应该和原扫描件一模一样,没有任何区别。
- 搜索测试:按下 Ctrl+F,输入图片中存在的一个词(例如“合同”或“Total”),看能否跳转到对应位置。
- 复制测试:鼠标拖拽选中一段文字,复制粘贴到记事本中,检查内容是否准确。
如果复制出来的文字有错别字,那是 Tesseract 识别准确率的问题,可以通过下载更精准的语言包或提高扫描图片的 DPI 来解决;如果文字选不中或位置偏移,请检查代码中的坐标转换逻辑。