YouTubeデータ取得 データ取得

YouTube Data APIでチャンネルの動画一覧をCSV保存する方法|uploads playlistを使う手順

2023年12月27日

この記事では、YouTube Data API v3を使って、指定したチャンネルの動画一覧を取得し、CSVに保存する方法をまとめます。

自分のデータに合う実行経路を選ぶ

以下はWindows PowerShellでの手順です。Python 3.10以上を用意し、必要なファイルを保存したフォルダーをエクスプローラーで開き、アドレス欄に powershell と入力してEnterを押します。Get-Location で作業フォルダーを確認してください。コード全文は後ろに掲載しています。選んだ経路だけを実行し、別の経路のコードを同じファイルへつなげないでください。

動画ID一覧の取得と、保存一覧への統計追加はいずれもYouTube API通信・APIキーが必要です。分析用CSVが完成済みなら 通信なしの集計・可視化へ進みます。キーなしの練習は 架空サンプルの可視化で可能です。

一覧をこれから取得する:完全版か条件記録付き版の一方

「実行コード全体」のPythonコード1個だけを youtube_channel_videos.py に保存します。その前の分割説明コードをさらに追加しません。同じフォルダーへAPIキーを入れた .env を置きます。以下は完全版を1回実行する経路で、main()内のchannel_idを実際の値にしてから実行します。

python -m pip install requests python-dotenv
python youtube_channel_videos.py

取得範囲・時点も残すなら上の実行コマンドの代わりに、条件記録付きコードrun_channel_list_recorded.py に保存して次を1回実行します。youtube_channel_videos.pyは必要ですが、importではmain()は走りません。両方を続けて実行して二重取得しないでください。記録付き版のchannel_idとmax_pagesはそちらのファイル内で設定します。

python run_channel_list_recorded.py

完全版はmain()の既存呼び出し、記録付き版はmax_pages変数が取得範囲を決めます。初回は1ページ。全ページを選ぶ場合だけ対応箇所をNoneへ変え、下に新たな取得呼び出しを追加しません。相対出力は作業フォルダー基準、完全版の.envはスクリプト横です。同じフォルダーから実行すれば両者が揃います。

一覧CSVがある:IDから統計を追加する(API通信あり)

統計追加コードだけを enrich_channel_statistics.py へ保存します。同じ作業フォルダーの outputs/youtube/channel_videos.csv を読み、IDで対応付けます。完全版をもう一度実行する必要はありません。.envとrequests・python-dotenvは必要です。

python -m pip install requests python-dotenv
python enrich_channel_statistics.py

最初に確認するのは一覧の channel_videos.csv と、追加版の channel_videos_with_statistics.csv(いずれもoutputs/youtube内)です。追加版で detail_status=not_returned の行を確認すると、詳細が応答に含まれなかった動画が分かります。削除や非公開とは断定しません。returnedの統計欄の0と空欄も別です。対応する.metadata.jsonに取得時点・件数・not_returned_idsがあります。条件記録付き版はsavedとrows、統計版は保存件数の表示を確認します。

掲載コードと詳しい条件

チャンネルの動画一覧を取得する方法はいくつかありますが、この記事では channels.list でアップロード用再生リストIDを取得し、playlistItems.list で動画一覧をたどる方法を使います。特定チャンネルの投稿一覧を取得する目的に直接対応した経路です。

取得対象は、YouTube Data APIで取得できる公開動画のメタデータです。非公開動画や削除済み動画、権限のない情報は通常取得できません。実行時はYouTube API Servicesの利用規約やクォータ制限を確認し、必要以上に繰り返し取得しないようにします。

この記事でできること

  • チャンネルIDからアップロード動画一覧を取得する
  • nextPageToken を使って複数ページを取得する
  • 動画ID、タイトル、公開日、説明文などをCSVに保存する
  • 必要に応じて再生数などの統計情報を追加する
  • search.list との使い分けを理解する

全体の流れ

  1. APIキーを読み込む
  2. channels.list でチャンネルの uploads playlist IDを取得する
  3. playlistItems.list でアップロード動画一覧を取得する
  4. ページングしながら全件を集める
  5. CSVに保存する

