返回博客

文档处理的边界在哪里:自建 vs. API 外包

做后端开发一段时间后,你会遇到一类需求,它们看起来并不复杂,但实现起来隐藏着大量工程量:把 Word 文档转成 PDF、从扫描件里提取表格、把合同里的手机号抹掉再分发。

这类需求有一个共同特征——表面上是「一行代码的事」,实际上是一个成熟软件产品才能覆盖的问题域。

文档处理的「冰山成本」

我曾经尝试自建 PDF 处理能力。第一个版本用 Python 的 reportlab 生成 PDF,看起来工作正常。然后需求来了:

  • 产品说「字体渲染不对,中文有乱码」
  • 客户上传了扫描件,不是文字 PDF,pdfplumber 提取出来全是空字符串
  • 需要把 Excel 表格里的内容嵌进 PDF 某几页
  • 合规部门要求分发前抹掉所有身份证号

每一条需求背后都是一套单独的工程。OCR 涉及语言模型的选型(Tesseract vs. PaddleOCR vs. 云 OCR)、扫描件质量预处理、多语言支持;格式转换涉及 LibreOffice 的无头模式部署、字体嵌入、页面布局兼容;PII 抹除涉及正则、预设规则、文档坐标系的像素级遮盖。

这些能力单独拿出来每一个都不算难,但组合在一起、部署在生产、处理真实用户上传的「野生」文档时,边界情况会把你淹没。

什么时候应该外包

我现在的判断标准很简单:如果文档处理不是你产品的核心差异化能力,就不要自建。

Nutrient DWS 这类专业文档 API 的价值不在于「它能做你做不到的事」,而在于它把一个复杂问题域的维护责任彻底转移出去。你用一个 multipart POST 请求,加一个 JSON 格式的 instructions 字段,就能描述一整条处理流水线:先 OCR、再抽取文本、最后抹掉 PII。

# 描述一条「OCR → 导出可搜索 PDF」流水线
curl -X POST https://api.nutrient.io/build \
  -H "Authorization: Bearer $NUTRIENT_API_KEY" \
  -F "scanned.pdf=@scanned.pdf" \
  -F 'instructions={"parts":[{"file":"scanned.pdf"}],"actions":[{"type":"ocr","language":"chi_sim"}]}' \
  -o searchable.pdf

这个设计的关键在于 instructions 是声明式的——你描述「要什么结果」,不描述「怎么做到」。格式兼容、语言模型选择、坐标系转换,全部封装在 API 内部。

声明式 vs. 命令式的工程选择

声明式接口的核心收益是将实现细节的演化与你的代码隔离。PII 抹除的正则规则更新了、OCR 模型换代了、某个格式的兼容性改善了——这些变化对你透明。命令式自建的代价恰好相反:你既是调用方,也是实现方,两个角色的维护负担全部落在你身上。

判断「外包」成本是否合理

外包给 API 也不是没有代价:接口依赖第三方稳定性、延迟变成网络 RTT 而不是本地计算、文档数据要出境(合规敏感场景需要审查)、按量付费的成本在高频场景下可能超过自建。

我的评估框架:

| 维度 | 倾向自建 | 倾向外包 | |------|---------|---------| | 数据合规 | 涉及高敏感数据、出境受限 | 通用文档、非核心业务数据 | | 调用频次 | 每天百万次以上 | 低频、峰值不稳定 | | 差异化需求 | 文档处理本身是产品核心 | 文档处理只是支撑流程 | | 团队能力 | 有专职文档/NLP 工程师 | 全栈小团队、人力有限 |

大多数业务系统落在「外包」那一列——文档处理是支撑性能力,不是产品的核心。这时候花几天自建 OCR 流水线,本质上是在用工程时间换取一个维护负担。

MCP 集成:给 Agent 加文档处理能力

值得单独提一句:这类 API 通常提供 MCP Server,可以直接作为工具注册进 AI Agent 的工具链。配置完成后,Agent 可以在推理过程中直接调用「转换这份合同为 PDF」「抹掉这个文件里的邮箱地址」这样的动作,不需要额外的胶水代码。

这是「外包」思路在 AI 工程场景下的自然延伸:把有明确规格、高可靠性要求的子任务,外包给专业工具,而不是让 LLM 自己生成代码去处理。

一句话心法

重新发明轮子的真实成本不在于第一次写出来,而在于之后每一次维护边界情况——把这笔账算进去,再决定要不要自建。