2026年8月14日 金曜日
AI時短ラボ
活用· 約21

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 を目で読むことである。

  1. 実在チャンネル(UCrpJWCdK_UUTKhC1-jGOFKQ)に対し、既定のcurl/8.7.1-A ""(ヘッダごと削除)・python-requestsWgetGooglebot・ブラウザUAなど9通りを試して、全部が200・62,941バイト・15エントリを返した(2026年8月14日16:07〜16:09 JST)
  2. 404本文のサイズは 1,589 + channel_idの文字数 で決まる。24文字のIDなら、正しかろうが間違っていようが404は必ず1,613バイトになるので、バイト数から原因は特定できない
  3. IDを小文字にしただけ、UCUUにしただけで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
UCUUに変える 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になる。設定ファイルやシェル変数を経由するときにtrlower()が挟まると、この形で壊れる。

2. UCUUの取り違えは404になる。 チャンネルの「全アップロード」再生リストIDはUCUUに置き換えたものなので混同しやすいが、これは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を「存在しない」と結論しない

  1. 404が出たら、まず本文をそのまま出力する。 Googleの汎用404ページには送信したURLがエコーバックされている。channel_idをここで目視する
  2. ステータスコードで層を切り分ける。 400ならパラメータ名・パラメータ欠落の疑い、404ならchannel_idの値の疑い、と分岐先が違う
  3. 大文字小文字とUC/UUを先に疑う。 UAより先に疑うべきはこの2つだった
  4. 「UAを変えて直った」ときは、UA以外を同時に変えていないか確認する。 今回の記録は、UAとchannel_idが同時に変わっていた可能性を排除できていなかった
  5. 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の新しい発表を、出典付きで整理して届けます。

関連記事

AI時短ラボ
活用08.14読了34

Search Console APIのsites.addはエラーを返さないのにデータは1件も読めない──permissionLevelがsiteUnverifiedUserで止まる二段構え

出典 ─ Sites: add | Search Co
AI時短ラボ
活用08.14読了32

HTTP 200・79,525バイトなのに可視テキストは416文字──「取得できた」を200で数えてはいけない

出典 ─ RFC 9110: HTTP Semanti
AI時短ラボ
活用08.14読了42

IndexNowにGoogleは参加していない──418URL送ってもGoogleには渡らない仕組みと、鍵が無効なときに返る403 UserForbiddedToAccessSite

出典 ─ IndexNow - Home(indexn
AI時短ラボ
活用08.14読了24

macOSのawkはIGNORECASE=1をエラーも出さず無視する──Linux前提のワンライナーが「検出0件」で静かに通る

出典 ─ GAWK: Effective AWK Pr
AI時短ラボ
活用08.14読了26

出典URLの死活チェックで404だけ見てはいけない──1,517件を全数検査したら、HEADに404を返す公式ドキュメントと403の一次資料が出てきた

出典 ─ RFC 9110: HTTP Semanti
AIでショート動画を作り続ける工程──固定する5つと、毎回変える3つの記事画像
活用08.14読了16

AIでショート動画を作り続ける工程──固定する5つと、毎回変える3つ

出典 ─ YouTube ショートの作成を始める -
AI生成サムネの色がまとまらない理由──配色を「主役・支持・背景」の3階層で書くプロンプトの型の記事画像
活用08.14読了23

AI生成サムネの色がまとまらない理由──配色を「主役・支持・背景」の3階層で書くプロンプトの型

出典 ─ Add custom thumbnails
AIで作った動画はYouTubeで収益化できるのか──公式ポリシー原文で確認した4つの線引きの記事画像
活用08.14読了23

AIで作った動画はYouTubeで収益化できるのか──公式ポリシー原文で確認した4つの線引き

出典 ─ YouTube パートナー プログラムの参加