最佳做法

本頁將說明使用 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 擷取 AdGroup。假設已知父項廣告活動 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 為 0 的關鍵字 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 (我的客戶中心) 指令碼

請優先使用 executeInParallel 而非序列執行

為管理員帳戶編寫指令碼時,請盡可能使用 executeInParallel() 而非逐一執行。executeInParallel() 可為指令碼提供更多處理時間 (最多一小時),每個處理的帳戶最多可處理 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 呼叫次數的關係,其效能仍不如第一個程式碼片段。