Automatiseren van prijsextensies in Google Ads met ChatGPT4

December 22, 2023
AI

In mijn eerdere blogpost van 7 december 2023 heb ik een script voor semi-geautomatiseerde prijsuitbreidingen beschreven. In dat script werd een spreadsheet ingevuld, waarbij gebruikers handmatig de koppen en beschrijvingen per product moesten toevoegen. Aangezien producttitels vaak langer zijn dan 25 tekens, moesten gebruikers zelf de titels herschrijven in een specifieke kolom, zodat deze binnen de limiet van 25 tekens zouden passen in de prijsuitbreiding [...]

In mijn eerdere blogpost van 7 december 2023 heb ik een script voor semi-geautomatiseerde prijsuitbreidingen beschreven. In dat script werd een spreadsheet ingevuld, waarbij gebruikers handmatig de koppen en beschrijvingen per product moesten toevoegen. Aangezien producttitels vaak langer zijn dan 25 tekens, moesten gebruikers zelf de titels herschrijven in een specifieke kolom, zodat deze binnen de limiet van 25 tekens zouden passen in de prijsuitbreiding.

De afgelopen week heb ik een script ontwikkeld dat ook de recente handmatige aanpassingen automatiseert. Het script maakt gebruik van je productfeed om beschrijvingen te genereren, en met behulp van het ChatGPT4 Apps-script worden de titels automatisch herschreven.

Dit script automatiseert advertentiebeheer in Google Ads door gegevens van de Google Merchant Center te koppelen aan advertentiegroepen en prijsuitbreidingen bij te werken.

Het script begint met het instellen van basisinformatie, zoals een Merchant Centre ID, het aantal dagen voor historische data, en een link naar de Google-spreadsheet. Het haalt ook de lijst met producttypen en ondersteunde talen op.

Daarna wordt data voor de afgelopen dagen opgehaald uit Google Ads. Vervolgens worden productgegevens van de verkoper opgehaald uit de Google Merchant Center-API. Het script filtert deze gegevens op basis van verkooplimieten en productlabels.

De geïdentificeerde producten worden vervolgens gestructureerd in categorieën en labels, zoals merk en categorie. Deze labels worden ook in een apart tabblad van de Google-spreadsheet geplaatst voor referentie.

Het script zoekt naar advertentiegroepen die zijn gelabeld met deze categorieën en labels. Voor elke advertentiegroep worden de beste presterende producten geselecteerd en vergeleken met de huidige prijsuitbreidingen.

Indien nodig worden verouderde prijsuitbreidingen verwijderd en nieuwe prijsuitbreidingen toegevoegd aan de advertentiegroepen. Dit helpt om de advertenties up-to-date te houden met actuele productinformatie.

Kortom, het script automatiseert het proces van het koppelen van productinformatie aan advertentiegroepen en het bijwerken van prijsuitbreidingen in Google Ads, wat resulteert in efficiënter en actueler adverteren voor de verkoper.

