LoginSignup
2
1

[GAS] Youtubeチャンネル内の動画情報を取得したいならSearch.listとVideos.listを使用する

Last updated at Posted at 2022-10-22

はじめに

本記事は執筆時時点の情報となります。2022年11月にハンドルが導入され、ハンドルURLはYouTubeチャンネルの表面上のデフォルトURLとなりました。しかしながら、YouTube Data API(Ver.3)では、返されるデータも含め、ハンドルを直接扱えるものはありません。(2023年3月現在)

その為現時点では、非公式なスクレイピング行為を除き、チャンネルIDを取得する方法として以下のものが考えられます。

  1. 当該チャンネルページのHTMLソースを直接確認する
  2. Search.listで対象をチャンネルに限定した上でハンドル名で検索する(ハンドル名で絞り込む検索演算子がないため完全一致の保証なし)

ハンドルの概要 - YouTube ヘルプ

YouTubeチャンネルに含まれる動画から、各種データを取得したいという相談がありました。相談者はWEBスクレイピングを考えていたようですが、公式のAPIがあるのでそれはお断りしました。同種の案件は受けることはないですが、APIを利用した公式のデータ取得に関してはやぶさかではありませんので備忘録として残しておきたいと思います。

なお、前提としてスクリプトを設置するプロジェクトにおいては、拡張サービスにてYouTube Data APIを追加する必要があります。追加、設定方法につきましては、拙記事ではありますがこちらを参考にしていただければと思います。1

目的

特定のYouTubeチャンネルに含まれる動画に関する情報を取得し、スプレッドシートで管理したい。
具体的には以下の項目を取得したい。

  • サムネイル画像
  • 動画タイトル
  • 動画の説明
  • アップロード日時
  • 動画再生回数
  • 高く評価された数
  • お気に入りの数
  • コメント数

低く評価された数は公式で自身管理の動画以外は取得できないように変更されました。
サンプルコードでは割愛していますが、取得項目としてご要望が多い動画URLは、動画IDを取得し、標準URLに成形します。

ロジックについて

以下のようなフローアウトラインとなります。

  1. Search.listでチャンネルIDによる指定チャンネル内の動画ID取得
  2. Videos.listで動画ごとの詳細情報を取得

補足
Channels.listからアップロード済み動画リストのIDを取得した後、PlaylistItems.listを経由しリスト内の動画IDを取得する、という方法を紹介したブログもありました。(当該ブログへのリンクは控えさせていただきます)

ただ、この方法はAPIリクエスト数、処理負荷、実行時間、1日あたりのQuotaの消費など諸々考慮するとおすすめできません。

チャンネルIDについて

特定のチャンネルということで、すでにチャンネルIDがわかっている前提となります。

お問い合わせ時によくあるのは、URLはわかっているけどチャンネルIDがわからないというものです。以下のような標準のURL形式なら、channel/以降のUCで始まる英数字をコピペ頂くか、数式等で抽出も可能です。

 https://youtube.com/channel/UC*******************

上記以外のカスタムURLの場合、当該チャンネルのHTMLソース内の記述部分を探して取得する必要があります。

スクレイピングでチャンネルIDを取得するツールもあるようですが、利用規約的にどうかと思いますので紹介はいたしません。

カスタムURLについては以下をご確認ください。

チャンネル URL について - YouTube ヘルプ

ロジック詳細

以下、ステップごとにサンプルコードを記述していますが、エラー処理等は割愛しています。

1-1. Search.listよりチャンネル内動画のID取得

データ総量削減のため、必須項目であるpartにはidのみを設定しています。また、初期状態では1リクエストあたり最大5件しか取得できませんので、50件まで取得するようにしています。

const channelPart  = 'id'; // id関連情報のみ取得する設定
const channelQuery = {
    channelId: channelId,
    type: 'video',
    maxResults: 50, // 最大取得件数の設定
    pageToken:''
};

動画数が50件超の場合、nextPageTokenを確認し、再帰処理を行うようにしています。ただし、チャンネルによっては1日あたりのQuotaを超えてしまう可能性があるため、別途繰り返し上限数を設定するか、取得期間の設定をするなどの対応が必要になります。

なお、当方で受注する際には初期状態で1チャンネルにつき500件までに制限していますが、運用状況や実行時間などの様子を見て、適宜上限数の調整をしていただいています。

1-2. 取得したIDリストをカンマ区切りテキスト化しておく

次の処理用に、取得した動画ID50件をtoString()により一括でカンマ区切りテキストにしています。また、ループ処理用として、最終的に一次元配列で返すようにしています。

つまり、下記getVideoIdsの戻り値videoIdsの1要素は最大50件分の動画IDのカンマ区切りテキストとなります。

動画IDリストの取得
const getVideoIds = (ids, res) => {
    // videoIdをカンマ区切りテキストにして一次元配列の要素としてセットする
    ids.push(res.items.flatMap(item => item.id.videoId).toString());
    let next = res.nextPageToken || false;

    // 再帰処理
    if(next){
        channelQuery.pageToken = next;
        getVideoIds(ids, YouTube.Search.list(channelPart, channelQuery));
    }
    return ids;
};

const videoIds = getVideoIds([], YouTube.Search.list(channelPart, channelQuery));

