Управление кампанией с учетом погоды

Ставки

Этот скрипт позволяет изменять настройки кампаний с учетом погоды. Здесь вы найдете общую версию скрипта, которую можно настраивать для управления кампаниями в соответствии с вашими целями.

Спрос на некоторые товары и услуги сильно зависит от погодных условий. Например, в солнечный день люди часто ищут информацию о парках аттракционов. Такую особенность можно учитывать при планировании рекламных кампаний, однако делать это вручную слишком сложно. Мы предлагаем использовать скрипт AdWords, который программно извлекает данные о погоде и практически моментально корректирует ставки.

Список кампаний и связанных с ними местоположений для этого скрипта хранится в таблице Google. Для каждого местоположения выполняется вызов API OpenWeatherMap, в котором на основе базовых правил рассчитываются погодные условия. Если правило возвращает значение true, применяется мультипликатор ставки.

Принцип работы

Скрипт считывает данные из таблицы, содержащей три листа:

1. Campaign data (данные по кампаниям)

Содержит набор правил, в соответствии с которыми при заданных погодных условиях применяется модификатор ставки. На этом листе есть пять обязательных столбцов:

  • Campaign name – название изменяемой кампании;
  • Weather location – местоположение, для которого проверяются погодные условия;
  • Weather condition – погодные условия, в отношении которых применяется правило;
  • Bid modifier – модификатор ставки для местоположения, который применяется при соответствующих погодных условиях;
  • Apply Modifier To – параметр, который определяет, должен ли модификатор ставки применяться только к целевым местоположениям, соответствующим условию Weather Location, или ко всем целевым местоположениям кампании;
  • Enabled – значение Yes или No, которое, соответственно, включает или отключает правило.
Ниже показаны различные варианты использования.

Тестовая кампания 1 – типичный пример. Она ориентирована на Бостон и содержит два правила:

  1. Применить модификатор ставки 1,3, если в Бостоне солнечная погода.
  2. Применить модификатор ставки 0,8, если в Бостоне идет дождь.

В тестовой кампании 2 рассматривается другая ситуация. В ней настроен таргетинг на несколько городов в штатах Нью-Йорк и Коннектикут, но ставки с учетом погоды необходимо задать только для Коннектикута. Поэтому мы создали два правила и связали Weather Location с несколькими городами в этом штате (см. ниже). В результате ставки будут корректироваться только для них, а не для местоположений в Нью-Йорке.

В тестовой кампании 3 таргетинг настроен на несколько городов во Флориде, но не на весь штат. Поскольку условия по погоде во Флориде заданы для всего штата, который не является целевым местоположением кампании, для параметра Apply Modifier To мы устанавливаем значение All Geo Targets (Все целевые местоположения).

Примечание. Если в отношении определенных кампании и местоположения выполняется одновременно несколько правил, применяется последнее из них.

2. Weather data (Данные о погоде)

Этот лист содержит погодные условия, используемые на листе Campaign data (Данные по кампаниям). Обязательные столбцы:

  • Condition Name – название условия, например Sunny (Солнечно).
  • Temperature – температура по шкале Фаренгейта.
  • Precipitation – интенсивность дождя за последние 3 часа (мм).
  • Wind – скорость ветра (миль в час).

На показанном выше листе определены два погодных условия:

  1. Sunny (солнечно) – температура от 65 до 80 градусов по Фаренгейту, не более 1 мм осадков за последние 3 часа, скорость ветра менее 5 миль в час.
  2. Rainy (дождь) – более 0 мм осадков за последние 3 часа и скорость ветра менее 10 миль в час.

Определение погодных условий

Погодные условия можно определять следующим образом:

  • below x – указанное значение должно быть ниже x, например меньше 10.
  • above x – указанное значение должно быть выше x, например больше 70.
  • x to y – указанное значение должно находиться в диапазоне от x до y включительно, например от 65 до 80.

Если ячейка не заполнена, значения параметра не учитываются при вычислении. Так, в показанном выше примере при вычислении условия Rainy (дождь) не учитывается значение температуры.

При вычислении заданные погодные условия объединяются оператором AND. Пример расчета погодного условия Sunny (Солнечно):

var isSunny = (temperature >= 65 && temperature <= 80) && (precipitation < 1) && (wind < 5);

3. Weather location data (данные о погодных местоположениях)

На этом листе определены погодные местоположения, используемые на листе Campaign data (данные по кампаниям). На листе есть два столбца:

  • Weather Location – название местоположения в формате OpenWeatherMap API. Полный список поддерживаемых местоположений приводится в файле http://openweathermap.org/help/city_list.txt.
  • Geo target code – код геотаргетинга в формате, поддерживаемом скриптом AdWords. Список допустимых вариантов приводится на странице https://developers.google.com/adwords/api/docs/appendix/geotargeting.

