广告定制工具

在广告文字中显示最新的动态值,可以对广告浏览者产生莫大的吸引力。想在广告中宣传价格优势的广告客户可以通过持续提供最新的广告文字而获益。删除然后重新添加广告不仅会失去所有历史信息,而且还需要等待政策审核。在广告定制工具中使用 AdWords 脚本可以读取实时数据并更新广告,以反映最新的价格、库存水平或其他任何随时间变化的情况。

以下脚本为一家虚构的鲜花店设置参数化广告,该鲜花店的库存数据存放在 Google 电子表格中。完成广告设置后,当用户搜寻有库存的某种鲜花后,广告会针对性地展示该鲜花品种及其价格;当用户搜寻更宽泛的字词(比如“鲜花”)时,广告将展示库存最多的鲜花品种,这样花店可以趁新鲜卖出更多花。

在实际的广告系列中,您可使用自己的数据源,无论它们是存储在 Google 电子表格、Google 云端硬盘还是其他可通过 UrlFetchApp 检索的数据 Feed 中。您可以修改此脚本,在示例中添加您自己的数据,而不是使用鲜花数据。

广告定制工具增强了广告参数的功能: - 可以使用任意文字,而不只是数字。 - 广告定制工具可以在广告系列或广告组级别使用,而不只是在关键字一级使用。 - 广告定制工具收集的统计信息可供您进行分析和报告。

工作原理

数据源、项和广告

定制广告和普通文字广告基本相同,区别在于其中的一些文字由自定义数据取代:

adGroup.newTextAdBuilder()
    .withHeadline('{=Flowers.name} For Sale')
    .withDescription1('Fresh cut {=Flowers.name_lowercase}')
    .withDescription2('starting at {=Flowers.price}')
    .withDisplayUrl('www.example.com')
    .withFinalUrl('http://www.example.com')
    .build();

此文字广告将插入它从名为“Flowers”的数据源中检索到的值。每个数据源都有一套相关的广告定制工具,并可声明一整套可替换到广告中的“属性”,在本例中为“name”和“name_lowercase”(提供类型为 text 的数据),以及“price”(提供类型为 price 的数据):

AdWordsApp.newAdCustomizerSourceBuilder()
    .withName('Flowers')
    .addAttribute('name', 'text')
    .addAttribute('name_lowercase', 'text')
    .addAttribute('price', 'price')
    // Attributes named 'Custom ID' are special: the system will make sure
    // that all values in the data source have unique custom IDs.
    .addAttribute('Custom ID', 'text')
    .build();

最后,每个数据源都包含各自的广告定制工具项,这些项提供要替换到广告中的实际值,并跟踪特定广告变体的统计信息。广告定制工具可应用到整个帐号,也可以针对具体的广告系列、广告组或关键字。以下脚本为每种有库存的鲜花创建了广告定制工具,当用户搜寻某种鲜花时,就会展示相应广告。

source.adCustomizerItemBuilder()
    .withAttributeValue('Custom ID', flower.name.toLowerCase())
    .withAttributeValue('name', flower.name),
    .withAttributeValue('name_lowercase', flower.name.toLowerCase()),
    .withAttributeValue('price', flower.price)
    .withTargetKeyword(flower.name)
    .build();

使用什么数据?

以下脚本从 Google 电子表格中读取鲜花库存数据。

// Read flower / quantity / price data from a spreadsheet. In a real
// advertising campaign, you would have your own data source to read from.
function readFlowers(url) {
  var flowersByName = {};
  var spreadsheet = SpreadsheetApp.openByUrl(url);
  var sheet = spreadsheet.getSheets()[0];
  var data = sheet.getRange(2, 1, sheet.getMaxRows() - 1, 3).getValues();
  for (var i = 0; i < data.length; i++) {
    if (data[i][0]) {
      var flower = {
        name: data[i][0],
        quantity: parseFloat(data[i][1]),
        price: data[i][2]
      };
      if (typeof flower.price != 'string') {
        // Spreadsheets will sometimes coerce "$4.99" into just the number
        // 4.99. In that case, add the dollar sign back.
        flower.price = '$' + flower.price.toFixed(2);
      }
      flowersByName[flower.name] = flower;
    }
  }
  return flowersByName;
}

更新广告定制工具

对于库存电子表格中的每种鲜花,以下脚本将尝试为相应的广告定制工具更新价格属性,或者,如果不存在相应的定制工具,则创建新的广告定制工具。它还会跟踪库存最多的鲜花品种,然后修改或创建一个应用到整个广告组的“Default”广告定制工具,无论触发广告的是哪些关键字。

