Nonbrand uitsluiten in Shopping

Sluit nonbranded zoektermen uit in branded Shopping advertentiegroepen. Dit script werkt één of meerdere producten per advertentiegroep en bij meerdere merken.

Start nu!
Nonbrand uitsluiten in Shopping
Nonbrand Get started!

Bij het opbouwen van de Google Shopping campagnes wordt vaak gekozen voor een splitsing van branded traffic en non-branded traffic. De statistieken van branded campagnes zijn vaak positiever dan non-branded campagnes en om biedingen hierop te optimaliseren, is het aan te raden om deze splitsing te maken in accounts.

Door gebruik te maken van de prioritering in Shopping campagnes, komen non-branded zoektermen binnen in de campagne met de hoogste prioriteit. In deze campagne worden branded zoektermen uitgesloten, zodat je nu een branded en een non-branded campagne hebt.

Praktijk

Hoewel in theorie een mooie opbouw van de campagnestrucuur, in de praktijk blijkt altijd dat dit niet waterdicht is. Ondanks het goed instellen van de prioritering en uitsluitingen, zullen we non-branded zoektermen binnenkomen in de branded campagnes. Dit is vervelend voor de optimalisatie van het account, omdat er nu te hoge geboden wordt voor non-branded zoektermen. De enige oplossing is het uitsluiten van alle non-branded termen in de brand campagnes. Omdat dit veel tijd kan kosten wanneer je dit handmatig doet, hebben we dit proces geautomatiseerd met onderstaand script.

Hoe het werkt

Dit scripts werkt voor verschillende campagnestructuren. Om dit script te kunnen gebruiken, moet de boomstructuur in een advertentiegroep uitgesplitst zijn tot op product-level. Het script bekijkt per advertentiegroep welke productgroepen hierin zitten, verzameld van deze productgroepen de merknamen en vergelijkt deze daarna met de zoektermen in de advertentiegroepen. Komt geen van de merknamen terug in de zoektermen? Dan wordt de zoekterm uitgesloten.

Om ervoor te zorgen dat er zoveel mogelijk matches worden gemaakt met de merknaam, splitsen we de merknamen op in het script wanneer deze uit meerdere woorden bestaat. Voor elk afzonderlijk woord wordt dan gekeken of er een match is. Daarnaast kan je er ook voor kiezen om afkortingen te gebruiken van merknamen of te matchen aan zoektermen.

Instellingen

Om dit script goed te kunnen draaien, hoef je niet zoveel in te stellen:

  • LOG: Geef aan of het script de tussenstappen moet rapporteren, door de waarde aan te passen naar 'true'.
  • MERCHANT_ID:Het ID van het Merchant Center account. De gebruiker die het script draait, moet ook als gebruiker zijn toegevoegd aan Merchant Center. Je moet ook de geavanceerde API 'Shopping Content' aanzetten in het script (rechtsboven het script-veld).
  • TARGET_COUNTRY: Selecteer alleen producten uit GMC voor een specifiek land. Leeg laten als je dit niet wilt gebruiken.
  • LABEL_ADGROUPS: Om de voortgang van het script bij te houden, worden alle advertentiegroepen gelabeld wanneer ze gecontroleerd zijn.
  • FILTER_CAMPAIGN_NAME: Gebruik dit filter om alleen branded campagnes te selecteren op basis van de campagnenaam.
  • USE_MPN: Zet op 'true' wanneer je MPN wilt meenemen als branded keyword.
  • USE_EAN: Zet op 'true' als je de EAN wilt meenemen als branded keyword.
  • USE_ACRONYMS: Inschakelen wanneer je afkortingen van merknamen wilt gebruiken.
  • BRAND_MAPPING_SHEET_URL: Optioneel, gebruik deze sheet om schrijfwijzen of typo's van merknamen aan te vullen.
  • BRAND_MAPPING_SHEET_NAME: Wanneer je de sheet gebruikt, zet hier dan de naam van het tabblad in.
The script
// Copyright 2024
// Free to use or alter for everyone. A mention would be nice ;-)
//
// Created By: Tibbe van Asten
// 
// Created: 21-03-2019
// Last update: 24-01-2024
//
// ABOUT THE SCRIPT
// With this script you can exclude non-branded queries 
// from branded shopping campaigns. The brand is taken from the Merchant Center.
// By doing this every day, the accountstructure will remain clean.
//
////////////////////////////////////////////////////////////////////

