本页将介绍使用 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 子句中的条件数量
运行脚本时,常见用例是为实体列表运行报告。为此,开发者通常会构建一个非常 使用 IN 子句过滤实体 ID 的 AWQL 查询。这种方法 在实体数量有限的情况下正常运行。不过,随着查询长度的增加,由于以下两个原因,脚本性能会下降:
- 较长的查询需要更长的时间来解析。
- 您向 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 脚本一次执行一项操作。第一种方法虽然相似,
因为我们在与 where 参数不同的循环中调用 operation.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 脚本对一次运行中可检索或更新的对象数量有限制,而第二种方法中的选择和更新操作会计入该限制。
就可批量上传的实体数量而言,批量上传具有较高的上限 更新和总执行时间。
将批量上传按广告系列分组
创建批量上传时,请尝试按父级对操作进行分组 广告系列。这样可以提高效率,并降低出现冲突更改/并发错误的几率。
考虑两个同时运行的批量上传任务。一个是暂停广告中的广告 group;另一组负责调整关键字出价。即使这些操作是不相关的 操作可能适用于同一广告组(或两个不同的 Google 产品)下的实体 同一广告系列下的广告组)。在这种情况下,系统会锁定 父级实体(共享的广告组或广告系列),从而导致批量上传 彼此阻塞。
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 (MCC) 脚本
选择 executeInParallel 而非串行执行
在为经理账号编写脚本时,请改用 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 调用次数较多。