楽したくて詰みました。YouTube用の動画を作るのにBGMが必要で、著作権フリーの音源サイトからいくつかまとめてダウンロードしようとしました。曲ごとにブラウザを開いてボタンを押していくのが面倒だったので、コマンドで済ませようとしたら、思ったより素直にいきませんでした。
URLを直接叩いても落ちてこない
最初にやったのは、ダウンロードボタンのリンク先をそのままcurlに渡すことです。
curl -O "https://example.com/download/1234"
結果は空振りでした。結果、音声ファイルではなくHTMLが返ってきました。中身を見るとエラーページで、リクエストが正しくないという趣旨のことが書いてあります。
ブラウザだと普通に落ちてくるのに、コマンドだと弾かれる。この時点で「ブラウザが送っていて、自分が送っていないものがある」という状況だと分かります。
ダウンロードがGETではなかった
そこで通信を覗きました。ブラウザの開発者ツールでネットワークタブを開いて、実際にボタンを押したときの通信を見てみました。
ダウンロードはリンクをたどるGETリクエストではなく、フォームのPOSTでした。そして送信データの中にcsrfmiddlewaretokenという項目が入っていました。
正体はこれでした。これはCSRF対策のトークンで、ざっくり言うと「このリクエストは、ちゃんとこのサイトのページから送られてきたものですよ」を証明するための使い捨ての文字列です。攻撃者が用意した別サイトから勝手にPOSTを飛ばされるのを防ぐ仕組みで、多くのフレームワークに標準で入っています。
つまり、URLを知っているだけでは足りなくて、先にページを開いてトークンを受け取っておく必要がありました。
2段階に分けて解決した
方針は決まりました。やることが分かれば手順は単純で、2段階に分けるだけです。
まずダウンロードページをGETして、cookieを保存しつつHTMLからトークンを抜き出します。
curl -c cookie.txt -s "https://example.com/detail/1234" > page.html
トークンはHTML内のinput要素に埋まっているので、そこから値を取り出します。次に、そのトークンと保存したcookieを一緒に付けてPOSTします。
curl -b cookie.txt -X POST -d "csrfmiddlewaretoken=$TOKEN" -O -J "https://example.com/download/1234"
肝はcookieでした。ここでcookieが要るのがポイントでした。トークンだけ合っていても通らないんです。サーバー側は「cookieに入っている値」と「POSTで送られてきたトークン」を突き合わせて検証するので、両方が揃って初めて正しいリクエストと判定されます。片方だけ真似しても意味がない、という作りになっています。
仕組みは完成です。あとは曲のIDをループで回せば、必要な曲がまとめて手元に落ちてきました。
やっていいことの線引き
線引きは大事です。この手の自動化をするときに、毎回考えるようにしていることがあります。
ひとつは、そのサイトの規約で許されている範囲かどうか。今回使ったのは商用利用可・クレジット表記不要で配布されている音源で、ダウンロード自体は誰でもできるものでした。ログインが必要なコンテンツを回避したり、有料のものを取ったりしているわけではありません。
もうひとつは、相手のサーバーに負荷をかけないこと。数曲程度なら人が手で押すのと変わりませんが、ループを回すときは間隔を空けるようにしています。
ここは自覚しています。CSRFトークンは本来セキュリティのための仕組みなので、それを迂回する形になっているのは事実です。ただ今回やっているのは「ブラウザと同じ手順を踏む」ことであって、検証を破っているわけではありません。この違いは意識しておきたいところだなと思っています。
まとめ
URLを叩いてもファイルが落ちてこないとき、原因は認証だけとは限りません。今回のようにCSRFトークンが要るケースだと、エラーメッセージからは何が足りないのか読み取れないことも多いです。
近道は決まっています。そういうときは開発者ツールでブラウザの通信をそのまま見るのが一番早いです。ブラウザは正解のリクエストを送っているので、それと自分のリクエストの差分を埋めていけば必ず通ります。今回もClaudeと相談しながらそれで解決しました。
この記事が参考になったら、応援クリックをお願いします




