Google Apps Scriptで広告運用を自動化する|GASの基本からレポート・アラートの実装まで

GASとは何か

Google Apps Script(GAS)は、Googleが提供するクラウドベースのスクリプト実行環境です。JavaScriptをベースとした言語で、Googleの各サービスをプログラムから操作できます。

スプレッドシート、Gmail、Googleドライブ、カレンダーなど、Googleサービスとの連携が組み込みで用意されています。外部APIへのHTTPリクエストも送信できるため、Slack通知やBigQueryへのデータ投入も実現可能です。

Googleアカウントがあれば無料で使えます。サーバーの構築や維持管理は不要です。ブラウザ上でコードを書いて実行できるため、ローカル環境の構築も必要ありません。

広告運用でGASが活用される場面

広告運用の業務には、繰り返し発生する定型的な処理が数多くあります。GASはこうした定型業務の自動化に適しています。

活用場面内容手動での所要時間目安
レポートの自動集約複数媒体のデータをスプレッドシートに統合30分〜1時間/日
異常値アラートCPAやCVの急変をSlack/メールで通知確認漏れリスクあり
月次レポート自動生成テンプレートの複製と当月データの自動入力2〜3時間/月
データ集約広告管理画面のデータをBigQueryに転送1〜2時間/回
入稿リストの整理命名規則に基づくキャンペーン名の自動生成30分〜1時間/回

これらの処理は、一度スクリプトを書いてしまえば自動で繰り返し実行されます。手動で毎日行っていたレポートの集約が自動化されるだけでも、月間10時間以上の時間が確保できるケースは珍しくありません。

以下の図は、GASを中心とした自動化の全体像です。

GASによる広告運用自動化アーキテクチャデータソースGoogle広告 APIMeta Marketing APIスプレッドシートGA4(BQ Export)Google Apps Scriptデータ取得・加工トリガーによる定期実行エラーハンドリング外部API連携出力先レポートシートSlack通知メール通知BigQuery時間ベーストリガー毎日 / 毎時 / 毎週 / 毎月各データソースからGASがデータを取得・加工し、レポートや通知として出力する

運用メモ GASはGoogle Ads APIへ直接アクセスすることも可能ですが、OAuth認証の設定や利用申請が必要です。まずはスプレッドシートからデータを読み取る方式で始めるのが手軽です。APIへの直接アクセスは、スプレッドシート経由では取得できないデータが必要になった段階で検討してください。

GASの始め方

スクリプトエディタを開く

Googleスプレッドシートから始めるのが最もシンプルです。スプレッドシートを開き、メニューバーの「拡張機能」→「Apps Script」を選択するとスクリプトエディタが開きます。

この方法で作成したスクリプトは「コンテナバインド型」と呼ばれ、親のスプレッドシートと紐づきます。SpreadsheetApp.getActiveSpreadsheet()で親シートに直接アクセスできる利点があります。

一方、script.google.com から直接作成するスクリプトは「スタンドアロン型」です。特定のファイルに紐づかないため、複数のスプレッドシートを横断して操作する場合に適しています。

種別作成方法用途
コンテナバインド型スプレッドシートの「拡張機能」から作成特定シートとの連携が中心の処理
スタンドアロン型script.google.com から直接作成複数ファイルの横断操作、Web API

基本的な構文

GASはJavaScriptベースの言語です。以下は最も基本的なスクリプトの形です。

function myFirstScript() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var value = sheet.getRange('A1').getValue();
  Logger.log('A1の値: ' + value);
}

Logger.log()で出力した内容は、エディタの「実行ログ」から確認できます。console.log()も使用できますが、Cloud Loggingに出力されるため、簡易的な確認にはLogger.log()が便利です。

実行とデバッグ

スクリプトエディタ上部の「実行」ボタンを押すと、選択中の関数が実行されます。初回実行時には、スクリプトが使用するGoogleサービスへのアクセス権限を承認する画面が表示されます。

