YouTubeAPIで特定のチャンネルの動画情報を取得する

## ほぼ自分用メモ
とあるYouTuberさんの動画一覧と高評価数、コメント数を取得してスプレッドシートに書き出したい。
と思い立ち、あれこれ試行錯誤した記録用として残します。
結果として、チャンネルIDがわかってから拍子抜けするほど簡単にできました・・・。
調べれば便利ですぐ使えるものが出てくるインターネット素敵。

## YouTubeAPIの使い方
設定方法は以下のリンク先を参考にしました。
実行回数の限度を超えると有料となるらしいので定時実行などは設定に要注意。

https://matsukiyoshi.com/gas-youtube/

https://zenn.dev/sungvalley/articles/ccce17e076984f

https://yasulab-pg.com/%E3%80%90youtube-api%E3%80%91api-key%E7%99%BA%E8%A1%8C%E3%81%BE%E3%81%A7/

## 対象のYouTubeチャンネルのチャンネルIDを取得
YouTubeチャンネルIDはチャンネルURLから取得できます。
壱百満天原サロメ様（お嬢様を目指しているゲーム実況系VTuber）のチャンネルURLを例に説明します。
サロメ様のチャンネルのURLは以下のとおり。
`https://www.youtube.com/channel/UCgIfLpQvelloDi8I0Ycbwpg`
URLの`channel/`の後ろにあるのがチャンネルIDになります。
なので、サロメ様のチャンネルIDは`UCgIfLpQvelloDi8I0Ycbwpg`となります。

ところが、YouTuberの中にはカスタムURLを使っているチャンネルもあります。
HikakinTV様を例にあげてみます。
HikakinTV様のチャンネルURLは以下のとおり。
`https://www.youtube.com/user/HikakinTV`
カスタムURLを使っているためチャンネルIDがわかりません。
（私はここで詰みました……。）

カスタムURLを使用しているチャンネルのチャンネルIDを取得した方法は以下の通り。
他にも方法はあるかもしれませんが、自分はこの方法で取得しました。

①HikakinTVをGoogle検索する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122198/48a4742c-0db4-27b7-7015-d827c7d277ba.png)
②検索結果からYouTubeを開く。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122198/ad25a0e9-e8a4-885d-23a4-a6f6de71e3a2.png)
③URLを確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122198/787b6b18-15f1-faa6-ad52-bbcf1873f76a.png)

ということで、HikakinTV様のチャンネルIDは`UCZf__ehlCEBPop-_sldpBUQ`となります。
YouTubeアプリ上ではカスタムURLが表示されていましたが、この方法で確認するとチャンネルIDが取得できました。
一歩前進！

## チャンネルIDが分かるURLで動画一覧を取得
以下のリンク先で公開されているスプレッドシートを自分のドライブにコピーして保存。
動画情報を取得したいチャンネルのURL **（チャンネルIDが分かる状態のURL）** を指定のセルに記載して実行する。
本当にありがとうございます。本当にありがとうございます。本当にありがとうございます。
一からプログラム書かなくていい素晴らしき世界。

https://givemegohan.xyz/2020/11/22/youtube-data-api%E3%82%92google-apps-scriptgas%E3%81%8B%E3%82%89%E4%BD%BF%E3%81%8A%E3%81%86/

## 時間の表示形式を変換する
上記リンク先のスプレッドシートを実行すると動画の再生時間（動画の長さ）が「ISO8601」と言う形式で出力されます。
これはYouTubeAPIの仕様のようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122198/2408848e-388b-0662-f653-9210d8b1bb67.png)
ということで、HH:MM:SSの形式に変換する仕組みを作りました。

以下記事の`durationのコンバート処理`部分をまるっといただきました。
本当にありがとうございます。本当にありがとうございます。本当にありがとうございます。

https://qiita.com/YMori0811/items/ee592aca5ad26b9cf988

