第 2 课
在 Python 里批量转换、接视觉大模型、自己写插件
用 MarkItDown 的 convert、convert_local、convert_stream 加 StreamInfo、convert_response 转换文件、内存数据和网页,写一个批量转换目录的脚本,用假客户端看清 llm_client 和 llm_prompt 怎样把图片交给视觉大模型,处理 FileNotFoundError 和 UnsupportedFormatException,再照官方示例插件写一个 .srt 字幕插件并用 --use-plugins 启用。
课程目录第 2 / 3 课
学习位置仅保存在当前浏览器,有效期 180 天。
图文教材
本课对照的官方页面:MarkItDown README 的 Python API 部分 、官方示例插件 markitdown-sample-plugin 、markitdown-ocr 插件说明 。测试环境:2026-11-05,Linux x86_64 云主机,Python 3.13.5,markitdown 0.1.9。接入真实视觉大模型的部分没有实测,用一个假的客户端代替,课内注明了。
上一课在命令行里转换了一个个文件。这一课在 Python 里使用 MarkItDown:读取转换结果、转换内存里的数据、批量转换一个目录、给图片接上大模型描述,最后自己写一个插件,让 MarkItDown 认识新格式。下面的脚本都放在 ~/markitdown-lab/l2/ 里运行,用的是上一课下载到 ~/markitdown-lab/files/ 的测试文件。
mkdir -p ~/markitdown-lab/l2 && cd ~/markitdown-lab/l2
source ../.venv/bin/activate
第 1 步:最基本的调用
新建 basics.py:
from markitdown import MarkItDown
md = MarkItDown(enable_plugins=False)
result = md.convert("../files/test.xlsx")
print(type(result).__name__, result.title)
print(result.markdown.splitlines()[0])
print(len(result.markdown), "chars")
local = md.convert_local("../files/test.docx")
print("docx title:", local.title)
运行 python basics.py,对照结果:
DocumentConverterResult None
## Sheet1
808 chars
docx title: None
convert() 返回一个 DocumentConverterResult 对象,正文在 .markdown 里,.title 只有网页这类有标题的来源才有值。convert() 能接受本地路径、网址或文件流;convert_local() 只接受本地文件。
第 2 步:转换内存里的数据和自己下载的网页
数据不在磁盘上时(比如从数据库或接口拿到的字节),用 convert_stream()。流必须是二进制模式,并用 StreamInfo 告诉它扩展名。如果你自己用 requests 下载网页,可以把响应对象交给 convert_response()。新建 stream.py:
import io
import requests
from markitdown import MarkItDown, StreamInfo
md = MarkItDown()
# 1) 内存里的字节流:用 StreamInfo 告诉它扩展名
data = open("../files/test.pdf", "rb").read()
info = StreamInfo(extension=".pdf")
r1 = md.convert_stream(io.BytesIO(data), stream_info=info)
print("stream:", r1.markdown.split()[1])
# 2) 自己发请求,再交给 convert_response
resp = requests.get("https://example.com/", timeout=20)
r2 = md.convert_response(resp)
print("response title:", r2.title)
对照结果:
stream: Introduction
response title: Example Domain
传给 convert_stream() 的应是二进制流,例如 io.BytesIO,或用 "rb" 模式打开的文件。
第 3 步:批量转换一个目录
新建 batch.py,把 files/ 里的 Office 文件和 PDF 全部转成 .md,放进 md/ 目录:
from pathlib import Path
from markitdown import MarkItDown
md = MarkItDown()
src, out = Path("../files"), Path("md")
out.mkdir(exist_ok=True)
for f in sorted(src.iterdir()):
if f.suffix not in {".docx", ".xlsx", ".pptx", ".pdf"}:
continue
text = md.convert_local(f).markdown
(out / (f.stem + f.suffix.replace(".", "_") + ".md")).write_text(text)
print(f"{f.name:12} -> {len(text):6} chars")
对照结果:
test.docx -> 4651 chars
test.pdf -> 5194 chars
test.pptx -> 2047 chars
test.xlsx -> 808 chars
输出文件名里保留了原扩展名(如 test_docx.md),这样同名的 .docx 和 .pdf 不会互相覆盖。
第 4 步:让大模型描述图片
给 MarkItDown() 传入 llm_client 和 llm_model 后,转换图片文件和 PowerPoint 时会把图片交给大模型,生成一段描述。官方 README 用的是 OpenAI 客户端:
from markitdown import MarkItDown
from openai import OpenAI
client = OpenAI(max_retries=5)
md = MarkItDown(llm_client=client, llm_model="你的视觉模型名",
llm_prompt="用中文描述这张图,并抄出图里的文字。")
print(md.convert("../files/test_llm.jpg").markdown)
上面这段需要 API Key 和可用的视觉模型,测试机没有接真实模型(未实测)。官方示例里的模型名是 gpt-4o,你可以换成自己账号里当前可用的视觉模型。按官方说明,大模型描述目前只用于 PowerPoint 和图片文件;max_retries=5 让 OpenAI 客户端遇到可重试的错误时最多尝试 6 次。如果最后仍然失败,MarkItDown 会改用其他转换器,全部失败时才抛出 FileConversionException。
为了在没有 Key 的情况下看清 MarkItDown 发给模型的是什么,可以写一个假客户端。MarkItDown 只调用 client.chat.completions.create(model=..., messages=...),并读取 choices[0].message.content。新建 llm_stub.py:
from types import SimpleNamespace
from markitdown import MarkItDown
class FakeCompletions:
def create(self, model, messages):
parts = messages[0]["content"]
print("model =", model)
print("prompt =", parts[0]["text"])
print("image =", parts[1]["image_url"]["url"][:23] + "...")
msg = SimpleNamespace(content="(这里会是模型写的图片描述)")
return SimpleNamespace(choices=[SimpleNamespace(message=msg)])
client = SimpleNamespace(chat=SimpleNamespace(completions=FakeCompletions()))
md = MarkItDown(llm_client=client, llm_model="your-vision-model",
llm_prompt="用中文描述这张图,并抄出图里的文字。")
print(md.convert("../files/test_llm.jpg").markdown)
对照结果:
model = your-vision-model
prompt = 用中文描述这张图,并抄出图里的文字。
image = data:image/jpeg;base64,...
# Description:
(这里会是模型写的图片描述)
可以看到:图片被编码成 base64 的 data URI,和你的提示词一起发给模型;模型返回的文字放在 # Description: 下面。不传 llm_prompt 时,默认提示词是 Write a detailed caption for this image.。
如果要从 PDF、Word、PowerPoint、Excel 里嵌入的图片中识别文字,官方另有 markitdown-ocr 插件(pip install markitdown-ocr),也用同样的 llm_client 和 llm_model 参数。本课没有实测这个插件。注意:它的 README 里写的命令行参数 --llm-client、--llm-model,在 markitdown 0.1.9 的命令行里并不存在(运行 markitdown --help 可以确认),请改用上面的 Python 写法。
第 5 步:处理出错的情况
新建 safety.py:
from markitdown import MarkItDown
md = MarkItDown()
try:
md.convert_local("https://example.com/")
except Exception as e:
print(type(e).__name__)
open("note.bin", "wb").write(bytes(range(256)))
try:
md.convert_local("note.bin")
except Exception as e:
print(type(e).__name__)
对照结果:
FileNotFoundError
UnsupportedFormatException
convert_local() 不会去下载网址。如果你的程序要处理用户传来的路径,用它比 convert() 更安全,可以避免程序被引导去访问任意网址。认不出格式的文件会抛出 UnsupportedFormatException,批量转换时记得捕获这个异常。
第 6 步:写一个插件,让它认识字幕文件
MarkItDown 默认不认识 .srt 字幕,会把它当纯文本原样输出。官方用 markitdown-sample-plugin 演示了插件的写法:一个 Python 包,通过 markitdown.plugin 入口点注册,提供 __plugin_interface_version__ 和 register_converters()。照着这个结构写一个字幕插件:
cd ~/markitdown-lab
mkdir -p markitdown-srt/src/markitdown_srt && cd markitdown-srt
新建 pyproject.toml:
[project]
name = "markitdown-srt"
version = "0.1.0"
dependencies = ["markitdown>=0.1.9,<0.2"]
[project.entry-points."markitdown.plugin"]
srt = "markitdown_srt"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
新建 src/markitdown_srt/__init__.py:
import re
from typing import Any, BinaryIO
from markitdown import (MarkItDown, DocumentConverter,
DocumentConverterResult, StreamInfo)
__plugin_interface_version__ = 1
TIME = re.compile(r"(\d\d:\d\d:\d\d),\d+ --> ")
class SrtConverter(DocumentConverter):
def accepts(self, file_stream: BinaryIO, stream_info: StreamInfo,
**kwargs: Any) -> bool:
return (stream_info.extension or "").lower() == ".srt"
def convert(self, file_stream: BinaryIO, stream_info: StreamInfo,
**kwargs: Any) -> DocumentConverterResult:
text = file_stream.read().decode(stream_info.charset or "utf-8")
lines = []
for block in text.strip().split("\n\n"):
rows = block.strip().splitlines()
m = TIME.match(rows[1]) if len(rows) > 2 else None
if m:
lines.append(f"- **{m.group(1)}** " + " ".join(rows[2:]))
return DocumentConverterResult(markdown="\n".join(lines))
def register_converters(markitdown: MarkItDown, **kwargs):
markitdown.register_converter(SrtConverter())
accepts() 决定这个转换器接不接这个文件,convert() 返回转换结果。再准备一个两条字幕的样例 demo.srt:
1
00:00:01,000 --> 00:00:03,500
大家好,欢迎来到昆仑学堂。
2
00:00:04,000 --> 00:00:07,200
今天我们用 MarkItDown
把字幕转成 Markdown。
用可编辑模式安装插件,然后确认 MarkItDown 能找到它:
pip install -e .
markitdown --list-plugins
对照结果:
Installed MarkItDown 3rd-party Plugins:
* srt (package: markitdown_srt)
Use the -p (or --use-plugins) option to enable 3rd-party plugins.
插件默认不启用。对比一下加不加 --use-plugins 的区别:
markitdown demo.srt | head -2
markitdown --use-plugins demo.srt
对照结果:
1
00:00:01,000 --> 00:00:03,500
- **00:00:01** 大家好,欢迎来到昆仑学堂。
- **00:00:04** 今天我们用 MarkItDown 把字幕转成 Markdown。
前两行是没启用插件时的原样输出,后两行是插件转出来的列表。在 Python 里启用插件的写法是 MarkItDown(enable_plugins=True)。
小结
MarkItDown().convert()返回结果对象,正文在.markdown。- 内存数据用
convert_stream()加StreamInfo;自己下载的网页用convert_response();只想读本地文件时用convert_local()。 - 传入
llm_client和llm_model后,图片会交给大模型描述,提示词可以用llm_prompt改。 - 插件是一个普通 Python 包:注册
markitdown.plugin入口点,实现accepts()和convert(),使用时加--use-plugins。
