日予算自動変更

更新履歴

2024/10/31 ディスプレイ版に対応
2024/12/03 メール送信時のエラーに対応

スクリプトの概要

概要

本ページでは、毎日の消化予算を取得し、最適な日予算の設定を自動で行うスクリプトをご紹介します。

※当ソリューションは、Web担当者ForumのGoogle 広告の日予算はいくらが適正? スプレッドシートとスクリプトで簡単に自動で計算・変更する方法(アタラ株式会社様作成)の記事を参考に作成いたしました。ソリューション作成に当たり、アタラ株式会社様よりご協力と、アイデアの転用を許諾いただきましたことをここに感謝申し上げます。

※当スクリプトは検索広告・ディスプレイ広告共通版となっております。(設定されたアカウントが検索広告のものかディスプレイ広告のものか自動で判定されます)

ご利用のイメージ

検索広告(またはディスプレイ広告)は1カ月の予算消化状況を確認しながら日々調整することで、より高い成果を上げることができます。

日予算最適化の例。
【日予算の設定状況】
1カ月の予算:30万円。これを30日で割ると、日予算:1万円です。

【日予算の消化状況】
月の残り日数:10日。残り予算:20万円。

【最適化】
残り日数から算出して日予算:2万円を設定する。

上記の例のように、最適化を行うことで予算を無駄なく消化することが可能です。

本ページでご紹介するソリューションでは、自動でレポートを取得し、Googleスプレッドシートのテンプレートにより当月の消化予算を確認、そして残り日数から適正日予算を自動で導き出します。
面倒なレポート取得と日予算設定は、Yahoo!広告スクリプトを使うことで全て自動で実施できます。

ご利用の流れ

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

2.スプレッドシートのテンプレートを、以下のボタンからご自身のGoogle ドライブにコピーしてください。
  テンプレートをコピー
 コピーで作成したスプレッドシートのスプレッドシートIDを確認してメモしておいてください。スクリプト内の定数の設定で利用します。
 ※GoogleスプレッドシートIDの取得方法についてはこちらをご覧ください。

3.管理画面上のスクリプト作成画面にて、後述の「レポート自動取得」にあるサンプルコードを設定してください。
 サンプルコード内の各定数については、「レポート自動取得」内の「サンプルコード内各定数のご説明」をご覧ください。
 ※スクリプトの新規作成および編集の手順はこちらをご覧ください。

4.後述の「スプレッドシート内の各種項目の設定」の通り、2でコピーしたスプレッドシート内で必要な項目の設定をしてください。

5.管理画面上のスクリプト作成画面にて、後述の「日予算設定」にあるサンプルコードを設定してください。
 サンプルコード内の各定数については、「日予算設定」内の「サンプルコード内各定数のご説明」をご覧ください。
 ※スクリプトの新規作成および編集の手順はこちらをご覧ください。

6. 管理画面上にてスクリプトの設定を完了後、スクリプトの実行頻度の設定をしてください。
 本スクリプトでは1日1回の設定が推奨です。
 設定の例)
 ・レポート自動取得スクリプト:毎日 7:00
 ・日予算設定スクリプト:毎日 8:00
 ※「日予算設定スクリプト」は「レポート自動取得スクリプト」で取得するレポートデータを参照するため、「レポート自動取得スクリプト」よりも1時間ほど遅い時間で設定することを推奨します。
 ※スクリプトの実行頻度の設定手順はこちらをご覧ください。

レポート自動取得

まずは当月のレポートを取得し結果をスプレッドシートに出力してまとめます。

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

後述のサンプルコードにおける各定数の設定方法についてご説明いたします。

■スプレッドシートIDの指定例

GoogleスプレッドシートIDの取得方法についてはこちらをご覧ください。
次の例のように、' '(シングルクオーテーション)で囲んで指定してください。(スプレッドシートID:'12345abcde'の場合)
const SPREAD_SHEET_ID = '12345abcde'; 

■シート名の指定例

次の例のように、' '(シングルクオーテーション)で囲んで指定してください。
テンプレートのタブ名をそのままご利用になる場合、SHEET_NAMEには「raw」を指定してください。
const SHEET_NAME = 'raw';

■メール、Slackの指定例

メール、Slack設定についてをご確認ください。

レポート自動取得のサンプルコード

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