デバッグには以下の方法が使えます。

  • Logger.log() : 変数の値を実行ログに出力する
  • ブレークポイント : エディタの行番号をクリックして設定し、デバッグモードで停止箇所を確認する
  • try-catch : エラー発生時の詳細情報を取得する

実用例1:広告レポートの自動集約

複数の広告媒体のデータをスプレッドシートに自動集約するスクリプトです。設定シートから対象のスプレッドシートURLや媒体情報を読み取り、日次で更新します。

設定シートの構成

まず、スクリプトの設定値を管理するシートを用意します。設定値をコード内にハードコードすると、変更のたびにスクリプトを編集する必要があり、運用負荷が上がります。

設定シート(シート名: 設定)の構成例は以下のとおりです。

セル項目値の例
B1レポート出力先シート名日次レポート
B2Google広告データシートURLhttps://docs.google.com/
B3Meta広告データシートURLhttps://docs.google.com/
B4通知先メールアドレスteam@example.com
B5データ保持日数90

レポート集約スクリプト

/**
 * 広告レポートの自動集約
 * 設定シートに定義されたデータソースから日次データを取得し、
 * レポートシートに書き出す。
 */
function aggregateAdReport() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var configSheet = ss.getSheetByName('設定');

  // 設定値の読み取り
  var reportSheetName = configSheet.getRange('B1').getValue();
  var googleAdsUrl = configSheet.getRange('B2').getValue();
  var metaAdsUrl = configSheet.getRange('B3').getValue();
  var notifyEmail = configSheet.getRange('B4').getValue();
  var retentionDays = configSheet.getRange('B5').getValue();

  var reportSheet = ss.getSheetByName(reportSheetName);
  if (!reportSheet) {
    reportSheet = ss.insertSheet(reportSheetName);
    reportSheet.appendRow([
      '日付', '媒体', 'キャンペーン名',
      '表示回数', 'クリック数', '広告費', 'CV数', 'CPA'
    ]);
  }

  try {
    // Google広告データの取得
    var googleData = fetchAdData(googleAdsUrl, 'Google広告');
    // Meta広告データの取得
    var metaData = fetchAdData(metaAdsUrl, 'Meta広告');

    // データの書き出し
    var allData = googleData.concat(metaData);
    if (allData.length > 0) {
      reportSheet.getRange(
        reportSheet.getLastRow() + 1, 1,
        allData.length, allData[0].length
      ).setValues(allData);
    }

    // 古いデータの削除
    cleanOldData(reportSheet, retentionDays);

    Logger.log(allData.length + '行のデータを書き出しました');
  } catch (e) {
    MailApp.sendEmail(
      notifyEmail,
      '【エラー】広告レポート集約に失敗',
      'エラー内容:\n' + e.message + '\n\nスタック:\n' + e.stack
    );
    throw e;
  }
}

/**
 * 指定URLのスプレッドシートからデータを取得する
 * @param {string} sourceUrl - データソースのスプレッドシートURL
 * @param {string} platform - 媒体名
 * @return {Array} 二次元配列のデータ
 */
function fetchAdData(sourceUrl, platform) {
  if (!sourceUrl) return [];

  var sourceSs = SpreadsheetApp.openByUrl(sourceUrl);
  var sourceSheet = sourceSs.getSheets()[0];
  var data = sourceSheet.getDataRange().getValues();
  var result = [];

  // ヘッダー行をスキップ(1行目)
  for (var i = 1; i < data.length; i++) {
    var row = data[i];
    var cost = Number(row[4]) || 0;
    var conversions = Number(row[5]) || 0;
    var cpa = conversions > 0 ? Math.round(cost / conversions) : 0;

    result.push([
      Utilities.formatDate(new Date(row[0]), 'Asia/Tokyo', 'yyyy-MM-dd'),
      platform,
      String(row[1]),
      Number(row[2]) || 0,
      Number(row[3]) || 0,
      cost,
      conversions,
      cpa
    ]);
  }

  return result;
}

/**
 * 指定日数より古いデータを削除する
 * @param {Sheet} sheet - 対象シート
 * @param {number} retentionDays - 保持日数
 */
