審査落ちキーワードの通知

スクリプトの概要

本ページでは、審査落ちしたキーワードがあった場合に、該当のキーワードの情報をGoogleスプレッドシートへ一覧で出力し、出力結果をメールまたはSlackで通知できるスクリプトをご紹介します。

本機能をご利用いただくことで、広告管理ツール上で確認するしかなかった審査落ち理由も含めてGoogleスプレッドシート上で一覧で確認できるため、キーワードの審査結果の確認工数が減少し運用の効率化が図れます。

キーワードの審査状況のステータスについて

審査状況のステータスについてについては、こちらのヘルプをご覧ください。

【Googleスプレッドシートへ出力される項目】
以下の項目が出力されます。
日(スクリプト実行日)
キャンペーンID
キャンペーン名
広告グループID
広告グループ名
キーワードID
キーワード
マッチタイプ
配信設定
審査状況
審査落ち理由
最終リンク先URL

ご利用の流れ

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

2.連携したGoogleアカウントにて、審査落ちキーワードを出力するGoogleスプレッドシートを新規作成してください。
 作成したスプレッドシートIDを確認してメモしておいてください。スクリプト内の定数の設定で利用します。
 ※GoogleスプレッドシートIDの取得方法についてはこちらをご覧ください。

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

4.管理画面上にてスクリプトの設定を完了後、必要に応じてスクリプトの実行頻度の設定をしてください。
本スクリプトでは1日1回(時間は任意)の設定が推奨です。
 ※スクリプトの実行頻度の設定手順はこちらをご覧ください。

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

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

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

次の例のように、' '(シングルクオーテーション)で囲んで指定してください。(スプレッドシートID:'12345abcde'、シート名:'Sheet1'の場合)
const SPREAD_SHEET_ID = '12345abcde'; 
const SHEET_NAME = 'Sheet1';

■チェック対象のキーワードの配信状況について

審査落ちをチェックする対象とするキーワードの配信状況を指定可能です。ご利用状況に合わせていずれかをご指定ください。
const TARGET_AD_STATUS = 'BOTH';// BOTH:両方、ACTIVE:配信オンのみ、PAUSED:配信オフのみ

■通知の頻度の指定について

スクリプトの実行結果を通知する頻度を指定できます。ご利用状況に合わせていずれかをご指定ください。
通知の頻度 解説
ON_ADDED 前回のスクリプト実行後、追加で審査落ちキーワードがある場合のみ通知します
ALWAYS スクリプトの実行後、毎回通知します
NONE 通知自体しません
const ALERT_TYPE = 'ON_ADDED';// ON_ADDED:追加がある場合のみ、ALWAYS:毎回実行後、NONE:通知自体しない

ご注意

通知の頻度を「NONE」にしていても、スクリプトの実行エラーの場合はメール/Slack通知されます。
実行エラーの場合も通知が不要の場合は、メール、Slackの指定例を参考にFLAG_MAILとFLAG_SLACKを両方falseにしてください。

■メール、Slackの指定例

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

サンプルコード

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

/*このソースコードは MIT License のもとで提供されています。
https://ads-developers.yahoo.co.jp/ja/ads-script/post/30418913.html
■スクリプト内容
アカウントに、実行時点で指定した配信状況の審査落ちのキーワードがあった場合にメール/Slack通知します。
■利用方法
1.当スクリプトを、検索広告のアカウントに設定してください。
2.定数を以下のように設定してください。
3.実行頻度を1日1回(時間は任意)に設定してください。
■定数
・SPREAD_SHEET_ID   //スプレットシートIDを指定
・SHEET_NAME        //出力シート名
・TARGET_KW_STATUS  //抽出対象とするキーワードの配信状況。BOTH:両方、ACTIVE:配信オンのみ、PAUSED:配信オフのみ。
・ALERT_TYPE        //通知の頻度。ON_ADDED:追加がある場合のみ、ALWAYS:毎回実行後、NONE:通知自体しない。
                      ※NONEにしていても、実行エラーの場合はメール/Slack通知されます。
                       実行エラーの場合も通知しなくていい場合は、FLAG_MAILとFLAG_SLACKを両方falseにしてください。
・FLAG_MAIL         //結果をメール送信するならtrue、しないならfalse
・MAIL_TO           //メール送信先のYahoo! BusinessID
・MAIL_TITLE        //メールタイトル
・FLAG_SLACK        //結果をSlack送信するならtrue、しないならfalse
・URL_FETCH_APP     //SlackのWebhook URL
■制限事項
・取得できる審査落ちのKWは、1万件が上限となります。
 */
