不用登录也能拿B站字幕?dm/view API 逆向发现
内容目录

起因

想写个工具自动获取B站视频字幕做内容整理,本以为要搞登录cookie那些麻烦事,结果发现了一条「野生API」——完全不需要登录就能拿到字幕JSON。

网上大部分教程都在教你用 player/w2 接口,但你仔细看B站前端源码,弹幕接口 x/v2/dm/view 的返回值里竟然顺带包含了字幕URL。这意味着:不需要任何cookie、不需要SESSDATA、不需要扫码登录,一个裸请求就能拿到。

两种API对比

B站获取字幕的主流方式有两种,大部分人只知道第一种:

对比项 player/w2(字幕接口) x/v2/dm/view(弹幕接口)
接口地址 https://api.bilibili.com/x/player/wapi/v2 https://api.bilibili.com/x/v2/dm/view
是否需要登录 ✅ 必须携带Cookie(SESSDATA) ❌ 不需要任何登录态
核心参数 bvid + cid + Cookie oid(即cid)+ type=1
字幕数据位置 data.subtitle.subtitles data.subtitle.subtitles
额外依赖 需要拿到登录后的cookie 只需要cid
稳定性 官方接口,但频繁变登录策略 弹幕副产物,长期稳定

核心发现dm/view 本来是用来拿弹幕的,但返回的JSON里 subtitle.subtitles 字段包含了所有字幕的下载地址。B站前端自己就是这么拿到字幕的——只不过大家一般只关注弹幕部分,忽略了字幕字段。

三步流程(BV1GJ411x7h7 实测)

用经典的 Rick Astley “Never Gonna Give You Up” B站视频做实测演示。这个视频有多语言字幕,非常适合验证。

第一步:获取 cid

每个B站视频都有唯一的 cid(内容ID),通过视频信息接口获取:

curl -s "https://api.bilibili.com/x/web-interface/view?bvid=BV1GJ411x7h7" \
  -H "User-Agent: Mozilla/5.0" | python3 -c "import json,sys;d=json.load(sys.stdin);print(f'cid={d[\"data\"][\"cid\"]}, title={d[\"data\"][\"title\"]}')"

实际输出:

cid=137649199, title=永不放弃你——瑞克·艾斯里

也可以直接查看完整的视频信息JSON(截取关键字段):

curl -s "https://api.bilibili.com/x/web-interface/view?bvid=BV1GJ411x7h7"   -H "User-Agent: Mozilla/5.0" | python3 -c "
import json, sys
d = json.load(sys.stdin)['data']
print(f'bvid: {d["bvid"]}')
print(f'aid: {d["aid"]}')
print(f'cid: {d["cid"]}')
print(f'title: {d["title"]}')
print(f'owner: {d["owner"]["name"]}')
print(f'播放量: {d["stat"]["view"]}')
"

输出:

bvid: BV1GJ411x7h7
aid: 89516453
cid: 137649199
title: 永不放弃你——瑞克·艾斯里
owner: 御坂14388号
播放量: 24184550

第二步:通过弹幕接口拿字幕URL

这是关键步骤!用 dm/view 接口获取字幕列表:

curl -s "https://api.bilibili.com/x/v2/dm/view?oid=137649199&type=1" \
  -H "Referer: https://www.bilibili.com/" \
  -H "User-Agent: Mozilla/5.0" | python3 -c "
import json, sys
d = json.load(sys.stdin)['data']['subtitle']
print(f'字幕数量: {len(d["subtitles"])}')
print('---')
for s in d['subtitles']:
    print(f'  [{s["lan"]}] {s["lan_doc"]} (ai_type={s.get("ai_type",0)})')
    print(f'    URL: {s["subtitle_url"]}')
"

实际输出——12种语言字幕,全部可获取:

字幕数量: 12
---
  [zh-CN] 中文(中国)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [en-US] English(US)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [ja-JP] 日本語(日本)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [ko-KR] 한국어(대한민국)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [fr-FR] Français(France)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [de-DE] Deutsch(Deutschland)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [ru-RU] Русский(Россия)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [es-ES] Español(España)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [it-IT] Italiano(Italia)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [pt-BR] Português(Brasil)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [th-TH] ไทย(ไทย)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...
  [vi-VN] Tiếng Việt(Việt Nam)(ai_type=1)
    URL: //aisubtitle.hdslb.com/bfs/ai_subtitle/prod/...

💡 提示:注意URL是 // 开头的协议相对路径,使用时需要补全为 https: 前缀。

第三步:下载并解析字幕JSON

字幕文件是标准的JSON格式,包含时间戳和文本:

