MCC配下のアカウントサマリーレポート

更新履歴

2024/02/26 スクリプトをV202402に対応
2024/07/10 スクリプトをV202406版に対応、機能強化
       コピーボタン追加などのレイアウト変更
2024/07/24 目次を追加
2024/10/17 スクリプトにヘッダー行の表示有無機能を追加
2024/11/18 ■レポート出力項目 の記述を最新版に修正しました

スクリプトの概要

Yahoo!広告スクリプトでは、Googleスプレッドシートに各種レポートを直接出力することが可能です。
本スクリプトは、MCCアカウントに設定することを想定したもので、MCC配下の各アカウントの主要な指標の月単位のレポートをGoogleスプレッドシートに出力します。
初期値では、検索広告とディスプレイ広告両方のレポートを出力する設定となっています。※後述のレポート出力対象のプロダクト種別(定数「USER_SPECIFIED_OUTPUT_TYPE」で指定)でそれぞれの指定も可能です。
集計する月単位の期間は以下の3つより選択可能です。ただし、月初に「今月(今日を含まない)」で実行された場合は先月分のレポートを出力します。
・今月(今日を含まない)
・先月
・先々月

ご注意

このスクリプトでは、配信設定がオンになっているアカウントの実績のみが出力されます。

必要な設定

1.Yahoo!広告スクリプトとGoogleアカウントを連携します
 詳しくはGoogleアカウントとの連携を確認してください。
 ※すでに連携済みの方は次のステップからご設定ください。

2.スプレッドシートIDを取得します
 詳しくはGoogleスプレッドシートIDの取得方法をご覧ください。

ご注意

Yahoo!広告スクリプトの実行処理時間には10分以内と言う制限があるため、MCCに紐づくアカウント数が多すぎる場合はレポート出力処理が完了せずに実行が中断されてしまいます(タイムアウト)。これにより、MCC配下の全てのアカウントのレポート出力が完了しない場合は、以下の対策をお客様側で行っていただき、タイムアウトを回避していただくようお願いいたします。
・MCCを分ける
・後述のレポート出力対象のプロダクト種別(定数「USER_SPECIFIED_OUTPUT_TYPE」で指定)を分ける
・コードをカスタマイズする

3.管理画面上のスクリプト作成画面にて、後述のサンプルコードを設定してください。
 ※スクリプトの新規作成および編集の手順はこちらをご覧ください。

4.管理画面上にてスクリプトの設定を完了後、スクリプトの実行頻度の設定をしてください。
 ※スクリプトの実行頻度の設定手順はこちらをご覧ください。

サンプルコード内各定数のご説明

後述のサンプルコードにおける各定数の設定方法についてご説明いたします。内容にあわせて変更を行ってください。

■スプレッドシートID、シート名

「必要な設定」で準備したスプレッドシートIDとシート名を指定します。

例)スプレッドシートID:'12345abcde'、シート名:'Sheet1'の場合
const SPREAD_SHEET_ID = '12345abcde'; //★書き出すスプレッドシートID。例:'SPREAD_SHEET_ID'
const SHEET_NAME = 'Sheet1';//★スプレッドシートのシート名。例:'シート1'

■レポート取得期間

何か月前の分のレポートを取得するか、0-2の整数で指定してください。今月分(昨日まで)なら0。先月分なら1、先々月分なら2を指定します。
※0の指定時、スクリプト起動した日が1日のときは先月分のレポートを取得し、1日以外のときは、今月分のレポートを取得します。

■レポート出力項目

本スクリプトでは、デフォルトで以下の項目をレポートに出力します。
・アカウントID
・アカウント名
・インプレッション数
・クリック数
・クリック率
・コスト
・平均クリック単価
・コンバージョン数
・コンバージョン率
・コンバージョン単価
・インプレッションシェア