//設定が必要な定数
const SPREAD_SHEET_ID = 'SPREAD_SHEET_ID';
const SHEET_NAME = 'SHEET_NAME';
const TARGET_KW_STATUS = 'BOTH';// BOTH:両方、ACTIVE:配信オンのみ、PAUSED:配信オフのみ
const ALERT_TYPE = 'ON_ADDED';// ON_ADDED:追加がある場合のみ、ALWAYS:毎回実行後、NONE:通知自体しない
const FLAG_MAIL = false;
const MAIL_TO = ['Yahoo! JAPAN Business ID'];
let MAIL_TITLE = '審査落ちキーワード通知';
const FLAG_SLACK = false;
const URL_FETCH_APP = 'SLACK_WEBHOOK_URL';
//設定が不要な定数(変更するとエラーになります)
const accountId = AdsUtilities.getCurrentAccountId();
const productType = AdsUtilities.getProductType();
let TEXT_MESSAGE_ARRAY = [];
const SPREAD_SHEET_URL = 'https://docs.google.com/spreadsheets/d/' + SPREAD_SHEET_ID;
const SS_HEADER = ['日', 'キャンペーンID', 'キャンペーン名', '広告グループID', '広告グループ名',
  'キーワードID', 'キーワード', 'マッチタイプ', '配信設定', '審査状況', '審査落ち理由', '最終リンク先URL'];