/*
■スクリプト内容
検索広告またはディスプレイ広告のレポートを取得し、結果をスプレッドシートに自動で書き込みます。
■利用方法
1.当スクリプトを、検索広告またはディスプレイ広告のスクリプトとしてください。
2.定数を以下のように設定してください。
■定数
・SPREAD_SHEET_ID:スプレッドシートID
・SHEET_NAME:スプレッドシート名
・FLAG_MAIL:結果をメール送信するならtrue、しないならfalse
・MAIL_TO:メール送信先のYahoo! BusinessID
・MAIL_TITLE:メールタイトル
・FLAG_SLACK:Slack配信するときはtrue、配信しないときはfalse
・URL_FETCH_APP:SlackのWebhook URL
*/
//設定が必要な定数
const SPREAD_SHEET_ID = '';//書き出すスプレッドシートID。例:'SPREAD_SHEET_ID'
const SHEET_NAME = '';//スプレッドシートのシート名。例:'シート1'
const FLAG_MAIL = false;//メール送信するならtrue、しないならfalse
const MAIL_TO = [''];//メール通知先のビジネスIDを指定。例:['abcde12345']
let MAIL_TITLE = '日予算自動変更のレポート取得';//メールタイトルを指定
const FLAG_SLACK = false;//Slack配信するときはtrue、配信しないときはfalse
const URL_FETCH_APP = '';//SlackのWebhook URL。例:'https://hooks.slack.com/services/XXXXXXXX/XXXXXXXX/XXXXXXXX'
//設定が不要な定数
const accountId = AdsUtilities.getCurrentAccountId();
const FIELDS =  ['DAY', 'CAMPAIGN_NAME', 'IMPS', 'CLICKS', 'COST', 'CONVERSIONS'];
const REPORT_DATE_RANGE_TYPE = 'THIS_MONTH';
const productType = AdsUtilities.getProductType();
let TEXT_MESSAGE_ARRAY = [];
function main() {
  try {
    getSearchReport();
  } catch (e) {
    MAIL_TITLE += ' スクリプトが失敗しています';
    TEXT_MESSAGE_ARRAY.push('何らかのエラーによりスクリプトが失敗しました。詳細は管理画面のログをご確認ください。');
    sendMailOrSlack();
    throw e;
  }
  sendMailOrSlack();
}
function getSearchReport() {
  //スプレッドシート
  const ss = validateAndGetSpreadsheet();
  const sh = validateAndGetSheet(ss);
  //スプレッドシートの内容をクリア
  sh.clear();
  //レポート取得
  let report = getReport();
  if(report.length <= 1){
    logAndMessage("レポートが存在しませんでした。");
    return;
  }
  //結果をスプレッドシートに書き込み
  sh.getRange('A1').setValues(report);
  logAndMessage("レポート取得とスプレッドシートへの書き込み処理が完了しました。");
}
function getReport(){
  let report;
  if (productType == 'SEARCH') {
    report = AdsUtilities.getSearchReport({
      accountId: accountId,
      fields: FIELDS,
      reportDateRangeType: REPORT_DATE_RANGE_TYPE,
      reportType: 'CAMPAIGN',
      reportSkipColumnHeader: 'FALSE',
      sortFields: [{
        field: 'DAY',
        reportSortType: 'ASC',
      }]
    }).reports[0].rows;
  } else if (productType == 'DISPLAY') {
    report = AdsUtilities.getDisplayReport({
      accountId: accountId,
      fields: FIELDS,
      reportDateRangeType: REPORT_DATE_RANGE_TYPE,
      reportSkipColumnHeader: 'FALSE',
      sortFields: [//並び順
        {//コストの多い順
          field: "DAY",
          reportSortType: "ASC"
        }
      ]
    }).reports[0].rows;
  } else {
    throw new Error('当スクリプトは検索広告またはディスプレイ広告のアカウントに設定してください(MCCアカウント等は対象外です)');
  }
  return report;
}
function validateAndGetSpreadsheet() {
  if (SPREAD_SHEET_ID === 'SPREAD_SHEET_ID' || SPREAD_SHEET_ID === '') {
    throw new Error('スプレッドシートIDを設定してください。');
  }
  try{
    return SpreadsheetApp.openById(SPREAD_SHEET_ID);
  } catch(e){
    throw new Error('スプレッドシートを開くことが出来ませんでした。スプレッドシートIDまたはスプレッドシートの権限が正しいか確認してください。' + e);
  }
}
function validateAndGetSheet(ss) {
  if (SHEET_NAME === '') {
    throw new Error('シート名を設定してください。');
  }
  const sh = ss.getSheetByName(SHEET_NAME);
  if(sh === null){
    throw new Error('シートが開けませんでした。シート名を確認してください。');
  }
  return sh;
}
//ログ、メールなどに流すテキスト
function logAndMessage(text) {
  Logger.log(text);
  TEXT_MESSAGE_ARRAY.push(text);
}
//メール、Slack、もしくはその両方に通知
function sendMailOrSlack() {
  if (TEXT_MESSAGE_ARRAY.length > 0) {
    validateAndSendMail();
    validateAndSendSlack();
  }
}
function validateAndSendSlack() {
  if (FLAG_SLACK) {
    if (URL_FETCH_APP === 'SLACK_WEBHOOK_URL' || URL_FETCH_APP === '') {
      throw new Error('SlackのWebhook URLを設定してください。Slack送信前までに実行された処理は完了しています。');
    }
    UrlFetchApp.fetch(URL_FETCH_APP, {
      method: 'post',
      contentType: 'application/json',
      payload: JSON.stringify({
        text: TEXT_MESSAGE_ARRAY.join('\n'),
      }),
    });
  }
}
function validateAndSendMail() {
  if (FLAG_MAIL) {
    if (MAIL_TO.length < 1 || !MAIL_TO.every(str => typeof str === 'string' && /^[a-zA-Z0-9]+$/.test(str))) {
      throw new Error('Yahoo! JAPANビジネスIDを設定してください。メール送信前までに実行された処理は完了しています。');
    }
    MailApp.sendEmail({
      to: MAIL_TO,
      subject: '【アカウントID:' + accountId + '】' + MAIL_TITLE,
      body: TEXT_MESSAGE_ARRAY.join('\n'),
    });
  }
}

