首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >PDF 全文翻译,保留样式,大模型加持的全新方案,附核心代码

PDF 全文翻译,保留样式,大模型加持的全新方案,附核心代码

作者头像
Ai学习的老章
发布2026-03-02 20:19:37
发布2026-03-02 20:19:37
230
举报

Claude 这份创建 Skills 指南非常有价值

但是学习起来有点低效,我要将其翻译成中文

四名选手参加(后面有 GTP-5.2-Codex 参赛)

K2.5 其实中间找到了一个开源项目PDF2Zh,计划按要求实行翻译,但是部署时遇到了 bug,我有点不太喜欢成熟方案,太重了。K2.5 发现部署有麻烦后,也转为了下面方案,我没有让它继续

Gemini-3-Pro 和 Claude-Opus-4.5 不约而同偷懒,Gemini-3-Pro 选择了最简单的 PDF 抽取文字翻译后重构为结构清晰的 Markdown 文档,然后生成支持中文渲染的 HTML 页面,并利用 Google Chrome 的 Headless 打印成 PDF

Claude-Opus-4.5 选择了原始 PDF → pdf2docx 转 DOCX → 翻译 DOCX 内容 → docx2pdf 转回 PDF,感觉前者更省事儿一些。

我觉得挺好的,唯一缺点就是没有保留原始样式

GPT-5.2-Codex 严格遵守的我的要求——保留样式

核心思路是“两阶段 + 缓存”:

  1. 文本抽取与翻译阶段
  • 用 PyMuPDF 读取 PDF,每页提取文本块(bbox、字号、文本内容)
  • 调用模型 API 翻译文本块,按块 ID 存入翻译缓存
  • 翻译缓存落盘,支持中断续跑
  1. 重新排版与导出阶段
  • 把每一页先渲染成位图,避免字体嵌入导致的问号问题
  • 在每个原文字块区域先画白底,彻底覆盖英文
  • 按区域宽高自动缩放字号,回填中文文本
  • 最后把所有页面合成为 PDF

这里只有一点瑕疵,就是文字白底,我尝试多次,它没有消除这个 bug

瑕不掩瑜

GPT-5.2-Codex 帮我写了 Python 脚本,其他英文 PDF 也可以直接执行

当然下一步也可以做成 Skills,这个下篇文章我再介绍

核心代码:

代码语言:javascript
复制
import json
import re
import fitz
import requests
from PIL import Image, ImageDraw, ImageFont

