目前DeepSeek-OCR只处理 config.py 中指定的单个 PDF 文件 (INPUT_PATH)。

要实现批量处理 /home/neu/deepseek_ocr/zhuanli_pdf_input 文件夹下的所有 PDF 文件,我们需要对 run_dpsk_ocr_pdf.py 文件的 if __name__ == "__main__": 部分进行修改和重构。


批量处理 DeepSeek-OCR 脚本改造

为了方便,我们直接修改原脚本的 if __name__ == "__main__": 块。

核心修改点

  1. 修改配置 (config.py):将 INPUT_PATH 更改为 INPUT_DIR

  2. 主函数封装:将原脚本中的所有处理逻辑封装到一个函数 process_single_pdf 中。

  3. 循环逻辑:在 if __name__ == "__main__": 块中,使用 glob.glob 遍历输入目录下的所有 .pdf 文件,并对每个文件调用新封装的函数。


💡 步骤一:更新 config.py 文件

确保 config.py 文件中,路径配置如下(将单文件路径改为文件夹路径):

# config.py (请这样修改)
# 找到SKIP_REPEAT参数
SKIP_REPEAT = False #没修改会导致丢弃,没有生成结束标志 <|end of sentence|>,产生空白文件
#找到文件中定义输入路径的那一行INPUT_PATH。
INPUT_DIR = '/home/neu/deepseek_ocr/zhuanli_pdf_input'  # <-- ❗ 重点:将变量名改为 INPUT_DIR,并指向文件夹
OUTPUT_PATH = '/home/neu/deepseek_ocr/zhuanli_pdf_output'
# ...

💡 步骤二:修改 run_dpsk_ocr_pdf.py

将代码的 末尾部分(即从 if __name__ == "__main__": 开始)替换为以下代码。这将把单文件处理逻辑封装,并添加批量循环。

# run_dpsk_ocr_pdf.py 文件的顶部,与其他导入语句一起,添加一行来导入 glob 模块:
import os
import fitz
import img2pdf
import io
import re
import torch
import glob # 👈 在这里或顶部添加,使用 glob.glob 函数来批量查找 PDF 文件
from tqdm import tqdm
from functools import partial
from concurrent.futures import ThreadPoolExecutor


# run_dpsk_ocr_pdf.py (修改后的末尾部分)
# ----------------------------------------------------------------------
# (前面的函数和类定义保持不变)
# ----------------------------------------------------------------------

# ⚠️ 确保从 config 中导入的是 INPUT_DIR
from config import MODEL_PATH, INPUT_DIR, OUTPUT_PATH, PROMPT, SKIP_REPEAT, MAX_CONCURRENCY, NUM_WORKERS, CROP_MODE
# ... (其他导入保持不变)

# ... (llm、sampling_params、Colors、pdf_to_images_high_quality 等函数保持不变)

# ----------------------------------------------------------------------
# 修改 process_single_image 函数定义
# 将 run_dpsk_ocr_pdf.py 中 process_single_image 的定义修改为接收 prompt 参数:
# ----------------------------------------------------------------------
def process_single_image(image, prompt): # 👈 增加 prompt 参数
    """single image"""
    prompt_in = prompt # 👈 现在可以使用这个参数
    cache_item = {
        "prompt": prompt_in,
        "multi_modal_data": {"image": DeepseekOCRProcessor().tokenize_with_images(images = [image], bos=True, eos=True, cropping=CROP_MODE)},
    }
    return cache_item

