Improving your Google Ads campaigns by using Rules, Experiments and Scripts

Rules, Experiments and Scripts - How to improve your Google campaigns automatically using scripts?

In the Update Upgrade Conference 2019 Avishay Freund gave a lecture about how to improve you Google campaigns by using automation. By using rules, experiments, and scripts we can maximize our results. All the details follow.

Automatic Rules
Also called rules / actions, and their objective is to perform certain actions automatically in a pre-selected time, for instance daily / weekly / monthly.

Examples:
When the cost of the campaign reached 6000 USD this month, hold the advertising.
Send a daily e-mail message with a search phrase ranking less than 3.

So How Do We Do It?

– Enter the campaign / group / keyword level

– Mark the desired (campaign, ad group, key phrases – you can choose one or more)

– Click “more” (the three dots)

– Click “Generating an Automatic Rule”:

First Example

Every morning at 6 a.m. the rule is activated: if a certain campaign spent 6,000 USD until today – we stop the campaign

What did we choose?

– We chose to stop the campaign
– We can choose whatever campaign we want
– We set the condition
– We set the activity frequency and the period of time it relates to

Second Example

– Sending an e-mail message every morning with all the search phrases that rank less than 3.

– Enter the key words

– Choose sending mail.

Experiments

– Today, Google sends us a variety of suggestions to manage our account.
– To switch between strategies, enter the “campaign” menu, go to “settings” and to “your price bid”

How to Activate a experiments?

First, create a draft, than define the experiment and the decision

Stage 1:

– At the bottom of the menu, choose drafts and experiment 
– In the new screen opened, add a new draft

– Name the draft and save it

– In the menu the draft will appear at the bottom
– Choose it (hover it with the mouse) and choose drafts and experiments again
– Choose a new experiments

Stage 2 - Experiments Generation:

– Fill in the details
– Originally, the campaign strategy was manual cost per click, I want to examine a transfer to conversion objective strategy
– The experiment will be done on half of the traffic
– The experiment will last about two weeks (from 5/2 to 22/3)
– Save

– Set up the campaign:

– The experiment started:

Notes

– It is possible to stop the experiment at any time.
– If the experiment worked and you don’t want to wait you can apply the experiment .

Scripts

What Is a Script?

– A script is a code that performs an automatic action. There are plenty of scripts you can download.
– The script exists on Google servers.
– The script can activate on an account or on your entire MCC.

The First Script

– The script monitors activity ads and checks their status, if the link is broken – it sends a report + an option for an immediate pause
– Script name: ® Ads Final URL Monitor (Paus + Report)
– Two parameters:

  1. A mail address to send the report
  2. Whether or not to stop the ads (just a report)
    – Account level script
  • Note: at a certain point you will be requested to authorize

You can copy the script from here:


var EMAIL = '[email protected]';
var PAUSE = false;

