拓冰建站拓冰建站
首页 / 资讯中心 / 正文

B站字幕提取全攻略:从CC字幕抓取到硬字幕OCR的三种方案

平时刷B站学习类视频遇到优质内容总想把字幕扒下来做笔记或者做对照翻译又或者给剪辑补一段引用文本。但B站页面本身没有“下载字幕”按钮很多人绕了一大圈却不得要领。实际上提取B站字幕这件事分两种完全不同的情况CC字幕是接口提供的一分钟就能抓下来硬字幕是烧在画面里的就得搬出OCR工具来认字。这篇内容我就把三套方案全部拆开讲清楚从最简单的浏览器抓包到半自动化的Python脚本再到硬字幕OCR提取大家按需取用就好。1. 先搞清楚B站字幕的两种形态1.1 CC字幕播放器里能开启的那种CC字幕是B站接口以JSON形式提供的字幕字幕不直接内嵌在视频画面中而是由播放器在渲染层之上显示。UP主上传的AI字幕或者AI自动识别生成的“中文自动生成”字幕都属于CC字幕。这类字幕有一个显著特点播放器右下角可以点开字幕选择列表你能够切换“中文字幕”“英文自动生成”“双语”等不同轨道。CC字幕最大的优点是干净。字幕文本是直接存在JSON里的字体、位置、时间轴都很规范没有画面背景干扰提取出来就是标准文本。时间轴以秒为单位精确到小数点后多位直接转成SRTSubRip Subtitle或者ASSAdvanced SubStation Alpha字幕都很方便。很多学习博主上传的长视频、课程视频通常都带CC字幕这也让它成为提取字幕场景里最高频的目标。这里要补充一个容易混淆的点B站的“AI字幕”和“CC字幕”本质上是一回事都是通过接口获取的。UP主手动上传的字幕文件同样被B站转成了内部JSON格式。所以只要播放器里能开启字幕就意味着接口端存在对应的字幕数据可以走API方案。1.2 硬字幕已经烤进画面的字幕与CC字幕相对很多影视混剪、搬运视频、访谈节目会把字幕直接压制在画面里。这种字幕被称为“硬字幕”因为字幕内容已经是画面像素的一部分缩放、旋转、滤镜都无法把文字和背景分离。硬字幕没有独立的字幕轨道你在播放器里找不到字幕选择列表。硬字幕无法通过接口获取必须走OCROptical Character Recognition光学字符识别路线先把视频画面中的字幕区域截取出来再识别其中的文字最后按时间轴合并成字幕文件。OCR方案比API方案慢得多而且准确率受字体、大小、背景干扰影响很大但它是这类视频唯一可行的提取路径。在实际工作中我通常会先用一个很快速的方式判断视频是哪种字幕在播放器里点开字幕按钮如果能弹出语言列表就是CC字幕点不开或根本不存在该按钮就直接判断为硬字幕。另一个判断方式是拖动进度条时字幕如果跟随画面一起被拉伸、模糊那就说明字幕已经被压进视频流了。对比维度CC字幕硬字幕存储方式接口JSON数据视频画面像素提取难度低接口直接拿高需要OCR识别准确率文本原样几乎无错受字体、背景、分辨率影响可编辑性高直接转SRT需人工校对推荐手法API抓取或开发者工具下载视频后本地OCR2. 最快方法浏览器开发者工具抓CC字幕JSON2.1 手把手一分钟抓包我先从最“土”但最可靠的方法说起因为它完全不需要装环境、不需要写代码只要你会按F12就行。整个流程我实测过熟练后一分钟内能拿到字幕原始JSON。第一步打开目标视频页面先把播放器里的CC字幕按钮打开让字幕真正显示在画面上。这一步很关键如果字幕没被激活后续的网络请求就不会触发。第二步按F12打开浏览器开发者工具切换到Network网络面板。为了让请求列表清爽一些可以在Filter筛选框里输入subtitle或者hdslb这个关键词。B站的字幕请求域名常见的是aisubtitle.hdslb.com或者i0.hdslb.com但这些域名下的具体URL里通常会带subtitle字符串。第三步刷新页面或上下滑动播放器让请求重新触发。此时在Network列表里会看到一个名称类似于subtitle?...的请求点击它后看右侧的Preview或Response标签。返回的JSON结构大致如下{ font_size: 0.4, font_color: #FFFFFF, body: [ { from: 1.5, to: 3.8, location: 2, content: 这是第一句字幕 }, { from: 4.2, to: 6.5, location: 2, content: 这是第二句字幕 } ] }这里的from和to分别表示字幕出现的起止时间点单位是秒content就是字幕文本location表示字幕在画面中的纵向位置通常填2辅助字幕或者弹幕式字幕会用到其他值。第四步如果想拿到独立可保存的字幕文件需要回到播放信息接口找到字幕下载地址。通常在Network里搜索player/v2或x/player找到https://api.bilibili.com/x/player/v2?bvid...cid...这个请求在返回JSON的data.subtitle.subtitles字段里能看到字幕列表。每个字幕对象包含lan语言代码、lan_doc语言说明和subtitle_url字幕下载地址。第五步复制subtitle_url然后在浏览器新标签页打开。注意这个URL通常以//开头是协议相对地址需要手动在前面补上https:。打开的JSON就是刚才那段字幕内容直接CtrlS保存即可。这里有一个新手常踩的坑如果你打开页面后没开启过CC字幕Network里可能根本没有字幕请求。先点击播放器右下角的字幕按钮让字幕出现再在网络面板里过滤。另一个坑是B站有些视频会针对不同登录状态返回不同字幕比如未登录时只给AI自动字幕登录后可能看到UP主手动上传的双语字幕。所以抓包时尽量用自己已登录的浏览器会话能拿到更完整的字幕轨道信息。2.2 Python脚本批量提取抓包方式适合偶尔提取一两集但如果你想批量处理一个收藏夹、一个系列课程脚本才是正解。下面这个Python脚本可以直接在本地运行输入BV号后会自动拉取CC字幕并转成标准SRT文件。import requests import time HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://www.bilibili.com/ } def get_video_info(bvid): url https://api.bilibili.com/x/web-interface/view params {bvid: bvid} resp requests.get(url, headersHEADERS, paramsparams) resp.raise_for_status() data resp.json()[data] return data[cid], data[title] def get_subtitle_list(bvid, cid): url https://api.bilibili.com/x/player/v2 params {bvid: bvid, cid: cid} resp requests.get(url, headersHEADERS, paramsparams) resp.raise_for_status() json_data resp.json() if json_data[code] ! 0: print(f接口返回异常: code{json_data[code]}, message{json_data[message]}) return [] return json_data[data][subtitle][subtitles] def format_time(seconds): seconds max(0, seconds) h int(seconds // 3600) m int((seconds % 3600) // 60) s int(seconds % 60) ms int(round((seconds - int(seconds)) * 1000)) return f{h:02d}:{m:02d}:{s:02d},{ms:03d} def subtitles_to_srt(body): lines [] for idx, item in enumerate(body, start1): start format_time(item[from]) end format_time(item[to]) text item[content].replace(br, \n) lines.append(f{idx}\n{start} -- {end}\n{text}\n) return \n.join(lines) def main(): bvid input(输入BV号: ).strip() cid, title get_video_info(bvid) print(f视频标题: {title}) sub_list get_subtitle_list(bvid, cid) if not sub_list: print(该视频没有CC字幕) return for i, sub in enumerate(sub_list): print(f[{i}] 语言: {sub[lan_doc]}, lan: {sub[lan]}) index int(input(选择要下载的字幕序号: )) target sub_list[index] subtitle_url https: target[subtitle_url] srt_data requests.get(subtitle_url, headersHEADERS).json() srt_content subtitles_to_srt(srt_data[body]) safe_title title.replace(/, _).replace(\\, _) filename f{safe_title}_{target[lan]}.srt with open(filename, w, encodingutf-8) as f: f.write(srt_content) print(f已保存: {filename}) if __name__ __main__: main()脚本逻辑很直白先用view接口拿视频cid再用player/v2接口拿字幕列表选一条后下载字幕JSON并解析成SRT。转出的SRT是标准字幕格式可以直接拖进PR、剪映、Aegisub等剪辑软件也能当文稿整理进笔记。这个脚本在正常网络环境下运行一次不会超过十秒所谓“一分钟提取B站字幕”本质就是把上述过程自动化了。不过我还是要提醒一句B站接口偶尔会调整风控策略如果脚本返回-403或-352多半是缺少Cookie。最简单的解决办法是在浏览器Network面板里找到任意请求从请求头里复制Cookie字段替换脚本里的HEADERS。2.3 接口字段与可能遇见的wbi签名这里多说一嘴接口层的细节。view接口的返回体里data下除了cid还有part、pic、owner等字段。如果你需要处理分P视频比如一个视频有多P每个分P有独立的cid可以用part参数来记录当前P的序号并在请求播放器信息时传对对应的cid。从2023年开始B站web端部分接口引入了wbi签名机制。player/v2接口在一些账号权限或网络环境下会要求带签名参数否则接口返回-403或者-352。这属于正常的风控策略调整不是绕过什么限制你说到底就是在浏览器能正常访问的接口上做合规请求。遇到这个报错后最省事的办法不一定是去实现wbi签名算法而是直接从浏览器复制当前网页的Cookie和请求头一起带上这样基本能过。在脚本里加Cookie的方式很简单把HEADERS字典补上一个Cookie: 你的cookie字段即可。考虑到Cookie有时效性长时间批量任务建议做成配置文件或者每隔一段时间手动更新。3. video-subtitle-extractor硬字幕OCR提取实战如果视频没有CC字幕或者字幕本身就是画面的一部分API方案就无能为力了。这时候需要上OCR。OCR方案里我用得比较多的是一个开源项目video-subtitle-extractor。它做的事情简单说就是把视频切成很多帧用目标检测模型先圈出字幕所在区域再用OCR识别文字最后把识别结果按时间轴拼成字幕文件。3.1 工具选型与安装其实OCR提取字幕的开源工具不止一个比如老牌的ffmpeg tesseract方案、PaddleOCR直接识别以及各个平台的付费字幕识别服务。我最终长期用video-subtitle-extractor主要是三个原因它支持PaddleOCR和百度OCR两种识别后端PaddleOCR是本地跑免费且离线可用私密性更好输出格式支持srt、txt、vtt、ass覆盖了绝大多数剪辑需求界面是GUI不用敲复杂的命令行对新手非常友好。安装方式按照项目README来操作git clone https://github.com/YaoFANGUK/video-subtitle-extractor.git cd video-subtitle-extractor pip install -r requirements.txt python main.py初次运行如果选择PaddleOCR作为识别引擎程序会自动下载对应的模型文件这个过程需要耐心等一会儿模型文件有好几百MB。模型下载完成后会进入一个图形界面界面上可以选择视频文件路径、输出目录、识别引擎和语言类型。这里有一个我踩过好几次的坑如果你的电脑是NVIDIA显卡建议显存容量别低于4G否则用GPU模式跑到一半可能爆显存直接闪退。显卡不行就老老实实选CPU模式速度慢一些但至少不会中途挂掉。另外在设置界面里语言类型不要选错中文视频用ch日文用ja如果语言模型选错了OCR准确率会大幅度下降。3.2 参数调优与效果提升视频画面里字幕区域的位置并不固定。有的UP主把字幕放在正中间有的放在底部有的从中间偏上。video-subtitle-extractor内置了字幕检测模型能够自动定位字幕区域所以大多数情况不用手动框选。但在处理双语硬字幕时经常会出现问题比如中英两行离得近模型会把两行当成同一个区域一起圈出来导致识别结果混乱。我的经验是如果视频本身是双语硬字幕优先切分任务把画面上下两行分别裁剪后再识别。或者退一步识别完再把两行文字人工分离。如果视频是单语字幕但周边有台标、水印、弹幕残影也会干扰检测。尽量选取“无弹幕”版本下载或者用剪辑工具先把字幕区域放大是提高准确率的两个实用招数。OCR识别出来的结果多少会有错别字尤其是字体比较艺术的视频识别率肉眼可见下降。正式产出前一定要先挑几帧检查识别文本如果错字特别多考虑把视频画面放大或者在提取前用视频处理工具增强画面清晰度、对比度再接OCR。处理完成后工具会分别在输出目录下生成srt、txt等多种格式。此时我建议再做一步清洗把没有任何字幕时间轴对应关系的空白行删除把被识别成标点符号的孤立字符去掉再做一次全文搜索替换处理OCR常见的“0/O混淆”“l/I混淆”。这些清洗动作能让最终字幕质量跨过一个台阶。3.3 配合其它OCR后端的经验如果你所在的环境比较特殊比如视频很难被下载到本地或者视频分辨率太低导致本地OCR效果很差可以考虑用百度OCR等云端接口。video-subtitle-extractor支持配置百度OCR的API Key和Secret Key调用时它会将字幕区域截图上传到云端再拉取识别结果。云端OCR的准确率通常比本地PaddleOCR高尤其是在中文字体多样的情况下识别美术字、手写字的效果更好。但代价也很明显百万字符以内免费额度够用一阵子超过之后就要按量付费并且视频帧要上传到云端隐私方面需要自己权衡。我个人的建议是能本地解决就本地解决云端接口作为备选方案更稳妥。4. m4s流媒体合并与字幕导出说完了提取再补一个在实操中绕不开的环节B站下载后的视频往往是m4s格式而且视频流和音频流分开存在直接双击根本没法播放。这个环节经常卡住新手但这几步躲是躲不掉的尤其是当视频需要交给OCR工具时必须先合成一个能正常解码的mp4文件。4.1 为什么会有m4s文件B站网页端的视频播放在部分下载工具里会得到两个文件一个是video.m4s一个是audio.m4s。m4s本身是一种基于MPEG-4的流媒体切片封装格式。B站把视频编码流和音频编码流分开存放便于在网页端做清晰度切换和音视频轨道的独立管理。对用户来说就多了一步合并工作。理解这一点之后你看到video.m4s和audio.m4s就不会慌了。这两个文件一个装着画面一个装着声音不是损坏文件也不是被加密了。m4s文件的容器格式和mp4很接近大部分情况下可以直接改后缀名然后用播放器打开试试但更标准的做法是通过ffmpeg做流复制合并。4.2 用ffmpeg合并视频音频合并工具优先选ffmpeg只要电脑装上了ffmpeg并加入环境变量一条命令搞定ffmpeg -i video.m4s -i audio.m4s -c copy output.mp4-c copy表示直接流复制不重新编码所以速度非常快不会损失画质和音质。如果两个文件不在当前目录记得写完整路径。执行完这行命令你会在当前目录下得到一个新的output.mp4文件正常播放器都能打开。如果两个文件里除了视频流和音频流之外还有别的数据流比如字幕轨道可以在命令后面加上-map 0:v:0 -map 1:a:0分别指定视频轨和音频轨避免把一些不需要的轨道也复制进去。有时候从手机缓存导出的m4s文件前面会带一些额外字节ffmpeg合并时会报“Invalid data”之类的错误。这种情况先尝试指定输入格式ffmpeg -f mp4 -i video.m4s -f mp4 -i audio.m4s -c copy output.mp4还是不行的话就需要把文件头裁掉再操作。但这属于比较极端的情况而且容易把文件搞坏建议直接使用视频下载器自带的修复功能或者在下载时设置输出为mp4格式绕开m4s合并这一步。4.3 把字幕JSON转成标准SRT合并好的mp4如果本身嵌了软字幕轨道可以用ffmpeg的-map参数直接提取。但实际中B站的CC字幕很少被封装到媒体文件里更多时候我们手里只有一份字幕JSON。把JSON转成SRT除了用前面那个Python脚本也可以用文本处理软件做批量替换或者用在线字幕转换工具。在线工具适合偶尔一次脚本适合批量场景。前面那段format_time函数已经处理了时间轴格式从秒转为“时:分:秒,毫秒”的标准SRT格式。需要提醒的是B站字幕JSON中content字段有时会包含br标签表示换行在转SRT时建议替换为真正的换行符\nlocation字段表示位置在多字幕同时出现时可以用它辅助排序避免同一条时间轴里出现乱七八糟的多行重复。4.4 时间轴偏移与帧率问题的处理字幕提取过程中还有一个不高频但很致命的问题时间轴偏移。比如视频片头加了10秒的过场动画或者B站视频开头有3秒广告字幕时间轴就会整体偏移。CC字幕由于和视频流是同一接口生成的通常不会发生偏移但OCR字幕在帧率转换时可能产生偏差此时需要整体调整。通用做法是拿到SRT文件后先用文本编辑器打开把每一句字幕的开始时间和结束时间统一增加或减少一个固定偏移量。比如“全体5秒”就用文本编辑器的正则表达式批量操作或者写个小脚本逐行处理。对字幕文件做时间偏移不要手工逐条改既费时间又容易漏正则配合脚本一秒钟搞定。5. 避坑指南与常见问题5.1 常见问题速查下面这个表是我实际操作中遇到过的问题集合按频率从高到低排列。遇到报错时先对照这个表排查大概率能直接解决。问题原因解决方法player/v2接口返回-403或-352缺Cookie或触发风控从浏览器复制Cookie放入请求头抓包时Network里搜不到subtitle没开启CC字幕先在播放器开启CC字幕再刷新页面subtitle_url打不开协议相对URL缺https在URL前手动补全https:OCR识别出来乱码字幕区域太小或背景复杂放大画面、增强对比度、换OCR后端GPU模式跑到一半闪退显存不够改用CPU模式或调低视频分辨率m4s合并失败文件头部异常用下载器修复或用-f mp4强制指定格式视频根本没有CC字幕视频未提供字幕轨道改用硬字幕OCR方案SRT在其他软件里时间轴错乱毫秒参数格式错误检查毫秒是否为三位数不足补零5.2 分享几个实战心得最后补几个自己实操后认为比较实用的经验。第一脚本不要贪快。B站接口做了不少并发限制批量抓字幕时最好在每个请求之间加一个time.sleep(1)否则连续高速请求很容易被临时限制。虽然这个限制时间不会太长但中断任务重头再来更浪费时间。第二字幕提取后一定要人工校对一遍。CC字幕如果是AI自动生成的可能在专业术语上错得离谱OCR字幕更是几乎必然有错字。把字幕当文稿用还好如果要做双语对照字幕并公开发布不校对直接扔出去会很尴尬。第三存储格式的选择。个人笔记用纯文本txt或markdown方便检索但要导入剪辑软件就转成srt或ass。srt是所有剪辑软件都吃的基础格式ass可以做样式定制但兼容性差一些。给字幕文件命名时记得带上视频标题、语言和日期不然攒多了根本不知道哪个对应哪集。第四也是最想提醒大家的提取字幕这件事本身是为了学习整理、翻译参考或个人二创素材准备这些都很正常。但不建议把别人的付费课程字幕、影视剧字幕文件拆解后打包传播版权问题不是小事这个意识越早建立越好。我个人惯用的工作流是优先走API拿CC字幕拿不到再下载视频走OCR合并m4s时一定保留原画质文件做备份字幕文件和视频文件分开存放。这套流程用下来单个视频的处理效率基本能稳定在五到十分钟已经比手动逐句抄写快太多。如果你也经常被B站字幕问题卡住希望这篇内容能帮你省下点时间少走几步弯路。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门