YouTubeのRSSフィードの404はUser-Agentのせいではなかった──UAヘッダごと削除しても200が返り、犯人はchannel_idだった
https://www.youtube.com/feeds/videos.xml?channel_id=... が404を返したとき、User-Agentを疑うのは筋が悪い。2026年8月14日にcurl 8.7.1で、User-Agentヘッダ自体を削除した場合を含む9通りのUAを試したが、実在チャンネルはすべて200・62,941バイト・15エントリを返した。404本文の1,613バイトというサイズは「channel_idを24文字のまま間違えた」ときの応答と一致し、実際にIDを小文字にしただけで404・1,613バイトが再現した。
目次
YouTubeのRSSフィード https://www.youtube.com/feeds/videos.xml?channel_id=... が404を返したとき、「curlがブロックされている」「User-Agentを付ければ通る」と考えるのは、少なくとも当サイトが2026年8月14日に測った範囲では誤りだった。User-Agentヘッダを完全に削除しても200が返る。 404が返るのは channel_id の値そのものが引けないときで、24文字の誤ったIDを渡した場合の404本文は1,613バイトになる。当サイトはこの記事より前の作業メモに「UA無しは404・UA付きで200」と書いていたが、同じフィードを9通りのUAで叩き直しても404は1度も再現しなかった。直し方は、UAをいじる前に404本文を開いて、そこにエコーバックされている channel_id を目で読むことである。
- 実在チャンネル(
UCrpJWCdK_UUTKhC1-jGOFKQ)に対し、既定のcurl/8.7.1・-A ""(ヘッダごと削除)・python-requests・Wget・Googlebot・ブラウザUAなど9通りを試して、全部が200・62,941バイト・15エントリを返した(2026年8月14日16:07〜16:09 JST)- 404本文のサイズは
1,589 + channel_idの文字数で決まる。24文字のIDなら、正しかろうが間違っていようが404は必ず1,613バイトになるので、バイト数から原因は特定できない- IDを小文字にしただけ、
UCをUUにしただけで404・1,613バイトが再現した。一方でパラメータ名を間違えると404ではなく400が返る
症状:ステータスコードだけ見ても何も分からない
まず、実際に返ってくる404の本文を全部載せる。人はこの文字列を貼って検索するからである。存在しないチャンネルIDを渡したときの応答は次のようになる。
$ curl -sS -o /dev/null -w '%{http_code} %{size_download} %{content_type}\n' \
"https://www.youtube.com/feeds/videos.xml?channel_id=UCzzzzzzzzzzzzzzzzzzzzzz"
404 1613 text/html; charset=UTF-8
本文はXMLではなくHTMLで、Googleの汎用エラーページである。テキスト部分だけ取り出すと次の3つしか書かれていない。
Error 404 (Not Found)!!1
404. That’s an error.
The requested URL /feeds/videos.xml?channel_id=UCzzzzzzzzzzzzzzzzzzzzzz was not found on this server. That’s all we know.
(「404。エラーです。リクエストされたURL /feeds/videos.xml?channel_id=UCzzzzzzzzzzzzzzzzzzzzzz はこのサーバー上で見つかりませんでした。分かっているのはそれだけです」)
この本文には、自分が送った channel_id がそのままエコーバックされている。 ステータスコードだけをif (!res.ok)で見ていると、この一行を読まずに捨てることになる。当サイトの取得コード(src/lib/youtube.ts)がまさにそれだった。
if (!res.ok) return [];
404でも空配列を返すだけなので、画面には「動画がありません」と出る。エラーは出ない。channel_idを1文字間違えた状態と、チャンネルに動画が1本も無い状態が、区別できない形で潰れている。
検証1:User-Agentを9通り変えても、404は1度も出なかった
当サイトのチャンネル UCrpJWCdK_UUTKhC1-jGOFKQ に対して、User-Agentだけを変えて叩いた結果である(2026年8月14日16時07分JST、curl 8.7.1、macOS 15.1.1)。
| 指定したUser-Agent | ステータス | 本文サイズ |
|---|---|---|
既定(curl/8.7.1) |
200 | 62,941 |
-A ""(ヘッダごと削除) |
200 | 62,941 |
-A " "(空白1文字) |
200 | 62,941 |
-A "curl/8.7.1"(明示) |
200 | 62,941 |
-A "python-requests/2.31.0" |
200 | 62,941 |
-A "node-fetch/3.3.2" |
200 | 62,941 |
-A "Wget/1.21.4" |
200 | 62,941 |
-A "Mozilla/5.0 (compatible; Googlebot/2.1; ...)" |
200 | 62,941 |
| ブラウザUA(Chrome 126相当) | 200 | 62,941 |
9通りすべてで、バイト数まで完全に同じ応答が返った。 エントリ数はいずれも15である。
-A "" が本当にヘッダを消しているかは、curlの-vで送信ヘッダを見て確認した。User-Agent:の行が消えている。
> GET /feeds/videos.xml?channel_id=UCrpJWCdK_UUTKhC1-jGOFKQ HTTP/2
> Host: www.youtube.com
> Accept: */*
curlのmanページは-A, --user-agentについてこう書いている(2026年8月14日取得)。
If you give an empty argument to --user-agent (""), it removes the header completely from the request. If you prefer a blank header, you can set it to a single space (" ").
By default, curl uses curl/VERSION, such as User-Agent: curl/8.22.0.
(--user-agentに空の引数("")を与えると、リクエストからヘッダを完全に削除する。空のヘッダが良ければ空白1文字を設定できる/既定ではcurlはcurl/VERSION、たとえばUser-Agent: curl/8.22.0を使う)
つまり「UAを付けないと弾かれる」どころか、UAヘッダが1行も無いリクエストでも200が返っている。
連射でスロットリングが起きるかも確かめた。既定UAで30回、ブラウザUAで30回、合計60回を約5秒の間に連続実行したが、60回すべて200で、404も429も出なかった。
検証2:404が出る条件はchannel_id側にあった
今度はUser-Agentを一切いじらず(-Aを付けないので既定のcurl/8.7.1が送られる)、channel_idの方を少しずつ壊した結果である。
| 渡した値 | ステータス | 本文サイズ |
|---|---|---|
| 正しいID(24文字) | 200 | 62,941 |
末尾に改行(%0A) |
200 | 62,944 |
末尾に空白(%20) |
200 | 62,944 |
| 全部小文字にする | 404 | 1,613 |
UCをUUに変える |
404 | 1,613 |
| 存在しない24文字ID | 404 | 1,613 |
| 末尾1文字を落とす(23文字) | 404 | 1,612 |
引用符が値に混入(%22...%22) |
404 | 1,619 |
ハンドル(@aijitanlab)を渡す |
404 | 1,600 |
パラメータ名をchannelIdにする |
400 | 1,555 |
| パラメータ自体を付けない | 400 | 1,555 |
パスのtypo(video.xml) |
404 | 758 |
読み取れることが3つある。
1. 大文字小文字は区別される。 IDを小文字にしただけで404になる。設定ファイルやシェル変数を経由するときにtrやlower()が挟まると、この形で壊れる。
2. UCとUUの取り違えは404になる。 チャンネルの「全アップロード」再生リストIDはUCをUUに置き換えたものなので混同しやすいが、これはplaylist_idパラメータに渡すべき値である。実際playlist_id=UUrpJWCdK_UUTKhC1-jGOFKQなら200・62,928バイトが返った。
3. パラメータ名を間違えると404ではなく400が返る。 channelId(キャメルケース)や、パラメータ無しは400・1,555バイトになる。404と400で原因の層が違うので、ここは切り分けに使える。逆に言うと、ハンドル(@aijitanlab)をchannel_idに渡した場合はパラメータ名自体は正しいので400にはならず、404・1,600バイトになる。値の間違いは400では拾えない。
なお末尾に改行や空白が付いた場合は200のままで、本文が3バイトだけ増える。これはフィード冒頭の<link rel="self">が、渡した値を%0A・%20という3文字のままエコーバックするためである(実際に該当行を取り出して確認した)。中身は正常なフィードで、エントリ数も15、<yt:channelId>の値も末尾に改行を付けない場合とまったく同じだった。シェル変数の末尾に改行が残っていても、このAPIは200を返してしまう。
ついでに気づいた点として、フィード冒頭の<yt:channelId>と<id>yt:channel:...</id>はUCが落ちた22文字(rpJWCdK_UUTKhC1-jGOFKQ)で返り、各<entry>の中の<yt:channelId>だけが24文字(UCrpJWCdK_UUTKhC1-jGOFKQ)だった。フィードからchannel_idを拾って再利用する場合、どちらの<yt:channelId>を読んだかで24文字にも22文字にもなる。 22文字の方をそのままchannel_idに渡すと、当然404・1,611バイトになる。
原因:1,613バイトという数字は犯人を教えてくれない
ここが今回いちばん引っかかった点である。404本文のサイズを、channel_idの文字数を変えて測ると次のようになる。
channel_idの文字数 |
404本文のバイト数 | 差 |
|---|---|---|
| 3 | 1,592 | 1,589 |
| 7 | 1,596 | 1,589 |
| 24 | 1,613 | 1,589 |
| 34 | 1,623 | 1,589 |
404本文のサイズ = 1,589 + channel_idの文字数 で決まる。エラーページが「リクエストされたURL」を本文に埋め込むからである。正確には、URLのクエリに書いたままの長さで数えられる。上の表で引用符混入が1,619バイトだったのは、%22+24文字+%22=30文字がそのままエコーバックされたためで、パーセントエンコードは3文字として数えられている。
したがって、正しい24文字のIDが何らかの理由で404になった場合も、間違った24文字のIDで404になった場合も、本文は等しく1,613バイトになる。 バイト数は原因の区別に使えない。当サイトがメモに残した「UA無し=404/1,613バイト」という記録は、UAが原因だという証拠にはなっていなかった。
RFC 9110は404の意味をこう定義している(2026年8月14日取得、15.5.5節)。
The 404 (Not Found) status code indicates that the origin server did not find a current representation for the target resource or is not willing to disclose that one exists. A 404 status code does not indicate whether this lack of representation is temporary or permanent; the 410 (Gone) status code is preferred over 404 if the origin server knows, presumably through some configurable means, that the condition is likely to be permanent.
(404(Not Found)ステータスコードは、オリジンサーバーが対象リソースの現在の表現を見つけられなかったか、その存在を明かす意思が無いことを示す。404ステータスコードは、この表現の欠如が一時的か恒久的かを示さない。オリジンサーバーが──おそらく何らかの設定可能な手段によって──その状態が恒久的である可能性が高いと知っている場合は、404よりも410(Gone)ステータスコードが望ましい)
「見つからなかった」と「見つかったが教える気が無い」がRFC上で同じコードに畳まれている。 だからステータスコードだけでは、遮断・入力ミス・一時障害を区別できない。区別できるのは本文の中身であり、今回の場合はそこにchannel_idが書いてある。
対処:取得スクリプトで404を「存在しない」と結論しない
- 404が出たら、まず本文をそのまま出力する。 Googleの汎用404ページには送信したURLがエコーバックされている。
channel_idをここで目視する - ステータスコードで層を切り分ける。 400ならパラメータ名・パラメータ欠落の疑い、404なら
channel_idの値の疑い、と分岐先が違う - 大文字小文字と
UC/UUを先に疑う。 UAより先に疑うべきはこの2つだった - 「UAを変えて直った」ときは、UA以外を同時に変えていないか確認する。 今回の記録は、UAと
channel_idが同時に変わっていた可能性を排除できていなかった if (!res.ok) return []を書かない。 空配列は「0件」と「取得失敗」を同じ見た目にする。少なくともステータスコードとURLはログに残す
当サイトのsrc/lib/youtube.tsは現時点で5番目に該当したままである。この記事は修正の記録ではなく、誤った原因帰属をした記録である。
観測条件
- 日時:2026年8月14日 16時06分57秒〜16時09分46秒(JST)。同一セッション・同一回線から連続実行
- 環境:macOS 15.1.1(ビルド24B91)、
curl 8.7.1 (x86_64-apple-darwin24.0) libcurl/8.7.1 (SecureTransport) LibreSSL/3.3.6 zlib/1.2.12 nghttp2/1.62.0、Node.js v25.9.0 - 対象:
https://www.youtube.com/feeds/videos.xml?channel_id=UCrpJWCdK_UUTKhC1-jGOFKQ(AI時短ラボの自チャンネル) - 回数:UA9通りを各1回、連射を計60回、
channel_idの破壊パターンを12通り - Node.js(undici)の
fetch()でも同じURLを叩き、200・62,941バイト・15エントリを確認した - 再実行:同日16時34分〜16時38分JSTに、同一のMac・同一の回線で、UA9通り・
channel_id破壊パターン・連射60回・404本文のサイズ式のすべてを測り直した。本文に載せた数値と1バイトも違わない結果になった(ハンドルの行だけ食い違い、本文を404・1,600バイトに訂正した)。ただし同じIPからの再実行なので、IP評価に依存する挙動はこれでは確かめられない
参考として、別チャンネル(Google Developers、UC_x5XG1OV2P6uZZ5FSM9Ttw)でも既定UA・ブラウザUAの両方で200・25,131バイトが返り、UAによる差は出なかった。
正直な但し書き
「UAで404になることは絶対に無い」とは言えない。 今回言えるのは「2026年8月14日16時台に、この回線・このIPから、この2チャンネルに対しては、9通りのUAすべてで200が返った」という観測までである。Googleの遮断はIP評価・地域・リクエスト頻度に依存しうるので、別の条件で別の結果が出る可能性は残る。データセンターIPからの大量アクセスは試していない。
元の記録が「なぜ404になったか」は特定できていない。 この記事より前に当サイトが残していた作業メモには「UA無しは404・UA付きで200」とあり、404時1,613バイト・200時26,533バイトという数字が併記されていた。ただし裏付けはこのメモの記述そのものしかなく、当時のコマンド・レスポンス本文・実行ログはいずれも保存されていない。記事の検品時にリポジトリ内を検索したが、これらの数字を裏付けるログファイルは見つからなかった。したがって、当時のリクエストが(a)別のchannel_idを使っていたのか、(b)一時的に遮断されていたのか、(c)メモへの転記が誤っていたのかは区別できない。200側の26,533バイトは今回実測した62,941バイトと一致しないので、少なくとも今回測ったのと同じチャンネル・同じ時点の応答ではないが、それが何の応答だったかは確認できていない。再現しなかった、という事実だけを残す。
60回の連射でスロットリングは出なかったが、それ以上は試していない。 数百回・数時間単位で叩いた場合の挙動は未検証である。実運用で1時間おきに取得する程度なら問題は出ていないが、これは当サイトの利用頻度での話である。
このフィードにGoogleの公式ドキュメントを見つけられなかった。 feeds/videos.xmlの仕様(パラメータ名、返却件数の上限、レート制限)を説明した公式ページを当サイトは特定できていない。本記事で引用した一次ソースはRFC 9110とcurlのmanページのみで、YouTube側の仕様はすべて実測である。仕様として保証されたものではないので、業務で使うなら公式のYouTube Data APIを検討したほうがよい。 返却件数が15件だったのも実測値であり、上限として保証されたものではない。
src/lib/youtube.tsはまだ直していない。 この記事を書いた時点ではif (!res.ok) return []のままである。直した結果どう変わったかは、まだ報告できない。
出典
- RFC 9110: HTTP Semantics(2026年8月14日取得、15.5.5節 404 Not Found): https://www.rfc-editor.org/rfc/rfc9110.html
- curl man page(2026年8月14日取得、
-A, --user-agentの項): https://curl.se/docs/manpage.html - AI時短ラボ 実測ログ(2026年8月14日16:06:57〜16:09:46 JST取得。UA9通り・
channel_id破壊12通り・連射60回・404本文サイズの4点測定)
出典・参照資料
AIニュースの解説を動画でも
YouTubeでは注目ニュースの背景を解説し、Xでは新着記事をお知らせしています。
コメント
まだコメントはありません。最初のコメントを書いてみませんか?
AIについて聞きたいことはありますか?
質問箱で無料で受け付けています。回答は公開され、他の方の参考にもなります。
質問箱を見る →新しい記事をメールで受け取る
AIの新しい発表を、出典付きで整理して届けます。