function cleanOldData(sheet, retentionDays) {
  var cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
  var data = sheet.getDataRange().getValues();
  var rowsToDelete = [];

  for (var i = data.length - 1; i >= 1; i--) {
    var rowDate = new Date(data[i][0]);
    if (rowDate < cutoffDate) {
      rowsToDelete.push(i + 1);
    }
  }

  // 下の行から削除する(行番号のずれを防止)
  for (var j = 0; j < rowsToDelete.length; j++) {
    sheet.deleteRow(rowsToDelete[j]);
  }
}

運用メモ SpreadsheetApp.openByUrl()は、対象のスプレッドシートにスクリプト実行アカウントの閲覧権限が必要です。複数人で運用する場合は、共有ドライブにデータソースを配置するか、サービスアカウントを利用してください。

実用例2:異常値アラートのSlack通知

CPAが閾値を超えた場合にSlackへ通知するスクリプトです。毎日1回実行し、前日のパフォーマンスを監視します。

Slack Webhook URLの取得

Slack通知にはIncoming Webhookを使います。SlackアプリのカスタムIntegrationsから「Incoming Webhook」を追加し、通知先チャンネルを選択するとWebhook URLが発行されます。

このURLはスクリプトプロパティに保存します。スクリプトエディタの「プロジェクトの設定」→「スクリプト プロパティ」から設定できます。

プロパティ名値の例説明
SLACK_WEBHOOK_URLhttps://hooks.slack.com/services/Slack通知先
CPA_THRESHOLD_RATE1.5平均CPAの何倍で警告するか
REPORT_SHEET_NAME日次レポートデータ取得元シート名

アラート通知スクリプト

/**
 * CPAの異常値をSlackに通知する
 * 前日のCPAがキャンペーン別の過去30日平均CPAの
 * 閾値倍率を超えた場合にアラートを送信する。
 */
function sendCpaAlert() {
  var props = PropertiesService.getScriptProperties();
  var webhookUrl = props.getProperty('SLACK_WEBHOOK_URL');
  var thresholdRate = Number(props.getProperty('CPA_THRESHOLD_RATE')) || 1.5;
  var sheetName = props.getProperty('REPORT_SHEET_NAME') || '日次レポート';

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName(sheetName);
  if (!sheet) {
    Logger.log('シート "' + sheetName + '" が見つかりません');
    return;
  }

  var data = sheet.getDataRange().getValues();
  var yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);
  var yesterdayStr = Utilities.formatDate(yesterday, 'Asia/Tokyo', 'yyyy-MM-dd');

  // 過去30日間の平均CPAをキャンペーン別に算出
  var campaignStats = calcCampaignAverageCpa(data, 30);

  // 前日データの抽出と異常値判定
  var alerts = [];
  for (var i = 1; i < data.length; i++) {
    var rowDate = Utilities.formatDate(
      new Date(data[i][0]), 'Asia/Tokyo', 'yyyy-MM-dd'
    );
    if (rowDate !== yesterdayStr) continue;

    var campaignName = String(data[i][2]);
    var cost = Number(data[i][5]) || 0;
    var conversions = Number(data[i][6]) || 0;

    if (conversions === 0) continue;

    var cpa = cost / conversions;
    var avgCpa = campaignStats[campaignName];

    if (avgCpa && cpa > avgCpa * thresholdRate) {
      alerts.push({
        campaign: campaignName,
        platform: String(data[i][1]),
        cpa: Math.round(cpa),
        avgCpa: Math.round(avgCpa),
        cost: Math.round(cost),
        conversions: conversions
      });
    }
  }

  if (alerts.length === 0) {
    Logger.log('異常値はありませんでした');
    return;
  }

  // Slack通知の送信
  var message = buildAlertMessage(alerts, yesterdayStr, thresholdRate);
  postToSlack(webhookUrl, message);
  Logger.log(alerts.length + '件のCPA異常値を通知しました');
}

/**
 * キャンペーン別の過去N日間の平均CPAを算出する
 * @param {Array} data - シートの全データ(二次元配列)
 * @param {number} days - 集計対象日数
 * @return {Object} キャンペーン名をキーとした平均CPAのオブジェクト
 */
