0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【GAS】YouTubeアナリティクスを使ってトラフィックソースを取得してスプレッドシートに出力する

Posted at

はじめに

GASでYouTubeアナリティクスを使ってトラフィックソースを取得してスプレッドシートに出力する。
この記事ではYouTube Analytics APIについて解説していきます。
自分のチャンネルで投稿しているすべての動画に対して特定の期間の動画情報を取得する方法、動画がショート動画かを一括判定する方法は以下の記事にまとめています。

目的

自分のチャンネルで投稿している動画のトラフィックソースを取得してスプレッドシートで管理したい。
無題4.jpg
無題5.jpg

コーディング

まずは実際のコードをご覧ください。
エラー処理等は割愛しています。
前回の記事とおおまかに違う部分のみ以下の項目で解説していきます。

/**
 * チャンネルが投稿したすべての動画から必要な情報をスプシに出力
 */
function getAndOutputChannelInfo() {
  // チャンネルが投稿した動画のプレイリストIDを取得
  const channel = YouTube.Channels.list(
    'contentDetails', {
    mine: true
  });

  const {contentDetails} = channel.items[0];
  // プレイリストID
  const channelId = contentDetails.relatedPlaylists.uploads;

  // チャンネル内の全ての動画のIDを取得する
  const videoIds = getVideoIds(channelId);

  // creatorContentType(動画の種類)を取得
  const contentTypeList = YouTubeAnalytics.Reports.query({
    ids: 'channel==MINE',
    dimensions: 'video,creatorContentType',
    startDate: new Date(Date.now() - 365*24*60*60*1000).toLocaleDateString('sv-SE'), // 365日前の日付
    endDate: new Date().toLocaleDateString('sv-SE'), // 今日の日付
    maxResults: 25,
    filters: `video==${videoIds.join(',')}`,
    metrics: 'views',
    sort: '-views'
  });

  // creatorContentTypeからショート動画か判別
  const shortVideoIds = [];
  const videoOnDemandIds = [];
  for (let contentType of contentTypeList.rows) {
    if (contentType[1] && contentType[1] === 'shorts') {
      shortVideoIds.push(contentType[0]);
    } else if (contentType[1] && contentType[1] === 'videoOnDemand') {
      videoOnDemandIds.push(contentType[0]);
    }
  }

  // 動画のトラフィックソースを取得
  const trafficSources = getTrafficSources(sliceByNumber(videoOnDemandIds, 50));

  // ショート動画のトラフィックソースを取得
  const shortTrafficSources = getTrafficSources(sliceByNumber(shortVideoIds, 50));

  // 動画の情報を取得し必要なデータを抽出
  const outputVideosData = createOutputData(videoOnDemandIds, trafficSources);

  // ショート動画の情報を取得し必要なデータを抽出
  const outputShortVideosData = createOutputData(shortVideoIds, shortTrafficSources);

  // スプシに出力
  const spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
  const videoSheet = spreadSheet.getSheetByName('動画');
  const shortVideoSheet = spreadSheet.getSheetByName('ショート動画');
  exportSpreadsheet(videoSheet, outputVideosData);
  exportSpreadsheet(shortVideoSheet, outputShortVideosData);
}

/**
 * チャンネル内の全ての動画のIDを取得する
 */
function getVideoIds(id, pageToken) {
  const playlistItemsInfo = YouTube.PlaylistItems.list([
    'contentDetails'
  ], {
    'playlistId': id,
    'maxResults': 50,
    pageToken,
  });

  let videoIds = playlistItemsInfo.items.map(item => item.contentDetails.videoId);
  const nextPageToken = playlistItemsInfo.nextPageToken;

  if (nextPageToken) {
    videoIds = [...videoIds, ...getVideoIds(id, nextPageToken)]
  }

  return videoIds;
}

/**
 * トラフィックソースを取得する
 */