結果確認

下記画像のように「raw」タブにキャンペーン名や今月のコストなどの値が出力されていれば成功です。

※以降の解説では、2024年9月の「キャンペーン1」の適正予算を設定するものとして解説いたします。

スプレッドシート内の各種項目の設定

レポート取得に成功したら、次は適正予算を割り出すためにスプレッドシートのテンプレートに記入されている記述を変更する必要があります。

下記の設定を行い、スプレッドシート上で適正予算を割り出してください。なお、テンプレートにはあらかじめ関数が組み込まれているため、以下でご案内している部分以外は変更しないようお願いいたします。

「日別レポート」タブでの設定

下記2項目について指定する必要があります。
項目名 指定する内容
開始日 該当キャンペーンの当月開始日を指定してください。
キャンペーン名 該当キャンペーン名を指定してください。


「当月予算進捗」タブでの設定

下記5項目について指定する必要があります。
項目名 指定する内容
今日の日付 初期値は「=today()」関数が入力されています。
開始日 該当キャンペーンの当月開始日を指定してください。
終了日 該当キャンペーンの当月終了日を指定してください。
キャンペーン名 該当キャンペーン名を指定してください。
予算 該当キャンペーンの当月予算を指定してください。


適正日予算を確認

「当月予算進捗」タブの「適正日予算」の列を確認します。スプレッドシートの関数により自動で割り出された予算が出力されていることを確認します。
次に解説するスクリプトでは、こちらの予算を自動で設定していきます。

日予算設定

スプレッドシートの関数が導き出した適正日予算をスクリプトで自動取得し、該当のキャンペーンに自動で設定します。

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

上記「レポート自動取得」欄の定数説明をご覧ください。
テンプレートのタブ名をそのままご利用になる場合、SHEET_NAMEには「当月予算進捗」を指定してください。
const SHEET_NAME = '当月予算進捗';

日予算変更のサンプルコード

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

