本頁將說明使用 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 呼叫次數的關係,其效能仍不如第一個程式碼片段。