Python結(jié)合PyMuPDF手把手實(shí)現(xiàn)PDF原版式翻譯的實(shí)戰(zhàn)指南
一、背景與目標(biāo)
在處理英文技術(shù)文檔、論文或說明書時,常見的 PDF 翻譯方案存在幾個痛點(diǎn):
- 翻譯后版式錯亂
- 圖片、公式丟失
- 代碼、URL 被誤翻譯
- 中文字體無法正常顯示
- 只能整頁 OCR,無法保持原始排版
本文介紹一種基于 PyMuPDF(fitz)+ Ollama 大模型的 PDF 翻譯方案,目標(biāo)是:
逐頁、逐文本塊翻譯 PDF,自然語言翻譯為中文,圖片與版式完全保持不變
翻譯后的效果如下圖

二、整體技術(shù)方案
技術(shù)選型
| 模塊 | 技術(shù) |
|---|---|
| PDF 解析 | PyMuPDF(fitz) |
| 圖片處理 | PyMuPDF Pixmap |
| 大模型推理 | Ollama |
| 翻譯模型 | qwen2.5:7b |
| 輸出方式 | 重新生成 PDF(逐頁) |
核心思路
整個流程可拆解為 5 個步驟:
- 逐頁解析 PDF
- 提取文本 span(包含字體、字號、位置)
- 提取并復(fù)制原始圖片
- 使用大模型翻譯自然語言文本
- 在原坐標(biāo)位置重新繪制文本,生成新 PDF
三、核心類設(shè)計(jì):PDFTranslator
整個翻譯邏輯被封裝在 PDFTranslator 類中,職責(zé)清晰、結(jié)構(gòu)合理。
必須安裝(核心依賴)
pip install pymupdf pip install ollama pip install pillow
完整代碼
import fitz # PyMuPDF
import os
import tempfile
from PIL import Image
import io
import ollama
class PDFTranslator:
def __init__(self, source_pdf_path, model="qwen2.5:7b"):
"""
初始化PDF翻譯器
Args:
source_pdf_path (str): 源PDF文件路徑
model (str): Ollama模型名稱
"""
self.source_pdf_path = source_pdf_path
self.doc = fitz.open(source_pdf_path)
self.model = model
def extract_text_and_positions(self, page_num):
"""
提取指定頁面的文本及其位置信息
Args:
page_num (int): 頁面編號(從0開始)
Returns:
list: 包含文本塊信息的列表
"""
page = self.doc[page_num]
text_blocks = []
# 獲取頁面上的文本塊
blocks = page.get_text("dict")["blocks"]
for block in blocks:
if "lines" in block: # 文本塊
for line in block["lines"]:
for span in line["spans"]:
text_blocks.append({
"text": span["text"],
"bbox": span["bbox"], # 邊界框 [x0, y0, x1, y1]
"size": span["size"],
"font": span["font"],
"color": span["color"]
})
return text_blocks
def extract_images(self, page_num):
"""
提取指定頁面的圖片
Args:
page_num (int): 頁面編號(從0開始)
Returns:
list: 包含圖片信息的列表
"""
page = self.doc[page_num]
image_list = []
# 獲取頁面上的圖片
image_list_raw = page.get_images()
for img_index, img in enumerate(image_list_raw):
xref = img[0]
pix = fitz.Pixmap(self.doc, xref)
# 如果是CMYK圖片,轉(zhuǎn)換為RGB
if pix.n < 5:
img_data = pix.tobytes("png")
else:
pix1 = fitz.Pixmap(fitz.csRGB, pix)
img_data = pix1.tobytes("png")
pix1 = None
# 獲取圖片在頁面上的位置
img_rects = page.get_image_rects(xref)
image_info = {
"image_data": img_data,
"rect": img_rects[0] if img_rects else None,
"xref": xref
}
image_list.append(image_info)
pix = None
return image_list
def translate_text(self, text, dest_lang='zh'):
"""
使用Ollama大模型翻譯文本
Args:
text (str): 要翻譯的文本
dest_lang (str): 目標(biāo)語言
Returns:
str: 翻譯后的文本
"""
try:
# 構(gòu)建翻譯提示,明確要求將英文翻譯成中文,只返回翻譯結(jié)果,保持原文格式
prompt = f"""請將以下英文文本翻譯成中文,保持原文的格式、空格和標(biāo)點(diǎn)符號,只返回翻譯結(jié)果,不要添加任何解釋。
翻譯要求:
1. 所有代碼內(nèi)容(包括代碼塊、函數(shù)名、類名、變量名、命令、配置項(xiàng)等)必須保持原樣,不得翻譯。
2. 所有網(wǎng)址(以 http://、https://、www. 開頭的鏈接)必須保持原樣,不得翻譯。
3. 所有數(shù)字保持原樣,不得翻譯或改寫(包括整數(shù)、小數(shù)、百分比、版本號、時間、日期、編號等)。
4. 保持原文中的空格、換行、縮進(jìn)等格式,確保翻譯后格式一致。
5. 僅翻譯自然語言描述性的英文文本,其余內(nèi)容全部保持不變。
英文文本如下:
{text}
"""
print(f"正在翻譯文本: {text[:50]}..." if len(text) > 50 else f"正在翻譯文本: {text}")
# 調(diào)用Ollama模型
response = ollama.chat(
model=self.model,
messages=[
{
'role': 'user',
'content': prompt
}
]
)
translated = response['message']['content'].strip()
print(f"翻譯結(jié)果: {translated[:50]}..." if len(translated) > 50 else f"翻譯結(jié)果: {translated}")
return translated
except Exception as e:
print(f"翻譯錯誤: {e}")
return text # 如果翻譯失敗,返回原文本
def create_translated_pdf(self, output_path, dest_lang='zh', start_page=0, end_page=None):
"""
創(chuàng)建翻譯后的PDF文件,保持原有版面布局
Args:
output_path (str): 輸出PDF文件路徑
dest_lang (str): 目標(biāo)語言
start_page (int): 開始頁面(從0開始)
end_page (int): 結(jié)束頁面(從0開始,None表示到最后一頁)
"""
# 確定頁面范圍
if end_page is None:
end_page = len(self.doc) - 1
else:
end_page = min(end_page, len(self.doc) - 1)
# 處理指定范圍的頁面
for page_num in range(start_page, end_page + 1):
print(f"正在處理第 {page_num + 1} 頁...(共{len(self.doc)}頁)")
# 創(chuàng)建新的PDF文檔
new_doc = fitz.open()
# 獲取原始頁面信息
original_page = self.doc.load_page(page_num)
page_width = original_page.rect.width
page_height = original_page.rect.height
# 創(chuàng)建新頁面
new_page = new_doc.new_page(width=page_width, height=page_height)
# 復(fù)制原始頁面的布局和圖片
original_page = self.doc[page_num]
# 復(fù)制圖片
images = self.extract_images(page_num)
for img_info in images:
if img_info["rect"]:
# 插入原始圖片
new_page.insert_image(img_info["rect"], stream=img_info["image_data"])
# 提取并翻譯文本
text_blocks = self.extract_text_and_positions(page_num)
# 為了更好地保持版面,我們需要更精確地處理文本
for block in text_blocks:
if block["text"].strip(): # 避免空文本
translated_text = self.translate_text(block["text"], dest_lang)
# 計(jì)算文本位置
bbox = block["bbox"]
x0, y0, x1, y1 = bbox
# 創(chuàng)建文本插入點(diǎn)(使用左下角作為基點(diǎn))
# fitz使用笛卡爾坐標(biāo)系,y軸向上
point = fitz.Point(x0, y0 + block["size"])
# 使用text writer來保持更好的版面
# 首先擦除原始文本區(qū)域(可選)
# 然后插入翻譯后的文本
# 檢查文本是否包含中文字符,如果是,則使用中文字體
if any(ord(char) > 127 for char in translated_text): # 包含非ASCII字符(如中文)
try:
new_page.insert_text(
point,
translated_text,
fontsize=block["size"],
fontname="china-ss",
color=(0, 0, 0) # 強(qiáng)制使用黑色文字以確??梢?
)
except:
# 如果內(nèi)置字體失敗,使用默認(rèn)字體
new_page.insert_text(
point,
translated_text,
fontsize=block["size"],
color=(0, 0, 0) # 強(qiáng)制使用黑色文字以確??梢?
)
else:
# 如果是英文文本,使用原始字體
try:
new_page.insert_text(
point,
translated_text,
fontsize=block["size"],
fontname=block["font"],
color=(0, 0, 0) # 強(qiáng)制使用黑色文字以確??梢?
)
except:
new_page.insert_text(
point,
translated_text,
fontsize=block["size"],
color=(0, 0, 0) # 強(qiáng)制使用黑色文字以確保可見
)
else:
# 如果是空文本,直接復(fù)制原始文本(如空格等)
bbox = block["bbox"]
x0, y0, x1, y1 = bbox
point = fitz.Point(x0, y0 + block["size"])
# 對于空文本塊,也檢查是否包含中文字符
if any(ord(char) > 127 for char in block["text"]): # 包含非ASCII字符(如中文)
try:
new_page.insert_text(
point,
block["text"],
fontsize=block["size"],
fontname="china-ss",
color=(0, 0, 0) # 強(qiáng)制使用黑色文字以確??梢?
)
except:
# 如果內(nèi)置字體失敗,使用默認(rèn)字體
new_page.insert_text(
point,
block["text"],
fontsize=block["size"],
color=(0, 0, 0) # 強(qiáng)制使用黑色文字以確??梢?
)
else:
# 如果是英文文本,使用原始字體
try:
new_page.insert_text(
point,
block["text"],
fontsize=block["size"],
fontname=block["font"],
color=(0, 0, 0) # 強(qiáng)制使用黑色文字以確??梢?
)
except:
new_page.insert_text(
point,
block["text"],
fontsize=block["size"],
color=(0, 0, 0) # 強(qiáng)制使用黑色文字以確保可見
)
# 為每一頁創(chuàng)建單獨(dú)的PDF文件
page_output_path = output_path.replace('.pdf', f'_page_{page_num + 1}.pdf')
new_doc.save(page_output_path)
new_doc.close()
print(f"第 {page_num + 1} 頁翻譯完成,輸出文件: {page_output_path}")
print(f"所有頁面翻譯完成!")
def get_page_count(self):
"""
獲取PDF總頁數(shù)
Returns:
int: PDF總頁數(shù)
"""
return len(self.doc)
def close(self):
"""
關(guān)閉文檔
"""
if self.doc:
self.doc.close()
def main():
# 使用示例
source_pdf = "1.pdf"
output_pdf = "translated_1.pdf"
# 創(chuàng)建翻譯器實(shí)例,使用Ollama模型
translator = PDFTranslator(source_pdf, model="qwen2.5:7b")
try:
print(f"PDF總頁數(shù): {translator.get_page_count()}")
# 翻譯PDF并保持版面(只翻譯前3頁作為示例)
translator.create_translated_pdf(
output_path=output_pdf,
dest_lang='zh',
start_page=8,
end_page=211 # 翻譯前3頁(0-2)
)
finally:
# 關(guān)閉文檔
translator.close()
if __name__ == "__main__":
main()
運(yùn)行結(jié)果

