个人站长为什么离不开 PDF 处理
做一个内容站或工具站,你迟早会遇到一堆和 PDF 打交道的事:给访客提供一份下载资料,需要合并、压缩、加水印;把合同或者发票打上页码归档;把扫描件里的文字提取出来;把一堆图片拼成一个 PDF;给文档加密设个打开密码;把 PDF 拆页或者旋转方向不对的页面转正。
这些操作如果去找在线网站,问题有三个:一是隐私——你的合同、发票、身份材料全都上传到了别人的服务器上;二是有文件大小和次数限制,稍微大一点就要付费;三是那些站点广告满天飞,还经常挂掉。装个桌面版 Adobe Acrobat 又是几百上千块,对只是偶尔用一下的个人站长来说不值。
Stirling-PDF 就是为解决这个问题而生的:一个完全开源的、可以自托管的 PDF 工具箱,把所有常用的 PDF 操作做成网页界面,跑在你自己的服务器上。它底层用 Java + PDFBox 和 LibreOffice 做处理,功能覆盖合并、拆分、压缩、转换、OCR、签名、水印、加密等几十项。本文把它部署起来,并讲清楚几个关键配置和容易踩的坑。
先看它能干什么:先确认它合不合适
Stirling-PDF 的功能可以分成几大类,先扫一眼,确认覆盖你的需求:
- 页面操作:合并、拆分、提取页面、删除页面、旋转、调整页面顺序、裁剪、多页合一。
- 格式转换:图片转 PDF、PDF 转图片、Office 文档(Word/Excel/PPT)转 PDF、HTML 转 PDF、PDF 转文本、Markdown 转 PDF。
- 优化:压缩减小体积、修复损坏的 PDF、去除元数据。
- 安全:加密加密码、移除密码、给文件打水印、数字签名验签、涂黑敏感信息(redact)。
- 识别与处理:对扫描件做 OCR 提取文字、自动识别并纠正歪斜的扫描页。
它跟前面我们写过的 Paperless-ngx 定位不同:Paperless-ngx 是一个文档管理系统,重点是「归档 + 全文检索 + 自动分类」,你上传的文档它替你长期管着;Stirling-PDF 是一个手动的工具箱,你上传一个文件、做一步处理、下载回来,重点是「处理」而不是「管理」。两个可以配合用。
Docker 部署:一个命令的事
Stirling-PDF 官方镜像叫 stirlingtools/stirling-pdf。最简部署:
mkdir -p /opt/stirling/{configs,trainingData,logs,extraConfigs}
cd /opt/stirling
写 docker-compose.yml:
services:
stirling-pdf:
image: stirlingtools/stirling-pdf:latest
container_name: stirling-pdf
environment:
- DOCKER_ENABLE_SECURITY=false
- LANGS=zh_CN
- TZ=Asia/Shanghai
volumes:
- ./configs:/configs
- ./logs:/logs
- ./extraConfigs:/configs/custom
ports:
- "127.0.0.1:8080:8080"
restart: unless-stopped
几个配置点的含义:
LANGS=zh_CN是为了启用中文界面。默认镜像为了控制体积只带英文,指定这个环境变量后镜像会加载中文语言包。DOCKER_ENABLE_SECURITY=false表示先不开登录认证。因为端口已经绑在127.0.0.1上,外人访问不到,认证交给 Nginx 那一层。如果你想在容器内就启用登录,把它改成true并配置用户名密码。- 挂载
/configs是为了让配置持久化,重启不丢。
启动:
docker compose up -d docker compose logs -f stirling-pdf
第一次启动会慢一些,因为它要初始化 Java 环境和一些依赖,日志里出现 Started Application 之类才算就绪。之后浏览器访问 http://127.0.0.1:8080 就能看到界面。用 SSH 隧道临时看一眼:
ssh -L 8080:127.0.0.1:8080 root@你的服务器IP
Office 转 PDF 与 OCR:两个最容易翻车的功能
Stirling-PDF 大部分功能开箱即用,但有两个功能对依赖特别敏感,必须先配好,否则要么报错要么出乱码。
第一,Office 文档转 PDF。 把 Word/Excel/PPT 转成 PDF 靠的是 LibreOffice。官方镜像里已经内置了它,但中文字体是个常见的坑——如果容器里没有安装中文字体,转换出来的 PDF 里中文会变成一排方块或者问号,而英文正常,很容易误判成「功能坏了」。
解决办法是在启动时安装字体,或者用一个挂了字体目录的镜像。最省事的做法是建一个自定义 Dockerfile:
FROM stirlingtools/stirling-pdf:latest USER root RUN apt-get update && apt-get install -y fonts-noto-cjk && rm -rf /var/lib/apt/lists/*
然后 docker build -t stirling-pdf-cn .,compose 里的 image 改成 stirling-pdf-cn。这样中文转换就不会出方块了。
第二,扫描件 OCR。 OCR 依赖 Tesseract,中文识别需要单独安装 tesseract-ocr-chi-sim 语言包。默认镜像往往只带英文。如果对一张中文扫描件做 OCR,出来的结果全是英文垃圾字符,就是这个原因。同样用上面的自定义 Dockerfile 加上 tesseract-ocr-chi-sim 即可。注意 Tesseract 的中文包命名是 chi_sim(简体)和 chi_tra(繁体),不是 ISO 语言码 zh-CN,这是很多人第一次配置时找不到包的原因。
Nginx 反向代理:别忘了放大上传体积
PDF 文件通常都不小,几十兆很常见,扫描件合集上百兆也有。Nginx 默认 client_max_body_size 只有 1M,超过就直接返回 413,页面提示「文件太大」,你会以为是 Stirling-PDF 的问题,其实是 Nginx 拦在了前面。
server {
listen 443 ssl http2;
server_name pdf.example.com;
ssl_certificate /etc/letsencrypt/live/pdf.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pdf.example.com/privkey.pem;
# 放大上传体积,建议 200M 起
client_max_body_size 200M;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 处理大文件时可能耗时较长,拉长超时
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
两处关键:client_max_body_size 200M 解决上传被拒;proxy_read_timeout 300s 解决大文件处理时连接被 Nginx 提前掐断。OCR 和 Office 转换都是耗时操作,几十秒很正常,超时太短会让你以为卡死了。
加一层访问控制:别把它直接对着公网
Stirling-PDF 能处理你的合同、发票、身份材料,绝不能让任何人访问到。除了端口绑回环,对外那一层要么用 HTTP Basic 认证,要么用更强的认证网关。
最轻量的做法是用 htpasswd:
htpasswd -c /etc/nginx/.htpasswd myuser
然后在 server 块里加:
location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:8080;
# ... 其余反代头
}
如果站点本身已经有认证网关(比如 Authelia),用 forward auth 更优雅,一次登录全站通行。无论哪种方式,原则都是:这类「能碰你敏感文件」的服务永远要有认证,而且优先考虑干脆只在内网/VPN 里访问,不暴露公网。
把它做成一个命令行的 PDF 处理入口
Stirling-PDF 除了网页界面,也暴露了 REST API,可以写进脚本里批量处理。比如批量压缩一个目录下的 PDF:
#!/bin/bash
# 需要先在网页界面生成一个 API Key,或使用认证
API=https://pdf.example.com
KEY=你的APIKey
for f in /data/pdfs/*.pdf; do
curl -s -X POST "$API/api/v1/misc/compress-pdf" \
-H "X-API-KEY: $KEY" \
-F "fileInput=@$f" \
-F "optimizeLevel=2" \
-o "${f%.pdf}_compressed.pdf"
done
API 路径会随版本变化,具体以你部署版本的 /swagger-ui(自带接口文档)为准。这个能力很适合把它接进你的内容流水线——比如访客上传照片生成 PDF 资料、或者定时把站点的某个报表转成 PDF 分发。
资源占用与性能取舍
Stirling-PDF 是 Java 应用,对内存有一定要求。Java 堆默认可能吃几百 MB 到 1G,如果你的服务器内存只有 1G,会和 MySQL、PHP 抢内存。可以在 compose 里限制:
deploy:
resources:
limits:
memory: 1g
另外注意,OCR 和 Office 转 PDF 是 CPU 密集操作,并发几个人同时用会把 CPU 打满。个人站一般不会有高并发,但如果对公网开放,最好限一下并发数,避免被人当成免费的 PDF 处理服务刷爆你的机器。可以在 Nginx 里加 limit_req 对上传接口做限速。
备份与升级:升级前一定要做的事
Stirling-PDF 本身是无状态的——它不像数据库那样存着你的业务数据,处理的文件都是「即用即走」,处理完就删。所以它没有「数据备份」的强需求,真正需要备份的只有两样:/configs 里的配置文件(比如你设的认证、界面偏好、API Key),以及你自定义的 Dockerfile 和字体安装脚本。
把这两样东西放进 Git 或者用 restic/rsync 定期同步到异地,就足够应付绝大多数意外了。真正要小心的是升级:Stirling-PDF 迭代很快,新版本偶尔会改动 API 路径、界面结构甚至配置项名称。升级前先把 compose 里的镜像 tag 从 latest 固定成一个具体版本号(比如 0.x.x),并记录当前能正常工作的版本。这样一旦新版有问题,改回旧 tag 就能立刻回滚,不用去猜「到底哪个版本是好的」。
升级动作本身就是:
cd /opt/stirling docker compose pull docker compose up -d
因为用了固定 tag,pull 只会在你主动改了 tag 之后才拉到新版本,日常不会因为官方发了新版就意外变动,这对生产环境很重要。
六个常见坑
- 上传大文件报 413:99% 是 Nginx
client_max_body_size默认 1M 太小,不是 Stirling-PDF 的问题。 - Office 转出来的中文是方块:容器里没装中文字体(
fonts-noto-cjk),英文正常中文乱码是典型特征。 - OCR 中文识别出英文垃圾:没装
tesseract-ocr-chi-sim语言包,且要注意语言码不是zh-CN。 - 处理大文件时页面一直转圈最后失败:Nginx 的
proxy_read_timeout太短,OCR/转换耗时几十秒被提前切断。 - 端口暴露到公网且没认证:等于把「处理你敏感文件」的工具交给全世界,务必绑回环 + 加认证。
- 容器重启后配置丢失:忘了挂载
/configs卷,或者自定义的字体/Dockerfile 没固化进镜像,重建后又变回默认状态。
小结
Stirling-PDF 补上了自托管工具链里一块很实用的拼图:把日常那些零散的 PDF 操作从「上传到陌生网站」变成「跑在自己机器上」。部署不复杂——Docker 起容器、绑回环、Nginx 反代加认证、按需装中文字体和中英文 OCR 包就能正常干活。
几个要点再强调一次:client_max_body_size 一定要放大,中文场景要装 fonts-noto-cjk 和 tesseract-ocr-chi-sim,反代超时拉到 300 秒,访问控制绝不能省。把这几点做到位,你会得到一个既省心又保隐私的私人 PDF 工作站。