天気予報による地域ターゲティングの配信切替

スクリプトの概要

本ページでは、Yahoo!天気・災害の天気予報データと連動させたディスプレイ広告の配信を実現するスクリプトをご紹介します。

本機能をご利用いただくことで、スクリプトを実行するたびに、指定した広告グループの地域ターゲティングに
・本日の天気予報の天気詳細(=天気テロップ)が指定したターゲット天気を含む  
かつ
・指定した除外天気を含まない
エリアを設定します。 (地域ターゲティングについてはこちらのヘルプをご確認ください)

なお本サンプルコードでは、
・Yahoo!天気・災害の天気テロップで「雨」を含む天気予報の地域
 かつ
・「'大雨'または '暴風雨'」を含まない天気予報の地域
を配信対象として指定する仕様になっています。(任意で変更可)

※本ソリューションでは、天気予報データは「都道府県(=県庁所在地)」を取得対象としています。(ただし東京都の場合は千代田区)

【スクリプト実行イメージ】

ご利用イメージ

実施したい内容に合わせて、「天気予報の条件」と「広告のアクション」を決めます。
実施したい内容 天気予報の条件 広告のアクション
スクリプトによる自動アクション 手動での設定や入稿(任意)
いままでの配信実績から「雨の日はCVRが高いはず」という仮説が立てられたので、天気予報が「雨」の日に実際に配信して検証したい Yahoo!天気・災害の天気テロップで「雨」を含む天気 天気予報で「雨」の地域を配信対象とするように、広告グループの地域ターゲティングを自動で切り替える ・雨の日用のデザインにした広告で配信する
・入札を調整する
雪関連の商材なので、需要があると想定される地域(=天気予報で「雪」の地域)だけに配信したい Yahoo!天気・災害の天気テロップで「雪」を含む天気 天気予報で「雪」の地域を配信対象とするように、広告グループの地域ターゲティングを自動で切り替える ・雪関連の商材の広告を配信する

ご利用の流れ

最初に

天気連動配信を実施する広告グループをご準備してください。(既存で作成済みの広告グループでも、新規作成のどちらでも可)

<配信ステータスの設定について>
スクリプト実行後、広告グループの配信ステータス(オンオフ)は以下の挙動になります。挙動を考慮したうえで、配信開始前はキャンペーンをオフにしておくなど適した事前設定をお願いいたします。
天気予報の条件との合致有無 スクリプト実行後の挙動 補足
指定した条件に合致する天気予報が出た地域がある場合 広告グループの地域ターゲティング設定でその地域が配信対象となり
広告グループの配信ステータスはオンになる(またはオンのままとなる)
どちらの場合も、本スクリプトの実行では、キャンペーンの配信ステータスは変更されませんのでご安心ください
(オンの場合はオンのまま、オフの場合はオフのままとなります)
指定した条件に合致する天気予報が出た地域がない場合 広告グループの配信ステータスがオフになる

スクリプト設定の流れ

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

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

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

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

■配信を実施する広告グループの指定

次の例のように指定してください。(広告グループID:11111111の場合)
const TARGET_ADG_ID = 11111111;//対象の広告グループ
※広告グループの複数指定は不可
※両側を''(半角シングルクォーテーション)で囲むのは不要

■天気予報の条件の指定

天気予報の条件の指定は、Yahoo!天気・災害の1日単位の天気テロップを文字列の「部分一致(~を含む、含まない)」で指定する仕様です。
「配信対象とする場合」と「配信対象"外"とする場合」の2つの条件が指定でき、「配信対象"外"とする場合」の条件は何も指定しないことも可能です。

【 配信対象とする天気予報の条件の指定例 】
次の例のように、配信対象として指定したい天気予報の条件を、' '(シングルクオーテーション)で囲んで指定してください。(天気テロップのうち「雨」を含む天気の場合)

const TARGET_TELOP = ['雨'];

【 配信対象"外"とする天気予報の条件の指定例 】
次の例のように、配信対象"外"として指定したい天気予報の条件を、' '(シングルクオーテーション)で囲んで指定してください。(天気テロップのうち「'大雨'または '暴風雨'」を含まない天気の場合)

const EXCLUDE_TELOP = ['大雨', '暴風雨'];

