ytdlp.online

返回专栏目录

文章目录

yt-dlp Rumble 403 修复:JSON 接口失败后,回退读取播放器数据

发布日期

下载修复插件 rumble_player.py

如果使用 yt-dlp 下载 Rumble 视频时报 Unable to download JSON metadata: HTTP Error 403: Forbidden,可以加载本文提供的兼容插件。当 Rumble 的 embedJS/u3 JSON 接口返回 403 时,插件会自动回退去拉取同一视频的官方嵌入播放器页面(/embed/),从中提取内联的媒体数据并继续下载。

如果你不想在本地配置插件或手动转换链接,也可以直接使用本站已内置该修复方案的 Rumble 视频下载器。

先确认 403 发生在哪一步

Rumble 下载过程中的 403 可能出现在三个不同阶段,本文插件专门解决第二种:

报错信息 拦截位置 处理方式
Unable to download webpage: HTTP Error 403 观看页 HTML 被拦截(见 issue #15089) 改用官方 oEmbed 接口转换出 /embed/ 地址,并配合 --impersonate firefox
Unable to download JSON metadata: HTTP Error 403 embedJS/u3 元数据接口被拦截(见 issue #17496) 适用本文插件:自动回退读取 /embed/ 页面内联数据
格式列表已打印,下载分片或 MP4 时报 403 CDN 媒体流请求被拒 检查代理、请求头或更换清晰度格式

快速修复步骤

1. 保存插件文件

在工作目录下按以下层级创建文件夹,将文末完整插件代码保存为 rumble_player.py(无需修改 yt-dlp 源码):

plugins/
└── rumble/
    └── yt_dlp_plugins/
        └── extractor/
            └── rumble_player.py

2. 获取视频的官方 /embed/ 地址

需要注意:Rumble 观看页 URL 中的 ID(如 v6xxxx)与嵌入播放器 ID(如 vb0ofn)并不相同,不能直接手动拼接。如果手里只有普通观看页链接,可以先调用 Rumble 官方公开的 oEmbed 接口拿到真实的 /embed/ 地址:

curl -fsSG --data-urlencode "url=https://rumble.com/your-video.html" "https://rumble.com/api/Media/oembed.json"

在返回的 JSON html 字段中找到 <iframe src="https://rumble.com/embed/xxxx/...",取出其中的 https://rumble.com/embed/xxxx/ 即可。

如果 oEmbed 返回 403,先安装 curl_cffi,再用 Firefox 指纹重试。把下面命令中的示例链接也替换为完整的观看页链接。

python -m pip install curl_cffi
python -c "import sys; from curl_cffi import requests; r = requests.get('https://rumble.com/api/Media/oembed.json', params=dict(url=sys.argv[1]), impersonate='firefox', timeout=15); r.raise_for_status(); print(r.text)" "https://rumble.com/your-video.html"

如果重试仍失败,可以用在线下载器自动转换链接。

3. 列出可用格式并验证回退

在包含 plugins 文件夹的目录下运行(实际使用时请替换为目标视频的 /embed/ 地址):

yt-dlp --ignore-config --plugin-dirs ./plugins --no-cache-dir -v -F "https://rumble.com/embed/vb0ofn/"

日志中出现 [RumbleEmbed+PlayerFallback] 说明插件已加载:

  • 如果 JSON 接口返回 403,紧接着会看到一条 Downloading player webpage 日志,说明备用路径已触发。随后输出格式列表,才能确认数据解析成功。
  • 如果当前网络环境下 JSON 接口本身返回 200,则直接走原生流程,不会发起额外的播放器页面请求。

4. 下载视频与解码校验

从 -F 列出的格式中选择一个格式 ID(如 mp4-360p)执行下载:

yt-dlp --ignore-config --plugin-dirs ./plugins --no-cache-dir -v --no-playlist -f mp4-360p -o "rumble-test.%(ext)s" "https://rumble.com/embed/vb0ofn/"

实测触发回退时的完整终端日志如下:

[RumbleEmbed+PlayerFallback] vb0ofn: Downloading JSON metadata
[RumbleEmbed+PlayerFallback] vb0ofn: Downloading player webpage
[info] vb0ofn: Downloading 1 format(s): mp4-360p
[download] 100% of    7.85MiB in 00:01:20 at 99.48KiB/s

如需验证下载后的 MP4 容器与音视频流是否完整无损,可使用 ffprobe 和 ffmpeg 检查:

ffprobe -v error -show_entries stream=codec_name,codec_type,width,height:format=duration,size -of json "rumble-test.mp4"
ffmpeg -nostdin -v error -xerror -i "rumble-test.mp4" -map 0:v:0 -map "0:a:0?" -f null -

技术原理:为什么播放器页面能绕过 JSON 403?

1. 同源内联数据复用

RumbleEmbedIE 默认直接请求 https://rumble.com/embedJS/u3/?request=video&ver=2&v=... 获取 JSON 元数据。当该接口被 Cloudflare 或站点风控策略单独拦截并返回 HTTP 403 时,提取就会中断。

但实际上,打开官方嵌入页 https://rumble.com/embed/<video_id>/ 时,网页脚本里已经内联了一份结构完全相同的视频配置对象(包含 ua 各清晰度流地址、标题、时长等字段)。只要把这份对象提取出来返回给 _download_json,yt-dlp 后续的格式解析代码一行都不用改就能继续工作。

2. 清理非标准 JSON 字段(loaded: h())

嵌入页内联的数据对象并不是严格的 JSON 字面量,它的末尾带有一个运行时函数调用 loaded: h(),形如:

u.f["vb0ofn"] = {"ua": {"mp4": {"360": {"url": "https://..."}}}, "title": "...", loaded: h()};

如果直接送入 JSON 解析器会因为 h() 报语法错误。因此插件在 _search_json 中通过 transform_source 钩子,仅在解析前用正则剥离尾部的 , loaded: xxx(),全程在内存文本层完成,不执行任何网页 JavaScript:

  • 精准匹配:只提取键名严格等于当前 video_id 的对象(\b[\w$]+\.f\[\s*["']{video_id}["']\s*\]\s*=),校验返回值为字典且包含非空 ua 媒体流字段。
  • 最小侵入:仅在 _download_json 捕获到 HTTPError 且状态码为 403 时触发回退;401、404、429、500 等错误或页面内无有效对象时,原样抛出原始异常。
  • 线上工程配合:在我们的线上服务中,除了挂载该插件外,还会自动调用 oEmbed 接口将用户输入的观看页 URL 转为 /embed/ 地址,并在未指定 --impersonate 时默认启用 firefox-135 指纹。

完整插件代码

将以下代码保存为 plugins/rumble/yt_dlp_plugins/extractor/rumble_player.py(已在 Python 3.12+ 与 yt-dlp 2026.10.10+ 下验证通过):

"""Rumble JSON 接口被拦截时复用官方播放器内的同源媒体数据。"""

import re

from yt_dlp.extractor.rumble import RumbleEmbedIE as BaseRumbleEmbedIE
from yt_dlp.networking.exceptions import HTTPError
from yt_dlp.utils import ExtractorError


class RumbleEmbedIE(BaseRumbleEmbedIE, plugin_name='PlayerFallback'):
    def _real_extract(self, url):
        self._player_url = url
        return super()._real_extract(url)

    def _download_json(self, url_or_request, video_id, *args, **kwargs):
        try:
            return super()._download_json(url_or_request, video_id, *args, **kwargs)
        except ExtractorError as error:
            if not isinstance(error.cause, HTTPError) or error.cause.status != 403:
                raise
            webpage = self._download_webpage(self._player_url, video_id, note='Downloading player webpage')
            # 仅解析当前视频的数据对象,移除播放器运行时加入的时钟调用,不执行网页脚本。
            data = self._search_json(
                rf'''\b[\w$]+\.f\[\s*["']{re.escape(video_id)}["']\s*\]\s*=\s*''',
                webpage, 'player data', video_id,
                fatal=False,
                transform_source=lambda source: re.sub(r',\s*loaded\s*:\s*[\w$]+\(\s*\)\s*(?=})', '', source))
            if not isinstance(data, dict) or not data.get('ua'):
                raise error
            return data
yt-dlp 工具箱
查看所有 yt-dlp 工具
yt-dlp 帮助与文档