補足
Search.list及びVideos.listともに、query内で複数IDを指定する際はカンマ区切りテキストにする必要があります。指定できる最大値の明記はありませんが、やはり50件が上限となります。

ご指摘頂いた重複について、当方環境での現象確認はできませんでしたが、コメント欄に動作確認済みのサンプルコードを提示しておきます。必要に応じて該当部分を差し替えていただければと思います。

2-1. Videos.listpart設定

取得項目の内容から、必須項目であるpartにはsnippetstatisticsを設定する必要があります。idはなくても構いませんが、今回は含めておきます。

const videoPart = 'id,snippet,statistics'; // idはなくても良い

2-2. 動画ごと詳細データの取得

以下のgetVideoDataにて、取得する項目の順番や整形を行っています。それぞれ取得できなかった場合には「-」や「0」を返すようにしています。

サムネイル画像につきましては、標準画質のURLを取得し、IMAGE関数にてスプレッドシート側で処理する前提となります。必要に応じて低画質、高画質を選択できますし、URLのみにすることもできます。

動画ごと詳細データの取得
const getVideoData = item => [
    `=IMAGE("${item.snippet.thumbnails.default.url}",1)` || '-', // サムネイル画像
    item.snippet.title || '-', // 動画タイトル
    item.snippet.description || '-', // 動画の説明
    new Date(item.snippet.publishedAt) || '-', // アップロード日時
    item.statistics.viewCount || 0, // 動画再生回数
    item.statistics.likeCount || 0, // 高く評価された数
    item.statistics.favoriteCount || 0, // お気に入りの数
    item.statistics.commentCount || 0 // コメント数
];

2-3. スプレッドシートへの展開準備処理

以下のgetDescriptionsにて、スプレッドシートへの展開用に最終的に二次元配列で返すようにしています。

動画詳細データ取得後シート展開用に二次元配列として返す
const getDescriptions = ids => ids.flatMap(id => {
    let query = {id: id};
    let res   = YouTube.Videos.list(videoPart, query);
    return res.items.map(item => getVideoData(item));
});

参考ロジック

以下、全体像となります。ご参考まで。

Sample.gs
const channelId    = __ チャンネルID __;

/** Search.list
---------------------------------------- */
const channelPart  = 'id';
const channelQuery = {
    channelId: channelId,
    type: 'video',
    maxResults: 50,
    pageToken:''
};

// 動画IDリストの取得
const getVideoIds = (ids, res) => {
    // videoIdをカンマ区切りテキストにして一次元配列の要素としてセットする
    ids.push(res.items.flatMap(item => item.id.videoId).toString());
    let next = res.nextPageToken || false;

    // 再帰処理
    if(next){
        channelQuery.pageToken = next;
        getVideoIds(ids, YouTube.Search.list(channelPart, channelQuery));
    }
    return ids;
};

/** Videos.list
---------------------------------------- */
const videoPart = 'id,snippet,statistics'; // idはなくても良い

// 動画ごと詳細データの取得
const getVideoData = item => [
    `=IMAGE("${item.snippet.thumbnails.default.url}",1)` || '-', // サムネイル画像
    item.snippet.title || '-', // 動画タイトル
    item.snippet.description || '-', // 動画の説明
    new Date(item.snippet.publishedAt) || '-', // アップロード日時
    item.statistics.viewCount || 0, // 動画再生回数
    item.statistics.likeCount || 0, // 高く評価された数
    item.statistics.favoriteCount || 0, // お気に入りの数
    item.statistics.commentCount || 0 // コメント数
];

// 動画詳細データ取得後シート展開用に二次元配列として返す
const getDescriptions = ids => ids.flatMap(id => {
    let query = {id: id};
    let res   = YouTube.Videos.list(videoPart, query);
    return res.items.map(item => getVideoData(item));
});

/** Main Process
---------------------------------------- */
// 動画IDリストの取得
const videoIds = getVideoIds([], YouTube.Search.list(channelPart, channelQuery));

// チャンネル内の動画情報取得
const descriptions = getDescriptions(videoIds);

// スプレッドシートへ展開

/* 以降の処理は割愛 */

なお、descriptions取得後の処理に関しましては、一般的なシートへの展開処理となりますので、割愛しています。

補足

約1000件の動画詳細情報取得について動作検証した際、getDescriptionsではArray.flatMapを使うより、Array.forEachを使うほうが約3秒ほど早く終了しました。以下が参考コードです。

const getDescriptions = ids =>  {
    const descriptions = [];

    ids.forEach(id => {
        let query = {id: id};
        let res   = YouTube.Videos.list(videoPart, query);
        res.items.forEach(item => descriptions.push(getVideoData(item)));
    });

    return descriptions;
};

最後に

シートへの展開時、データの並び順はデフォルトで検索クエリの関連性が高い順となっています。データの並び替えはシート上でもできますが、Search.listqueryorderプロパティを追加し、任意の並び順にすることもできます。

コードボリュームも多くないので、その他色々ご要望に合わせたカスタマイズも容易かと思います。

誰かの役に立てば幸いです。

[参考・引用]

  1. Magicodeから記事移動後、若干の文言修正をしています。

2
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1