从图片PDF到结构化文本:基于Python+Dify的批量OCR自动化解决方案

在日常工作中,我们经常会遇到大量图片格式的PDF文件(如扫描件、截图生成的PDF),这类文件无法直接复制文本,手动逐页OCR识别效率极低。本文将介绍一套基于Python+Dify的全自动化解决方案,实现从PDF批量转图片、图片上传OCR识别到文本合并的完整流程,适用于文档数字化、数据提取等场景。

一、方案整体架构

本方案围绕"批量处理、自动化执行、精准识别"三大核心需求设计,整体架构分为4个关键环节,全程无需人工干预:

graph TD A[PDF文件目录] --> B[Python批量读取PDF] B --> C[PDF转PNG图片(按页拆分)] C --> D[图片上传Dify平台] D --> E[Dify OCR工作流识别文本] E --> F[按PDF原顺序合并文本] F --> G[生成同名结构化文本文件]

核心技术栈

  • 后端语言:Python 3.8+(跨平台兼容,生态丰富)
  • PDF转图片:pdf2image(封装Poppler引擎,转换精度高)
  • 图像处理:Pillow(图片格式标准化)
  • API调用:requests(与Dify平台交互)
  • OCR识别:Dify工作流(可视化配置,支持第三方OCR工具集成)

二、前置准备工作

在开始实现前,需完成环境配置和平台准备,确保全流程顺畅运行。

1. 开发环境配置

(1)Python依赖安装
bash 复制代码
# 核心依赖库
pip install pdf2image pillow requests glob3 pathlib
(2)Poppler引擎安装(PDF转图片核心依赖)

2. Dify平台配置