function main() {
  var adGroup = getAdGroup(CAMPAIGN, ADGROUP);
  var source = getOrCreateDataSource();
  maybeCreateAds(adGroup);

  // Get all customizer items in the 'Flowers' data source, and create a map
  // from item ID to item.
  var customizers = source.items().get();
  var customizersById = {};
  while (customizers.hasNext()) {
    var customizer = customizers.next();
    customizersById[customizer.getAttributeValue('Custom ID')] = customizer;
  }

  // For each flower in inventory, update the matching ad customizer item's
  // 'price' attribute. Also find the flower with the highest quantity in stock.
  var flowersInStock = readFlowers(SPREADSHEET_URL);
  var mostInStock;
  var highestStock = 0;
  for (var flowerName in flowersInStock) {
    var flower = flowersInStock[flowerName];
    if (flower.quantity > highestStock) {
      highestStock = flower.quantity;
      mostInStock = flower;
    }

    var customizer = customizersById[flower.name.toLowerCase()];
    if (customizer) {
      customizer.setAttributeValue('price', flower.price);
    } else {
      source.adCustomizerItemBuilder()
          .withAttributeValue('Custom ID', flower.name.toLowerCase())
          .withAttributeValue('name', flower.name)
          .withAttributeValue('name_lowercase', flower.name.toLowerCase())
          .withAttributeValue('price', flower.price)
          .withTargetKeyword(flower.name)
          .build();
    }
  }

  // Point the 'default' customizer item to the flower that has the largest
  // quantity in stock. The default customizer item has no target keyword, so
  // it'll be triggered for generic search terms like 'flowers'.
  var defaultCustomizer = customizersById['Default'];
  if (defaultCustomizer) {
    defaultCustomizer.setAttributeValues({
      name: mostInStock.name,
      name_lowercase: mostInStock.name.toLowerCase(),
      price: mostInStock.price
    });
  } else {
    source.adCustomizerItemBuilder()
        .withAttributeValue('Custom ID', 'Default')
        .withAttributeValue('name', mostInStock.name)
        .withAttributeValue('name_lowercase', mostInStock.name.toLowerCase())
        .withAttributeValue('price', mostInStock.price)
        .build();
  }
}

设置

  • 创建一个新的广告组,并添加任何您想要的关键字。
  • 使用下面的源代码设置基于电子表格的脚本。 使用此电子表格模板的副本。
  • 切勿忘记更新代码中的 SPREADSHEET_URLCAMPAIGNADGROUP
  • 将脚本设为每小时 (hourly) 运行。

源代码


// 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 Ad Customizer
 *
 * @overview The Ad Customizer script shows how to setup parameterized ads in
 *     your account. This script generates ads for a hypothetical flower shop
 *     whose inventory data is stored in Google Spreadsheets. In real
 *     advertising campaigns, you'll need to substitute it with your own
 *     datasources. See
 *     https://developers.google.com/adwords/scripts/docs/solutions/customizer
 *     for more details.
 *
 * @author AdWords Scripts Team [adwords-scripts@googlegroups.com]
 *
 * @version 1.0.2
 *
 * @changelog
 * - version 1.0.2
 *   - Added validation for external spreadsheet setup.
 * - version 1.0.1
 *   - Updated to use expanded text ads.
 * - version 1.0
 *   - Released initial version.
 */

// Replace this with the URL of the spreadsheet containing your data.
var SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL';
// Replace this with the name of the campaign you want to use.
var CAMPAIGN = 'Campaign name';
// Replace this with the name of the ad group you want to use.
var ADGROUP = 'Flowers';

/**
 * Retrieves an ad group given a campaign and ad group name.
 *
 * @param {string} campaignName The campaign name.
 * @param {string} adGroupName The ad group name.
 * @return {!AdGroup} The ad group.
 * @throws if the specified campaign and ad group does not exist.
 */
function getAdGroup(campaignName, adGroupName) {
  return AdWordsApp.adGroups()
      .withCondition('Name = "' + adGroupName + '"')
      .withCondition('CampaignName = "' + campaignName + '"')
      .get()
      .next();
}

/**
 * Creates customized ads if they haven't already been created.
 *
 * @param {!AdGroup} adGroup The ad group within which to create the ads.
 */
function maybeCreateAds(adGroup) {
  var ads = adGroup.ads().get();
  while (ads.hasNext()) {
    var ad = ads.next();
    if (ad.isType().expandedTextAd()) {
      var expandedTextAd = ad.asType().expandedTextAd();
      if (expandedTextAd.getHeadlinePart1() == 'Flowers For Sale') {
        // The ads have already been created; no need to do more
        return;
      }
    }
  }

  // Reference the 'Flowers' data source here; text will be inserted when the
  // ad is served.
  adGroup.newAd().expandedTextAdBuilder()
      .withHeadlinePart1('{=Flowers.name} For Sale')
      .withHeadlinePart2('Fresh cut {=Flowers.name_lowercase}')
      .withDescription('starting at {=Flowers.price}')
      .withFinalUrl('http://example.com')
      .build();
  // All ad groups also need to have an ad without ad customizers to fall back
  // on, in case no ad customizers are able to serve.
  adGroup.newAd().expandedTextAdBuilder()
      .withHeadlinePart1('Flowers For Sale')
      .withHeadlinePart2('Fresh cut flowers')
      .withDescription('delivered for cheap')
      .withFinalUrl('http://www.example.com')
      .build();
}

/**
 * Retrieves or creates the Flowers data source.
 *
 * @return {!AdCustomizerSource}
 */
