Yahoo!広告 スクリプト | Developer Center
English事例集(入稿系)
・Q.自動入札のスポット調整を複数アカウント一括で設定したいのですが、可能ですか?・Q.当日の天気が雨の地域のみに広告配信したいのですが、可能ですか?
・Q.当日の予報最高気温が指定した気温以上の地域のみに広告配信したいのですが、可能ですか?
Q.自動入札のスポット調整を複数アカウント一括で設定したいのですが、可能ですか?
A.はい、可能です。
以下のスクリプトで、スプレッドシートで指定した、自動入札スポット調整の設定を一括で設定・更新いただけます。
以下からスプレッドシートのテンプレートをコピーしてご利用ください。
テンプレートをコピー
※当スクリプトはMCCアカウントまたは検索広告アカウントに設定いただけます。
動作確認バージョン:v202406
コードサンプル ここをクリックすると折り畳みます。
/* このソースコードは MIT License のもとで提供されています。
https://ads-developers.yahoo.co.jp/ja/ads-script/post/30418913.html
■スクリプト内容
スプレッドシートで指定した、自動入札スポット調整の設定を一括で設定・更新する。
■利用方法
1.当スクリプトを、MCCアカウントまたは検査広告アカウントのスクリプトとしてください。
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
■制限事項
・スプレッドシート内の「スポット調整名」列はアカウントごとに一意になるようにしてください(重複の場合エラーとなります)
・「対象のキャンペーンID」列は半角カンマ(,)で区切ってください。それ以外の記号で区切ると、先頭1件のみ対象となります
*/
//設定が必要な定数
const SPREAD_SHEET_ID = '';
const SHEET_NAME = '';
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 = [];
const SS_COL_ENUM = {
AccountId: 0,
TargetCampaignIds: 1,
BiddingSeasonalityAdjustmentName: 2,
StartDate: 3,
StartTime: 4,
EndDate: 5,
EndTime: 6,
conversionRate: 7,
Device: 8,
Description: 9
};
const MODE = {
Add: 'add',
Set: 'set'
};
const CV_RATE_MAX = 900;
const CV_RATE_MIN = -90;
function main() {
try {
const ss = validateAndGetSpreadsheet();
const sh = validateAndGetSheet(ss);
const ssData = sh.getDataRange().getValues();
// アカウントIDでデータをまとめる
const dataByAccountId = groupDataByAccountId(ssData);
updateMaster(dataByAccountId);
sendSlackAndMail();
} catch (error) {
MAIL_TITLE = '!!エラー!!' + MAIL_TITLE;
TEXT_MESSAGE_ARRAY.unshift('!!エラーが発生しています!!詳細は管理画面のログをご確認ください。\n' + error);//先頭に入れる
sendSlackAndMail();
throw error;
}
}
//スプレッドシートからアカウント単位で行データをまとめる
function groupDataByAccountId(ssData) {
let dataByAccountId = {};
let errRowCnt = 0;
for (let i = 1; i < ssData.length; i++) {//ヘッダは飛ばす
const row = ssData[i];
if (!checkSSData(row, i + 1)) {
errRowCnt++;
continue;
}
const accountId = row[SS_COL_ENUM.AccountId];
if (!dataByAccountId[accountId]) {
dataByAccountId[accountId] = [];
}
dataByAccountId[accountId].push(row);
}
if (errRowCnt > 0) {
throw new Error('スプレッドシートの設定にエラーがある行が ' + errRowCnt + ' 行あるため、更新しませんでした。詳細は管理画面のログをご確認ください。\n');
}
return dataByAccountId;
}
//スプレッドシートのデータチェック
function checkSSData(row, rowNo) {
let errMsgArr = [];
//空欄チェック
if (hasEmptyCol(row)) {
errMsgArr.push('・空の列があります。');
}
//コンバージョン率チェック
if (!checkCVRate(row[SS_COL_ENUM.conversionRate])) {
errMsgArr.push('・コンバージョン率は-90%~+900%の間で指定してください。+と-は半角で指定してください。');
}
//日付チェック
let dateErrMsg = checkDate(row);
if (dateErrMsg.length > 0) {
errMsgArr.push(dateErrMsg);
}
if (errMsgArr.length > 0) {
//入力エラーがある場合
logAndMessage(rowNo + '行目の入力にエラーがあります。エラー内容は次のとおりです:\n' + errMsgArr.join('\n'));
return false;
}
//ここまでくればOK
return true;
}
//空の列チェック
function hasEmptyCol(row) {
for (const key in SS_COL_ENUM) {
const colIdx = SS_COL_ENUM[key];
if (colIdx == SS_COL_ENUM.Description) {
//説明は任意なのでチェック対象外
continue;
}
if (row[colIdx].toString().trim() == '') {
return true;
}
}
return false;
}
//コンバージョン率チェック
function checkCVRate(ssCvRate) {
const tempCVRate = parseInt(ssCvRate.replace('%', '').replace('%', ''));
// 数値かどうかのチェック
if (isNaN(tempCVRate)) {
return false;
}
return tempCVRate >= CV_RATE_MIN && tempCVRate <= CV_RATE_MAX;
}
//日付チェック
function checkDate(row) {
let errMsgArr = [];
const startDate = row[SS_COL_ENUM.StartDate];
const startTime = row[SS_COL_ENUM.StartTime];
const endDate = row[SS_COL_ENUM.EndDate];
const endTime = row[SS_COL_ENUM.EndTime];
//日付形式チェック
const startDateTime = new Date(startDate + ' ' + startTime);
if (startDate != '' && startTime != '' && isNaN(startDateTime.getDate())) {
errMsgArr.push('・開始日時の形式に間違いがあります。入力例 2024/04/01 12:00 実際に入力された値:' + startDate + ' ' + startTime);
}
const endDateTime = new Date(endDate + ' ' + endTime);
if (endDate != '' && endTime != '' && isNaN(endDateTime.getDate())) {
errMsgArr.push('・終了日時の形式に間違いがあります。入力例 2024/04/01 12:00 実際に入力された値:' + endDate + ' ' + endTime);
}
// 開始日が本日より未来かどうかをチェック
// 本日の日付を取得
let today = new Date();
today.setHours(0, 0, 0, 0); // 時間部分を0に設定
if (startDateTime < today) {//UIも本日ならOKにしている(時刻までは見ていない)
errMsgArr.push('・開始日付は本日以降の日付を入力してください。開始日:' + startDate);
}
//前後チェック
if (startDateTime > endDateTime) {
errMsgArr.push('・開始日時>終了日時になっています。開始日時<終了日時となるように入力してください。開始日:'
+ startDate + '、終了日' + endDate);
}
//14日以内かチェック
const dateDiff = (endDateTime - startDateTime) / (1000 * 60 * 60 * 24); // 日数差を計算
if (dateDiff > 14) {
errMsgArr.push('・開始日時~終了日時が14日以上になっています。開始日時~終了日時は14日以内で入力してください。開始日:'
+ startDate + '、終了日' + endDate);
}
return errMsgArr.join('\n');
}
//マスタ更新
function updateMaster(dataByAccountId) {
for (const targetAccountId in dataByAccountId) {
//アカウントごとに処理
const existingBiddingSeasonalityMap = getExistingBiddingSeasonalityMap(targetAccountId);
const accountRows = dataByAccountId[targetAccountId];
let addOperands = [];
let setOperands = [];
//あるなしで追加/更新を判定
for (let i = 0; i < accountRows.length; i++) {
const row = accountRows[i];
if (existingBiddingSeasonalityMap.has(row[SS_COL_ENUM.BiddingSeasonalityAdjustmentName])) {
const biddingSeasonalityAdjustmentId = existingBiddingSeasonalityMap.get(row[SS_COL_ENUM.BiddingSeasonalityAdjustmentName]).biddingSeasonalityAdjustmentId;
//あれば更新
setOperands.push(createOperand(row, MODE.Set, biddingSeasonalityAdjustmentId));
} else {
//なければ追加
addOperands.push(createOperand(row, MODE.Add));
}
}
//ここで追加・更新する
if (addOperands.length > 0) {
addOrUpdateBiddingSeasonality(targetAccountId, addOperands, MODE.Add);
}
if (setOperands.length > 0) {
addOrUpdateBiddingSeasonality(targetAccountId, setOperands, MODE.Set);
}
}
}
//operand作成
function createOperand(row, mode, biddingSeasonalityAdjustmentId) {
const targetCampaignIds = row[SS_COL_ENUM.TargetCampaignIds].split(',').map(id => parseInt(id.trim()));
//getとsetのoperandは同じ
const operand = {
accountId: row[SS_COL_ENUM.AccountId],
biddingSeasonalityAdjustmentName: row[SS_COL_ENUM.BiddingSeasonalityAdjustmentName],
description: row[SS_COL_ENUM.Description],
devices: createDeviceArr(row[SS_COL_ENUM.Device]),
startDateTime: formatDateTime(row[SS_COL_ENUM.StartDate], row[SS_COL_ENUM.StartTime]),
endDateTime: formatDateTime(row[SS_COL_ENUM.EndDate], row[SS_COL_ENUM.EndTime]),
conversionRate: getPercentToDecimal(row[SS_COL_ENUM.conversionRate]),
campaignIds: targetCampaignIds
};
if (mode == MODE.Set) {
operand.biddingSeasonalityAdjustmentId = biddingSeasonalityAdjustmentId;
}
return operand;
}
function formatDateTime(ssDate, ssTime) {
const ssDatePart = ssDate.split('/');
const year = ssDatePart[0];
const month = ssDatePart[1].padStart(2, '0');//0埋め2桁にする
const day = ssDatePart[2].padStart(2, '0');//0埋め2桁にする
const ssTimePart = ssTime.split(':');
const hour = ssTimePart[0].padStart(2, '0');//0埋め2桁にする
const minute = ssTimePart[1].padStart(2, '0');//0埋め2桁にする
return year + month + day + hour + minute + '00';
}
//コンバージョン率変換
function getPercentToDecimal(inputValue) {
const inputNum = inputValue.replace('%', '').replace('%', '');//数値チェックは事前に済
const outputMin = 0.1;
const outputMax = 10;
// 線形変換
const mappedValue = ((inputNum - CV_RATE_MIN) * (outputMax - outputMin)) / (CV_RATE_MAX - CV_RATE_MIN) + outputMin;
return mappedValue.toFixed(2);
}
//追加/更新処理
function addOrUpdateBiddingSeasonality(targetAccountId, operands, execMode) {
const biddingSeasonality = Search.BiddingSeasonalityAdjustmentService[execMode]({
accountId: targetAccountId,
operand: operands
}).rval;
let succeedCnt = 0;
for (let i = 0; i < biddingSeasonality.values.length; i++) {
if (biddingSeasonality.values[i].operationSucceeded) {
succeedCnt++;
} else {
logAndMessage('アカウントID:' + targetAccountId + 'の自動入札スポット調整名' +
operands[i].biddingSeasonalityAdjustmentName + ' の設定に失敗しました');
}
}
logAndMessage('アカウントID:' + targetAccountId + 'の自動入札スポット調整の設定が' + succeedCnt + '件成功しました');
}
//デバイスobj生成
function createDeviceArr(ssDeviceValue) {
if (ssDeviceValue == '全て') {
return [
'MOBILE', 'TABLET', 'DESKTOP'
];
} else {
return [ssDeviceValue];
}
}
//既存の自動入札スポット調整の設定をMap形式{自動入札スポット調整名:obj}で取得
function getExistingBiddingSeasonalityMap(targetAccountId) {
const biddingSeasonality = Search.BiddingSeasonalityAdjustmentService.get({
accountId: targetAccountId
}).rval;
if (biddingSeasonality.totalNumEntries == 0) {
return new Map();
}
let existingBiddingSeasonalityMap = new Map();
for (let i = 0; i < biddingSeasonality.values.length; i++) {
const biddingSeasonalityAdjustment = biddingSeasonality.values[i].biddingSeasonalityAdjustment;
existingBiddingSeasonalityMap.set(biddingSeasonalityAdjustment.biddingSeasonalityAdjustmentName, biddingSeasonalityAdjustment);
}
return existingBiddingSeasonalityMap;
}
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);
}
function sendSlackAndMail() {
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' + 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'),
});
}
}
Q.当日の天気が雨の地域のみに広告配信したいのですが、可能ですか?
A.広告グループの地域ターゲティングと天気予報データを使うことで可能です。
以下のスクリプトでは、当日の天気がTARGET_TELOPで指定した天気かつ、EXCLUDE_TELOPで指定した天気ではない地域を、TARGET_ADG_IDで指定した広告グループの地域ターゲティングに設定します。
エリアがない場合は、対象の広告グループをオフにします。
対象の広告グループ配下に指定した天気だった場合の広告を入れていただければ、その地域だけに配信されます。
実行頻度の設定は1日1回または1時間ごとで設定してください。
※スクリプトの実行頻度の設定手順はこちらをご覧ください。
動作確認バージョン:v202406
コードサンプル ここをクリックすると折り畳みます。
/* このソースコードは 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 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 = [];
const TARGET_AREA = ["北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県",
"茨城県", "栃木県", "群馬県", "埼玉県", "千葉県", "東京都", "神奈川県", "新潟県", "富山県",
"石川県", "福井県", "山梨県", "長野県", "岐阜県", "静岡県", "愛知県", "三重県", "滋賀県",
"京都府", "大阪府", "兵庫県", "奈良県", "和歌山県", "鳥取県", "島根県", "岡山県", "広島県",
"山口県", "徳島県", "香川県", "愛媛県", "高知県", "福岡県", "佐賀県", "長崎県", "熊本県",
"大分県", "宮崎県", "鹿児島県", "沖縄県"];//47都道府県
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'),
}),
});
}
}
Q.ディスプレイ広告で、当日の予報最高気温が指定した気温以上の地域のみに広告配信したいのですが、可能ですか?
A.広告グループの地域ターゲティングと天気予報データを使うことで可能です。
以下のスクリプトでは、当日の予報最高気温が指定した気温以上の地域を、指定した広告グループの地域ターゲティングに設定し、エリアがない場合は、対象の広告グループをオフにします。
広告グループ配下に予報最高気温以上だった場合の広告を入れていただければ、その地域だけに配信されます。
実行頻度の設定は1日1回で設定してください。
※スクリプトの実行頻度の設定手順はこちらをご覧ください。
動作確認バージョン:v202406
コードサンプル ここをクリックすると折り畳みます。
/* このソースコードは MIT License のもとで提供されています。
https://ads-developers.yahoo.co.jp/ja/ads-script/post/30418913.html
■スクリプト内容
当日の予報最高気温が指定した気温以上の地域を、指定した広告グループの地域ターゲティングに設定する。
エリアがない場合は、対象の広告グループをオフにする。
■利用方法
1.当スクリプトを、ディスプレイ広告のスクリプトとしてください。
2.各定数を以下のように設定してください。
3.実行頻度を1日1回任意のタイミングに設定してください。
■定数
・TARGET_ADG_ID:対象の広告グループ
・TARGET_TEMP:判定対象の気温
・FLAG_MAIL:結果をメール送信するならtrue、しないならfalse
・MAIL_TO:メール送信先のYahoo! BusinessID
・MAIL_TITLE:メールタイトル
・FLAG_SLACK:Slack配信するときはtrue、配信しないときはfalse
・SLACK_FETCH_URL:SlackのWebhook URL
*/
//設定が必要な定数
const TARGET_ADG_ID = 11111111;//対象の広告グループ
const TARGET_TEMP = 30;//判定対象の気温
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();
let TEXT_MESSAGE_ARRAY = [];
const TARGET_AREA = ["北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県",
"茨城県", "栃木県", "群馬県", "埼玉県", "千葉県", "東京都", "神奈川県", "新潟県", "富山県",
"石川県", "福井県", "山梨県", "長野県", "岐阜県", "静岡県", "愛知県", "三重県", "滋賀県",
"京都府", "大阪府", "兵庫県", "奈良県", "和歌山県", "鳥取県", "島根県", "岡山県", "広島県",
"山口県", "徳島県", "香川県", "愛媛県", "高知県", "福岡県", "佐賀県", "長崎県", "熊本県",
"大分県", "宮崎県", "鹿児島県", "沖縄県"];//47都道府県
let globalCampaignId = 0;//対象ADGの属するキャンペーン
//実処理
function main() {
try {
//天気を取得
const tempOverAreaOperands = getTargetAreaOperands();
//ターゲティングを変更
changeAdGroupTarget(tempOverAreaOperands);
sendMailOrSlack();
} catch (error) {
//エラー処理
MAIL_TITLE = '!!エラー発生!!' + MAIL_TITLE;
logAndMessage('【!!エラーが発生しました!!】\nアカウントID:' + accountId +
'の管理画面のログを確認してください。\nエラー内容:' + error);
sendMailOrSlack();
throw error;
}
}
//対象地域の天気を取得
//戻り値:targetAreaOperands:条件に合致する都道府県リスト
function getTargetAreaOperands() {
let tempOverAreaOperands = [];
const dict = getGeographicLocationDict();
//対象ADGの属するキャンペーンIDを取得してグローバル変数にset
globalCampaignId = getCampaignIdFromAdGroupId();
for (let i = 0; i < TARGET_AREA.length; i++) {
const areaName = TARGET_AREA[i];
const weather = WeatherApp.getWeatherByName(
areaName,
'TODAY'
);
const todayMaxTemp = weather[0].day[0].temp.max;//本日の最高気温
const targetGeoCode = dict[areaName];
//指定した気温以上の場合
if (todayMaxTemp >= TARGET_TEMP) {
tempOverAreaOperands.push(createGeoTargetOperand(targetGeoCode, TARGET_ADG_ID));
}
Logger.log(areaName + 'の今日の予報最高気温:' + todayMaxTemp + '℃');//検証のためログに出しておく
};
if (tempOverAreaOperands.length == 0) {
logAndMessage('今日の予報最高気温が' + TARGET_TEMP + '℃以上の都道府県はありませんでした');
} else {
logAndMessage('今日の予報最高気温が' + TARGET_TEMP + '℃以上の都道府県は' + tempOverAreaOperands.length + '件です');
}
return tempOverAreaOperands;
}
//地域ターゲティングのコードを取得し、{都道府県名:コード}の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, adGroupId) {
return {
accountId: accountId,
campaignId: globalCampaignId,
adGroupId: adGroupId,
target: {
targetType: 'GEO_TARGET',
targetId: geoCode,
geoTarget: {
areaSearchType: 'GEO',
}
}
};
}
//ターゲティング変更・広告グループのオンオフ変更
function changeAdGroupTarget(tempOverAreaOperands) {
// 雨の広告グループのターゲティングを変更
if (tempOverAreaOperands.length > 0) {
updateAdGroupTarget(tempOverAreaOperands);
} 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:' + TARGET_ADG_ID + 'のターゲティング更新でエラーが発生しました。管理画面のログをご確認ください。');
}
}
logAndMessage('広告グループID:' + TARGET_ADG_ID + 'は配信なし');
//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'),
}),
});
}
}