Microsoft Advertising 脚本的最佳做法

若要提高脚本和平台的性能,请查看并遵循下面概述的最佳做法。

使用选择器

使用筛选器

使用选择器的筛选器,而不是自行筛选实体。 选择器允许按 ID 和条件进行筛选。 例如,可以按平均 CPC 大于 10) 的实体的性能 (返回市场活动、其状态 (已暂停) 的市场活动、实体的父对象的名称等进行筛选。

使用筛选器的好处:

  • 选择器 返回的实体数限制为仅所需的那些实体。

  • 允许脚本更快地执行 (更少的实体返回和处理)

  • 降低出现实体读取限制的可能性, (请参阅 脚本执行限制) 。

正确方式

    var adGroups = AdsApp.adGroups()
        .withCondition('Status = PAUSED')
        .get();

    while (adGroups.hasNext()) {
        var adGroup = adGroups.next();
        // Do something with paused ad group.
    }

错误的方式

    var adGroups = AdsApp.adGroups().get();

    while (adGroups.hasNext()) {
        var adGroup = adGroups.next();
        
        if (adGroup.isPaused() == true) {
            // Do something with paused ad group.
        }
    }

不要遍历实体层次结构

如果要获取实体的子实体或实体的父实体,请不要遍历实体层次结构来获取它们。

若要获取子实体,请在所需的级别使用子实体的集合。

正确方式

    // Get all ads.
    var ads = AdsApp.ads().get();

    while (ads.hasNext()) {
        var ad = ads.next();
        // Do something with ad.
    }

或者,如果想要来自特定市场活动的广告:

    // Get all ads in the campaign, 'mycampaign'.
    var ads = AdsApp.ads()
        .withCondition("CampaignName = 'mycampaign'")
        .get();

    while (ads.hasNext()) {
        var ad = ads.next();
        // Do something with ad.
    }

或者,如果你有市场活动对象,则获取市场活动的广告:

    // Get all ads in the campaign.
    var ads = campaign.ads().get();

    while (ads.hasNext()) {
        var ad = ads.next();
        // Do something with ad.
    }

错误的方式

    var campaigns = AdsApp.campaigns().get();

    while (campaigns.hasNext()) {
        var adGroups = campaigns.next().adGroups().get();
        
        while (adGroups.hasNext()) {
            var ads = adGroups.next().ads().get();

            while (ads.hasNext()) {
                var ad = ads.next();
                // Do something with ad.
            }
        }
    }

如果想要获取实体的父级,则同样适用。 使用子实体的父访问器方法,而不是遍历层次结构以获取父级。

正确方式

    // Get all ads.
    var ads = AdsApp.ads()
        .withCondition('Clicks > 5')
        .forDateRange('LAST_7_DAYS')
        .get();

    while (ads.hasNext()) {
        var ad = ads.next();
        
        // Do something with campaign and adGroup.
        var adGroup = ad.adGroup();
        var campaign = ad.campaign();
    }

错误的方式

    var campaigns = AdsApp.campaigns().get();

    while (campaigns.hasNext()) {
        var campaign = campaigns.next();
        var adGroups = campaign.adGroups().get();
        
        while (adGroups.hasNext()) {
            var adGroup = adGroups.next();
            var ads = adGroup.ads().get();

            while (ads.hasNext()) {
                var ad = ads.next();
                
                if ('<some condition is met>') {
                    // Do something with campaign and adGroup.
                }
            }
        }
    }

尽可能使用实体 ID

使用 ID 筛选实体可提供最佳性能。

    var adGroups = AdsApp.adGroups()
        .withIds(["123456"])
        .get();

    while (adGroups.hasNext()) {
        var adGroup = adGroups.next();
        
        // Do something with adGroup.
    }

提供比这更好的性能

    var adGroups = AdsApp.adGroups()
        .withCondition("Name = 'myadgroup'")
        .get();

    while (adGroups.hasNext()) {
        var adGroup = adGroups.next();
        
        // Do something with adGroup.
    }

避免使用选择器和不必要的获取数的紧密循环

避免使用获取单个实体的 get 请求的循环。 例如,假设你运行了关键字 (keyword) 性能报告,并且想要更新报表中的关键字。 在循环浏览报表的每一行时,应创建关键字 (keyword) ID 列表,而不是从报表获取行、获取关键字 (keyword) 然后更新它。 然后,将 ID 列表传递给选择器,以获取单个获取请求中的所有关键字。 然后,可以循环访问关键字列表并更新它们。

正确方式

    var report = AdsApp.report('<report query goes here>');

    var rows = report.rows();
    var idLists = []; // an array where each element contains an array of IDs.
    var idList = [];  // array of IDs that's limited to maxCount.
    var maxCount = 10000;

    while (rows.hasNext()) {
        var row = rows.next();

        if (idList.length < maxCount) {
            idList.push(row['id']);
        }
        else {
            idLists.push(idList);
            idList = [];
        }
    }

    for (idList of idLists) {
        var keywords = AdsApp.keywords()
            .withIds(idList)
            .get();

        while (keywords.hasNext()) {
            var keyword = keywords.next();
            // update the keyword        
        }
    }

错误的方式

    var report = AdsApp.report('<report query goes here>');

    var rows = report.rows();

    while (rows.hasNext()) {
        var row = rows.next();

        var keyword = AdsApp.keywords()
            .withIds([row['id']])
            .get()
            .next();

        // update the keyword        
    }

仅当计划调用实体的 getStats 方法时,才包括 forDateRange 方法

调用选择器的 forDateRange 方法会导致选择器获取实体的性能数据。 获取实体的性能数据成本高昂,因此仅当计划调用实体 getStats 的 方法并使用数据时,才获取它。

为实体指定的日期范围不适用于从该实体访问的父实体或子实体。 例如,如果收到一个广告组,然后获取其父市场活动,并尝试访问市场活动的性能指标,则调用会失败。

campaignStats.getReturnOnAdSpend()以下示例中的调用失败,因为日期范围适用于广告组,而不是市场活动。

    var myAdGroups = AdsApp.adGroups().
        .withCondition("CampaignName CONTAINS 'gen'")
        .forDateRange("LAST_7_DAYS")
        .get();

    while (myAdGroups.hasNext()) {
        var adGroup = myAdGroups.next();
        var campaign = adGroup.getCampaign();
        var campaignStats = campaign.getStats();
        var campaignROAS = campaignStats.getReturnOnAdSpend();
    }

为此,你需要为市场活动创建一个选择器。

    var myAdGroups = AdsApp.adGroups().
        .withCondition("CampaignName CONTAINS 'gen'")
        .forDateRange("LAST_7_DAYS")
        .get();

    while (myAdGroups.hasNext()) {
        var adGroup = myAdGroups.next();
        var campaign = AdsApp.campaigns()
            .withIds([adGroup.getCampaign().getId()])
            .forDateRange("LAST_7_DAYS")
            .get()
            .next();
        var campaignStats = campaign.getStats();
        var campaignROAS = campaignStats.getReturnOnAdSpend();
    }

不要更改在选择器中用作条件的实体属性

迭代器通过一次只加载一个项目而不是整个项目集来减轻内存压力。 因此,更改在选择器中用作条件的属性可能会导致意外行为。

正确方式

    var adGroups = []; 

    var iterator = AdsApp.adGroups()
        .withCondition('Status = ENABLED')
        .get();

    while (iterator.hasNext()) {
        adGroups.push(iterator.next());
    }

    for (var adGroup of adGroups) {
        adGroup.pause();
    }

错误的方式

    var adGroups = AdsApp.adGroups()
        .withCondition('Status = ENABLED')
        .get();

    while (adGroups.hasNext()) {
        var adGroup = adGroups.next();
        adGroup.pause();
    }

批处理更新

为了提高性能,脚本会批量处理生成请求。 如果调用生成请求的操作方法,它将强制脚本立即处理排队的生成请求,以消除任何性能提升。 如果要创建多个实体,请不要在用于生成实体的同一循环中执行操作方法。 这会导致性能不佳,因为一次只处理一个实体。 相反,创建操作数组并在生成循环后进行处理。

正确方式

    // An array to hold the operations, so you 
    // can process them after all the entities are queued.
    var operations = []; 

    // Create all the new entities.
    for (var i = 0; i < keywords.length; i++) {
        var keywordOperation = AdsApp.adGroups().get().next()
          .newKeywordBuilder()
          .withText(keywords[i])
          .build();
        operations.push(keywordOperation);
    }

    // Now call the operation method so the build requests
    // get processed in batches.
    for (var i = 0; i < operations.length; i++) {
        var newKeyword = operations[i].getResult();
    }

错误的方式

    for (var i = 0; i < keywords.length; i++) {
        var keywordOperation = AdsApp.adGroups().get().next()  // Get the first ad group
          .newKeywordBuilder()  // Add the keyword to the ad group
          .withText(keywords[i])
          .build();

        // Don't get results in the same loop that creates
        // the entity because Scripts then only processes one
        // entity at a time.
        var newKeyword = keywordOperation.getResult();
    }

如果更新实体,然后获取更新的相同属性,则情况也是如此。 不要这样做

    var bidAmount = 1.2;

    while (keywords.hasNext()) {
        var keyword = keywords.next();

        keyword.bidding().setCpc(bidAmount);

        if (keyword.bidding().getCpc() != bidAmount) {
            Logger.log(`Failed to update bid amount for keyword, ${keyword.getText()} (${keyword.getId()})`);
        }
    }

获取大型实体集时使用收益率关键字 (keyword)

检索大量实体并将其加载到循环中处理的单个列表中有几个缺点:

  1. 根据请求的大小,可能需要 n 个后端请求才能在循环启动之前提取所有实体。 如果不全部处理它们,则用于获取未处理的实体的时间和计算能力将浪费。 例如,如果检索 10K 个关键字,并且仅处理 2K 个关键字后循环中断,则用于获取剩余 8K 关键字的时间和计算能力将浪费。

  2. 创建列表需要更多内存来同时容纳所有实体。

若要解决这些问题,请使用收益关键字 (keyword) ,它允许脚本按需提取实体,或者,从某种意义上说,仅在需要时才“延迟”提取实体。 这意味着脚本的调用数不会超过其目前所需的数量,并且不会传递大量对象列表。

此示例包括日志记录,以说明使用收益关键字 (keyword) 时的控制流。

function main() {
    const keywords = getKeywords();

    //@ts-ignore <-- suppresses iterator error
    for (const keyword of keywords) {
        Logger.log("in for loop\n\n");
    }
}

// Note that you must use the yield keyword in a generator function - see the
// '*' at the end of the function keyword.

function* getKeywords() {
    const keywords = AdsApp.keywords()
        .withCondition("Status = ENABLED")
        .withCondition("CombinedApprovalStatus = APPROVED")
        .withLimit(10)
        .get();

    Logger.log(`total keywords in account: ${keywords.totalNumEntities()} \n\n`);

    while (keywords.hasNext()) {
        Logger.log("before next()\n\n");
        yield keywords.next();
        Logger.log("after next\n\n");
    }
}

避免实体限制的调用模式

脚本可以为帐户返回的实体数有限制。 如果请求将返回超过此限制,则脚本会引发错误,并显示消息“ 实体过多”。 以下示例演示获取大量实体时应使用的调用模式。 该示例首先尝试在帐户级别提取所有关键字。 如果失败,它会尝试多次调用以按市场活动提取关键字,因为市场活动级别的实体通常较少。 如果需要,通常可以继续此模式到广告组级别。

有关使用收益关键字 (keyword) 的信息,请参阅在获取大型实体集时使用收益关键字 (keyword)

function* getEntities() {
    const applyConditions = _ => _
        .withCondition('CampaignStatus = ENABLED')
        .withCondition('AdGroupStatus = ENABLED')
        .withCondition('Status = ENABLED')
        .withCondition("CombinedApprovalStatus = DISAPPROVED");

    try {
        // Get the account's keywords. 
        const keywords = applyConditions(AdsApp.keywords()).get();

        while (keywords.hasNext()) {
            yield keywords.next();
        }
    } catch (e) {
        if (!e.message.startsWith('There are too many entities')) {
            throw e;
        }

        // If there are too many keywords at the account level,
        // get keywords by campaigns under the account.
        const campaigns = AdsApp.campaigns().get();

        while (campaigns.hasNext()) {
            const campaign = campaigns.next();

            const keywords = applyConditions(campaign.keywords()).get();

            while (keywords.hasNext()) {
                yield keywords.next();
            }
        }
    }
}