ただ、このままだと処理の中で取得した再生時間を変換するfunctionになっているので、
スプレッドシートに一度出力した結果を再度変換する仕組みに変えました。
（functionの呼び出しを組み込んで動かなくなるのが怖かっただけです……。）

```javascript
function convertDurationTime() {
  var ss = SpreadsheetApp.openByUrl('スプレッドシートのURL'); // スプレッドシート
  var sheet = ss.getSheetByName("動画テスト"); // 1シート目の名前
  var gettime = '';
  const lastRow = sheet.getLastRow(); // データの入っている最終行

// データが入っているのは7行目～
  for(let i = 7; i <= lastRow; i++) {
    gettime = sheet.getRange(i,3).getValue(); // 動画時間
    var reg = new RegExp('^PT([0-9]*H)?([0-9]*M)?([0-9]*S)?');
    var regResult = gettime.match(reg);

    var hour = regResult[1];
    var minutes = regResult[2];
    var sec = regResult[3];

    if(hour == undefined) {hour = '00';}
    else {
      hour = hour.split('H')[0];
      if(hour.length == 1){hour = '0' + hour;}
    }

    if(minutes == undefined) {minutes = '00';}
    else {
      minutes = minutes.split('M')[0];
      if(minutes.length == 1){minutes = '0' + minutes;}
    }

    if(sec == undefined) {sec = '00';}
    else {
      sec = sec.split('S')[0];
      if(sec.length == 1){sec = '0' + sec;}
    }

    gettime = hour + ":" + minutes + ":" + sec;
    sheet.getRange(i,3).setValue(gettime);
    }  
}
```

プログラムを書いたあと、`convertDurationTime`だけを実行して変換完了。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122198/da6a788a-2339-8a0a-a1d5-c240ea87f190.png)
動画再生時間がゼロの場合（ライブ配信の予定を取得したときなどに発生）にエラーとなります。
エラー回避はいれていないので、不要な行は削除してから実行するといいでしょう。

## 低評価数は取得できないので代わりにコメント数を取得する
元のプログラムでは低評価数`dislikeCount`を指定して、低評価数を取得していましたが、
YouTubeでは低評価数は本人以外には非表示となったためYouTubeAPIでも取得不可となりました。
なので、コメント数を代わりに取得する内容に変更しました。
`dislikeCount`としているところを`commentCount`に変更しています。
変数名は変えなくてもいいですが、分かりづらくなるので合わせて変更しました。
全体プログラムは折りたたんでおきます。

## 動画のURLも追加
タイトルから動画を探してもいいですが、せっかくなら動画URLも取得して直接動画へとべるようにしました。
どんどん便利になっていく……！
URLをまんま取得するものはないので、固定のURL`https://www.youtube.com/watch?v=`と動画IDを組み合わせてリンクにし一番左の列に追加しました。
コードは下記の折りたたみセクションを参照してください。
以下記事を参考にしました。

https://qiita.com/h0ng0yut0/items/02a18f3097bc19b2bfc5

<details><summary>変更後のコード</summary>