Вы можете задавать несколько кодов геотаргетинга для одного погодного местоположения, поскольку у параметров таргетинга в AdWords более высокая детализация. Чтобы связать одно погодное местоположение с несколькими географическими, создайте несколько строк с одинаковыми погодными местоположениями и разными геокодами.

В таблице выше определено три погодных местоположения:

  1. Бостон – геокод 10108127.
  2. Коннектикут – геокоды нескольких городов: 1014778, 1014743 и 1014843.
  3. Флорида – 21142.

Целевая область в радиусе

Правила, использующие Matching Geo Targets (соответствующие целевые местоположения), можно применить к местоположениям, областям в радиусе или и тому, и другому с помощью пометки TARGETING. Геотаргетинг будет сопоставлять геокоды с идентификаторами местоположений, а таргетинг на целевую область в радиусе – проверять, находятся ли указанные географические координаты в пределах радиуса по формуле гаверсинуса.

Логика скрипта

Скрипт считывает правила со всех трех листов, после чего последовательно пытается выполнить каждое из них на листе с данными по кампаниям.

Для каждого выполняемого правила проверяется, настроен ли в кампании таргетинг на заданное местоположение. Если да, то скрипт извлекает текущий модификатор ставки. Затем скрипт вызывает API OpenWeatherMap, чтобы извлечь погодные условия для нужного местоположения, и проверяет, соответствуют ли они правилам. Если да, то при различии между новым и текущим модификаторами ставки скрипт изменяет модификатор для указанного местоположения.

Если погодные условия не соответствуют заданным, значения модификаторов ставки совпадают или для параметра Apply Modifier To установлено значение Matching Geo Targets, изменения не вносятся.

Настройка

  • Зарегистрируйтесь для получения ключа API на странице http://openweathermap.org/appid.
  • Создайте копию шаблона таблицы и внесите необходимые изменения в настройки кампании и правил.
  • Создайте новый скрипт с приведенным ниже кодом.
  • Обновите в сценарии переменные OPEN_WEATHER_MAP_API_KEY, SPREADSHEET_URL и TARGETING.
  • Запланируйте время выполнения скрипта.

Код

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @name Bid By Weather
 *
 * @overview The Bid By Weather script adjusts campaign bids by weather
 *     conditions of their associated locations. See
 *     https://developers.google.com/adwords/scripts/docs/solutions/weather-based-campaign-management#bid-by-weather
 *     for more details.
 *
 * @author AdWords Scripts Team [adwords-scripts@googlegroups.com]
 *
 * @version 1.2.2
 *
 * @changelog
 * - version 1.2.2
 *   - Add support for video and shopping campaigns.
 * - version 1.2.1
 *   - Added validation for external spreadsheet setup.
 * - version 1.2
 *   - Added proximity based targeting.  Targeting flag allows location
 *     targeting, proximity targeting or both.
 * - version 1.1
 *   - Added flag allowing bid adjustments on all locations targeted by
 *     a campaign rather than only those that match the campaign rule
 * - version 1.0
 *   - Released initial version.
 */

// Register for an API key at http://openweathermap.org/appid
// and enter the key below.
var OPEN_WEATHER_MAP_API_KEY = 'INSERT_OPEN_WEATHER_MAP_API_KEY_HERE';

// Create a copy of https://goo.gl/7b5mYI and enter the URL below.
var SPREADSHEET_URL = 'INSERT_SPREADSHEET_URL_HERE';

// A cache to store the weather for locations already lookedup earlier.
var WEATHER_LOOKUP_CACHE = {};

// Flag to pick which kind of targeting "LOCATION", "PROXIMITY", or "ALL".
var TARGETING = 'ALL';


/**
 * The code to execute when running the script.
 */
function main() {
  validateApiKey();
  // Load data from spreadsheet.
  var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
  var campaignRuleData = getSheetData(spreadsheet, 1);
  var weatherConditionData = getSheetData(spreadsheet, 2);
  var geoMappingData = getSheetData(spreadsheet, 3);

  // Convert the data into dictionaries for convenient usage.
  var campaignMapping = buildCampaignRulesMapping(campaignRuleData);
  var weatherConditionMapping =
      buildWeatherConditionMapping(weatherConditionData);
  var locationMapping = buildLocationMapping(geoMappingData);

  // Apply the rules.
  for (var campaignName in campaignMapping) {
    applyRulesForCampaign(campaignName, campaignMapping[campaignName],
        locationMapping, weatherConditionMapping);
  }
}

/**
 * Retrieves the data for a worksheet.
 *
 * @param {Object} spreadsheet The spreadsheet.
 * @param {number} sheetIndex The sheet index.
 * @return {Array} The data as a two dimensional array.
 */
function getSheetData(spreadsheet, sheetIndex) {
  var sheet = spreadsheet.getSheets()[sheetIndex];
  var range =
      sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn());
  return range.getValues();
}

/**
 * Builds a mapping between the list of campaigns and the rules
 * being applied to them.
 *
 * @param {Array} campaignRulesData The campaign rules data, from the
 *     spreadsheet.
 * @return {Object.<string, Array.<Object>> } A map, with key as campaign name,
 *     and value as an array of rules that apply to this campaign.
 */
