当前位置: 首页 > news >正文

把英语电子书翻译为中文 epub

1. 起因, 目的:

  • 英语的电子书,勉强也能读,只是不方便,尤其是速读。
  • 把英文的电子书,翻译为中文,下次再遇到这种电子书,直接能用。

2. 先看效果

能用。请添加图片描述

3. 过程:

代码 1, 使用远程的 api 来翻译
  • 涉及大量的网络请求,很慢。
import ebooklib
from ebooklib import epub
from bs4 import BeautifulSoup, Comment, CData  # 导入 Comment 和 CData 用于更精确地过滤
from googletrans import Translator
import time
import os# --- 配置 ---
INPUT_EPUB_FILE = 'a.epub'
OUTPUT_EPUB_FILE = 'a_chinese_opt_v1.epub'  # 修改输出文件名以作区分
TARGET_LANGUAGE = 'zh-cn'  # 目标语言代码 (中文简体)
DELAY_BETWEEN_REQUESTS = 1  # 每次翻译API请求之后的延迟(秒)
MAX_CHARS_PER_REQUEST = 4500  # 单次API请求的最大字符数 (保守值)# --- 初始化翻译器 ---
translator = Translator(service_urls=['translate.google.com'])  # 可以指定服务URLdef translate_text_robust(text_to_translate, retries=3, delay=5):"""带重试机制的翻译函数,处理可能的网络问题或API限制。"""if not text_to_translate or text_to_translate.isspace():return text_to_translatefor attempt in range(retries):try:# print(f"      Attempting translation for: '{text_to_translate[:60]}...'") # Debugtranslated = translator.translate(text_to_translate, dest=TARGET_LANGUAGE)# print(f"      Original: {text_to_translate[:30]}... Translated: {translated.text[:30]}...")return translated.textexcept Exception as e:print(f"      翻译错误: {e}. 尝试次数 {attempt + 1}/{retries}.")if attempt < retries - 1:print(f"      等待 {delay} 秒后重试...")time.sleep(delay)else:print(f"      翻译失败: {text_to_translate[:50]}...")return f"[翻译失败] {text_to_translate}"return text_to_translate  # 如果所有重试都失败def translate_html_content(html_content_str):"""解析HTML内容,合并块级元素的文本进行翻译。注意:此方法会丢失块内原有的HTML子标签。"""soup = BeautifulSoup(html_content_str, 'html.parser')# 定义我们关心的主要文本容器标签# 'div' 有时用于布局,可能包含非常大的、非纯文本内容,需谨慎处理。# 我们先从段落、标题、列表项开始。block_tags_to_translate = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'caption']# 你可以根据EPUB的具体内容调整这个列表,例如加入 'td', 'th' 等elements_for_translation = []for tag_name in block_tags_to_translate:elements_for_translation.extend(soup.find_all(tag_name))print(f"  找到 {len(elements_for_translation)} 个指定的块级元素进行处理。")api_calls_count = 0for i, element in enumerate(elements_for_translation):# 检查元素是否仍存在于DOM树中 (如果父元素被处理并清空,子元素可能已脱离)if not element.parent:continueoriginal_block_text = element.get_text(separator=' ', strip=True)if original_block_text:print(f"  处理块 {i + 1}/{len(elements_for_translation)} (标签: <{element.name}>): '{original_block_text[:70].replace(chr(10), ' ').replace(chr(13), ' ')}...'")translated_parts = []# 处理长文本块,分片翻译if len(original_block_text) > MAX_CHARS_PER_REQUEST:num_chunks = (len(original_block_text) + MAX_CHARS_PER_REQUEST - 1) // MAX_CHARS_PER_REQUESTprint(f"    文本过长 ({len(original_block_text)} chars), 将分割成 {num_chunks} 个片段进行翻译。")for chunk_idx in range(0, len(original_block_text), MAX_CHARS_PER_REQUEST):text_chunk = original_block_text[chunk_idx: chunk_idx + MAX_CHARS_PER_REQUEST]if text_chunk and not text_chunk.isspace():# print(f"      翻译片段 {chunk_idx // MAX_CHARS_PER_REQUEST + 1}/{num_chunks}: '{text_chunk[:50]}...'")translated_chunk_text = translate_text_robust(text_chunk)translated_parts.append(translated_chunk_text)api_calls_count += 1# print(f"    API call {api_calls_count}. Pausing for {DELAY_BETWEEN_REQUESTS}s...")time.sleep(DELAY_BETWEEN_REQUESTS)  # 每次API调用后延迟else:# 文本长度合适,直接翻译translated_parts.append(translate_text_robust(original_block_text))api_calls_count += 1# print(f"    API call {api_calls_count}. Pausing for {DELAY_BETWEEN_REQUESTS}s...")time.sleep(DELAY_BETWEEN_REQUESTS)  # 每次API调用后延迟final_translated_text = "".join(translated_parts)# 清空元素内所有原有子节点和文本,然后设置新的翻译后文本# 这会丢失块内如 <b>, <i>, <a> 等子标签element.clear()element.string = final_translated_text# else:# print(f"  块 {i+1}/{len(elements_for_translation)} (标签: <{element.name}>) 无有效文本内容,跳过。")# 对于不在上述 block_tags_to_translate 中的文本节点(例如直接在 <body> 下的文本,或在 <span> 等行内元素中的文本)# 此简化版本不会处理它们,除非它们被包含在被处理的块级元素内。# 一个更彻底的方案会复杂得多,需要仔细遍历和重建HTML结构。return str(soup)def main():overall_start_time = time.time()  # 程序总开始时间if not os.path.exists(INPUT_EPUB_FILE):print(f"错误:输入文件 '{INPUT_EPUB_FILE}' 不存在。")returnprint(f"正在加载 EPUB 文件: {INPUT_EPUB_FILE}")book = epub.read_epub(INPUT_EPUB_FILE)print("开始翻译内容 (方案一:合并块级元素优化)...")# new_items = [] # epublib 的 item 是可变的,可以直接修改book.items中的对象for item_idx, item in enumerate(list(book.get_items())):  # 使用list副本迭代,以防万一修改影响迭代if item.get_type() == ebooklib.ITEM_DOCUMENT:  # HTML/XHTML内容文件print(f"\n处理文件 {item_idx + 1}/{len(list(book.get_items()))}: {item.get_name()}")try:html_content_bytes = item.get_content()# 尝试UTF-8解码,如果失败则尝试常见的备选编码try:html_content_str = html_content_bytes.decode('utf-8')except UnicodeDecodeError:print(f"  警告: 文件 {item.get_name()} UTF-8解码失败,尝试使用 'latin-1'。")try:html_content_str = html_content_bytes.decode('latin-1')except UnicodeDecodeError:print(f"  警告: 文件 {item.get_name()} latin-1解码失败,尝试使用 'cp1252'。")html_content_str = html_content_bytes.decode('cp1252', errors='replace')  # 使用replace避免程序中断except Exception as e_decode:print(f"  错误: 文件 {item.get_name()} 解码失败: {e_decode}。跳过此文件。")# new_items.append(item) # 如果创建新列表,则加入未修改项continuefile_process_start_time = time.time()translated_html_str = translate_html_content(html_content_str)file_process_end_time = time.time()print(f"  文件 {item.get_name()} 处理完毕,耗时: {file_process_end_time - file_process_start_time:.2f} 秒。")item.set_content(translated_html_str.encode('utf-8'))# new_items.append(item)# else:# new_items.append(item) # 非HTML内容直接保留# book.items = new_items # 如果之前创建了new_items并填充print("\n更新书本元数据中的语言信息...")book.set_language(TARGET_LANGUAGE)print(f"书本语言元数据已更新为: {TARGET_LANGUAGE}")print(f"\n正在保存翻译后的 EPUB 文件: {OUTPUT_EPUB_FILE}")epub.write_epub(OUTPUT_EPUB_FILE, book, {})print("翻译完成!")overall_end_time = time.time()  # 程序总结束时间total_processing_time = overall_end_time - overall_start_timeprint(f"\n--- 任务完成 ---")print(f"总耗时: {total_processing_time:.2f} 秒 ({total_processing_time / 60:.2f} 分钟)")if __name__ == '__main__':# 确保你有一个名为 'a.epub' 的文件在脚本同目录下,或者修改 INPUT_EPUB_FILE 的路径# 例如: INPUT_EPUB_FILE = 'path/to/your/ebook.epub'if not os.path.exists(INPUT_EPUB_FILE):# 创建一个简单的 a.epub 用于测试print(f"警告: 输入文件 '{INPUT_EPUB_FILE}' 不存在。将尝试创建一个简单的测试EPUB。")book = epub.EpubBook()book.set_identifier('id123456')book.set_title('Sample Book for Translation')book.set_language('en')c1 = epub.EpubHtml(title='Intro', file_name='chap_01.xhtml', lang='en')c1.content = u'<h1>Introduction</h1><p>This is a sample paragraph. It has some  سنا ہے text to translate.</p><p>Another paragraph here. Followed by a list:<ul><li>First item</li><li>Second item to check</li><li>Third one for the road.</li></ul></p><p>Short text.</p><p>A very long text string to test chunking, this needs to be repeated many times to exceed 4500 characters. Let\'s repeat this sentence multiple times to simulate a very long paragraph that will require splitting by the translation API character limit. ' + ('This is a long sentence fragment. ' * 200) + ' End of the long paragraph.</p>'book.add_item(c1)book.toc = (epub.Link('chap_01.xhtml', 'Introduction', 'intro'),)book.add_item(epub.EpubNcx())book.add_item(epub.EpubNav())style = 'BODY {color: white;}'nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style)book.add_item(nav_css)book.spine = ['nav', c1]epub.write_epub(INPUT_EPUB_FILE, book, {})print(f"测试EPUB '{INPUT_EPUB_FILE}' 已创建。请重新运行脚本。")else:main()# 依赖: pip install ebooklib beautifulsoup4 googletrans==4.0.0rc1# --- 任务完成 ---# 总耗时: 166.86 秒 (2.78 分钟)
代码 2, 使用本地的模型, huggingface + tensorflow
  • 会快一点。
