Python+PyMuPDF提取PDF表格并翻译:表格结构检测与双语重建实战

前言

在PDF翻译场景中,表格是最容易出问题的部分。纯文本翻译工具会把表格内容拆散,翻译后表格结构完全错乱。本文分享一个用PyMuPDF检测PDF表格结构、提取单元格文本、调用翻译API后重建双语表格的完整方案。

环境准备

  • Python 3.10+
  • 依赖:pip install PyMuPDF requests
python 复制代码
import fitz  # PyMuPDF
import requests
import json
import re
from typing import List, Dict, Tuple

实现步骤

Step 1: 检测PDF页面中的表格区域

PyMuPDF提供了page.find_tables()方法(1.23.0+),可以自动检测页面中的表格区域:

python 复制代码
def detect_tables(pdf_path: str) -> List[Dict]:
    """检测PDF中所有页面的表格
    
    Args:
        pdf_path: PDF文件路径
        
    Returns:
        表格信息列表,每个元素包含页码、表格边界、单元格内容
    """
    doc = fitz.open(pdf_path)
    tables_info = []
    
    for page_num in range(len(doc)):
        page = doc[page_num]
        
        # 使用PyMuPDF内置表格检测
        tables = page.find_tables()
        
        for table_idx, table in enumerate(tables):
            # 获取表格边界
            bbox = table.bbox  # (x0, y0, x1, y1)
            
            # 提取单元格内容
            cells = []
            for row in table.extract():
                cells.append([
                    cell.strip() if cell else "" 
                    for cell in row
                ])
            
            tables_info.append({
                "page": page_num,
                "table_idx": table_idx,
                "bbox": bbox,
                "rows": len(cells),
                "cols": len(cells[0]) if cells else 0,
                "cells": cells
            })
    
    doc.close()
    return tables_info

Step 2: 翻译表格文本内容

提取到表格单元格后,批量调用翻译API:

python 复制代码
def translate_text(text: str, target_lang: str = "zh") -> str:
    """调用翻译API翻译文本
    
    Args:
        text: 待翻译文本
        target_lang: 目标语言代码
        
    Returns:
        翻译后的文本
    """
    if not text.strip():
        return text
    
    # 使用PDFTranslator API或自建翻译服务
    # 这里用开源翻译API作为示例
    url = "https://api.pdftranslator.org/translate"
    payload = {
        "text": text,
        "target_lang": target_lang,
        "source_lang": "auto"
    }
    
    try:
        resp = requests.post(url, json=payload, timeout=30)
        resp.raise_for_status()
        result = resp.json()
        return result.get("translated_text", text)
    except Exception as e:
        print(f"翻译失败: {text[:30]}... -> {e}")
        return text  # 失败时返回原文


def translate_table_cells(tables_info: List[Dict]) -> List[Dict]:
    """翻译所有表格单元格
    
    Args:
        tables_info: 表格信息列表
        
    Returns:
        带翻译结果的表格信息列表
    """
    for table in tables_info:
        translated_cells = []
        for row in table["cells"]:
            translated_row = []
            for cell in row:
                translated = translate_text(cell)
                translated_row.append({
                    "original": cell,
                    "translated": translated,
                    "bilingual": f"{cell}\n{translated}" if cell else ""
                })
            translated_cells.append(translated_row)
        
        table["translated_cells"] = translated_cells
    
    return tables_info

Step 3: 在原PDF上重建双语表格

将翻译结果写回PDF,保留原始表格位置,在原文下方添加译文:

