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 API 回傳 403 時,外掛會自動改為取得同一支影片的官方嵌入播放器頁面(/embed/),從中擷取內嵌的媒體資料並繼續下載。

如果不想在本機設定外掛或手動轉換連結,也可以直接使用本站已內建此修復方案的 Rumble 影片下載器。

先確認 403 發生在哪個步驟

Rumble 下載過程中的 403 可能出現在三個不同階段,本文外掛專門處理第二種:

錯誤訊息 遭到攔截的請求 處理方式
Unable to download webpage: HTTP Error 403 觀看頁面的 HTML 被攔截(參見 issue #15089) 使用官方 oEmbed API 取得 /embed/ 網址,並搭配 --impersonate firefox
Unable to download JSON metadata: HTTP Error 403 embedJS/u3 中繼資料 API 被攔截(參見 issue #17496) 適用本文外掛:自動改讀 /embed/ 頁面的內嵌資料
已列出格式,但下載片段或 MP4 時出現 403 CDN 媒體串流請求遭到拒絕 檢查代理伺服器、請求標頭,或改選其他畫質的格式

快速修復步驟

1. 儲存外掛檔案

在工作目錄中建立以下資料夾結構,並將文末完整外掛程式碼儲存為 rumble_player.py,不需要修改 yt-dlp 原始碼:

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

2. 取得影片的官方 /embed/ 網址

Rumble 觀看頁面網址中的 ID(例如 v6xxxx)與嵌入播放器的 ID(例如 vb0ofn)並不相同,不能直接用觀看頁面的 ID 組合網址。如果只有一般的觀看頁面連結,可以先呼叫 Rumble 官方公開的 oEmbed API,取得實際的 /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 API 回傳 403,接著會看到一筆 Downloading player webpage 記錄,表示備援路徑已啟動。之後顯示格式清單,才能確認資料解析成功。
  • 如果目前的網路環境中 JSON API 本身回傳 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 或網站的存取管制單獨攔截這個 API,並回傳 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 API,將使用者輸入的觀看頁面網址轉為 /embed/ 網址。未指定 --impersonate 時,預設啟用 firefox-135 指紋。

完整外掛程式碼

將以下程式碼儲存為 plugins/rumble/yt_dlp_plugins/extractor/rumble_player.py。已在 Python 3.12+ 與 yt-dlp 2026.10.10+ 環境中驗證通過:

"""Reuse official player media data when Rumble's JSON endpoint is blocked."""

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')
            # Parse this video's object and strip the runtime call without executing page scripts.
            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 說明與文件