function calcCampaignAverageCpa(data, days) {
  var cutoff = new Date();
  cutoff.setDate(cutoff.getDate() - days - 1);
  var stats = {};

  for (var i = 1; i < data.length; i++) {
    var rowDate = new Date(data[i][0]);
    if (rowDate < cutoff) continue;

    var campaign = String(data[i][2]);
    var cost = Number(data[i][5]) || 0;
    var conversions = Number(data[i][6]) || 0;

    if (!stats[campaign]) {
      stats[campaign] = { totalCost: 0, totalCv: 0 };
    }
    stats[campaign].totalCost += cost;
    stats[campaign].totalCv += conversions;
  }

  var result = {};
  for (var key in stats) {
    if (stats[key].totalCv > 0) {
      result[key] = stats[key].totalCost / stats[key].totalCv;
    }
  }
  return result;
}

/**
 * Slackに送信するアラートメッセージを組み立てる
 * @param {Array} alerts - アラート対象のキャンペーン情報
 * @param {string} dateStr - 対象日付
 * @param {number} thresholdRate - 閾値倍率
 * @return {string} Slackメッセージ文字列
 */
function buildAlertMessage(alerts, dateStr, thresholdRate) {
  var lines = [
    '【CPA異常値アラート】' + dateStr,
    '平均CPAの' + thresholdRate + '倍を超えたキャンペーン:',
    ''
  ];

  for (var i = 0; i < alerts.length; i++) {
    var a = alerts[i];
    lines.push(
      '• ' + a.platform + ' / ' + a.campaign
    );
    lines.push(
      '  CPA: ' + a.cpa.toLocaleString() + '円' +
      '(平均: ' + a.avgCpa.toLocaleString() + '円)' +
      ' 広告費: ' + a.cost.toLocaleString() + '円' +
      ' CV: ' + a.conversions + '件'
    );
  }

  return lines.join('\n');
}

/**
 * Slack Incoming WebhookにメッセージをPOSTする
 * @param {string} webhookUrl - Webhook URL
 * @param {string} message - 送信するメッセージ
 */
function postToSlack(webhookUrl, message) {
  var payload = JSON.stringify({ text: message });
  var options = {
    method: 'post',
    contentType: 'application/json',
    payload: payload,
    muteHttpExceptions: true
  };

  var response = UrlFetchApp.fetch(webhookUrl, options);
  if (response.getResponseCode() !== 200) {
    throw new Error(
      'Slack通知に失敗しました: ' + response.getContentText()
    );
  }
}

運用メモ Slack Webhook URLは必ずスクリプトプロパティに保存してください。コード内にURLをハードコードすると、スクリプトを共有した際にWebhookが漏洩するリスクがあります。スクリプトプロパティはプロジェクトごとに暗号化されて保存されます。

実用例3:月次レポートの自動生成

テンプレートシートを複製して当月のデータを自動入力するスクリプトです。毎月1日に実行し、前月分のレポートを生成します。

/**
 * 月次レポートを自動生成する
 * テンプレートシートを複製し、前月のデータを自動入力する。
 * 毎月1日にトリガーで実行する想定。
 */