公式ドキュメントでも、チャンネルにアップロードされた動画を取得する例では、チャンネルの uploads playlistを取得してから playlistItems.list を呼び出す流れが紹介されています。

PlaylistItems: list - YouTube Data API

search.listを使わない理由

search.list でもチャンネルIDを指定して動画を検索できます。ただし、検索APIは検索結果として返るため、チャンネルの投稿一覧を安定して順番に集めたい用途では、uploads playlistを使うほうが素直です。

方法向いている用途補足
playlistItems.list特定チャンネルの投稿動画一覧uploads playlistをたどる。1回1ユニット(通常のクォータ枠)
search.listキーワードや条件で探す検索用途。1回1ユニット/専用枠は標準100回・日

API仕様確認:2026年9月11日(記事の更新日時とは別に、公式仕様を照合した日です)。Googleのクォータ表では、playlistItems.listsearch.listも1回の呼び出しにつき1ユニットです。ただし、search.listはSearch Queries専用のクォータ枠で、標準上限は1日100回です。playlistItems.listなどの通常枠は、他の対象APIと合計で標準1日10,000ユニットです。追加ページの取得も1回ずつ数えます。1回あたりのコスト差ではなく、投稿一覧にはuploads playlist、キーワード・条件検索にはsearch.listと、目的に応じて使い分けます。実際の割当はGoogle Cloud Consoleで確認してください。2026年6月1日の更新履歴では、search.listとvideos.insertの専用枠への移行が案内されています。

Quota Calculator - YouTube Data API

事前準備

APIキーは .env に保存します。

YOUTUBE_API_KEY=ここにAPIキーを書く

必要なライブラリをインストールします。

APIキーの管理方法は、次の記事で整理しています。

APIキーを外部ファイルで管理する方法|.envとconfig.iniの使い分け

チャンネルIDからuploads playlist IDを取得する

まず、channels.list を使って、チャンネルのアップロード動画が入っている再生リストIDを取得します。

import os

import requests
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.getenv("YOUTUBE_API_KEY")

if not API_KEY:
    raise RuntimeError("YOUTUBE_API_KEY が設定されていません")


def get_uploads_playlist_id(channel_id: str) -> str:
    url = "https://www.googleapis.com/youtube/v3/channels"
    params = {
        "part": "contentDetails",
        "id": channel_id,
        "key": API_KEY,
    }

    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
    except requests.RequestException as exc:
        status = getattr(exc.response, "status_code", None)
        if status is not None:
            raise RuntimeError(
                f"HTTP {status}: APIキー・権限・quota等を確認してください。"
            ) from None
        raise RuntimeError("通信に失敗しました。接続とタイムアウトを確認してください。") from None
    data = response.json()

    if not data["items"]:
        raise RuntimeError("チャンネルが見つかりませんでした")

    return data["items"][0]["contentDetails"]["relatedPlaylists"]["uploads"]

HTTPエラー時はステータスと確認事項だけを表示し、通信エラー時もリクエストURLや例外全文は出力しません。APIキーを含む例外の連鎖表示も抑制しています。

uploads は、そのチャンネルに投稿された動画が入る再生リストです。以降は、この再生リストをページングしながら取得します。

動画一覧をページング取得する

playlistItems.list は、1回のリクエストで最大50件まで取得できます。さらに取得する場合は、レスポンスに含まれる nextPageToken を次のリクエストに渡します。

def get_playlist_items(playlist_id: str, max_pages: int | None = None) -> list[dict]:
    url = "https://www.googleapis.com/youtube/v3/playlistItems"
    items = []
    page_token = None
    page_count = 0

    while True:
        params = {
            "part": "snippet,contentDetails",
            "playlistId": playlist_id,
            "maxResults": 50,
            "key": API_KEY,
        }

        if page_token:
            params["pageToken"] = page_token

        try:
            response = requests.get(url, params=params, timeout=30)
            response.raise_for_status()
        except requests.RequestException as exc:
            status = getattr(exc.response, "status_code", None)
            if status is not None:
                raise RuntimeError(
                    f"HTTP {status}: APIキー・権限・quota等を確認してください。"
                ) from None
            raise RuntimeError("通信に失敗しました。接続とタイムアウトを確認してください。") from None
        data = response.json()

        items.extend(data.get("items", []))

        page_count += 1
        if max_pages is not None and page_count >= max_pages:
            break

        page_token = data.get("nextPageToken")
        if not page_token:
            break

    return items