この中で不要な項目がある場合、定数 REPORT_FIELDS の配列内から削除してください。
const REPORT_FIELDS = [
  'ACCOUNT_ID', //アカウントID
  'ACCOUNT_NAME', //アカウント名
  'IMPS', //インプレッション数
  'CLICKS', //クリック数
  'CLICK_RATE', //クリック率
  'COST', //コスト
  'AVG_CPC', //平均クリック単価
  'CONVERSIONS', //コンバージョン数
  'CONV_RATE', //コンバージョン率
  'COST_PER_CONV', //コンバージョン単価
  'IMPRESSION_SHARE', //インプレッションシェア
];

検索広告の場合

加えたい項目がある場合は、以下のCSVをご参照いただき、上記配列に対象の項目を追加してください。
https://ads-developers.yahoo.co.jp/reference/ads-search-api/v14/ReportDefinitionService/reports/v14/ACCOUNT.csv

ディスプレイ広告の場合

加えたい項目がある場合は以下のCSVをご参照ください。
https://ads-developers.yahoo.co.jp/reference/ads-display-api/v14/ReportDefinitionService/reports/v14/AD.csv

■MCC複数階層の場合の孫アカウント取得有無

MCC複数階層の場合、設定されたMCCアカウントの直下のアカウントだけでなく、複数階層化された一番下のアカウントまで取得するかどうかを指定してください。
trueの場合、複数階層化されたすべてのアカウントを取得します。ルートMCCアカウントに設定すると、アカウント配下の全てのアカウント(アカウント招待でリンクされているアカウントを除く)のレポートを取得します。
falseの場合、設定されたMCCアカウント直下のアカウントのみ取得します。
※初期値はtrueとなっています。MCC複数階層がない場合は初期値のままで問題ありません。

■レポート出力対象のプロダクト種別

レポート出力対象のプロダクト種別を指定してください。
BOTH の場合、検索広告アカウントとディスプレイ広告アカウントの両方のレポートを出力します。
SEARCH の場合は検索広告アカウントのみ、DISPLAY の場合はディスプレイ広告アカウントのみ出力します。
※初期値は BOTH となっています。

例)検索広告アカウントのみ出力する場合
const USER_SPECIFIED_OUTPUT_TYPE = 'SEARCH';// BOTH or SEARCH or DISPLAY

■ヘッダー行の有無

ヘッダー行を表示するかを指定してください。
TRUE の場合、ヘッダー行を表示しません。
FALSE の場合、ヘッダー行を表示します。
※初期値は TRUE となっています。

例)ヘッダー行を表示する場合
const HEADER = 'FALSE';

サンプルコード

下記のスクリプトを、「コピー」ボタンを押してコピーし、スクリプトの入力画面に貼り付けてください。(このとき、灰色のコメント部分は消さずに残しておいてください)
「サンプルコード内各定数のご説明」またはスクリプト内に記載の利用方法に沿って、設定が必要な定数を設定してください。

