MarkItDown 是微软开源的一款将任何格式的文件转换为 markdown 纯文本的工具。
这个工具的用途主要是让 AI 方便对内容进行索引和文本分析。
此工具声称能处理下面这些类型的文件
- PowerPoint
- Word
- Excel
- 图片 (EXIF 元数据和 OCR)
- 语音 (EXIF 元数据和字幕)
- HTML
- 文本格式 (CSV, JSON, XML)
- 压缩文件 (逐个批量处理)
这个工具是用 Python 开发的。最简单的使用方法,类似这样
from markitdown import MarkItDown
md = MarkItDown()
result = md.convert("test.xlsx")
print(result.text_content)
构造 MarkItDown
实例时也可以传入大模型参数,
from markitdown import MarkItDown
from openai import OpenAI
client = OpenAI()
md = MarkItDown(llm_client=client, llm_model="gpt-4o")
result = md.convert("example.jpg")
print(result.text_content)
仅仅就一个简单的 HTML 到 markdown 的转换,其实就有不少细节。看到这里,我的小脑袋里禁不住有这样一些问题:
- 如何能支持这么多源文件格式?
- 大模型在项目里是什么作用?
- 转换的质量如何?特别是 HTML 里嵌入的代码、图片、音视频,能否美观的在 markdown 里呈现?
项目的代码规模
将项目下载后,用 loc 看
Language | Files | Lines | Blank | Comment | Code |
---|---|---|---|---|---|
HTML | 3 | 2448 | 185 | 47 | 2216 |
Python | 6 | 1924 | 328 | 163 | 1433 |
Markdown | 4 | 238 | 74 | 0 | 164 |
Toml | 1 | 83 | 9 | 0 | 74 |
XML | 1 | 1 | 0 | 0 | 1 |
Total | 15 | 4694 | 596 | 210 | 3888 |
这里面除了 Python 之外,其它基本上是测试和说明文件。关键代码才几个。
src
└── markitdown
├── __about__.py
├── __init__.py
├── __main__.py
├── _markitdown.py
└── py.typed
核心逻辑,全部在 _markitdown.py
里。
转换方法详解
由于核心代码不长,才 1500 行,非常适合交给 Gemini 2.0 去分析。
下面是 Gemini 2.0 Flash Experimental 分析代码后给出的回答
1. 它是如何支持如此多的源文件格式的?
该工具通过多种技术的结合来实现其广泛的文件格式支持:
使用转换器类的模块化设计:
- 它采用模块化方法,为每种文件类型定义单独的转换器类(例如,
PlainTextConverter
、HtmlConverter
、PdfConverter
、DocxConverter
、ImageConverter
等)。 - 每个转换器类都继承自一个公共基类 (
DocumentConverter
),并实现特定于其格式的convert
方法。这使得将来添加新的转换器变得容易。
- 它采用模块化方法,为每种文件类型定义单独的转换器类(例如,
文件扩展名和 MIME 类型检测:
- 它利用 Python 的
os.path.splitext
和mimetypes
库从扩展名和内容类型推断文件类型。 - 它还使用
puremagic
来检查文件内容(魔术字节)以猜测文件类型,当扩展名缺失或不可靠时,这尤其有用,对于从 URL 检索的文件尤其如此。
- 它利用 Python 的
外部库:
mammoth
:将 DOCX 文件转换为 HTML。markdownify
:将 HTML 转换为 Markdown。pandas
:从 Excel (XLSX) 文件中读取数据到数据帧。pdfminer
:从 PDF 文件中提取文本。pptx
:从 PowerPoint (PPTX) 文件中读取数据。bs4 (BeautifulSoup)
:解析 HTML 和 XML。speech_recognition
:转录音频文件。pydub
:读取和转换音频文件。youtube_transcript_api
:从 YouTube 视频中提取转录。Exiftool: 脚本还使用
exiftool
命令行工具(如果系统存在)从图像和音频文件中提取元数据。它利用几个外部库来处理特定于格式的解析和转换:
针对复杂格式的特定处理:
HTML: 使用
BeautifulSoup
解析 HTML,提取相关内容,并利用markdownify
转换为 Markdown,同时去除 Javascript 和复杂的样式。RSS/Atom: 使用 minidom 解析 XML,并通过提取和格式化每个帖子的标题和内容来创建 Markdown 输出。
Jupyter Notebook (.ipynb): 从 JSON 内容中提取 Markdown 和代码单元格并转换为 Markdown。
YouTube: 尝试提取视频标题、描述和转录,并将其格式化为 Markdown。
Zip:
ZipConverter
将文件提取到临时文件夹,并递归处理文件夹中的每个文件。它针对更复杂的格式定制了转换器,例如:
错误处理和回退:
MarkItDown.convert
方法遍历转换器,尝试每个转换器,直到找到合适的转换器。如果所有尝试都失败,它会记录错误并引发异常。
本质上,该工具通过以下方式支持多种格式:
- 通过 MIME 类型、扩展名和魔术字节的组合来识别源格式。
- 将每个文件的处理委托给专门为给定文件格式设计的专用转换器。
- 使用外部库和工具来完成文件解析的繁重工作。
- 优雅地回退,并且仅在尝试所有方法后才报告失败。
2. 大型语言模型 (LLM) 在该项目中的作用是什么?
该代码目前利用 LLM 进行 图像字幕/描述。
图像描述:
ImageConverter
类具有一个_get_llm_description
方法,该方法调用外部 LLM 客户端(llm_client
,在构造函数中配置)来生成图像的文本描述。- 图像被编码为 base64 字符串,嵌入到数据 URL 中,并与提示一起发送到 LLM(提示可以自定义,但默认为“为这张图片写一个详细的标题。”)。
- 然后,LLM 的文本响应包含在 Markdown 输出中。
它如何在
MarkItDown
中使用:- 构造函数接受
llm_client
和llm_model
参数,这些参数随后传递给ImageConverter
。 - 这是一个可选功能,仅当在创建
MarkItDown
实例时配置了这些参数才使用。
- 构造函数接受
要点:
- LLM 集成目前 仅限于图像描述。
- LLM 被视为通过客户端调用的 外部服务。
- 系统允许将自定义提示发送到 LLM。
- 代码使用 LLM 客户端生成描述,而不是实际执行转换逻辑。
3. 转换质量如何?HTML 中嵌入的代码、图像和音频/视频能否在 Markdown 中很好地表示?
转换质量因源格式的复杂性而异:
总体良好:
- 文本: 纯文本、基本 HTML、PDF、DOCX 和 XLSX 文件通常可以很好地转换为 Markdown,保留基本的文本格式、标题、列表和表格(如果适用)。
- 元数据: 图像和音频文件的 EXIF 元数据被正确提取并显示。
HTML 转换:
- 该脚本在删除
script
和style
标签以降低复杂性方面做得很好。 - 该脚本会转义 URI,这意味着链接应该是安全的,并且下划线将正确呈现。
- 默认情况下,
markdownify
包括所有图像,但_CustomMarkdownify
会删除数据 URI,因为大量的嵌入数据会减慢 LLM 的速度。
- 该脚本在删除
代码块:
- 转换器使用标准
```
markdown 渲染带有语法突出显示的代码块。
- 转换器使用标准
局限性:
生成的 Markdown 主要关注内容,而不是复杂的布局或演示。不支持复杂的 Markdown 扩展。
PowerPoint 旨在支持表格和图表(以基本格式),但复杂的图表样式可能无法正确表示。
如果图像是 PPTX 的一部分,它们将通过占位符名称显示。
HTML、XLSX 和 PPTX 中的表格会转换为基本的 Markdown 表格格式,但可能无法完全表示具有复杂样式或格式的表格。
音频通过元数据和转录转换为 Markdown(如果环境已设置为执行转换)。但是,结果将仅是文本,而不是实际的音频文件。
视频(例如 YouTube)通过元数据和转录转换为 Markdown(如果环境已设置为执行转换)。但是,结果将仅是文本,而不是实际的视频文件。
没有对 HTML 中嵌入的视频的特定支持。
图像路径在 Markdown 中保留为相对路径。它无法直接嵌入图像(PPTX 的缩略图除外)。
PPTX 幻灯片中的图像的处理方式是仅包含图像的
alt
文本和一个占位符名称(例如,
)。如果需要保留图像,则必须手动解析占位符名称。对于标准图像,可以提取元数据、EXIF 以及通过 LLM 进行的描述(如果使用)。
HTML/CSS 复杂性: 虽然
markdownify
可以将大多数标准 HTML 转换为 Markdown,但复杂的布局、样式和交互元素可能无法完全保留。诸如 CSS Grid、JavaScript 驱动的元素和动画之类的东西将不会呈现。同样重要的是要提到,复杂文件类型的大多数转换逻辑都依赖于在生成 markdown 之前转换为 HTML,并且 HTML 的复杂性可能会导致次优的结果。图像:
音频/视频:
表格:
PowerPoint:
没有高级格式:
它们在 Markdown 中的表示程度如何?
- HTML 中嵌入的代码表示为标准的 Markdown 代码围栏。
- 图像使用标准的 Markdown 语法引用,但实际文件不会嵌入。
- 音频和视频文件通过其元数据和(在某些情况下)转录引用,但实际的音频/视频不会嵌入到生成的 Markdown 文本中。
大模型在里面起到的作用
使用多模态的大模型将图片转换成描述图片的提示词,关键在下面这个函数。说穿了,就是一句提示词
Write a detailed caption for this image.
def _get_llm_description(self, local_path, extension, client, model, prompt=None):
if prompt isNoneor prompt.strip() == "":
prompt = "Write a detailed caption for this image."
data_uri = ""
with open(local_path, "rb") as image_file:
content_type, encoding = mimetypes.guess_type("_dummy" + extension)
if content_type isNone:
content_type = "image/jpeg"
image_base64 = base64.b64encode(image_file.read()).decode("utf-8")
data_uri = f"data:{content_type};base64,{image_base64}"
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {
"url": data_uri,
},
},
],
}
]
response = client.chat.completions.create(model=model, messages=messages)
return response.choices[0].message.content
项目工程优点
微软开源的 Python 项目值得学习的地方
- 使用了社区最新的构建打包方案,
pyproject.toml
没错,Python 这种解释型编程语言也需要构建了。
可以简单的把这个文件的作用理解为 JavaScript 项目里的 package.json
,以一种工具中立的方式说明项目的元数据、依赖等等。
- 启用了 Python 的类型检查 (type hinting)
相当于 TypeScript。类型检查可以让弱类型的 Python 项目在规模变大后不容易失控。
- 有测试 CI
使用 hatch test
。测试用例可以让多人贡献项目时,保证质量。
小结
核心代码 1500 行,一周收获 26K 星。
拆解后,发现全是调包。
项目工程质量不错,也为以后继续迭代功能留足了空间。
可以说这是一个“有想法,先上线,再优化”的典型实践案例了。你有什么想法呢,欢迎在评论区留言探讨。