参考にしたい動画のサムネイルだけを、まとめて手元に集めたい。1枚ずつ右クリックで保存するのは骨が折れるし、そもそも高解像度版がどこにあるのか分かりにくい。トレンド動画を一覧して、気になったサムネをまとめて取得できるツールを、フロントとバックに分けて作る。
目次
このガイドで作るもの
YouTube Data API v3 で日本のトレンド動画を一覧表示し、カテゴリ絞り込み・キーワード検索・地域切り替え(JP/US 等)をしながら、選んだ動画のサムネイルを一括ダウンロードするダッシュボード。一覧・検索は React(Vite) フロント、画像の取得・保存は小さな Express サーバーが担う。

構成は素直に分かれている。API呼び出しは src/services/youtube.js、画面は src/App.jsx と src/components/VideoCard.jsx、保存サーバーは server.js。フロント(一覧)とバック(保存)が別プロセスで、間を動画IDで繋ぐだけだ。
開発中は、この2プロセスを両方起動して使う。ターミナルを2枚開き、片方でフロント(Vite の開発サーバー)、もう片方で server.js(Express)を動かす。「画面は出るのに保存だけ失敗する」ときは、たいてい Express 側を起動し忘れている。2プロセス構成の定番のつまずきなので、先に知っておくと慌てない。毎回2枚のターミナルを開くのが面倒になったら、両方を一度に起動するスクリプトをAIに書いてもらえば1コマンドにまとまる。この「面倒になったら自動化」の順番なら、仕組みを理解してから楽になれる。
集めたサムネは、こう研究に使う
ツールの前に、集めた後の話を少し。例えば「ゲーム実況」ジャンルのトレンド上位50本のサムネをフォルダに並べると、1枚ずつ見ていたときには気づかない共通項が浮かぶ。文字は何文字まで載せているか、顔のアップは全体の何割か、背景は暗色系と明色系のどちらが多いか。エクスプローラーの大アイコン表示で俯瞰するだけで、そのジャンルの「勝ちパターンの土俵」が見えてくる。
比較の軸を先に決めておくと、収集が調査になる。「同ジャンルで登録者が1桁違う2チャンネルのサムネを並べる」「同じ投稿者の半年前と今を並べる」。差分を見る問いを持って集めれば、50枚のフォルダは立派な研究資料になる。逆に、問い無しで集めた100枚は、眺めて終わる壁紙になりがちだ。集める前に「何と何を比べたいか」を1行メモしておく。それだけで、このツールの元は取れる。念のため繰り返すと、用途は自分の企画の参考(私的利用)までだ。
設計の起点:「一覧」はフロント、「保存」はバックに分ける
ブラウザだけでは、外部ドメインの画像をローカルへ自動保存するのは扱いにくい(クロスオリジン制約・保存ダイアログ)。そこで一覧・検索は React フロント、サムネの取得・ファイル書き出しは Express バックに分けた。フロントは動画IDを渡すだけ、サーバーが画像を取りに行ってディスクに書く。役割を割ると、それぞれを単純に保てる。

作り方の要点
1. Data API v3 でトレンド・検索・統計を取る
APIキーは .env(VITE_YOUTUBE_API_KEY)に置き、コードへ直書きしない。トレンドは chart=mostPopular + regionCode で取る。
APIキーの取得は無料だ。Google Cloud Console でプロジェクトを作り、「YouTube Data API v3」を有効化して、認証情報からキーを発行する。クレジットカードの登録は不要で、既定の無料クォータ(1日10,000ユニット)の範囲ならお金は一切かからない。発行したキーを .env に書けば準備完了になる。
// src/services/youtube.js
import axios from 'axios';
const youtubeApi = axios.create({
baseURL: 'https://www.googleapis.com/youtube/v3',
params: { key: import.meta.env.VITE_YOUTUBE_API_KEY },
});
export const getTrendingVideos = async (categoryId = '0', maxResults = 50, region = 'JP') => {
const res = await youtubeApi.get('/videos', {
params: {
part: 'snippet,statistics',
chart: 'mostPopular',
videoCategoryId: categoryId === '0' ? undefined : categoryId,
maxResults,
regionCode: region || 'US', // mostPopular は regionCode 必須
},
});
return res.data.items;
};
地域切り替え(regionCode)は、単なるおまけ機能ではない。JP と US を切り替えて同じジャンルのトレンドを見比べると、サムネの作法が地域でまるで違うことが分かる。文字量・色使い・顔の写し方。海外の型を早めに取り入れる参考にも、日本向けのローカライズの確認にも、この1パラメータが効く。

