yt-dlpでbilibiliの動画ダウンロードが途中で止まる原因を調べたら、CDNキャッシュが関係していそうだった件

コンピュータ関連
スポンサーリンク

bilibiliの動画をyt-dlpでダウンロードしていると、特定のCDNから配信される場合だけ、ダウンロード途中で速度が極端に低下したり、The read operation timed outが発生したりすることがあった。
Got error: Downloaded 87390979 bytes, expected 195457749 bytes. Retrying (1/10)...とか続いて、結局失敗する。

-vで詳細なログを出しながら何度か試しているうちに、少し不思議な挙動が見えてきた。

結論から言うと今回の現象は、bilibili側のCDNアクセス時に一部データの遅延によりエラー発生、という可能性が高そう。一度動画を最後まで取得すればその後のダウンロードは安定する。

ただしCDN内部のキャッシュ状態は外部から直接確認できないため、これはあくまで観測結果からの推測となります。

スポンサーリンク

発生した現象:Got error. Retrying (1/10)…

yt-dlpで動画をダウンロードしていると、以下のようなログが出ることがあった。

Got error: Downloaded 87390979 bytes, expected 195457749 bytes. Retrying (1/10)...

つまり、約195MBのデータを受信するはずだったHTTPリクエストが、約87MB受信したところで途中終了している。

その後、何度かリトライされるも、結局完全なファイルとはならずに終了。

CDNによって挙動が違う

興味深かったのは、同じ動画でも配信されるCDNによって挙動が違ったこと。

今回問題が発生したのは、

upos-sz-mirrorcosov.bilivideo.com

一方、別のCDNである、

upos-hz-mirrorakam.akamaized.net

から配信された場合は、ダウンロード速度は遅いものの、途中で止まることなく最後まで完走した。

つまり、

bilivideo.com の場合、
速度は比較的速い
    ↓
途中で速度低下・タイムアウト
    ↓
リトライ


akamaized.net の場合、
速度は遅い
    ↓
しかし安定して完走

という違いがあった。

この時点で、動画ファイルそのものの破損や、yt-dlpの基本的なダウンロード処理が原因である可能性は低そうだと考えた。

socket-timeout を延長してみる

yt-dlpにはソケット通信のタイムアウトを指定する--socket-timeoutオプションがある。

そこで、タイムアウトをすこし長くして試してみた。

yt-dlp --socket-timeout 60 URL

すると、約27~30%付近で止まっていたダウンロードが、少し先まで進むようになった。

約27.9%
    ↓
タイムアウト


タイムアウト時間を延長(--socket-timeout設定)
    ↓
さらに先まで進む

やはり、動画ファイルのデータが特定の場所で壊れているわけではなさそう。
まぁこのような状況が発生するのは1つだけじゃないからね、それはそう。

http-chunk-size を小さくしてみる

次に、HTTPダウンロードのチャンクサイズを小さくして試した。

例えば、

yt-dlp --socket-timeout 60 --http-chunk-size 5M URL

のような設定。

すると、さらにダウンロードが先まで進むようになった。

最終的には、

27.9%付近で停止
    ↓
44.7%付近で停止
    ↓
86.4%付近で停止
    ↓
93%付近、速度低下しながらも・・・
    ↓
100% 完走

という状態になった。

最初は27~30%付近でおかしくなっていたものが、設定を変更すると44%、86%、93%と、徐々に先へ進んでいった。

つまり、その時々で通信が詰まったりしている?けれど一応最後までダウンロードはできる、ということ。

一度100%までダウンロードすると、次はスムーズに

そして今回の検証で最も重要だったのがここ。

まず、タイムアウト時間を延長し、HTTPチャンクサイズを小さくすることで、何度か詰まりながらも最終的に100%までダウンロードした。

そして、その直後に同じURLを、今度はオプションを付けずに再度ダウンロードしてみた。

結果は、

最初から最後までスムーズにダウンロード完了!

bilivideo.comだったのにも関わらず。
この結果から、
一度100%まで取得したことでCDN側のキャッシュ状態が変化し、2回目以降のダウンロードではデータが安定して配信されるようになった、のではないかと思う。
一定のサイズで通信に制限をかけているわけでもなかった。

初回アクセス
    ↓
bilivideo CDN
キャッシュに存在しないデータ
    ↓
originやバックエンドから取得
    ↓
一部のデータ範囲で取得に時間がかかる
    ↓
HTTPレスポンスが遅延
タイムアウトしながらもリトライ
    ↓
徐々にデータ取得
    ↓
100%まで完走
    ↓
(CDN側のキャッシュ状態が改善した可能性)


2回目のアクセス
    ↓
キャッシュから安定して配信

まとめ

ということで CDNのキャッシュが問題 なのだと思う。多分。
(所詮素人なので勝手な推測ですが)

そしてそれがわかったところでユーザー側にできることはあまりないかもしれない。

ただタイムアウトを長くしたり、チャンクサイズを小さくしたりすることで、途中で失敗せず最後まで取得できる可能性を高めることはできるかもしれない。
そんなことを思ったので簡単にまとめてみました。