function buildCampaignRulesMapping(campaignRulesData) {
  var campaignMapping = {};
  for (var i = 0; i < campaignRulesData.length; i++) {
    // Skip rule if not enabled.

    if (campaignRulesData[i][5].toLowerCase() == 'yes') {
      var campaignName = campaignRulesData[i][0];
      var campaignRules = campaignMapping[campaignName] || [];
      campaignRules.push({
          'name': campaignName,

          // location for which this rule applies.
          'location': campaignRulesData[i][1],

          // the weather condition (e.g. Sunny).
          'condition': campaignRulesData[i][2],

          // bid modifier to be applied.
          'bidModifier': campaignRulesData[i][3],

          // whether bid adjustments should by applied only to geo codes
          // matching the location of the rule or to all geo codes that
          // the campaign targets.
          'targetedOnly': campaignRulesData[i][4].toLowerCase() ==
                          'matching geo targets'
      });
      campaignMapping[campaignName] = campaignRules;
    }
  }
  Logger.log('Campaign Mapping: %s', campaignMapping);
  return campaignMapping;
}

/**
 * Builds a mapping between a weather condition name (e.g. Sunny) and the rules
 * that correspond to that weather condition.
 *
 * @param {Array} weatherConditionData The weather condition data from the
 *      spreadsheet.
 * @return {Object.<string, Array.<Object>>} A map, with key as a weather
 *     condition name, and value as the set of rules corresponding to that
 *     weather condition.
 */
function buildWeatherConditionMapping(weatherConditionData) {
  var weatherConditionMapping = {};

  for (var i = 0; i < weatherConditionData.length; i++) {
    var weatherConditionName = weatherConditionData[i][0];
    weatherConditionMapping[weatherConditionName] = {
      // Condition name (e.g. Sunny)
      'condition': weatherConditionName,

      // Temperature (e.g. 50 to 70)
      'temperature': weatherConditionData[i][1],

      // Precipitation (e.g. below 70)
      'precipitation': weatherConditionData[i][2],

      // Wind speed (e.g. above 5)
      'wind': weatherConditionData[i][3]
    };
  }
  Logger.log('Weather condition mapping: %s', weatherConditionMapping);
  return weatherConditionMapping;
}

/**
 * Builds a mapping between a location name (as understood by OpenWeatherMap
 * API) and a list of geo codes as identified by AdWords scripts.
 *
 * @param {Array} geoTargetData The geo target data from the spreadsheet.
 * @return {Object.<string, Array.<Object>>} A map, with key as a locaton name,
 *     and value as an array of geo codes that correspond to that location
 *     name.
 */
function buildLocationMapping(geoTargetData) {
  var locationMapping = {};
  for (var i = 0; i < geoTargetData.length; i++) {
    var locationName = geoTargetData[i][0];
    var locationDetails = locationMapping[locationName] || {
      'geoCodes': []      // List of geo codes understood by AdWords scripts.
    };

    locationDetails.geoCodes.push(geoTargetData[i][1]);
    locationMapping[locationName] = locationDetails;
  }
  Logger.log('Location Mapping: %s', locationMapping);
  return locationMapping;
}

/**
 * Applies rules to a campaign.
 *
 * @param {string} campaignName The name of the campaign.
 * @param {Object} campaignRules The details of the campaign. See
 *     buildCampaignMapping for details.
 * @param {Object} locationMapping Mapping between a location name (as
 *     understood by OpenWeatherMap API) and a list of geo codes as
 *     identified by AdWords scripts. See buildLocationMapping for details.
 * @param {Object} weatherConditionMapping Mapping between a weather condition
 *     name (e.g. Sunny) and the rules that correspond to that weather
 *     condition. See buildWeatherConditionMapping for details.
 */
function applyRulesForCampaign(campaignName, campaignRules, locationMapping,
                               weatherConditionMapping) {
  for (var i = 0; i < campaignRules.length; i++) {
    var bidModifier = 1;
    var campaignRule = campaignRules[i];

    // Get the weather for the required location.
    var locationDetails = locationMapping[campaignRule.location];
    var weather = getWeather(campaignRule.location);
    Logger.log('Weather for %s: %s', locationDetails, weather);

    // Get the weather rules to be checked.
    var weatherConditionName = campaignRule.condition;
    var weatherConditionRules = weatherConditionMapping[weatherConditionName];

    // Evaluate the weather rules.
    if (evaluateWeatherRules(weatherConditionRules, weather)) {
      Logger.log('Matching Rule found: Campaign Name = %s, location = %s, ' +
          'weatherName = %s,weatherRules = %s, noticed weather = %s.',
          campaignRule.name, campaignRule.location,
          weatherConditionName, weatherConditionRules, weather);
      bidModifier = campaignRule.bidModifier;

      if (TARGETING == 'LOCATION' || TARGETING == 'ALL') {
        // Get the geo codes that should have their bids adjusted.
        var geoCodes = campaignRule.targetedOnly ?
          locationDetails.geoCodes : null;
        adjustBids(campaignName, geoCodes, bidModifier);
      }

      if (TARGETING == 'PROXIMITY' || TARGETING == 'ALL') {
        var location = campaignRule.targetedOnly ? campaignRule.location : null;
        adjustProximityBids(campaignName, location, bidModifier);
      }

    }
  }
  return;
}

