
公司给配了 Claude 企业版,理论上应该是够用的。但凡遇到要分析一批行业报告或产品信息,扔进去一堆pdf之类的东西,一个上午就能把当日配额消耗掉大半。费用倒不是问题,让人不舒服的是那种”还没做完事,额度就见底”的紧迫感。这促使我去想一件事:AI 处理信息是按 Token 计费的,那不同的文件格式,送进去的”字”究竟一不一样多?
输入格式影响多大?比想象的更显著
直觉上总觉得,同一份内容,换个格式应该差不多。实际不是。
大模型处理文本,靠的是将字符切分成 Token 来计量。Markdown 的语法开销极低——表示一个三级标题只需要三个 # 号,而 HTML 要写 <h3></h3>,Word 文档(.docx)本质上是一堆 ZIP 压缩的 XML,里面混杂着字号、颜色、字体、页边距等大量人类肉眼感知、但模型根本不需要的视觉元数据。
以下是一个大致的消耗量级对比(非精确测量值,实际会因内容结构不同而有偏差):
| 格式 | 相对 Token 消耗(Markdown = 100) |
|---|---|
| Markdown(.md) | 100(基准) |
| 纯文本(.txt) | 95–100 |
| HTML(.html) | 140–200+ |
| Word(.docx) | 250–400+ |
| PPT(.pptx) | 350–600+ |
| PDF(.pdf) | 200–800+(取决于解析质量) |
| 图片(JPG/PNG,1080P) | 约 1,100(按像素网格计算) |
图片的情况尤其值得单独说明:模型处理图片不是把像素”读成文字”,而是将图像切成固定大小的网格逐块处理。一张 1080P 图片消耗的 Token,相当于同等信息量 Markdown 文本的 10 倍以上。如果是合同扫描件、装箱单、发票之类本质是文字的图片,先过 OCR 再转成 Markdown 送进去,是最划算的做法。
问了问 AI,确认了这个判断
光凭推断还不放心,我把这个问题抛给了自己另外课金的 Gemini:不同文件格式输入给大模型,Token 消耗差距有多大?
结果和自己预判的方向基本一致:Markdown 是纯文本场景下最节省的格式,PDF 和 Office 系格式由于夹带了大量结构和样式元数据,消耗量会显著更高。最理想的操作路径,是在把内容交给模型之前,先把它”刮干净”,转成 Markdown。这让我意识到,日常工作中大量的分析成本,其实是被文件格式的噪声吃掉的。
找一个顺手的工具:能把网页变成干净 Markdown
思路有了,下一步是找工具。
微软开源了一个叫 MarkItDown 的 Python 库,专门做各类文件到 Markdown 的转换,支持 PDF、Word、Excel、PPT 等格式,转换质量相当不错。但它是纯命令行工具,每次用都要开终端,不适合日常高频使用。
在 GitHub 上搜了一圈,找到了 pullmd(AeternaLabsHQ/pullmd)。它的定位是一个自托管的网页 URL 转 Markdown 服务,最新的 v3 版本采用微服务架构,整合了 Trafilatura(静态页面正文提取)、Playwright(处理需要 JavaScript 渲染的动态页面),以及 MarkItDown(作为侧车服务,补足 Office/PDF 文档的转换能力),还支持 Reddit 帖子(包含完整评论树)的抓取,转换结果在 PWA 界面里直接展示、可一键复制。
这样一来,网页链接和 Office/PDF 文档两类需求就都被这一套服务覆盖了——每天需要喂给 AI 的内容里,行业资讯、研究报告网页、供应商官网、合同附件占了相当大的比例。
在 Easypanel 上部署全程记录
pullmd v3 由一个主服务加三个侧车(Trafilatura、Playwright、MarkItDown)组成。最干净的部署方式是通过 Easypanel 的 Compose 功能,直接导入 Docker Compose 配置。
硬件要求先确认
Playwright 镜像包含 Chromium、Firefox、WebKit 三套浏览器内核,体积约 3.7 GB。建议服务器留有:
- 至少 5 GB 可用磁盘空间
- 至少 2 GB 可用内存
第一步:创建 Compose
登录 Easypanel 管理面板,点击 Create Project,命名为 pullmd。进入项目后,点击 Go to Project,选择右上角的 Create → Compose(通过 Docker Compose 创建)。
第二步:Docker Compose 配置
把以下配置粘贴进 Compose 的编辑框。这份配置已经根据实际部署中遇到的告警做过清理——去掉了废弃的 version 字段和会与 Easypanel 命名机制冲突的 container_name:
services:
pullmd:
image: aeternalabshq/pullmd:latest
restart: unless-stopped
environment:
- PUBLIC_URL=https://${APP_DOMAIN}
- TRAFILATURA_URL=http://trafilatura:8001/extract
- PLAYWRIGHT_URL=http://playwright:8002/render
- MARKITDOWN_URL=http://markitdown:8003/convert
- CACHE_DB=/data/cache.db
volumes:
- data:/data
depends_on:
- trafilatura
- playwright
- markitdown
trafilatura:
image: aeternalabshq/pullmd-trafilatura:latest
restart: unless-stopped
playwright:
image: aeternalabshq/pullmd-playwright:latest
restart: unless-stopped
markitdown:
image: aeternalabshq/pullmd-markitdown:latest
restart: unless-stopped
volumes:
data:
几点说明:
- 不要加
version:和container_name:——现代 Docker Compose 规范已不需要顶部的 version 声明;container_name 则会与 Easypanel 自身的容器命名机制冲突,Easypanel 会自动为容器分配合规的名字。 ${APP_DOMAIN}变量:这是 Easypanel 的动态变量,在还没绑定域名之前会显示”variable is not set”的告警,这并不影响镜像拉取和服务启动。等第三步绑定好域名后,这条告警会在下次部署时自动消失。
第三步:绑定域名
进入 pullmd 主服务 → Domains 标签页 → Add Domain,填入准备好的域名(如 pullmd.yourdomain.com),Port 填 3000(这是 pullmd 的内部默认监听端口),保存。
绑定成功后,Easypanel 会自动把域名注入到 Compose 文件的 ${APP_DOMAIN} 变量里。如果暂时只想用 IP + 端口访问、不想等域名解析,也可以直接在环境变量里把 PUBLIC_URL 写死成实际地址或服务器 IP,同样能正常工作。
第四步:部署
回到 Compose 主页,点击 Deploy。四个镜像依次拉取,首次因 Playwright 体积较大约需 3–5 分钟,等状态全部变为绿色 Running 即可。
部署过程中如果看到 container_name、version 或 APP_DOMAIN 相关的黄色告警,不必紧张——它们都是提示性质,不会导致部署失败。按上面清理过的配置操作,前两项告警不会再出现,第三项在绑定域名后也会自然消失。
可选:开启图片解析与语音转录
如果需要用到 pullmd v3 的图片描述(Vision)或音频转文字(STT)功能,可以在 pullmd 主服务的 Environment 标签页里补充相应的 API Key、Base URL 和模型名参数,两类功能都是走 OpenAI 兼容接口,按需接入即可,不影响基础的网页转 Markdown 功能。
实际使用下来的感受
部署完成后,通过绑定的域名打开就是一个简洁的 PWA 界面。粘入网页链接,几秒后干净的 Markdown 就出来了,可以直接复制发给 Claude 分析,不再夹带导航栏、广告、页脚等无关内容的噪声。
对比之前直接把原始网页链接或 HTML 丢进去,同等内容的 Token 消耗下降明显。更重要的是,转换后的文本结构更清晰,模型回答的质量也跟着有所提升——输入的噪声少了,输出自然更准。
工具本身不复杂,但把它接入日常工作流之后,那种”额度莫名其妙就用光了”的感觉确实少了很多。
pullmd 项目地址:github.com/AeternaLabsHQ/pullmd
.png)