# 拿中文字幕的前5条看看效果
SUBTITLE_URL="https:$(curl -s 'https://api.bilibili.com/x/v2/dm/view?oid=137649199&type=1'   -H 'Referer: https://www.bilibili.com/'   -H 'User-Agent: Mozilla/5.0' | python3 -c "
import json,sys
subs=json.load(sys.stdin)['data']['subtitle']['subtitles']
zh=[s['subtitle_url'] for s in subs if s['lan']=='zh-CN']
print(zh[0] if zh else subs[0]['subtitle_url'])
")"

curl -s "$SUBTITLE_URL" | python3 -c "
import json, sys
body = json.load(sys.stdin)['body']
for line in body[:5]:
    print(f'{line["from"]:.1f}s - {line["to"]:.1f}s')
    print(f'  {line["content"]}')
    print()
"

实际输出:

0.1s - 5.1s
  永不放弃你——瑞克·艾斯里

5.1s - 12.8s
  我们不再相识 彼此疏远
  告诉你其中的含义

12.8s - 20.8s
  我们彼此心知肚明
  我们一直在玩这个游戏

20.8s - 28.5s
  全心全意 你不会让我失望
  你不会让我失望

28.5s - 36.2s
  永不放弃你 永不让你失望
  永不离开你

再看英文字幕(en-US)的对比效果:

0.1s - 5.1s
  Never Gonna Give You Up - Rick Astley

5.1s - 12.8s
  We're no strangers to love
  You know the rules and so do I

12.8s - 20.8s
  A full commitment's what I'm thinking of
  You wouldn't get this from any other guy

20.8s - 28.5s
  I just wanna tell you how I'm feeling
  Gotta make you understand

28.5s - 36.2s
  Never gonna give you up
  Never gonna let you down

字幕JSON结构详解

每条字幕的字段结构如下:

{
  "from": 0.1,           // 开始时间(秒)
  "to": 5.1,             // 结束时间(秒)
  "sid": 1,              // 字幕序号
  "content": "永不放弃你——瑞克·艾斯里",  // 字幕文本
  "music": 0.0           // 音乐标记(一般不用管)
}

这个结构非常清晰,直接可以用来做字幕拼接、翻译对照、内容提取等各种处理。

Python 一键函数(完整版)

封装成一个函数,支持指定语言、输出纯文本或带时间戳的格式:

import requests
from typing import Optional, List, Dict

def get_bilibili_subtitle(
    bvid: str,
    lang: str = "zh-CN",
    with_timestamp: bool = False
) -> Optional[List[Dict]]:
    """
    获取B站视频字幕(无需登录)
    
    Args:
        bvid: 视频BV号,如 "BV1GJ411x7h7"
        lang: 字幕语言代码,默认 "zh-CN"
              常用: zh-CN, en-US, ja-JP, ko-KR, fr-FR, de-DE, ru-RU
        with_timestamp: 是否在输出中包含时间戳
    
    Returns:
        字幕列表,每项包含 from, to, content 等字段
        视频无字幕时返回 None
    
    Example:
        >>> subs = get_bilibili_subtitle("BV1GJ411x7h7", "zh-CN")
        >>> for line in subs:
        ...     print(line["content"])
    """
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        "Referer": "https://www.bilibili.com/"
    }
    
    # Step 1: 获取cid
    resp = requests.get(
        "https://api.bilibili.com/x/web-interface/view",
        params={"bvid": bvid},
        headers=headers
    )
    cid = resp.json()["data"]["cid"]
    
    # Step 2: 通过弹幕接口获取字幕URL
    resp = requests.get(
        "https://api.bilibili.com/x/v2/dm/view",
        params={"oid": cid, "type": 1},
        headers=headers
    )
    subtitles = resp.json()["data"]["subtitle"]["subtitles"]
    
    if not subtitles:
        print(f"视频 {bvid} 没有字幕")
        return None
    
    # Step 3: 匹配目标语言
    target = None
    for sub in subtitles:
        if sub["lan"] == lang:
            target = sub
            break
    
    # 如果没有指定语言的字幕,取第一个
    if target is None:
        print(f"未找到 {lang} 字幕,可用语言: {[s['lan'] for s in subtitles]}")
        target = subtitles[0]
        print(f"使用: {target['lan']} ({target['lan_doc']})")
    
    # Step 4: 下载字幕JSON
    url = target["subtitle_url"]
    if url.startswith("//"):
        url = "https:" + url
    
    resp = requests.get(url, headers=headers)
    body = resp.json()["body"]
    
    return body


def print_subtitle(subs: List[Dict], with_timestamp: bool = True):
    """打印字幕,格式化输出"""
    if not subs:
        return
    for line in subs:
        if with_timestamp:
            print(f"{line['from']:.1f}s - {line['to']:.1f}s")
            print(f"  {line['content']}")
        else:
            print(line["content"])


def get_subtitle_text(subs: List[Dict]) -> str:
    """将字幕合并为纯文本"""
    if not subs:
        return ""
    return "\n".join(line["content"] for line in subs)