max_pages を用意しておくと、動作確認時に最初の1ページだけ取得できます。いきなり全件取得するより、まず少量で確認するほうが安全です。

CSVに保存する

取得した動画一覧から、分析に使いやすい項目だけを取り出してCSVに保存します。

import csv
from pathlib import Path


def normalize_playlist_item(item: dict) -> dict:
    snippet = item["snippet"]
    content_details = item["contentDetails"]

    return {
        "video_id": content_details["videoId"],
        "title": snippet.get("title", ""),
        "published_at": content_details.get("videoPublishedAt", ""),
        "channel_id": snippet.get("channelId", ""),
        "channel_title": snippet.get("channelTitle", ""),
        "position": snippet.get("position", ""),
        "description": snippet.get("description", ""),
    }


def save_csv(rows: list[dict], csv_path: Path) -> None:
    csv_path.parent.mkdir(parents=True, exist_ok=True)

    fieldnames = [
        "video_id",
        "title",
        "published_at",
        "channel_id",
        "channel_title",
        "position",
        "description",
    ]

    with csv_path.open("w", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)

説明文は長くなることがあります。CSVで扱いにくい場合は、最初は説明文を外して保存しても問題ありません。

実行コード全体

ここまでをまとめたコードです。まずは max_pages=1 で動作確認し、問題なければ None に変更します。

import csv
from pathlib import Path
import os

import requests
from dotenv import load_dotenv

load_dotenv(Path(__file__).with_name(".env"))

API_KEY = os.getenv("YOUTUBE_API_KEY")

if not API_KEY:
    raise RuntimeError("YOUTUBE_API_KEY が設定されていません")


def get_uploads_playlist_id(channel_id: str) -> str:
    url = "https://www.googleapis.com/youtube/v3/channels"
    params = {
        "part": "contentDetails",
        "id": channel_id,
        "key": API_KEY,
    }

    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
    except requests.RequestException as exc:
        status = getattr(exc.response, "status_code", None)
        if status is not None:
            raise RuntimeError(
                f"HTTP {status}: APIキー・権限・quota等を確認してください。"
            ) from None
        raise RuntimeError("通信に失敗しました。接続とタイムアウトを確認してください。") from None
    data = response.json()

    if not data["items"]:
        raise RuntimeError("チャンネルが見つかりませんでした")

    return data["items"][0]["contentDetails"]["relatedPlaylists"]["uploads"]

def get_playlist_items(playlist_id: str, max_pages: int | None = None) -> list[dict]:
    url = "https://www.googleapis.com/youtube/v3/playlistItems"
    items = []
    page_token = None
    page_count = 0

    while True:
        params = {
            "part": "snippet,contentDetails",
            "playlistId": playlist_id,
            "maxResults": 50,
            "key": API_KEY,
        }

        if page_token:
            params["pageToken"] = page_token

        try:
            response = requests.get(url, params=params, timeout=30)
            response.raise_for_status()
        except requests.RequestException as exc:
            status = getattr(exc.response, "status_code", None)
            if status is not None:
                raise RuntimeError(
                    f"HTTP {status}: APIキー・権限・quota等を確認してください。"
                ) from None
            raise RuntimeError("通信に失敗しました。接続とタイムアウトを確認してください。") from None
        data = response.json()

        items.extend(data.get("items", []))

        page_count += 1
        if max_pages is not None and page_count >= max_pages:
            break

        page_token = data.get("nextPageToken")
        if not page_token:
            break

    return items

def normalize_playlist_item(item: dict) -> dict:
    snippet = item["snippet"]
    content_details = item["contentDetails"]

    return {
        "video_id": content_details["videoId"],
        "title": snippet.get("title", ""),
        "published_at": content_details.get("videoPublishedAt", ""),
        "channel_id": snippet.get("channelId", ""),
        "channel_title": snippet.get("channelTitle", ""),
        "position": snippet.get("position", ""),
        "description": snippet.get("description", ""),
    }


