安装与使用 MarkItDown Image: AI generated

仅凭 pip install markitdown,PDF、Word 文档、Excel 表格和 PowerPoint 演示文稿就能转换成 markdown。但如果喂给它一份纯扫描图片的 PDF——比如一份扫描合同——得到的却是一份空文档。本文分两条线展开:为什么以及如何使用 MarkItDown,以及如何在这份信任崩溃的确切位置——纯图片 PDF——接入 OCR。


为什么要使用 MarkItDown——agent-operable 这个条件

MarkItDown 是微软开源的 CLI/Python 库,可将 PDF、Word、Excel、PowerPoint、HTML、图片和音频文件转换为 markdown。

先从 agent-operable(智能体可操作的状态)这个概念说起。要让智能体自主处理一份文档,该文档必须处于机器能够结构化解析的状态——而不仅仅是人类觉得可读的状态。这并非只适用于代码的条件。埋藏在 PDF 或 Excel 里的业务规则,对智能体来说等于不存在,因为人类必须打开它才能知道里面写了什么。这正是我在《构建 Agent-Operable 系统》中列出的三个条件——可读、可验证、可持久——中的第一个:“无噪声的可读性”。

那为什么偏偏是 MarkItDown 呢?你完全可以自己拼接第三方解析器(PyPDF2、python-docx)来实现同样的转换。区别在于是谁做的。docx、xlsx、pptx 都是 OOXML——微软自己拥有的规范。当规范的所有者亲自为自家格式打造转换器时,边缘情况(合并单元格、嵌套表格、脚注)在结构上比第三方解析器更可靠。如果转换本身不可信,那么在其之上为了达到 agent-operable 状态所做的一切都毫无意义。这就是使用 MarkItDown 的理由——对于微软自家格式,这份信任是默认自带的。

问题在于,这份信任在 PDF,尤其是纯图片 PDF 上并不会延续下去。本文后半部分专注于用 OCR 补上这个缺口。


安装

基础安装

pip install markitdown

仅基础安装只能处理纯文本、HTML 等轻量格式。要转换 PDF 和 Office 文档,需要一并安装格式专属的扩展项。

按格式安装扩展项

pip install 'markitdown[pdf]'    # PDF (pdfminer.six + pdfplumber)
pip install 'markitdown[docx]'   # Word
pip install 'markitdown[pptx]'   # PowerPoint
pip install 'markitdown[xlsx]'   # Excel
pip install 'markitdown[all]'    # everything above, plus audio transcription, YouTube captions, Azure Document Intelligence, etc.

如果本文只需要处理 PDF,markitdown[pdf] 就够用了。


基本用法

CLI

markitdown example.pdf                  # print markdown to stdout
markitdown example.pdf -o example.md    # save to a file
cat example.pdf | markitdown            # read from stdin (pipe)

Python API

from markitdown import MarkItDown

md = MarkItDown()
result = md.convert("example.pdf")
print(result.markdown)

两种方式都会根据文件扩展名或 MIME 类型自动选择转换器。当没有扩展名信息时——比如从标准输入读取——可以用 -x(--extension)或 -m(--mime-type)提供提示。


PDF 转换的实际行为——只读取文本层

MarkItDown 的 PDF 转换器(packages/markitdown/src/markitdown/converters/_pdf_converter.py)首先用 pdfplumber 检测每一页的表格/表单结构,如果不是表格则回退到 page.extract_text()。如果 pdfplumber 失败,则回退到 pdfminer.six

这两个库都是读取 PDF 内部已存在文本层的工具。如果某一页只是一张扫描图片、没有文本层,extract_text() 会返回空字符串,MarkItDown 的输出实际上也就是空的。基础包里根本没有渲染图片或识别文字的代码路径。

换言之,这是两个不同的问题:

  • 带文本层的 PDF(从 Word 导出的 PDF、大多数电子合同等)——基础安装就能很好地工作。微软自家格式转换所带来的那份信任在这里同样延续。
  • 没有文本层的 PDF(扫描仪扫出的文档、以图片形式嵌入的照片)——基础安装只会返回空结果。Agent Operable 的"可读性"条件在这里断裂。

