最佳做法

本页将介绍使用 Google Ads 脚本进行开发的各种最佳做法。

选择器

使用选择器进行过滤

尽量使用过滤器,做到只请求您需要的实体。应用适当的过滤器有以下几个好处:

  • 代码更简单、更容易理解。
  • 脚本执行速度更快。

我们来比较以下代码段:

编码方案 代码段
使用选择器进行过滤(推荐)
var keywords = AdsApp.keywords()
    .withCondition('Clicks > 10')
    .forDateRange('LAST_MONTH')
    .get();
while (keywords.hasNext()) {
  var keyword = keywords.next();
  // Do work here.
}
在代码中过滤(不推荐)
var keywords = AdsApp.keywords().get();

while (keywords.hasNext()) {
  var keyword = keywords.next();
  var stats = keyword.getStatsFor(
      'LAST_MONTH');
  if (stats.getClicks() > 10) {
    // Do work here.
  }
}

不建议使用第二种方法,因为它会尝试检索您账号中的所有关键字列表,仅为了对该列表应用过滤条件。

避免遍历广告系列的各个层级

如需检索特定级别的实体,请使用该级别的集合方法,而不是遍历整个广告系列层次结构。这样,除了更加简单,执行效果也会更好:系统不必读取所有广告系列和广告组。

比较以下用于检索账号中所有广告的代码段:

编码方案 代码段
使用适当的收集方法(推荐)

var ads = AdsApp.ads();

遍历层次结构(不推荐)
var campaigns = AdsApp.campaigns().get();
while (campaigns.hasNext()) {
  var adGroups = campaigns.next().
      adGroups().get();
  while (adGroups.hasNext()) {
    var ads = adGroups.next().ads().get();
    // Do your work here.
  }
}

不建议使用第二种方法,因为它会尝试提取整个对象层次结构(广告系列、广告组),而您只需要广告。

使用特定的父级访问器方法

有时候您需要获取某个检索对象的父级实体。在这种情况下,您应使用提供的访问器方法,而不是提取整个层次结构。

请比较以下代码段,它们会提取在上个月获得超过 50 次点击的文字广告的广告组:

编码方案 代码段
使用适当的父级访问器方法(推荐)
var ads = AdsApp.ads()
    .withCondition('Clicks > 50')
    .forDateRange('LAST_MONTH')
    .get();

while (ads.hasNext()) {
  var ad = ads.next();
  var adGroup = ad.getAdGroup();
  var campaign = ad.getCampaign();
  // Store (campaign, adGroup) to an array.
}
遍历层次结构(不推荐)
var campaigns = AdsApp.campaigns().get();
while (campaigns.hasNext()) {
  var adGroups = campaigns.next()
      .adGroups()
      .get();
  while (adGroups.hasNext()) {
    var ads = adGroups.ads()
       .withCondition('Clicks > 50')
       .forDateRange('LAST_MONTH')
       .get();
    if (ads.totalNumEntities() > 0) {
      // Store (campaign, adGroup) to an array.
    }
  }
}

不建议使用第二种方法,因为它会提取您账号中的整个广告系列和广告组层次结构,而您只需要与一组广告相关联的部分广告系列和广告组。第一种方法会限制自己仅提取相关的广告集合,并使用适当的方法访问其父级对象。

使用特定的父级过滤器

如需访问特定广告系列或广告组中的实体,请在选择器中使用特定过滤条件,而不是提取后遍历层次结构。

比较以下代码段,它们用于检索指定广告系列和广告组(上个月的点击次数超过 50 次)中的文字广告列表。

编码方案 代码段
使用适当的父级级别过滤条件(推荐)
var ads = AdsApp.ads()
    .withCondition('CampaignName = "Campaign 1"')
    .withCondition('AdGroupName = "AdGroup 1"')
    .withCondition('Clicks > 50')
    .forDateRange('LAST_MONTH')
    .get();

while (ads.hasNext()) {
  var ad = ads.next();
  var adGroup = ad.getAdGroup();
  var campaign = ad.getCampaign();
  // Store (campaign, adGroup, ad) to
  // an array.
}
遍历层次结构(不推荐)
var campaigns = AdsApp.campaigns()
    .withCondition('Name = "Campaign 1"')
    .get();

