Скріпт корегування ставок у режимі 24/7

Цей скріпт дозволить вам робити більш гнучкі ставки – буквально в будь-яку хвилину будь-якого дня на будь-якому пристрої.

Як використовувати скріпт (сценарій)

Коефіцієнти ставок працюють з електронної таблиці Google, і URL-адресу потрібно вставити в код як електронну таблицю. Скопіюйте шаблон-коду та встановіть в ньому свої ставки. Документ повинен виглядати приблизно так:

Шаблон документу: https://docs.google.com/spreadsheets/d/1Om0jiszc8Uuuv_DhFkH1KV23glqssGB6Xqziofxcc-0/edit#gid=0
  1. У клітинках B2-H25 введіть множники ставок: 0% множник ставок не змінює жодних ставок, а X%/-X% – корегує ставки
  2. Якщо ви також хочете використовувати мобільні корекції, їх варто розмістити на другому аркуші цієї ж електронної таблиці.
  3. Потрібно встановити для змінної shoppingCampaigns значення true, якщо ви хочете використовувати її в торгових кампаніях, або значення false для використання в пошукових або медійних кампаніях.
  4. Встановіть для runMobileBids значення true, якщо ви хочете змінювати коригування ставок для мобільних пристроїв щогодини, або встановіть значення false, якщо ви хочете змінити лише розклад показу оголошень.
  5. Якщо ви хочете використовувати ці множники лише в деяких кампаніях, використовуйте excludeCampaignNameContains і includeCampaignNameContains, щоб сказати, що повинно, а що не повинно бути у назвах кампаній.
  6. Потім вам потрібно встановити розклад, щоб сценарій працював щогодини. Сценарій не встановлює всі розклади заздалегідь, тому що Google Ads не допускає достатньої кількості ставок – натомість він постійно оновлюватиме графіки показу оголошень, тому наступні кілька годин завжди заповнюються.

Код скріпта для корекції ставок в Google Ads