# 聊天记录
# https://gemini.google.com/gem/coding-partner/f82fda8d1724f03aimport ebooklib
from ebooklib import epub
from bs4 import BeautifulSoup, Comment, CData # 导入 Comment 和 CData 用于更精确地过滤
from googletrans import Translator
import time
import os# --- 新增:Hugging Face Transformers 初始化 ---
from transformers import MarianMTModel, MarianTokenizer
import torch # 如果你安装了 PyTorch# --- 配置 ---
INPUT_EPUB_FILE = 'a.epub'
OUTPUT_EPUB_FILE = 'a_chinese.epub'
TARGET_LANGUAGE = 'zh-cn' # 目标语言代码 (中文简体)
DELAY_BETWEEN_REQUESTS = 1 # 每次翻译请求之间的延迟(秒),防止IP被封# --- 初始化翻译器 ---
# translator = Translator()# --- 初始化本地翻译模型 ---
MODEL_NAME = 'Helsinki-NLP/opus-mt-en-zh' # 英文到中文的模型
tokenizer = None
model = None
device = None # 用于GPU加速try:print(f"正在加载本地翻译模型: {MODEL_NAME}...")# 检查是否有可用的GPUif torch.cuda.is_available():device = torch.device("cuda")print("检测到 GPU,将使用 GPU 进行翻译。")else:device = torch.device("cpu")print("未检测到 GPU,将使用 CPU 进行翻译 (可能会比较慢)。")tokenizer = MarianTokenizer.from_pretrained(MODEL_NAME)model = MarianMTModel.from_pretrained(MODEL_NAME).to(device) # 将模型移到GPU或CPUprint("本地翻译模型加载成功!")
except Exception as e:print(f"错误:无法加载本地翻译模型 {MODEL_NAME}。错误信息: {e}")print("请确保已安装 'transformers', 'torch', 'sentencepiece' 库,并且模型名称正确。")print("将无法使用本地翻译功能。")# 这里可以选择退出程序或回退到其他翻译方式(如果实现了的话)exit()# --- 重写翻译函数 ---
# translate_text_local
def translate_text_local(text_to_translate, max_length=512):"""使用本地 MarianMT 模型翻译文本。"""if not text_to_translate or text_to_translate.isspace() or not model or not tokenizer:return text_to_translatetry:inputs = tokenizer(text_to_translate, return_tensors="pt", truncation=False).to(device)  # 不立即截断,先获取所有input_idsinput_ids = inputs['input_ids'][0]  # 获取单个样本的input_idstranslated_text_parts = []# 定义一个合理的块大小,略小于模型的最大长度以留有余地chunk_size = max_length - 2  # 减去特殊token的长度for i in range(0, len(input_ids), chunk_size):chunk = input_ids[i: i + chunk_size]# 需要确保chunk是torch.Tensor并且有正确的维度 (1, sequence_length)chunk_tensor = torch.tensor([chunk.tolist()], device=device)  # 转换为list再转tensor以避免slice问题# 创建 attention_mask (全1即可,因为我们自己做了分块)attention_mask = torch.ones_like(chunk_tensor)translation_tokens = model.generate(input_ids=chunk_tensor, attention_mask=attention_mask,max_length=max_length)  # 可以增加其他生成参数translated_text_parts.append(tokenizer.decode(translation_tokens[0], skip_special_tokens=True))translated_text = "".join(translated_text_parts)  # 根据需要用空格或其他连接符# print(f"Original: {text_to_translate[:30]}... Translated: {translated_text[:30]}...")return translated_textexcept Exception as e:print(f"本地翻译错误: {e}. 对于文本: {text_to_translate[:50]}...")return f"[本地翻译失败] {text_to_translate}"def translate_html_content(html_content):"""解析HTML内容,翻译其中的文本节点。"""soup = BeautifulSoup(html_content, 'html.parser')text_nodes = soup.find_all(string=True)count = 0total_nodes = len(text_nodes)for i, text_node in enumerate(text_nodes):# 1. 忽略脚本和样式标签内的文本if text_node.parent.name in ['script', 'style', 'title', 'meta', 'link']:continue# 2. 忽略HTML注释if isinstance(text_node, Comment):continue# 3. 忽略CDATA块 (虽然EPUB中不常见,但以防万一)if isinstance(text_node, CData):continueoriginal_text = text_node.string# 4. 忽略纯粹的空白文本节点if original_text and not original_text.isspace():stripped_text = original_text.strip() # 去除首尾空白再判断if stripped_text: # 确保strip后还有内容print(f"  正在翻译片段 { i +1}/{total_nodes}: '{stripped_text[:50]}...'")# translated_text = translate_text_robust(stripped_text)translated_text = translate_text_local(stripped_text)# 直接替换节点内容# 如果原始文本有前后空格,我们需要保留它们prefix = original_text[:len(original_text) - len(original_text.lstrip())]suffix = original_text[len(original_text.rstrip()):]text_node.replace_with(prefix + translated_text + suffix)count += 1# if count % 5 == 0: # 每翻译5个片段,稍微休息一下# time.sleep(DELAY_BETWEEN_REQUESTS)return str(soup)def main():if not os.path.exists(INPUT_EPUB_FILE):print(f"错误:输入文件 '{INPUT_EPUB_FILE}' 不存在。")returnprint(f"正在加载 EPUB 文件: {INPUT_EPUB_FILE}")book = epub.read_epub(INPUT_EPUB_FILE)print("开始翻译内容...")new_items = []for item in book.get_items():if item.get_type() == ebooklib.ITEM_DOCUMENT: # 这是HTML/XHTML内容文件print(f"处理文件: {item.get_name()}")# 解码内容try:html_content = item.get_content().decode('utf-8')except UnicodeDecodeError:print(f"  警告: 文件 {item.get_name()} UTF-8解码失败,尝试使用 'latin-1'。")try:html_content = item.get_content().decode('latin-1') # 有些EPUB可能编码不规范except Exception as e_decode:print(f"  错误: 文件 {item.get_name()} 解码失败: {e_decode}。跳过此文件。")new_items.append(item) # 保留原样continuetranslated_html = translate_html_content(html_content)# 更新item的内容item.set_content(translated_html.encode('utf-8'))new_items.append(item)print(f"  文件 {item.get_name()} 处理完毕。")# 也可以在这里加一个更长的延时,如果单个文件翻译了很多片段# time.sleep(DELAY_BETWEEN_REQUESTS * 2)else:# 对于非HTML内容(如图片、CSS、字体等),直接保留new_items.append(item)# 更新书本元数据中的语言信息book.set_language(TARGET_LANGUAGE)print(f"书本语言元数据已更新为: {TARGET_LANGUAGE}")# 将所有处理过的(或未处理的)items重新赋值给book对象# (其实我们是直接修改了book.items列表中的对象,所以这一步理论上不是必须的,# 但为了清晰,可以这样做,或者直接用原来的book对象写入)# book.items = new_items # 如果创建了new_items列表并想用它替换print(f"正在保存翻译后的 EPUB 文件: {OUTPUT_EPUB_FILE}")epub.write_epub(OUTPUT_EPUB_FILE, book, {})print("翻译完成!")if __name__ == '__main__':main()# pip install ebooklib beautifulsoup4 googletrans==4.0.0-rc1# pip install transformers torch sentencepiece