while (campaigns.hasNext()) {
  var adGroups = campaigns.next()
      .adGroups()
      .withCondition('Name = "AdGroup 1"')
      .get();
  while (adGroups.hasNext()) {
    var ads = adGroups.ads()
       .withCondition('Clicks > 50')
       .forDateRange('LAST_MONTH')
       .get();
    while (ads.hasNext()) {
      var ad = ads.next();
      // Store (campaign, adGroup, ad) to
      // an array.
    }
  }
}

不推荐第二种方案,因为它会迭代您账号中的广告系列和广告组层次结构,而您只需要一组选定的广告及其父级广告系列和广告组。第一种方法是通过对选择器中的父实体应用特定过滤条件,将迭代限制为广告列表。

尽量使用 ID 进行过滤

过滤实体时,最好按 ID 过滤实体,而不是按其他字段过滤。

我们来考虑以下用于选择广告系列的代码段。

编码方案 代码段
按 ID 过滤(推荐)
var campaign = AdsApp.campaigns()
    .withIds([12345])
    .get()
    .next();
按名称过滤(不太理想)
var campaign = AdsApp.campaigns()
    .withCondition('Name="foo"')
    .get()
    .next();

第二种方案按照非 ID 字段进行过滤,因此不是最优方案。

尽量按照父级 ID 进行过滤

选择实体时,应尽量按父级 ID 进行过滤。这会限制服务器在过滤结果时检索的实体列表,从而加快查询速度。

请考虑以下代码段,该代码段会按 ID 检索广告组。假设父级广告系列 ID 已知。

编码方案 代码段
按广告系列和广告组 ID 过滤(推荐)
var adGroup = AdsApp.adGroups()
    .withIds([12345])
    .withCondition('CampaignId="54678"')
    .get()
    .next();
仅按广告组 ID 过滤(不太理想)
var adGroup = AdsApp.adGroups()
    .withIds([12345])
    .get()
    .next();

虽然这两个代码段给出的结果完全相同,但代码段 1 中使用父级 ID (CampaignId="54678") 的额外过滤可以限制服务器在过滤结果时必须迭代的实体列表,从而提高代码的效率。

在过滤条件太多时使用标签

如果过滤条件过多,建议为您要处理的实体创建标签,并使用该标签过滤实体。

我们来考虑以下代码段,它们按名称检索广告系列列表。

编码方案 代码段
使用标签(推荐)
var label = AdsApp.labels()
    .withCondition('Name = "My Label"')
    .get()
    .next();
var campaigns = label.campaigns.get();
while (campaigns.hasNext()) {
  var campaign = campaigns.next();
  // Do more work
}
构建复杂的选择器(不推荐)
var campaignNames = [‘foo’, ‘bar’, ‘baz’];

for (var i = 0; i < campaignNames.length; i++) {
  campaignNames[i] = '"' + campaignNames[i] + '"';
}

var campaigns = AdsApp.campaigns
    .withCondition('CampaignName in [' + campaignNames.join(',') + ']')
    .get();

while (campaigns.hasNext()) {
  var campaign = campaigns.next();
  // Do more work.
}

虽然这两个代码段的性能大致相同,但随着选择器中条件数量的增加,第二种方法生成的代码往往会更复杂。与修改脚本以添加新实体相比,将标签应用于新实体更为简单。

限制 IN 子句中的条件数量

运行脚本时,常见用例是为实体列表运行报告。开发者通常通过构建一个非常长的 AWQL 查询来实现此目的,该查询使用 IN 子句过滤实体 ID。当实体数量有限时,此方法非常适用。不过,随着查询长度的增加,由于以下两个原因,脚本性能会下降:

  • 较长的查询需要更长的时间来解析。
  • 您添加到 IN 子句的每个 ID 都是一个要评估的额外条件,因此需要更长的时间。

在这种情况下,最好为实体应用标签,然后按 LabelId 过滤。

编码方案 代码段
应用标签并按 labelID 过滤(推荐)
// The label applied to the entity is "Report Entities"
var label = AdsApp.labels()
    .withCondition('LabelName contains "Report Entities"')
    .get()
    .next();

var report = AdsApp.report('SELECT AdGroupId, Id, Clicks, ' +
    'Impressions, Cost FROM KEYWORDS_PERFORMANCE_REPORT ' +
    'WHERE LabelId = "' + label.getId() + '"');
使用 IN 子句构建长查询(不推荐)
var report = AdsApp.report('SELECT AdGroupId, Id, Clicks, ' +
    'Impressions, Cost FROM KEYWORDS_PERFORMANCE_REPORT WHERE ' +
    'AdGroupId IN (123, 456) and Id in (123,345, 456…)');

