本頁將介紹使用 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
較高的關鍵字最高單次點擊出價:
程式設計方法 | 程式碼片段 |
---|---|
使用大量上傳功能 (建議做法) |
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); } } |
Google Ads 管理員帳戶 (MCC) 指令碼
優先使用 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 呼叫次數較多,效能仍不佳。