function main() {
  // The Google sheet to use
  var spreadsheetUrl = 'https://docs.google.com/spreadsheets/d/1Om0jiszc8Uuuv_DhFkH1KV23glqssGB6Xqziofxcc-0/edit#gid=0';

  // Shopping or regular campaigns
  // Use true if you want to run script on shopping campaigns (not regular campaigns).
  // Use false for regular campaigns.
  var shoppingCampaigns = false;

  // Use true if you want to set mobile bid adjustments as well as ad schedules.
  // Use false to just set ad schedules.
  var runMobileBids = false;

  // Optional parameters for filtering campaign names. The matching is case insensitive.
  // Select which campaigns to exclude e.g ["foo", "bar"] will ignore all campaigns
  // whose name contains 'foo' or 'bar'. Leave blank [] to not exclude any campaigns.
  var excludeCampaignNameContains = [];

  // Select which campaigns to include e.g ["foo", "bar"] will include only campaigns
  // whose name contains 'foo' or 'bar'. Leave blank [] to include all campaigns.
  var includeCampaignNameContains = [];

  // When you want to stop running the ad scheduling for good, set the lastRun
  // variable to true to remove all ad schedules.
  var lastRun = false;

  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

  // Initialise for use later.
  var weekDays = ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'];
  var adScheduleCodes = [];
  var campaignIds = [];

  // Retrieving up hourly data
  var scheduleRange = 'B2:H25';
  var accountName = AdWordsApp.currentAccount().getName();
  var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
  var sheets = spreadsheet.getSheets();

  var timeZone = AdWordsApp.currentAccount().getTimeZone();
  if (timeZone === 'Etc/GMT') {
    timeZone = 'GMT';
  }
  var date = new Date();
  var dayOfWeek = parseInt(Utilities.formatDate(date, timeZone, 'uu'), 10) - 1;
  var hour = parseInt(Utilities.formatDate(date, timeZone, 'HH'), 10);

  var sheet = sheets[0];
  var data = sheet.getRange(scheduleRange).getValues();

  // This hour's bid multiplier.
  var thisHourMultiplier = data[hour][dayOfWeek];
  var lastHourCell = 'I2';
  sheet.getRange(lastHourCell).setValue(thisHourMultiplier);

  // The next few hours' multipliers
  var timesAndModifiers = [];
  var otherDays = weekDays.slice(0);
  for (var h = 0; h < 5; h++) {
    var newHour = (hour + h) % 24;
    if (hour + h > 23) {
      var newDay = (dayOfWeek + 1) % 7;
    } else {
      var newDay = dayOfWeek;
    }
    otherDays[newDay] = '-';

    if (h < 4) {
      // Use the specified bids for the next 4 hours
      var bidModifier = data[newHour][newDay];
      if (isNaN(bidModifier) || (bidModifier < -0.9 && bidModifier > -1) || bidModifier > 9) {
        Logger.log("Bid modifier '" + bidModifier + "' for " + weekDays[newDay] + ' ' + newHour + ' is not valid.');
        timesAndModifiers.push([newHour, newHour + 1, weekDays[newDay], 0]);
      } else if (bidModifier != -1 && bidModifier.length != 0) {
        timesAndModifiers.push([newHour, newHour + 1, weekDays[newDay], bidModifier]);
      }
    } else {
      // Fill in the rest of the day with no adjustment (as a back-up incase the script breaks)
      timesAndModifiers.push([newHour, 24, weekDays[newDay], 0]);
    }
  }

  if (hour > 0) {
    timesAndModifiers.push([0, hour, weekDays[dayOfWeek], 0]);
  }

  for (var d = 0; d < otherDays.length; d++) {
    if (otherDays[d] != '-') {
      timesAndModifiers.push([0, 24, otherDays[d], 0]);
    }
  }

  // Pull a list of all relevant campaign IDs in the account.
  var campaignSelector = ConstructIterator(shoppingCampaigns);
  for (var i = 0; i < excludeCampaignNameContains.length; i++) {
    campaignSelector = campaignSelector.withCondition('Name DOES_NOT_CONTAIN_IGNORE_CASE "' + excludeCampaignNameContains[i] + '"');
  }
  campaignSelector = campaignSelector.withCondition('Status IN [ENABLED,PAUSED]');
  var campaignIterator = campaignSelector.get();
  while (campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
    var campaignName = campaign.getName();
    var includeCampaign = false;
    if (includeCampaignNameContains.length === 0) {
      includeCampaign = true;
    }
    for (var i = 0; i < includeCampaignNameContains.length; i++) {
      var index = campaignName.toLowerCase().indexOf(includeCampaignNameContains[i].toLowerCase());
      if (index !== -1) {
        includeCampaign = true;
        break;
      }
    }
    if (includeCampaign) {
      var campaignId = campaign.getId();
      campaignIds.push(campaignId);
    }
  }

  // Return if there are no campaigns.
  if (campaignIds.length === 0) {
    Logger.log('There are no campaigns matching your criteria.');
    return;
  }

  // Remove all ad scheduling for the last run.
  if (lastRun) {
    checkAndRemoveAdSchedules(campaignIds, [], shoppingCampaigns);
    return;
  }

  // Change the mobile bid adjustment
  if (runMobileBids) {
    if (sheets.length < 2) {
      Logger.log('Mobile ad schedule sheet was not found in the Google spreadsheet.');
    } else {
      var sheet = sheets[1];
      var data = sheet.getRange(scheduleRange).getValues();
      var thisHourMultiplier_Mobile = data[hour][dayOfWeek];

      if (thisHourMultiplier_Mobile.length === 0) {
        thisHourMultiplier_Mobile = -1;
      }

      if (isNaN(thisHourMultiplier_Mobile) || (thisHourMultiplier_Mobile < -0.9 && thisHourMultiplier_Mobile > -1) || thisHourMultiplier_Mobile > 3) {
        Logger.log("Mobile bid modifier '" + thisHourMultiplier_Mobile + "' for " + weekDays[dayOfWeek] + ' ' + hour + ' is not valid.');
        thisHourMultiplier_Mobile = 0;
      }

      var totalMultiplier = ((1 + thisHourMultiplier_Mobile) * (1 + thisHourMultiplier)) - 1;
      sheet.getRange('I2').setValue(thisHourMultiplier_Mobile);
      sheet.getRange('T2').setValue(totalMultiplier);
      ModifyMobileBidAdjustment(campaignIds, thisHourMultiplier_Mobile);
    }
  }

  // Check the existing ad schedules, removing those no longer necessary
  var existingSchedules = checkAndRemoveAdSchedules(campaignIds, timesAndModifiers, shoppingCampaigns);

  // Add in the new ad schedules
  AddHourlyAdSchedules(campaignIds, timesAndModifiers, existingSchedules, shoppingCampaigns);
}