OCR 设置方法——修补断裂信任的地方

从这里开始是本文的第二条线。MarkItDown 在自家格式上确保的那份信任,在纯图片 PDF 上必须另行搭建。

1. 安装插件

OCR 支持位于一个独立的包 markitdown-ocr 中。它位于同一个仓库(microsoft/markitdown)里,但不包含在 pip install markitdown 中。

pip install markitdown-ocr
pip install openai   # an OpenAI-compatible client — this plugin has no OCR model of its own

安装后确认插件已被识别。

markitdown --list-plugins
#   * ocr             (package: markitdown_ocr)

2. 不可或缺的东西——LLM Vision 客户端

markitdown-ocr 不内置 Tesseract、PaddleOCR 之类的传统 OCR 引擎。它实际做的事只是把图片进行 base64 编码,然后向兼容 OpenAI 的 chat.completions API(例如 gpt-4o)请求"从这张图片中提取文字"(_ocr_service.py 中的 LLMVisionOCRService)。因此,必须拥有能调用视觉模型的 API 密钥,OCR 才能真正运作。

from markitdown import MarkItDown
from openai import OpenAI

md = MarkItDown(
    enable_plugins=True,
    llm_client=OpenAI(),      # reads OPENAI_API_KEY from the environment
    llm_model="gpt-4o",
)

result = md.convert("scanned_contract.pdf")
print(result.markdown)

Azure OpenAI 等任何兼容 OpenAI 的客户端都可以照样传入。

from openai import AzureOpenAI

md = MarkItDown(
    enable_plugins=True,
    llm_client=AzureOpenAI(
        api_key="...",
        azure_endpoint="https://<resource>.openai.azure.com/",
        api_version="2024-02-01",
    ),
    llm_model="gpt-4o",
)

3. CLI 做不到——通过实际代码确认的事实

markitdown-ocr 的 README 给出了如下命令作为示例:

markitdown document.pdf --use-plugins --llm-client openai --llm-model gpt-4o

但直接查看 MarkItDown 核心的 CLI 参数定义(packages/markitdown/src/markitdown/__main__.py)后发现,--llm-client--llm-model 这两个标志根本不存在。CLI 支持的选项只有 -o(输出文件)、-x(扩展名提示)、-m(MIME 提示)、-c(字符集)、-d/--use-docintel--use-cu-p/--use-plugins--list-plugins--keep-data-urisMarkItDown 构造函数收到的也只有 enable_plugins=args.use_plugins。也就是说,README 里的 CLI 示例是一个实际跑不通的文档错误。

OCR 只能通过 Python API 使用——必须直接传入 llm_client/llm_model。没有办法通过 CLI 传递 API 密钥。

4. 运作原理——嵌入图片 OCR 与整页 OCR 回退

PdfConverterWithOCR(_pdf_converter_with_ocr.py)分两个阶段运作。

  1. 优先处理嵌入图片:当页面中文字与图片混杂时(例如正文中间插入了扫描的签名图片),只裁切出图片发送给 LLM Vision,并按其原始 Y 坐标位置与周围文字重新交织,保持阅读顺序。
  2. 整页 OCR 回退:如果文本提取结果整体为空(整份扫描 PDF),则将整页渲染为 300dpi 的 PNG,逐页发送给 LLM Vision(_ocr_full_pages)。

提取出的文本总是以下列格式包裹输出。

*[Image OCR]
<extracted text>
[End OCR]*

5. 用自定义提示词提升准确度

默认提示词是"提取这张图片中的所有文字,保持原始版面和顺序"。对于表格较多、竖排文字,或掺有印章、签名的文档,可以用 llm_prompt 直接覆盖提示词来提升准确度。

md = MarkItDown(
    enable_plugins=True,
    llm_client=OpenAI(),
    llm_model="gpt-4o",
    llm_prompt="Extract all text from this image, but preserve table structure as markdown tables.",
)

