MarkItDown
MarkItDown 架构:字节流、格式提示与转换器选择
沿已核对的源码追踪本地输入、StreamInfo 猜测、优先级派发、Markdown 规范化与两类失败结果。
你将学会
- StreamInfo 猜测与优先级共同决定转换器选择。
- 不可 seek 的输入会先完整缓存。
- 不支持输入与转换尝试失败是不同结果。
开始前需要
- 具备基础 Python 与命令行知识
- 准备一份内容可核验且不含敏感信息的文档
使用本章清单解释并验证文档导入流程中的对应环节。
先看结论
- StreamInfo 猜测与优先级共同决定转换器选择。
- 不可 seek 的输入会先完整缓存。
- 不支持输入与转换尝试失败是不同结果。
输入最终汇合为字节流与元数据
在核对的版本中,convert_local 根据路径构建 StreamInfo,包含文件名与扩展名,然后以二进制方式打开文件,获取流信息猜测,再把二者交给 _convert。因此,格式处理位于输入获取之后,并不是仅凭文件名参数完成。
convert_stream 接收二进制流。对于不可 seek 的输入,实现会先把完整内容缓存在内存中,再进入转换流程。这样便于探测和重试,但也影响内存规划:以流上传到这个 API,并不代表整个提取过程的内存占用有界。
选择过程有顺序,也允许多种猜测
核心 _convert 方法对已注册转换器按优先级稳定排序,依次遍历流信息猜测和一个空的兜底猜测,再询问各转换器是否接受输入。这解释了为什么扩展名只是提示之一,而不是唯一的判断依据。
专用格式转换器通常使用优先级零,通用文本、HTML 和 ZIP 转换器使用十;数值越小越先尝试。注册时新条目插在前方,稳定排序后同优先级仍偏向较晚注册的条目。自定义插件不仅要选择优先级,更要谨慎编写接受条件。
成功与失败都有可观察的契约
实现用断言检查 accepts 不改变文件游标。实际转换尝试结束时,finally 会恢复流位置,让后续候选在前一个候选失败后仍能读取相同输入,而不是面对已经消费过的流。阅读插件代码时应同时检查这两个边界。
成功后,核心会移除行尾多余空白并压缩连续空行。如果有转换尝试失败,则携带记录抛出 FileConversionException;如果没有转换器尝试输入,则抛出 UnsupportedFormatException。调用方可以据此选择诊断路径,而不是对所有错误统一重试。
实施步骤
- 1
阅读固定版本中的 convert_local 和 convert_stream。
- 2
跟踪到 _get_stream_info_guesses 与 _convert。
- 3
检查优先级排序及 accepts/convert 边界。
- 4
对照两类终止异常。
可复制示例
本地文件 / 二进制流
-> 字节流 + StreamInfo 猜测
-> 按优先级稳定排序
-> accepts() -> convert() -> 恢复游标
-> 规范化 Markdown -> 返回结果
-> 尝试失败,或格式不支持常见问题
只看扩展名就能确定转换器吗?
不能。该版本会把多种 StreamInfo 猜测交给按优先级排序的转换器,也包含兜底猜测。
accepts 可以消耗输入字节吗?
它必须保持原始游标位置,核心会在继续处理前检查这一约束。