def save_csv(rows: list[dict], csv_path: Path) -> None:
    csv_path.parent.mkdir(parents=True, exist_ok=True)

    fieldnames = [
        "video_id",
        "title",
        "published_at",
        "channel_id",
        "channel_title",
        "position",
        "description",
    ]

    with csv_path.open("w", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)


def main() -> None:
    channel_id = "UCxxxxxxxxxxxxxxxxxxxxxx"  # 対象チャンネルのIDへ変更
    uploads_playlist_id = get_uploads_playlist_id(channel_id)
    print("uploads playlist:", uploads_playlist_id)
    items = get_playlist_items(uploads_playlist_id, max_pages=1)
    rows = [normalize_playlist_item(item) for item in items]
    csv_path = Path("outputs/youtube/channel_videos.csv")
    save_csv(rows, csv_path)
    print(f"saved: {csv_path}")
    print(f"rows: {len(rows)}")


if __name__ == "__main__":
    main()

Python 3.10以上で、上の完全版コードを youtube_channel_videos.py として保存します。同じフォルダーにAPIキーを設定した .env を置き、channel_id を対象のチャンネルIDに変更してください。Windowsのターミナルで保存先フォルダーに移動して実行します。

正常終了時の出力例(以下のID・動画名・件数は説明用の架空データです):

uploads playlist: UUexample
saved: outputs\youtube\channel_videos.csv
rows: 3

CSVの列と内容の見本です。先頭は見出し、続く3行が動画データです。実際の保存内容はAPIの応答によって異なります。

video_id,title,published_at,channel_id,channel_title,position,description
example01,サンプル動画1,2026-01-03T09:00:00Z,UCexample,サンプルチャンネル,0,説明1
example02,サンプル動画2,2026-01-02T09:00:00Z,UCexample,サンプルチャンネル,1,説明2
example03,サンプル動画3,2026-01-01T09:00:00Z,UCexample,サンプルチャンネル,2,説明3

全件取得する場合は、上の完全版コードの main() 内にある既存の get_playlist_items() 呼び出しを、max_pages=1 から max_pages=None に変更します。次の行は差し替え後の例です。呼び出しを追加するのではなく、既存の行を書き換えてください。

items = get_playlist_items(uploads_playlist_id, max_pages=None)

一覧の取得範囲・取得時点も保存する場合

既存CSVがある方は取り直さず、次の統計追加へ進んでください。今後の取得で条件も残す場合のみ、上の完全版を youtube_channel_videos.py として保存し、次を同じフォルダーの run_channel_list_recorded.py に保存します。完全版の直接実行に代わる手順で、両方を続けて実行しません。channel_idを設定し、実行するとCSVと同名のmetadataを保存します。max_pages=1は最大1ページ、Noneはその時点でAPIからたどれる全ページです。

"""Alternative to running youtube_channel_videos.py directly; not an extra fetch step."""
import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
from youtube_channel_videos import get_uploads_playlist_id, get_playlist_items, normalize_playlist_item, save_csv

channel_id = "UCxxxxxxxxxxxxxxxxxxxxxx"  # Change to the actual channel ID.
max_pages = 1  # None follows all API pages available at acquisition.
output = Path("outputs/youtube/channel_videos.csv")
sidecar = output.with_suffix(".metadata.json")
if output.exists() or sidecar.exists():
    raise SystemExit("既存CSVを保持します。別フォルダーを指定してください。再取得は不要です。")
started = datetime.now(timezone.utc).isoformat()
playlist_id = get_uploads_playlist_id(channel_id)
items = get_playlist_items(playlist_id, max_pages=max_pages)
finished = datetime.now(timezone.utc).isoformat()
rows = [normalize_playlist_item(item) for item in items]
save_csv(rows, output)
metadata = {"csv_file": output.name, "csv_sha256": hashlib.sha256(output.read_bytes()).hexdigest(),
    "channel_ids": [channel_id], "list_started_at": started, "list_finished_at": finished, "saved_rows": len(rows),
    "scope": {"source": "uploads playlist", "max_pages": max_pages,
              "coverage": "API pagination exhausted" if max_pages is None else "at most specified pages"}}