/**
 * Converts a temperature value from kelvin to fahrenheit.
 *
 * @param {number} kelvin The temperature in Kelvin scale.
 * @return {number} The temperature in Fahrenheit scale.
 */
function toFahrenheit(kelvin) {
  return (kelvin - 273.15) * 1.8 + 32;
}

/**
 * Evaluates the weather rules.
 *
 * @param {Object} weatherRules The weather rules to be evaluated.
 * @param {Object.<string, string>} weather The actual weather.
 * @return {boolean} True if the rule matches current weather conditions,
 *     False otherwise.
 */
function evaluateWeatherRules(weatherRules, weather) {
  // See http://bugs.openweathermap.org/projects/api/wiki/Weather_Data
  // for values returned by OpenWeatherMap API.
  var precipitation = 0;
  if (weather.rain && weather.rain['3h']) {
    precipitation = weather.rain['3h'];
  }
  var temperature = toFahrenheit(weather.main.temp);
  var windspeed = weather.wind.speed;

  return evaluateMatchRules(weatherRules.temperature, temperature) &&
      evaluateMatchRules(weatherRules.precipitation, precipitation) &&
      evaluateMatchRules(weatherRules.wind, windspeed);
}

/**
 * Evaluates a condition for a value against a set of known evaluation rules.
 *
 * @param {string} condition The condition to be checked.
 * @param {Object} value The value to be checked.
 * @return {boolean} True if an evaluation rule matches, false otherwise.
 */
function evaluateMatchRules(condition, value) {
  // No condition to evaluate, rule passes.
  if (condition == '') {
    return true;
  }
  var rules = [matchesBelow, matchesAbove, matchesRange];

  for (var i = 0; i < rules.length; i++) {
    if (rules[i](condition, value)) {
      return true;
    }
  }
  return false;
}

/**
 * Evaluates whether a value is below a threshold value.
 *
 * @param {string} condition The condition to be checked. (e.g. below 50).
 * @param {number} value The value to be checked.
 * @return {boolean} True if the value is less than what is specified in
 * condition, false otherwise.
 */
function matchesBelow(condition, value) {
  conditionParts = condition.split(' ');

  if (conditionParts.length != 2) {
    return false;
  }

  if (conditionParts[0] != 'below') {
    return false;
  }

  if (value < conditionParts[1]) {
    return true;
  }
  return false;
}

/**
 * Evaluates whether a value is above a threshold value.
 *
 * @param {string} condition The condition to be checked. (e.g. above 50).
 * @param {number} value The value to be checked.
 * @return {boolean} True if the value is greater than what is specified in
 *     condition, false otherwise.
 */
function matchesAbove(condition, value) {
  conditionParts = condition.split(' ');

  if (conditionParts.length != 2) {
    return false;
  }

  if (conditionParts[0] != 'above') {
    return false;
  }

  if (value > conditionParts[1]) {
    return true;
  }
  return false;
}

/**
 * Evaluates whether a value is within a range of values.
 *
 * @param {string} condition The condition to be checked (e.g. 5 to 18).
 * @param {number} value The value to be checked.
 * @return {boolean} True if the value is in the desired range, false otherwise.
 */
function matchesRange(condition, value) {
  conditionParts = condition.replace('\w+', ' ').split(' ');

  if (conditionParts.length != 3) {
    return false;
  }

  if (conditionParts[1] != 'to') {
    return false;
  }

  if (conditionParts[0] <= value && value <= conditionParts[2]) {
    return true;
  }
  return false;
}

/**
 * Retrieves the weather for a given location, using the OpenWeatherMap API.
 *
 * @param {string} location The location to get the weather for.
 * @return {Object.<string, string>} The weather attributes and values, as
 *     defined in the API.
 */
function getWeather(location) {
  if (location in WEATHER_LOOKUP_CACHE) {
    Logger.log('Cache hit...');
    return WEATHER_LOOKUP_CACHE[location];
  }

  var url = Utilities.formatString(
      'http://api.openweathermap.org/data/2.5/weather?APPID=%s&q=%s',
      encodeURIComponent(OPEN_WEATHER_MAP_API_KEY),
      encodeURIComponent(location));
  var response = UrlFetchApp.fetch(url);
  if (response.getResponseCode() != 200) {
    throw Utilities.formatString(
        'Error returned by API: %s, Location searched: %s.',
        response.getContentText(), location);
  }

  var result = JSON.parse(response.getContentText());

  // OpenWeatherMap's way of returning errors.
  if (result.cod != 200) {
    throw Utilities.formatString(
        'Error returned by API: %s,  Location searched: %s.',
        response.getContentText(), location);
  }

  WEATHER_LOOKUP_CACHE[location] = result;
  return result;
}

