01

为什么需要 MarkItDown

把 Word、PDF、Excel 变成 AI 能读懂的纯文本 —— 这就是 116K 开发者的选择

AI 的"阅读障碍"

你用 ChatGPT 写代码、让 Claude 分析文档。但你有没有想过:AI 其实看不懂你的 Word 文件。它只能读纯文本。

💡
核心洞察

大语言模型(LLM)"说" Markdown 不是巧合 —— 它们在海量 Markdown 文本上训练过。这意味着 Markdown 是喂给 AI 的最高效格式:结构清晰,token 消耗最少。

没有 MarkItDown 的世界 vs 有它的世界

1

收到一个 .docx 文件

2

手动打开、复制文本

3

粘贴给 AI,格式全乱

4

AI 理解不了表格

1

一行命令

2

完整的 Markdown 输出

3

AI 完美理解

它能转换什么?

MarkItDown 是一个"万能翻译器",把 15+ 种文件格式统一翻译成 Markdown。

📄

PDF

含表格提取、表单识别,双引擎(pdfplumber + pdfminer)

📝

Word (.docx)

保留标题层级、表格结构,用 mammoth 转 HTML 再转 Markdown

📊

Excel (.xlsx)

每个 Sheet 变成独立的 Markdown 表格

📽️

PowerPoint (.pptx)

逐页转换,保留图片 alt 文字、图表数据、演讲者备注

🖼️

图片 / 音频

提取 EXIF 元数据,还能用 LLM 生成图片描述、转录音频

🌐

HTML / URL

网页、YouTube 字幕、Wikipedia、RSS 订阅源都能处理

架构总览:它是怎么工作的

用户层

⌨️
命令行 CLI
🐍
Python API

核心引擎

🔧
MarkItDown 类
🔍
Magika 文件识别

转换器家族

📦
17 个内置转换器
🔌
插件系统
点击上方任意组件,了解它的作用

一次转换的完整旅程

📁
输入文件
🔍
Magika 识别
⚖️
匹配转换器
⚙️
执行转换
Markdown 输出
点击"下一步"开始演示

为什么 MarkItDown 选择 Markdown 而不是 HTML 或 JSON 作为输出格式?

如果有人把一个 PDF 文件重命名为 .docx,MarkItDown 会怎样?

02

五分钟上手

安装、命令行使用、Python 调用 —— 三种方式,总有一种适合你

安装:一行命令搞定

就像装一个 App 一样简单。在 终端 里输入:

终端命令

# 安装所有格式的支持(推荐新手)
pip install "markitdown[all]"

# 或者只装你需要的格式
pip install "markitdown[pdf, docx, pptx]"
            
中文解释

方括号里的 [all] 表示安装所有可选依赖,让它能处理所有文件格式

如果你只需要处理 PDF、Word 和 PPT,可以只装这三个格式

💡
小贴士

pip 是 Python 的"应用商店"。方括号 [all] 是 pip 的"套餐"语法 —— 表示安装这个包的"全家桶"版本。

命令行:转换一个文件有多简单

终端命令

# 基本用法:转换后输出到屏幕
markitdown report.pdf

# 保存到文件
markitdown report.pdf -o report.md

# 管道操作:从其他命令读取输入
cat report.pdf | markitdown

# 使用 Azure 文档智能服务
markitdown report.pdf -d -e "<your-endpoint>"
            
中文解释

最简单的用法,转换结果直接显示在终端里

-o 参数指定输出到文件,就像"另存为"

管道符 | 把前一个命令的输出传给后一个命令

用微软 Azure 云服务来处理,效果更好但需要付费

Python 代码调用:最灵活的方式

如果你在写自己的程序,需要在代码里调用 MarkItDown:

Python

from markitdown import MarkItDown

md = MarkItDown()
result = md.convert("季度报告.xlsx")
print(result.text_content)
            
中文解释

从 markitdown 包里导入主类

创建一个 MarkItDown 实例,就像打开了一个"翻译机"

把文件喂给 convert() 方法,它会自动识别类型并转换

打印转换后的 Markdown 文本

用 AI 增强:让图片和音频也能"说话"

传入一个 LLM 客户端,MarkItDown 就能让 AI 描述图片内容:

Python

from markitdown import MarkItDown
from openai import OpenAI

