省 Token 的秘密:自建网页转 Markdown 服务

省 Token 的秘密:自建网页转 Markdown 服务

公司给配了 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 上搜了一圈,找到了 pullmdAeternaLabsHQ/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),Port3000(这是 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

分享或订阅:
🧡 喜欢我的内容?欢迎点击 订阅 RSS Feed 获取最新文章更新。