function getTrafficSources(ids) {
  const trafficSourceInfos = YouTubeAnalytics.Reports.query({
      ids: 'channel==MINE',
      dimensions: 'video,insightTrafficSourceType',
      startDate: new Date(Date.now() - 365*24*60*60*1000).toLocaleDateString('sv-SE'), // 365日前の日付
      endDate: new Date().toLocaleDateString('sv-SE'), // 今日の日付
      maxResults: 25,
      filters: `video==${ids.join(',')}`,
      metrics: 'views',
      sort: '-video'
    });

    // 配列をJSON形式に置換
    const trafficSources = {};
    for (const [id, key, value] of trafficSourceInfos.rows) {
      if (!trafficSources[id]) {
        trafficSources[id] = {};
      }
      trafficSources[id][key] = value;
    }
  return trafficSources;
}

/**
 * 動画の詳細を取得する
 */
function getVideos(ids) {
  let videos = [];

  for(let i = 0, l = ids.length; i < l; i++) {
    const videoInfo = YouTube.Videos.list([
      'snippet',
      'statistics',
    ], {
      'id': ids[i].join(','),
    });

    videos = [...videos, ...videoInfo.items];
  }

  return videos;
}

/**
 * 第1引数のarrayを第2引数指定したnumberで区切って返す
 */
function sliceByNumber(array, number) {
  const length = Math.ceil(array.length / number);
  return new Array(length).fill().map((_, i) =>
    array.slice(i * number, (i + 1) * number)
  )
}

/**
 * スプシ出力用のデータを作成
 */