client = OpenAI()
md = MarkItDown(
    llm_client=client,
    llm_model="gpt-4o",
)
result = md.convert("产品截图.jpg")
print(result.text_content)
            
中文解释

导入 OpenAI 客户端,用来调用 AI 模型

创建一个 OpenAI 客户端(会自动读取你的 API Key)

创建 MarkItDown,并告诉它用哪个 AI 来看图说话

指定用 gpt-4o 模型(它的"视觉"能力很强)

转换图片时,AI 会自动生成文字描述

一次转换的"后台对话"

当你调用 md.convert("报告.pptx") 时,代码内部发生了什么?想象几个角色在对话:

你有一个本地 PDF 文件和一个网页 URL,想分别转换。你应该:

你用 MarkItDown 转换一个包含产品截图的 PPTX,但输出只有 "图片.jpg" 这样的占位文字。最可能的原因是?

03

转换器引擎揭秘

理解 accepts/convert 模式、优先级排序和 StreamInfo —— MarkItDown 的"小脑"

所有转换器的"老祖宗":DocumentConverter

想象一个基类是一份合同。每个转换器都必须签两份"承诺书":

Python 源码

class DocumentConverter:
    def accepts(self, file_stream,
                 stream_info, **kwargs) -> bool:
        "我能处理这个文件吗?"
        raise NotImplementedError

    def convert(self, file_stream,
                 stream_info, **kwargs):
        "转换它!"
        raise NotImplementedError
            
中文解释

DocumentConverter 是所有转换器的基类

accepts() —— "举手报到":给我看看文件信息,如果我会处理就返回 True

NotImplementedError 表示"子类必须重写这个方法"

convert() —— "真刀真枪":把文件转成 Markdown

两个方法的参数完全一样,保证调用安全

StreamInfo:文件的"身份证"

每个文件在被转换前,都会拿到一张"身份证",上面记录了引擎知道的所有信息:

Python 源码

@dataclass(kw_only=True, frozen=True)
class StreamInfo:
    mimetype: Optional[str] = None
    extension: Optional[str] = None
    charset: Optional[str]   = None
    filename: Optional[str]  = None
    local_path: Optional[str]= None
    url: Optional[str]       = None
            
中文解释

@dataclass 自动生成构造函数,frozen=True 表示不可修改

mimetype:文件的 MIME 类型,如 "application/pdf"

extension:文件后缀,如 ".docx"

charset:文本编码,如 "utf-8"

filename / local_path / url:来源信息

🔑
关键设计

frozen=True 意味着 StreamInfo 是不可变的。想更新?用 copy_and_update() 创建新副本。这保证了多转换器并发时的安全性。

优先级:谁先来,谁后来

引擎按照"专业优先、通用兜底"的原则排序。数值越小,优先级越高:

0.0 专业转换器(PdfConverter、DocxConverter 等)—— 先试它们
9.0 第三方插件 —— 比通用转换器优先,可以覆盖内置行为
10.0 通用兜底(PlainTextConverter、HtmlConverter、ZipConverter)—— 最后试
Python 源码

# 排序后逐个尝试
sorted_registrations = sorted(
    self._converters,
    key=lambda x: x.priority
)

for converter_registration in sorted_registrations:
    _accepts = converter.accepts(
        file_stream, stream_info, **_kwargs
    )
    if _accepts:
        res = converter.convert(
            file_stream, stream_info, **_kwargs
        )
    if res is not None:
        return res
            
中文解释

按优先级排序所有已注册的转换器

逐个尝试,问它们"你能处理这个文件吗?"

第一个说"能"的,就让它来转换

如果转换成功(结果不为 None),立刻返回

错误处理:失败了不放弃

如果第一个匹配的转换器失败了怎么办?引擎不会立刻报错,而是继续尝试下一个:

成功路径

转换器 accepts() = True → convert() 成功 → 直接返回结果

单转换器失败

convert() 抛异常 → 记录到 failed_attempts → 继续试下一个转换器

🚫

全军覆没

所有转换器都不行 → 抛出 FileConversionException,报告所有失败原因

⚠️

无人认领

没有任何转换器 accepts() = True → 抛出 UnsupportedFormatException

结果归一化:统一输出格式

不管哪个转换器输出的,最终都会经过"保洁"处理:

Python 源码