4. 结论 + todo

  • 整天搞这些一时兴起的事情。
  • 我还是没有找到真正值得做的事情。

希望对大家有帮助。

相关文章:

  • NDVI谐波拟合(基于GEE实现)
  • MySQL安装配置指南
  • 精华贴分享|个股拥挤度分析研究分析
  • PyQt学习系列11-综合项目:多语言文件管理器
  • MCP 服务与 Agent 协同架构的实践解码:双轮驱动下的场景化价值创造
  • 镭神N10P SLAM算法选型
  • Datawhale_PyPOTS_task6
  • Elastic:什么是 DevOps?
  • Oracle 11g导出数据库结构和数据
  • 【线程池】线程池的使用汇总
  • ​​3D 几何建模工具库​Open CASCADE(OCCT)简单介绍。
  • 在TIA 博途中下载程序时找不到对应的网卡怎么办?
  • 使用Kotlin创建Spring Boot用户应用项目
  • 在Kotlin中绕过泛型类型擦除的实战指南
  • Kotlin 中该如何安全地处理可空类型?
  • RequestBody注解中Map
  • 「MATLAB」计算校验和 Checksum
  • 摩尔线程S4000国产信创计算卡性能实战——Pytorch转译,多卡P2P通信与MUSA编程
  • uv sync --frozen卡住不动
  • 爱普生晶振赋能UWB汽车数字钥匙,解锁未来出行新方式
  • 广告网站建设流程/百度seo搜索营销新视角
  • wordpress 出名主题/搜狗seo
  • 欢迎访问中国建设银行网站/软文街怎么样
  • 备案个人网站做淘宝客/企业文化设计
  • 深圳手机集团网站建设/有哪些免费推广网站
  • wordpress 网址/苏州seo整站优化