/**
 * Adjusts the bidModifier for a list of geo codes for a campaign.
 *
 * @param {string} campaignName The name of the campaign.
 * @param {Array} geoCodes The list of geo codes for which bids should be
 *     adjusted.  If null, all geo codes on the campaign are adjusted.
 * @param {number} bidModifier The bid modifier to use.
 */
function adjustBids(campaignName, geoCodes, bidModifier) {
  // Get the campaign.
  var campaign = getCampaign(campaignName);
  if (!campaign) return null;

  // Get the targeted locations.
  var locations = campaign.targeting().targetedLocations().get();
  while (locations.hasNext()) {
    var location = locations.next();
    var currentBidModifier = location.getBidModifier().toFixed(2);

    // Apply the bid modifier only if the campaign has a custom targeting
    // for this geo location or if all locations are to be modified.
    if (!geoCodes || (geoCodes.indexOf(location.getId()) != -1 &&
      currentBidModifier != bidModifier)) {
        Logger.log('Setting bidModifier = %s for campaign name = %s, ' +
            'geoCode = %s. Old bid modifier is %s.', bidModifier,
            campaignName, location.getId(), currentBidModifier);
        location.setBidModifier(bidModifier);
    }
  }
}

/**
 * Adjusts the bidModifier for campaigns targeting by proximity location
 * for a given weather location.
 *
 * @param {string} campaignName The name of the campaign.
 * @param {string} weatherLocation The weather location for which bids should be
 *     adjusted.  If null, all proximity locations on the campaign are adjusted.
 * @param {number} bidModifier The bid modifier to use.
 */
function adjustProximityBids(campaignName, weatherLocation, bidModifier) {
  // Get the campaign.
  var campaign = getCampaign(campaignName);
  if(campaign === null) return;

  // Get the proximity locations.
  var proximities = campaign.targeting().targetedProximities().get();
  while (proximities.hasNext()) {
    var proximity = proximities.next();
    var currentBidModifier = proximity.getBidModifier().toFixed(2);

    // Apply the bid modifier only if the campaign has a custom targeting
    // for this geo location or if all locations are to be modified.
    if (!weatherLocation ||
        (weatherNearProximity(proximity, weatherLocation) &&
      currentBidModifier != bidModifier)) {
        Logger.log('Setting bidModifier = %s for campaign name = %s, with ' +
            'weatherLocation = %s in proximity area. Old bid modifier is %s.',
            bidModifier, campaignName, weatherLocation, currentBidModifier);
        proximity.setBidModifier(bidModifier);
      }
  }
}

/**
 * Checks if weather location is within the radius of the proximity location.
 *
 * @param {Object} proximity The targeted proximity of campaign.
 * @param {string} weatherLocation Name of weather location to check within
 * radius.
 * @return {boolean} Returns true if weather location is within radius.
 */
function weatherNearProximity(proximity, weatherLocation) {
  // See https://en.wikipedia.org/wiki/Haversine_formula for details on how
  // to compute spherical distance.
  var earthRadiusInMiles = 3960.0;
  var degreesToRadians = Math.PI / 180.0;
  var radiansToDegrees = 180.0 / Math.PI;
  var kmToMiles = 0.621371;

  var radiusInMiles = proximity.getRadiusUnits() == 'MILES' ?
    proximity.getRadius() : proximity.getRadius() * kmToMiles;

  // Compute the change in latitude degrees for the radius.
  var deltaLat = (radiusInMiles / earthRadiusInMiles) * radiansToDegrees;
  // Find the radius of a circle around the earth at given latitude.
  var r = earthRadiusInMiles * Math.cos(proximity.getLatitude() *
      degreesToRadians);
  // Compute the change in longitude degrees for the radius.
  var deltaLon = (radiusInMiles / r) * radiansToDegrees;

  // Retrieve weather location for lat/lon coordinates.
  var weather = getWeather(weatherLocation);
  // Check if weather condition is within the proximity boundaries.
  return (weather.coord.lat >= proximity.getLatitude() - deltaLat &&
          weather.coord.lat <= proximity.getLatitude() + deltaLat &&
          weather.coord.lon >= proximity.getLongitude() - deltaLon &&
          weather.coord.lon <= proximity.getLongitude() + deltaLon);
}

/**
 * Finds a campaign by name, whether it is a regular, video, or shopping
 * campaign, by trying all in sequence until it finds one.
 *
 * @param {string} campaignName The campaign name to find.
 * @return {Object} The campaign found, or null if none was found.
 */