regionCode ひとつで一覧の顔つきが変わる。検索は2段構えにする。/search は動画IDしか返さないので、まずIDを集め、/videos でまとめて統計を取る。search は1回あたりのコストが高いので、まとめて1回で引くのがクォータ節約の要だ。

search で集めた動画IDを videos でまとめて引く2段構えで取得している。export const searchVideos = async (query, { region = 'JP' } = {}, maxResults = 50) => {
// 1) search で動画IDを集める
const search = await youtubeApi.get('/search', {
params: {
part: 'snippet', q: query, type: 'video',
maxResults, regionCode: region || 'US',
relevanceLanguage: region === 'JP' ? 'ja' : undefined,
},
});
const items = search.data.items;
if (items.length === 0) return [];
const ids = items.map(i => i.id.videoId).join(',');
// 2) videos でまとめて統計を取る(search を何度も叩かない=クォータ節約)
const details = await youtubeApi.get('/videos', {
params: { part: 'snippet,statistics', id: ids },
});
return details.data.items;
};
2. サムネは動画IDから予測できるURLで取る
YouTube のサムネイルは動画IDから URL が決まる。最高解像度は https://img.youtube.com/vi/<videoId>/maxresdefault.jpg だ。「IDさえあればサムネのURLは組み立てられる」と知っているだけで実装は一気に楽になる。なお maxresdefault が存在しない動画もあるため、無い場合は hqdefault.jpg 等へフォールバックする想定にしておく。
解像度違いも同じ規則で並んでいる。用途で使い分けられるよう、URLの“品揃え”を一覧にしておく。
| ファイル名 | 解像度 | 用途の目安 |
|---|---|---|
default.jpg |
120×90 | 最小。ほぼ確実に存在する |
mqdefault.jpg |
320×180 | 一覧表示に十分・軽い |
hqdefault.jpg |
480×360 | フォールバックの定番 |
sddefault.jpg |
640×480 | 無い動画もある |
maxresdefault.jpg |
1280×720 | 保存用の本命。無い動画もある |
このアプリでは、一覧のカード表示は軽いサムネ、保存だけ maxresdefault、と役割を分けている。一覧まで最高解像度で並べると、50件の画面表示だけで無駄に重くなるからだ。
細かい癖も1つ。hqdefault(480×360)は4:3の枠なので、16:9のサムネは上下に黒帯が付いた形で返ってくる。参考閲覧なら気にならないが、黒帯を避けたい一覧表示には16:9のまま返る mqdefault(320×180)が向いている。
3. 保存はバックエンドでストリーム書き出し
サーバー(Express + axios)で画像をストリーム取得し、メモリに溜めず createWriteStream へそのまま流す。大きな画像でもメモリを食わない。

