💡 方案设计理念
在处理大规模JSON文件翻译时,我们需要克服几个关键挑战:API的Token限制、网络延迟导致的效率低下、以及请求失败时的健壮性。本方案的核心思想是通过将大问题分解为小问题,并并行高效地处理这些小问题,同时辅以容错机制。
✂️ 分而治之
将大JSON文件中所有需要翻译的文本片段独立提取出来,避免单次API请求的数据量过大。
🚀 并行加速
利用异步I/O和信号量机制,同时发起多个DeepSeek API请求,最大化API吞吐量。
🛡️ 容错与恢复
实现请求重试和本地缓存,确保网络波动或API临时故障时,任务能够稳定完成并支持断点续传。
✍️ 精准重建
在翻译完成后,根据记录的原始路径,将翻译结果准确地回填到JSON的原始结构中,确保数据完整性。
🏗️ 详细架构流程
路径, 原始文本] D --> E[生成任务列表
每个任务包含路径和原始文本] E --> F{并发翻译环节
aiohttp + asyncio.Semaphore} F --> G[单个文本翻译
DeepSeek API] G --> H{缓存检查
MD5哈希文件名} H -->|命中| I[返回缓存结果] H -->|未命中| J[调用DeepSeek API] J --> K{API响应} K -->|成功| L[保存翻译结果到缓存] L --> M[返回翻译结果] K -->|API错误/超时| N[错误重试
最多X次] N --> J M ---> O[汇总所有翻译结果
路径: 翻译文本] O ---> P[基于原始JSON和翻译结果
重建完整JSON结构] P --> Q[保存翻译后的JSON到文件] Q --> R[完成] subgraph 并发翻译环节 F ---> G G ---> H H ---> I H ---> J J ---> K K ---> L K ---> N L ---> M N ---> J end
💻 核心代码解析
1. 配置与初始化
DeepSeekJSONTranslator 类负责整个翻译流程的协调。初始化时需要提供DeepSeek API密钥,并可配置并发数、重试策略等。
class DeepSeekJSONTranslator:
def __init__(self, api_key: str, base_url: str = "https://api.deepseek.com/v1"):
self.api_key = api_key
self.base_url = base_url
self.cache_dir = Path("translation_cache") # 缓存目录
self.cache_dir.mkdir(exist_ok=True)
self.max_tokens_per_request = 4000 # 单次请求最大Token数(DeepSeek的限制)
self.max_concurrent_requests = 5 # 限制并发请求数量,防止API限速或过载
self.retry_times = 3 # 请求失败时的重试次数
self.retry_delay = 2 # 重试间隔时间(秒)
2. 缓存机制 (断点续传的核心)
每个待翻译的文本片段及其目标语言会生成一个唯一的MD5哈希作为缓存键。翻译成功后,结果会以文本文件的形式存储在本地缓存目录中。下次遇到相同的翻译请求,会优先从缓存中读取,避免重复调用API。
def _get_cache_key(self, text: str, target_lang: str) -> str:
content = f"{text}_{target_lang}"
return hashlib.md5(content.encode()).hexdigest()
def _load_cache(self, cache_key: str) -> str:
cache_file = self.cache_dir / f"{cache_key}.txt"
if cache_file.exists():
return cache_file.read_text(encoding='utf-8')
return None
def _save_cache(self, cache_key: str, translation: str):
cache_file = self.cache_dir / f"{cache_key}.txt"
cache_file.write_text(translation, encoding='utf-8')
⚠️ 注意:
这种缓存只针对完全相同的输入文本和目标语言。如果JSON结构或内容在翻译过程中发生变化,应清除缓存或确保旧缓存不会干扰新翻译。
3. 单个文本翻译与重试
translate_text 方法封装了与DeepSeek API的交互,包括构建请求、发送请求、解析响应以及处理错误和重试。
async def translate_text(self, session: aiohttp.ClientSession,
text: str, target_lang: str = "中文") -> str:
cache_key = self._get_cache_key(text, target_lang)
cached = self._load_cache(cache_key)
if cached:
return cached # 直接返回缓存结果
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": f"你是一个专业的翻译助手,请将用户输入的文本翻译成{target_lang},只返回翻译结果,不要添加任何解释。"},
{"role": "user", "content": text}
],
"temperature": 0.3 # 较低的温度值有助于获得更准确、更少创造性的翻译
}
for attempt in range(self.retry_times):
try:
async with session.post(
f"{self.base_url}/chat/completions",
headers=headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=60) # 设置请求超时
) as response:
if response.status == 200:
result = await response.json()
translation = result['choices'][0]['message']['content'].strip()
self._save_cache(cache_key, translation) # 翻译成功后保存到缓存
return translation
else:
error_text = await response.text()
print(f"API错误 (状态码: {response.status}): {error_text}")
# 对于特定的错误码,可能需要更精细的处理,例如等待更长时间或更换模型
except Exception as e:
print(f"请求失败 (尝试 {attempt + 1}/{self.retry_times}): {str(e)}")
if attempt < self.retry_times - 1:
await asyncio.sleep(self.retry_delay) # 发生异常后等待一定时间再重试
return text # 所有重试失败,返回原文,避免数据丢失
4. 提取可翻译文本 (深度遍历JSON)
extract_translatable_texts 方法通过递归遍历JSON结构(字典和列表),找到所有的字符串类型的值。同时,它会记录每个字符串在JSON中的“路径”,以便后续将翻译结果准确地回填。
def extract_translatable_texts(self, data: Any, path: str = "") -> List[tuple]:
texts = []
if isinstance(data, dict):
for key, value in data.items():
current_path = f"{path}.{key}" if path else key # 构造路径
texts.extend(self.extract_translatable_texts(value, current_path))
elif isinstance(data, list):
for idx, item in enumerate(data):
current_path = f"{path}[{idx}]" # 列表元素使用索引作为路径的一部分
texts.extend(self.extract_translatable_texts(item, current_path))
elif isinstance(data, str) and data.strip(): # 只处理非空字符串
texts.append((path, data))
return texts
✅ 优势:
这种路径记录方式非常灵活,可以处理任意深度的嵌套JSON结构,是准确回填翻译结果的关键。
5. 重建JSON结构
rebuild_json 方法同样通过递归方式遍历原始JSON,当遇到一个在翻译结果字典中存在其路径的字符串时,就用对应的翻译结果替换掉原始字符串。
def rebuild_json(self, original: Any, translations: Dict[str, str]) -> Any:
def rebuild(data: Any, path: str = "") -> Any:
if isinstance(data, dict):
return {
key: rebuild(value, f"{path}.{key}" if path else key)
for key, value in data.items()
}
elif isinstance(data, list):
return [
rebuild(item, f"{path}[{idx}]")
for idx, item in enumerate(data)
]
elif isinstance(data, str) and path in translations: # 检查路径是否在翻译结果中
return translations[path] # 替换为翻译后的文本
else:
return data # 非字符串或无需翻译的部分保持不变
return rebuild(original)
6. 异步并发控制
translate_json_file 方法 orchestrates 整个翻译过程。它利用 asyncio.Semaphore 来严格控制同时进行的API请求数量,从而避免超出API的速率限制,同时最大化并行效率。
async def translate_json_file(self, input_file: str, output_file: str,
target_lang: str = "中文"):
# ... (加载和提取文本部分) ...
async with aiohttp.ClientSession() as session:
semaphore = asyncio.Semaphore(self.max_concurrent_requests) # 初始化信号量
async def translate_with_semaphore(path: str, text: str):
async with semaphore: # 每次进入with块都会尝试获取一个信号量
result = await self.translate_text(session, text, target_lang)
return path, result
tasks = [
translate_with_semaphore(path, text)
for path, text in texts
]
# 使用 tqdm 封装 asyncio.as_completed 来显示异步任务的进度
with tqdm(total=len(tasks), desc="翻译进度") as pbar:
for coro in asyncio.as_completed(tasks):
path, translation = await coro
translations[path] = translation
pbar.update(1) # 完成一个任务,更新进度条
# ... (重建和保存部分) ...
⭐ 关键点:
aiohttp.ClientSession用于高效的HTTP请求。asyncio.Semaphore是并发控制的利器,它限制了同时运行的协程(API请求)数量,既提高了效率又避免了API限流。asyncio.as_completed能够迭代已完成的协程,允许我们实时处理翻译结果并更新进度。tqdm提供了美观的进度条,实时反馈翻译进度,增强用户体验。
📊 性能考虑
该方案的整体性能取决于以下几个因素:
- DeepSeek API的响应速度和吞吐量: 这是外部瓶颈,我们只能通过并发和重试机制来适应。
- 您的网络带宽和延迟: 影响API请求和响应的传输速度。
- `max_concurrent_requests` 参数: 合理设置此值非常关键。过低会导致效率低下,过高可能触发API限流。建议根据DeepSeek的官方速率限制和实际测试进行调整。
- JSON文件的复杂度和大小: 包含的字符串越多、嵌套越深,翻译时间越长。
⚠️ 成本提示:
DeepSeek API是按Token计费的。并发请求意味着更快的Token消耗,请务必关注您的API用量和预算。
✨ 扩展性和优化
- 日志记录: 完善日志系统,记录每个请求的状态、耗时、错误信息,便于调试和监控。
- 自定义翻译规则: 允许用户定义哪些JSON键不需要翻译,或者特定键需要特殊处理(例如,只翻译值,不翻译键)。这可以通过修改
extract_translatable_texts实现。 - 多模型支持: 扩展为可以使用不同的DeepSeek模型甚至其他LLM服务进行翻译,根据内容类型选择最合适的模型。
- 输入/输出格式扩展: 除了JSON,还可以支持YAML、XML等格式的翻译。
- Token计数预估: 在发送请求前,估算待翻译文本的Token数量,确保不超过
max_tokens_per_request配置,如果超出则进一步拆分。Python的tiktoken库可以用于GPT模型的Token计数,DeepSeek通常与OpenAI的Tokeniser兼容。
这个方案提供了一个坚实的基础,您可以在此之上根据具体业务需求进行灵活的扩展和优化。
此方案由 AI 助手提供,旨在帮助您高效解决Python+DeepSeek大JSON翻译问题。