function generateMonthlyReport() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var props = PropertiesService.getScriptProperties();
  var templateName = props.getProperty('TEMPLATE_SHEET_NAME') || 'テンプレート';
  var dataSheetName = props.getProperty('REPORT_SHEET_NAME') || '日次レポート';

  // 前月の年月を算出
  var today = new Date();
  var lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);
  var year = lastMonth.getFullYear();
  var month = lastMonth.getMonth() + 1;
  var monthStr = year + '年' + month + '月';
  var newSheetName = monthStr + 'レポート';

  // 既存チェック
  if (ss.getSheetByName(newSheetName)) {
    Logger.log(newSheetName + ' は既に存在します');
    return;
  }

  // テンプレートの複製
  var template = ss.getSheetByName(templateName);
  if (!template) {
    throw new Error(
      'テンプレートシート "' + templateName + '" が見つかりません'
    );
  }
  var newSheet = template.copyTo(ss);
  newSheet.setName(newSheetName);

  // 前月データの集計
  var dataSheet = ss.getSheetByName(dataSheetName);
  var data = dataSheet.getDataRange().getValues();
  var summary = summarizeMonthlyData(data, year, month);

  // サマリーデータの書き込み
  newSheet.getRange('B2').setValue(monthStr);
  newSheet.getRange('B4').setValue(summary.impressions);
  newSheet.getRange('B5').setValue(summary.clicks);
  newSheet.getRange('B6').setValue(summary.cost);
  newSheet.getRange('B7').setValue(summary.conversions);
  newSheet.getRange('B8').setValue(
    summary.conversions > 0
      ? Math.round(summary.cost / summary.conversions)
      : 0
  );
  newSheet.getRange('B9').setValue(
    summary.impressions > 0
      ? (summary.clicks / summary.impressions * 100).toFixed(2) + '%'
      : '0%'
  );

  // 媒体別データの書き込み
  var platformRow = 12;
  for (var platform in summary.byPlatform) {
    var p = summary.byPlatform[platform];
    newSheet.getRange(platformRow, 1).setValue(platform);
    newSheet.getRange(platformRow, 2).setValue(p.impressions);
    newSheet.getRange(platformRow, 3).setValue(p.clicks);
    newSheet.getRange(platformRow, 4).setValue(p.cost);
    newSheet.getRange(platformRow, 5).setValue(p.conversions);
    newSheet.getRange(platformRow, 6).setValue(
      p.conversions > 0 ? Math.round(p.cost / p.conversions) : 0
    );
    platformRow++;
  }

  Logger.log(newSheetName + ' を生成しました');
}

/**
 * 指定年月のデータを集計する
 * @param {Array} data - 日次レポートの全データ
 * @param {number} year - 対象年
 * @param {number} month - 対象月(1-12)
 * @return {Object} 全体サマリーと媒体別サマリー
 */
function summarizeMonthlyData(data, year, month) {
  var summary = {
    impressions: 0, clicks: 0, cost: 0, conversions: 0,
    byPlatform: {}
  };

  for (var i = 1; i < data.length; i++) {
    var rowDate = new Date(data[i][0]);
    if (rowDate.getFullYear() !== year) continue;
    if (rowDate.getMonth() + 1 !== month) continue;

    var platform = String(data[i][1]);
    var imp = Number(data[i][3]) || 0;
    var clicks = Number(data[i][4]) || 0;
    var cost = Number(data[i][5]) || 0;
    var cv = Number(data[i][6]) || 0;

    summary.impressions += imp;
    summary.clicks += clicks;
    summary.cost += cost;
    summary.conversions += cv;

    if (!summary.byPlatform[platform]) {
      summary.byPlatform[platform] = {
        impressions: 0, clicks: 0, cost: 0, conversions: 0
      };
    }
    summary.byPlatform[platform].impressions += imp;
    summary.byPlatform[platform].clicks += clicks;
    summary.byPlatform[platform].cost += cost;
    summary.byPlatform[platform].conversions += cv;
  }

  return summary;
}

運用メモ テンプレートシートには数式やグラフをあらかじめ設定しておくと、データが入力された時点で自動的に可視化されます。テンプレートの変更はシートを編集するだけで反映され、スクリプトの修正は不要です。テンプレートとスクリプトの責務を分離しておくと保守が楽になります。

トリガーの設定

GASのトリガーは、スクリプトを自動的に実行するための仕組みです。手動で実行ボタンを押す必要がなくなります。

トリガーの種類

GASトリガーの種別と特徴時間ベーストリガー定期的に自動実行分単位(1/5/10/15/30分)リアルタイム監視向け。API上限に注意時間単位(1/2/4/6/8/12時間)日次レポートの更新に最適日単位(毎日 指定時刻)朝のアラート通知、日次集計に週単位 / 月単位週次・月次レポートの自動生成イベントトリガー特定のイベントで起動onOpenスプレッドシートを開いたときonEditセルが編集されたときonFormSubmitGoogleフォームの回答送信時onChange構造変更(行追加・シート削除等)時

