DeepSeek-OCR批量处理pdf
将。
目前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__": 块。
核心修改点
修改配置 (config.py):将
INPUT_PATH更改为INPUT_DIR。主函数封装:将原脚本中的所有处理逻辑封装到一个函数
process_single_pdf中。循环逻辑:在
if __name__ == "__main__":块中,使用glob.glob遍历输入目录下的所有
💡 步骤一:更新 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'\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 专利文件批量转换完成 ---")
运行说明
修改配置: 确保
config.py中的INPUT_DIR和OUTPUT_PATH已正确设置为批量处理文件夹。替换代码: 用上面的新代码替换原始
run_dpsk_ocr_pdf.py文件中的if __name__ == "__main__":块及其后续内容。运行: 直接运行修改后的脚本。
python run_dpsk_ocr_pdf.py
脚本现在会遍历 zhuanli_pdf_input 文件夹中的所有 PDF 文件,并为每个文件在 zhuanli_pdf_output 文件夹中生成:
-
一个 Markdown 文件 (
filename.md)。 -
一个包含原始模型输出的
.mmd文件 (filename_det.mmd)。 -
一个带有布局检测框的 PDF 文件 (
filename_layouts.pdf)。 -
所有切出的图像块将统一存放在
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 文件,并手动清理这一行和附近的特殊字符。
-
定位文件和行号: 打开
/home/neu/deepseek_ocr/DeepSeek-OCR-main/DeepSeek-OCR-master/DeepSeek-OCR-vllm/run_dpsk_ocr_pdf.py文件的 第 261 行 附近。 -
删除并重新输入: 找到这行代码:
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 变量,因为:
-
在原来的脚本中,
prompt = PROMPT是在if __name__ == "__main__":块中定义的,是局部于该块的。 -
在我们的重构中,
process_single_image及其调用的prompt变量被封装进了process_single_pdf函数,但process_single_image仍然无法访问。
解决方案 ✅
最简单的修复方法是修改 process_single_image 函数,让它接收 prompt 作为一个参数。
需要进行两处修改:
1. 修改 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
2. 修改 process_single_pdf 函数内的调用
在 process_single_pdf 函数中,修改 ThreadPoolExecutor 的 executor.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_det 和 contents 都没有新增该页内容。如果所有页面都出现这种情况,最终文件就会是空白的。
解决方案:
-
修改
config.py:将SKIP_REPEAT设置为False。# config.py SKIP_REPEAT = False -
运行脚本:重新运行
run_dpsk_ocr_pdf.py。
如果禁用 SKIP_REPEAT 后文件不再空白,那就证实了是模型没有生成 EOS 标记导致内容被丢弃。应该保持 SKIP_REPEAT = False,或者修改代码逻辑,让它更健壮地处理 EOS 缺失的情况。
如此,就可以得到一个可以批量 DeepSeek-OCR PDF 转换脚本。
更多推荐
所有评论(0)