def extract_blocks(input_pdf):
    doc = fitz.open(str(input_pdf))
    blocks = []
    for p in range(len(doc)):
        d = doc[p].get_text("dict")
        for b in d.get("blocks", []):
            if b.get("type") != 0:
                continue
            lines, sizes = [], []
            for line in b.get("lines", []):
                spans = line.get("spans", [])
                t = "".join(s.get("text", "") for s in spans)
                if t.strip():
                    lines.append(t)
                for s in spans:
                    if"size"in s:
                        sizes.append(s["size"])
            text = "\n".join(lines).strip()
            ifnot text:
                continue
            blocks.append({
                "page": p,
                "bbox": b.get("bbox"),
                "text": text,
                "font_size": sizes[len(sizes)//2] if sizes else10
            })
    return blocks

def call_translate(api_base, model, api_key, items):
    payload = {
        "model": model,
        "messages": [
            {"role": "system", "content": "Translate English to Simplified Chinese. Keep line breaks. Return JSON."},
            {"role": "user", "content": json.dumps({"items": items}, ensure_ascii=False)},
        ],
        "temperature": 0.2,
        "response_format": {"type": "json_object"},
    }
    headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
    r = requests.post(f"{api_base.rstrip('/')}/chat/completions", json=payload, headers=headers, timeout=120)
    r.raise_for_status()
    content = r.json()["choices"][0]["message"]["content"]
    m = re.search(r"\{.*\}\s*$", content, re.S)
    if m:
        content = m.group(0)
    return json.loads(content)["items"]

def translate_blocks(blocks, api_base, model, api_key, batch_size=8):
    cache = {}
    pending = [(i, b["text"]) for i, b in enumerate(blocks)]
    for i in range(0, len(pending), batch_size):
        batch = pending[i:i+batch_size]
        items = [{"id": idx, "text": txt} for idx, txt in batch]
        translated = call_translate(api_base, model, api_key, items)
        for it in translated:
            cache[int(it["id"])] = it["text"]
    return cache

def build_pdf(input_pdf, output_pdf, blocks, translations, fontfile, dpi=200, min_font_size=3):
    doc = fitz.open(str(input_pdf))
    scale = dpi / 72.0
    font_cache = {}

    def get_font(size):
        key = int(size * scale)
        if key notin font_cache:
            font_cache[key] = ImageFont.truetype(fontfile, key)
        return font_cache[key]

    pages = []
    for p in range(len(doc)):
        pix = doc[p].get_pixmap(dpi=dpi, alpha=False)
        img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
        draw = ImageDraw.Draw(img)

        for idx, b in enumerate(blocks):
            if b["page"] != p:
                continue
            rect = fitz.Rect(b["bbox"])
            x0, y0, x1, y1 = rect.x0*scale, rect.y0*scale, rect.x1*scale, rect.y1*scale

            # 覆盖英文 + 去灰条
            draw.rectangle([x0, y0, x1, y1], fill=(255, 255, 255))

            text = translations.get(idx, b["text"]).replace("\t", "    ")
            size = max(float(b.get("font_size") or10), min_font_size)
            font = get_font(size)

            # 简化版直接写入(完整版里有自动缩放和换行)
            draw.text((x0, y0), text, font=font, fill=(0, 0, 0))

        pages.append(img)

    first, rest = pages[0], pages[1:]
    first.save(str(output_pdf), "PDF", save_all=True, append_images=rest, resolution=dpi)

使用方法

环境准备

安装依赖:

代码语言:javascript
复制
python3 -m pip install pymupdf pillow requests

配置 API Key(示例):

代码语言:javascript
复制
export SILICONFLOW_API_KEY="YOUR_API_KEY"

可选配置:

代码语言:javascript
复制
export SILICONFLOW_API_BASE="https://api.siliconflow.cn/v1"
export SILICONFLOW_MODEL="MiniMaxAI/MiniMax-M2"

最简命令

代码语言:javascript
复制
python3 -B /Users/zz/Library/Mobile\ Documents/iCloud~md~obsidian/Documents/zhangAI/tmp/pdfs/translate_pdf.py \
  --input-pdf /absolute/path/to/source.pdf

常用命令(指定输出与缓存目录)

代码语言:javascript
复制
python3 -B /Users/zz/Library/Mobile\ Documents/iCloud~md~obsidian/Documents/zhangAI/tmp/pdfs/translate_pdf.py \
  --input-pdf /absolute/path/to/source.pdf \
  --output-pdf /absolute/path/to/source-zh.pdf \
  --work-dir /absolute/path/to/tmp/pdfs

指定中文字体(推荐)

代码语言:javascript
复制
python3 -B /Users/zz/Library/Mobile\ Documents/iCloud~md~obsidian/Documents/zhangAI/tmp/pdfs/translate_pdf.py \
  --input-pdf /absolute/path/to/source.pdf \
  --font /System/Library/Fonts/Supplemental/Songti.ttc

常见问题

  1. 看到问号或乱码
  • 原因:字体不支持中文。
  • 处理:使用 --font 指定可用 CJK 字体。
  1. 部分文本太小或挤压
  • 原因:中文长度通常大于英文,原块高度不足。
  • 处理:提高 --dpi 或调整 --min-font-size
  1. 重跑很慢
  • 原因:重新翻译全部文本。
  • 处理:保留 <stem>.translations.json,脚本会自动续跑。
  1. 英文没有完全隐藏
  • 处理:当前脚本已对文本块区域强制白底覆盖;若个别英文仍存在,通常是未被识别为文本块(例如图像里的文字)。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 机器学习与统计学 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用方法
  • 环境准备
  • 最简命令
  • 常用命令(指定输出与缓存目录)
  • 指定中文字体(推荐)
  • 常见问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档