var config = {
  
  LOG : true,  
  
  // Connect Merchant Center. Add user to MC that runs this script.
  // Also enable Advanced API 'Shopping Content'.
  MERCHANT_ID : “123456789”,
  
  // Label all checked adgroups.
  LABEL_ADGROUPS : "Non Brand Excluded",
  
  // Select only your branded campaigns by campaignname.
  FILTER_CAMPAIGN_NAME : “campaignname example”,
  
  // If you want to use MPN or EAN as branded keywords, set to true.
  USE_MPN : false,
  USE_EAN : false,
  
  // If you don't want to use acronyms as brandname, set to false.
  USE_ACRONYMS : false,
  
  // Optionally, use a spreadsheet for brand typo's or different languages.
  // Copy the following sheet: https://docs.google.com/spreadsheets/d/1fAxCYsksEljl71LljDzk4jkfBtSTg2ur1zyGnvXOU1E/copy
  // Leave empty when you don't want to use this.
  BRAND_MAPPING_SHEET_URL : "",
  BRAND_MAPPING_SHEET_NAME : ""
  
}

////////////////////////////////////////////////////////////////////

function main() {
  
  var labelId = checkLabel();  
  var values = connectSheet();
  var products = connectMerchant();
  var adGroupIterator = selectAdgroups(labelId);  
  
    if(config.LOG === true){
      Logger.log("Products collected from Merchant Center");
      Logger.log("Connected Brand Mapping Sheet");
      Logger.log("Selected adgroups to check");
      Logger.log(" ");
    }
  
  // When all adgroups are checked, all labels will be removed to start again
  if(adGroupIterator.totalNumEntities() == 0) {
      var adGroupIterator = AdsApp
        .shoppingAdGroups()
        .withCondition("ad_group.labels CONTAINS ANY ('customers/"+AdsApp.currentAccount().getCustomerId().replace(/-/g, '')+"/labels/"+labelId+"')")
        .withCondition("campaign.name = '"+config.FILTER_CAMPAIGN_NAME+"'")
        .withCondition("campaign.status = ENABLED")
        .get();
    
      while(adGroupIterator.hasNext()) {
    	  var adGroup = adGroupIterator.next(); 
        adGroup.removeLabel(config.LABEL_ADGROUPS);
      }    
  }
  
  while(adGroupIterator.hasNext()){
    var adGroup = adGroupIterator.next(); 
    
      if(config.LOG === true){
        Logger.log("Selected " + adGroup.getName());
      }
    
    var brands = [];
    var brands = selectBrands(adGroup, products, brands, values);
    
    if(brands.length > 0){
      
      var keywordCount = 0;
    
      // Select queries within this adgroup
      var rows = AdsApp.search(
        "SELECT search_term_view.search_term " +
        "FROM search_term_view " +
        "WHERE search_term_view.ad_group = 'customers/"+AdsApp.currentAccount().getCustomerId().replace(/-/g, '')+"/adGroups/"+adGroup.getId()+"' " +
        "AND segments.date DURING LAST_30_DAYS"
      );
      
        if(config.LOG === true){
          Logger.log(" ");
          Logger.log("Search Terms: " + rows.totalNumEntities());
        }

      while (rows.hasNext()) {
        let row = rows.next();
        
        var query = trimQuery(row.searchTermView.searchTerm);

        // This variable will define if a query is excluded or not. If false, the query will
        // be excluded. If set to true, we will keep the query.
        var match = false;

        for(var i = 0;i < brands.length;i++){

          var brandSplitLength = brands[i].replace("'", " ").replace("-", " ").replace("&", "").replace("  ", " ").split(" ").length;
          var brandSplit = brands[i].replace("'", " ").replace("-", " ").replace("&", "").replace("  ", " ").split(" ");

          // It's possible that parts of a brandname are present in a query.
          // In that case, we will treat the query as branded
          if(brandSplitLength > 1) {
            for (var x = 0; x < brandSplit.length; x++) {  

              if(brandSplit[x].match(/[a-z]/g)){
                if((query.indexOf(brandSplit[x]) > -1 || query.indexOf(brandSplit[x].replace("é", "e")) > -1) && brandSplit[x].match(/[a-z]/g).length > 1){
                  var match = true;
                  break;
                }    
              }
            }
          }

          // After looking at parts of the brandname, we want to know if the brandname
          // is present or the brandname without numbers or spaces is present in the query.
          if(match === false && (query.indexOf(brands[i]) > 1  || query.indexOf(brandSplit) > -1)) {
            var match = true;
            break;
          }  
          
          // The last thing we'll check, is acronyms. Like th for Tommy Hilfiger or hb for Hugo Boss
          // These therms will also not be excluded
          if(match === false && brandSplitLength > 1 && config.USE_ACRONYMS === true){
            var acronym = brands[i].match(/\b(\w)/g).join('').toLowerCase();
            
            if(query.indexOf(acronym) > -1){
              var match = true;              
              break;
            }       
          }

        } // brandsIterator
        
        // If no match is found in the query, we will exclude the query from the adgroup
        if(match === false){
          adGroup.createNegativeKeyword(query);
          
          if(keywordCount == 0){
            if(config.LOG === true){
              Logger.log(" ");
              Logger.log("Campaign: " + adGroup.getCampaign().getName() + ", Adgroup: " + adGroup.getName());
              Logger.log("---");
            }
          } keywordCount++;

          Logger.log("EXCLUDED: " + query + " in " + adGroup.getName());                                                                                                                                                                
        } 

      } // rowIterator
      
    } // if brands exist
    
    adGroup.applyLabel(config.LABEL_ADGROUPS);
    if(config.LOG === true){
      Logger.log("Label added to " + adGroup.getName());
    }
    
    if(config.LOG === true){
      Logger.log(" ");
    }
    
  } // adGroupIterator
  
  Logger.log(" ");
  Logger.log("Thanks for using this custom script by Tibbe van Asten. Winning!");
  
} // function main()