function main() {
    const merchant_id = XXXXXX
    const days = 60
    const sheet_url = "https://docs.google.com/spreadsheets/d/1S5SDhNO1pouXvGprRAXnwqvVpzNmaGQjPDaUzPONxmQ/copy"
    const priceType = "PRODUCT_TIERS" //supported types 'BRANDS', 'EVENTS', 'LOCATIONS', 'NEIGHBORHOODS', 'PRODUCT_CATEGORIES', 'PRODUCT_TIERS', 'SERVICES', 'SERVICE_CATEGORIES', 'SERVICE_TIERS'
    const language = "nl" //supported languages: de, en, es, es-419, fr, it, ja, nl, pl, pt-BR, pt-PT, sv
    const sales_limit = 0 // first time running this script? Please start first with sales_limit = 0. After inserting all products into the spreadsheet you could set this limit > 0.
    const field = "customLabel0" // customLabel0, customLabel1, customLabel2, customLabel3, customLabel4
    
    const sh = SpreadsheetApp.openByUrl(sheet_url).getSheetByName("List")
    const sales = getStats(days);
    const products = connectMerchant({merchant_id, stats:sales, sh, sales_limit, field});
    Logger.log('The items info is collected')
    const labels = [...Object.keys(products)]
    for (const label of labels) {
        const label_query = get_label_id(label)
        if (label_query == null) {
            Logger.log(`Cant find the label "${label}"`)
            continue
        }
        let adgroups = getAdGroups(label_query)
        if (adgroups.length == 0) {
            Logger.log(`The adgroup with label ${label} cant find`)
        }

        let cat_items = [...Object.values(products[label])].sort(function (a, b) {
            return b.sales - a.sales;
        })
        cat_items = cat_items.splice(0, 4)
        const cat_arr = cat_items.map(i => `${i.title}|${i.description}|${i.price}|${i.url}`)
        const searchResults = AdsApp.search(`SELECT ad_group_asset.field_type,
                                                    ad_group_asset.asset,
                                                    ad_group.id,
                                                    asset.price_asset.price_offerings,
                                                    ad_group.labels,
                                                    asset.type,
                                                    asset.id
                                             FROM ad_group_asset
                                             WHERE asset.type = "PRICE"
                                               AND ad_group.id IN (${adgroups.join(", ")})`)
        for (let row of searchResults) {

            const adgroup_id = row.adGroup.id
            const asset_id = row.asset.id
            const offerings = row.asset.priceAsset.priceOfferings.map(i => `${i.header}|${i.description}|${costMicros(i.price.amountMicros)}|${i.finalUrl}`)
            //offerings
            //Logger.log(offerings)
            //Logger.log(cat_arr)
            //Logger.log(cat_arr.filter(i => offerings.indexOf(i) == -1).length)
            if (cat_arr.filter(i => offerings.indexOf(i) == -1).length == 0) adgroups = adgroups.filter(i => i != adgroup_id)
            const adgroupIterator = AdsApp.adGroups().withCondition(`ad_group.id="${adgroup_id}"`).get()
            if (!adgroupIterator.hasNext()) continue
            const adgroup = adgroupIterator.next()
            const priceIterator = AdsApp.extensions().prices().withCondition(`asset.id = ${asset_id}`).get()
            if (!priceIterator.hasNext()) continue
            const priceExtension = priceIterator.next()
            adgroup.removePrice(priceExtension)
        }
        if (adgroups.length == 0) continue
        //Logger.log('Here')
        //return
        let priceBuilder = AdsApp.extensions().newPriceBuilder();
        var priceOperation = priceBuilder
            .withPriceType(priceType)             // required
            .withLanguage(language)
        const priceItems = cat_items.map(i => createPriceItem(i)).map(i => (priceOperation.addPriceItem(i)))
        priceOperation = priceOperation.build()

        if (!priceOperation.isSuccessful()) Logger.log(priceOperation.getErrors())

        const price = priceOperation.getResult()
        for (let id of adgroups) {
            const adgroupIterator = AdsApp.adGroups().withCondition(`ad_group.id="${id}"`).get()
            if (!adgroupIterator.hasNext()) continue
            const adgroup = adgroupIterator.next()
            adgroup.addPrice(price)
        }
    }

}

