最佳做法

本頁說明使用 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 指令碼一次執行一項作業。第一個方法雖然相似,但由於我們在不同迴圈中呼叫 operations.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);
  }
}

Ads Manager (我的客戶中心) 指令碼

優先執行 runInParallel 而非序列執行

編寫管理員帳戶的指令碼時,請盡可能使用 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 呼叫數量,因此效能不如第一個程式碼片段。