Dify作为低代码平台,提供了便捷的工作流配置能力,无需手动开发OCR算法:

  1. 登录Dify平台(https://dify.ai/),创建「工作流」
  2. 添加「图片上传」输入节点,字段名设为photo(类型选择「图片」,允许PNG格式)
  3. 集成OCR识别节点(支持内置OCR或第三方工具如阿里云OCR、百度OCR、谷歌LLM模型)
  4. 配置输出节点,将识别结果映射为text字段
  5. 发布工作流,获取「工作流ID」和「API密钥」(个人设置→API密钥)

三、核心功能实现

下面按流程拆解核心功能代码,每个模块均提供完整实现和关键注释。

1. 工具函数:批量读取PDF文件

实现指定目录下所有PDF文件的自动扫描和排序,确保处理顺序可预期:

python 复制代码
import glob
import os
from typing import List

def get_all_pdf_in_dir(pdf_dir: str) -> List[str]:
    """
    获取指定目录下所有PDF文件的绝对路径(按文件名排序)
    """
    if not os.path.exists(pdf_dir):
        raise FileNotFoundError(f"PDF目录不存在:{pdf_dir}")
    
    # 匹配所有.pdf文件(不区分大小写)
    pdf_pattern = os.path.join(pdf_dir, "*.pdf")
    pdf_files = glob.glob(pdf_pattern, recursive=False)
    
    # 按文件名排序,确保处理顺序一致
    pdf_files.sort()
    
    if not pdf_files:
        print(f"警告:目录 {pdf_dir} 中未找到PDF文件")
        return []
    
    print(f"成功找到 {len(pdf_files)} 个PDF文件:")
    for idx, pdf_file in enumerate(pdf_files, start=1):
        print(f"  {idx}. {os.path.basename(pdf_file)}")
    return pdf_files

2. PDF转PNG:按页拆分并独立存储

为每个PDF创建独立目录存储图片,避免文件混淆,同时保证页码顺序:

python 复制代码
from pdf2image import convert_from_path
from pathlib import Path

def pdf_to_png(
    pdf_path: str,
    root_image_dir: str = "pdf_images",
    dpi: int = 300,
    fmt: str = "png"
) -> List[str]:
    """
    PDF按页转PNG,每个PDF生成独立目录,返回图片路径列表(按页码排序)
    """
    pdf_filename = os.path.splitext(os.path.basename(pdf_path))[0]
    pdf_image_dir = os.path.join(root_image_dir, pdf_filename)
    
    # 创建独立目录(支持多级目录自动创建)
    Path(pdf_image_dir).mkdir(parents=True, exist_ok=True)
    print(f"  图片输出目录:{os.path.abspath(pdf_image_dir)}")
    
    # Mac/Linux可自动识别Poppler,Windows需指定路径
    poppler_path = "/opt/homebrew/Cellar/poppler/25.12.0/bin" if os.name != "nt" else None
    if os.name == "nt" and not poppler_path:
        raise ValueError("Windows系统需指定Poppler的bin目录路径")
    
    try:
        # 按页转换PDF为图片(保持原顺序)
        images = convert_from_path(
            pdf_path=pdf_path,
            dpi=dpi,  # 分辨率越高识别越准,建议≥200
            thread_count=4,  # 多线程提速
            fmt=fmt,
            grayscale=False,
            poppler_path=poppler_path
        )
        
        image_paths = []
        for page_num, image in enumerate(images, start=1):
            output_filename = f"{pdf_filename}_page_{page_num:02d}.{fmt}"
            output_path = os.path.join(pdf_image_dir, output_filename)
            image.save(output_path, fmt.upper())
            image_paths.append(output_path)
        
        print(f"  PDF转PNG完成:共生成 {len(image_paths)} 张图片")
        return image_paths
    except Exception as e:
        print(f"  PDF转PNG失败:{str(e)}")
        raise

3. Dify交互:图片上传与OCR工作流调用

基于Dify API实现图片上传和工作流调用,处理网络异常和重试逻辑:

python 复制代码
import requests
import time
from typing import Optional

# Dify全局配置(需替换为实际信息)
DIFY_API_KEY = "app-xxxxxxxx"
DIFY_API_BASE_URL = "https://api.dify.ai/v1"
DIFY_USER = "difyuser"
RETRY_TIMES = 3
RETRY_DELAY = 2

def upload_image_to_dify(image_path: str) -> Optional[str]:
    """上传图片到Dify,返回文件ID"""
    upload_url = f"{DIFY_API_BASE_URL}/files/upload"
    headers = {"Authorization": f"Bearer {DIFY_API_KEY}"}
    
    try:
        with open(image_path, 'rb') as file:
            files = {'file': (os.path.basename(image_path), file, 'image/png')}
            data = {"user": DIFY_USER, "type": "IMAGE"}
            
            response = requests.post(upload_url, headers=headers, files=files, data=data)
            if response.status_code == 201:
                file_id = response.json().get("id")
                print(f"    图片上传成功,file_id:{file_id[:10]}...")
                return file_id
            else:
                print(f"    图片上传失败,状态码:{response.status_code},响应:{response.text}")
                return None
    except Exception as e:
        print(f"    图片上传异常:{str(e)}")
        return None

def run_ocr_workflow(file_id: str) -> Optional[str]:
    """调用Dify OCR工作流,返回识别文本"""
    workflow_url = f"{DIFY_API_BASE_URL}/workflows/run"
    headers = {
        "Authorization": f"Bearer {DIFY_API_KEY}",
        "Content-Type": "application/json"
    }
    
    data = {
        "inputs": {
            "photo": {
                "transfer_method": "local_file",
                "upload_file_id": file_id,
                "type": "image"
            }
        },
        "response_mode": "blocking",
        "user": DIFY_USER
    }
    
    try:
        response = requests.post(workflow_url, headers=headers, json=data)
        if response.status_code == 200:
            result = response.json()
            # 解析Dify响应结构(data→outputs→text)
            if "data" in result and result["data"]["status"] == "succeeded":
                outputs = result["data"].get("outputs", {})
                ocr_text = outputs.get("text", "").strip()
                if ocr_text:
                    print(f"    OCR识别成功,文本长度:{len(ocr_text)}字")
                    return ocr_text
            print(f"    OCR结果解析失败:{result}")
            return None
        else:
            print(f"    工作流调用失败,状态码:{response.status_code},响应:{response.text}")
            return None
    except Exception as e:
        print(f"    工作流调用异常:{str(e)}")
        return None

4. 文本合并:按原顺序生成结构化文件

将每页OCR结果按PDF原页码顺序合并,生成与原PDF同名的文本文件:

python 复制代码
def batch_ocr_and_merge(
    image_paths: List[str],
    pdf_path: str,
    output_dir: str = "ocr_results",
    output_suffix: str = "txt"
) -> None:
    """批量OCR识别并按页码顺序合并文本"""
    total_images = len(image_paths)
    merged_text = []
    
    # 创建文本输出目录
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    pdf_filename = os.path.splitext(os.path.basename(pdf_path))[0]
    output_text_path = os.path.join(output_dir, f"{pdf_filename}.{output_suffix}")
    
    # 跳过已处理文件,避免重复劳动
    if os.path.exists(output_text_path):
        print(f"  跳过已处理文件:{os.path.basename(output_text_path)}")
        return
    
    print(f"  开始OCR处理(共{total_images}张图片)...")
    for idx, image_path in enumerate(image_paths, start=1):
        print(f"\n    处理第{idx}/{total_images}张:{os.path.basename(image_path)}")
        
        # 图片上传(带重试机制)
        file_id = None
        for retry in range(RETRY_TIMES):
            file_id = upload_image_to_dify(image_path)
            if file_id:
                break
            print(f"    第{retry+1}次上传重试...")
            time.sleep(RETRY_DELAY)
        
        if not file_id:
            merged_text.extend([f"===== 第{idx}页(图片上传失败) =====", "\n"])
            continue
        
        # OCR识别(带重试机制)
        ocr_text = None
        for retry in range(RETRY_TIMES):
            ocr_text = run_ocr_workflow(file_id)
            if ocr_text:
                break
            print(f"    第{retry+1}次OCR重试...")
            time.sleep(RETRY_DELAY)
        
        if ocr_text:
            merged_text.extend([f"===== 第{idx}页 =====", ocr_text, "\n"])
        else:
            merged_text.extend([f"===== 第{idx}页(OCR识别失败) =====", "\n"])
    
    # 保存合并文本(UTF-8编码避免中文乱码)
    final_text = "\n".join(merged_text)
    with open(output_text_path, "w", encoding="utf-8") as f:
        f.write(final_text)
    
    print(f"\n  文本合并完成!保存路径:{os.path.abspath(output_text_path)}")
    print(f"  合并后总文本长度:{len(final_text)}字")

5. 主函数:串联全流程执行

python 复制代码
def main():
    # 配置参数(可根据实际需求调整)
    PDF_DIR = "./pdf_files"  # PDF文件存放目录
    ROOT_IMAGE_DIR = "pdf_images"  # 图片存储根目录
    OCR_OUTPUT_DIR = "ocr_results"  # 文本输出目录
    OUTPUT_SUFFIX = "txt"  # 输出文件后缀(支持txt/md)
    PDF_DPI = 300  # 图片分辨率(建议200-300dpi)
    
    try:
        # 步骤1:获取所有PDF文件
        pdf_files = get_all_pdf_in_dir(PDF_DIR)
        if not pdf_files:
            return
        
        # 步骤2:批量处理每个PDF
        total_pdfs = len(pdf_files)
        for pdf_idx, pdf_path in enumerate(pdf_files, start=1):
            print(f"\n=== 处理第{pdf_idx}/{total_pdfs}个PDF:{os.path.basename(pdf_path)} ===")
            try:
                # 子步骤1:PDF转PNG
                image_paths = pdf_to_png(pdf_path, ROOT_IMAGE_DIR, PDF_DPI)
                # 子步骤2:OCR识别+文本合并
                batch_ocr_and_merge(image_paths, pdf_path, OCR_OUTPUT_DIR, OUTPUT_SUFFIX)
                print(f"=== 第{pdf_idx}/{total_pdfs}个PDF处理完成! ===\n" + "-"*50)
            except Exception as e:
                print(f"=== 第{pdf_idx}/{total_pdfs}个PDF处理失败:{str(e)} ===\n" + "-"*50)
                continue
        
        print(f"\n所有PDF文件处理完毕!")
        print(f"📁 图片文件存放于:{os.path.abspath(ROOT_IMAGE_DIR)}")
        print(f"📄 OCR文本文件存放于:{os.path.abspath(OCR_OUTPUT_DIR)}")
    except Exception as e:
        print(f"\n=== 批量处理流程异常终止:{str(e)} ===")

if __name__ == "__main__":
    main()

四、关键问题解决方案

在实际部署和运行过程中,可能会遇到各类问题,以下是常见问题的解决方案:

1. PDF转PNG失败:Poppler未找到

  • 问题原因:系统未识别到Poppler引擎
  • 解决方案:
    • Windows:手动指定Poppler路径,在pdf_to_png函数中添加poppler_path=r"C:\poppler\bin"
    • Mac/Linux:重新安装Poppler,确保环境变量配置正确

2. Dify API调用400错误

  • 错误类型1:photo in input form must be a file
    • 解决方案:确保run_ocr_workflow函数中photo字段是对象格式(而非列表),即"photo": {}而非"photo": [{}]
  • 错误类型2:Detected file type does not match
    • 解决方案:上传图片时MIME类型设为image/png,Dify工作流输入字段类型设为「图片」,参数type统一为image

3. OCR识别结果为空或乱码

  • 解决方案:
    • 提高PDF转图片的DPI(建议300dpi),确保图片清晰度
    • 检查Dify工作流中OCR节点配置,选择支持中文识别的模型
    • 合并文本时使用UTF-8编码,避免中文乱码

4. 批量处理速度慢

  • 解决方案:
    • 增加thread_count参数(如CPU为8核可设为6-7)
    • 优化Dify工作流响应模式(使用blocking同步模式确保顺序)
    • 对超大PDF文件可分批次处理,避免内存占用过高

五、方案优势与扩展方向

方案优势

  1. 全自动化:从PDF读取到文本生成全程无需人工干预,支持批量处理
  2. 顺序保障:严格保持原PDF页码顺序,文本合并时添加分页标识,便于核对
  3. 高兼容性:支持Windows/Mac/Linux跨平台运行,适配不同格式的图片PDF
  4. 健壮性强:包含重试机制、异常捕获、已处理文件跳过等功能,稳定性高
  5. 低代码门槛:借助Dify平台快速配置OCR工作流,无需手动开发复杂识别算法

扩展方向

  1. 支持加密PDF:集成PyPDF2库解析加密PDF文件
  2. 文本格式优化:添加多余空行去除、表格结构还原等后处理逻辑
  3. 多线程并发:使用concurrent.futures实现多PDF并行处理,提升效率
  4. 结果校验:集成人工审核环节,标记识别失败或模糊的页面
  5. 云原生部署:打包为Docker镜像,部署到云服务器或K8s集群,支持定时任务

六、总结

本文提出的基于Python+Dify的批量OCR解决方案,完美解决了图片PDF的文本提取难题。通过将PDF转图片、Dify OCR识别、文本合并等环节自动化串联,大幅提升了文档处理效率,适用于企业办公、教育培训、政务处理等多个场景。

该方案兼顾了易用性和扩展性,既可以直接部署使用,也可以根据实际需求进行二次开发。随着AI识别技术的不断进步,结合Dify平台的灵活配置能力,后续还可以进一步优化识别精度和处理效率,为文档数字化转型提供更强大的支持。

相关推荐
呆萌很1 小时前
文件读写和异常处理练习题
python
杨超越luckly1 小时前
HTML应用指南:利用POST请求获取全国极氪门店位置信息
python·arcgis·html·数据可视化·门店数据
青春不败 177-3266-05201 小时前
最新AI-Python机器学习与深度学习实践技术应用
人工智能·python·深度学习·机器学习·卷积神经网络·语义分割·自编码
三维鱼1 小时前
Python组合数据类型----5.2列表( 5.2.4 )
python
零日失眠者1 小时前
【系统监控系列】005:CPU温度监控脚本
后端·python
远瞻。1 小时前
【环境配置】快速转移conda上的python环境
开发语言·python·conda
上班职业摸鱼人1 小时前
Python迭代器与生成器深度解析:吃透yield关键字,写出高效内存代码
python
棒棒的皮皮1 小时前
【OpenCV】Python图像处理之图像加法运算
图像处理·python·opencv·计算机视觉
熊文豪1 小时前
使用Python快速开发一个MCP服务器
服务器·开发语言·python·mcp