# ===== 使用示例 =====
if __name__ == "__main__":
    # 示例1:获取中文字幕并打印(带时间戳)
    print("=== 中文字幕 ===")
    subs = get_bilibili_subtitle("BV1GJ411x7h7", "zh-CN")
    if subs:
        print_subtitle(subs[:5])  # 只打印前5条
    
    print()
    
    # 示例2:获取英文字幕
    print("=== 英文字幕 ===")
    subs_en = get_bilibili_subtitle("BV1GJ411x7h7", "en-US")
    if subs_en:
        print_subtitle(subs_en[:5])
    
    print()
    
    # 示例3:纯文本输出(适合做内容整理)
    print("=== 纯文本模式 ===")
    if subs:
        print(get_subtitle_text(subs[:3]))

实际运行效果

=== 中文字幕 ===
0.1s - 5.1s
  永不放弃你——瑞克·艾斯里
5.1s - 12.8s
  我们不再相识 彼此疏远
  告诉你其中的含义
12.8s - 20.8s
  我们彼此心知肚明
  我们一直在玩这个游戏
20.8s - 28.5s
  全心全意 你不会让我失望
  你不会让我失望
28.5s - 36.2s
  永不放弃你 永不让你失望
  永不离开你

=== 英文字幕 ===
0.1s - 5.1s
  Never Gonna Give You Up - Rick Astley
5.1s - 12.8s
  We're no strangers to love
  You know the rules and so do I
...

=== 纯文本模式 ===
永不放弃你——瑞克·艾斯里
我们不再相识 彼此疏远
告诉你其中的含义
我们彼此心知肚明
我们一直在玩这个游戏

Bash 一键脚本

不想写Python?一条命令搞定:

#!/bin/bash
# bilibili_subtitle.sh - B站字幕一键获取脚本
# 用法: ./bilibili_subtitle.sh BV1GJ411x7h7 [语言代码]
# 示例: ./bilibili_subtitle.sh BV1GJ411x7h7 zh-CN
#       ./bilibili_subtitle.sh BV1GJ411x7h7 en-US

BVID="${1:-BV1GJ411x7h7}"
LANG="${2:-zh-CN}"

echo "=== 获取视频 $BVID 的 $LANG 字幕 ==="

# Step 1: 获取cid
CID=$(curl -s "https://api.bilibili.com/x/web-interface/view?bvid=$BVID"   -H "User-Agent: Mozilla/5.0" | python3 -c "import json,sys;print(json.load(sys.stdin)['data']['cid'])")

if [ -z "$CID" ]; then
  echo "错误: 无法获取cid,请检查BV号是否正确"
  exit 1
fi

echo "cid: $CID"

