把 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/activate

Windows 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-multipart

3. 先确认 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![把 Microsoft MarkItDown 做成本地文档转换 API:PDF/Office 转 Markdown 后,用 cpolar 接收远程上传 - 发布前检查图](https://i-blog.csdnimg.cn/img_convert/img/20260701-backfill/topic-20260604-afternoon-002/topic-20260604-afternoon-002-safety-check.png) = 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.txt

9. 常见问题处理

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 8000

9.4 大文件转换很慢或失败

本文示例限制单文件 20MB:

MAX_FILE_SIZE = 20 * 1024 * 1024

如果机器性能有限,不建议直接放开限制。文档转换会占 CPU、内存和磁盘临时空间。尤其是大型 PDF、PPT、Excel,转换耗时会明显增加。

更稳的做法:

  • 限制文件大小;
  • 限制允许的文件类型;
  • 只在需要时打开 cpolar;
  • 转换完成后关闭服务或换新 Token。

10. 安全建议:不要把它当成无门槛公网服务

这个方案的重点是“临时暴露本地能力”,不是把文档转换接口长期裸奔在公网。

至少要做到:

  1. 必须加鉴权
    示例里用了X-API-Token,不要删除。

  2. 只临时开放 cpolar
    用完就停止cpolar http 8000,不要长期挂着。

  3. 限制文件大小和类型
    不要允许任意文件上传。

  4. 谨慎处理敏感文档
    合同、客户资料、财务文件、内部方案,不建议随便通过公网接口传输。

  5. 不要使用高权限目录处理上传文件
    示例使用临时目录,并且文件名用 UUID 重写,避免路径穿越和覆盖本地文件。

  6. 注意 MarkItDown 的 I/O 权限
    MarkItDown 官方也提醒:转换器会以当前进程权限访问资源。处理不可信输入时,应该限制输入来源、文件类型和运行权限。

11. 小结

这套方案把文档转换拆成了三个清晰步骤:

MarkItDown 负责转换 FastAPI 负责上传接口 cpolar 负责临时公网访问

本地使用时,访问:

http://127.0.0.1:8000/convert

远程临时使用时,访问:

https://你的cpolar地址/convert

对团队协作来说,它的价值不是“又搭了一个服务”,而是把原本手动、零散的文档处理动作,变成一个可复用的 API:远程同事、脚本、自动化流程都能按同一套方式上传文件并拿回 Markdown。

如果只是临时协作,开 FastAPI + cpolar 就够了;如果要长期使用,再考虑队列、日志、权限系统、对象存储和更完整的审计能力。