function getOrCreateDataSource() {
  var sources = AdWordsApp.adCustomizerSources().get();
  while (sources.hasNext()) {
    var source = sources.next();
    if (source.getName() == 'Flowers') {
      return source;
    }
  }
  return AdWordsApp.newAdCustomizerSourceBuilder()
      .withName('Flowers')
      .addAttribute('name', 'text')
      .addAttribute('name_lowercase', 'text')
      .addAttribute('price', 'price')
      // Attributes named 'Custom ID' are special: the system will make sure
      // that all values in the data source have unique custom IDs.
      .addAttribute('Custom ID', 'text')
      .build()
      .getResult();
}

/**
 * Reads flower / quantity / price data from a spreadsheet. In a real
 * advertising campaign, you would have your own data source to read from.
 *
 * @param {string} url The URL of the spreadsheet to read from.
 * @return {!Object} A lookup of flower details, by name.
 */
function readFlowers(url) {
  var flowersByName = {};

  Logger.log('Using spreadsheet - %s.', url);
  var spreadsheet = validateAndGetSpreadsheet(url);

  var sheet = spreadsheet.getSheets()[0];
  var data = sheet.getRange(2, 1, sheet.getMaxRows() - 1, 3).getValues();
  for (var i = 0; i < data.length; i++) {
    if (data[i][0]) {
      var flower = {
        name: data[i][0],
        quantity: parseFloat(data[i][1]),
        price: data[i][2]
      };
      if (typeof flower.price != 'string') {
        // Spreadsheets will sometimes coerce "$4.99" into just the number 4.99.
        // In that case, add the dollar sign back.
        flower.price = '$' + flower.price.toFixed(2);
      }
      flowersByName[flower.name] = flower;
    }
  }
  return flowersByName;
}

/**
 * Obtains a mapping from Customizer ID to Customizer object.
 *
 * @param {!AdCustomizerSource} source
 * @return {!Object.<!AdCustomizerItem>} A mapping from custom ID to item.
 */
function getCustomizersById(source) {
  var customizers = source.items().get();
  var customizersById = {};
  while (customizers.hasNext()) {
    var customizer = customizers.next();
    customizersById[customizer.getAttributeValue('Custom ID')] = customizer;
  }
  return customizersById;
}

/**
 * Sets the price value of existing customizers, or creates new customizers
 * where none exists.
 *
 * @param {!AdCustomizerSource} source
 * @param {!Object} flowersInStock A mapping of flower names to objects
 *     objects containing flower details, including price.
 * @param {!Object.<!AdCustomizerItem>} customizersById A mapping from custom ID
 *     to ad customizer.
 * @return {!Object} The object representing the flower with most in stock.
 */
function setCustomizerValues(source, flowersInStock, customizersById) {
  var mostInStock;
  var highestStock = 0;
  for (var flowerName in flowersInStock) {
    var flower = flowersInStock[flowerName];
    if (flower.quantity > highestStock) {
      highestStock = flower.quantity;
      mostInStock = flower;
    }
    var customizer = customizersById[flower.name.toLowerCase()];
    if (customizer) {
      customizer.setAttributeValue('price', flower.price);
    } else {
      source.adCustomizerItemBuilder()
          .withAttributeValue('Custom ID', flower.name.toLowerCase())
          .withAttributeValue('name', flower.name)
          .withAttributeValue('name_lowercase', flower.name.toLowerCase())
          .withAttributeValue('price', flower.price)
          .withTargetKeyword(flower.name)
          .build();
    }
  }
  return mostInStock;
}

/**
 * Main entry point.
 */
function main() {
  var adGroup = getAdGroup(CAMPAIGN, ADGROUP);
  var source = getOrCreateDataSource();
  maybeCreateAds(adGroup);

  // Get all customizer items in the 'Flowers' data source, and create a map
  // from item ID to item.
  var customizersById = getCustomizersById(source);

  // For each flower in inventory, update the matching ad customizer item's
  // 'price' attribute. Also find the flower with the highest quantity in stock.
  var flowersInStock = readFlowers(SPREADSHEET_URL);
  var mostInStock =
      setCustomizerValues(source, flowersInStock, customizersById);

  // Point the 'default' customizer item to the flower that has the largest
  // quantity in stock. The default customizer item has no target keyword, so
  // it'll be triggered for generic search terms like 'flowers'.
  var defaultCustomizer = customizersById['Default'];
  if (defaultCustomizer) {
    defaultCustomizer.setAttributeValues({
      name: mostInStock.name,
      name_lowercase: mostInStock.name.toLowerCase(),
      price: mostInStock.price
    });
  } else {
    source.adCustomizerItemBuilder()
        .withAttributeValue('Custom ID', 'Default')
        .withAttributeValue('name', mostInStock.name)
        .withAttributeValue('name_lowercase', mostInStock.name.toLowerCase())
        .withAttributeValue('price', mostInStock.price)
        .build();
  }
}

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

/**
 * Validates the provided spreadsheet URL and email address
 * to make sure that they're 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 or email hasn't been set
 */
function validateAndGetSpreadsheet(spreadsheeturl) {
  if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
    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;
}

发送以下问题的反馈:

此网页
AdWords Scripts
AdWords Scripts
需要帮助?请访问我们的支持页面