Skrypt Google Ads: Automatic SPAG creator

SPAG, czyli Single Product Ad Groups, to koncept, który zakłada tworzenie jednego produktu dla jednej grupy reklam.

Rozwiązanie to pozwala analizować wyszukiwane hasła na poziomie produktu (w klasycznej strukturze opcja ta nie jest dostępna).

W niektórych branżach podział tego typu może pozytywnie wpłynąć na efektywność reklam.

Niestety ręczne przeprowadzenie takiego procesu jest niezwykle czasochłonne.
Szczególnie jeśli nasz sklep posiada szeroki asortyment produktów.
Na szczęście Google Ads umożliwia automatyzację tego procesu za pomocą skryptu.

Przygotowane rozwiązanie tworzy SPAG-i w wybranej kampanii – jej nazwę należy wprowadzić do zmiennej „CAMPAIGN_NAME” (kampania musi być wcześniej utworzona ręcznie).

Do kampanii tej zostaną dodane tylko te produkty, które wybraliśmy za pomocą etykiety w pliku produktów.


Więcej na temat etykiet w oficialnej dokumentacji:
https://support.google.com/google-ads/answer/6275295

Przykładowa konfiguracja:
1. Tworzymy ręcznie pustą kampanię np. SPAG – wszystkie produkty
2. Wprowadzamy nazwę kampanii do zmiennej CAMPAIGN_NAME = „SPAG – wszystkie produkty”
3. Wprowadzamy ID naszego Merchant Center do zmiennej MERCHANT_ID
4. W pliku produktów za pomocą własnej etykiety oznaczamy produkty, które chcemy, aby zostały uwzględnione w procesie tworzenia SPAG-ów, np. własna etykieta 1 i wartość: „WYDZIEL”.
Dalej w zmiennej „CUSTOM_LABEL_NUMBER” nadajemy wartość: „customLabel1”, a w zmiennej „CUSTOM_LABEL_VALUE” wartość „WYDZIEL”, czyli tę nadaną w pliku produktów.
5. W zaawansowanych ustawieniach włączamy „Shopping content”:
https://developers.google.com/google-ads/scripts/docs/features/advanced-apis

Harmonogram: co godzinę


Po więcej na temat Skryptów zapraszam na grupę FB o automatyzacji:
https://www.facebook.com/groups/skrypty.google.ads


//Updated on 05.04.2020: Code structure improvements

