YouTubeのRSSが全チャンネルで404になったとき
要約
- 10月30日の午後、登録チャンネルを3つ読み込んだばかりの画面が空でした。確認したYouTubeのRSSは、大きなチャンネルまですべて404でした。
- 以前取得したことのあるチャンネルはAPIで代替取得していましたが、未取得のチャンネルは代替取得しないようにしていました。存在しないチャンネルIDでクォータを使わせないためのルールでした。
- 今は、未取得のチャンネルが本当にあるかを1ユニットで先に確認し、あるチャンネルだけを代替取得します。それでも取得できなければ、画面でそう伝えます。
Plainfeedは、選んだYouTubeチャンネルの新しい動画だけを、おすすめやショートなしで投稿された順に表示するウェブサイトです。新しい動画の一覧は無料のYouTubeのRSSで取得し、RSSが使えないときだけ、その一覧をYouTube APIで代替取得します。この記事は、その「RSSが使えないとき」が一度に来た日の短い記録です。
- RSS:チャンネルごとに最近の動画を教えてくれる無料のURL(
/feeds/videos.xml?channel_id=…)。公式ドキュメントにはなく、自分で確かめたときは15本ずつでした - ユニット:APIを呼ぶたびに減る点数で、1日1万ユニット。この記事に出てくる2つのメソッドは1回1ユニット
- 代替取得(フォールバック):RSSが失敗したチャンネルを、APIのアップロード一覧(
playlistItems)で取得すること
何が起きたか
プレビュー用のサイトでGoogleアカウントから登録チャンネルを3つ読み込んだところ、タイムラインに何も表示されませんでした。サーバーのキャッシュを見ると、3チャンネルとも15:43と15:46(日本時間)に「取得できず」と記録されていました。
15:50ごろMacでRSSのURLを直接開いてみると、その3チャンネルだけでなく、Googleの開発者向けチャンネルのような大きなチャンネルも404でした。チャンネルページは普通に開けました。16:08にもう一度開いたときには戻っていました。いつ始まっていつ終わったのか、正確にはわかりません。
初めてのことではありませんでした。RSSリーダーFeederのヘルプは、YouTubeのフィードサーバーが数時間すべてのフィードに404を返すことがあると案内していますし、Google AI Developers Forumにも、2025年12月末からRSSがときどき404を返すというユーザーの投稿が続いています(Googleからの回答はありません)。
なぜ画面が空になったのか
結論:以前取得したチャンネルは持ちこたえ、未取得のチャンネルだけが空になりました。
本番サイトのキャッシュには、そのとき取得済みのチャンネルが40ありました。こうしたチャンネルは、RSSが失敗しても取得済みの一覧をそのまま見せるか、APIで代替取得します。
未取得のチャンネルは違いました。代替取得せず、1時間「取得できず」のままにしていました。誰かが存在しないチャンネルIDを大量に送ってもクォータを使わせないためのルールでしたが、こうした障害のときには、来たばかりの人の最初の画面を1時間空にしてしまいます。すでに使っている人には正しく、初めて来た人には間違ったルールでした。
直したこと
結論:未取得のチャンネルはまず存在を確認し、あるチャンネルだけを代替取得します。
RSSが失敗した未取得のチャンネルをまとめて、channels.listに一度問い合わせます。一度に複数のチャンネルを問い合わせても1ユニットで(このサイトは1リクエストにつき最大30チャンネル)、YouTubeが返したチャンネルだけをアップロード一覧で代替取得します(チャンネルごとに1ユニット)。存在しないIDは返ってこないので、代替取得でクォータを無駄にすることもなく、チャンネル名とアイコンも同じ応答で埋まるので別に問い合わせる必要もありません。
// 簡略化したコード (lib/youtube.js)
const unchecked = results.filter((r) => r.check); // RSSが失敗した未取得のチャンネル
if (unchecked.length) {
const found = await channelsList(unchecked.map((r) => r.id)); // 1ユニット
for (const r of unchecked) {
if (!found.has(r.id) || fallbacks >= maxFallback) { r.dead = true; continue; } // 1時間後にもう一度
fallbacks++;
r.feed = await fetchUploads(r.id); // 1ユニット
}
}
代替取得は、もともとあった上限にそのまま従います。1リクエストにつき6チャンネル(取得済みのチャンネルと合わせて)、1日に全体で1,000チャンネル、リクエスト元ごとに200チャンネルです(チャンネルごとに1ユニット)。存在確認の1ユニットは、未取得のチャンネルの1日の予算で数えます。
結論:取得できなかったチャンネルがあれば、画面がそう伝えます。
以前は1つも取得できなくても「新しい動画はありません」と表示されていました。間違いですよね。今は1つも取得できなければその文言の代わりに下の案内だけを出し、一部だけ取得できなければその数を伝えます。

かかるユニット
| RSSが失敗したとき(未取得のチャンネルだけのとき) | 以前 | 今 |
|---|---|---|
| 未取得の3チャンネル | 0ユニット(最大1時間空の画面) | 4ユニット(確認1 + 代替取得3) |
| 未取得の30チャンネル | 0ユニット(最大1時間空の画面) | 7ユニット(確認1 + 代替取得6) |
| 存在しないチャンネルID 30個 | 0ユニット | 1ユニット(確認だけ) |
代替取得した動画の長さを問い合わせる費用(50本で1ユニット、6チャンネルなら2ユニット)は表から外しています。存在しないIDは障害がなくてもRSSがいつも404なので、今はふだんからそうしたリクエストのたびに1ユニットかかります。未取得のチャンネルの1日の予算がその上限です。
しなかったこと
- 1リクエストで代替取得するチャンネルは6のままです。障害中に登録チャンネルを30まとめて読み込むと6チャンネルだけが先に表示され、残りは1時間後にもう一度取得を試みます。無料プランの1リクエストあたり外部呼び出し50回の中に、RSS 30、代替取得6、確認1、動画情報9、チャンネル情報1(合計47)を収めた数なので、増やしていません。
- 「取得できず」の1時間もそのままです。RSSが戻っても、その1時間は問い合わせ直しません。
- プレビュー用のサイトにはAPIキーがないので、代替取得は動きません。そのため代替取得は、偽のYouTubeの応答で作ったテストで確かめました。
おわりに
無料のRSSに頼る作りなら、RSSが丸ごと止まるときを先に考えておくべきでした。取得済みのチャンネルだけを守るルールは、すでに使っている人の画面しか守っていなかったのです。次の技術記事では、こうした上限をCloudflareの無料プラン1つでどう収めているかを書くつもりです。