# 归一化内容
res.text_content = "\n".join(
    [line.rstrip()
     for line in re.split(r"\r?\n",
         res.text_content)]
)
res.text_content = re.sub(
    r"\n{3,}", "\n\n",
    res.text_content
)
            
中文解释

第一步:去掉每行末尾的多余空格

处理 Windows(\r\n)和 Unix(\n)换行符差异

第二步:把 3 个以上的连续空行压缩成 2 个

确保输出格式干净整洁

找 Bug 挑战

下面这段代码有一个潜在问题,看看你能不能找到:

这段转换器注册代码有什么隐藏风险?

1 def register_converter(self, converter, *, priority=0.0):
2 self._converters.insert(0, ConverterRegistration(
3 converter=converter, priority=priority
4 ))

如果一个 PDF 文件被 PdfConverter.accepts() 认领,但 convert() 时抛了异常,引擎会怎样?

StreamInfo 被设计为 frozen=True(不可变)。如果你需要更新它的某个字段,应该怎么做?

04

文件格式大通关

深入 PDF、Word、Excel、PowerPoint 转换器的内部实现

17 个内置转换器全家福

每个转换器都是某个文件格式的"专家":

converters/ 所有转换器都在这个目录下
办公文档三剑客
_pdf_converter.py双引擎:pdfplumber(表格)+ pdfminer(纯文本)
_docx_converter.pymammoth 转 HTML → HtmlConverter 转 Markdown
_xlsx_converter.pypandas 读 Excel → HTML 表格 → Markdown
_pptx_converter.py逐页处理幻灯片、表格、图表、图片
媒体与特殊格式
_image_converter.pyEXIF 元数据 + LLM 图片描述
_audio_converter.py元数据提取 + 语音转录
_epub_converter.py电子书格式转换
网络与数据
_html_converter.pyBeautifulSoup 解析 → markdownify
_youtube_converter.py提取视频字幕
_wikipedia_converter.py抓取维基百科页面
_zip_converter.py递归处理压缩包内所有文件

PDF 转换器:双引擎策略

PDF 是最复杂的格式。MarkItDown 用了一个聪明的"双引擎"策略:

📊
pdfplumber(表格专家)

分析每个文字的位置坐标,把对齐的文字识别为表格行和列。特别擅长处理无边框表格和表单。

📖
pdfminer(散文专家)

更擅长处理连续文本,保持正确的段落间距和阅读顺序。当 pdfplumber 不擅长时,它来兜底。

Python 源码

# 逐页判断是否是表单
with pdfplumber.open(pdf_bytes) as pdf:
    for page_idx, page in enumerate(pdf.pages):
        page_content = _extract_form_content(
            page
        )
        if page_content is not None:
            markdown_chunks.append(
                page_content
            )
        page.close()  # 立即释放内存
            
中文解释

用 pdfplumber 打开 PDF,逐页分析

对每一页,尝试用表单提取方法

如果这一页有表格/表单结构,就用 pdfplumber 的结果

每一页处理完立刻关闭,释放内存(重要!)

💾
内存优化

page.close() 立刻释放 pdfplumber 的缓存对象。这样无论 PDF 有 10 页还是 1000 页,内存使用量保持恒定。这是一个流式处理的典型实践。

Word 转换器:两段接力

DocxConverter 用了一个优雅的"中间人"策略 —— 先转 HTML,再转 Markdown:

1

读取 .docx 文件

2

mammoth 转 HTML

3

HtmlConverter 转 Markdown

Python 源码

class DocxConverter(HtmlConverter):
    def convert(self, file_stream,
                 stream_info, **kwargs):
        pre_process_stream = pre_process_docx(
            file_stream
        )
        return self._html_converter.convert_string(
            mammoth.convert_to_html(
                pre_process_stream,
                style_map=style_map
            ).value,
            **kwargs,
        )
            
中文解释

DocxConverter 继承 HtmlConverter,复用 HTML 转 Markdown 的能力

先对 docx 做预处理(修复一些格式问题)

用 mammoth 库把 docx 转成 HTML

然后把 HTML 交给 HtmlConverter 转成 Markdown —— 完美的接力

Excel 和 PowerPoint 的转换策略

📊

Excel:pandas 做桥梁

用 pandas 读取每个 Sheet → 转 HTML 表格 → HtmlConverter 转 Markdown 表格。每个 Sheet 独立一个标题。