function getCampaign(campaignName) {
  var selectors = [AdWordsApp.campaigns(), AdWordsApp.videoCampaigns(),
      AdWordsApp.shoppingCampaigns()];
  for(var i = 0; i < selectors.length; i++) {
    var campaignIter = selectors[i].
        withCondition('CampaignName = "' + campaignName + '"').
        get();
    if (campaignIter.hasNext()) {
      return campaignIter.next();
    }
  }
  return null;
}

/**
 * DO NOT EDIT ANYTHING BELOW THIS LINE.
 * Please modify your spreadsheet URL and API key at the top of the file only.
 */

/**
 * Validates the provided spreadsheet URL to make sure that it's set up
 * properly. Throws a descriptive error message if validation fails.
 *
 * @param {string} spreadsheeturl The URL of the spreadsheet to open.
 * @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
 * @throws {Error} If the spreadsheet URL hasn't been set
 */
function validateAndGetSpreadsheet(spreadsheeturl) {
  if (spreadsheeturl == 'INSERT_SPREADSHEET_URL_HERE') {
    throw new Error('Please specify a valid Spreadsheet URL. You can find' +
        ' a link to a template in the associated guide for this script.');
  }
  var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
  return spreadsheet;
}

/**
 * Validates the provided API key to make sure that it's not the default. Throws
 * a descriptive error message if validation fails.
 *
 * @throws {Error} If the configured API key hasn't been set.
 */
function validateApiKey() {
  if (OPEN_WEATHER_MAP_API_KEY == 'INSERT_OPEN_WEATHER_MAP_API_KEY_HERE') {
    throw new Error('Please specify a valid API key for OpenWeatherMap. You ' +
        'can acquire one here: http://openweathermap.org/appid');
  }
}

Управление кампанией

Ниже приведен пример стандартного скрипта, который можно видоизменять для управления кампанией с учетом погоды. Здесь вы найдете версию этого скрипта для управления ставками на основе данных о погоде.

Скрипт для управления ставками с учетом погоды настраивает определенных местоположений. Однако он выполняет одну конкретную задачу и его сложно перенастроить для управления кампаниями в целом (например, для приостановки группы кампаний). Ниже рассматривается скрипт более общего назначения.

Получение погодных условий

Скрипт получает данные о погодных условиях с серверов OpenWeatherMap. Можно запрашивать сведения о погоде в настоящий момент или прогноз на неделю. Названия городов для OpenWeatherMap указываются в произвольной форме.

// Get current weather information for New York City.
var nyWeather = getWeatherForLocation("New York City, US");

// Get the weather forecast for the next week in Toronto.
var torontoWeather = getWeatherForecastForLocation("Toronto, CA");

Работа с данными

Данные о текущей погоде возвращаются методом getWeatherForLocation() в следующем формате:

{
  name: "New York",
  weather: {
    clouds: 48,
    windspeed: 2.66,
    status: {
      summary: "Clouds",
      id: 802,
      description: "scattered clouds"
    },
    snow: 0.0,
    rain: 0.0,
    temperature: 286.16
  },
  country: "United States of America"
}

Полученные значения расшифровываются следующим образом:

Поле Описание
name Общепринятое название местоположения, возвращенное OpenWeatherMap.
clouds Облачность, %.
windspeed Скорость ветра, м/с.
snow Уровень снега за последние 3 часа, мм.
rain Уровень дождя за последние 3 часа, мм.
summary Метеорологическая сводка. Допустимые значения см. по ссылке http://openweathermap.org/weather-conditions.
description Описание погоды. Допустимые значения см. по ссылке http://openweathermap.org/weather-conditions.
id Идентификатор погодных условий, возвращенный a OpenWeatherMap. Допустимые значения см. по ссылке http://openweathermap.org/weather-conditions.
temperature Температура по шкале Кельвина.

Данные о прогнозе погоды возвращаются методом getWeatherForecastForLocation() в следующем формате:

{
  name: "Toronto",
  country: "CA"
  forecast: {
    "20150416": {
       clouds: 0.0,
       windspeed: 2.06,
       status: {
         summary: "Rain",
         id: 500,
         description: "light rain"
       },
       snow: 0.0,
       rain: 1.89,
       temperature: 0.0
    },
    "20150417": {
      ...
    }
  }
}

Полученные значения для прогноза погоды расшифровываются так же, как и аналогичные значения для текущей погоды. Дополнительно в прогнозе присутствует карта, ключ к которой – дата в формате yyyyMMdd, а значение – прогноз погоды на указанный день. По умолчанию возвращаются прогнозы на семь дней. Скрипт упрощает JSON, возвращаемый API OpenWeatherMap.

Управление кампаниями

Ниже рассматриваются примеры управления кампаниями с использованием данных о погоде.

Погода в настоящий момент

Пример 1. Запрос сводки о погоде от OpenWeathermap

var nyWeather = getWeatherForLocation("New York City, US");
if (nyWeather.weather.status.summary === "Rain") {
  var campaign = AdWordsApp.campaigns()
      .withCondition("CampaignName = 'Sunny Trips'")
      .get()
      .next();
  campaign.pause();
}

