広告カスタマイザ

広告文に最新の動的な値を表示すると、ユーザーに強くアピールすることができます。広告に価格を掲載しようと考えている広告主様にとって、最新の情報が常に広告文に反映される機能は便利です。広告を削除して追加し直すと、過去のデータは失われ、さらにポリシーの審査が終わるまで広告掲載を待たなければなりません。AdWords スクリプトと広告カスタマイザを組み合わせることで、ライブデータを取得して広告を更新し、最新の価格や在庫レベルなど、時間とともに変化するあらゆる項目を反映させることができます。

このスクリプトでは、在庫データを Google スプレッドシートに保存している架空のフラワーショップ用に、パラメータ化された広告を設定します。広告の設定により、在庫にある特定の花が検索されると、その花と価格でカスタマイズされた広告が表示されるようになっています。「花」など、より抽象的な言葉が検索された場合は、新鮮なうちにより多く販売できるよう、在庫数が最も多い花を表示します。

実際の広告キャンペーンでは、Google スプレッドシートや Google ドライブに保存された情報や、その他の UrlFetchApp で取得可能なデータ フィードの情報など、各広告主様の独自のデータを使用します。このスクリプトを修正して、前述の花のデータの代わりに独自のデータを組み込むことができます。

広告カスタマイザでは、広告パラメータが提供する機能がさらに強化されています。数値だけでなく任意の文字列を使うことができ、キーワード単位だけでなく、キャンペーンや広告グループ単位でも適用できます。また、広告カスタマイザが収集する掲載結果は分析やレポートに活かすことができます。

仕組み

データソース、アイテム、広告

カスタマイズされた広告は、文字列がカスタム データによって置き換えられている点を除けば、基本的には通常のテキスト広告です。

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」というデータ ソースから検索された値が挿入されます。各データソースは、関連する広告カスタマイザが 1 セット保持し、広告に挿入可能な属性のセットを定義します。この場合は、「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;
}

広告カスタマイザの更新

スクリプトは、在庫スプレッドシートのそれぞれの花について、対応する広告カスタマイザの price 属性を更新する一方、対応する広告カスタマイザが存在しない場合は、新しく作成します。また、在庫量が最も多い花を追跡し、広告を表示するキーワードに関わらず、広告グループ全体に適用される「デフォルト」の広告カスタマイザを変更または作成します。

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 は必ず変更します。
  • スクリプトが毎時間実行されるようスケジュールを設定します。

ソースコード


// 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;
}

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。