指定なしの場合は [ ] 内に何も記載しないでください。
const EXCLUDE_TELOP = [];
▽上記の例の条件で配信対象になる天気テロップ
「雨」を含む天気かつ「'大雨'または '暴風雨'」を含まない以下のテロップが対象となります。
晴一時 晴時々 晴のち 晴のちで雷を伴う 晴時々で雷を伴う
曇一時 曇時々 曇のち 曇のちで雷を伴う 曇時々で雷を伴う
時々晴 時々曇 のち晴 のち曇
のち雪 時々雪 で雷を伴いのち晴 で雷を伴いのち曇 で雷を伴いのち雪
で雷を伴う 雪のち 雪で雷を伴いのち

※サンプルコードの例以外の天気テロップをご利用になられる場合は、
 以下リファレンス内の1日天気の天気コード対応表の「天気詳細(=天気テロップ)」をご参照ください。
 リファレンス:https://ads-developers.yahoo.co.jp/ja/ads-script/product-guide/reference/interfaces/weatherapp.ForecastWeather.html

■配信対象とする地域(都道府県)指定の例

指定した天気予報の条件に合致した場合に広告の配信対象とする地域は、初期値で47都道府県としています。
配信対象"外"としたい地域がある場合は、その都道府県を初期値から削除してください。

(初期値:47都道府県を指定する場合 )
const TARGET_AREA = ["北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県",
  "茨城県", "栃木県", "群馬県", "埼玉県", "千葉県", "東京都", "神奈川県", "新潟県", "富山県",
  "石川県", "福井県", "山梨県", "長野県", "岐阜県", "静岡県", "愛知県", "三重県", "滋賀県",
  "京都府", "大阪府", "兵庫県", "奈良県", "和歌山県", "鳥取県", "島根県", "岡山県", "広島県",
  "山口県", "徳島県", "香川県", "愛媛県", "高知県", "福岡県", "佐賀県", "長崎県", "熊本県",
  "大分県", "宮崎県", "鹿児島県", "沖縄県"];//47都道府県
(関東のみを指定する場合)
const TARGET_AREA = ["茨城県", "栃木県", "群馬県", "埼玉県", "千葉県", "東京都", "神奈川県"];

■メール、Slackの指定例

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

サンプルコード

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

/* このソースコードは MIT License のもとで提供されています。
https://ads-developers.yahoo.co.jp/ja/ads-script/post/30418913.html
■スクリプト内容
雨用広告グループの地域ターゲティングに、本日の天気予報の天気詳細が指定したターゲット天気を含むかつ、指定した除外天気を含まないエリアを振り分ける。
条件を満たすエリアがない場合は広告グループをオフにする。
■利用方法
1.当スクリプトを、ディスプレイ広告のスクリプトとしてください。
2.各定数を以下のように設定してください
■定数
・TARGET_ADG_ID:対象となる広告グループID
・TARGET_TELOP:対象となる天気(部分一致)
・EXCLUDE_TELOP:対象外となる天気(部分一致)
・FLAG_MAIL:結果をメール送信するならtrue、しないならfalse
・MAIL_TO:メール送信先のYahoo! BusinessID
・MAIL_TITLE:メールタイトル
・FLAG_SLACK:Slack配信するときはtrue、配信しないときはfalse
・URL_FETCH_APP:SlackのWebhook URL
*/
//設定が必要な定数
const TARGET_ADG_ID = 11111111;//対象の広告グループ
const TARGET_TELOP = ['雨'];
const EXCLUDE_TELOP = ['大雨', '暴風雨'];
//設定が任意の定数
const TARGET_AREA = ["北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県",
  "茨城県", "栃木県", "群馬県", "埼玉県", "千葉県", "東京都", "神奈川県", "新潟県", "富山県",
  "石川県", "福井県", "山梨県", "長野県", "岐阜県", "静岡県", "愛知県", "三重県", "滋賀県",
  "京都府", "大阪府", "兵庫県", "奈良県", "和歌山県", "鳥取県", "島根県", "岡山県", "広島県",
  "山口県", "徳島県", "香川県", "愛媛県", "高知県", "福岡県", "佐賀県", "長崎県", "熊本県",
  "大分県", "宮崎県", "鹿児島県", "沖縄県"];//47都道府県