python 复制代码
def rebuild_bilingual_table(pdf_path: str, output_path: str, 
                             tables_info: List[Dict]):
    """在原PDF上重建双语表格
    
    策略:在每个单元格原文下方插入译文
    使用红色标注译文以区分
    
    Args:
        pdf_path: 原始PDF路径
        output_path: 输出PDF路径
        tables_info: 包含翻译结果的表格信息
    """
    doc = fitz.open(pdf_path)
    
    for table in tables_info:
        page = doc[table["page"]]
        bbox = table["bbox"]
        
        # 计算每个单元格的宽高
        cell_width = (bbox[2] - bbox[0]) / table["cols"]
        cell_height = (bbox[3] - bbox[1]) / table["rows"]
        
        for row_idx, row in enumerate(table["translated_cells"]):
            for col_idx, cell_data in enumerate(row):
                if not cell_data["translated"]:
                    continue
                
                # 计算单元格位置
                x0 = bbox[0] + col_idx * cell_width + 2
                y0 = bbox[1] + row_idx * cell_height + 2
                
                # 在原文下方插入译文(红色)
                # 先覆盖原文区域用白色矩形
                rect = fitz.Rect(x0, y0 + cell_height * 0.5, 
                               x0 + cell_width - 4, 
                               bbox[1] + (row_idx + 1) * cell_height - 2)
                
                page.draw_rect(rect, color=None, fill=(1, 1, 1))
                
                # 插入译文
                page.insert_text(
                    (x0, y0 + cell_height * 0.7),
                    cell_data["translated"],
                    fontsize=7,
                    color=(0.8, 0.2, 0.2),  # 红色标注译文
                    fontname="helv"
                )
    
    doc.save(output_path)
    doc.close()
    print(f"双语表格PDF已保存: {output_path}")

Step 4: 处理合并单元格和复杂表格

实际PDF中的表格往往包含合并单元格,需要额外处理:

python 复制代码
def detect_merged_cells(page, table) -> List[List[Dict]]:
    """检测合并单元格情况
    
    通过分析表格线的位置判断哪些单元格被合并了
    """
    # 获取表格的水平线和垂直线
    drawings = page.get_drawings()
    
    h_lines = []  # 水平线
    v_lines = []  # 垂直线
    
    for draw in drawings:
        for item in draw["items"]:
            if item[0] == "l":  # line
                p1, p2 = item[1], item[2]
                if abs(p1.y - p2.y) < 1:  # 水平线
                    h_lines.append(p1.y)
                elif abs(p1.x - p2.x) < 1:  # 垂直线
                    v_lines.append(p1.x)
    
    # 去重并排序
    h_lines = sorted(set(round(y, 1) for y in h_lines))
    v_lines = sorted(set(round(x, 1) for x in v_lines))
    
    return h_lines, v_lines

完整代码

python 复制代码
#!/usr/bin/env python3
"""PDF表格提取、翻译与双语重建工具

用法: python table_translate.py input.pdf output.pdf --target-lang zh
"""

import fitz
import requests
import argparse
import sys
from typing import List, Dict


def detect_tables(pdf_path: str) -> List[Dict]:
    """检测PDF中所有表格"""
    doc = fitz.open(pdf_path)
    tables_info = []
    
    for page_num in range(len(doc)):
        page = doc[page_num]
        tables = page.find_tables()
        
        for table_idx, table in enumerate(tables):
            cells = []
            for row in table.extract():
                cells.append([
                    cell.strip() if cell else "" 
                    for cell in row
                ])
            
            tables_info.append({
                "page": page_num,
                "table_idx": table_idx,
                "bbox": table.bbox,
                "rows": len(cells),
                "cols": len(cells[0]) if cells else 0,
                "cells": cells
            })
    
    doc.close()
    return tables_info


def translate_text(text: str, target_lang: str = "zh") -> str:
    """翻译文本"""
    if not text.strip():
        return text
    # 调用翻译API(示例)
    try:
        resp = requests.post(
            "https://api.pdftranslator.org/translate",
            json={"text": text, "target_lang": target_lang},
            timeout=30
        )
        return resp.json().get("translated_text", text)
    except:
        return text