/*
■スクリプト内容
スプレッドシートの内容を参照し、指定したキャンペーンの予算を自動で変更します。
■利用方法
1.当スクリプトを、検索広告またはディスプレイ広告のスクリプトとしてください。
2.定数を以下のように設定してください。
■定数
・SPREAD_SHEET_ID:スプレッドシートID
・SHEET_NAME:スプレッドシート名
・FLAG_MAIL:結果をメール送信するならtrue、しないならfalse
・MAIL_TO:メール送信先のYahoo! BusinessID
・MAIL_TITLE:メールタイトル
・FLAG_SLACK:Slack配信するときはtrue、配信しないときはfalse
・URL_FETCH_APP:SlackのWebhook URL
■制限事項
検索広告の場合、予算の数値は10の位で切り捨てた値となります。
*/
//設定が必要な定数
const SPREAD_SHEET_ID = ''; //書き出すスプレッドシートID。例:'SPREAD_SHEET_ID'
const SHEET_NAME = '';//スプレッドシートのシート名。例:'シート1'
const FLAG_MAIL = false;//メール送信するならtrue、しないならfalse
const MAIL_TO = [''];//メール通知先のビジネスIDを指定。例:['abcde12345']
let MAIL_TITLE = '日予算自動変更';//メールタイトルを指定
const FLAG_SLACK = false;//Slack配信するときはtrue、配信しないときはfalse
const URL_FETCH_APP = '';//SlackのWebhook URL。例:'https://hooks.slack.com/services/XXXXXXXX/XXXXXXXX/XXXXXXXX'
//設定が不要な定数
const accountId = AdsUtilities.getCurrentAccountId();
const productType = AdsUtilities.getProductType();
let campaignArray = [];
let TEXT_MESSAGE_ARRAY = [];
let errFlag = false;
function main() {
  try {
    setBudget();
  } catch (e) {
    MAIL_TITLE += ' スクリプトが失敗しています';
    TEXT_MESSAGE_ARRAY.push('何らかのエラーによりスクリプトが失敗しました。詳細は管理画面のログをご確認ください。');
    sendMailOrSlack();
    throw e;
  }
  sendMailOrSlack();
}
function setBudget() {
  //スプレッドシート
  const ss = validateAndGetSpreadsheet();
  const sh = validateAndGetSheet(ss);
  let lastRow = sh.getLastRow();
  let firstLow = lastRow - 6;
  if(lastRow == 6){
    throw new Error(SHEET_NAME + 'シートにキャンペーン情報を記入してください。');
  }
  //スプレッドシートのデータ取得
  let data = sh.getRange(7, 2, firstLow, 9).getValues();
  //日予算自動変更
  for(let i = 0; i < firstLow; i++){
    let campaignName = data[i][2];
    let budgetAmount = data[i][7];
    //上の2つがnullじゃないかチェックする。
    if(!campaignName || !budgetAmount){
      throw new Error(SHEET_NAME + 'シートのキャンペーン情報に不備があります。');
    }
    //適正日予算がマイナスかチェック。
    if(budgetAmount.indexOf('-') == 0){
      throw new Error(SHEET_NAME + 'シートの適正日予算がマイナスの値です。');
    }
    //CampaignService/getを実行してキャンペーンを取得する。
    let campaigns =  getCampaign();
    //予算変更のsetリクエストを作成する。
    createSetOperand(campaigns, campaignName, budgetAmount);
  }
  if(campaignArray.length == 0){
    logAndMessage("予算変更の対象となるキャンペーンは一つも存在しませんでした。");
    return;
  }
  //日予算の変更。
  setCampaign();
  Logger.log("処理を終了します。");
}
function getCampaign(){
  let campaigns;
  if (productType == 'SEARCH') {
    campaigns = Search.CampaignService.get({
      accountId: accountId,
    }).rval;
  } else if (productType == 'DISPLAY') {
    campaigns = Display.CampaignService.get({
      accountId: accountId,
    }).rval;
  } else {
    throw new Error('当スクリプトは検索広告またはディスプレイ広告のアカウントに設定してください(MCCアカウント等は対象外です)');
  }
  return campaigns;
}
function createSetOperand(campaigns, campaignName, budgetAmount){
  let checkFlg = false;
  for (let j = 0; j < campaigns.values.length; j++) {
    let campaign = campaigns.values[j].campaign;
    //スプレッドシートのキャンペーン名と一致するか確認する。
    if(campaignName === campaign.campaignName){
      let campaignId = campaign.campaignId;
      //予算の数値のみ抽出。
      let amountValue = budgetAmount.replace(/[^0-9]/g, '');
      let amountNumber = parseInt(amountValue);
      if(!amountNumber){
        throw new Error('適正日予算に不正な値が存在します。');
      }
      if (productType == 'SEARCH') {
        //2桁以下の数値は切り捨て。
        amountNumber = Math.floor(amountNumber/100)*100;
      }
      //operandの中にデータを入れる。
      let campaignOperand = {
        campaignId: campaignId,
        budget: {
          amount: amountNumber
        },
      };
      campaignArray.push(campaignOperand);
      checkFlg = true;
    }
  }
  if(!checkFlg) {
    Logger.log(SHEET_NAME + "シートの " + campaignName + 'は存在しないため、予算変更はできませんでした。');
    errFlag = true;
  }
}
function setCampaign(){
  let campaignsSet;
  if (productType == 'SEARCH') {
    campaignsSet = Search.CampaignService.set({
      accountId: accountId,
      operand: campaignArray,
    }).rval;
  } else if (productType == 'DISPLAY') {
    campaignsSet = Display.CampaignService.set({
      accountId: accountId,
      operand: campaignArray,
    }).rval;
  }
  //処理が成功したかチェック。
  let successCnt = 0;
  for (let k = 0; k < campaignsSet.values.length; k++) {
    if (campaignsSet.values[k].operationSucceeded){
      successCnt++;
    }
  }
  if(successCnt > 0 && !errFlag){
    logAndMessage("予算変更に" + successCnt +"件成功しました。");
  } else if(successCnt > 0 && errFlag){
    logAndMessage("予算変更に" + successCnt +"件成功しましたが、一部のキャンペーンについては予算変更に失敗しております。詳細は管理画面のログをご確認ください。");
  } else{
    logAndMessage("予算変更に失敗しました。");
  }
}
function validateAndGetSpreadsheet() {
  if (SPREAD_SHEET_ID === 'SPREAD_SHEET_ID' || SPREAD_SHEET_ID === '') {
    throw new Error('スプレッドシートIDを設定してください。');
  }
  try{
    return SpreadsheetApp.openById(SPREAD_SHEET_ID);
  } catch(e){
    throw new Error('スプレッドシートを開くことが出来ませんでした。スプレッドシートIDまたはスプレッドシートの権限が正しいか確認してください。' + e);
  }
}
function validateAndGetSheet(ss) {
  if (SHEET_NAME === '') {
    throw new Error('シート名を設定してください。');
  }
  const sh = ss.getSheetByName(SHEET_NAME);
  if(sh === null){
    throw new Error('シートが開けませんでした。シート名を確認してください。');
  }
  return sh;
}
//ログ、メールなどに流すテキスト
function logAndMessage(text) {
  Logger.log(text);
  TEXT_MESSAGE_ARRAY.push(text);
}
//メール、Slack、もしくはその両方に通知
function sendMailOrSlack() {
  if (TEXT_MESSAGE_ARRAY.length > 0) {
    validateAndSendMail();
    validateAndSendSlack();
  }
}
function validateAndSendSlack() {
  if (FLAG_SLACK) {
    if (URL_FETCH_APP === 'SLACK_WEBHOOK_URL' || URL_FETCH_APP === '') {
      throw new Error('SlackのWebhook URLを設定してください。Slack送信前までに実行された処理は完了しています。');
    }
    UrlFetchApp.fetch(URL_FETCH_APP, {
      method: 'post',
      contentType: 'application/json',
      payload: JSON.stringify({
        text: TEXT_MESSAGE_ARRAY.join('\n'),
      }),
    });
  }
}
function validateAndSendMail() {
  if (FLAG_MAIL) {
    if (MAIL_TO.length < 1 || !MAIL_TO.every(str => typeof str === 'string' && /^[a-zA-Z0-9]+$/.test(str))) {
      throw new Error('Yahoo! JAPANビジネスIDを設定してください。メール送信前までに実行された処理は完了しています。');
    }
    MailApp.sendEmail({
      to: MAIL_TO,
      subject: '【アカウントID:' + accountId + '】' + MAIL_TITLE,
      body: TEXT_MESSAGE_ARRAY.join('\n'),
    });
  }
}

結果確認

日予算変更の対象となるキャンペーンがあった場合に、ログに「予算変更に~件成功しました。」などの結果が出力されれば成功です。