📽️

PPT:逐页遍历

遍历每张幻灯片的 shapes(形状):图片、表格、图表、文本框、分组形状。按位置排序,从上到下、从左到右。

📦

ZIP:递归解包

解压 ZIP → 对每个内部文件递归调用 markitdown.convert_stream() → 拼合所有结果。一个 ZIP 包含 10 个 PDF?全部转换!

转换策略连连看

把每个转换器拖到它使用的"中间格式"上:

DocxConverter
XlsxConverter
PdfConverter (表格)

mammoth → HTML → Markdown(两段接力)

拖到这里

pandas DataFrame → HTML → Markdown(数据做桥)

拖到这里

分析文字 X/Y 坐标 → 直接生成 Markdown 表格

拖到这里

DocxConverter 为什么选择继承 HtmlConverter?

PDF 转换器为什么在处理每一页后立刻调用 page.close()?

05

插件系统与 MCP

扩展 MarkItDown 的能力边界 —— 自定义转换器和 AI 工具集成

插件:像装 App 一样扩展功能

MarkItDown 的插件系统就像手机的应用商店。任何人都可以写一个新转换器,安装后自动生效:

1
开发者写一个 Python 包

在包的配置里声明 markitdown.plugin entry point

2
用户 pip install 这个包

安装后,entry point 自动注册到 Python 环境

3
启用插件:enable_plugins=True

MarkItDown 自动发现并加载所有已安装的插件转换器

插件加载:懒加载 + 容错

Python 源码

def _load_plugins():
    global _plugins
    if _plugins is not None:
        return _plugins  # 已加载过

    _plugins = []
    for entry_point in entry_points(
        group="markitdown.plugin"
    ):
        try:
            _plugins.append(
                entry_point.load()
            )
        except Exception:
            warn("Plugin failed to load")
    return _plugins
            
中文解释

全局变量 _plugins 缓存已加载的插件,避免重复加载

如果已经加载过,直接返回缓存结果

查找所有注册了 "markitdown.plugin" 的 Python 包

尝试加载每个插件

某个插件失败不会影响其他插件 —— 容错设计

实战:一个 RTF 插件的完整代码

这是 MarkItDown 官方示例插件,只有 40 行代码,给 MarkItDown 加上了 RTF(富文本格式)支持:

Python 源码

from markitdown import (
    MarkItDown, DocumentConverter,
    DocumentConverterResult, StreamInfo,
)

def register_converters(
    markitdown: MarkItDown, **kwargs
):
    markitdown.register_converter(
        RtfConverter()
    )

class RtfConverter(DocumentConverter):
    def accepts(self, file_stream,
                 stream_info, **kwargs) -> bool:
        ext = (stream_info.extension
               or "").lower()
        return ext in [".rtf"]

    def convert(self, file_stream,
                 stream_info, **kwargs):
        encoding = stream_info.charset
        data = file_stream.read()
                      .decode(encoding)
        return DocumentConverterResult(
            markdown=rtf_to_text(data)
        )
            
中文解释

导入 MarkItDown 的核心接口

register_converters() 是插件的入口函数,MarkItDown 会自动调用它

注册一个 RtfConverter 实例

accepts():只接受 .rtf 后缀的文件

convert():读取文件内容,解码文本

用 striprtf 库把 RTF 转成纯文本,作为 Markdown 返回

🎯
写插件就是这么简单

三步走:(1) 继承 DocumentConverter,(2) 实现 accepts()convert(),(3) 在 register_converters() 里注册。你的插件就能处理全新的文件格式了。

markitdown-ocr:用 AI 看懂图片中的文字

官方 OCR 插件利用 LLM 的视觉能力,从 PDF/DOCX/PPTX 的嵌入图片中提取文字:

📄
含图片的 PDF
⚙️
内置转换器
🤖
LLM Vision
📝
文字描述
完整 Markdown
点击"下一步"开始演示

MCP:让 AI 工具直接调用 MarkItDown

MCP 让 MarkItDown 变成 AI 助手的"手"和"眼"。Claude、Cursor 等 AI 工具可以通过 MCP 直接调用 MarkItDown:

🤖
markitdown-mcp 包

一个独立的 MCP 服务器,AI 工具通过它调用 MarkItDown 的转换能力。你的 AI 助手瞬间就能"读懂"任何文件。