账号更新

批量更改

当您更改 Google Ads 实体时,Google Ads 脚本不会立即执行更改。而是会尝试将多个更改合并到批处理中,以便发出一个请求来执行多个更改。这种方法可以加快脚本的运行速度,并减轻 Google Ads 服务器的负载。但是,有些代码模式会强制 Google Ads 脚本频繁刷新其批处理操作,从而导致脚本运行缓慢。

来考虑以下脚本,该脚本将更新某个关键字列表的出价。

编码方案 代码段
跟踪更新的元素(推荐)
var keywords = AdsApp.keywords()
    .withCondition('Clicks > 50')
    .withCondition('CampaignName = "Campaign 1"')
    .withCondition('AdGroupName = "AdGroup 1"')
    .forDateRange('LAST_MONTH')
    .get();

var list = [];
while (keywords.hasNext()) {
  var keyword = keywords.next();
  keyword.bidding().setCpc(1.5);
  list.push(keyword);
}

for (var i = 0; i < list.length; i++) {
  var keyword = list[i];
  Logger.log('%s, %s', keyword.getText(),
      keyword.bidding().getCpc());
}
在紧密循环中检索更新后的元素(不推荐)
var keywords = AdsApp.keywords()
    .withCondition('Clicks > 50')
    .withCondition('CampaignName = "Campaign 1"')
    .withCondition('AdGroupName = "AdGroup 1"')
    .forDateRange('LAST_MONTH')
    .get();

while (keywords.hasNext()) {
  var keyword = keywords.next();
  keyword.bidding().setCpc(1.5);
  Logger.log('%s, %s', keyword.getText(),
      keyword.bidding().getCpc());
}

不建议使用第二种方法,因为调用 keyword.bidding().getCpc() 会强制 Google Ads 脚本刷新 setCpc() 操作,并且一次只能执行一项操作。虽然第一种方法与第二种方法类似,但它还有一个额外的好处,即支持批处理,因为 getCpc() 调用是在调用 setCpc() 的循环之外完成的。

尽量使用构建器

Google Ads 脚本支持通过两种方式创建新对象:构建器和创建方法。构建器比创建方法更灵活,因为它可让您访问通过 API 调用创建的对象。

我们来考虑以下代码段:

编码方案 代码段
使用构建器(推荐)
var operation = adGroup.newKeywordBuilder()
    .withText('shoes')
    .build();
var keyword = operation.getResult();
使用创建方法(不推荐)
adGroup.createKeyword('shoes');
var keyword = adGroup.keywords()
    .withCondition('KeywordText="shoes"')
    .get()
    .next();

由于检索关键字涉及额外的选择操作,因此第二种方法不是首选。此外,创建方法也已废弃。

不过,请注意,如果使用不当,构建器可能会阻止 Google Ads 脚本批量处理操作。

请考虑以下代码段,它们会创建一个关键字列表,并输出新创建的关键字的 ID:

编码方案 代码段
跟踪更新的元素(推荐)
var keywords = [‘foo’, ‘bar’, ‘baz’];

var list = [];
for (var i = 0; i < keywords.length; i++) {
  var operation = adGroup.newKeywordBuilder()
      .withText(keywords[i])
      .build();
  list.push(operation);
}

for (var i = 0; i < list.length; i++) {
  var operation = list[i];
  var result = operation.getResult();
  Logger.log('%s %s', result.getId(),
      result.getText());
}
在紧密循环中检索更新的元素(不推荐)
var keywords = [‘foo’, ‘bar’, ‘baz’];

for (var i = 0; i < keywords.length; i++) {
  var operation = adGroup.newKeywordBuilder()
      .withText(keywords[i])
      .build();
  var result = operation.getResult();
  Logger.log('%s %s', result.getId(),
      result.getText());
}

不建议采用第二种方案,因为它在创建操作的同一循环中调用 operation.getResult(),从而迫使 Google Ads 脚本一次只执行一项操作。虽然第一种方法与第二种方法类似,但它支持批处理,因为我们是在创建 operation.getResult() 的不同循环中调用该方法。

考虑对大量更新使用批量上传

开发者执行的一项常见任务是根据当前效果值运行报告并更新实体属性(例如关键字出价)。当必须更新大量实体时,批量上传往往可以提供更好的性能。例如,请考虑以下脚本,它们会提高上个月 TopImpressionPercentage > 0.4 较高的关键字的 MaxCpc:

编码方案 代码段
使用批量上传功能(推荐)