トリガーの設定方法

トリガーはスクリプトエディタから手動設定する方法と、コードから動的に作成する方法があります。

手動設定はエディタ左側の時計アイコン(トリガー)から行います。コードから作成する場合は以下のように書きます。

/**
 * トリガーを初期設定する
 * 既存トリガーを削除してから新規作成する。
 * この関数は手動で1回だけ実行する。
 */
function setupTriggers() {
  // 既存トリガーの削除(重複防止)
  var triggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < triggers.length; i++) {
    ScriptApp.deleteTrigger(triggers[i]);
  }

  // 日次レポート集約: 毎日 午前8-9時に実行
  ScriptApp.newTrigger('aggregateAdReport')
    .timeBased()
    .everyDays(1)
    .atHour(8)
    .create();

  // CPAアラート: 毎日 午前9-10時に実行
  ScriptApp.newTrigger('sendCpaAlert')
    .timeBased()
    .everyDays(1)
    .atHour(9)
    .create();

  // 月次レポート: 毎月1日に実行
  ScriptApp.newTrigger('generateMonthlyReport')
    .timeBased()
    .onMonthDay(1)
    .atHour(10)
    .create();

  Logger.log('トリガーを設定しました');
}

運用メモ GASのトリガーは「午前8時〜9時」のように1時間の幅で実行されます。正確な時刻を指定することはできません。広告データの反映タイミング(Google広告は前日分が午前中に確定するケースが多い)を考慮して、レポート集約は午前9時以降に設定するのが安全です。

実行頻度の設計

トリガーの実行頻度は、用途に応じて適切に設計してください。

処理推奨頻度理由
日次レポート集約毎日1回(午前)前日データが確定したタイミングで
異常値アラート毎日1回(午前)朝の業務開始前に確認できるよう
月次レポート生成毎月1日前月データが確定した直後に
リアルタイム監視1〜4時間ごと大規模施策の初日など限定的に

頻度が高すぎるとAPIの呼び出し回数やスクリプトの実行時間が制約に達するリスクがあります。必要十分な頻度に抑えてください。

GASの制限と注意点

GASには無料で使える代わりに、いくつかの制約があります。実装前に把握しておくことで、想定外のエラーを避けられます。

主な制限値

制限項目上限値備考
スクリプトの実行時間6分/回トリガー実行でも同じ
日次のトリガー実行時間合計90分/日全トリガーの合計
UrlFetch呼び出し20,000回/日(個人アカウント)、100,000回/日(Google Workspace)外部APIへのリクエスト
メール送信100通/日MailApp経由
スプレッドシートのセル数1,000万セル/ファイルシート全体の合計
スクリプトプロパティ500KB/プロジェクト設定値の保存領域

6分制限への対策

大量のデータを処理する場合、6分の実行時間制限に抵触する可能性があります。以下のパターンで対処できます。

/**
 * 大量データを分割処理する
 * スクリプトプロパティに処理位置を保存し、
 * 複数回のトリガー実行で全件処理する。
 */
function processBatchData() {
  var props = PropertiesService.getScriptProperties();
  var startRow = Number(props.getProperty('BATCH_START_ROW')) || 1;
  var batchSize = 500;
  var startTime = new Date().getTime();
  var timeLimit = 5 * 60 * 1000; // 5分(余裕を持たせる)

  var sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName('データ');
  var lastRow = sheet.getLastRow();

  if (startRow > lastRow) {
    props.deleteProperty('BATCH_START_ROW');
    Logger.log('全件の処理が完了しました');
    return;
  }

  var endRow = Math.min(startRow + batchSize - 1, lastRow);
  var range = sheet.getRange(startRow, 1, endRow - startRow + 1, 8);
  var data = range.getValues();

  for (var i = 0; i < data.length; i++) {
    // 各行の処理
    processRow(data[i]);

    // 経過時間チェック
    var elapsed = new Date().getTime() - startTime;
    if (elapsed > timeLimit) {
      props.setProperty('BATCH_START_ROW', String(startRow + i + 1));
      Logger.log('時間制限のため中断。次回は ' + (startRow + i + 1) + ' 行目から再開');
      return;
    }
  }

  // 次のバッチを設定
  props.setProperty('BATCH_START_ROW', String(endRow + 1));
  Logger.log(startRow + '〜' + endRow + ' 行目を処理しました');
}

