PDF 工具最容易被低估的部分,不是“能不能打开文件”,而是文件进入系统之后如何被稳定处理。普通文本型 PDF 可以直接复制内容,扫描件却只有图片;表格、页眉页脚、双栏排版又会让简单的文本抽取产生错序。把文件放在 NAS 上,还要额外面对权限、远程访问、数据备份和接口密钥泄露等问题。
一个适合个人或小团队的方案,应当把任务拆成几个边界清晰的阶段:
- 文件上传与原始文件保存。
- 判断 PDF 是否含有可用文本层。
- 对扫描页执行 OCR,并保留页码、坐标或置信度等证据。
- 对文本做清洗、分段和结构化抽取。
- 让模型只处理必要内容,并记录输入版本、输出结果和人工修订。
- 通过反向代理提供远程访问,限制暴露面。
这样做的目的不是把所有工作交给模型,而是让每一步都能单独重试、审查和替换。
整体原理
文本层与图像层分流
PDF 页面可能包含字符对象,也可能只是嵌入图片。首先检查文本层长度和可读性:如果连续多页几乎没有文本,就应转入 OCR;如果文本存在但顺序混乱,可以先尝试版面分析,再决定是否对特定页面重新识别。
OCR 的结果不应只保存为一段纯文本。至少应保留 page、text 和可选的 bbox、confidence 字段。页码是后续引用和人工复核的最小证据;坐标可用于定位表格单元格或高亮原文。不同 OCR 引擎的字段名称和置信度含义可能不同,落库前应统一自己的数据格式。
中间格式隔离模型
模型不直接读取原始 PDF,而是读取经过清洗的文档块。例如:
{
"document_id": "contract-2026-001",
"pages": [
{
"page": 3,
"text": "付款条件:验收合格后十五个工作日内付款。",
"source": "ocr",
"confidence": 0.93
}
]
}
中间格式带来三个好处:OCR 引擎可以替换;模型请求可以脱敏和限长;最终结论能够回指页码,而不是只保留一段无法核验的摘要。
模型接入的边界
模型适合做分类、字段抽取、摘要和风险提示,但不应被当作原文事实库。对合同、发票、投标文件等材料,建议要求模型返回固定 JSON,并在服务端执行字段校验。涉及金额、日期、主体名称等关键字段时,还应使用规则或原文比对进行二次检查。
如果选择 HaerAPI 或其他中转接口,需要先确认其当前是否提供目标模型、认证方式、请求格式、数据保留政策和地域合规信息。下面的代码只依赖常见的 OpenAI 兼容请求形态,不能据此推断任何服务一定支持该接口。
部署自托管服务
以下 Compose 片段演示一个通用文档服务的部署骨架。镜像名、端口和环境变量必须替换为所选 PDF 服务的当前文档值;示例不代表某个具体产品的默认配置。
services:
pdf-worker:
image: your-registry/pdf-worker:stable
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
volumes:
- ./data:/var/lib/pdf-worker
- ./jobs:/var/lib/pdf-worker/jobs
environment:
TZ: Asia/Shanghai
OCR_LANGUAGE: chi_sim+eng
MAX_UPLOAD_MB: "50"
启动前创建持久化目录,并把权限交给容器实际运行用户:
mkdir -p data jobs
chmod 750 data jobs
docker compose up -d
将端口绑定到 127.0.0.1 是一个保守起点,表示服务只接受本机访问。需要远程使用时,应由 Nginx、Caddy 或 NAS 自带的反向代理承接 HTTPS、身份认证和访问日志,而不是直接把容器端口暴露到公网。
构建抽取接口
下面的 Python 示例读取已经完成 OCR 的结构化数据,调用一个可配置的模型端点,并对返回结果做最小校验。密钥从环境变量获取,程序不会把密钥写入代码或日志。
import json
import os
from pathlib import Path
import requests
API_URL = os.environ["MODEL_API_URL"]
API_KEY = os.environ["MODEL_API_KEY"]
MODEL = os.environ["MODEL_NAME"]
source = json.loads(Path("ocr-result.json").read_text(encoding="utf-8"))
context = "\n".join(
f"[第{item['page']}页] {item['text']}"
for item in source["pages"]
if item.get("text", "").strip()
)
prompt = f"""请从以下文档中抽取字段,只返回合法 JSON:
{
{"parties": [], "amounts": [], "deadlines": [], "risks": []}}
每个结论附上 page 字段;找不到时使用空数组,不要猜测。
文档内容:
{context[:50000]}"""
response = requests.post(
API_URL,
headers={
"Authorization": f"Bearer {API_KEY}"},
json={
"model": MODEL,
"temperature": 0,
"messages": [
{
"role": "system", "content": "你是文档抽取器。"},
{
"role": "user", "content": prompt},
],
},
timeout=90,
)
response.raise_for_status()
body = response.json()
content = body["choices"][0]["message"]["content"]
result = json.loads(content)
for key in ("parties", "amounts", "deadlines", "risks"):
if not isinstance(result.get(key), list):
raise ValueError(f"字段格式错误:{key}")
Path("extracted-result.json").write_text(
json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8"
)
设置变量时使用当前服务文档中的实际 URL 和模型标识:
export MODEL_API_URL="https://example.invalid/v1/chat/completions"
export MODEL_API_KEY="从密钥管理系统注入"
export MODEL_NAME="在服务商文档中确认的模型名"
python extract.py
生产环境不要把变量直接写入公开的 Shell 历史记录。可以使用 Docker secrets、NAS 的密钥管理功能或 CI/CD 的受保护变量。日志中也应过滤 Authorization、完整提示词和可能包含个人信息的原文。
远程访问与任务治理
远程访问至少要设置四层约束:
- 传输层:只通过 HTTPS,证书续期和域名解析由反向代理管理。
- 身份层:启用应用自身账户,或在代理层增加单点登录、访问白名单和多因素认证。
- 文件层:限制单文件大小、总存储量和允许的 MIME 类型,压缩包与脚本文件不要当作普通文档处理。
- 任务层:为每个任务分配唯一 ID,状态使用
queued、running、succeeded、failed等有限集合,并保存错误原因和重试次数。
长文档处理不宜由 HTTP 请求同步等待。上传后立即返回任务 ID,由后台 worker 执行文本提取、OCR 和模型调用;前端通过轮询或事件订阅查询状态。重试时只重做失败阶段,避免每次都重新上传和重新计费。对模型请求还应设置超时、并发上限和输入长度上限。
备份应同时覆盖原始文件、OCR 结果、结构化结果和任务元数据。只备份最终摘要无法恢复处理过程,也无法在模型更换后重新生成结果。恢复演练应确认备份可读、文件权限正确、任务不会重复执行。
常见问题
为什么 OCR 结果不能直接交给模型?
OCR 可能存在错字、漏字、页序异常和表格错位。模型有时会根据上下文“补全”缺失内容,形成看似合理但无法在原文中找到的结论。因此应保留原页图像或原始 PDF,并要求输出页码;关键字段还要回到原文核对。
为什么不把整个 PDF 一次发送?
输入长度、隐私暴露范围和费用都会随全文增长。更稳妥的做法是先按页或章节切块,只把与任务相关的片段送入模型。切块时保留文档 ID、页码和段落序号,避免截断表格标题或条件说明。
中转接口是否一定更方便?
不一定。中转接口可能统一了请求格式,但实际可用模型、限额、故障表现、数据处理方式和合规责任需要逐项确认。对敏感文档,先使用脱敏样本验证链路,再决定是否上线;不能仅凭“兼容某种 API”推断其具备企业级审计或隐私能力。
如何处理模型返回的非 JSON 内容?
服务端不能直接信任返回值。应先记录任务 ID 和响应摘要,再尝试解析 JSON;解析失败时将任务标记为可重试或人工处理。对于金额、日期、枚举值和页码,增加类型、范围和原文存在性校验。
NAS 性能不足怎么办?
先区分瓶颈是磁盘 I/O、OCR CPU、模型网络延迟还是并发队列。可将 OCR worker 与 Web 服务分开,并限制 worker 数量;若调用外部模型,尽量传输清洗后的文本而不是大图。没有实际监控数据时,不应预先断言某种硬件一定足够。
总结
自托管 PDF 工具的关键不在于一次性部署成功,而在于形成可检查的处理链路:原始文件持久化,文本与 OCR 分流,结果带页码证据,模型通过稳定的中间格式接入,任务支持重试和回滚,远程访问由 HTTPS 与身份控制保护。
将 OCR、规则校验和模型抽取分层后,系统可以按需替换单个组件,也更容易解释错误来自哪一步。对于个人资料和企业文件,最终上线前还应结合数据分类、访问主体、保留期限和第三方处理条款进行评估。技术链路可用,不等于业务结论已经可信;可复核性应当成为交付标准的一部分。