/*
■スクリプト内容
MCCに紐づくアカウントに対して、「今日を含まない今月(実行日が1日なら前月)分」、「先月分」「先々月分」のいずれかのレポートをスプレッドシートに出力します。
■利用方法
1.当スクリプトを、ルートMCCアカウントまたはMCCアカウントのスクリプトとしてください。
2.各定数を以下のように設定してください。
・SPREAD_SHEET_ID:スプレッドシートID
・SPREAD_SHEET_NAME:シート名
・MONTHS_AGO:何か月前の分のレポート取得するか、0-2の整数で指定。今月分(昨日まで)なら0。先月分なら1、先々月分なら2。
(0の指定時、スクリプト起動した日が1日のときは先月分のレポートを取得し、1日以外のときは、今月分のレポートを取得します)
・REPORT_FIELDS:レポート出力するフィールド名を配列で記載。(フィールド名は以下を確認してください)
 検索広告:https://ads-developers.yahoo.co.jp/reference/ads-search-api/v13/ReportDefinitionService/reports/v13/ACCOUNT.csv
 ディスプレイ広告:https://ads-developers.yahoo.co.jp/reference/ads-display-api/v13/ReportDefinitionService/reports/v13/AD.csv
・INCLUDE_MULTILEVEL_MCC:MCC複数階層の場合、設定されたMCCアカウントの直下のアカウントだけでなく、複数階層化された一番下のアカウントまで取得するかどうか
                         trueの場合、複数階層化されたすべてのアカウントを取得します。
                         falseの場合、設定されたMCCアカウント直下のアカウントのみ取得します。
・USER_SPECIFIED_OUTPUT_TYPE:レポート出力対象のプロダクト種別を指定してください。
                      'BOTH':検索アカウント、ディスプレイアカウント両方、'SEARCH':検索アカウントのみ、'DISPLAY':ディスプレイアカウントのみ、
・HEADER:ヘッダー行を表示するかを指定してください。TRUE:表示しません、FALSE:表示します
■制限事項
このスクリプトでは、配信設定がオンになっているアカウントの実績のみが出力されます。
*/
//設定が必要な定数
const SPREAD_SHEET_ID = 'スプレッドシートID';
const SHEET_NAME = 'シート名';
let MONTHS_AGO = 0;
const REPORT_FIELDS = [
  'ACCOUNT_ID', //アカウントID
  'ACCOUNT_NAME', //アカウント名
  'IMPS', //インプレッション数
  'CLICKS', //クリック数
  'CLICK_RATE', //クリック率
  'COST', //コスト
  'AVG_CPC', //平均クリック単価
  'CONVERSIONS', //コンバージョン数
  'CONV_RATE', //コンバージョン率
  'COST_PER_CONV', //コンバージョン単価
  'IMPRESSION_SHARE', //インプレッションシェア
];
const INCLUDE_MULTILEVEL_MCC = true;
const USER_SPECIFIED_OUTPUT_TYPE = 'BOTH';// BOTH or SEARCH or DISPLAY
const HEADER = 'TRUE';// TRUE or FALSE
//設定が不要な定数(変更するとエラーになります)
const thisMccAccountId = AdsUtilities.getCurrentAccountId();
const PRODUCT_TYPE = {
  Search: Search,
  Display: Display
};
const OUTPUT_TYPE = {
  Search: 'SEARCH',
  Display: 'DISPLAY',
  Both: 'BOTH'
}
const ACCOUNT_TYPE = {
  MCC: 'MCC',
  ADS: '通常'
}
let headerFlg = false;
function main() {
  Logger.log('開始');
  if (MONTHS_AGO < 0 || MONTHS_AGO > 2 || Number.isInteger(MONTHS_AGO) == false) {
    throw new Error('MONTHS_AGO を、0-2の整数で指定してください');
  }
  if (!Object.values(OUTPUT_TYPE).includes(USER_SPECIFIED_OUTPUT_TYPE)) {
    throw new Error('USER_SPECIFIED_OUTPUT_TYPE には、BOTH または SEARCH または DISPLAY の値を指定してください');
  }
  writeToSpreadsheet();
}
//YSAレポートをスプレッドシートに書き込み
function writeToSpreadsheet() {
  let ss = validateAndGetSpreadsheet();
  let sh = validateAndGetSheet(ss);
  sh.clear();//前処理でシートクリア
  //レポート用の開始日と終了日を取得するのは1度で十分
  let dateString = getDateString();
  //MCC内のYSA用のaccountIdsを取得する
  if (USER_SPECIFIED_OUTPUT_TYPE == OUTPUT_TYPE.Search || USER_SPECIFIED_OUTPUT_TYPE == OUTPUT_TYPE.Both) {
    getReportAndOutput(sh, OUTPUT_TYPE.Search, PRODUCT_TYPE.Search, dateString);
  }
  //MCC内のYDA用のaccountIdsを取得する
  if (USER_SPECIFIED_OUTPUT_TYPE == OUTPUT_TYPE.Display || USER_SPECIFIED_OUTPUT_TYPE == OUTPUT_TYPE.Both) {
    getReportAndOutput(sh, OUTPUT_TYPE.Display, PRODUCT_TYPE.Display, dateString);
  }
}
function getReportAndOutput(sh, adType, productType, dateString) {
  let adAccountIds = getTargetAccountIds(productType, adType);
  if (adAccountIds.length > 0) {
    let reportData = getReport(adAccountIds, productType, dateString);
    Logger.log('getReport完了');
    if (reportData.length > 0) {
      let startRow = (adType === OUTPUT_TYPE.Search) ? 1 : sh.getLastRow() + 1;
      sh.getRange(startRow, 1, reportData.length, reportData[0].length).setValues(reportData);
      Logger.log(adType + '広告アカウント' + adAccountIds.length + '件のレポートをスプレッドシートに出力しました');
    } else {
      Logger.log(adType + '広告アカウントで配信オンのレポートデータはありませんでした');
    }
  }
}
function validateAndGetSpreadsheet() {
  if (SPREAD_SHEET_ID === 'SPREAD_SHEET_ID' || SPREAD_SHEET_ID === '') {
    throw new Error('スプレッドシートIDを設定してください。');
  }
  return SpreadsheetApp.openById(SPREAD_SHEET_ID);
}
function validateAndGetSheet(ss) {
  if (SHEET_NAME === '') {
    throw new Error('シート名を設定してください。');
  }
  return ss.getSheetByName(SHEET_NAME);
}
function getTargetAccountIds(productType, adType) {
  let targetAccountIds = [];
  const mccChildAccountIds = getMccChildAccountIds(thisMccAccountId, productType);
  Logger.log('スクリプトを設定したMCC直下の' + adType + 'アカウント:' + JSON.stringify(mccChildAccountIds));
  if (mccChildAccountIds.length == 0) return targetAccountIds;
  if (INCLUDE_MULTILEVEL_MCC) {
    //複数階層化対応
    targetAccountIds = classifyAccountsByType(mccChildAccountIds, productType);
  } else {
    //直下だけ取ればよい(=従来どおりの挙動)
    targetAccountIds = getFilteredAccountIds(mccChildAccountIds, productType, ACCOUNT_TYPE.ADS);
  }
  Logger.log(adType + 'レポート取得対象アカウント:' + JSON.stringify(targetAccountIds));
  return targetAccountIds;
}
//引数のアカウントIDを、通常のアカウントとMCCアカウントに分け、一番下の階層まで深掘りする
function classifyAccountsByType(accountIds, productType) {
  //普通の広告アカウントかつ配信オン
  let adsAccountIds = getFilteredAccountIds(accountIds, productType, ACCOUNT_TYPE.ADS);
  Logger.log('普通の広告アカウント:' + adsAccountIds);
  //MCCの場合
  const mccAccountIds = getFilteredAccountIds(accountIds, productType, ACCOUNT_TYPE.MCC);
  for (let i = 0; i < mccAccountIds.length; i++) {
    Logger.log('MCCアカウント:' + mccAccountIds[i]);
    const childAccounts = getMccChildAccountIds(mccAccountIds[i], productType);
    Logger.log('MCCアカウント:' + mccAccountIds[i] + ' のchildAccounts:' + JSON.stringify(childAccounts));
    if (childAccounts.length > 0) {
      const grandChildAccount = classifyAccountsByType(childAccounts, productType);
      adsAccountIds = adsAccountIds.concat(grandChildAccount);
    }
  }
  return adsAccountIds;
}
//MCC内のaccountIdsを取得する
function getMccChildAccountIds(mccAccountId, productType) {
  let accountIds = [];
  let num = 0;
  while (true) {
    const accountLinks = productType.AccountLinkService.get({
      mccAccountId: mccAccountId,
      accountStatuses: ['SERVING'],
      numberResults: 500,
      startIndex: num * 500 + 1,
    }).rval;
    if (accountLinks.totalNumEntries == 0) break;
    for (let i = 0; i < accountLinks.values.length; i++) {
      accountIds.push(accountLinks.values[i].accountLink.accountId);
    }
    num++;
    if (num * 500 >= accountLinks.totalNumEntries) break;
  }
  return accountIds;
}
function getFilteredAccountIds(accountIds, productType, accountType) {
  const includeMccAccount = accountType == ACCOUNT_TYPE.MCC ? 'ONLY_MCC_ACCOUNT' : 'ONLY_ADS_ACCOUNT';
  let filterdAccountIds = [];
  let num = 0;
  while (true) {
    const accounts = productType.AccountService.get({
      accountIds: accountIds.slice(num * 200, Math.min((num + 1) * 200, accountIds.length)),
      includeMccAccount: includeMccAccount,
    }).rval;
    if (accounts.totalNumEntries == 0) break;
    for (let i = 0; i < accounts.values.length; i++) {
      let account = accounts.values[i].account;
      if (accountType == ACCOUNT_TYPE.ADS && account.deliveryStatus != 'ACTIVE') {
        continue;//通常の広告アカウントの場合は、オフの場合リストに追加せずskip
      }
      filterdAccountIds.push(account.accountId);
    }
    num++;
    if (num * 200 >= accountIds.length) break;
  }
  return filterdAccountIds;
}
//レポートを取得
function getReport(accountIds, productType, dateString) {
  let reportData = [];
  for (let i = 0; i < accountIds.length; i++) {
    //operandはほぼ共通
    const operand = {
      accountId: accountIds[i],
      dateRange: {
        startDate: dateString.startDateString,
        endDate: dateString.endDateString,
      },
      fields: REPORT_FIELDS,
      reportDateRangeType: 'CUSTOM_DATE',
    };
    if(i == 0 && HEADER == 'FALSE' && headerFlg == false){
      operand.reportSkipColumnHeader = HEADER;
      headerFlg = true;
    }
    //レポートを取得する
    let reports;
    if (productType == PRODUCT_TYPE.Search) {
      operand.reportType = 'ACCOUNT';//検索のみレポートタイプの指定が必要
      reports = AdsUtilities.getSearchReport(operand).reports[0].rows;
    } else {
      reports = AdsUtilities.getDisplayReport(operand).reports[0].rows;
    }
    Logger.log('アカウントID:' + accountIds[i] + 'のレポートを取得しました');
    //レポートをreportDataにマージする
    reportData = reportData.concat(reports);
  }
  return reportData;
}
//レポートの開始日と終了日を取得する
function getDateString() {
  let jstNow = new Date(Date.now() + ((new Date().getTimezoneOffset() + (9 * 60)) * 60 * 1000));
  let day = Utilities.formatDate(jstNow, 'GMT', 'd');
  if (MONTHS_AGO == 0 && day == 1) {
    MONTHS_AGO = 1;
  }
  let startDateString;
  let endDateString;
  if (MONTHS_AGO == 0) {
    jstNow.setDate(jstNow.getDate() - 1);
    endDateString = Utilities.formatDate(jstNow, 'GMT', 'yyyyMMdd');
    jstNow.setDate(1);
    startDateString = Utilities.formatDate(jstNow, 'GMT', 'yyyyMMdd');
  } else {
    jstNow.setDate(1);
    jstNow.setMonth(jstNow.getMonth() - MONTHS_AGO);
    startDateString = Utilities.formatDate(jstNow, 'GMT', 'yyyyMMdd');
    jstNow.setMonth(jstNow.getMonth() + 1);
    jstNow.setDate(jstNow.getDate() - 1);
    endDateString = Utilities.formatDate(jstNow, 'GMT', 'yyyyMMdd');
  }
  return { startDateString: startDateString, endDateString: endDateString };
}