/**
 * 1行分のデータを処理する
 * @param {Array} row - 行データの配列
 */
function processRow(row) {
  // 実際の処理内容をここに記述
  Utilities.sleep(10); // APIレートリミット対策の待機
}

エラーハンドリングの基本

本番で運用するスクリプトには、必ずエラーハンドリングを実装してください。エラー発生時に通知がなければ、気づかないまま処理が止まり続けることになります。

/**
 * エラーハンドリングのラッパー
 * メインの処理関数をtry-catchで囲み、
 * エラー発生時にメールとSlackで通知する。
 * @param {Function} mainFunc - 実行するメイン関数
 * @param {string} taskName - 処理名(通知メッセージ用)
 */
function runWithErrorHandling(mainFunc, taskName) {
  var props = PropertiesService.getScriptProperties();
  var notifyEmail = props.getProperty('NOTIFY_EMAIL');
  var webhookUrl = props.getProperty('SLACK_WEBHOOK_URL');

  try {
    mainFunc();
    Logger.log(taskName + ' が正常に完了しました');
  } catch (e) {
    var errorMsg = taskName + ' でエラーが発生しました\n\n' +
      'エラー: ' + e.message + '\n' +
      'スタック: ' + e.stack + '\n' +
      '日時: ' + new Date().toLocaleString('ja-JP');

    // メール通知
    if (notifyEmail) {
      MailApp.sendEmail(
        notifyEmail,
        '【エラー】' + taskName,
        errorMsg
      );
    }

    // Slack通知
    if (webhookUrl) {
      postToSlack(webhookUrl, errorMsg);
    }

    throw e;
  }
}

設定値管理のベストプラクティス

設定値の管理方法は、用途に応じて使い分けます。

方法適する用途メリットデメリット
スクリプトプロパティAPIキー、Webhook URLコードに機密情報が含まれない設定画面からの確認がやや手間
設定シート閾値、宛先、対象リスト非エンジニアでも変更可能シートの保護設定が必要
コード内の定数変更頻度の低い固定値シンプルで把握しやすい変更にコード編集が必要

機密情報はスクリプトプロパティ、運用で変更する値は設定シートに配置するのが実用的です。コード内の定数は、変更頻度が極めて低い技術的なパラメータ(バッチサイズ等)に限定してください。

運用メモ GASのスクリプトは、エラーが発生しても次回のトリガー実行には影響しません。ただし、連続してエラーが発生するとGoogleがトリガーを自動的に無効化する場合があります。エラー通知を必ず実装し、問題を早期に検知できる状態にしておいてください。

まとめ

GASは広告運用の定型業務を自動化するための有力な手段です。Googleサービスとの連携が組み込まれており、スプレッドシートのデータ操作からSlack通知まで、幅広い処理を無料で自動化できます。

導入のステップとしては、まず最もシンプルなレポート集約から始めるのが現実的です。スプレッドシート間のデータ転記を自動化するだけでも、日々の業務時間を大幅に削減できます。

次のステップとして、アラート通知を追加すれば、異常値の早期検知が可能になります。月次レポートの自動生成まで実装できれば、レポート業務のほぼ全体をカバーできます。

スクリプトの品質を高めるには、エラーハンドリングと設定値の外出しが重要です。エラーが起きても気づけること、設定変更にコード修正が不要であること。この2点を満たしていれば、安定した自動化基盤として長期的に運用できます。

r
ryottaman

運用型広告のコンサルタント。Google広告・Meta広告・Yahoo!広告を中心に10年以上の実務経験。

この記事について感想やご質問を送れます

誤りの指摘、補足情報、ご質問など、お気軽にどうぞ。