function createOutputData(ids, trafficSources) {
  // 詳細データを取得
  const videos = getVideos(sliceByNumber(ids, 50));
  // 詳細データを追加
  const videoInfos = [];
  videos.map((item) => {
    videoInfos.push([
      item.id, // 動画ID
      item.snippet.title, // 動画タイトル
      Utilities.formatDate(new Date(item.snippet.publishedAt), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm'), // 投稿日
      item.statistics.viewCount || 0, // 再生回数
      item.statistics.likeCount || 0, // 高評価の数
      item.statistics.commentCount || 0, // コメントの数
      `https://www.youtube.com/watch?v=${item.id}` // 動画URL
    ])
  });

  // トラフィックソースを追加
  const outputData = videoInfos.map((row) => {
    const id = row[0];
    if (trafficSources[id]) {
      // JSONのデータを配列に展開して追加
      return [
        ...row,
        trafficSources[id].ADVERTISING || 0, // YouTube広告
        trafficSources[id].ANNOTATION || 0, // アノテーション
        trafficSources[id].CAMPAIGN_CARD || 0, // キャンペーンカード
        trafficSources[id].END_SCREEN || 0, // 終了画面
        trafficSources[id].EXT_URL || 0, // ウェブサイトのリンク
        trafficSources[id].HASHTAGS || 0, // ハッシュタグのページ
        trafficSources[id].LIVE_REDIRECT || 0, // ライブリダイレクト
        trafficSources[id].NO_LINK_EMBEDDED || 0, // ウェブサイトの埋め込み
        trafficSources[id].NO_LINK_OTHER || 0, // 直接入力または不明
        trafficSources[id].NOTIFICATION || 0, // 通知
        trafficSources[id].PLAYLIST || 0, // 再生リスト
        trafficSources[id].PRODUCT_PAGE || 0, // 商品ページ
        trafficSources[id].PROMOTED || 0, // プロモーション
        trafficSources[id].RELATED_VIDEO || 0, // 関連動画
        trafficSources[id].SHORTS || 0, // ショートフィード
        trafficSources[id].SOUND_PAGE || 0, // 音声のページ
        trafficSources[id].SUBSCRIBER || 0, // ブラウジング機能
        trafficSources[id].YT_CHANNEL || 0, // チャンネルページ
        trafficSources[id].YT_OTHER_PAGE || 0, // その他のYouTube機能
        trafficSources[id].YT_SEARCH || 0, // YouTube検索
        trafficSources[id].VIDEO_REMIXES || 0 // リミックス動画
      ];
    }
    return row;
  });
  return outputData;
}

/**
 * スプシに出力
 */
function exportSpreadsheet(sheet, outputData) {
  sheet.getRange(1, 1, 1, 28).setValues([['動画id', 'タイトル', '投稿日', '再生回数', '高評価', 'コメント', '動画URL', 'YouTube広告', 'アノテーション', 'キャンペーンカード',
  '終了画面', 'ウェブサイトのリンク', 'ハッシュタグのページ', 'ライブリダイレクト', 'ウェブサイトの埋め込み', '直接入力または不明', '通知', '再生リスト', '商品ページ', 'プロモーション',
  '関連動画', 'ショートフィード', '音声のページ', 'ブラウジング機能', 'チャンネルページ', 'その他のYouTube機能', 'YouTube検索', 'リミックス動画']]);
  sheet.getRange(2, 1, outputData.length, 28).setValues(outputData);
}

トラフィックソースの取得

取得項目の内容からdimensionsにはvideoinsightTrafficSourceTypeを設定しています。
startDateendDateは必須項目ですが本記事では期間を一年以内に投稿した動画を対象にしています。
お好きな期間に変更してください。
YouTube Analytics APIの詳細は以下のリファレンスをご覧ください。

function getTrafficSources(ids) {
  const trafficSourceInfos = YouTubeAnalytics.Reports.query({
      ids: 'channel==MINE',
      dimensions: 'video,insightTrafficSourceType',
      startDate: new Date(Date.now() - 365*24*60*60*1000).toLocaleDateString('sv-SE'), // 365日前の日付
      endDate: new Date().toLocaleDateString('sv-SE'), // 今日の日付
      maxResults: 25,
      filters: `video==${ids.join(',')}`,
      metrics: 'views',
      sort: '-video'
    });

    // 配列をJSON形式に置換
    const trafficSources = {};
    for (const [id, key, value] of trafficSourceInfos.rows) {
      if (!trafficSources[id]) {
        trafficSources[id] = {};
      }
      trafficSources[id][key] = value;
    }
  return trafficSources;
}

スプシ出力用のデータを作成

スプシに出力するためトラフィックソースの項目を取得しています。
全てのトラフィックソースのタイプや、その他YouTube Studioで見られるデバイス、地域やユーザー情報等あらゆる項目についての詳細は以下のリファレンスをご覧ください。

// 同じ動画のトラフィックソースデータを追加
const outputData = videoInfos.map((row) => {
  const id = row[0];
  if (trafficSources[id]) {
    // JSONのデータを配列に展開して追加
    return [
      ...row,
      trafficSources[id].ADVERTISING || 0, // YouTube広告
      trafficSources[id].ANNOTATION || 0, // アノテーション
      trafficSources[id].CAMPAIGN_CARD || 0, // キャンペーンカード
      trafficSources[id].END_SCREEN || 0, // 終了画面
      trafficSources[id].EXT_URL || 0, // ウェブサイトのリンク
      trafficSources[id].HASHTAGS || 0, // ハッシュタグのページ
      trafficSources[id].LIVE_REDIRECT || 0, // ライブリダイレクト
      trafficSources[id].NO_LINK_EMBEDDED || 0, // ウェブサイトの埋め込み
      trafficSources[id].NO_LINK_OTHER || 0, // 直接入力または不明
      trafficSources[id].NOTIFICATION || 0, // 通知
      trafficSources[id].PLAYLIST || 0, // 再生リスト
      trafficSources[id].PRODUCT_PAGE || 0, // 商品ページ
      trafficSources[id].PROMOTED || 0, // プロモーション
      trafficSources[id].RELATED_VIDEO || 0, // 関連動画
      trafficSources[id].SHORTS || 0, // ショートフィード
      trafficSources[id].SOUND_PAGE || 0, // 音声のページ
      trafficSources[id].SUBSCRIBER || 0, // ブラウジング機能
      trafficSources[id].YT_CHANNEL || 0, // チャンネルページ
      trafficSources[id].YT_OTHER_PAGE || 0, // その他のYouTube機能
      trafficSources[id].YT_SEARCH || 0, // YouTube検索
      trafficSources[id].VIDEO_REMIXES || 0 // リミックス動画
    ];
  }
  return row;
});

最後に

細かい解説は省いていますが、ソースに必要なコメントは残しているので、順を追って理解することができるかと思います。
動画の詳細データとトラフィックソースデータを合わせる処理は少し頭を捻りました。

誰かのお役に立てると幸いです。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?