function connectMerchant({merchant_id, stats, sh, sales_limit, field}) {
    const arr  = []
    let obj = {}
    sh.getDataRange().getDisplayValues().filter((v,i)=>i!=0).map((v,i)=>(obj[v[0]]={index:i+2,title:v[2],description:v[3]}))
   
    var pageToken;
    var pageNum = 1;
    var maxResults = 250;
    var products = {};

    do {
        var productList = ShoppingContent.Products.list(merchant_id, {
            pageToken: pageToken,
            maxResults: maxResults
        });

        if (productList.resources) {
            for (var i = 0; i < productList.resources.length; i++) {
                const product = productList.resources[i]
                if (product.productTypes == null || product.productTypes.length == 0) continue

                const cat = product.productTypes[0].split(" > ").pop()
                // We'll only check for products that are in stock
                let cur_cat = products[cat]

                if (product["availability"] == "in stock" || (cur_cat == null || cur_cat[product.offerId] == null)) {
                    let sh_item = obj[product.offerId.toString()]
                    const sales = stats[product.offerId.toString()] || 0
                    if (!sh_item && sales >= sales_limit) arr.push([`'${product.offerId.toString()}`, product.title,  product.title.length > 50 ? "" : product.title,""])
                  
                    const title = sh_item?.title==null||sh_item?.title==""?product.title.substring(0, 25):sh_item.title
                    
                    if (title == "") continue
                    
                    const description = product[field]==null ? "" : product[field].substring(0, 25)
                    //Logger.log(product)
                    cur_cat = cur_cat || {}
                    //console.log(product)
                    const item = {
                        id: product.offerId.toString(),
                        title,
                        description,
                        price: parseFloat(product.price.value.replace(/[^0-9.]/g, '')),
                        sales,
                        url: product.link
                    }
                    
                    cur_cat[product.offerId.toString()] = item
                    products[cat] = cur_cat
                }
            }
        }
        //break
        pageToken = productList.nextPageToken;
        pageNum++;
    } while (pageToken);
    
    if (arr.length>0){
    const tmp = sh.getDataRange().getValues()
    sh.getRange(tmp.length+1,1,arr.length,arr[0].length).setValues(arr)
      }
  
    return products;

}

function getStats(days) {
    let result = {}
    const [start, end] = getDates(days)
    const searchResults = AdsApp.search(`SELECT segments.product_item_id, metrics.conversions
                                         FROM shopping_performance_view
                                         WHERE segments.date >= '${start}'
                                           AND segments.date <= '${end}'`)
    for (let row of searchResults) {
        if (row?.segments?.productItemId == null) continue
        //Logger.log(row)
        result[row.segments.productItemId] = parseFloat(row.metrics.conversions)
    }
    return result
}


function getDates(days) {
    const start = Utilities.formatDate(new Date(Date.now() - days * 86400000), AdsApp.currentAccount().getTimeZone(), "yyyy-MM-dd")
    const end = Utilities.formatDate(new Date(Date.now() - 1 * 86400000), AdsApp.currentAccount().getTimeZone(), "yyyy-MM-dd")
    return [start, end]
}

function costMicros(cost) {
    return parseInt(cost || 0) / 1000000
}

function get_label_id(Labelname) {
    const customerId = AdsApp.currentAccount().getCustomerId().replaceAll("-", "");
    const labelIterator = AdsApp.labels()
        .withCondition(`label.name = "${Labelname}"`)
        .get();
    if (labelIterator.hasNext()) {
        const label = labelIterator.next().getId();
        return `ad_group.labels CONTAINS ALL ('customers/${customerId}/labels/${label}') `;
    }
    return null;
}

function getAdGroups(label_query) {
    let result = []
    const searchResults = AdsApp.search(`SELECT ad_group.id
                                         FROM ad_group
                                         WHERE ${label_query}`)
    for (let {adGroup} of searchResults) {
        result.push(adGroup.id)
    }
    return result
}

function createPriceItem(obj) {
    var priceItemBuilder = AdsApp.extensions().newPriceItemBuilder();
    var priceItemOperation = priceItemBuilder
        .withHeader(obj.title.substring(0, 25))               // required
        .withDescription(obj.description.substring(0, 25))         // required
        .withAmount(obj.price)                            // required
        .withCurrencyCode(AdsApp.currentAccount().getCurrencyCode())//AdsApp.currentAccount().getCurrencyCode())                   // required
        .withUnitType("UNSPECIFIED")                  // required
        .withFinalUrl(obj.url)   // required
        .build();
    return priceItemOperation.getResult()

}
jermaya leijen
Jermaya Leijen arrow icon

In de loop der jaren heb ik de liefde ontwikkeld voor automation, AI en data(analyse). Hoewel ik me altijd heb gericht op Google Ads, ben ik al geruime tijd betrokken bij uiteenlopende SEO-projecten. Ik heb mezelf Python en JavaScript aangeleerd om automation en data(verwerking) te kunnen integreren in zowel mijn eigen projecten als die van klanten, met als doel hen te ondersteunen bij het behalen van hun bedrijfs- en omzetdoelstellingen.

Cases Blogs Audit Tooling