function AddHourlyAdSchedules(campaignIds, timesAndModifiers, existingSchedules, shoppingCampaigns) {
  // times = [[hour,day],[hour,day]]
  var campaignIterator = ConstructIterator(shoppingCampaigns)
    .withIds(campaignIds)
    .get();
  while (campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
    for (var i = 0; i < timesAndModifiers.length; i++) {
      if (existingSchedules.indexOf(
        timesAndModifiers[i][0] + '|' + (timesAndModifiers[i][1]) + '|' + timesAndModifiers[i][2]
          + '|' + Utilities.formatString('%.2f', (timesAndModifiers[i][3] + 1)) + '|' + campaign.getId()
      )
        > -1) {
        continue;
      }

      campaign.addAdSchedule({
        dayOfWeek: timesAndModifiers[i][2],
        startHour: timesAndModifiers[i][0],
        startMinute: 0,
        endHour: timesAndModifiers[i][1],
        endMinute: 0,
        bidModifier: Math.round(100 * (1 + timesAndModifiers[i][3])) / 100
      });
    }
  }
}

function checkAndRemoveAdSchedules(campaignIds, timesAndModifiers, shoppingCampaigns) {
  var adScheduleIds = [];

  var report = AdWordsApp.report(
    'SELECT CampaignId, Id '
    + 'FROM CAMPAIGN_AD_SCHEDULE_TARGET_REPORT '
    + 'WHERE CampaignId IN ["' + campaignIds.join('","') + '"]'
  );

  var rows = report.rows();
  while (rows.hasNext()) {
    var row = rows.next();
    var adScheduleId = row.Id;
    var campaignId = row.CampaignId;
    if (adScheduleId == '--') {
      continue;
    }
    adScheduleIds.push([campaignId, adScheduleId]);
  }

  var chunkedArray = [];
  var chunkSize = 10000;

  for (var i = 0; i < adScheduleIds.length; i += chunkSize) {
    chunkedArray.push(adScheduleIds.slice(i, i + chunkSize));
  }

  var wantedSchedules = [];
  var existingWantedSchedules = [];

  for (var j = 0; j < timesAndModifiers.length; j++) {
    wantedSchedules.push(timesAndModifiers[j][0] + '|' + (timesAndModifiers[j][1]) + '|' + timesAndModifiers[j][2] + '|' + Utilities.formatString('%.2f', timesAndModifiers[j][3] + 1));
  }

  for (var i = 0; i < chunkedArray.length; i++) {
    var unwantedSchedules = [];

    var adScheduleIterator = AdWordsApp.targeting()
      .adSchedules()
      .withIds(chunkedArray[i])
      .get();
    while (adScheduleIterator.hasNext()) {
      var adSchedule = adScheduleIterator.next();
      var key = adSchedule.getStartHour() + '|' + adSchedule.getEndHour() + '|' + adSchedule.getDayOfWeek() + '|' + Utilities.formatString('%.2f', adSchedule.getBidModifier());

      if (wantedSchedules.indexOf(key) > -1) {
        if (shoppingCampaigns) {
          var campaign = adSchedule.getShoppingCampaign();
        } else {
          var campaign = adSchedule.getCampaign();
        }
        existingWantedSchedules.push(key + "|" + campaign.getId());
      } else {
        unwantedSchedules.push(adSchedule);
      }
    }

    for (var j = 0; j < unwantedSchedules.length; j++) {
      unwantedSchedules[j].remove();
    }
  }

  return existingWantedSchedules;
}

function ConstructIterator(shoppingCampaigns) {
  if (shoppingCampaigns === true) {
    return AdWordsApp.shoppingCampaigns();
  }
  return AdWordsApp.campaigns();
}

function ModifyMobileBidAdjustment(campaignIds, bidModifier) {
  var platformIds = [];
  var newBidModifier = Math.round(100 * (1 + bidModifier)) / 100;

  for (var i = 0; i < campaignIds.length; i++) {
    platformIds.push([campaignIds[i], 30001]);
  }

  var platformIterator = AdWordsApp.targeting()
    .platforms()
    .withIds(platformIds)
    .get();
  while (platformIterator.hasNext()) {
    var platform = platformIterator.next();
    platform.setBidModifier(newBidModifier);
  }
}