- 人工智能
- AI 应用
- MCP 服务
【免费下载链接】markitdown
Python tool for converting files and office documents to Markdown.
导读
MarkItDown 是微软推出的"文件/Office 文档转 Markdown"的 Python 工具,其核心转换逻辑由一系列DocumentConverter组成。但当内置转换器无法覆盖你所需的文件格式(如 RTF)时,该怎么办?本文以仓库中的 markitdown-sample-plugin 为骨架,完整讲解"插件接口协议 → 自定义转换器实现 → pyproject.toml 入口点声明 → 安装验证 → CLI/Python 两种启用方式 → 优先级机制 → 测试"的全链路开发流程。读完本文,你将能独立为 MarkItDown 编写一个可被markitdown --use-plugins或MarkItDown(enable_plugins=True)自动发现与调用的第三方转换插件。
一、插件机制概览:MarkItDown 如何发现第三方转换器
MarkItDown 本身内置了 Docx、Xlsx、Pptx、Pdf、Html、Csv 等二十余种转换器,但插件机制允许外部包在不修改核心库的情况下扩展新的文件格式支持。整套插件协议由三块拼图组成(对应 sample plugin 的实现):
- 一个自定义的
DocumentConverter子类:实现accepts()与convert()两个方法; - 两个模块级导出:
__plugin_interface_version__(接口版本号)与register_converters(markitdown, **kwargs)(注册回调); pyproject.toml中的入口点声明:[project.entry-points."markitdown.plugin"]。
MarkItDown 侧通过importlib.metadata.entry_points(group="markitdown.plugin")枚举并懒加载所有已安装插件的入口点,然后逐个调用其register_converters将转换器注册进MarkItDown实例。相关实现位于 packages/markitdown/src/markitdown/_markitdown.py(_load_plugins)与 同文件 enable_plugins 方法。
二、实现自定义 DocumentConverter:accepts 与 convert 双方法协议
插件最核心的部分是转换器本身。Sample Plugin 以 RTF 转换器为例,展示了最小可用的DocumentConverter骨架:
from typing import BinaryIO, Any from markitdown import MarkItDown, DocumentConverter, DocumentConverterResult, StreamInfo, PRIORITY_SPECIFIC_FILE_FORMAT class RtfConverter(DocumentConverter): def __init__( self, priority: float = PRIORITY_SPECIFIC_FILE_FORMAT ): super().__init__(priority=priority) def accepts( self, file_stream: BinaryIO, stream_info: StreamInfo, **kwargs: Any, ) -> bool: # Implement logic to check if the file stream is an RTF file # ... raise NotImplementedError() def convert( self, file_stream: BinaryIO, stream_info: StreamInfo, **kwargs: Any, ) -> DocumentConverterResult: # Implement logic to convert the file stream to Markdown # ... raise NotImplementedError()2.1 基类契约(来自源码的事实)
根据 packages/markitdown/src/markitdown/_base_converter.py 中DocumentConverter的基类定义,协议细节如下:
accepts():依据stream_info(典型是mimetype、extension,必要时也可参考url、filename)快速判断"是否应由本转换器尝试转换",返回布尔值。其签名与convert()保持一致,以保障"accepts 为 True 时 convert 必能处理"。重要约束:若在accepts()中读取了文件流(如file_stream.read(...)),返回前必须把流位置复位(file_stream.seek(cur_pos)),因为convert()会紧接着以原位置读取。convert():真正把文件流转换为 Markdown,返回DocumentConverterResult。失败时可抛出FileConversionException(mimetype 已识别但转换失败)或MissingDependencyException(缺少依赖)。DocumentConverterResult:构造参数为markdown(必填)与title(可选);text_content是其软弃用别名(源码中注明"新代码应迁移到markdown或__str__")。StreamInfo:字段包括mimetype、extension、charset、filename、local_path、url,均可为 None,见 packages/markitdown/src/markitdown/_stream_info.py。
2.2 真实可运行的 RTF 实现(仓库源码)
Sample Plugin 实际交付的 RtfConverter 并未停留在NotImplementedError骨架,而是给出了完整实现,是编写自有转换器的最佳模板:
import locale from typing import BinaryIO, Any from striprtf.striprtf import rtf_to_text from markitdown import ( MarkItDown, DocumentConverter, DocumentConverterResult, StreamInfo, ) ACCEPTED_MIME_TYPE_PREFIXES = [ "text/rtf", "application/rtf", ] ACCEPTED_FILE_EXTENSIONS = [".rtf"] class RtfConverter(DocumentConverter): """Converts an RTF file in the simplest possible way.""" def accepts( self, file_stream: BinaryIO, stream_info: StreamInfo, **kwargs: Any, ) -> bool: mimetype = (stream_info.mimetype or "").lower() extension = (stream_info.extension or "").lower() if extension in ACCEPTED_FILE_EXTENSIONS: return True for prefix in ACCEPTED_MIME_TYPE_PREFIXES: if mimetype.startswith(prefix): return True return False def convert( self, file_stream: BinaryIO, stream_info: StreamInfo, **kwargs: Any, ) -> DocumentConverterResult: # Read the file stream into a str using the provided charset encoding, or using the system default encoding = stream_info.charset or locale.getpreferredencoding() stream_data = file_stream.read().decode(encoding) # Return the result return DocumentConverterResult( title=None, markdown=rtf_to_text(stream_data), )几个值得注意的实现细节:
- 匹配策略:
accepts()先比对扩展名(.rtf),再按 MIME 前缀(text/rtf、application/rtf)做宽松匹配(startswith),大小写归一化后比较,覆盖面更稳; - 编码处理:
convert()优先使用stream_info.charset,缺失时回退到locale.getpreferredencoding(),再对二进制流解码,兼顾了 HTTP 响应中携带 charset 与本地文件两种场景; - 文本转换:调用第三方库
striprtf的rtf_to_text完成 RTF 语法剥离,title传None表示不提供文档标题。
三、声明插件协议导出:接口版本号与注册回调
实现转换器之后,插件包还必须导出以下两个模块级对象(源码见 packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py):
# The version of the plugin interface that this plugin uses. # The only supported version is 1 for now. __plugin_interface_version__ = 1 # The main entrypoint for the plugin. This is called each time MarkItDown instances are created. def register_converters(markitdown: MarkItDown, **kwargs): """ Called during construction of MarkItDown instances to register converters provided by plugins. """ # Simply create and attach an RtfConverter instance markitdown.register_converter(RtfConverter())__plugin_interface_version__:当前协议版本号,唯一受支持的值是1;register_converters(markitdown, **kwargs):每次创建MarkItDown实例并启用插件时都会被调用。其中markitdown是刚构造的实例,kwargs透传构造参数。回调内部只需markitdown.register_converter(...)即可完成注册,示例中把RtfConverter的一个实例直接挂上。
插件包的__init__.py(packages/markitdown-sample-plugin/src/markitdown_sample_plugin/init.py)会再导出__version__、__plugin_interface_version__、register_converters、RtfConverter四个符号,方便测试与使用者直接from markitdown_sample_plugin import RtfConverter。
四、在 pyproject.toml 中声明插件入口点
register_converters只是模块内函数,MarkItDown 要发现它,靠的是打包元数据中的 entry point。在插件的 pyproject.toml 中声明:
[project.entry-points."markitdown.plugin"] sample_plugin = "markitdown_sample_plugin"语义说明:
- 键
sample_plugin可以是任意字符串,但最好与插件名一致(--list-plugins输出中会显示它); - 值
markitdown_sample_plugin是实现插件的包的全限定名(即register_converters所在模块,MarkItDown 加载入口点后直接调用该模块的register_converters); - 入口点分组名
markitdown.plugin是固定的协议字符串,与 _markitdown.py 中entry_points(group="markitdown.plugin")一一对应,写错分组名将导致插件无法被发现。
该 pyproject.toml 还给出了可参考的完整打包配置:requires-python = ">=3.10"、依赖markitdown>=0.1.0a1与striprtf、采用 hatchling 构建。若插件还需转换其他格式,只需在dependencies中追加对应解析库即可。
五、安装与验证:从 pip 安装到插件列表确认
5.1 安装插件
在插件包目录下执行(此处指仓库中的 packages/markitdown-sample-plugin 目录):
pip install -e .-e(editable)模式会以可编辑方式安装当前目录的包,开发调试时改动源码无需重装。
5.2 验证插件已被 MarkItDown 识别
markitdown --list-plugins该命令的输出逻辑在 packages/markitdown/src/markitdown/main.py:它会枚举entry_points(group="markitdown.plugin"),打印形如* sample_plugin (package: markitdown_sample_plugin)的条目;若没有任何插件,则提示 "No 3rd-party plugins installed."。注意:插件只有在使用-p/--use-plugins时才真正加载(见 5.3 与 6.1),--list-plugins只是列出已安装的入口点。
六、启用插件:CLI 与 Python 两种方式
6.1 CLI 方式
使用--use-plugins(简写-p)标志启用全部已安装插件,例如转换一个 RTF 文件:
markitdown --use-plugins path-to-file.rtf在main.py 中,该标志被透传为MarkItDown(enable_plugins=args.use_plugins),从而在实例构造阶段触发插件注册。
6.2 Python 方式
在 Python 中通过enable_plugins=True构造MarkItDown:
from markitdown import MarkItDown md = MarkItDown(enable_plugins=True) result = md.convert("path-to-file.rtf") print(result.text_content)结合 _markitdown.py 可知:enable_plugins默认是None(等效关闭),仅当显式传True时才调用enable_plugins();内置转换器默认开启(enable_builtins默认True),插件转换器与内置转换器共存于同一注册表。
七、优先级机制:插件转换器如何"插队"到内置转换器之前
注册时通过register_converter(converter, priority=...)指定优先级,其语义在 _markitdown.py 的 docstring 中有权威说明:
- 两个内置常量:
PRIORITY_SPECIFIC_FILE_FORMAT = 0.0(如 docx、pdf、xlsx 这类精确格式,定义见此处)与PRIORITY_GENERIC_FILE_FORMAT = 10.0(text/*等接近兜底的通用转换器,如 PlainTextConverter、HtmlConverter、ZipConverter); - 数值越小越先尝试:每次
_convert前对注册表按 priority 做稳定排序(sorted(self._converters, key=lambda x: x.priority),见实现),同优先级的转换器保持注册顺序,且越晚注册的越靠前; - 插件的插队空间:插件可用任意优先级控制出场顺序。例如 priority 为 9 的插件会排在
PRIORITY_GENERIC_FILE_FORMAT(10)的 PlainTextConverter 之前,但排在大多数内置精确格式(0)之后;想让插件优先于一切内置转换器,则可用小于 0 的数值; - 兜底保障:每个
StreamInfo猜测(含magika内容识别的猜测与空StreamInfo()兜底)都会依次过一遍排序后的转换器,直到某个accepts()返回 True 且convert()成功为止;全部失败时若存在异常记录则抛FileConversionException,否则抛UnsupportedFormatException。
Sample Plugin 的RtfConverter默认使用PRIORITY_SPECIFIC_FILE_FORMAT(0.0),与 Docx、Pdf 等内置精确格式同优先级,依靠"后注册者优先"的稳定排序规则排在它们之前,保证 RTF 输入能命中插件自身。
八、用测试验证插件:直接调用与端到端两条路径
仓库自带测试 packages/markitdown-sample-plugin/tests/test_sample_plugin.py,给出了两条验证思路:
from markitdown import MarkItDown, StreamInfo from markitdown_sample_plugin import RtfConverter RTF_TEST_STRINGS = { "This is a Sample RTF File", "It is included to test if the MarkItDown sample plugin can correctly convert RTF files.", } def test_converter() -> None: """Tests the RTF converter directly.""" with open(os.path.join(TEST_FILES_DIR, "test.rtf"), "rb") as file_stream: converter = RtfConverter() result = converter.convert( file_stream=file_stream, stream_info=StreamInfo( mimetype="text/rtf", extension=".rtf", filename="test.rtf" ), ) for test_string in RTF_TEST_STRINGS: assert test_string in result.text_content def test_markitdown() -> None: """Tests that MarkItDown correctly loads the plugin.""" md = MarkItDown(enable_plugins=True) result = md.convert(os.path.join(TEST_FILES_DIR, "test.rtf")) for test_string in RTF_TEST_STRINGS: assert test_string in result.text_content- 单元级:跳过插件加载链路,直接构造
RtfConverter并手工传入StreamInfo(mimetype="text/rtf", extension=".rtf", filename="test.rtf"),验证convert()输出包含预期文本; - 集成级:
MarkItDown(enable_plugins=True)+convert()端到端验证入口点发现、register_converters回调、路由匹配(accepts)与转换(convert)全链路。测试样本为仓库内的 test.rtf。
九、完整插件清单(可复制的开发模板)
综合上述各节,一个完整、可直接运行的 MarkItDown 插件包由以下四部分构成(文件组织方式参照 markitdown-sample-plugin):
1. 转换器与注册回调(src/<your_plugin>/_plugin.py):__plugin_interface_version__ = 1+register_converters(markitdown)+ 自定义DocumentConverter子类(accepts判格式、convert转 Markdown,返回DocumentConverterResult(markdown=..., title=...))。
2. 包导出(src/<your_plugin>/__init__.py):re-export__plugin_interface_version__、register_converters与转换器类,方便测试与用户直接引用。
3. 入口点声明(pyproject.toml):
[project.entry-points."markitdown.plugin"] sample_plugin = "markitdown_sample_plugin"4. 安装与使用:
pip install -e . markitdown --list-plugins # 确认入口点可见 markitdown --use-plugins file.rtf # CLI 启用插件转换from markitdown import MarkItDown md = MarkItDown(enable_plugins=True) result = md.convert("file.rtf") print(result.text_content) # 或 result.markdown从接口版本号、注册回调到入口点声明、优先级数值,每一步都有明确的源码与测试佐证,读者可对照仓库中的 sample plugin 源码、核心注册逻辑 与 基类协议 逐行核验,并据此为自己的专属格式编写转换插件。
- 人工智能
- AI 应用
- MCP 服务
【免费下载链接】markitdown
Python tool for converting files and office documents to Markdown.
相关推荐
F3D 插件开发实战:基于 libf3d Plugin SDK 编写自定义三维格式读取插件
F3D 插件开发实战:基于 libf3d Plugin SDK 编写自定义三维格式读取插件 F3D 的插件机制允许开发者在不修改主程序的前提下,通过 Plugi
3D渲染图形学桌面应用在 AntV G6 中编写自定义插件(Custom Plugin):从注册、配置到生命周期实战
在 AntV G6 中编写自定义插件(Custom Plugin):从注册、配置到生命周期实战 导读 插件(Plugin)是 G6( @antv/g6 )中扩展
数据可视化前端图表库Apache Airflow 插件开发实战:从零编写一个自定义 Plugin 视图
Apache Airflow 插件开发实战:从零编写一个自定义 Plugin 视图 导读 Apache Airflow 提供了强大的插件(Plugin)机制,允
后端任务调度工作流自动化数据编排批处理数据工程流程编排
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考