DeepSeek API实战:Python批量翻译SRT字幕的完整流程

发布时间:2026/9/1 15:54:41
DeepSeek API实战:Python批量翻译SRT字幕的完整流程 做老动画字幕时大家应该都遇到过类似情况手里有一部 1990 年的 OVA 资源画质修复得不错但只有葡萄牙语字幕网上找不到现成的中文字幕想找人翻译成本又高。我这次用 DeepSeek 的 API 做了一次“葡转中”字幕翻译实战把原本需要好几天的人工翻译缩短到几十分钟而且翻译质量在可接受范围内。这篇文章就把完整流程拆开来讲覆盖 DeepSeek API 接入、字幕解析、批量翻译、回写格式化、本地部署方案和工程化建议无论你是第一次接触 DeepSeek还是已经在业务里接入过都能拿到一套可直接运行的方案。1. 项目背景与整体方案1.1 这个任务到底要解决什么问题字幕翻译看起来只是把 A 语言文本换成 B 语言文本实际落到工程上问题比想象中多得多。首先字幕文件不是纯文本那么简单。常见的.srt格式自带序号和时间轴翻译时必须把「文本内容」和「时间轴信息」分离开翻译完再原样拼回去。如果直接把整个文件丢给模型翻译模型很可能把时间轴也改了或者把序号弄乱最后字幕跟画面完全对不上。其次字幕翻译对上下文有要求。同一部动画里角色名、专有名词、口头禅需要在全文范围内保持一致。如果逐条独立翻译同一个葡萄牙语词汇可能在第一条被翻译成“魔法少女”第三条又被翻译成“小魔女”观众看起来就会很困惑。最后批量调用大模型 API 还需要考虑速率限制、失败重试、断点续传。几十条字幕还好几百上千条字幕如果中途失败不能从头再翻一遍。所以这个项目的完整技术链路应该拆成四段将 SRT 字幕解析为结构化数据构造合理的翻译 Prompt分批调用 DeepSeek API对返回结果做容错处理把翻译后的文本回写到 SRT 文件。1.2 DeepSeek 在字幕翻译任务中的定位DeepSeek 是深度求索公司推出的大语言模型目前在开发者社区里讨论度很高。它的 API 接口兼容 OpenAI 的调用格式这意味着你不需要学习一套全新的 SDK只要把base_url和api_key换成 DeepSeek 的再用 OpenAI 的 Python SDK 就能直接调用。在字幕翻译这个场景里DeepSeek 的优势主要是三点中文表达质量稳定对葡语等小语种也有不错的理解和翻译能力上下文窗口足够大可以一次性塞入多条字幕文本减少逐条调用的成本API 调用成本相对可控适合个人项目和批量文本处理。需要注意DeepSeek 虽然接口兼容 OpenAI但它并不等于 OpenAI。在实际使用中模型对 Prompt 的敏感度、返回格式的稳定性、超时表现都可能有差异需要针对自己的任务做少量调参这一点后面会专门讲。1.3 技术选型与工具链本文采用的工具链如下Python 3.9编写字幕解析与翻译脚本openai SDK通过兼容协议访问 DeepSeek APIDeepSeek API提供大模型翻译能力ffmpeg可选用于视频字幕封装或字幕格式转换SRT 文件作为输入和输出格式。文章会以一部 1990 年的老 OVA 为例演示完整流程但不会涉及具体资源获取重点放在“如何用代码完成字幕翻译”这件事上。2. 环境准备与依赖安装2.1 基础运行环境在开始之前需要确认你的机器满足以下条件Python 3.9 或更高版本pip 包管理工具可以正常访问 DeepSeek API 的网络环境一个 DeepSeek 开放平台的账号并创建好 API Key。如果你之前装过旧版的openai建议先升级pip install --upgrade openai这里有一个容易踩坑的地方不同版本的 openai SDK 在初始化客户端时写法不同。旧版本使用openai.api_key xxx新版本则推荐用OpenAI(api_keyxxx, base_urlxxx)这种方式。本文统一使用新版写法也是 DeepSeek 官方文档推荐的接入方式。2.2 创建项目结构建议单独建一个目录存放本次项目的所有文件避免把脚本、字幕、中间结果混在一起subtitle-translator/ ├── input/ │ └── episode01.pt.srt # 原始葡萄牙语字幕 ├── output/ │ └── episode01.zh.srt # 翻译后的中文字幕 ├── cache/ │ └── translation_cache.json # 翻译缓存用于断点续跑 ├── translate_srt.py # 核心翻译脚本 └── requirements.txt # 依赖清单input目录放原始字幕文件output目录放翻译结果cache目录用于保存翻译过程中的中间结果。这个结构看起来简单实际会很省心尤其是遇到字幕较长需要分批处理的时候。2.3 安装 Python 依赖在项目根目录创建requirements.txtopenai1.0.0 tqdm4.0.0然后执行安装pip install -r requirements.txttqdm不是必须的但翻译几百条字幕时有一个进度条会直观很多建议装上。2.4 配置 DeepSeek API KeyDeepSeek 的 API Key 可以在开放平台控制台创建。创建后建议通过环境变量读取不要硬编码在脚本里避免密钥泄露。export DEEPSEEK_API_KEYsk-xxxxxxxxxxxxxxxx如果你使用的是 Windows可以改用set DEEPSEEK_API_KEYsk-xxxxxxxxxxxxxxxx脚本内部通过os.getenv(DEEPSEEK_API_KEY)读取。3. DeepSeek API 接入基础3.1 为什么能直接用 OpenAI SDK 调用 DeepSeekDeepSeek 的 API 采用 OpenAI 兼容协议。也就是说在发送 HTTP 请求时路径、请求体结构、鉴权方式都与 OpenAI 的标准接口保持一致只是在base_url上指向 DeepSeek 的服务地址。这样做的好处是开发者无需额外学习一套调用规范只要会 OpenAI SDK就能无缝切换到 DeepSeek。对于已经写好 OpenAI 调用的老项目迁移成本几乎为零。3.2 最小调用示例先看一个最简单的调用示例确认 API 连通性from openai import OpenAI import os client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com ) response client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是一个专业的字幕翻译引擎。}, {role: user, content: 请把下面这句话翻译成中文Olá, meu nome é Licca.} ], temperature0.3, max_tokens500 ) print(response.choices[0].message.content)如果一切正常输出应该是一句类似“你好我叫丽佳”的中文。这里需要说明的是base_url和model这两个参数以 DeepSeek 官方文档为准不同时期文档可能会有调整。如果你发现连接失败第一步应该去官方文档核对当前最新的接口地址和模型名。3.3 关键参数的作用temperature和max_tokens是字幕翻译中最需要调整的两个参数。temperature控制随机性。值越大输出越发散值越小输出越稳定。字幕翻译属于强约束任务建议设为 0.2 到 0.4 之间不要用默认的 1.0否则同一个词每次翻译结果可能都不一样。max_tokens限制单次返回的最大 token 数。字幕文本通常不会太长设成 500 到 1000 就够用。如果翻译的是长段落再适当调大。top_p也可以用来控制采样范围但一般和temperature二选一即可不需要同时调。另外建议在system消息里明确翻译任务的角色和要求例如“你是一个专业的字幕翻译引擎请保持术语一致不要添加解释”这比把大段指令塞进每一条字幕请求里更高效。4. 核心实战SRT 字幕葡转中完整脚本4.1 SRT 文件结构分析先看一个典型的 SRT 字幕片段1 00:00:01,000 -- 00:00:04,000 Olá, meu nome é Licca. 2 00:00:05,000 -- 00:00:08,000 Vamos começar a aventura!SRT 格式由四个部分组成字幕序号从 1 开始递增时间轴格式为小时:分钟:秒,毫秒 -- 小时:分钟:秒,毫秒字幕文本可以有一行或多行空白行用于分隔相邻两条字幕。解析时我们只需保留序号和时间轴原样不动把文本部分替换为翻译结果。4.2 编写 SRT 解析模块import re def parse_srt(content): 将 SRT 文件内容解析为列表。 每条字幕是一个 dict包含 index、time、text 三个字段。 blocks content.strip().split(\n\n) subtitles [] for block in blocks: lines block.strip().split(\n) if len(lines) 2: continue index lines[0].strip() time_line lines[1].strip() text \n.join(lines[2:]).strip() # 简单校验时间轴格式避免脏数据进入翻译流程 if not re.match(r\d{2}:\d{2}:\d{2},\d{3} -- \d{2}:\d{2}:\d{2},\d{3}, time_line): continue subtitles.append({ index: index, time: time_line, text: text }) return subtitles这个函数的逻辑很简单用空行把字幕块切开然后依次提取序号、时间轴、文本。加一个正则校验是为了过滤掉格式异常的行防止这些异常内容被当成字幕文本发给模型。4.3 构造翻译 Prompt直接翻译单条字幕对模型来说比较轻松但如果希望上下文一致可以使用分组翻译。将同一批字幕的文本拼成一个带编号的列表让模型逐条翻译然后你再用代码把结果按编号拆开。示例 Prompt 结构如下请将以下字幕从葡萄牙语翻译成中文。 要求 1. 保持每条字幕的编号输出 JSON 格式。 2. 角色名和专有名词保持前后一致。 3. 只输出 JSON不要额外解释。 输入 [ {id: 1, text: Olá, meu nome é Licca.}, {id: 2, text: Vamos começar a aventura!} ]这里选择 JSON 格式返回是为了方便程序解析。需要注意大模型不一定保证每次都返回合法 JSON所以代码里必须有容错处理。4.4 批量翻译与并发控制如果一条一条调用 API翻译速度会很慢。这里我们可以使用ThreadPoolExecutor实现并发请求但并发数不宜过大否则容易触发 API 速率限制。from concurrent.futures import ThreadPoolExecutor, as_completed import json import time def translate_batch(client, batch, src_lang葡萄牙语, tgt_lang中文): 翻译一批字幕文本。 batch: [{id: 1, text: ...}, ...] 返回: {id: translated_text} prompt_input json.dumps(batch, ensure_asciiFalse) system_prompt ( f你是一个专业的字幕翻译引擎。 f请将用户输入的字幕文本从{src_lang}翻译成{tgt_lang}。 f严格输出 JSON格式为 {\id\: \翻译后的文本\}不要输出其他内容。 ) response client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: system_prompt}, {role: user, content: prompt_input} ], temperature0.3, max_tokens1000 ) raw_output response.choices[0].message.content.strip() # 尝试解析 JSON如果失败则逐条提取 try: result json.loads(raw_output) return {int(k): v for k, v in result.items()} except json.JSONDecodeError: # 容错如果模型输出了多余的文字尝试提取 JSON 片段 match re.search(r\{.*\}, raw_output, re.S) if match: result json.loads(match.group()) return {int(k): v for k, v in result.items()} raise RuntimeError(f模型返回格式异常: {raw_output}) def translate_srt(client, subtitles, batch_size10, max_workers3): 并发翻译所有字幕。 results {} batches [] current_batch [] for sub in subtitles: current_batch.append({id: sub[index], text: sub[text]}) if len(current_batch) batch_size: batches.append(current_batch) current_batch [] if current_batch: batches.append(current_batch) with ThreadPoolExecutor(max_workersmax_workers) as executor: futures [] for batch in batches: futures.append(executor.submit(translate_batch, client, batch)) for future in as_completed(futures): try: batch_result future.result() results.update(batch_result) except Exception as e: print(f批次翻译失败: {e}) return results这里有几个设计点可以展开说明。第一batch_size控制每次请求携带多少条字幕。批次太大模型可能记不住所有内容输出也可能被截断批次太小又浪费上下文窗口。10 到 15 条是比较合理的经验值。第二max_workers控制并发请求数。建议从 3 开始如果 API 没有报限流错误再逐步调大。并发数太高反而容易出现大量超时和重试。第三JSON 解析失败的容错逻辑很关键。模型有时会在 JSON 前后加一句“好的这是翻译结果”导致json.loads报错。用正则提取大括号片段可以解决绝大多数情况。4.5 回写 SRT 文件翻译完成后把结果按原来的顺序写回 SRTdef write_srt(subtitles, translation_map, output_path): 将翻译结果写回 SRT 文件。 subtitles: 原始解析出的字幕列表 translation_map: {index: translated_text} with open(output_path, w, encodingutf-8) as f: for sub in subtitles: idx sub[index] translated translation_map.get(str(idx)) or translation_map.get(int(idx)) f.write(f{sub[index]}\n) f.write(f{sub[time]}\n) f.write(f{translated}\n\n)这里要注意编码问题。SRT 文件必须使用 UTF-8 编码保存否则中文在播放器里可能出现乱码。部分播放器对 UTF-8 BOM 更友好如果你发现系统播放器识别不了编码可以改成utf-8-sigwith open(output_path, w, encodingutf-8-sig) as f:4.6 主流程组装有了上面的模块主流程就很清晰了def main(): api_key os.getenv(DEEPSEEK_API_KEY) if not api_key: raise ValueError(请先设置 DEEPSEEK_API_KEY 环境变量) client OpenAI( api_keyapi_key, base_urlhttps://api.deepseek.com ) input_path input/episode01.pt.srt output_path output/episode01.zh.srt with open(input_path, r, encodingutf-8) as f: content f.read() subtitles parse_srt(content) print(f解析到 {len(subtitles)} 条字幕) results translate_srt(client, subtitles, batch_size10, max_workers3) # 检查是否有未翻译的空缺 missing [sub[index] for sub in subtitles if sub[index] not in results] if missing: print(f以下字幕翻译失败需要重试: {missing}) write_srt(subtitles, results, output_path) print(f翻译完成结果已保存到 {output_path}) if __name__ __main__: main()运行python translate_srt.py首次运行建议先用一个短字幕文件测试确认流程通了再处理完整版字幕。4.7 结果检查翻译完之后打开output/episode01.zh.srt重点检查几个地方每条字幕的序号是否仍然连续递增时间轴是否跟原文件完全一致翻译文本是否通顺角色名是否统一。如果发现某条字幕内容缺失可以针对该条单独重试也可以修改 Prompt 后整批重跑。由于有缓存机制后面会讲重跑成本很低。5. 本地部署 DeepSeek 的替代方案5.1 什么情况下需要本地部署调用 DeepSeek 官方 API 是最省事的方案但有些场景需要考虑本地部署字幕内容涉及未公开素材不方便发送到外部 API需要批量翻译的数据量很大API 费用超出预算网络不稳定长时间批量任务容易中断。本地部署的核心理念是使用开源的 DeepSeek 系列模型权重在自有机器上运行推理服务。这种方式前期需要一定的硬件投入尤其是显存要求比较高但部署完成后的调用成本几乎为零而且数据不出内网。5.2 本地部署后的 API 接入差异本地部署推理服务后通常也会提供一个兼容 OpenAI 的本地接口。此时脚本中只需要改两个地方client OpenAI( api_keylocal, # 本地服务通常不校验 key但需要占位 base_urlhttp://localhost:8000/v1 )模型名字也可能不一样需要按你实际部署时配置的名字来写。其余代码包括 SRT 解析、翻译、回写都可以完全复用。这也是使用 OpenAI 兼容协议的好处之一上层业务代码不依赖具体推理服务商。5.3 关于 DeepSeek harness 这类工具在 DeepSeek 社区里经常能看到 “harness”“桌面版”“插件” 等关键词。简单来说这类工具通常是对 DeepSeek API 的进一步封装把模型调用、批量任务管理、Prompt 编排等功能做成了可视化界面或命令行工具。对于字幕翻译这类项目harness 类工具的核心价值并不在“调用模型”这一步而在“任务管理”和“结果复用”。例如你可以把字幕翻译做成一个工作流输入 SRT 文件自动完成解析、翻译、回写、压制等步骤。如果你使用的是这类工具需要注意版本兼容性。底层 API 一旦调整工具未同步更新就可能导致调用失败。遇到问题先去对应工具的文档或 GitHub Issue 查找而不是盲目升级依赖。本地部署和工具链的选择最终应根据硬件条件、数据安全要求和预算综合判断没有绝对的最优解。6. 常见问题与排查思路6.1 API 调用异常清单问题现象常见原因解决思路Connection error或请求超时网络环境不稳定或代理配置冲突检查网络连通性确认 DNS 能解析api.deepseek.comAuthenticationErrorAPI Key 配置错误或已失效核对DEEPSEEK_API_KEY环境变量和官方控制台中的 KeyRateLimitError请求频率超过 API 限制降低max_workers增大批次间隔模型返回空内容max_tokens设置过小调大max_tokens并检查输入是否为空JSON 解析失败模型输出了额外说明文字用正则提取 JSON 片段或调整 system 指令6.2 翻译质量问题排查如果翻译结果能输出但质量不理想通常从以下三个方向排查Prompt 不够明确。如果你只写“请翻译”模型会按自己的理解发挥。建议在 system 里明确指定“字幕翻译”“保持专有名词一致”“不要添加解释”等约束。批次过大导致上下文干扰。一个批次塞入太多字幕模型可能混淆编号和内容。可以适当减小batch_size把每批控制在 5 到 8 条。缺乏术语表。对于角色名、特定世界观词汇可以在 Prompt 中预先提供中葡对照表例如术语对照表 Licca - 丽佳 Mariko - 真理子模型在翻译时会优先使用这些术语减少前后不一致的概率。6.3 SRT 回写后的播放问题翻译完成后如果播放器显示乱码或时间轴错乱优先检查文件是否以 UTF-8 编码保存每条字幕之间是否有空行时间轴格式是否与原始文件一致是否误把翻译文本写入了时间轴区域。另外不同播放器对 SRT 解析的宽松程度不同。如果某个播放器显示异常先换一个播放器确认是文件问题还是播放器问题。7. 工程化最佳实践7.1 增加缓存实现断点续传批量翻译最怕中途失败。要避免从头再来可以在翻译前先把已有结果加载到内存翻译成功后立即写入缓存文件。缓存文件的格式很简单一个 JSONkey是字幕序号value是翻译结果import os import json CACHE_PATH cache/translation_cache.json def load_cache(): if os.path.exists(CACHE_PATH): with open(CACHE_PATH, r, encodingutf-8) as f: return json.load(f) return {} def save_cache(cache): os.makedirs(os.path.dirname(CACHE_PATH), exist_okTrue) with open(CACHE_PATH, w, encodingutf-8) as f: json.dump(cache, f, ensure_asciiFalse, indent2)在translate_batch返回结果后把该批次所有字幕的翻译写入缓存。这样即使程序在 500 条字幕时崩溃重启后可以直接从缓存加载前 499 条结果只翻译最后一条。7.2 日志记录与错误追踪建议在脚本中加入简单的日志输出import logging logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, handlers[ logging.FileHandler(translate.log, encodingutf-8), logging.StreamHandler() ] )日志不仅能帮你定位崩溃位置还能统计每个批次的耗时和失败率。如果某个批次反复失败说明该批次内容可能有异常比如包含特殊字符或超长文本。7.3 成本与速率控制大模型 API 是按 token 计费的。字幕翻译的 token 消耗主要在输入内容你发送的字幕文本越多、Prompt 写得越长成本越高。为了控制成本如果字幕文本本身是干净的可以去掉冗余的格式信息如果原文很长但语法很基础可以先做简单清洗再翻译设置单批次的max_tokens避免模型输出过多无关内容在批量任务推进前先用 20 条字幕做小规模测试估算总体费用。换算成本时不要只算翻译的 token还要把 system prompt、返回的 JSON 结构等隐性消耗算进去。实际经验是短句为主的字幕有效 token 占比大约在 60% 到 70% 之间。7.4 版权与合规提示最后必须强调版权问题。字幕翻译本身是合法的技术操作但翻译后的字幕如果用于公开传播、二次分发必须确保你拥有对应视频和字幕的合法授权。建议只在以下场景使用这套流程自己收藏的、有合法来源的影视素材明确授权允许翻译和二次创作的内容学习、研究、个人使用等非商业用途。如果涉及商业项目务必先确认素材版权避免因字幕分发引发版权纠纷。这是工程问题之外同样重要的一环。8. 总结这次用 DeepSeek API 给一部 1990 年的老 OVA 做葡转中字幕完整走完了「解析 SRT → 构造 Prompt → 批量翻译 → 回写 SRT」的链路。过程中最有价值的并不是“调用 API”这一步而是对字幕格式的解析、对模型返回结果的容错处理以及对翻译一致性的控制。把这四块做好这套脚本可以直接复用到任意语言方向的字幕翻译任务。如果你接下来想深入建议先做三件事一是给脚本加上缓存续传二是制作一个适合自己的术语表三是在本地部署一个小模型做对比测试。这样你就能真正理解 API 调用和本地推理的差异也能在后续项目中做出更合理的技术选型。希望这篇实战笔记能帮你在 DeepSeek 字幕翻译的路上少踩几个坑。