def rebuild_bilingual(pdf_path: str, output_path: str, tables_info: List[Dict]):
    """重建双语表格PDF"""
    doc = fitz.open(pdf_path)
    
    for table in tables_info:
        page = doc[table["page"]]
        bbox = table["bbox"]
        cell_w = (bbox[2] - bbox[0]) / table["cols"]
        cell_h = (bbox[3] - bbox[1]) / table["rows"]
        
        for r_idx, row in enumerate(table.get("translated_cells", [])):
            for c_idx, cell in enumerate(row):
                if not cell["translated"]:
                    continue
                x0 = bbox[0] + c_idx * cell_w + 2
                y0 = bbox[1] + r_idx * cell_h + cell_h * 0.5
                page.insert_text(
                    (x0, y0), cell["translated"],
                    fontsize=7, color=(0.8, 0.2, 0.2)
                )
    
    doc.save(output_path)
    doc.close()


def main():
    parser = argparse.ArgumentParser(description="PDF表格翻译工具")
    parser.add_argument("input_pdf", help="输入PDF文件路径")
    parser.add_argument("output_pdf", help="输出PDF文件路径")
    parser.add_argument("--target-lang", default="zh", help="目标语言")
    args = parser.parse_args()
    
    print("Step 1: 检测表格...")
    tables = detect_tables(args.input_pdf)
    print(f"  检测到 {len(tables)} 个表格")
    
    print("Step 2: 翻译表格内容...")
    for table in tables:
        translated = []
        for row in table["cells"]:
            t_row = []
            for cell in row:
                t = translate_text(cell, args.target_lang)
                t_row.append({
                    "original": cell,
                    "translated": t,
                    "bilingual": f"{cell}\n{t}" if cell else ""
                })
            translated.append(t_row)
        table["translated_cells"] = translated
    
    print("Step 3: 重建双语表格...")
    rebuild_bilingual(args.input_pdf, args.output_pdf, tables)
    print(f"完成! 输出: {args.output_pdf}")


if __name__ == "__main__":
    main()

运行效果

测试文档:一份包含8个表格的英文财务报告(30页)

复制代码
Step 1: 检测表格...
  检测到 8 个表格
Step 2: 翻译表格内容...
  表格1 (5x3): 15个单元格翻译完成
  表格2 (8x4): 32个单元格翻译完成
  ...
Step 3: 重建双语表格...
完成! 输出: output_bilingual.pdf

输出PDF中每个表格单元格内,原文下方红色标注译文,表格结构和位置完全保留。

总结

  • PyMuPDF的find_tables()方法可以自动检测PDF表格区域
  • 分块翻译表格单元格比整页翻译更精准
  • 双语重建时在原单元格内插入译文,保留格式
  • 合并单元格需要额外检测线条位置处理
  • 此方案适合需要保留表格结构的技术文档翻译场景

标签:PDF翻译、Python自动化、AI翻译、效率工具

相关推荐
神奇霸王龙14 分钟前
Codex MCP GA 实测:Qwen / GLM / Kimi / DeepSeek / MiniMax 调度 Codex 沙箱的真实成本
人工智能·ai·agent·ai编程·原型模式·mcp
weixin_3077791315 分钟前
PySpark根据输入的表名和过滤条件生成 INSERT 语句
python·spark·云计算·big data
GodSure091421 分钟前
Java单一职责原则SRP详解
java·python·单一职责原则
Mojitocean31 分钟前
Win开发环境配置(持续更新)
开发语言·python
千里码aicood39 分钟前
flask基于数字人的储粮知识问答原型系统研究与实现
后端·python·flask
StarRocks_labs1 小时前
从 6000+ Commit 中识别升级风险:一个 StarRocks AI 升级扫描工具的实现
starrocks·ai·commit·分析·claude code
E_ICEBLUE1 小时前
Python 实现 Markdown 转 Word、PDF:从转换到页面设置
python·pdf·word·markdown·格式转换
哒咩哒咩1291 小时前
Agent 智能体开发全攻略:从 ReAct 到企业级架构
python·langchain·fastapi
徒步@天涯1 小时前
dify安装报错:fetch-pack: invalid index-pack output
ai