// server.js — サムネを取得してディスクへ保存
app.post('/api/download', async (req, res) => {
const { videoId, title } = req.body;
const url = `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
// ファイル名を安全化(OS禁止文字を置換+長さ制限)+IDで一意化
const cleanTitle = title.replace(/[\\/:*?"<>|]/g, '_').substring(0, 50);
const filePath = path.join(SAVE_DIR, `${cleanTitle}_${videoId}.jpg`);
try {
const response = await axios({ url, method: 'GET', responseType: 'stream' });
const writer = fs.createWriteStream(filePath);
response.data.pipe(writer);
writer.on('finish', () => res.json({ success: true, path: filePath }));
writer.on('error', () => res.status(500).json({ error: 'ファイルの書き込みに失敗しました' }));
} catch (e) {
res.status(500).json({ error: '画像の取得に失敗しました' });
}
});
ファイル名の作り方にも小さな配慮がある。タイトルからOSの禁止文字を置換し、先頭50文字に制限するのは、Windows のパス長制限を踏みにくくするためだ。末尾に動画IDを付けているので、同じ動画を2回保存しても同名になって上書きされ、重複ファイルが溜まらない。似たタイトルの動画同士がぶつかることもない。ファイル名の設計は書いた瞬間より、100枚溜まった後に効いてくる類の仕事だ。

img.youtube.com の予測URLから取得し、image/ フォルダへストリームで書き出す。つまずきと対策
- クォータ枯渇:
searchは高コスト。partを絞り、検索後は/videosで一括取得して呼び出し回数を減らす。 - maxres が無い動画:404 になることがある。
hqdefault等へフォールバックする。 - なぜブラウザだけで完結しないか:クロスオリジン画像のローカル保存はブラウザの制約が強い。だからこそ保存はサーバー側に置く。
次に足すなら、この2つ
骨格が動いたら、育て先の候補も置いておく。どちらも今の構成(フロント一覧+バック保存)のまま足せる。
- チャンネル単位のまとめ取り … いまはトレンド・検索起点だが、「このチャンネルの直近50本」を起点にできると、特定の投稿者の変遷を追う研究がしやすくなる。
channels→ アップロード一覧 →videosと、APIの呼び順が1段増えるだけだ。 - 保存済みの管理 … 同じ調査を何度かやると、どれを保存済みか分からなくなる。保存時に動画ID・タイトル・保存日をCSVに1行追記しておけば、フォルダが増えても台帳で追える。
逆に、足さない方がいい方向もある。取得の自動化・定期巡回は、私的な参考収集の範囲を超えやすく、APIのクォータも規約も窮屈になる。手で選んで、選んだ分だけ保存する。この道具はその範囲が一番心地よい。
使用ライブラリと順守メモ(重要)
このツールは「自分が参考にするための私的な収集」を前提に作る。公開・配布する成果物ではない。以下を必ず守る。
- 商標:YouTube / Google は各社の商標。本ガイドは説明のための記述的な言及に留め、提携・公認を示唆しない。
- API利用規約:YouTube API Services の利用規約・デベロッパーポリシーの範囲内で利用する。APIキーは読者が各自取得し、クォータと規約(取得データの扱い等)を順守する。
- 著作権:取得する動画のメタデータ・サムネイル画像は各投稿者(権利者)の著作物。本ツールでの取得は私的利用(手元での参考閲覧)の範囲に限る。
- 再配布の禁止:取得したサムネを再配布・再アップロード・自作物への転用・商用利用しない。あくまで参考資料として手元に置くまで。
- YouTube 利用規約:YouTube の利用規約は、サービスが提供する手段以外でのコンテンツのダウンロードを原則制限している。
img.youtube.comの直接取得は Data API の枠外のアクセスであり、本手法の利用は各自の責任で現行規約を確認のうえ、私的な参考閲覧の範囲に留めること。 - アプリ自体の依存(React / Vite / Express / axios / cors)はいずれも MIT ライセンス。
まとめ
勘所は3つ。① 一覧はフロント・保存はバックに分ける、② サムネは動画IDから予測URLで取れる、③ 取得はストリームで書き出す。同じ発想は他のメディア収集ツールにもそのまま効く。「一覧して選ぶ」と「まとめて保存する」を分ける形は、画像に限らず何を集めるときも使い回せる。ただし集めた画像は他者の著作物なので、私的な参考の範囲を超えないことが、長く安全に使う条件だ。
👉 関連: 開発実例:YouTubeサムネイル取得 ・ YouTube関連の開発実例 ・ ほかのアプリの作り方