```javascript
function exec() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName('動画テスト'); // シート名
  const channelURL = sheet.getRange("A2");
  
  const channelId = channelURL.getDisplayValue().match(/channel\/.*$/g)[0].replace('channel/','');
  if (channelId.length <= 0) {
    return;
  }
  console.log(channelId);
  
  const playlistId = getUploadPlaylistId(channelId);
  if (playlistId.length <= 0) {
    return;
  }
  console.log(playlistId);
  
  const videos = getVideos(playlistId);
  if (videos.length <= 0) {
    return;
  }
  console.log(videos);
  
  const stats = getVideoInfo(videos);
  if (stats.length <= 0) {
    return;
  }
  console.log(stats);
  
  const beforelastrow = sheet.getLastRow();
  if (beforelastrow >= 7) {
    sheet.deleteRows(7, beforelastrow - 6);
  }
  
  let results = [];
  for (let i = 0; i < stats.length; i++) {
    results.push([
      stats[i].title,
      stats[i].publishedAt,
      stats[i].duration,
      stats[i].viewCount,
      stats[i].likeCount,
      stats[i].commentCount,
      stats[i].likeCount / stats[i].viewCount,
      stats[i].videoUrl
    ]);
  }
  sheet.getRange(7, 1, results.length, 8).setValues(results);
  
  return;
}

function getUploadPlaylistId(channelId) {
  return YouTube.Channels.list("contentDetails", {id: channelId}).items[0].contentDetails.relatedPlaylists.uploads;
}

function getVideos(playlistId, maxNum = 1000) {
  let results = [];
  let cnt = 0;
  let nextPageToken = '';
  let maxResults = Math.min(50, maxNum);
  apiloop : while (1) {
    const apiResult = YouTube.PlaylistItems.list("contentDetails", {playlistId : playlistId, maxResults : maxResults, pageToken : nextPageToken});
    const totalResults = apiResult.pageInfo.totalResults;
    nextPageToken = apiResult.nextPageToken;
    
    for (let i = 0; i < apiResult.items.length; i++) {
      results.push(apiResult.items[i].contentDetails.videoId);
      cnt++;
      if (cnt >= maxNum || cnt >= totalResults) {
        break apiloop;
      }
    }
  }
  return results;
}

function getVideoInfo(videoIds) {
  let results = [];
  for (let idSlice = 0; idSlice < videoIds.length / 50; idSlice++) {
    const videoIdStr = videoIds.slice(idSlice * 50,idSlice * 50 + 50).join(',');
    const apiResult = YouTube.Videos.list("contentDetails,statistics,snippet", {id : videoIdStr});
    for (let i = 0; i < apiResult.items.length; i++) {
      let item = apiResult.items[i];
      results.push({
        title : item.snippet.title,
        publishedAt : item.snippet.publishedAt,
        duration : item.contentDetails.duration,
        viewCount : item.statistics.viewCount,
        likeCount : item.statistics.likeCount,
        commentCount : item.statistics.commentCount,
        videoUrl : 'https://www.youtube.com/watch?v=' + item.id
      });
    }
  }
  return results;
}

function convertDurationTime() {
  var ss = SpreadsheetApp.openByUrl('スプレッドシートURL'); // スプレッドシート
  var sheet = ss.getSheetByName("動画テスト"); // 1シート目の名前
  var gettime = '';
  const lastRow = sheet.getLastRow(); // データの入っている最終行

  for(let i = 7; i <= lastRow; i++) {
    gettime = sheet.getRange(i,3).getValue(); // 動画時間
    var reg = new RegExp('^PT([0-9]*H)?([0-9]*M)?([0-9]*S)?');
    var regResult = gettime.match(reg);

    var hour = regResult[1];
    var minutes = regResult[2];
    var sec = regResult[3];

    if(hour == undefined) {hour = '00';}
    else {
      hour = hour.split('H')[0];
      if(hour.length == 1){hour = '0' + hour;}
    }

    if(minutes == undefined) {minutes = '00';}
    else {
      minutes = minutes.split('M')[0];
      if(minutes.length == 1){minutes = '0' + minutes;}
    }

    if(sec == undefined) {sec = '00';}
    else {
      sec = sec.split('S')[0];
      if(sec.length == 1){sec = '0' + sec;}
    }

    gettime = hour + ":" + minutes + ":" + sec;
    sheet.getRange(i,3).setValue(gettime);
    }  
}
```
</details>

## できたもの
動画情報を取得し、再生時間を変換した結果は以下の画像のとおりです。
取得したYouTuberさんは、フロリダ在住の日本の方でウォルトディズニーワールドの情報を発信してくれている「[すちわーとinディズニーワールド]()」さんです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122198/fafb26b5-2b65-4b4d-69b1-b4ec61197dd4.png)


いじった箇所は数箇所。これで欲しいもの作れるんですから優しいインターネットの世界。
そういえばAPIKeyをコードに記載していないけどどうなってるんだろう……。
まぁ、動いているからいいか……。