var report = AdsApp.report(
  'SELECT AdGroupId, Id, CpcBid FROM KEYWORDS_PERFORMANCE_REPORT ' +
  'WHERE TopImpressionPercentage > 0.4 DURING LAST_MONTH');

var upload = AdsApp.bulkUploads().newCsvUpload([
  report.getColumnHeader('AdGroupId').getBulkUploadColumnName(),
  report.getColumnHeader('Id').getBulkUploadColumnName(),
  report.getColumnHeader('CpcBid').getBulkUploadColumnName()]);
upload.forCampaignManagement();

var reportRows = report.rows();
while (reportRows.hasNext()) {
  var row = reportRows.next();
  row['CpcBid'] = row['CpcBid'] + 0.02;
  upload.append(row.formatForUpload());
}

upload.apply();
按 ID 选择和更新关键字(效果不太理想)
var reportRows = AdsApp.report('SELECT AdGroupId, Id, CpcBid FROM ' +
    'KEYWORDS_PERFORMANCE_REPORT WHERE TopImpressionPercentage > 0.4 ' +
    ' DURING LAST_MONTH')
    .rows();

var map = {
};

while (reportRows.hasNext()) {
  var row = reportRows.next();
  var adGroupId = row['AdGroupId'];
  var id = row['Id'];

  if (map[adGroupId] == null) {
    map[adGroupId] = [];
  }
  map[adGroupId].push([adGroupId, id]);
}

for (var key in map) {
  var keywords = AdsApp.keywords()
      .withCondition('AdGroupId="' + key + '"')
      .withIds(map[key])
      .get();

  while (keywords.hasNext()) {
    var keyword = keywords.next();
    keyword.bidding().setCpc(keyword.bidding().getCpc() + 0.02);
  }
}

虽然第二种方法可以提供相当不错的性能,但在本例中,首选方法是第一种方法,因为

  • Google Ads 脚本对一次运行中可检索或更新的对象数量有限制,而第二种方法中的选择和更新操作会计入该限制。

  • 批量上传在可更新的实体数量和总执行时间方面具有更高的上限。

将批量上传按广告系列分组

创建批量上传文件时,请尝试按父级广告系列对操作进行分组。这样可以提高效率,并降低发生冲突更改 / 并发错误的可能性。

考虑两个同时运行的批量上传任务。前者会暂停广告组中的广告;后者会调整关键字出价。即使操作之间没有关联,这些操作也可能应用于同一广告组下的实体(或同一广告系列下的两个不同广告组)。发生这种情况时,系统将锁定父实体(共享的广告组或广告系列),从而导致批量上传任务相互阻塞。

Google Ads 脚本可以在单个批量上传任务中优化执行方式,因此最简单的方法是,每个账号一次只运行一个批量上传任务。如果您决定为每个账号运行多项批量上传,请确保批量上传操作针对互斥的广告系列列表(及其子实体)运行,以便获得最佳效果。

报告

使用报告获取统计信息

当您想要检索大量实体及其统计信息时,通常最好使用报告,而不是标准的 AdsApp 方法。建议使用报告,原因如下:

  • 对于大型查询,报告的执行效果更好。
  • 报告不会触及常规的抓取配额。

比较以下用于提取上个月获得超过 50 次点击的所有关键字的点击次数、展示次数、费用和文本的代码段:

编码方案 代码段
使用报告(推荐)
  report = AdsApp.search(
      'SELECT ' +
      '   ad_group_criterion.keyword.text, ' +
      '   metrics.clicks, ' +
      '   metrics.cost_micros, ' +
      '   metrics.impressions ' +
      'FROM ' +
      '   keyword_view ' +
      'WHERE ' +
      '   segments.date DURING LAST_MONTH ' +
      '   AND metrics.clicks > 50');
  while (report.hasNext()) {
    var row = report.next();
    Logger.log('Keyword: %s Impressions: %s ' +
        'Clicks: %s Cost: %s',
        row.adGroupCriterion.keyword.text,
        row.metrics.impressions,
        row.metrics.clicks,
        row.metrics.cost);
  }
使用 AdsApp 迭代器(不推荐)
var keywords = AdsApp.keywords()
    .withCondition('metrics.clicks > 50')
    .forDateRange('LAST_MONTH')
    .get();
while (keywords.hasNext()) {
  var keyword = keywords.next();
  var stats = keyword.getStatsFor('LAST_MONTH');
  Logger.log('Keyword: %s Impressions: %s ' +
      'Clicks: %s Cost: %s',
      keyword.getText(),
      stats.getImpressions(),
      stats.getClicks(),
      stats.getCost());
}