# ----------------------------------------------------------------------
# 封装单文件处理逻辑
# ----------------------------------------------------------------------
def process_single_pdf(pdf_path: str, output_dir: str):
    """处理单个 PDF 文件并输出 Markdown"""
    
    # 获取文件名和基础路径信息
    filename = os.path.basename(pdf_path)
    base_name = os.path.splitext(filename)[0]

    # --- 1. PDF 渲染为图像 ---
    print(f'{Colors.RED}PDF: {filename} loading .....{Colors.RESET}')
    images = pdf_to_images_high_quality(pdf_path)

    # --- 2. 图像预处理 (并行) ---
    prompt = PROMPT # 👈 确保这里定义了 prompt

    # 修改 map 的调用方式,使用 lambda 或 functools.partial 传递额外的 prompt 参数
    from functools import partial
    
    # 包装 process_single_image,固定 prompt 参数
    process_func = partial(process_single_image, prompt=prompt) 

    with ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:  
        batch_inputs = list(tqdm(
            executor.map(process_func, images), # 👈 使用包装后的函数
            total=len(images),
            desc=f"[{base_name}] Pre-processing images"
        ))

    # --- 3. VLLM 推理 ---
    print(f'{Colors.GREEN}[{base_name}] Starting vLLM generation...{Colors.RESET}')
    outputs_list = llm.generate(
        batch_inputs,
        sampling_params=sampling_params
    )

    # --- 4. 结果后处理与写入 ---
    
    # 设置输出文件路径
    mmd_det_path = os.path.join(output_dir, f'{base_name}_det.mmd')
    mmd_path = os.path.join(output_dir, f'{base_name}.md') # 💡 建议将后缀改为 .md
    pdf_out_path = os.path.join(output_dir, f'{base_name}_layouts.pdf')

    contents_det = ''
    contents = ''
    draw_images = []
    jdx = 0
    
    # 遍历每个页面的输出结果
    for output, img in zip(outputs_list, images):
        content = output.outputs[0].text

        # 检查和处理 EOS 标志
        if '<|end of sentence|>' in content:
            content = content.replace('<|end of sentence|>', '')
        else:
            if SKIP_REPEAT:
                continue

        page_num = f'\n<--- Page Split --->'
        contents_det += content + f'\n{page_num}\n'

        image_draw = img.copy()
        
        # 处理 grounding 标记
        matches_ref, matches_images, mathes_other = re_match(content)
        result_image = process_image_with_refs(image_draw, matches_ref, jdx)
        draw_images.append(result_image)

        # 替换 grounding 标记为 Markdown 格式
        for idx, a_match_image in enumerate(matches_images):
            # 将检测到的图像块替换为 Markdown 图片链接,路径指向 images 子文件夹
            content = content.replace(a_match_image, f'![](images/{base_name}_{jdx}_{idx}.jpg)\n')
            
        # 移除其他 grounding 标记并进行简单清理
        for idx, a_match_other in enumerate(mathes_other):
            content = content.replace(a_match_other, '').replace('\\coloneqq', ':=').replace('\\eqqcolon', '=:').replace('\n\n\n\n', '\n\n').replace('\n\n\n', '\n\n')

        contents += content + f'\n{page_num}\n'
        jdx += 1
        
    # --- 5. 写入文件 ---
    # 写入带检测框的原始输出 (mmd_det_path)
    with open(mmd_det_path, 'w', encoding='utf-8') as afile:
        afile.write(contents_det)

    # 写入最终 Markdown 结果 (mmd_path)
    with open(mmd_path, 'w', encoding='utf-8') as afile:
        afile.write(contents)
        
    # 写入带布局检测框的 PDF
    pil_to_pdf_img2pdf(draw_images, pdf_out_path)
    
    print(f"{Colors.YELLOW}[{base_name}] ✅ 处理完成。Markdown 结果已保存至: {mmd_path}{Colors.RESET}")
    

# ----------------------------------------------------------------------
# 脚本入口:批量处理逻辑
# ----------------------------------------------------------------------
if __name__ == "__main__":
    
    # 创建输出文件夹及其子文件夹
    os.makedirs(OUTPUT_PATH, exist_ok=True)
    os.makedirs(f'{OUTPUT_PATH}/images', exist_ok=True) # 用于存放切出的图像
    
    print(f"--- DeepSeek-OCR 专利文件批量转换开始 ---")
    print(f"输入目录: {INPUT_DIR}")
    print(f"输出目录: {OUTPUT_PATH}")

    # 查找所有 PDF 文件
    pdf_files = sorted(glob.glob(os.path.join(INPUT_DIR, "*.pdf")))
    
    if not pdf_files:
        print(f"\n{Colors.RED}❌ 错误: 在 '{INPUT_DIR}' 文件夹中未找到任何 .pdf 文件。{Colors.RESET}")
        exit()

    print(f"\n找到 {len(pdf_files)} 个 PDF 文件进行批量处理...")

    # 循环调用单文件处理函数
    for i, pdf_path in enumerate(pdf_files):
        print(f"\n\n=======================================================")
        print(f"       处理第 {i+1}/{len(pdf_files)} 个文件...")
        print(f"=======================================================")
        
        try:
            process_single_pdf(pdf_path, OUTPUT_PATH)
        except Exception as e:
            print(f"{Colors.RED}❌ 严重错误: 处理文件 {os.path.basename(pdf_path)} 时失败。{e}{Colors.RESET}")
            continue

    print("\n--- DeepSeek-OCR 专利文件批量转换完成 ---")

