做字幕整理这件事,我踩过的坑比想象中多。最开始我以为“下载字幕”就是把视频拖进某个工具、点一下按钮、等一个 srt 文件出现,结果第一次尝试就失败了——工具跑完提示成功,打开文件却是空的。后来才想明白,问题根本不在工具好不好用,而在于我当时连自己要的是哪一种字幕都没搞清楚。标题里说的那个弹幕视频社区(下文统一用“该站”指代),站内的字幕其实分三种完全不同的东西,来源不同、存放位置不同、获取链路也完全不同,你拿处理 A 的方法去套 B,必然失败。
这篇内容就是把这三种字幕拆开讲清楚,然后分别给出可复现的获取和处理路径:外挂字幕怎么从浏览器里手工捞出来、怎么批量跑、硬字幕只能走画面识别、以及那些标着“一键”的工具为什么三天两头失灵。适合做课程笔记整理、做视频二次创作素材准备、或者需要把字幕当语料做文本分析的人看。不需要你有多深的编程功底,但至少得会用浏览器开发者工具、能照着敲几行命令。
1. 先分清三种字幕,这决定了你后面所有工具的选择
我见过太多人卡在这一步。他们在网上搜到一个工具,输入链接,工具报错或者产出空文件,然后就开始怀疑工具不行、网络不行、电脑不行。实际情况是:该站上的“字幕”是三个物理位置完全不同的东西,有的存在服务器上是一个 JSON 文件,有的压根不存在于任何服务器,只存在于视频画面的像素里。你让一个设计用来下载外挂字幕的工具去处理硬字幕,它当然只能给你一个空文件。
所以在动手之前,先花三十秒判断你要的那个视频属于哪一类。判断方法很简单:打开视频,看播放器右下角的字幕按钮(一个“字幕”字样或者“CC”图标),点开看有没有可选项。有,就是外挂字幕;没有,但画面上确实有一行行的字,那就是硬字幕。这一步做完,后面的路才有得选。
1.1 创作者手动上传的外挂字幕
这一类是最好的情况,也是唯一能拿到“干净文本”的情况。创作者在投稿时通过后台的字幕管理功能上传了一份字幕文件,平台把它转成自己的格式存起来,播放时按时间轴渲染。这种字幕有几个特征:断句是人手工做的,标点符号规范,同音错字少,时间轴贴合语音节奏,甚至还有说话人标注。
它的价值在于“可直接复用”。你拿到手基本不用改就能当笔记、当语料、当翻译底稿。所以如果你的目标视频有 CC 字幕,优先走这条路,别去想什么 OCR,那是给自己找麻烦。
需要提醒的是,创作者上传的字幕不一定对所有语言都齐全。有些视频只有中文,有些只有英文,有些上传了双语但默认只显示一条。在字幕菜单里逐个点一遍,把所有可选项都记下来,因为不同语言的字幕对应不同的字幕 ID,后面提取的时候要用到。
1.2 平台语音识别自动生成的字幕
没有创作者上传的字幕时,平台可能会用语音识别跑一份出来。这类字幕的典型问题是:断句按声学模型切,经常把一句话切成两三段;专有名词、人名、生僻词错得离谱;标点基本靠猜,有时候整段没有标点;遇到背景音乐或者多人抢话,识别质量断崖式下跌。
但它的优势是覆盖率高。很多口语类、访谈类、直播回放类内容只有自动字幕,没有人工字幕。这时候你没得挑,只能拿它,然后自己做后期清洗。
清洗的活其实有套路。我的做法是先把所有字幕条目导出成一行行纯文本,去掉时间轴,然后做三件事:一是合并被切碎的短句(判断标准是上一条结尾没有句末标点、且与下一条的间隔小于 0.5 秒);二是用一份自定义词表做批量替换,把反复出错的那几个专有名词一次性修掉;三是通读一遍补标点。这三步做完,可读性能提升一个档次。
1.3 已经烧进画面的硬字幕
这是最麻烦的一类。字幕在视频压制阶段就已经合进画面像素了,服务器上没有任何文本形式的字幕文件存在。你在开发者工具里翻遍所有请求,也找不到一个带字幕内容的响应,因为它根本不存在。
硬字幕的来源通常是:创作者用剪辑软件直接烧的字幕、搬运内容的二次压制、或者老视频的原始版本。判断方法很直接:把视频音量关掉,看画面上的字是不是跟着画面一起动的。如果是,那就是烧进去的。
处理硬字幕只有一条路——画面文字识别。后面第 4 节会专门讲,但这里先给个预期管理:OCR 出来的结果错误率明显高于前两种,尤其是背景复杂、字体细、有描边或者半透明的情况。如果你只是想要个大概内容做检索,勉强够用;如果要做逐字稿,后期校对的工作量会很大。
1.4 一张表看清三者的获取难度差异
| 字幕类型 | 数据是否存在 | 典型获取方式 | 文本质量 | 时间成本 |
|---|---|---|---|---|
| 创作者上传外挂字幕 | 存在于服务器,JSON 格式 | 抓接口 / 命令行工具 | 高,可直接用 | 低 |
| 平台自动识别字幕 | 存在于服务器,JSON 格式 | 抓接口 / 命令行工具 | 中,需清洗 | 中 |
| 硬字幕 | 不存在,融在像素里 | 抽帧 + OCR | 低,需大量校对 | 高 |
看懂这张表,你就明白为什么“一键”这个词在字幕这件事上基本是个营销话术。真正能称得上“一键”的只有第一种和第二种,而且还得是在工具恰好没被接口变动搞挂的前提下。硬字幕的“一键”永远只是个开始,后面跟着的是漫长的校对。
2. 浏览器开发者工具里的手工提取链路
先说最原始但最可靠的办法:用浏览器自带的开发者工具,把字幕请求揪出来。这条路的好处是不依赖任何第三方工具,接口怎么变你都能自己适配;坏处是每次只能处理一个视频,适合数量少、或者你想先搞清楚数据结构的情况。
我把这套动作拆成固定的几步,熟练之后一个视频两三分钟能搞定。
2.1 从打开面板到定位字幕请求的完整动作
第一步,先在浏览器里把视频页面打开,不要急着开开发者工具。原因是要先让页面把该加载的都加载完,否则你会被一堆无关请求淹没。等视频能正常播放、字幕也能正常显示了,再按 F12 打开开发者工具。
第二步,切到 Network(网络)面板,然后在过滤器输入框里敲关键词。这里有个小技巧:不同平台的接口命名不一样,但大概率会包含subtitle、sub、caption、text这类词中的一个。如果搜不到,就改搜json,然后按请求大小排序,字幕文件通常不大,几十 KB 到几百 KB。
第三步,也是最关键的一步:在开发者工具开着的情况下,刷新页面,或者把字幕开关关掉再打开一次。很多人漏了这步,结果在已经加载完的请求列表里翻半天找不到,因为字幕请求在页面初次加载时就发完了,不重新触发就抓不到。
第四步,找到候选请求后,点开看 Response(响应)标签。如果里面是一串带时间戳和文本的 JSON 数组,恭喜,就是它。这时候右键该请求,选择 Copy → Copy link address,把地址存下来。同时看一眼 Request Headers 里的 Cookie 和 Referer,这两个字段往往是能不能拿到数据的关键。
提示:如果 Response 里返回的是 403、401 或者一个空数组,八成是缺了登录态。先在浏览器里正常登录该站,再重复上面的步骤。字幕接口对身份的校验比视频流严格得多。
这里必须点出一个高频误区:很多人以为拿到 URL 就能直接在浏览器地址栏打开。不一定。字幕接口通常要求带 Referer,你在地址栏直接访问是没有这个头的,会被拒绝。所以拿到 URL 之后,要么用命令行工具带上请求头去请求,要么在开发者工具里右键选择 Copy as cURL,把整条命令复制出来用。
2.2 返回体长什么样:字段逐一拆解
字幕接口返回的 JSON 结构大同小异,常见的形态是外面套一层对象,里面一个数组,数组每一项是一句话。典型结构长这样:
{ "font_size": 0.4, "font_color": "#FFFFFF", "background_alpha": 0.5, "body": [ { "from": 1.24, "to": 3.86, "location": 2, "content": "第一句话的内容" }, { "from": 3.90, "to": 7.12, "location": 2, "content": "第二句话的内容" } ] }关键字段就四个:
from:这句话出现的起始时间,单位是秒,带小数。to:结束时间,同样是秒。content:文本内容。有些平台会给换行符,有些会给 HTML 实体(比如&),需要反转义。location:位置标记,一般 2 表示底部居中,不用管它,转换时忽略即可。
这里有两个容易忽略的细节。一是content里可能包含<br>或者\n,如果直接塞进 SRT,某些播放器解析会出问题,最好统一替换成空格。二是时间精度:from和to如果只精确到两位小数,转换到 SRT 的毫秒格式时可能出现3.86转成3,860的情况——这是对的,但要确保你没有用浮点数直接做字符串拼接,否则会得到3.86这种非法格式。
还有一个坑:有些视频的字幕是按分段返回的。比如一个两小时的直播回放,接口可能一次只返回五分钟的字幕,需要带着一个pid或者segment_index参数连续请求多次。判断方法是看返回数组的最后一个to值是不是接近视频总时长。如果远小于,就说明还有后续分段。这种情况手工处理很痛苦,建议直接上第 3 节的批量方案。
2.3 把 JSON 转成 SRT 的脚本与运行方法
拿到 JSON 之后,下一步是转成通用字幕格式。SRT 是最稳的选择,几乎所有播放器、剪辑软件、字幕编辑器都认。格式规矩很简单:序号、时间轴、文本、空行,四件套循环。
下面这个脚本我用了很久,处理几万条字幕没出过问题:
import json import html import sys def fmt_time(t: float) -> str: """秒 -> SRT 时间格式 00:00:01,230""" if t < 0: t = 0.0 h = int(t // 3600) m = int((t % 3600) // 60) s = t % 60 return f"{h:02d}:{m:02d}:{s:06.3f}".replace(".", ",") def clean(text: str) -> str: text = html.unescape(text or "") for token in ("<br>", "<br/>", "\\n", "\n", "\r"): text = text.replace(token, " ") return " ".join(text.split()) def to_srt(items) -> str: fixed = [] for it in items: start = float(it.get("from", 0)) end = float(it.get("to", 0)) if end <= start: end = start + 0.8 # 时间轴异常的兜底 fixed.append((start, end, clean(it.get("content", "")))) fixed.sort(key=lambda x: x[0]) out = [] for idx, (start, end, text) in enumerate(fixed, 1): if not text: continue out.append(str(idx)) out.append(f"{fmt_time(start)} --> {fmt_time(end)}") out.append(text) out.append("") return "\n".join(out) def load_items(path): with open(path, "r", encoding="utf-8") as f: data = json.load(f) if isinstance(data, list): return data for key in ("body", "subtitles", "data", "list"): if isinstance(data.get(key), list): return data[key] raise ValueError("没找到字幕数组,请检查 JSON 结构") if __name__ == "__main__": src, dst = sys.argv[1], sys.argv[2] srt = to_srt(load_items(src)) with open(dst, "w", encoding="utf-8") as f: f.write(srt) print(f"已写入 {dst},共 {srt.count('-->')} 条")用法很简单,把浏览器里复制出来的响应保存成sub.json,然后:
python sub2srt.py sub.json output.srt脚本里有几个设计是踩坑换来的。load_items会尝试多个可能的键名,因为不同接口外面那层壳不一样,有的是body,有的是data,硬编码一个键名迟早会挂。end <= start的兜底是为了应对部分自动字幕里出现的时间轴倒挂,这种数据直接转 SRT 会让播放器卡住。fixed.sort是按起始时间重排,防止分段拼接后顺序错乱。clean里的html.unescape是必须的,因为有些字幕里的"、&不反转义就会原样显示。
跑完之后一定要抽几条检查。我的习惯是打开生成的 srt,看第一条和最后一条的时间,和视频总时长对一下;再随机挑中间三条,跳转到对应时间点,看字幕是不是跟画面同步。如果整体偏移几秒,说明你拿到的可能是某个分段的字幕,不是完整版。
2.4 分P视频和多语言字幕的处理
多P(或者叫合集、分集)视频是另一个高频卡点。同一个页面下不同分P的字幕是独立的,接口参数里会带一个分集标识。手工处理的话,你得在切换分P之后重新抓一次请求,把每个分P的 JSON 分别存下来,命名上带序号,再逐个转换。
我一般会建立一个目录结构:
项目名/ raw/ 01_第一集标题.json 02_第二集标题.json srt/ 01_第一集标题.srt 02_第二集标题.srt notes.mdraw放原始 JSON,srt放转换结果,notes.md记录这个视频的字幕类型(人工/自动)、总时长、跳过的集数、遇到的问题。别嫌这个笔记多余,隔两周你回头看,绝对想不起来当时为什么少转了一集。
多语言的情况类似。同一个分P可能有中文、英文两份字幕,接口参数里带语言代码。都下下来,文件名后面加上语言后缀,比如01_第一集.zh.srt、01_第一集.en.srt。做双语对照的时候方便得多。
3. 上百个视频怎么办:批量化处理的工程化思路
手工抓三个视频还行,抓三十个就是折磨。我做过一次两百多个视频的字幕整理,全程手工的话至少要两个通宵,后来改成命令行批量跑,一个下午搞定。这一节讲的就是批量的工程化思路。
3.1 单条命令行的参数组合
批量的核心是找到一条能跑通的单条命令,然后套循环。目前主流的开源媒体下载工具基本都支持只下字幕、不下视频。以常见的命令行下载器为例(下文用dl代指可执行文件名,具体装哪个自己找对应的即可):
dl \ --write-subs \ --write-auto-subs \ --sub-langs "zh-Hans,zh-CN,en" \ --convert-subs srt \ --skip-download \ -o "output/%(uploader)s/%(title)s.%(ext)s" \ "视频页面地址"参数逐个解释,因为每一个都有它的必要性:
--write-subs:下载人工上传的字幕。--write-auto-subs:下载自动生成的字幕。这两个要同时加上,因为你不知道目标视频属于哪种。--sub-langs:语言过滤器。这里写得太宽松会下下来一堆你不需要的小语种,白占空间还拖慢速度。我一般只留简体中文、繁体中文、英文三种。--convert-subs srt:把下载下来的字幕转成 srt。不转的话默认格式是平台自己的,有些播放器不认。--skip-download:只下字幕,不下视频。这个参数是省时间的关键,不加的话它会把整个视频拖下来,几百个视频就是几百 GB。-o:输出模板。%(uploader)s是创作者名,%(title)s是视频标题,用目录分创作者,后面找东西方便太多。
第一次跑之前,先用单个视频试试,确认能产出文件、文件里有内容、时间轴正常。别一上来就丢两百个链接进去,跑完发现全是空的,白等。
3.2 批量循环与命名规范
单条通了之后,把链接整理成一个文本文件,一行一个,然后套一个循环。Linux/macOS 下用 bash:
#!/usr/bin/env bash set -u INPUT="urls.txt" LOG="run.log" : > "$LOG" while IFS= read -r url; do [ -z "$url" ] && continue case "$url" in \#*) continue ;; esac echo "[$(date '+%H:%M:%S')] 开始: $url" | tee -a "$LOG" if dl --write-subs --write-auto-subs \ --sub-langs "zh-Hans,zh-CN,en" \ --convert-subs srt --skip-download \ -o "output/%(uploader)s/%(title)s.%(ext)s" \ "$url" >> "$LOG" 2>&1; then echo " 成功" | tee -a "$LOG" else echo " 失败: $url" | tee -a "$LOG" fi sleep 2 done < "$INPUT"几个细节值得说。set -u是防止变量未定义时静默出错。case那段是允许你在urls.txt里写注释行,方便标注每个链接是干什么的。sleep 2是给请求之间留点间隔,太密集的连续请求容易被限流,加个两秒能省掉很多莫名其妙的失败。日志用tee同时写文件和控制台,跑长任务的时候你能随时瞄一眼进度。
Windows 下用 PowerShell 也能写,逻辑一样:
Get-Content urls.txt | ForEach-Object { $url = $_.Trim() if (-not $url -or $url.StartsWith('#')) { return } Write-Host "开始: $url" dl --write-subs --write-auto-subs ` --sub-langs "zh-Hans,zh-CN,en" ` --convert-subs srt --skip-download ` -o "output/%(uploader)s/%(title)s.%(ext)s" ` $url Start-Sleep -Seconds 2 }3.3 失败重试与断点续跑
批量跑一百个链接,中间挂掉几个是必然的。原因五花八门:网络抖动、接口临时限流、某个视频的字幕接口参数特殊、文件名里有非法字符导致写入失败。所以你需要一个“失败清单 + 重跑”的机制。
最简单的做法是从日志里捞失败项:
grep "失败:" run.log | sed 's/.*失败: //' > retry.txt然后拿retry.txt当输入再跑一遍。我一般会重试两轮,两轮还失败的单独拎出来手工看,通常是有特殊情况,比如那个视频压根没有字幕、或者需要登录态。
另一个机制是断点续跑。如果你把输出目录按%(uploader)s分了子目录,那重跑的时候可以先扫一遍已有文件,把已经成功的跳过:
while IFS= read -r url; do [ -z "$url" ] && continue # 如果输出目录里已经有对应文件就跳过(这里用标题是否已存在做粗判断) dl --write-subs --write-auto-subs --sub-langs "zh-Hans,zh-CN,en" \ --convert-subs srt --skip-download \ --no-overwrites \ -o "output/%(uploader)s/%(title)s.%(ext)s" "$url" done < urls.txt--no-overwrites是关键,已存在同名文件就不重复下载,这比自己去写判断逻辑省事得多。
注意:批量跑的时候一定要留意磁盘和文件名的合法性。视频标题里经常出现
/、:、?、*这些字符,某些系统上会直接导致写入失败。下载器一般会自动替换,但不是百分百可靠,跑完之后扫一遍有没有零字节文件,比事后重来强。
批量跑完之后,我还会写一个小校验脚本,把所有 srt 扫一遍,输出统计:文件数、总条数、平均每条时长、有没有空文件、有没有单条时长超过 15 秒的异常条目(超过 15 秒通常意味着断句出问题了)。这个统计表能让你一眼看出哪几个文件有问题,而不是等用的时候才发现。
4. 硬字幕这条路只能靠画面识别
回到最麻烦的那类。当服务器上压根没有字幕文件时,你唯一能做的就是“看图识字”:把视频切成帧,找到字幕出现的位置,把那一块抠出来做文字识别,再按时间戳拼回去。这套流程听起来复杂,实际做起来也有成熟的组件可以拼。
4.1 抽帧策略:抽太密是浪费
第一步是抽帧。用 ffmpeg 把视频按固定频率抽成图片:
ffmpeg -i input.mp4 -vf "fps=2" frames/%06d.pngfps=2表示每秒抽两帧。为什么是 2 而不是 25(原始帧率)?因为字幕的变化频率远低于视频帧率。一句话字幕通常持续两到五秒,每秒抽两帧已经能覆盖,抽 25 帧纯属浪费算力和硬盘——一个十分钟的视频抽 25fps 就是一万五千张图,处理起来非常慢。
但 2fps 有个副作用:一句话的中间可能被抽到多次,产生大量重复。这没关系,后面做去重就行。反过来,如果字幕变化特别快(比如歌词、快节奏解说),2fps 可能漏掉某些短句,这时候可以提到 4fps 或者 5fps。我的经验是先 2fps 跑一段试试,看抽出来的图里字幕覆盖完整不完整,不完整再往上调。
抽帧之后是裁剪。字幕一般在画面底部,你不需要识别整张图。先用 ffmpeg 的 crop 把底部三分之一裁出来:
ffmpeg -i input.mp4 -vf "fps=2,crop=iw:ih/3:0:ih*2/3" frames/%06d.png裁剪的好处有两个:一是减少 OCR 引擎要处理的像素量,速度快很多;二是去掉画面上半部分的内容,减少误识别——如果不裁,画面里的标题、水印、人物衣服上的字都可能被识别进来,污染结果。
如果你的视频里字幕位置不固定(有些视频字幕会在上下位置之间跳),那裁剪就得放宽一点,或者干脆不裁,靠后期过滤。
4.2 识别、去重与时间轴还原
OCR 这一步用什么引擎都行,开源的识别引擎现在效果都不差。关键不在引擎,在于后处理。我给一个大概的处理逻辑:
- 逐帧识别,得到每一帧的文本。如果某帧识别出多行,按位置从上到下拼接。
- 空帧剔除。大量帧可能识别出空字符串(字幕间隙),直接丢掉。
- 相似度去重。相邻两帧的文本相似度超过 90% 就认为是同一句,只保留一条。相似度用编辑距离算就行,不用上什么复杂算法。
- 合并短碎片。OCR 有时候会把一句话拆成两行识别,出现“今天我们来”“讲一下这个”这种。判断标准是上一行没有句末标点,且两帧时间相邻,就合并。
- 还原时间轴。每一条文本的起始时间 = 它第一次出现的帧的时间,结束时间 = 它最后一次出现的帧的时间。这里要注意补一个“尾巴”,因为抽帧是离散的,实际字幕可能比抽到的帧早一点点出现、晚一点点消失,统一往外扩 0.2 秒会比较自然。
举一个具体的例子说明去重的必要性。假设一句字幕从第 10 秒持续到第 13 秒,2fps 抽帧会得到第 10.0、10.5、11.0……13.0 共 7 帧,每帧都识别出同一句话。不做去重,你的 srt 里就会有 7 条一模一样、时间轴首尾相接的条目,播放时字幕会闪七次。去重之后只剩下一条,时间轴从 10.0 到 13.0,这才是正确的。
再说一个我踩过的坑:OCR 对半透明描边字幕的识别率会明显下降。如果创作者用的是细字体加半透明黑边,识别出来经常缺字。这时候可以先把图做一次二值化增强(提高对比度、转灰度、按阈值转黑白),能明显改善。但如果字幕本身是花体字或者有复杂动效,那就别指望了,老老实实人工校对。
还有一个现实预期要摆正:OCR 出来的结果,准确率能到 90% 就已经很不错了,剩下 10% 的错误分散在各处,逐条校对的工作量可能比你自己听打一遍还大。所以这条路我只推荐给两种场景:一是内容量大到人工打不完,二是你只需要关键词检索、不需要逐字准确。
5. 那些标着“一键”的工具为什么三天两头失灵
我见过太多号称能一键提取字幕的网页工具,它们的共同生命周期是:上线、被疯传、一周后大面积报错、作者失联或者偷偷改成收费。用户的第一反应通常是“这工具真垃圾”,但实际上,绝大多数失效是结构性的,跟工具本身写得好不好关系不大。
5.1 五类高频失效原因
把失效原因归归类,其实就五种,看懂了你自己就能判断问题出在哪一层:
| 失效现象 | 底层原因 | 你能否自己解决 |
|---|---|---|
| 提示成功但文件为空 | 目标视频本身没有外挂字幕 | 不能,只能换方法 |
| 报 403 / 无权限 | 接口需要登录态,工具没带 Cookie | 部分可以,手工补请求头 |
| 之前能用突然全挂 | 接口路径或字段名变动 | 看情况,需重新抓包分析 |
| 只下到前几分钟 | 字幕分段返回,工具没做翻页 | 可以,自己补分段逻辑 |
| 文件名乱码或写入失败 | 标题含非法字符 | 可以,加文件名清洗 |
第一类最常见,也最容易被误解。用户看到一个工具宣传“支持某站字幕下载”,就默认所有视频都能下。实际上工具的能力上限取决于视频有没有字幕,没有的东西它变不出来。这时候正确的动作不是换工具,而是回到第 1 节,重新判断这个视频属于哪一类。
第二类也很多。字幕接口对身份的校验比视频接口严格,很多工具为了简单,请求时只带最基本的头,结果被拒。这种情况你手工在浏览器里能拿到数据,但工具拿不到,差别就在 Cookie。解决办法要么在工具里配置 Cookie,要么干脆用浏览器的“复制为 cURL”功能,把那整条命令拿到终端里跑,请求头是完整的。
第三类是结构性问题,无解,只能等更新或者自己动手。这也是为什么我建议至少掌握第 2 节的手工方法——工具挂了的时候,你还有兜底手段。
第四类最隐蔽。工具显示“下载完成”,文件也有内容,但你打开一看只有前五分钟,后面全没有。这种时候很多人会以为视频后面没字幕,实际上是分段机制没处理。判断方法是拿字幕最后一条的时间戳和视频总时长比一下,差得远就是分段漏了。
5.2 自己做一个排查清单
与其每次失效都抓瞎,不如固化一个排查顺序。我现在遇到“下不下来”的情况,基本按这个清单走一遍,五分钟内能定位到具体是哪一环出了问题:
- 确认视频有没有字幕。播放器里点开字幕菜单,看有没有可选项。没有的话,先确认是不是硬字幕,是的话直接转 OCR 路线,别在下载工具上浪费时间。
- 确认是哪种字幕。人工上传和自动识别的接口参数可能不同,工具是否两种都支持。
- 在浏览器里手工验证一次。F12 打开,刷新页面,看能不能抓到字幕请求、Response 里有没有内容。这一步能区分“接口层面拿不到”和“工具层面拿不到”。
- 对比手工请求和工具请求的头部。重点看 Cookie、Referer、User-Agent 三项。缺哪个补哪个。
- 检查时间戳覆盖范围。字幕最后一条的
to值是否接近视频总时长,不接近就是分段问题。 - 检查输出文件。文件大小是不是 0,内容是不是只有几行,编码是不是 UTF-8。
这套清单的价值在于把“玄学”变成“可定位”。大部分人的问题是跳过第 1、3 步,直接在第 5、6 步瞎试,试半天也不知道问题在哪。
6. 使用边界:什么时候该停手
技术上讲,只要字幕以数据形式存在于你能访问的响应里,提取它并不难。但“能做”和“该做”是两件事,这条线我建议每个人都自己划清楚。
我的原则是三条。第一,只处理自己有权访问的内容。正常播放、正常显示字幕的视频,说明平台对你是开放的,这种情况下做个人用途的整理没问题。需要特殊手段才能看到的、或者平台明确限制的内容,不要碰。第二,成果只用于个人学习研究,不做二次分发、不上传到公开渠道、不用于商业用途。字幕的著作权归属比较复杂,创作者上传的字幕文本本身是有独创性的,整段搬运发布风险很大。第三,遵守平台的服务条款。批量请求时控制频率,别把人家接口打挂,这既是礼貌也是自保——请求过于密集被限流,最后麻烦的还是你自己。
具体到操作层面,有几件事我从来不做:不把整理好的字幕打包成资源包分享、不在公开内容里大段引用他人字幕原文、不用提取的字幕去做任何形式的商业化产品。做笔记、做检索、做个人语料分析,这些是我认为合理的范围。
另外提醒一句,平台的字幕功能和接口是在持续迭代的。今天能用的方法,半年后可能就变了。所以比起记住某个具体的接口地址,更有价值的是掌握那套排查思路:怎么定位请求、怎么看响应结构、怎么判断字幕类型、怎么验证结果的完整性。方法会过时,思路不会。
最后分享一个我一直在用的小习惯。每整理完一个视频的字幕,我会在文件头加三行注释(srt 允许以NOTE开头的行,或者干脆单独存一个同名.meta文件),记录:抓取日期、字幕类型(人工/自动/OCR)、来源页面的标题。这个习惯看起来多余,但当你半年后翻出几十个 srt 文件,完全不记得哪份是人工的、哪份是 OCR 出来错误一堆的,这三行注释能救你。尤其是 OCR 那份,一定要标清楚,不然哪天拿去做语料分析,错误率能把你坑哭。