/*

Copyright 2019 Krzysztof Bycina, www.LiveAds.pl
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/


// -------------------------------------------- Configuration:

var MERCHANT_ID = 'ENTER THE MERCHANT ID HERE';
var CAMPAIGN_NAME = 'ENTER THE DESTINATION CAMPAIGN NAME HERE'; // Destination shopping campaign
var CUSTOM_LABEL_NUMBER = "customLabel4"; // Possible values: customLabel0, customLabel1, customLabel2, customLabel3, customLabel4
var CUSTOM_LABEL_VALUE = "ENTER YOUR VALUE HERE"; // Only products with this value will be build.
var STARTING_BID = 0.5; // What bid for created products?

// --------------------------------------------  End of the configuration




function main() {
    verifyCampaign();
    verifyMerchantId();
    verifyCustomLabel();

    var currentAdGroups = verifyCurrentAdGroups();
    var merchantData = fetchTheData(currentAdGroups);

    if (merchantData.titleData.length > 0) {
        Logger.log("Creating Ad groups...");
        createShoppingAdGroups(merchantData);
    } else {
        Logger.log("There are no products to build.");
    }
}


function createShoppingAdGroups(merchantData) {
    var shoppingCampaign = AdWordsApp.shoppingCampaigns()
        .withCondition("CampaignName = '" + CAMPAIGN_NAME + "'")
        .get()
        .next();
    for (i = 0; i < merchantData.titleData.length; i++) {
        Logger.log(merchantData.titleData[i] + " // " + merchantData.itemIdData[i])

        //build an ad group
        var adGroupOperation = shoppingCampaign.newAdGroupBuilder()
            .withName(merchantData.titleData[i] + " // " + merchantData.itemIdData[i])
            .withStatus("ENABLED")
            .build();

        //build a product
        var shoppingAdGroup = adGroupOperation.getResult();
        shoppingAdGroup.createRootProductGroup();
        shoppingAdGroup.rootProductGroup().newChild()
            .itemIdBuilder()
            .withBid(STARTING_BID)
            .withValue(merchantData.itemIdData[i])
            .build();

        //build an ad
        var ad = shoppingAdGroup
            .newAdBuilder()
            .build()
            .getResult();

        //Exclude everything else:
        findOtherCase = shoppingAdGroup.productGroups().get();
        while (findOtherCase.hasNext()) {
            var isThisOtherCase = findOtherCase.next()
            if (isThisOtherCase.isOtherCase()) {
                isThisOtherCase.exclude()
            }
        }
    }
}


function fetchTheData(currentAdGroups) {
    var theData = {
        titleData: [],
        itemIdData: []
    }

    var productNumber = 0
    var pageToken;
    var pageNum = 1;
    var maxResults = 250;
    do {
        var products = ShoppingContent.Products.list(MERCHANT_ID, {
            pageToken: pageToken,
            maxResults: maxResults
        });
        if (products.resources) {
            for (var i = 0; i < products.resources.length; i++) {
                if (products.resources[i][CUSTOM_LABEL_NUMBER] === CUSTOM_LABEL_VALUE && products.resources[i][CUSTOM_LABEL_NUMBER]) {
                    var newAdGroupName = products.resources[i].title + " // " + products.resources[i].offerId;

                    //Check if the current ad group already exists
                    if (currentAdGroups.indexOf(newAdGroupName) === -1.0) {
                        theData.titleData.push(products.resources[i].title);
                        theData.itemIdData.push(products.resources[i].offerId);
                    }
                  }
                }
            }
            pageToken = products.nextPageToken;
            pageNum++;
        }
        while (pageToken);
        Logger.log("Total products to build: " + theData.titleData.length);
        return theData;
    }

    function verifyCustomLabel() {
        if (CUSTOM_LABEL_NUMBER === "customLabel0" || CUSTOM_LABEL_NUMBER === "customLabel1" || CUSTOM_LABEL_NUMBER === "customLabel2" || CUSTOM_LABEL_NUMBER === "customLabel3" || CUSTOM_LABEL_NUMBER === "customLabel4") {} else {
            throw new Error("There is a problem with the CUSTOM_LABEL_NUMBER configuration. Remember: it is CASE SENSITIVE.");
        }
    }

    function verifyMerchantId() {

        if (isNaN(MERCHANT_ID)) {
            throw new Error("Error: Please, enter a correct Merchand ID number.");
        }
    }

    function verifyCurrentAdGroups() {
        var adGroupsData = [];
        var adGroupIterator = AdsApp
            .shoppingAdGroups()
            .withCondition("CampaignName = '" + CAMPAIGN_NAME + "'")
            .get();
        while (adGroupIterator.hasNext()) {
            var adGroup = adGroupIterator.next();
            adGroupsData.push(adGroup.getName());
        }
        Logger.log("Total products in the Campaign: " + adGroupsData.length);
        return adGroupsData;
    }

    function verifyCampaign() {
        var campaign = AdWordsApp.shoppingCampaigns().withCondition("Name = '" + CAMPAIGN_NAME + "'").get();
        if (campaign.totalNumEntities() === 0) {
            throw new Error("Shopping Campaign with the name: " + CAMPAIGN_NAME + " Dosen't exist. Verify the script configuration & run it again.");
        }
    }

8 comments / Add your comment below

  1. Hejka,
    Świetny pomysł z tym skryptem, jednak mimo wszystko wciąż nie udało mi się go odpalić.
    czy możesz napisać jak powinienem poprawić skrypt aby zamiast CUSTOM_LABEL_NUMBER, wykorzystać kolumnę z „marką produktu” a jako wydziel podać „brand”?

    Drugi pomysł który był by równie wartosciowy to nie korzystać z w ogólę z „CUSTOM_LABEL_NUMBER”, tylko utworzyć jedną kampanie która ma wszystkie produkty.

  2. Chciałbym dopytać jak wygląda sytuacja gdy np dziś dodam SPAG z feeda który ma np 300 produktów. Za 2 tygodnie 10% produktów wypada i dochodzi 20% nowych produktów.
    Czy nie masz jakiegoś pomysłu na aktualizację takiej kampanii SPAG (pauzowanie produktów ze statusem brak w magazynie to się powinno dać zrobić regułą) oraz dodawanie nowych aktualnych których nie ma obecnie?
    Pozdrawiam Tomasz.

    1. Cześć Tomasz,

      skrypt radzie sobie z powyższym scenariuszem. Należy ustawić częstotliwość na: raz dziennie, w godzinach nocnych.

      Gdy pojawią się nowe produkty w Merchant Center, to skrypt doda je automatycznie (sprawdza co jest obecnie i co należy jeszcze dodać). Produkty, które nie są aktywne w Merchant, nie powodują wyświetleń reklamy, więc nie trzeba ich wstrzymywać.

  3. Hmm. A co z sytuacją gdy zmieniają się tytuły produktów (optymalizacja)? Hmm. Czy jest jakaś opcja żeby spagi tworzyły się z tytułem ad group jako id produktu? 🙂

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *