From a21f5063c845a7abff9fc2d0dd6d248fc861ce22 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Tue, 17 Feb 2026 21:58:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8F=82=E6=95=B0=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E6=98=AF=E5=90=A6=E4=BD=BF=E7=94=A8ocr=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E8=A7=A3=E6=9E=90pdf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- temp/scripts/README.md | 46 +++++++++------- temp/scripts/parser.py | 37 ++++++++++--- temp/scripts/pdf_parser.py | 106 ++++++++++++++++++++++++++++--------- 3 files changed, 141 insertions(+), 48 deletions(-) diff --git a/temp/scripts/README.md b/temp/scripts/README.md index e033cae..bf292f0 100644 --- a/temp/scripts/README.md +++ b/temp/scripts/README.md @@ -46,6 +46,12 @@ python parser.py [options] |--------|--------|------| | `-n ` | `--context ` | 每个匹配结果包含的前后非空行数(默认:2) | +PDF 专用参数: + +| 长选项 | 说明 | +|--------|------| +| `--high-res` | 启用 OCR 版面分析(需要额外依赖,处理较慢) | + ### 退出码 | 退出码 | 含义 | @@ -178,23 +184,25 @@ uv run --with docling --with "unstructured[xlsx]" --with markdownify --with "mar ### PDF -优先级:Docling → unstructured → MarkItDown → pypdf +默认优先级:Docling → unstructured (fast) → MarkItDown → pypdf + +`--high-res` 优先级:Docling OCR → unstructured OCR (hi_res) → Docling → unstructured (fast) → MarkItDown → pypdf ```bash -# pip - 基础文本提取(使用 fast 策略,无需 OCR) -pip install docling "unstructured[pdf]"" markdownify "markitdown[pdf]" pypdf +# pip - 基础文本提取(fast 策略,无需 OCR) +pip install docling "unstructured[pdf]" markdownify "markitdown[pdf]" pypdf -# pip - OCR 版面分析(使用 hi_res 策略 + PaddleOCR) +# pip - OCR 版面分析(--high-res 所需依赖) pip install docling "unstructured[pdf]" unstructured-paddleocr "paddlepaddle==2.6.2" ml-dtypes markdownify "markitdown[pdf]" pypdf # uv - 基础文本提取 uv run --with docling --with "unstructured[pdf]" --with markdownify --with "markitdown[pdf]" --with pypdf parser.py report.pdf # uv - OCR 版面分析 -uv run --with docling --with "unstructured[pdf]" --with unstructured-paddleocr --with "paddlepaddle==2.6.2" --with ml-dtypes --with markdownify --with "markitdown[pdf]" --with pypdf parser.py report.pdf +uv run --with docling --with "unstructured[pdf]" --with unstructured-paddleocr --with "paddlepaddle==2.6.2" --with ml-dtypes --with markdownify --with "markitdown[pdf]" --with pypdf parser.py report.pdf --high-res ``` -> PDF 无内置 XML 原生解析,至少需要安装 pypdf。unstructured 的 `hi_res` 策略需要额外安装 `unstructured-paddleocr`、`paddlepaddle==2.6.2`、`ml-dtypes`,不可用时自动回退 `fast` 策略。PaddlePaddle 必须锁定 2.x 版本,3.x 在 Windows 上有 OneDNN 兼容问题。 +> PDF 无内置 XML 原生解析,至少需要安装 pypdf。默认模式下 Docling 不启用 OCR,unstructured 使用 fast 策略。指定 `--high-res` 后,Docling 启用 OCR,unstructured 使用 hi_res 策略配合 PaddleOCR 进行版面分析。hi_res 策略需要额外安装 `unstructured-paddleocr`、`paddlepaddle==2.6.2`、`ml-dtypes`。PaddlePaddle 必须锁定 2.x 版本,3.x 在 Windows 上有 OneDNN 兼容问题。 > ### 安装所有依赖 @@ -225,7 +233,7 @@ pip install "markitdown[pdf]" # PDF pip install "markitdown[docx,pptx,xlsx,pdf]" # 全部 ``` -**Docling**:首次运行会自动下载 OCR/视觉模型到缓存目录,需保持网络连通。 +**Docling**:DOCX/PPTX/XLSX 使用 SimplePipeline 直接解析 XML 结构,不涉及 OCR。PDF 默认不启用 OCR(`do_ocr=False`),指定 `--high-res` 后启用 OCR(`do_ocr=True`)。首次运行 OCR 模式会自动下载模型到缓存目录,需保持网络连通。 **unstructured**:需同时安装 `markdownify`。支持按文档类型安装特定 extras 以减少依赖量: @@ -284,7 +292,7 @@ pip install "markitdown[docx,pptx,xlsx,pdf]" # 全部 **XLSX** — 以 `## SheetName` 区分工作表,数据以 Markdown 表格呈现: ```markdown -# Excel数据转换结果 +# Excel数据转换结果 (原生XML解析) ## Sheet1 @@ -309,9 +317,9 @@ pip install "markitdown[docx,pptx,xlsx,pdf]" # 全部 |------|------| | 图片移除 | 删除 `![alt](url)` 语法 | | 空行规范化 | 连续多个空行合并为一个 | -| RGB 噪声过滤 | 移除 `R:255 G:128 B:0` 格式的颜色值行 | -| 页码噪声过滤 | 移除 `— 3 —` 格式的页码行 | -| 页眉/页脚过滤 | unstructured 解析器自动跳过 Header/Footer 元素 | +| RGB 噪声过滤 | 移除 `R:255 G:128 B:0` 格式的颜色值行(仅 unstructured 解析器) | +| 页码噪声过滤 | 移除 `— 3 —` 格式的页码行(仅 unstructured 解析器) | +| 页眉/页脚过滤 | 自动跳过 Header/Footer 元素(仅 unstructured 解析器) | ## 错误处理 @@ -384,12 +392,14 @@ $ python parser.py report.docx -s "[invalid" ### PDF -| 解析器 | 优点 | 缺点 | 适用场景 | -|---------|------|--------|---------| -| **Docling** | 内置 OCR;结构化 Markdown;表格/图片占位 | 模型体积大;OCR 耗时长 | 扫描版 PDF;多语言 | -| **unstructured** | hi_res 版面分析 + PaddleOCR;元素感知;自动回退 fast | 需额外 PaddleOCR 依赖 | 版面分析;OCR | -| **MarkItDown** | 微软官方;格式规范 | 输出简洁 | 标准格式 | -| **pypdf** | 轻量;速度快;安装简单 | 功能简单 | 快速文本提取 | +| 解析器 | 模式 | 优点 | 缺点 | 适用场景 | +|---------|------|------|--------|---------| +| **Docling** | 默认 | 结构化 Markdown;表格/图片占位 | 首次需下载模型 | 有文本层的 PDF | +| **Docling OCR** | `--high-res` | 内置 OCR;结构化 Markdown | 模型体积大;OCR 耗时长 | 扫描版 PDF;多语言 | +| **unstructured** | 默认 | fast 策略;速度快 | 不做版面分析;标题不可靠 | 快速文本提取 | +| **unstructured OCR** | `--high-res` | hi_res 版面分析 + PaddleOCR;标题识别 | 需额外 PaddleOCR 依赖 | 版面分析;OCR | +| **MarkItDown** | 通用 | 微软官方;格式规范 | 输出简洁 | 标准格式 | +| **pypdf** | 通用 | 轻量;速度快;安装简单 | 功能简单 | 快速文本提取 | ## 常见问题 @@ -399,7 +409,7 @@ $ python parser.py report.docx -s "[invalid" ### PDF 文件没有标题层级? -PDF 是版面描述格式,不包含语义化标题结构。Docling 或 unstructured hi_res 策略可通过版面分析识别部分标题,准确度取决于排版质量。建议用 `-s` 搜索定位内容,或用 `-c` / `-l` 了解文档规模。 +PDF 是版面描述格式,不包含语义化标题结构。使用 `--high-res` 参数可启用 Docling OCR 或 unstructured hi_res 策略,通过版面分析识别部分标题,准确度取决于排版质量。默认模式下建议用 `-s` 搜索定位内容,或用 `-c` / `-l` 了解文档规模。 ### 表格格式不正确? diff --git a/temp/scripts/parser.py b/temp/scripts/parser.py index 9280d1e..f8c251a 100644 --- a/temp/scripts/parser.py +++ b/temp/scripts/parser.py @@ -2,8 +2,17 @@ """文档解析器命令行交互模块,提供命令行接口。支持 DOCX、PPTX、XLSX 和 PDF 文件。""" import argparse +import logging import os import sys +import warnings + +# 抑制第三方库的进度条和日志,仅保留解析结果输出 +os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1" +os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1" +os.environ["TQDM_DISABLE"] = "1" +warnings.filterwarnings("ignore") +logging.disable(logging.WARNING) import common import docx_parser @@ -27,6 +36,12 @@ def main() -> None: help="与 -s 配合使用,指定每个检索结果包含的前后行数(不包含空行)", ) + parser.add_argument( + "--high-res", + action="store_true", + help="PDF 解析时启用 OCR 版面分析(需要额外依赖,处理较慢)", + ) + group = parser.add_mutually_exclusive_group() group.add_argument( "-c", "--count", action="store_true", help="返回解析后的 markdown 文档的总字数" @@ -88,12 +103,22 @@ def main() -> None: ("XML 原生解析", xlsx_parser.parse_xlsx_with_xml), ] else: - parsers = [ - ("docling", pdf_parser.parse_pdf_with_docling), - ("unstructured", pdf_parser.parse_pdf_with_unstructured), - ("MarkItDown", pdf_parser.parse_pdf_with_markitdown), - ("pypdf", pdf_parser.parse_pdf_with_pypdf), - ] + if args.high_res: + parsers = [ + ("docling OCR", pdf_parser.parse_pdf_with_docling_ocr), + ("unstructured OCR", pdf_parser.parse_pdf_with_unstructured_ocr), + ("docling", pdf_parser.parse_pdf_with_docling), + ("unstructured", pdf_parser.parse_pdf_with_unstructured), + ("MarkItDown", pdf_parser.parse_pdf_with_markitdown), + ("pypdf", pdf_parser.parse_pdf_with_pypdf), + ] + else: + parsers = [ + ("docling", pdf_parser.parse_pdf_with_docling), + ("unstructured", pdf_parser.parse_pdf_with_unstructured), + ("MarkItDown", pdf_parser.parse_pdf_with_markitdown), + ("pypdf", pdf_parser.parse_pdf_with_pypdf), + ] failures = [] content = None diff --git a/temp/scripts/pdf_parser.py b/temp/scripts/pdf_parser.py index 30ba55d..392bc76 100644 --- a/temp/scripts/pdf_parser.py +++ b/temp/scripts/pdf_parser.py @@ -3,42 +3,69 @@ from typing import Optional, Tuple -from common import _unstructured_elements_to_markdown, parse_with_docling, parse_with_markitdown +from common import _unstructured_elements_to_markdown, parse_with_markitdown def parse_pdf_with_docling(file_path: str) -> Tuple[Optional[str], Optional[str]]: - """使用 docling 库解析 PDF 文件""" - return parse_with_docling(file_path) + """使用 docling 库解析 PDF 文件(不启用 OCR)""" + try: + from docling.datamodel.base_models import InputFormat + from docling.datamodel.pipeline_options import PdfPipelineOptions + from docling.document_converter import DocumentConverter, PdfFormatOption + except ImportError: + return None, "docling 库未安装" + + try: + converter = DocumentConverter( + format_options={ + InputFormat.PDF: PdfFormatOption( + pipeline_options=PdfPipelineOptions(do_ocr=False) + ) + } + ) + result = converter.convert(file_path) + markdown_content = result.document.export_to_markdown() + if not markdown_content.strip(): + return None, "文档为空" + return markdown_content, None + except Exception as e: + return None, f"docling 解析失败: {str(e)}" + + +def parse_pdf_with_docling_ocr(file_path: str) -> Tuple[Optional[str], Optional[str]]: + """使用 docling 库解析 PDF 文件(启用 OCR)""" + try: + from docling.document_converter import DocumentConverter + except ImportError: + return None, "docling 库未安装" + + try: + converter = DocumentConverter() + result = converter.convert(file_path) + markdown_content = result.document.export_to_markdown() + if not markdown_content.strip(): + return None, "文档为空" + return markdown_content, None + except Exception as e: + return None, f"docling OCR 解析失败: {str(e)}" def parse_pdf_with_unstructured(file_path: str) -> Tuple[Optional[str], Optional[str]]: - """使用 unstructured 库解析 PDF 文件,优先 hi_res 策略配合 PaddleOCR""" + """使用 unstructured 库解析 PDF 文件(fast 策略)""" try: from unstructured.partition.pdf import partition_pdf except ImportError: return None, "unstructured 库未安装" - base_kwargs = {"filename": file_path, "infer_table_structure": True} - try: - # 优先 hi_res 策略(版面分析 + PaddleOCR),失败则回退 fast - try: - from unstructured.partition.utils.constants import OCR_AGENT_PADDLE - - elements = partition_pdf( - **base_kwargs, - strategy="hi_res", - languages=["chi_sim"], - ocr_agent=OCR_AGENT_PADDLE, - table_ocr_agent=OCR_AGENT_PADDLE, - ) - trust_titles = True - except Exception: - # fast 策略不做版面分析,Title 类型标注不可靠 - elements = partition_pdf(**base_kwargs, strategy="fast", languages=["chi_sim"]) - trust_titles = False - - content = _unstructured_elements_to_markdown(elements, trust_titles) + elements = partition_pdf( + filename=file_path, + infer_table_structure=True, + strategy="fast", + languages=["chi_sim"], + ) + # fast 策略不做版面分析,Title 类型标注不可靠 + content = _unstructured_elements_to_markdown(elements, trust_titles=False) if not content.strip(): return None, "文档为空" return content, None @@ -46,6 +73,37 @@ def parse_pdf_with_unstructured(file_path: str) -> Tuple[Optional[str], Optional return None, f"unstructured 解析失败: {str(e)}" +def parse_pdf_with_unstructured_ocr( + file_path: str, +) -> Tuple[Optional[str], Optional[str]]: + """使用 unstructured 库解析 PDF 文件(hi_res 策略 + PaddleOCR)""" + try: + from unstructured.partition.pdf import partition_pdf + except ImportError: + return None, "unstructured 库未安装" + + try: + from unstructured.partition.utils.constants import OCR_AGENT_PADDLE + except ImportError: + return None, "unstructured-paddleocr 库未安装" + + try: + elements = partition_pdf( + filename=file_path, + infer_table_structure=True, + strategy="hi_res", + languages=["chi_sim"], + ocr_agent=OCR_AGENT_PADDLE, + table_ocr_agent=OCR_AGENT_PADDLE, + ) + content = _unstructured_elements_to_markdown(elements, trust_titles=True) + if not content.strip(): + return None, "文档为空" + return content, None + except Exception as e: + return None, f"unstructured OCR 解析失败: {str(e)}" + + def parse_pdf_with_markitdown(file_path: str) -> Tuple[Optional[str], Optional[str]]: """使用 MarkItDown 库解析 PDF 文件""" return parse_with_markitdown(file_path)