function main(){  
  var htmlService = HTMLBuilderService();
  
  var ads = AdWordsApp.ads()
  .withCondition('CampaignStatus = ENABLED')
  .withCondition('AdGroupStatus = ENABLED')
  .withCondition('Status = ENABLED')
  .withCondition('Type NOT_IN [CALL_ONLY_AD]') // IMAGE_AD, MOBILE_AD, MOBILE_IMAGE_AD, PRODUCT_AD, RICH_MEDIA_AD, TEMPLATE_AD, TEXT_AD, CALL_ONLY_AD
  .withLimit(4)
  .get();
  
  Logger.log('%s ads found.', ads.totalNumEntities());
  
  var matches = 0;  
  while(ads.hasNext()){
    var ad = ads.next();      
    var adURL = ad.urls().getFinalUrl();
    var adHeader = ad.getHeadline();
    var adType = ad.getType();
    
    Logger.log('ad: %s (%s)', adHeader, adType);
    
    var statusCode = -1;
    try{
      var response = UrlFetchApp.fetch(adURL, { muteHttpExceptions: true });
      statusCode = response.getResponseCode();      
      Logger.log('[status %s] %s', statusCode, adURL);      
    }
    catch(e){
      Logger.log('[exception %s] %s', e.message, adURL);      
    }
    
    // OK
    if(statusCode == 200 || statusCode == -1) 
      continue;    
    
    var adgroup = ad.getAdGroup();
    var adgroupName = adgroup.getName();
    
    Logger.log('adgroup: %s', adgroupName);
    Logger.log('%s [%s]', adURL, statusCode);    
    
    matches++;
    
    htmlService.add(
      '

statusCode: ' + statusCode +'

' + '
adGroup: ' + adgroupName +'
' + '

adURL: ' + adURL + '

' ); if(PAUSE) { Logger.log('ad paused'); ad.pause(); } } if(matches > 0){ MailApp.sendEmail(EMAIL, 'Ad Monitor', '', { htmlBody: htmlService.get() }); } } var HTMLBuilderService = function(){ var _html = ''; return { add: function(content){ _html += content; }, get: function(){ return _html; } }; }

Name your script, authorize it, and paste it.

Write down your mail and whether or not to stop

Click the scripts and choose the frequency of activating the script:

You can see the history of the scripts, when the script was active and which action did it perform:

Second Script - Monthly Report

– Script name: (R) Account Daily Stats + Charts – Current Month
– Script objective: generating an excel with nice graphs
– The scripts generates a Google Sheet document within the drive
– At the head of the script put down the name of the file you want
– If the file does not exist, the script generates one automatically, if the file exists, the script updates it.
– The script is at the account level

You can copy the script from here:


var SPREADSHEET = 'AccountDailyStats';

function main() {  
  var sheetService = SpreadsheetLoader.loadSheetService(SPREADSHEET);
  if(!sheetService) // create the sheet 
    sheetService = SpreadsheetLoader.createSheetService(SPREADSHEET);
  
  sheetService.clear();
  
  Logger.log('Loading "%s" Account Statistics ...', AdWordsApp.currentAccount().getName());
  var report = AdWordsApp.report(
     'SELECT Date, Impressions, Clicks, ConversionRate, Cost ' +
     'FROM   ACCOUNT_PERFORMANCE_REPORT ' +
     'DURING THIS_MONTH');
  
  report.exportToSheet(sheetService.sheet);
  sheetService.sort(1);
  
  SetTotals(sheetService);    
  
  if(!sheetService.hasCharts)
    DrawChart(sheetService);  
  
  Logger.log('Done!');
}

function SetTotals(sheetService){
  
  // TOTALS (using formulas)
  Logger.log('Setting formulas ...');
  
  var lastRowIndex = sheetService.getLastRowIndex();  
  var totalRowIndex = (parseInt(lastRowIndex) + 1).toString();
  
  sheetService.setCellSumFormula('B2:B'.concat(lastRowIndex), 'B'.concat(totalRowIndex));
  sheetService.setCellSumFormula('C2:C'.concat(lastRowIndex), 'C'.concat(totalRowIndex));
  sheetService.setCellAvgFormula('D2:D'.concat(lastRowIndex), 'D'.concat(totalRowIndex));
  sheetService.setCellSumFormula('E2:E'.concat(lastRowIndex), 'E'.concat(totalRowIndex));
  sheetService.setDataUI('A'.concat(totalRowIndex, ':E', totalRowIndex), '#F5F6F9', '#000000', 20, 'center');   
}

function DrawChart(sheetService){
  Logger.log('Drawing Chart ... ');
     
  var lastRowIndex = sheetService.getLastRowIndex();
  
  // arrRanges, vAxisDataLeft, vAxisDataRight, width, height, rowPosition, columnPosition, chartTitle 
  // vAxisData: { title, ticks, color, legend }
  sheetService.addLineChartDualY(
    ['A2:A'.concat(lastRowIndex), 'B2:B'.concat(lastRowIndex), 'C2:C'.concat(lastRowIndex)],
    { 
      title: 'impressions',
      color:'#4572A7', 
      legend:'impressions'
    },
    { 
      title: 'clicks',       
      color:'#AA4643', 
      legend:'clicks'
    },   
    700, 160, 3, 7, // chart location - row 3, column 7
    'impressions vs clicks'
  ); 
  
  sheetService.addLineChartDualY(
    ['A2:A'.concat(lastRowIndex), 'D2:D'.concat(lastRowIndex), 'C2:C'.concat(lastRowIndex)],
    { 
      title: 'conv.Rate',
      color:'#4572A7', 
      legend:'conv.Rate'
    },
    { 
      title: 'clicks',       
      color:'#AA4643', 
      legend:'clicks'
    },  
    700, 160, 11, 7, // chart location - row 10, column 7
    'conv.Rate vs clicks'
  ); 
  
  sheetService.addLineChartDualY(
    ['A2:A'.concat(lastRowIndex), 'E2:E'.concat(lastRowIndex), 'C2:C'.concat(lastRowIndex)],
    { 
      title: 'cost',
      color:'#4572A7', 
      legend:'cost'
    },
    { 
      title: 'clicks',       
      color:'#AA4643', 
      legend:'clicks'
    },
    700, 160, 19, 7, // chart location - row 17, column 7
    'cost vs clicks'
  ); 
}

/* SPREADSHEET SERVICE */
var SpreadSheetService = function(spreadSheet) {
  var _spreadSheet = spreadSheet;
  var _sheets = [];

  (function(){
    var sheetServices = [];
    var temp_sheets = spreadSheet.getSheets();
    for(var i= 0; i  0,
     findColumnValuesByFilter: function(columnPosition, filterValue, filterColumnPosition){
       /* 
          get column values filtered by other column
          
          e.g: findColumnValuesByFilter(2, '100', 1)          
          all B column values that the value in A column equals to '100'
       */

       var result = [];       
       var rows = _sheet.getDataRange().getValues();
 
       for(row in rows)
         if(rows[row][filterColumnPosition - 1] == filterValue)
           result.push(rows[row][columnPosition]);
       return result;  
     }, 
     clear: function(charts, format, contents){
       charts = charts || false;
       format = format || false;
       contents = contents || true;
       
       if(!charts) return; 
       
       // clear all charts
       _sheet.clear({ formatOnly: format, contentsOnly: contents });        
       var charts = _sheet.getCharts();
       for (var i in charts)
         _sheet.removeChart(charts[i]);
     },          
     setValue: function(rowPosition, columnPosition, value){ 
       _sheet.getRange(rowPosition, columnPosition).setValue(value);
     },     
     setRangeValue: function(strRange, value){ 
       // e.g: setCurrencyFormat('A1'); // set cell
       _sheet.getRange(strRange).setValue(value);
     },
     setDataUI: function(strRange, backgroundColor, foreColor, fontSize, align){
       var range = _sheet.getRange(strRange);
       if(backgroundColor)
           range.setBackground(backgroundColor);
       if(foreColor)
           range.setFontColor(foreColor);
       if(fontSize)
           range.setFontSize(fontSize);
       if(align)
         range.setHorizontalAlignment(align);
     }, 
     setNumberFormat: function(strRange){
       setFormat(strRange, '0');       
     },
     setDecimalFormat: function(strRange){
       setFormat(strRange, '0.00');
     },
     setCurrencyFormat: function(strRange){
       // e.g: setCurrencyFormat('A1'); // set cell
       // e.g: setCurrencyFormat('A1:A10'); // set range
       
       setFormat(strRange, '$0.00');
     },
     setCellSumFormula: function(strRange, strCell){
       // e.g: setCellSumFormula('A1:A10', 'B1'); 
       // set SUM value of cells A1 to A10 to cell B1
       
       var cell = _sheet.getRange(strCell);
       cell.setFormula('=SUM(' + strRange + ')');
     },
     setCellAvgFormula: function(strRange, strCell){
       // e.g: setCellSumFormula('A1:A10', 'B1'); 
       // set AVG value of cells A1 to A10 to cell B1
       
       var cell = _sheet.getRange(strCell);
       cell.setFormula('=AVERAGE(' + strRange + ')');
     }       
   }; 
}

/* SPREADSHEET LOADER */
var SpreadsheetLoader = {
  createSpreadSheet: function(spreadSheetName, folderName){
    Logger.log('CREATING %s ... ', spreadSheetName);
    var spreadsheet = SpreadsheetApp.create(spreadSheetName); // create new file         
    
    if(!folderName || folderName == '') 
      return spreadsheet; // folder not specified  - return spreadsheet
     
    // save in specific folder 
    
    for(var i=0;i

Third Script - Handle Non Converting Ads:

– Script name: (R) Pause Non Converting or High Cost Ads
– Script objective: Pause non converting or high cost ads
– Account level script

You can copy the script from here:


    var PERIOD = 'ALL_TIME'; // ALL_TIME, LAST_30_DAYS

function main() {
  HandleNonConvertingAds();
  HandleConvertingAds();
}

function HandleNonConvertingAds(){ 
  Logger.log('## Non Converting Ads ##')
  var ads = AdWordsApp.ads()
     .withCondition('ConvertedClicks = 0')
     .withCondition('Status = ENABLED')
     .withCondition('Cost > 0')  
     .forDateRange(PERIOD)
     .orderBy('Cost ASC')
     .get();
  
  while (ads.hasNext()) {
    var ad = ads.next();
    var adGroup = ad.getAdGroup();
    var adGroupCPA = GetAdGroupCPA(adGroup);
    var adCost = ad.getStatsFor(PERIOD).getCost();
    
    Logger.log('ad: %s -> cost %s', ad.getHeadline(), adCost);
    Logger.log('adGroup %s -> CPA %s', adGroup.getName(), adGroupCPA);
        
    if(adGroupCPA == null || adCost  0')
     .withCondition('Status = ENABLED')
     .withCondition('Cost > 0')  
     .forDateRange(PERIOD)
     .orderBy('ConvertedClicks DESC')
     .get();
  
  while (ads.hasNext()) {
    var ad = ads.next();
    var adGroup = ad.getAdGroup();
    var adGroupCPA = GetAdGroupCPA(adGroup);
    var stats = ad.getStatsFor(PERIOD);
    var adCPA = stats.getCost() / stats.getConvertedClicks(); // calculate the ad "CPA" - cost per converted
    
    Logger.log('ad: %s -> Conversions %s, CPA %s', ad.getHeadline(), stats.getConvertedClicks(), adCPA)
    Logger.log('adGroup %s -> CPA %s', adGroup.getName(), adGroupCPA);
        
    if(adGroupCPA == null || adCPA 

Fourth Script - Hidden Keywords

– Script name: (R) queries 2 keywords – mcc (set)
– Script objective: taking keywords to perform conversion but do not appear
– Explanation: ensuring that the original keyword is a phrase or BMM expression
– MCC level script

You can copy the script from here:


function main() {
  var accounts = MccApp.accounts().get();

  while (accounts.hasNext()) {
    var account = accounts.next();
    var accountName = account.getName() ? account.getName() : '--';
    Logger.log('account #%s %s', account.getCustomerId(), accountName);

    MccApp.select(account); 
    execute();    
  }
}

function execute() {
  var report = AdWordsApp.report(
   'SELECT Query,Clicks,ConvertedClicks,AverageCpc ' +
   'FROM SEARCH_QUERY_PERFORMANCE_REPORT ' +
   'WHERE ConvertedClicks > 0 ' + 
   'DURING 20000101,' + dateFormat(new Date()));
 
  var rows = report.rows();  
  while(rows.hasNext()) {
    var row = rows.next();
    
    var clicks = row['Clicks'];
    var conversions = row['ConvertedClicks'];

    var query = row['Query'];
    //Logger.log(query + ' ' + clicks);
    var found_in_my_keywords = AdWordsApp.keywords().withCondition('Text CONTAINS "' + query + '"').get().hasNext();
    if(found_in_my_keywords)
    {
      Logger.log('"' + query + '" found in my keywords');
      continue;
    }
    
    var adGroup = AdWordsApp.adGroups().get().next();
    var maxcpc = row['AverageCpc'];
    
    if(!AdWordsApp.getExecutionInfo().isPreview())
      adGroup.createKeyword(query, maxcpc);
    Logger.log('"' + query + '" added as keyword with maxcpc ' + maxcpc)
  }
}

function dateFormat(date){
  var year = date.getFullYear().toString();
  var month = date.getMonth().toString();
  var day = date.getDate().toString();
  
  if(month.length == 1) month = '0' + month; // return yyyyMMdd
  if(day.length == 1) day = '0' + day;
  
  return year + month + day;
}
    

Further Examples

– E-commerce website, monitoring the active ads and pausing the ad when the auction is over – reading data from the HTML
– Managing a complete feed based campaign (Excel or XML or Jason) from creating a new ad when the product is new, for instance: “only today product X is on sale”, pausing the ad when the product is out of stock, updating the price in the ad.
– Managing the budget to reach the set budget, no more and no less.

– Injecting data into ads
– An alert system for campaign problems

Summary

The objective of the rules, experiments and scripts is to help us manage the account automatically and to generate work processes to improve our performances. I recommend everyone to use the rules for automatic actions, experiments to try new things without affecting the performance, and scripts for instances with no rules.

Author details:

Avishay Freund

– Manager and owner of Logus Online Marketing

– Expert for online marketing, advertising and promotion

– Advising and guiding institutions and private parties in all that relates the internet medium

– Certified Google Advertising Professional

– Senior lecturer in universities and colleges for online marketing and web technologies online

– Serves as a court expert for advertising and marketing

– MBA certified from the faculty of engineer school of management, Ben Gurion University, Beer Sheva