////////////////////////////////////////////////////////////////////

function connectSheet(){

  if(config.BRAND_MAPPING_SHEET_URL != "" && config.BRAND_MAPPING_SHEET_NAME != ""){
    
    // Connect to the spreadsheet
    var ss = SpreadsheetApp.openByUrl(config.BRAND_MAPPING_SHEET_URL);
    var sheet = ss.getSheetByName(config.BRAND_MAPPING_SHEET_NAME);
    var range = sheet.getDataRange();
    var values = range.getValues();
  
  }
  
  return values;
    
} // connectSheet()

////////////////////////////////////////////////////////////////////

function connectMerchant(){
  
    if(config.MERCHANT == "123456789"){
      throw Error("Change the Merchant ID in the settings");
    }  
    
  var pageToken;
  var pageNum = 1;
  var maxResults = 250;
  var products = [];

  do {

    var productList = ShoppingContent.Products.list(config.MERCHANT_ID, {
      pageToken: pageToken,
      maxResults: maxResults,
      
    });
    
    if (productList.resources) {
      for (var i = 0; i < productList.resources.length; i++) {
        
        // We'll focus only on products that are in stock.
        if(productList.resources[i]["availability"] == "in stock"){
          products.push(productList.resources[i]);
        }
      }
    }

    pageToken = productList.nextPageToken;
    pageNum++;
  } while (pageToken);
  
  return products;
  
} // function connectMerchant

////////////////////////////////////////////////////////////////////

function selectAdgroups(labelId){
  
    if(config.FILTER_CAMPAIGN_NAME == ""){
      
      var adGroupIterator = AdsApp
        .shoppingAdGroups()
        .withCondition("ad_group.status = ENABLED")
        .withCondition("campaign.status = ENABLED")        
        .withCondition("ad_group.labels CONTAINS NONE ('customers/"+AdsApp.currentAccount().getCustomerId().replace(/-/g, '')+"/labels/"+labelId+"')")
        .get();
      
    } else {
      
       var adGroupIterator = AdsApp
        .shoppingAdGroups()
        .withCondition("ad_group.status = ENABLED")
        .withCondition("campaign.status = ENABLED")
        .withCondition("campaign.name = '"+config.FILTER_CAMPAIGN_NAME+"'")
        .withCondition("ad_group.labels CONTAINS NONE ('customers/"+AdsApp.currentAccount().getCustomerId().replace(/-/g, '')+"/labels/"+labelId+"')")
        .get();   
      
      Logger.log("Adgroups found: "+ adGroupIterator.totalNumEntities());
   
    }
  
	return adGroupIterator;
  
} // function selectAdgroups()

////////////////////////////////////////////////////////////////////

function checkLabel(){
  
  if(config.LABEL_ADGROUPS !== "" && !AdsApp.labels().withCondition("label.name = '"+config.LABEL_ADGROUPS+"'").get().hasNext()) {
    AdsApp.createLabel(config.LABEL_ADGROUPS);
  }
  
  if(config.LABEL_ADGROUPS !== "" && AdsApp.labels().withCondition("label.name = '"+config.LABEL_ADGROUPS+"'").get().hasNext()){
    var labelId = AdsApp.labels().withCondition("label.name = '"+config.LABEL_ADGROUPS+"'").get().next().getId();
  }
  
  return labelId;
  
} // function checkLabel()