Пример 2. Запрос сводки с дополнительными параметрами

var nyWeather = getWeatherForLocation("New York City, US");
if (nyWeather.weather.snow > 5 && nyWeather.weather.temperature < 273) {
  var adGroup = AdWordsApp.adGroups()
     .withCondition("CampaignName = 'New York Shoes'")
     .withCondition("AdGroupName='Snow boots'")
     .get()
     .next();
  adGroup.bidding().setCpc(adGroup.bidding().getCpc() + 0.3);
}

Прогноз погоды

Пример 1. Запрос сводки о погоде от OpenWeathermap

var nyWeather = getWeatherForecastForLocation("New York City, US");
var rainyDays = 0;
for (var date in nyWeather.forecast) {
  var forecast = nyWeather.forecast[date];
  if (forecast.status.summary === "Rain") {
    rainyDays = rainyDays + 1;
  }
}

if (rainyDays > 4) {
  var campaign = AdWordsApp.campaigns()
      .withCondition("CampaignName = 'Sunny Trips'")
      .get()
      .next();
  campaign.pause();
}

Пример 2. Запрос сводки с дополнительными параметрами

var nyWeather = getWeatherForecastForLocation("New York City, US");
var coldDays = 0;
for (var date in nyWeather.forecast) {
  var forecast = nyWeather.forecast[date];

  if (forecast.snow > 5 && forecast.temperature < 273) {
    coldDays = coldDays + 1;
  }
}

if (coldDays > 4) {
  var adGroup = AdWordsApp.adGroups()
      .withCondition("CampaignName = 'New York Shoes'")
      .withCondition("AdGroupName='Snow boots'")
      .get()
      .next();
  adGroup.bidding().setCpc(adGroup.bidding().getCpc() + 0.3);
}

Настройка

  • Зарегистрируйтесь для получения ключа API на странице http://openweathermap.org/appid.
  • Создайте новый скрипт с приведенным ниже исходным кодом.
  • Обновите OPEN_WEATHER_MAP_API_KEY.
  • Чтобы использовать скрипт в аккаунте рекламодателя или в аккаунте Центра клиентов, соответствующий блок кода в методе main().
  • Чтобы использовать текущие данные о погоде или прогноз, раскомментируйте соответствующий блок кода в методе processSingleAccount().
  • Замените данные в методе manageCampaignsBasedOnCurrentWeather() или manageCampaignsBasedOnWeatherForecast() в соответствии с вашими целями.
  • Запланируйте время выполнения скрипта.

Код

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @name Weather Based Campaign Management
 *
 * @overview The Weather Based Campaign management script allows you to perform
 *     various campaign management tasks based on weather information. See
 *     https://developers.google.com/adwords/scripts/docs/solutions/weather-based-campaign-management#generic-weather
 *     for more details.
 *
 * @author AdWords Scripts Team [adwords-scripts@googlegroups.com]
 *
 * @version 1.0
 *
 * @changelog
 * - version 1.0
 *   - Released initial version.
 */

// Register for an API key at http://openweathermap.org/appid
// and enter the key below.
var OPEN_WEATHER_MAP_API_KEY = 'INSERT_OPEN_WEATHER_MAP_API_KEY_HERE';

var OPEN_WEATHER_MAP_SERVER_URL = 'http://api.openweathermap.org/data/2.5';
var FORECAST_ENDPOINT = OPEN_WEATHER_MAP_SERVER_URL + '/forecast/daily';
var WEATHER_ENDPOINT = OPEN_WEATHER_MAP_SERVER_URL + '/weather';

function main() {
  // Uncomment the following lines if you are writing code for a single account.
  // processSingleAccount();

  // Uncomment the following lines if you are writing code for an MCC account.
  // var accounts = MccApp.accounts().withIds(['1234567890', '3456789012']);
  // accounts.executeInParallel("processSingleAccount");
}

/**
 * Process a single account.
 */
function processSingleAccount() {
  // Uncomment this block if you want to do campaign management based on
  // current weather.
  // manageCampaignsBasedOnCurrentWeather();

  // Uncomment this block if you want to do campaign management based on
  // weather forecast.
  // manageCampaignsBasedOnWeatherForecast();
}

/**
 * Manage your campaigns based on current weather. The contents of
 * this method are for your reference; replace it with your campaign
 * management logic.
 */
function manageCampaignsBasedOnCurrentWeather() {
  var nyWeather = getWeatherForLocation('New York City, US');

  // Example 1: Use weather summary provided by OpenWeathermap.
  // See http://openweathermap.org/weather-conditions for more details.
  if (nyWeather.weather.status.summary === 'Rain') {
    // Add your logic here.
  }

  // Example 2: Check more specific weather parameters.
  if (nyWeather.weather.snow > 5 && nyWeather.weather.temperature < 273) {
    // Add your logic here.
  }
}