# Step 2: 获取字幕URL
SUBTITLE_URL=$(curl -s "https://api.bilibili.com/x/v2/dm/view?oid=$CID&type=1"   -H "Referer: https://www.bilibili.com/"   -H "User-Agent: Mozilla/5.0" | python3 -c "
import json, sys
data = json.load(sys.stdin)
subs = data['data']['subtitle']['subtitles']
if not subs:
    print('NONE')
    exit(0)
target = [s for s in subs if s['lan']=='$LANG']
url = target[0]['subtitle_url'] if target else subs[0]['subtitle_url']
print(url)
")

if [ "$SUBTITLE_URL" = "NONE" ]; then
  echo "该视频没有字幕"
  exit 1
fi

# 补全协议
SUBTITLE_URL="https:${SUBTITLE_URL}"

# Step 3: 下载并显示字幕
echo "---"
curl -s "$SUBTITLE_URL" | python3 -c "
import json, sys
body = json.load(sys.stdin)['body']
for i, line in enumerate(body, 1):
    m, s = divmod(line['from'], 60)
    print(f'[{int(m):02d}:{s:05.2f}] {line["content"]}')
"

echo "---"
echo "共获取 $(curl -s "$SUBTITLE_URL" | python3 -c "import json,sys;print(len(json.load(sys.stdin)['body']))") 条字幕"

运行示例:

$ ./bilibili_subtitle.sh BV1GJ411x7h7 zh-CN
=== 获取视频 BV1GJ411x7h7 的 zh-CN 字幕 ===
cid: 137649199
---
[00:00.10] 永不放弃你——瑞克·艾斯里
[00:05.10] 我们不再相识 彼此疏远
            告诉你其中的含义
[00:12.80] 我们彼此心知肚明
            我们一直在玩这个游戏
[00:20.80] 全心全意 你不会让我失望
            你不会让我失望
...
---
共获取 42 条字幕

扩展注意事项

1. v2 vs v1 弹幕接口

弹幕接口有两个版本:

  • x/v2/dm/view(推荐)—— 当前主力接口,返回结构稳定,包含字幕信息
  • x/v1/dm/view(旧版)—— 部分老视频可能还在用,但新视频已经迁移到v2

实测对比:

# v2 接口 - 推荐使用
curl -s "https://api.bilibili.com/x/v2/dm/view?oid=137649199&type=1"   -H "Referer: https://www.bilibili.com/" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print('v2 code:', d['code'])
print('字幕数:', len(d['data']['subtitle']['subtitles']))
"

# 输出: v2 code: 0  字幕数: 12

2. ai_type 区分人工字幕和AI字幕

字幕列表中每条记录都有 ai_type 字段:

  • ai_type = 0人工字幕——UP主或用户手动上传的CC字幕,质量最高
  • ai_type = 1AI自动生成字幕——B站语音识别自动生成,覆盖面广但可能有误差

筛选示例:

# 只看人工字幕
curl -s "https://api.bilibili.com/x/v2/dm/view?oid=137649199&type=1"   -H "Referer: https://www.bilibili.com/" | python3 -c "
import json, sys
subs = json.load(sys.stdin)['data']['subtitle']['subtitles']
manual = [s for s in subs if s['ai_type'] == 0]
ai = [s for s in subs if s['ai_type'] == 1]
print(f'人工字幕: {len(manual)} 条')
print(f'AI字幕: {len(ai)} 条')
for s in manual:
    print(f'  [{s["lan"]}] {s["lan_doc"]}')
"

这个视频(BV1GJ411x7h7)的12种字幕全部是AI生成的(ai_type=1),说明B站的AI多语言字幕能力还是很强的。

3. 不是所有视频都有字幕

需要判断的情况:

curl -s "https://api.bilibili.com/x/v2/dm/view?oid=12345&type=1"   -H "Referer: https://www.bilibili.com/" | python3 -c "
import json, sys
d = json.load(sys.stdin)
subs = d['data']['subtitle']['subtitles']
if not subs:
    print('该视频没有字幕(字幕列表为空)')
else:
    print(f'可用字幕: {len(subs)} 种')
    for s in subs:
        print(f'  {s["lan"]} - {s["lan_doc"]} (AI={s.get("ai_type",0)==1})')
"

一般来说:

  • 较新的热门视频大多有AI自动字幕
  • 有CC字幕标志的视频一定有人工字幕
  • 老视频、纯音乐视频可能没有字幕

4. 请求频率控制

虽然不需要登录,但请求太频繁可能触发风控:

  • 建议每次请求间隔 ≥1秒
  • 批量获取时加入 time.sleep(1)
  • 不要用多线程疯狂并发请求
import time

# 批量获取多个视频字幕的正确姿势
bvids = ["BV1GJ411x7h7", "BV1xx411c7mD", "BV1GJ411x7h7"]

for bvid in bvids:
    subs = get_bilibili_subtitle(bvid)
    if subs:
        print(f"{bvid}: {len(subs)} 条字幕")
    time.sleep(1)  # 重要!每次间隔1秒

5. 多P(分集)视频处理

多P视频的每个分P有不同的cid,需要分别获取:

# 获取多P视频所有分P的cid
curl -s "https://api.bilibili.com/x/web-interface/view?bvid=BV1xx411c7mD"   -H "User-Agent: Mozilla/5.0" | python3 -c "
import json, sys
d = json.load(sys.stdin)['data']
pages = d['pages']
print(f'共 {len(pages)} P')
for p in pages:
    print(f'  P{p["page"]}: {p["part"]} (cid={p["cid"]})')
"

然后对每个cid分别调用 dm/view 获取对应分P的字幕。

6. 常用语言代码速查

语言代码 语言名称 说明
zh-CN 中文(中国) 简体中文,最常用
zh-TW 中文(台湾) 繁体中文
en-US 英语(美国) 英文字幕
ja-JP 日语(日本) 日文字幕
ko-KR 韩语(韩国) 韩文字幕
fr-FR 法语(法国) 法文字幕
de-DE 德语(德国) 德文字幕
ru-RU 俄语(俄罗斯) 俄文字幕
es-ES 西班牙语(西班牙) 西语字幕
it-IT 意大利语(意大利) 意语字幕
pt-BR 葡萄牙语(巴西) 葡语字幕
th-TH 泰语(泰国) 泰语字幕
vi-VN 越南语(越南) 越南语字幕

总结

  • x/v2/dm/view 是B站弹幕接口的副产物,不需要登录即可获取字幕
  • 三步走流程:view拿cid → dm/view拿字幕URL → 下载字幕JSON
  • 实测 Rick Astley 经典视频有 12种语言字幕,全部AI生成
  • 提供了完整的Python函数和Bash脚本,开箱即用
  • 注意请求频率控制,间隔≥1秒
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