第二种方法不是首选方法,因为它会迭代关键字,并一次检索一个实体的统计信息。在这种情况下,报告的运行速度会更快,因为它会在单次调用中提取所有数据,并根据需要对其进行串流传输。此外,使用第二种方法检索到的关键字会计入脚本使用 get() 调用检索的实体数量配额。

使用搜索功能,而不是报告

该报告方法专为旧版基础架构而构建,即使您使用的是 GAQL,也会以平面格式输出结果。这意味着,它必须转换查询结果以匹配旧版样式,但并非所有字段都支持这种样式,而且会给每次调用增加开销。

我们建议您改用搜索功能,以便充分利用新版 Google Ads API 报告的所有功能。

优先使用 GAQL 而非 AWQL

虽然报告查询和 withCondition 调用仍支持 AWQL,但它是通过转换层运行的,该层与真正的 AWQL 不完全兼容。如需完全控制查询,请确保您使用的是 GAQL

如果您有要转换的现有 AWQL 查询,可以使用我们的查询迁移工具

选择的行数不要超过所需数量

报告(和选择器)的执行速度取决于报告将返回的行总数,无论您是否会迭代这些行。这意味着,您应始终使用特定过滤条件,尽可能缩小结果集,以符合您的用例。

例如,假设您想查找出价不在某个特定范围内的广告组。与提取所有广告组并忽略不感兴趣的广告组相比,分别发出两个查询(一个查询出价低于下限,另一个查询出价高于上限)会更快。

编码方案 代码段
使用两个查询(推荐)
var adGroups = []
var report = AdsApp.search(
    'SELECT ad_group.name, ad_group.cpc_bid_micros' +
    ' FROM ad_group WHERE ad_group.cpc_bid_micros < 1000000');

while (report.hasNext()) {
  var row = report.next();
  adGroups.push(row.adGroup);
}
var report = AdsApp.search(
    'SELECT ad_group.name, ad_group.cpc_bid_micros' +
    ' FROM ad_group WHERE ad_group.cpc_bid_micros > 2000000');

while (report.hasNext()) {
  var row = report.next();
  adGroups.push(row.adGroup);
}
从通用查询中向下过滤(不推荐)
var adGroups = []
var report = AdsApp.search(
    'SELECT ad_group.name, ad_group.cpc_bid_micros' +
    ' FROM ad_group');

while (report.hasNext()) {
  var row = report.next();
  var cpcBidMicros = row.adGroup.cpcBidMicros;
  if (cpcBidMicros < 1000000 || cpcBidMicros > 2000000) {
    adGroups.push(row.adGroup);
  }
}

Ad Manager (MCC) 脚本

选择 executeInParallel 而非串行执行

为经理账号编写脚本时,请尽可能使用 executeInParallel() 而非串行执行。executeInParallel() 可为您的脚本提供更多处理时间(最多 1 小时),并且每个要处理的账号最多可获得 30 分钟的处理时间(而不是串行执行的总时间为 30 分钟)。如需了解详情,请参阅我们的限制页面

电子表格

更新电子表格时使用批量操作

更新电子表格时,请尝试使用批量操作方法(例如 getRange()),而不是每次更新一个单元格的方法。

假设以下代码段在电子表格中生成分形图案。

编码方案 代码段
在单次调用中更新一组单元格(推荐)
var colors = new Array(100);
for (var y = 0; y < 100; y++) {
  xcoord = xmin;
  colors[y] = new Array(100);
  for (var x = 0; x < 100; x++) {
    colors[y][x] = getColor_(xcoord, ycoord);
    xcoord += xincrement;
  }
  ycoord -= yincrement;
}
sheet.getRange(1, 1, 100, 100).setBackgroundColors(colors);
一次更新一个单元格(不推荐)
var cell = sheet.getRange('a1');
for (var y = 0; y < 100; y++) {
  xcoord = xmin;
  for (var x = 0; x < 100; x++) {
    var c = getColor_(xcoord, ycoord);
    cell.offset(y, x).setBackgroundColor(c);
    xcoord += xincrement;
  }
  ycoord -= yincrement;
  SpreadsheetApp.flush();
}

虽然 Google 表格会尝试通过缓存值来优化第二个代码段,但与第一个代码段相比,其性能仍然较差,这是因为所进行的 API 调用次数较多。