运行说明

  1. 修改配置: 确保 config.py 中的 INPUT_DIROUTPUT_PATH 已正确设置为批量处理文件夹。

  2. 替换代码: 用上面的新代码替换原始 run_dpsk_ocr_pdf.py 文件中的 if __name__ == "__main__": 块及其后续内容。

  3. 运行: 直接运行修改后的脚本。

python run_dpsk_ocr_pdf.py

脚本现在会遍历 zhuanli_pdf_input 文件夹中的所有 PDF 文件,并为每个文件在 zhuanli_pdf_output 文件夹中生成:

  1. 一个 Markdown 文件 (filename.md)。

  2. 一个包含原始模型输出的 .mmd 文件 (filename_det.mmd)。

  3. 一个带有布局检测框的 PDF 文件 (filename_layouts.pdf)。

  4. 所有切出的图像块将统一存放在 zhuanli_pdf_output/images/ 文件夹下。


上面是已经修改好的代码,在这个过程中我遇到了很多报错。

第一个报错错误分析 🧐

报错信息核心内容:

File "/home/neu/deepseek_ocr/DeepSeek-OCR-main/DeepSeek-OCR-master/DeepSeek-OCR-vllm/run_dpsk_ocr_pdf.py", line 261
    with ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:  
                                                                   ^
SyntaxError: invalid non-printable character U+00A0

错误原因:

SyntaxError: invalid non-printable character U+00A0 表明在脚本的 第 261 行(即 with ThreadPoolExecutor(...) 这一行),存在一个无效的、不可打印的字符 U+00A0

这个字符是 Unicode 编码的“不间断空格”(Non-Breaking Space, NBSP)。它看起来像一个普通空格,但实际上是一个不同的字符。当从网页、聊天窗口或某些文档中复制粘贴代码时,很容易引入这种不可见的特殊字符。Python 解释器无法将其识别为合法的空格或代码字符,因此抛出语法错误。

解决方案

需要打开 run_dpsk_ocr_pdf.py 文件,并手动清理这一行和附近的特殊字符。

  1. 定位文件和行号: 打开 /home/neu/deepseek_ocr/DeepSeek-OCR-main/DeepSeek-OCR-master/DeepSeek-OCR-vllm/run_dpsk_ocr_pdf.py 文件的 第 261 行 附近。

  2. 删除并重新输入: 找到这行代码:

with ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:  

请执行以下操作:

  • 删除 :executor: 后面的所有空格(直到行尾)。

  • 重新键入一个标准的空格字符。

清理掉 U+00A0 字符后,代码将能够正确解析并继续执行。


第二个报错错误分析 🛠️

报错信息核心内容:

[rank0]: NameError: name 'glob' is not defined. Did you forget to import 'glob'?

错误原因:

这个错误发生在尝试使用 glob.glob 函数来批量查找 PDF 文件时:

[rank0]:   File "/home/neu/deepseek_ocr/.../run_dpsk_ocr_pdf.py", line 349, in <module>
[rank0]:     pdf_files = sorted(glob.glob(os.path.join(INPUT_DIR, "*.pdf")))

虽然在代码开头可能导入了 os 和其他库,但用于模式匹配和文件查找的 glob 模块没有被明确导入到脚本中。

解决方案

只需要在 run_dpsk_ocr_pdf.py 文件的顶部,与其他导入语句一起,添加一行来导入 glob 模块:

找到文件开头,添加:

import glob # 👈 加上这一行

# 确保它与您的其他导入在一起,例如:
import os
import fitz
import img2pdf
import io
import re
from tqdm import tqdm
import torch
from concurrent.futures import ThreadPoolExecutor
import glob # 👈 在这里或顶部添加

添加 import glob 后,重新运行脚本,这个 NameError 就会消除,代码应该能正常进入 PDF 文件的批量处理循环。


第三个报错错误分析 🔎

遇到的错误是一个常见的 Python NameError,发生在并行预处理阶段:

NameError: name 'prompt' is not defined

这个错误发生在 process_single_pdf 函数内部,具体是在执行 图像预处理 的线程池中:

def process_single_image(image):
    """single image"""
    prompt_in = prompt # ❌ 错误发生在这里
    cache_item = {
        "prompt": prompt_in,
        # ...
    }
    return cache_item