/**
 * Manage your campaigns based on weather forecast. The contents of
 * this method are for your reference; replace it with your campaign
 * management logic.
 */
function manageCampaignsBasedOnWeatherForecast() {
  var nyWeather = getWeatherForecastForLocation('Toronto, CA');

  // Example 1: Use weather summary provided by OpenWeathermap.
  var rainyDays = 0;
  for (var date in nyWeather.forecast) {
    var forecast = nyWeather.forecast[date];
    if (forecast.status.summary === 'Rain') {
      rainyDays = rainyDays + 1;
    }
  }

  if (rainyDays > 4) {
    // Add your logic here.
  }

  // Example 2: Check more specific weather parameters.
  var coldDays = 0;
  for (var date in nyWeather.forecast) {
    var forecast = nyWeather.forecast[date];

    if (forecast.snow > 5 && forecast.temperature < 273) {
      coldDays = coldDays + 1;
    }
  }

  if (coldDays > 4) {
    // Add your logic here.
  }
}

/**
 * Make a call to the OpenWeatherMap server.
 *
 * @param {string} endpoint the server endpoint.
 * @param {string} location the location for which weather
 *     information is retrieved.
 * @return {Object} the server response.
 */
function callWeatherServer(endpoint, location) {
    var url = Utilities.formatString(
      '%s?APPID=%s&q=%s',
      endpoint,
      encodeURIComponent(OPEN_WEATHER_MAP_API_KEY),
      encodeURIComponent(location));
  var response = UrlFetchApp.fetch(url);
  if (response.getResponseCode() != 200) {
    throw Utilities.formatString(
        'Error returned by API: %s, Location searched: %s.',
        response.getContentText(), location);
  }

  var result = JSON.parse(response.getContentText());

  // OpenWeatherMap's way of returning errors.
  if (result.cod != 200) {
    throw Utilities.formatString(
        'Error returned by API: %s,  Location searched: %s.',
        response.getContentText(), location);
  }
  return result;
}

/**
 * Parse the weather response from the OpenWeatherMap server.
 *
 * @param {Object} weather the weather information from
 *     OpenWeatherMap server.
 * @return {Object} the parsed weather response.
 */
function parseWeather(weather) {
  var retval = {
    'rain': 0,
    'temperature': 0,
    'windspeed': 0,
    'snow': 0,
    'clouds': 0,
    'status': {
      'id': 0,
      'summary': '',
      'description': ''
    }
  };

  if (weather.rain) {
    if (typeof weather.rain === 'object' && weather.rain['3h']) {
      retval.rain = weather.rain['3h'];
    } else {
      retval.rain = weather.rain;
    }
  }

  if (weather.snow) {
    if (typeof weather.snow === 'object' && weather.snow['3h']) {
      retval.snow = weather.snow['3h'];
    } else {
      retval.snow = weather.snow;
    }
  }

  if (weather.clouds && weather.clouds.all) {
    retval.clouds = weather.clouds.all;
  }

  if (weather.main) {
    retval.temperature = weather.main.temp.toFixed(2);
  } else if (main.temp) {
    retval.temperature = weather.temp.toFixed(2);
  }

  if (weather.wind) {
    retval.windspeed = weather.wind.speed;
  } else if (weather.speed) {
    retval.windspeed = weather.speed;
  }

  if (weather.weather && weather.weather.length > 0) {
    retval.status.id = weather.weather[0].id;
    retval.status.summary = weather.weather[0].main;
    retval.status.description = weather.weather[0].description;
  }
  return retval;
}

/**
 * Get the weather forecast for a location for the next 7 days.
 *
 * @param {string} location the location for which weather
 *     forecast information is retrieved.
 * @return {Object} the parsed weather response.
 */
function getWeatherForecastForLocation(location) {
  var result = callWeatherServer(FORECAST_ENDPOINT, location);

  var retval = {
    'name': result.city.name,
    'country': result.city.country,
    'forecast': {
    }
  };

  for (var i = 0; i < result.list.length; i++) {
    var forecast = result.list[i];
    var date = formatDate(forecast.dt);
    retval.forecast[date] = parseWeather(forecast);
  }

  return retval;
}

/**
 * Get the current weather information for a location.
 *
 * @param {string} location the location for which weather
 *     information is retrieved.
 * @return {Object} the parsed weather response.
 */
function getWeatherForLocation(location) {
  var result = callWeatherServer(WEATHER_ENDPOINT, location);

  var retval = {
    'name': result.name,
    'country': result.sys.country,
    'weather': parseWeather(result)
  };

  return retval;
}

/**
 * Formats the date in yyyyMMdd format.
 *
 * @param {Number} dt unix timestamp from OpenWeatherMap server.
 * @return {string} the formatted date.
 */
function formatDate(dt) {
  var date = new Date(dt * 1000);
  return Utilities.formatDate(date, 'GMT', 'yyyyMMdd');
}

Оставить отзыв о...

Текущей странице
Скрипты AdWords
Скрипты AdWords