🔗
无缝集成

在 Claude Desktop 或 Cursor 的配置里加一行 MCP 服务器地址,AI 就能自动转换你拖进去的文件。

配置示例

# Claude Desktop 配置
{
  "mcpServers": {
    "markitdown": {
      "command": "uvx",
      "args": ["markitdown-mcp"]
    }
  }
}
            
中文解释

在 Claude Desktop 的 MCP 配置文件中添加

用 uvx(Python 包运行工具)启动 markitdown-mcp 服务

之后 Claude 就能自动转换你发来的任何文件了

你想写一个 MarkItDown 插件来处理 .svg 文件。你的插件需要实现什么?

如果一个第三方插件有 Bug 导致加载失败,MarkItDown 会怎样?

06

实战:构建你的转换管道

用 MarkItDown 搭建真实场景的文件处理管道 —— 从批量转换到 AI 增强

场景一:批量转换文件夹

假设你的老板发来一个压缩包,里面有 50 份季度报告(混合 PDF、Word、Excel)。你需要把它们全部转成 Markdown 存入知识库。

Python

from markitdown from pathlib import Path

md = MarkItDown()
output_dir = Path("markdown_output")
output_dir.mkdir(exist_ok=True)

# 转换 ZIP 包内所有文件
result = md.convert("季度报告.zip")

# 或者遍历文件夹
for f in Path("reports/").glob("*.*"):
    if f.suffix in [".pdf", ".docx", ".xlsx"]:
        r = md.convert(str(f))
        out = output_dir / (f.stem + ".md")
        out.write_text(r.text_content,
                     encoding="utf-8")
        print(f"Converted: {f.name}")
            
中文解释

创建输出目录(如果不存在)

方案 A:直接转换 ZIP,ZipConverter 会递归处理内部所有文件

方案 B:遍历文件夹,过滤支持的文件类型

对每个文件调用 convert(),自动识别格式

把结果保存为 .md 文件,文件名保持一致

场景二:AI 增强的文档分析管道

把 MarkItDown 和 RAG 结合,打造智能文档问答系统:

📁
文件上传
🔄
MarkItDown
✂️
文本切片
🧮
向量化
🗄️
向量数据库
点击"下一步"查看 RAG 管道流程

场景三:命令行高手的工作流

终端命令

# 提示文件类型(从管道读取时有用)
cat data.bin | markitdown -x .pdf

# 指定 MIME 类型
markitdown data.file -m application/pdf

# 列出所有已安装的插件
markitdown --list-plugins

# 启用插件转换
markitdown --use-plugins document.rtf

# 保留 base64 图片数据
markitdown slides.pptx --keep-data-uris
            
中文解释

-x 参数提示文件后缀(从管道读取时没有文件名)

-m 参数指定 MIME 类型(比后缀更精确)

查看有哪些第三方插件已安装

启用第三方插件(默认是关闭的)

保留图片的 base64 编码数据(默认会截断)

安全提醒:能力越大,责任越大

MarkItDown 能访问当前进程权限下的所有资源。在处理不受信任的文件时,请遵循以下原则:

🛡️

消毒输入

不要把不受信任的用户输入直接传给 MarkItDown。在服务端应用中,必须先验证和限制文件路径、URI 方案。

🎯

使用最窄的 API

如果你的应用只需要读取本地文件,调用 convert_local() 而不是万能的 convert()。这样 MarkItDown 不会去请求网络。

🔐

限制网络访问

convert() 可以处理 HTTP URL 和 data URI。在安全环境中,应该阻止访问私有地址、环回地址和元数据服务地址。

⚠️
安全 API 层级

从最安全到最灵活:convert_stream() > convert_local() > convert_response() > convert_uri() > convert()。选择你能接受的最窄范围。

完整实战对话:构建 RAG 管道

假设你是一个 vibe coder(用 AI 工具写代码),你要和 AI 助手配合搭建一个文档问答系统:

你在做一个 Web 服务,允许用户上传文件并转换。为了安全,你应该用哪个 API?

为什么 MarkItDown 的 Markdown 输出对 RAG 系统特别友好?

你在命令行中使用 MarkItDown 转换一个 RTF 文件,已经安装了 markitdown-ocr 插件。你需要加什么参数才能让插件生效?