////////////////////////////////////////////////////////////////////

function selectBrands(adGroup, products, brands, values) {
  
  var productGroups = [];
 
  var productGroupIterator = adGroup
    .productGroups()
    .withCondition("PartitionType = UNIT")
    .get();

  while(productGroupIterator.hasNext()){
    var productGroup = productGroupIterator.next();

    if(productGroup.isOtherCase() === false){
      
      productGroups.push(productGroup.getValue());
      var brand = checkValue(products, productGroup.getValue(), "brand") || ""; 
      var mpn = checkValue(products, productGroup.getValue(), "mpn") || "";  
      var gtin = checkValue(products, productGroup.getValue(), "gtin") || "";

        if(config.LOG === true){
          Logger.log("Selected product: " + productGroup.getValue() + ". Brand: " + brand); 
        }

      // Only add new brands
      if(brands.indexOf(brand.toLowerCase()) < 0 && brand != ""){
        brands.push(brand.toLowerCase());

          if(config.LOG === true){
            Logger.log("Added " + brand + " to brands"); 
          }
      }

      // Add MPN as branded keywords
      if(config.USE_MPN === true && brands.indexOf(mpn.toLowerCase()) < 0 && mpn != ""){
        brands.push(mpn.toLowerCase());
      }

      // Add EAN as branded keywords
      if(config.USE_EAN === true && brands.indexOf(gtin.toLowerCase()) < 0 && gtin != ""){
        brands.push(gtin.toLowerCase());
      }

      // Check sheet for additional brandname alternatives
      if(brand != "" && config.BRAND_MAPPING_SHEET_URL != "" && config.BRAND_MAPPING_SHEET_NAME != ""){      
        for(var i = 1;i < values.length;i++){
          if(values[i][0] == brand){
            for(var x = 1;x < values[i].length;x++){
              if(brands.indexOf(values[i][x].toLowerCase()) < 0){
                brands.push(values[i][x].toLowerCase());
              }
            } // brandIterator
          }
        } // sheetIterator      
      } // check brand mapping
      
    } // otherCase
  
  } // productGroupIterator
  
  return brands;
  
} // function selectBrands()

////////////////////////////////////////////////////////////////////

function checkValue(products, productId, field) {
  
  for (var i = 0; i < products.length; i++) {
    
    if(products[i]["offerId"] == productId || products[i]["offerId"] == productId.toUpperCase()){
      	var value = products[i][field];
      	break;
    } // if statement

  } // for statement
  
  return value;
  
} // function checkValue()

////////////////////////////////////////////////////////////////////

function trimQuery(searchTerm){
  
  // When the searchquery exceeds the limit of 10 words, we will split
  // the query and put max. 10 words back together as a phrasematch query    	
  var query = "";
  if (searchTerm.split(" ").length < 10) {
    query = "[" + searchTerm + "]";
  } else {
    for (var i = 0; (i < searchTerm.split(" ").length) && (i < 10); i++){ 
      query += searchTerm.split(" ")[i] + " ";                 
    }
    query = '"' + query.replace(/\s+$/,'') + '"';
  }
  
  return query;
  
} // function trimQuery()
Show whole script!
Loading Comments
The Experts
Tibbe van Asten Team Lead Performance Marketing
Nils Rooijmans Water Cooler Topics
Martijn Kraan Freelance PPC Specialist
Bas Baudoin Teamlead SEA @ Happy Leads
Jermaya Leijen Digital Marketing Strategist
Krzysztof Bycina PPC Specialist from Poland
How about you? JOIN US!
Sharing Knowledge
Caring

Adsscripts.com staat voor het delen van kennis. In de huidige markt houden SEA-specialisten de kennis en ervaring graag voor zich. Wij zijn er van overtuigd dat het delen van kennis ervoor kan zorgen dat iedereen beter wordt in haar of zijn werk. Daarom lopen wij hier graag in voorop, door onze kennis over scripts te delen met iedereen.

Wil jij ook graag een bijdrage leveren? Wij staan open voor nieuwe ideeën en feedback op alles wat je op Adsscripts.com vindt.

Neem contact op

Training &
Workshop
Neem contact op!
Adsscripts Training & Workshop