重ねて言いますが、あくまでもこれは今回観測した環境での結果、推測であり、すべての状況に当てはまるとは限らないので悪しからず。

動画と音声を別々にDLして結合する

初心者の方に向けて。わかる方は読み飛ばしてください

ここで終わってはそれこそ何の役にも立たない話で終わってしまうので、ffmpegを使って結合するやり方を書いておきます。
yt-dlpでも動画と音声をそれぞれダウンロードし、結合しています。
が、不完全なデータを無理やり結合しても不完全なファイルになるわけで、またダウンロードし直して・・・なんてやってたらいつまで経っても終わらない。

そういう時は-Fオプションでリストを表示、-fオプション(小文字)で指定してまずは個別にダウンロードします。

#一般的には「コマンド、オプション、URL」の順(URLの前)に書きますが、後ろでも問題はありません。その方が入力しやすいので
yt-dlp url -F

出力結果例:

30216  m4a audio only     │ ≈ 16.56MiB   66k https │ audio only          mp4a.40.2  66k
30232  m4a audio only     │ ≈ 21.11MiB   84k https │ audio only          mp4a.40.2  84k
30280  m4a audio only     │ ≈ 21.11MiB   84k https │ audio only          mp4a.40.2  84k
30011  mp4 640x360     30 │ ≈ 34.26MiB  136k https │ hvc1.1.6.L120  136k video only
100022 mp4 640x360     30 │ ≈ 30.42MiB  120k https │ av01.0.08M.08  120k video only
30016  mp4 640x360     30 │ ≈ 48.65MiB  193k https │ avc1.640033    193k video only
30033  mp4 852x480     30 │ ≈ 48.98MiB  194k https │ hvc1.1.6.L120  194k video only
100023 mp4 852x480     30 │ ≈ 43.01MiB  170k https │ av01.0.08M.08  170k video only
30032  mp4 852x480     30 │ ≈ 71.04MiB  281k https │ avc1.640033    281k video only
30066  mp4 1280x720    30 │ ≈ 82.85MiB  328k https │ hvc1.1.6.L120  328k video only
100024 mp4 1280x720    30 │ ≈ 70.91MiB  281k https │ av01.0.08M.08  281k video only
30064  mp4 1280x720    30 │ ≈132.65MiB  525k https │ avc1.640033    525k video only
30077  mp4 1920x1080   30 │ ≈168.07MiB  666k https │ hvc1.1.6.L150  666k video only
100026 mp4 1920x1080   30 │ ≈139.98MiB  554k https │ av01.0.08M.08  554k video only
30080  mp4 1920x1080   30 │ ≈285.27MiB 1130k https │ avc1.640033   1130k video only

そしたら左の数字(ID)を指定して個別にDL

#動画1080p av01コーデックを指定
yt-dlp url -f 100026

#音声を指定
yt-dlp url -f 30280

#ちなみに一緒にDLする時は
yt-dlp url -f 100026+30280

それぞれ、.mp4.m4aファイルが出来上がります。

仮に test.mp4test.m4a だとすると、

ffmpeg -i test.mp4 -i test.m4a -c copy sample.mp4

で劣化なく sample.mp4 を作ってくれます。
-c copyはそのままコピーして出力ファイルに詰め替えるの意味で、再エンコードしません)

Mac使いの人はAutomatorで シェルスクリプトを実行 に組み込めば、作成された2つのファイルを選んで右クリックから簡単に結合することもできます。

コメント

  1. k.k より:

    初めまして。コメント失礼します。
    yt-dlpでbilibli動画をmp3で保存したく利用を始めたのですが、ダウンロードされた音声データが途中で切れてしまいます。
    また、bilibili.pyをダウンロードしたのですが、使い方がわからない状態です。
    そこで以下2点についてご教授を頂きたいです。

    ・下記のコマンドで問題ないでしょうか。
    yt-dlp -x –audio-format mp3 –socket-timeout 60 –http-chunk-size 5M “動画URL”

    ・bilibili.pyの使い方について(yt-dlpを保存しているフォルダの配下に保存?)

    以上です。
    初心者質問で大変恐縮ですが、よろしくお願いいたします。

    • watary watary より:

      socket-timeout や http-chunk-size はサイズの大きいvideoをDLしたい場合で、音声には必要ないと思います。
      音声のみでいいなら音声だけDL(m4aやwebm)、yt-dlp -f ba "動画URL"
      互換性などでm4a等をmp3にしたいなら、yt-dlp -f ba -x --audio-format mp3 "動画URL"
      bilibili.pyは置き換える必要ありません。最新のyt-dlpにアップデートしてください。(ファイル自体はextractorフォルダの中です)
      音声が途切れるのは途中で失敗しているからですか?元ファイルは途切れていませんか?
      タイムアウトで途切れるのなら、繰り返しトライし続けるのが現状一番の対応策だと思います。あるいはDLBunnyなどのサイトを使うのも一つの手です。

タイトルとURLをコピーしました