把 Microsoft MarkItDown 做成本地文档转换 API:PDF/Office 转 Markdown 后,用 cpolar 接收远程上传
把 Microsoft MarkItDown 做成本地文档转换 API:PDF/Office 转 Markdown 后,用 cpolar 接收远程上传
同事丢过来一份 PDF、Word 或 PPT,想快速变成 Markdown:如果每次都手动打开工具、另存、复制,流程很碎。更麻烦的是,远程同事或自动化流程不在你的电脑旁边,没法直接调用你本地的转换环境。
这篇文章做一个实用方案:把 Microsoft 开源的MarkItDown封装成本地 FastAPI 服务,提供一个/convert文件上传接口;本机完成 PDF/Office 等文件到 Markdown 的转换;需要临时给远程同事或工作流使用时,再用 cpolar 暴露本地8000端口。
适合这些场景:
- 远程同事临时上传 PDF/Word,拿回 Markdown 文本;
- 自动化脚本把合同、产品文档、会议纪要转成 Markdown;
- 本地保留转换环境,不把服务长期部署到公网;
- 给后续知识库整理、文档清洗、内容归档提供统一入口。
说明:本文只做“文档转 Markdown API”,不延伸到知识库入库,也不做模型服务部署。
1. 整体流程
流程很简单:
远程用户 / 自动化脚本 │ │ 上传 PDF / DOCX / PPTX / XLSX ▼ cpolar 公网临时地址 │ ▼ 本地 FastAPI 服务 :8000 │ ▼ Microsoft MarkItDown │ ▼ 返回 Markdown 文本为什么要加 cpolar?
因为转换服务跑在本地电脑上,默认只能本机访问。cpolar 可以把本地8000端口临时映射成一个公网 HTTPS 地址,远程同事不用 VPN、不用进内网,就能通过接口上传文件。
但注意:这类服务处理的往往是文档原文,通常包含敏感信息。公网暴露必须加鉴权,并且建议只临时开放。
2. 准备 Python 环境
MarkItDown 要求 Python 3.10 或更高版本。先创建项目目录:
mkdir markitdown-api cd markitdown-api创建并激活虚拟环境:
python3 -m venv .venv source .venv/bin/activateWindows PowerShell 可以用:
python -m venv .venv .\.venv\Scripts\Activate.ps1安装依赖:
pip install -U pip pip install 'markitdown[all]' fastapi uvicorn python-multipart如果只想先支持常见 Office 和 PDF,也可以按需安装 MarkItDown 的可选依赖,例如:
pip install 'markitdown[pdf,docx,pptx,xlsx]' fastapi uvicorn python-multipart本文为了减少格式缺依赖导致的转换失败,示例使用:
pip install 'markitdown[all]' fastapi uvicorn python-multipart3. 先确认 MarkItDown 本身可用
写 API 之前,建议先用命令行确认 MarkItDown 能正常转换文件。
准备一个测试文件,比如:
test.docx test.pdf test.pptx执行:
markitdown test.docx -o output.md或者把结果直接输出到终端:
markitdown test.pdf如果这一步能得到 Markdown 内容,再继续封装 API。这样排查问题会更简单:
- 命令行都失败:优先检查文件格式、Python 版本、依赖安装;
- 命令行成功但 API 失败:再检查上传文件保存、路径、权限等问题。
4. 编写 FastAPI 转换服务
在项目根目录创建main.py:
from pathlib import Path from tempfile import TemporaryDirectory from uuid import uuid4 from fastapi import FastAPI, File, Header, HTTPException, UploadFile from fastapi.responses import PlainTextResponse from markitdown import MarkItDown app = FastAPI(title="Local MarkItDown API") # 简单 API Token。生产环境建议改成环境变量或更完整的鉴权方案。 API_TOKEN = "change-this-token" # 单文件大小限制:20MB,可按机器性能调整。 MAX_FILE_SIZE = 20 * 1024 * 1024 # 允许的扩展名。按需增减,避免把未知文件直接丢给转换器。 ALLOWED_SUFFIXES = { ".pdf", ".docx", ".pptx", ".xlsx", ".xls", ".csv", ".json", ".xml", ".html", ".htm", ".txt", ".md", } # 官方 Python API:MarkItDown(enable_plugins=False).convert(path).text_content md_converter = MarkItDown(enable_plugins=False) def check_token(x_api_token: str | None) -> None: if x_api_token != API_TOKEN: raise HTTPException(status_code=401, detail="Invalid API token") @app.get("/health") def health(): return {"status": "ok"} @app.post("/convert", response_class=PlainTextResponse) async def convert_file( file: UploadFile = File(...), x_api_token: str | None = Header(default=None), ): check_token(x_api_token) original_name = file.filename or "upload.bin" suffix = Path(original_name).suffix.lower() if suffix not in ALLOWED_SUFFIXES: raise HTTPException( status_code=400, detail=f"Unsupported file type: {suffix or 'no suffix'}", ) content = await file.read() if len(content) > MAX_FILE_SIZE: raise HTTPException(status_code=413, detail="File too large") with TemporaryDirectory() as tmpdir: safe_name = f"{uuid4().hex}{suffix}" input_path = Path(tmpdir) / safe_name input_path.write_bytes(content) try: result = md_converter.convert(str(input_path)) except Exception as exc: raise HTTPException( status_code=500, detail=f"Convert failed: {type(exc).__name__}: {exc}", ) from exc return result.text_content这里用了 MarkItDown README 中给出的 Python API:
from markitdown import MarkItDown md = MarkItDown(enable_plugins=False) result = md.convert("test.xlsx") print(result.text_content)所以在 FastAPI 中,我们先把上传文件保存到临时目录,再调用:
result = md_converter.convert(str(input_path)) markdown = result.text_content这样比直接猜测内部方法更稳,也方便和命令行行为对齐。
5. 启动本地 API 服务
启动服务:
uvicorn main:app --host 0.0.0.0 --port 8000看到类似输出即可:
Uvicorn running on http://0.0.0.0:8000先检查健康接口:
curl http://127.0.0.1:8000/health预期返回:
{"status":"ok"}6. 本地测试 /convert 接口
准备一个测试文档,例如demo.docx,然后执行:
curl -X POST "http://127.0.0.1:8000/convert" \ -H "X-API-Token: change-this-token" \ -F "file=@demo.docx" \ -o demo.md查看转换结果:
head -40 demo.md如果想直接在终端查看:
curl -X POST "http://127.0.0.1:8000/convert" \ -H "X-API-Token: change-this-token" \ -F "file=@demo.pdf"测试错误 Token:
curl -i -X POST "http://127.0.0.1:8000/convert" \ -H "X-API-Token: wrong-token" \ -F "file=@demo.docx"应该返回401。
测试不支持的文件类型:
curl -i -X POST "http://127.0.0.1:8000/convert" \ -H "X-API-Token: change-this-token" \ -F "file=@demo.exe"应该返回400。
7. 用 cpolar 暴露本地 8000 端口
本地 API 确认没问题后,再用 cpolar 暴露:
cpolar http 8000启动后,cpolar 会生成一个公网访问地址,形式类似:
https://xxxx.cpolar.top假设你的公网地址是:
https://abcd-1234.cpolar.top远程同事就可以这样调用:
curl -X POST "https://abcd-1234.cpolar.top/convert" \ -H "X-API-Token: change-this-token" \ -F "file=@demo.docx" \ -o demo.md自动化流程也可以直接接入这个接口。例如 Python 脚本:
import requests url = "https://abcd-1234.cpolar.top/convert" headers = {"X-API-Token": "change-this-token"} with open("demo.pdf", "rb") as f: files = {"file": ("demo.pdf", f, "application/pdf")} resp = requests.post(url, headers=headers, files=files, timeout=120) resp.raise_for_status() with open("demo.md", "w", encoding="utf-8") as f: f.write(resp.text)这样就能把“本地文档转换能力”临时变成一个远程可调用 API。
8. 推荐的项目结构
最终目录可以这样放:
markitdown-api/ ├── .venv/ ├── main.py ├── demo.docx └── demo.md如果后续要长期使用,建议再补一个requirements.txt:
pip freeze > requirements.txt以后在新机器上恢复环境:
python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt9. 常见问题处理
9.1 上传接口提示 422
FastAPI 文件上传依赖python-multipart。确认已经安装:
pip install python-multipart接口参数要用:
-F "file=@demo.docx"不要把文件当 JSON 传。
9.2 转换出来的 Markdown 内容不完整
MarkItDown 更适合把文档转成给 LLM、文本分析、自动化流程使用的 Markdown,不是高保真排版还原工具。
如果源文件里有复杂排版、扫描图片、嵌入对象,转换结果需要人工检查。
建议:
- 先用简单 DOCX/PDF 做通路测试;
- 对扫描 PDF,确认 OCR 相关依赖和识别效果;
- 对重要文件,保留原文件和转换后的 Markdown 供人工核对。
9.3 cpolar 地址能打开,但接口返回 401
检查请求头是否带了:
-H "X-API-Token: change-this-token"也建议把main.py里的 Token 改成更复杂的值,例如:
API_TOKEN = "mdit-2026-your-random-token"更进一步,可以改成从环境变量读取:
import os API_TOKEN = os.environ.get("MARKITDOWN_API_TOKEN", "change-this-token")启动前设置:
export MARKITDOWN_API_TOKEN='mdit-2026-your-random-token' uvicorn main:app --host 0.0.0.0 --port 80009.4 大文件转换很慢或失败
本文示例限制单文件 20MB:
MAX_FILE_SIZE = 20 * 1024 * 1024如果机器性能有限,不建议直接放开限制。文档转换会占 CPU、内存和磁盘临时空间。尤其是大型 PDF、PPT、Excel,转换耗时会明显增加。
更稳的做法:
- 限制文件大小;
- 限制允许的文件类型;
- 只在需要时打开 cpolar;
- 转换完成后关闭服务或换新 Token。
10. 安全建议:不要把它当成无门槛公网服务
这个方案的重点是“临时暴露本地能力”,不是把文档转换接口长期裸奔在公网。
至少要做到:
必须加鉴权
示例里用了X-API-Token,不要删除。只临时开放 cpolar
用完就停止cpolar http 8000,不要长期挂着。限制文件大小和类型
不要允许任意文件上传。谨慎处理敏感文档
合同、客户资料、财务文件、内部方案,不建议随便通过公网接口传输。不要使用高权限目录处理上传文件
示例使用临时目录,并且文件名用 UUID 重写,避免路径穿越和覆盖本地文件。注意 MarkItDown 的 I/O 权限
MarkItDown 官方也提醒:转换器会以当前进程权限访问资源。处理不可信输入时,应该限制输入来源、文件类型和运行权限。
11. 小结
这套方案把文档转换拆成了三个清晰步骤:
MarkItDown 负责转换 FastAPI 负责上传接口 cpolar 负责临时公网访问本地使用时,访问:
http://127.0.0.1:8000/convert远程临时使用时,访问:
https://你的cpolar地址/convert对团队协作来说,它的价值不是“又搭了一个服务”,而是把原本手动、零散的文档处理动作,变成一个可复用的 API:远程同事、脚本、自动化流程都能按同一套方式上传文件并拿回 Markdown。
如果只是临时协作,开 FastAPI + cpolar 就够了;如果要长期使用,再考虑队列、日志、权限系统、对象存储和更完整的审计能力。