到此這篇關(guān)于Python結(jié)合PyMuPDF手把手實(shí)現(xiàn)PDF原版式翻譯的實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Python PyMuPDF翻譯PDF內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于python?win32setpixel?api?實(shí)現(xiàn)計(jì)算機(jī)圖形學(xué)相關(guān)操作(推薦)
這篇文章主要介紹了基于python?win32setpixel?api?實(shí)現(xiàn)計(jì)算機(jī)圖形學(xué)相關(guān)操作,這次的主要分為2個主要模塊,一個是實(shí)現(xiàn)畫線,畫圓的算法,還有填充的算法,以及裁剪的算法,需要的朋友可以參考下2021-12-12
詳解使用Python寫一個向數(shù)據(jù)庫填充數(shù)據(jù)的小工具(推薦)
這篇文章主要介紹了用Python寫一個向數(shù)據(jù)庫填充數(shù)據(jù)的小工具,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09
學(xué)習(xí)Python爬蟲的幾點(diǎn)建議
這篇文章主要介紹了學(xué)習(xí)Python爬蟲的幾點(diǎn)建議,對新手學(xué)習(xí)爬蟲有很大的幫助,感興趣的朋友可以了解下2020-08-08
python 讀取數(shù)據(jù)庫并繪圖的實(shí)例
今天小編就為大家分享一篇python 讀取數(shù)據(jù)庫并繪圖的實(shí)例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12
Pandas中Series和DataFrame的索引實(shí)現(xiàn)
這篇文章主要介紹了Pandas中Series和DataFrame的索引實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06

