本頁將介紹使用 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 子句中的條件數量
執行指令碼時,常見的用途是為實體清單執行報表。開發人員通常會建構非常長的 GAQL 查詢,並使用 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
雖然 AWQL 在技術上仍可處理報表查詢和 withCondition 呼叫,但我們不建議使用。如要全面掌控查詢,請務必改用 GAQL。
請勿選取超過需求的資料列
報表 (和選取器) 的執行速度取決於報表傳回的總列數,無論您是否要逐一疊代這些列。也就是說,您應一律使用特定篩選器,盡可能縮小結果集,以符合您的用途。
舉例來說,假設您想找出出價超出特定範圍的廣告群組,與其擷取所有廣告群組並忽略您不感興趣的廣告群組,不如分別查詢低於最低門檻的出價和高於最高門檻的出價,這樣會更快。
| 程式設計方法 | 程式碼片段 |
|---|---|
| 使用兩項查詢 (建議做法) |
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 呼叫次數較多,效能仍不佳。