sidecar.write_text(json.dumps(metadata, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"saved: {output.resolve()} / rows: {len(rows)}")

保存済みCSVへ統計情報を追加する

新しいPython実行から outputs/youtube/channel_videos.csv を読み、動画IDで統計を結合します。次を enrich_channel_statistics.py に保存し、同じフォルダーの.envへAPIキーを設定します。requestsとpython-dotenvを使い、ほかのPythonファイルの変数には依存しません。元一覧は維持し、channel_videos_with_statistics.csv とmetadataを別保存します。

"""Run from the project directory. Input CSV is never overwritten."""
import csv
import hashlib
import json
import os
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path

import requests
from dotenv import load_dotenv


def utc_now():
    return datetime.now(timezone.utc).isoformat()


def read_source(path):
    with path.open(encoding="utf-8-sig", newline="") as f:
        reader = csv.DictReader(f)
        columns = reader.fieldnames or []
        if "video_id" not in columns:
            raise ValueError("video_id列が必要です。動画一覧CSVを確認してください。")
        rows = list(reader)
    if any(not (r.get("video_id") or "").strip() for r in rows):
        raise ValueError("空の動画IDがあります。元CSVを確認してください。")
    for r in rows:
        r["video_id"] = r["video_id"].strip()
    meta_path = path.with_suffix(".metadata.json")
    source_meta = {"scope": "UNKNOWN", "list_started_at": None, "list_finished_at": None}
    digest = hashlib.sha256(path.read_bytes()).hexdigest()
    if meta_path.exists():
        source_meta = json.loads(meta_path.read_text(encoding="utf-8"))
        if source_meta.get("csv_sha256") != digest:
            raise ValueError("CSVとmetadataのhashが一致しません。同じ取得回の組を選んでください。")
    return rows, columns, source_meta, digest


def get_video_statistics(video_ids, api_key):
    result = {}
    for start in range(0, len(video_ids), 50):
        ids = video_ids[start:start + 50]
        response = requests.get("https://www.googleapis.com/youtube/v3/videos",
            params={"part": "statistics,contentDetails", "id": ",".join(ids), "key": api_key}, timeout=30)
        response.raise_for_status()
        for item in response.json().get("items", []):
            key = item["id"]
            if key not in ids or key in result:
                raise ValueError("応答IDが要求IDと整合しません。保存を中止します。")
            stats = item.get("statistics", {})
            result[key] = {name: stats.get(api_name) for name, api_name in
                [("view_count", "viewCount"), ("like_count", "likeCount"), ("comment_count", "commentCount")]}
            result[key]["duration"] = item.get("contentDetails", {}).get("duration")
    return result


def enrich(source, output, api_key):
    if source.resolve() == output.resolve():
        raise ValueError("元CSVと保存先は分けてください。")
    meta_path = output.with_suffix(".metadata.json")
    if output.exists() or meta_path.exists():
        raise FileExistsError("保存先が存在します。別名を指定してください。")
    rows, columns, source_meta, digest = read_source(source)
    counts = Counter(r["video_id"] for r in rows)
    ids = list(counts)
    added = ["view_count", "like_count", "comment_count", "duration", "detail_status", "statistics_fetched_at"]
    if set(added) & set(columns):
        raise ValueError("統計追加前の一覧CSVを入力してください。")
    started = utc_now()
    # All requests must succeed before any result CSV is written.
    stats = get_video_statistics(ids, api_key) if ids else {}
    finished = utc_now()
    for row in rows:
        found = stats.get(row["video_id"])
        row.update(found or {k: None for k in added[:4]})
        row["detail_status"] = "returned" if found is not None else "not_returned"
        row["statistics_fetched_at"] = finished
    output.parent.mkdir(parents=True, exist_ok=True)
    with output.open("x", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=columns + added)
        writer.writeheader()
        writer.writerows(rows)
    metadata = {"csv_file": output.name, "csv_sha256": hashlib.sha256(output.read_bytes()).hexdigest(),
        "source_file": str(source), "source_sha256": digest, "source_metadata": source_meta,
        "scope": source_meta.get("scope", "UNKNOWN"),
        "statistics_started_at": started, "statistics_finished_at": finished,
        "input_rows": len(rows), "unique_ids": len(ids), "saved_rows": len(rows),
        "duplicate_ids": {k: v for k, v in counts.items() if v > 1},
        "not_returned_ids": [key for key in ids if key not in stats],
        "note": "重複行は維持し、API要求のみIDを一意化。未応答の理由は未確認。"}
    meta_path.write_text(json.dumps(metadata, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"saved: {output.resolve()} / rows: {len(rows)} / unique IDs: {len(ids)}")
    return rows, metadata


if __name__ == "__main__":
    load_dotenv(Path(__file__).with_name(".env"))
    source = Path("outputs/youtube/channel_videos.csv")
    output = Path("outputs/youtube/channel_videos_with_statistics.csv")
    # Empty input needs no API key and makes no API request.
    key = os.getenv("YOUTUBE_API_KEY", "")
    try:
        rows, _, _, _ = read_source(source)
        if rows and not key:
            raise ValueError(".envのYOUTUBE_API_KEYを確認してください。")
        enrich(source, output, key)
    except (requests.RequestException, ValueError, OSError) as exc:
        # Request URLs can contain a key; do not print the exception text.
        raise SystemExit(f"保存未完了: {type(exc).__name__}。入力列・保存先・認証・通信を確認してください。") from None

detail_status=returnedでview_count=0なら実測0、returnedで統計欄が空なら項目欠損、not_returnedなら動画詳細自体が応答に含まれなかった状態です。未応答の理由を削除済み・非公開とは断定しません。重複IDはAPI要求だけ一意化し、元CSVの行と順序は保持します。metadataのduplicate_idsとnot_returned_idsで確認できます。

0行でも見出し付きCSVとmetadataを保存し、APIは呼びません。API要求が1つでも失敗すると結果CSVの書き込み前に停止します。ディスク書き込み失敗時はmetadataがない不完全な出力を成功扱いしないでください。既存の出力には上書きしません。CSVをpandasや表計算で開き、行数・detail_status・空欄と0を確認します。元一覧のmetadataがない場合は取得時点・範囲を不明のまま記録し、統計取得開始・終了と混同しません。

取得後にできる分析

  • 投稿日ごとの投稿本数を集計する
  • タイトルに含まれる単語を集計する
  • 再生数の多い動画と投稿時期を比較する
  • 動画時間と再生数の関係を見る
  • 特定シリーズや企画の投稿頻度を確認する

この段階では、まだ「動画一覧の取得」が目的です。分析記事に使う場合は、取得日、対象チャンネル、取得項目、欠損の有無をメモしておくと、後で読み返しやすくなります。

動画一覧を取得したあとに、通常動画・ライブ配信アーカイブ・Shortsなどの動画種別や、ゲーム・雑談・音楽などの独自カテゴリを付けたい場合は、YouTube APIで取得した動画一覧を分析用データセットに整える方法で整理しています。

よくあるつまずき

チャンネルIDがわからない

チャンネルURLがハンドル形式の場合は、channels.listのforHandleにハンドルを指定して、応答のチャンネルIDを確認できます。先頭の@は付けても省略しても構いません。取得したIDを、このページのプログラムで指定するチャンネルIDとして使います。ハンドルが分かっている場合は、キーワード検索で候補を探す必要はありません。

取得件数が想定より少ない

max_pages を指定している場合は、そのページ数で止まります。全件取得したい場合は max_pages=None にします。また、非公開動画や削除済み動画は通常の公開データとして取得できません。

クォータが不安

まず1ページだけ取得し、必要な項目が取れているか確認します。統計情報を追加する videos.list もコストは1ですが、不要な項目を何度も取り直さないように、CSVやJSONで途中保存しておくとよいです。

関連記事

まとめ

チャンネルの動画一覧を取得する場合は、channels.listuploads playlist IDを取得し、playlistItems.list でページングしながら動画一覧を取得する流れが扱いやすいです。

投稿動画一覧にはuploads playlist、キーワード・条件検索にはsearch.listを使い分けます。どちらも1回1ユニットですが、search.listは独立した標準1日100回の枠です。CSVを保存すれば、同じ一覧を取り直さずに投稿頻度やタイトル傾向を集計でき、必要な動画の統計情報を追加する手順にも進めます。

-YouTubeデータ取得, データ取得