6. 批量处理多个文件

一次性处理整个扫描 PDF 文件夹时,每个文件都会触发一次 API 调用——需要对每次转换单独捕获异常,避免一次失败拖垮整批处理。

from pathlib import Path
from markitdown import MarkItDown
from openai import OpenAI

md = MarkItDown(enable_plugins=True, llm_client=OpenAI(), llm_model="gpt-4o")

for pdf_path in Path("scans/").glob("*.pdf"):
    try:
        result = md.convert(str(pdf_path))
        pdf_path.with_suffix(".md").write_text(result.markdown, encoding="utf-8")
    except Exception as e:
        print(f"failed: {pdf_path.name}{e}")

7. 只安装插件而不提供 llm_client 会怎样?

插件会加载,但 OCR 会悄悄关闭。_plugin.py 里的 register_converters() 只有在 llm_clientllm_model 同时存在时才会创建 LLMVisionOCRService,否则会以 ocr_service=None 注册转换器。在这种状态下,会自动回退到仅提取文本层的基础行为,既不报错也不警告。“装了插件但 OCR 不起作用"的问题大多都源于这一点。

8. 甚至会尝试修复损坏的 PDF

当 pdfplumber 和 pdfminer 完全无法打开某个 PDF 时(例如 EOF 被截断的文件),markitdown-ocr 会回退到直接用 PyMuPDF(fitz)渲染页面并重试。如果这条路径也失败,就会在结果中留下 *[Error: Could not process scanned PDF]*

9. 成本与速度

由于是把整页发送给视觉模型,处理页数越多的扫描件,视觉模型的 API 调用成本和延迟就越是按页数比例累积。300dpi 渲染每张图片消耗的 token 并不少,因此在把成百上千页的文档整份跑完之前,先用几页样本估算质量和成本更为稳妥。


OCR 结果验证 (Verifier)——另行处理

到目前为止的设置都是关于如何"开启"OCR。但开启了不代表结果就可信——在目前这种即便 llm_client 未设置、即便页面为空也会静默通过的结构下,并不存在判断 OCR 输出是否真正可信的确定性流程。

这一流程本文不涉及。如何将"LLM 生成 → 确定性工具判定 → 反馈 → 重复"这一 Symbolic Feedback Loop 原则应用到 OCR 输出上,留给另一篇文章/另一个工具处理。


实战检查清单

  • 如果只处理带文本层的普通 PDF,基础的 markitdown[pdf] 安装就足够了,不需要 OCR 插件。
  • 如果必须处理扫描件或照片 PDF,markitdown-ocr 加上 OpenAI(或兼容)API 密钥是必需的。没有与 Tesseract 等免费本地传统 OCR 引擎的集成。
  • 如果 OCR 结果为空,首先要怀疑的是是否真的传入了 llm_client/llm_model。由于是静默跳过,日志中不会显示任何异常。
  • 仅靠 CLI 无法开启 OCR。必须通过调用 MarkItDown(enable_plugins=True, llm_client=..., llm_model=...) 的 Python 脚本。
  • 目前还没有以机器方式判断结果可信度的手段——现阶段需要人工抽样检查。

总结表

情况所需条件备注
带文本层的 PDFpip install markitdown[pdf]pdfplumber/pdfminer 提取文本
部分嵌入图片需要 OCRmarkitdown-ocr + LLM Vision 客户端只裁切图片进行 OCR,与文本交织
整页扫描(整页都是图片)markitdown-ocr + LLM Vision 客户端整页渲染为 300dpi 后进行 OCR
损坏的 PDF同上(自动回退)用 PyMuPDF 渲染重试
未设置 llm_client静默跳过 OCR,不报错
在 CLI 中指定 --llm-client不可能(实际不存在该标志)仅能通过 Python API
判断 OCR 结果可靠性(计划中)交由另一个 verifier 处理

相关文章


参考文献

变更历史

  • 2026-07-09: 初版 — 增加 agent-operable 框架、以 OCR 设置作为后半部分重点扩充、加入 verifier 占位小节