共用方式為


Microsoft Advertising 腳本的最佳做法

若要改善腳本的效能,以及平臺的效能,請檢閱並遵循下列概述的最佳做法。

使用選取器

使用篩選

使用選取器的篩選準則,而不是自行篩選實體。 選取器可讓您依識別碼和條件進行篩選。 例如,您可以依實體的效能進行篩選, (傳回平均值大於 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.
                }
            }
        }
    }

盡可能使用實體識別碼

使用識別碼來篩選實體可提供最佳效能。

    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 要求中的所有關鍵詞。 接著,您可以逐一查看關鍵字清單並加以更新。

正確方式

    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()})`);
        }
    }

取得大型實體集時,請使用 yield 關鍵字

擷取大量實體,並將其載入您在迴圈中處理的單一清單,有幾個缺點:

  1. 視要求的大小而定,在迴圈啟動之前,可能需要 n 個後端要求來擷取所有實體。 如果您未全部處理它們,則會浪費用來取得未處理實體的時間和運算能力。 例如,如果您擷取 10K 關鍵字,且迴圈在只處理 2K 關鍵字之後中斷,則用來取得其餘 8K 關鍵字的時間和運算能力會浪費。

  2. 建立清單需要更多記憶體,才能同時保存所有實體。

若要解決這些問題,請使用 yield 關鍵字,讓您的腳本視需要擷取實體,或者,在某些情況下,只有在需要實體時才「延遲」擷取它們。 這表示您的腳本目前所發出的呼叫數目不會超過其需求,也不會傳遞大型物件清單。

此範例包含記錄,以說明使用 yield 關鍵字時的控制流程。

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");
    }
}

呼叫模式以避免實體限制

腳本可針對帳戶傳回的實體數目有限制。 如果要求傳回超過此限制,腳本會擲回錯誤並顯示訊息: 實體太多。 下列範例顯示您在取得大量實體時應該使用的呼叫模式。 此範例會先嘗試擷取帳戶層級的所有關鍵詞。 如果失敗,它會嘗試依行銷活動多次呼叫擷取關鍵字,因為行銷活動層級的實體通常較少。 如有需要,您通常可以繼續此模式到廣告群組層級。

如需使用 yield 關鍵字的相關資訊,請參 閱取得大型實體集時使用 yield 關鍵字

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();
            }
        }
    }
}