process_single_image 函数在定义时没有接收任何名为 prompt 的参数,并且它在全局或父作用域中也找不到 prompt 变量,因为:

  1. 在原来的脚本中,prompt = PROMPT 是在 if __name__ == "__main__": 块中定义的,是局部于该块的。

  2. 在我们的重构中,process_single_image 及其调用的 prompt 变量被封装进了 process_single_pdf 函数,但 process_single_image 仍然无法访问。

解决方案 

最简单的修复方法是修改 process_single_image 函数,让它接收 prompt 作为一个参数。

需要进行两处修改:

1. 修改 process_single_image 函数定义

run_dpsk_ocr_pdf.pyprocess_single_image 的定义修改为接收 prompt 参数:

def process_single_image(image, prompt): # 👈 增加 prompt 参数
    """single image"""
    prompt_in = prompt # 👈 现在可以使用这个参数
    cache_item = {
        "prompt": prompt_in,
        "multi_modal_data": {"image": DeepseekOCRProcessor().tokenize_with_images(images = [image], bos=True, eos=True, cropping=CROP_MODE)},
    }
    return cache_item

2. 修改 process_single_pdf 函数内的调用

process_single_pdf 函数中,修改 ThreadPoolExecutorexecutor.map 调用,将 prompt 作为额外参数传入:

# process_single_pdf 函数内部的预处理部分:

    # --- 2. 图像预处理 (并行) ---
    prompt = PROMPT # 👈 确保这里定义了 prompt

    # 修改 map 的调用方式,使用 lambda 或 functools.partial 传递额外的 prompt 参数
    from functools import partial
    
    # 包装 process_single_image,固定 prompt 参数
    process_func = partial(process_single_image, prompt=prompt) 

    with ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:  
        batch_inputs = list(tqdm(
            executor.map(process_func, images), # 👈 使用包装后的函数
            total=len(images),
            desc=f"[{base_name}] Pre-processing images"
        ))

确保在 run_dpsk_ocr_pdf.py 文件的开头添加 from functools import partial,并且进行上述两处修改。这将解决 NameError: name 'prompt' is not defined 的问题,并允许脚本继续执行 vLLM 推理部分。


现在脚本已经能够成功加载模型、遍历 PDF 文件、并且启动预处理,但生成出来的md文件和mmd文件都是空白的,这通常意味着模型推理的结果文本为空,或者由于某种后处理逻辑导致内容被丢弃。

核心问题分析SKIP_REPEAT 导致的丢弃

在 process_single_pdf 函数(即原 run_dpsk_ocr_pdf.py 中处理结果的部分)中,有这样一段逻辑:

# run_dpsk_ocr_pdf.py 结果后处理片段:

    # ... (循环开始)
    for output, img in zip(outputs_list, images):
        content = output.outputs[0].text
        
        if '<|end of sentence|>' in content: # repeat no eos
            content = content.replace('<|end of sentence|>', '')
        else:
            if SKIP_REPEAT:
                continue # 👈 关键点:如果内容不包含 EOS 且 SKIP_REPEAT 为 True,则跳过!
            
        page_num = f'\n<--- Page Split --->'
        contents_det += content + f'\n{page_num}\n'
        # ... (后续处理逻辑)

导致空白原因:

模型没有生成结束标志 <|end of sentence|>

DeepSeek-OCR 模型可能在生成文档内容时,由于 max_tokens (8192) 的限制或模型本身的生成特性,没有在输出末尾包含 <|end of sentence|>(通常是 <|eos|>,但此处代码中是 <|end of sentence|>)。

  • 如果模型输出的 content 不包含 <|end of sentence|>

  • 同时,如果配置变量 SKIP_REPEAT 被设置为 True

那么 continue 语句将执行,导致当前页面的所有内容 (content) 被丢弃contents_detcontents 都没有新增该页内容。如果所有页面都出现这种情况,最终文件就会是空白的。

解决方案:

  1. 修改 config.py:将 SKIP_REPEAT 设置为 False

    # config.py
    SKIP_REPEAT = False 
    
  2. 运行脚本:重新运行 run_dpsk_ocr_pdf.py

如果禁用 SKIP_REPEAT 后文件不再空白,那就证实了是模型没有生成 EOS 标记导致内容被丢弃。应该保持 SKIP_REPEAT = False,或者修改代码逻辑,让它更健壮地处理 EOS 缺失的情况。


如此,就可以得到一个可以批量 DeepSeek-OCR PDF 转换脚本。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