const keywordIdColIdx = 5;//並び順変えたとき対策
function main() {
  try {
    if (productType == 'MCC' || productType == 'DISPLAY') {
      throw new Error('このスクリプトは検索広告アカウントでのみ実行できます');
    }
    //審査落ちキーワードを取得
    const disapprovalKeywords = getDisapprovalKeywords();
    if (disapprovalKeywords != null && disapprovalKeywords.length > 0) {
      //対象がある場合のみ出力・通知
      const addCnt = outputdisapprovalKeywordsToSS(disapprovalKeywords);
      if (ALERT_TYPE == 'ALWAYS'//毎回通知は常に通知
        || (ALERT_TYPE == 'ON_ADDED' && addCnt > 0)//追加がある場合のみ通知
      ) {
        sendMailAndSlack();
      }
    } else {
      Logger.log('審査落ちキーワードはありませんでした。');
    }
  } catch (error) {
    MAIL_TITLE = '!!エラー発生!!' + MAIL_TITLE;
    logAndMessage('!!エラーが発生しました!!詳細は管理画面のログをご確認ください。' + error);
    sendMailAndSlack();
    throw Error(error);
  }
}
//マスタから審査落ちキーワードを取得
function getDisapprovalKeywords() {
  const operand = {
    accountId: accountId,
    numberResults: 10000,
    approvalStatuses: [
      'PRE_DISAPPROVED', 'POST_DISAPPROVED',
    ],
    use: 'BIDDABLE',
    userStatuses: [TARGET_KW_STATUS]
  };
  if (TARGET_KW_STATUS == 'BOTH') {
    //両方の場合はuserStatusesで絞らない
    delete operand.userStatuses;
  }
  const criterions = Search.AdGroupCriterionService.get(operand).rval.values;
  Logger.log('対象データを取得しました');
  return criterions;
}
//スプレッドシートに審査落ちキーワードを出力する
function outputdisapprovalKeywordsToSS(disapprovalKeywords) {
  //出力用データ生成
  let outputData = createOutputData(disapprovalKeywords);
  Logger.log('出力データ生成が完了しました');
  //出力
  const ss = validateAndGetSpreadsheet();
  const sh = validateAndGetSheet(ss);
  const previousSSData = sh.getDataRange().getValues();//前回データ
  //ヘッダを付ける前のデータ行だけを比較する
  const addCnt = comparePreviousAndCurrent(previousSSData, outputData);
  logAndMessage('前回からの追加は' + addCnt + '件です。');
  //洗い替え
  sh.clear();
  outputData.unshift(SS_HEADER);//ヘッダ行を先頭に追加
  sh.getRange('A1').setValues(outputData);
  logAndMessage('スプレッドシートにデータを出力しました。\n' + SPREAD_SHEET_URL);
  return addCnt;
}
//出力データを生成する
function createOutputData(disapprovalKeywords) {
  const disapprovalReasonDictArr = getDisapprovalReasons();
  const today = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd');
  let outputData = [];
  for (let i = 0; i < disapprovalKeywords.length; i++) {
    const adGroupCriterion = disapprovalKeywords[i].adGroupCriterion;
    let titleArray = [];
    for (let j = 0; j < adGroupCriterion.biddableAdGroupCriterion.disapprovalReasonCodes.length; j++) {
      titleArray.push(disapprovalReasonDictArr[adGroupCriterion.biddableAdGroupCriterion.disapprovalReasonCodes[j]]);
    }
    outputData.push([
      today,
      adGroupCriterion.campaignId,
      adGroupCriterion.campaignName,
      adGroupCriterion.adGroupId,
      adGroupCriterion.adGroupName,
      adGroupCriterion.criterion.criterionId,
      adGroupCriterion.criterion.keyword.text,
      getMatchTypeStr(adGroupCriterion.criterion.keyword.keywordMatchType),
      (adGroupCriterion.biddableAdGroupCriterion.userStatus == 'ACTIVE') ? 'オン' : 'オフ',
      getApprovalStatusStr(adGroupCriterion.biddableAdGroupCriterion.approvalStatus),
      titleArray.join(','),
      adGroupCriterion.biddableAdGroupCriterion.finalUrl
    ]);
  }
  return outputData;
}
//審査落ち理由をDictionaryServiceから取得する
function getDisapprovalReasons() {
  const dictionaries = Search.DictionaryService.getDisapprovalReason({
    lang: 'JA',
  }).rval;
  let dictionaryArray = [];
  for (let i = 0; i < dictionaries.totalNumEntries; i++) {
    let dictionary = dictionaries.values[i].disapprovalReason;
    dictionaryArray[dictionary.disapprovalReasonCode] = dictionary.title;
  }
  return dictionaryArray;
}
//審査状況の文字列生成
function getApprovalStatusStr(approvalStatus) {
  switch (approvalStatus) {
    case 'PRE_DISAPPROVED':
      return '掲載不可';
    case 'POST_DISAPPROVED':
      return '掲載停止';
    default://ないとは思うが
      return '';
  }
}
function getMatchTypeStr(matchType) {
  const matchTypes = {
    'EXACT': '完全一致',
    'BROAD': 'インテントマッチ',
    'PHRASE': 'フレーズ一致'
  };
  return matchTypes[matchType];
}
//前回と今回を比較して追加された数を取得する
function comparePreviousAndCurrent(previousSSData, outputData) {
  previousSSData.shift();//前回スプシ内容全件なので、ヘッダは抜いておく
  const previousAdIdSet = createAdIdSet(previousSSData);
  const currentAdIdSet = createAdIdSet(outputData);
  //比較
  let addCnt = 0
  currentAdIdSet.forEach(function (currentAdId) {
    if (!previousAdIdSet.has(currentAdId)) {
      addCnt++;
    }
  })
  return addCnt;
}
//キーワードIDのSetを生成する
function createAdIdSet(ssData) {
  let adIdSet = new Set();//重複排除するためSetを使う
  for (let i = 0; i < ssData.length; i++) {
    const adId = Number(ssData[i][keywordIdColIdx]);
    adIdSet.add(adId);
  }
  return adIdSet;
}
//スプレッドシート部品
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 sendMailAndSlack() {
  validateAndSendSlack();
  validateAndSendMail();
}
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:
          '【アカウントID:' + accountId + '】' + MAIL_TITLE + '\n' +//Slackもタイトルがあった方がいい
          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')
    });
  }
}

結果確認

審査落ちしたキーワードがあった場合に、Googleスプレッドシートに結果が出力されていれば成功です。