const FLAG_MAIL = false;
const MAIL_TO = ['Yahoo! BusinessID'];
let MAIL_TITLE = '天気による配信切り替え';
const FLAG_SLACK = false;
const URL_FETCH_APP = 'SLACK_WEBHOOK_URL';
//設定が不要な定数(変更すると動かなくなります)
const accountId = AdsUtilities.getCurrentAccountId();
let TEXT_MESSAGE_ARRAY = [];
let globalCampaignId = 0;//対象ADGの属するキャンペーン
//実処理
function main() {
  try {
    //天気を取得
    const targetWeatherAreaOperands = getTargetWeatherAreaArray();
    //ターゲティングを変更
    changeAdGroupTarget(targetWeatherAreaOperands);
    sendMailOrSlack();
  } catch (error) {
    //エラー処理
    MAIL_TITLE = '!!エラー発生!!' + MAIL_TITLE;
    logAndMessage('【!!エラーが発生しました!!】\nアカウントID:' + accountId +
      'の管理画面のログを確認してください。\nエラー内容:' + error);
    sendMailOrSlack();
    throw error;
  }
}
//対象地域の天気を取得
//戻り値:対象となる都道府県リスト
function getTargetWeatherAreaArray() {
  let targetWeatherAreaOperands = [];
  const dict = getGeographicLocationDict();
  globalCampaignId = getCampaignIdFromAdGroupId();
  for (let i = 0; i < TARGET_AREA.length; i++) {
    const areaName = TARGET_AREA[i];
    const weather = WeatherApp.getWeatherByName(
      areaName,
      'TODAY'
    );
    const todayWeatherTelop = weather[0].day[0].weather.telop;//本日の天気
    if (isTargetWeather(todayWeatherTelop)) {
      //条件を満たすときだけoperand作成
      targetWeatherAreaOperands.push(createGeoTargetOperand(dict[areaName]));
    }
    Logger.log(areaName + '/天気:' + todayWeatherTelop);
  };
  // ログ出力
  const conditionStr = createLogStr();
  if (targetWeatherAreaOperands.length == 0) {
    logAndMessage('条件を満たす地域はありませんでした\n条件:' + conditionStr);
  } else {
    logAndMessage('条件を満たす都道府県は' + targetWeatherAreaOperands.length + '件でした。\n条件:' + conditionStr);
  }
  return targetWeatherAreaOperands;
}
//条件を満たすかの確認
function isTargetWeather(todayWeatherTelop) {
  let containsTarget = false;
  let doesNotContainExclude = true;
  // TARGET_TELOPのいずれかがtodayWeatherTelopに含まれているかどうかをチェック
  for (let i = 0; i < TARGET_TELOP.length; i++) {
    if (todayWeatherTelop.includes(TARGET_TELOP[i])) {
      containsTarget = true;
      break;
    }
  }
  // EXCLUDE_TELOPのいずれもtodayWeatherTelopに含まれていないかどうかをチェック
  for (let i = 0; i < EXCLUDE_TELOP.length; i++) {
    if (todayWeatherTelop.includes(EXCLUDE_TELOP[i])) {
      doesNotContainExclude = false;
      break;
    }
  }
  // 全ての条件を満たすかどうかを返す
  return containsTarget && doesNotContainExclude;
}
function createLogStr() {
  let logStr = TARGET_TELOP.join(',') + 'を含む';
  if (EXCLUDE_TELOP.length > 0) {
    logStr += 'かつ、' + EXCLUDE_TELOP.join(',') + 'を含まない';//ログ用条件
  } else {
    logStr += '(除外条件:なし)';
  }
  return logStr;
}
//地域ターゲティングのコードを取得し、{都道府県名:コード}のdictionaryを返す
function getGeographicLocationDict() {
  const dict = {};
  const geographicLocations = Display.DictionaryService.getGeographicLocation({
    geographicLocationType: "TARGETING",
    lang: "JA"
  }).rval;
  geographicLocations.values.forEach(value => {
    const location = value.geographicLocation;
    if (TARGET_AREA.includes(location.fullName)) {
      //定数で指定された都道府県のみdictに格納
      dict[location.fullName] = location.code;
    }
  });
  return dict;
}
//指定した広告グループIDの所属するキャンペーンIDを取得
function getCampaignIdFromAdGroupId() {
  const adGroups = Display.AdGroupService.get({
    accountId: accountId,
    adGroupIds: [TARGET_ADG_ID],
  }).rval;
  if (adGroups.totalNumEntries == 0) {
    throw new Error('指定した広告グループIDに合致するデータが存在しません');
  }
  return adGroups.values[0].adGroup.campaignId;
}
//GEO_TARGETのoperandを作成する
function createGeoTargetOperand(geoCode) {
  return {
    accountId: accountId,
    campaignId: globalCampaignId,
    adGroupId: TARGET_ADG_ID,
    target: {
      targetType: 'GEO_TARGET',
      targetId: geoCode,
      geoTarget: {
        areaSearchType: 'GEO',
      }
    }
  };
}
//ターゲティング変更・広告グループのオンオフ変更
function changeAdGroupTarget(targetWeatherAreaOperands) {
  // 雨の広告グループのターゲティングを変更
  if (targetWeatherAreaOperands.length > 0) {
    updateAdGroupTarget(targetWeatherAreaOperands);
  } else {
    removeAdGroupTarget();
  }
}
//ターゲティング差し替え・配信オンに
function updateAdGroupTarget(operandArr) {
  //ターゲティング差し替え
  const adgTarget = Display.AdGroupTargetService.replace({
    accountId: accountId,
    operand: operandArr
  });
  if (adgTarget.errors != null || adgTarget.rval.errors != null) {
    //ターゲティング更新に失敗したときは処理を止めるためエラーをスロー
    throw new Error('広告グループID:' + TARGET_ADG_ID + 'のターゲティング更新でエラーが発生しました。管理画面のログをご確認ください。');
  }
  //ADGを配信オンにする
  updateAdGroupStatus('ACTIVE');
}
//ターゲティング削除・配信オフに
function removeAdGroupTarget() {
  //まずget
  const adGroupAds = Display.AdGroupTargetService.get({
    accountId: accountId,
    adGroupIds: [TARGET_ADG_ID],
    targetTypes: ['GEO_TARGET']
  }).rval;
  //ターゲティング削除
  if (adGroupAds.values != null) {
    //元々nullなら削除する必要はない
    let newOperand = [];
    for (let i = 0; i < adGroupAds.values.length; i++) {
      const adGroupTargetList = adGroupAds.values[i].adGroupTargetList;
      newOperand.push(adGroupTargetList);
    }
    const removedAdGroup = Display.AdGroupTargetService.remove({
      accountId: accountId,
      operand: newOperand
    });
    if (removedAdGroup.errors != null || removedAdGroup.rval.errors != null) {
      //ターゲティング更新に失敗したときは処理を止めるためエラーをスロー
      throw new Error('広告グループID:' + adGroupId + 'のターゲティング更新でエラーが発生しました。管理画面のログをご確認ください。');
    }
  }
  //ADGを配信オフにする
  updateAdGroupStatus('PAUSED');
}
//広告グループの配信切り替え
function updateAdGroupStatus(afterStatus) {
  //まず取得する
  const adGroups = Display.AdGroupService.get({
    accountId: accountId,
    adGroupIds: [TARGET_ADG_ID],
  }).rval;
  const beforeStatus = adGroups.values[0].adGroup.userStatus;
  //変更がある場合のみsetする
  if (beforeStatus != afterStatus) {
    let setAdGroup = {
      accountId: accountId,
      adGroupId: TARGET_ADG_ID,
      campaignId: globalCampaignId,
      userStatus: afterStatus
    };
    const adGroupsSet = Display.AdGroupService.set({
      accountId: accountId,
      operand: [setAdGroup],
    }).rval;
    logAndMessage('広告グループ「' + adGroupsSet.values[0].adGroup.adGroupName + '」を ' + afterStatus + ' にしました。');
  }
}
//ログ、メールなどに流すテキスト
function logAndMessage(text) {
  Logger.log(text);
  TEXT_MESSAGE_ARRAY.push(text);
}
function sendMailOrSlack() {
  validateAndSendMail();
  validateAndSendSlack();
}
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: MAIL_TITLE,
      body: TEXT_MESSAGE_ARRAY.join('\n'),
    });
  }
}
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'),
      }),
    });
  }
}

結果確認

天気予報データに基づき、対象の広告グループの地域ターゲティングが紐づいていれば成功です。