Search Engine Land
  • SEO
    • > All SEO
    • > What Is SEO?
    • > SEO Periodic Table
    • > Google: SEO
    • > Bing SEO
    • > Google Algorithm Updates
  • PPC
    • > All PPC
    • > What is PPC?
    • > Google Ads
    • > Microsoft Ads
    • > The Periodic Tables of PPC
  • Focuses
    • > Local
    • > Commerce
    • > Shopify SEO Guide
    • > Content
    • > Email Marketing Periodic Table
    • > Social Media Marketing
    • > Analytics
    • > Search Engine Land Awards
    • > All Focuses
  • SMX
  • Webinars
  • Intelligence Reports
  • White Papers
  • About
    • > About Search Engine Land
    • > Newsletter
    • > Third Door Media
    • > Advertise

Processing...Please wait.

Search Engine Land » Channel » Content » An AdWords Script To Make Exact Match, Well…Exact

An AdWords Script To Make Exact Match, Well…Exact

Many of you will have heard about Google’s decision to terminate exact match (at the same time as telling us that it’s for our own good). It’s a clear move to grab some more advertising dollars, and the news has been met with fury by SEM experts. Most two-year-old kids know that there is a semantic difference between singular and plural […]

Daniel Gilbert on September 29, 2014 at 2:35 pm

adwords-scripts-exact-match

Many of you will have heard about Google’s decision to terminate exact match (at the same time as telling us that it’s for our own good). It’s a clear move to grab some more advertising dollars, and the news has been met with fury by SEM experts.

Most two-year-old kids know that there is a semantic difference between singular and plural forms — and anyone with the slightest command of the English language will know that there is a difference between [photographer] and [photography]. While a professional photographer might want to spend money on [photographer], they probably wouldn’t want to appear for [photography] as this is more likely to be a search for photos that users can download.

Instead of signing the petition on change.org asking Google to reverse this change, we’ve just written a script to automatically make exact match, well…exact.

The AdWords script runs search term reports and adds “close variant” terms as exact negatives if they are not the exact original keyword.

You can run this script at MCC level for all your accounts, or choose individual accounts, campaigns or ad groups. Just copy the code below, log in to AdWords, go to Bulk Operations (left-hand column) > Scripts > New. Paste the code into the box and click Preview to see the changes that the script will make if you run it.  Set up a schedule to run this script daily and your keyword matching will behave similarly to how it did before. If you’ve never run a script before you can read our Introduction to AdWords Scripts.

Two caveats: we’ve seen a lot of search terms appear under “Other search terms” in Search Query Reports. These cannot be excluded as Google does not tell us what they are. Secondly, search term report data does not appear on the same day, so we’ll always be a day behind Google’s close variants.

/**
 *
 * Adds as campaign or AdGroup negatives search queries which have triggered exact keywords
 * Version: 1.0 - maintained AdWords script on brainlabsdigital.com
 * Authors: Visar Shabi & Daniel Gilbert
 * brainlabsdigital.com
 *
 **/
function main() {
  
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  //Options
  
  //Choose whether to add your negative exact keywords at campaign or AdGroup level.
  //Set variable as "true" to add or as "false" to not add.
  var AddAdGroupNegative = true;  // true or false
  var AddCampaignNegative = true; // true of false
  
  //Parameters for filtering by campaign name and AdGroup name. The filtering is case insensitive.
  //Leave blank, i.e. "", if you want this script to run over all campaigns and AdGroups.
  var campaignNameContains = "";
  var adGroupNameContains = "";
  
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  
  var campaigns = {};
  var adGroups = {};
   
  var exactKeywords = [];
  
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  //Pull a list of all exact match keywords in the account
  
  var report = AdWordsApp.report(
    "SELECT AdGroupId, Id " +
    "FROM KEYWORDS_PERFORMANCE_REPORT " +
    "WHERE Impressions > 0 AND KeywordMatchType = EXACT " +
    "DURING LAST_7_DAYS");
  
  var rows = report.rows();
  while (rows.hasNext()) {
    var row = rows.next();
    var keywordId = row['Id'];
    var adGroupId = row['AdGroupId'];
    exactKeywords.push(adGroupId + "#" + keywordId);
  }
  
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  //Pull a list of all exact (close variant) search queries
  
  var report = AdWordsApp.report(
    "SELECT Query, AdGroupId, CampaignId, KeywordId, KeywordTextMatchingQuery, Impressions, MatchType " +
    "FROM SEARCH_QUERY_PERFORMANCE_REPORT " +
    "WHERE CampaignName CONTAINS_IGNORE_CASE '" + campaignNameContains + "' " +
    "AND AdGroupName CONTAINS_IGNORE_CASE  '" + adGroupNameContains + "' " +
    "DURING LAST_7_DAYS");
  
  var rows = report.rows();
  while (rows.hasNext()) {
    var row = rows.next();
    var adGroupId = parseInt(row['AdGroupId']);
    var campaignId = parseInt(row['CampaignId']);
    var keywordId = parseInt(row['KeywordId']);
    var searchQuery = row['Query'];
    var keyword = row['KeywordTextMatchingQuery'];
    var matchType = row['MatchType'].toLowerCase();
    if(keyword !== searchQuery && matchType.indexOf("exact (close variant)") !== -1){
      
      if(!campaigns.hasOwnProperty(campaignId)){
        campaigns[campaignId] = [[], []];
      }
      
      campaigns[campaignId][0].push(searchQuery);
      campaigns[campaignId][1].push(adGroupId + "#" + keywordId);
      
      if(!adGroups.hasOwnProperty(adGroupId)){
        adGroups[adGroupId] = [[], []];
      }
      
      adGroups[adGroupId][0].push(searchQuery);
      adGroups[adGroupId][1].push(adGroupId + "#" + keywordId);
    }
  }
  
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  //Parse data correctly
  
  var adGroupIds = [];
  var campaignIds = [];
  var adGroupNegatives = [];
  var campaignNegatives = [];
  
  for(var x in campaigns){
    campaignIds.push(parseInt(x));
    campaignNegatives.push([]);
    for(var y = 0; y < campaigns[x][0].length; y++){
      var keywordId = campaigns[x][1][y];
      var keywordText = campaigns[x][0][y];
      if(exactKeywords.indexOf(keywordId) !== -1){
        campaignNegatives[campaignIds.indexOf(parseInt(x))].push(keywordText);
      }
    }
  }
  
  for(var x in adGroups){
    adGroupIds.push(parseInt(x));
    adGroupNegatives.push([]);
    for(var y = 0; y < adGroups[x][0].length; y++){
      var keywordId = adGroups[x][1][y];
      var keywordText = adGroups[x][0][y];
      if(exactKeywords.indexOf(keywordId) !== -1){
        adGroupNegatives[adGroupIds.indexOf(parseInt(x))].push(keywordText);
      }
    }
  }
  
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  //Create the new negative exact keywords
  
  var campaignResults = {};
  var adGroupResults = {};
  
  if(AddCampaignNegative){
    var campaignIterator = AdWordsApp.campaigns()
    .withIds(campaignIds)
    .get();
    while(campaignIterator.hasNext()){
      var campaign = campaignIterator.next();
      var campaignId = campaign.getId();
      var campaignName = campaign.getName();
      var campaignIndex = campaignIds.indexOf(campaignId);
      for(var i = 0; i < campaignNegatives[campaignIndex].length; i++){
        campaign.createNegativeKeyword("[" + campaignNegatives[campaignIndex][i] + "]")
        if(!campaignResults.hasOwnProperty(campaignName)){
          campaignResults[campaignName] = [];
        }
        campaignResults[campaignName].push(campaignNegatives[campaignIndex][i]);
      }
    }
  }
  
  if(AddAdGroupNegative){
    var adGroupIterator = AdWordsApp.adGroups()
    .withIds(adGroupIds)
    .get();
    while(adGroupIterator.hasNext()){
      var adGroup = adGroupIterator.next();
      var adGroupId = adGroup.getId();
      var adGroupName = adGroup.getName();
      var adGroupIndex = adGroupIds.indexOf(adGroupId);
      for(var i = 0; i < adGroupNegatives[adGroupIndex].length; i++){
        adGroup.createNegativeKeyword("[" + adGroupNegatives[adGroupIndex][i] + "]");
        if(!adGroupResults.hasOwnProperty(adGroupName)){
          adGroupResults[adGroupName] = [];
        }
        adGroupResults[adGroupName].push(adGroupNegatives[adGroupIndex][i]);
      }
    }
  }
  
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  //Format the results
  
  var resultsString = "The following negative keywords have been added to the following campaigns:";

  for(var x in campaignResults){
    resultsString += "\n\n" + x + ":\n" + campaignResults[x].join("\n");
  }
  
  resultsString += "\n\n\n\nThe following negative keywords have been added to the following AdGroups:";
  
  for(var x in adGroupResults){
    resultsString += "\n\n" + x + ":\n" + adGroupResults[x].join("\n");
  }
  
  Logger.log(resultsString);
  
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  
}

How The Script Works

For those brave enough, we’ll now break the script down into more detail.

Choosing Your Settings

We’ve made the script flexible so that it works for different account structures. For example, you can decide whether to add your negative exact keywords at campaign or ad group level. We’ve built in functionality to exclude certain campaigns or adgroups as the script runs.

 //Choose whether to add your negative exact keywords at campaign or AdGroup level.
  //Set variable as "true" to add or as "false" to not add.
  var AddAdGroupNegative = true;  // true or false
  var AddCampaignNegative = true; // true of false
  
  //Parameters for filtering by campaign name and AdGroup name. The filtering is case insensitive.
  //Leave blank, i.e. "", if you want this script to run over all campaigns and AdGroups.
  var campaignNameContains = "";
  var adGroupNameContains = "";

Getting Started

We start by pulling a search query report for the last seven days (or any other time period you wish). We store all this information in arrays, with an array for the ad group ids, campaign ids, keyword ids, search queries, keywords, and match types. It’s important to note that all the data associated with a search query is located in the same position in each of the arrays.

   var report = AdWordsApp.report(
 "SELECT Query, AdGroupId, CampaignId, KeywordId, KeywordTextMatchingQuery, Impressions, MatchType " +
 "FROM SEARCH_QUERY_PERFORMANCE_REPORT " +
 "WHERE CampaignName CONTAINS_IGNORE_CASE '" + campaignNameContains + "' " +
 "AND AdGroupName CONTAINS_IGNORE_CASE '" + adGroupNameContains + "' " +
 "DURING LAST_7_DAYS");
 
 var rows = report.rows();
 while (rows.hasNext()) {
 var row = rows.next();
 var adGroupId = parseInt(row['AdGroupId']);
 var campaignId = parseInt(row['CampaignId']);
 var keywordId = parseInt(row['KeywordId']);
 var searchQuery = row['Query'];
 var keyword = row['KeywordTextMatchingQuery'];
 var matchType = row['MatchType'].toLowerCase();

The Important Part

Next is the crucial step: the if statement below takes all the search terms that do not exactly match the keyword they triggered AND have keyword match type exact (close variant). This gives us exactly what we’re after: all those pesky new search terms that have been created as a result of Google’s change.

if(keyword !== searchQuery && matchType.indexOf("exact (close variant)") !== -1)

We store all these search queries with the campaign id, ad group id and keyword id that they are associated with.

Finally Adding The Negatives

All that is left to do now is to add campaign (or ad group) negatives to the relevant campaigns (or ad groups). It looks complicated, but it’s actually quite straightforward.

We first take all the campaigns that we need to add negatives to, together with their id and name. Iterating through each campaign, we next create all the negative keywords associated with this campaign by matching up ids. And finally all that is left to do is to add these negative keywords to the correct campaign.

  var campaignResults = {};
  var adGroupResults = {};
  
  if(AddCampaignNegative){
    var campaignIterator = AdWordsApp.campaigns()
    .withIds(campaignIds)
    .get();
    while(campaignIterator.hasNext()){
      var campaign = campaignIterator.next();
      var campaignId = campaign.getId();
      var campaignName = campaign.getName();
      var campaignIndex = campaignIds.indexOf(campaignId);
      for(var i = 0; i < campaignNegatives[campaignIndex].length; i++){
        campaign.createNegativeKeyword("[" + campaignNegatives[campaignIndex][i] + "]")
        if(!campaignResults.hasOwnProperty(campaignName)){
          campaignResults[campaignName] = [];
        }
        campaignResults[campaignName].push(campaignNegatives[campaignIndex][i]);
      }
    }
  }

Checking The New Negative Keywords

For our final act, we log all the changes we’ve made to check that we’re happy with the new negative exact keywords.

var resultsString = "The following negative keywords have been added to the following campaigns:";

  for(var x in campaignResults){
    resultsString += "\n\n" + x + ":\n" + campaignResults[x].join("\n");
  }

And then we’re done. Sorry, Google!


Opinions expressed in this article are those of the guest author and not necessarily Search Engine Land. Staff authors are listed here.


New on Search Engine Land

    SEO reporting to impress: How to successfully report your SEO process, efforts and result

    9 ways to become an SEO problem-solver

    More FAQ rich results being displayed in Google Search

    Webinar: Benchmark your social media performance for a competitive edge

    Google releases May 2022 broad core update

About The Author

Daniel Gilbert
Daniel Gilbert is the CEO at Brainlabs, the best paid media agency in the world (self-declared). He has started and invested in a number of big data and technology startups since leaving Google in 2010.

Related Topics

ContentGoogleGoogle AdsPPC

Get the daily newsletter search marketers rely on.

Processing...Please wait.

See terms.

ATTEND OUR EVENTS

The SMX Conference logo.

Learn actionable search marketing tactics that can help you drive more traffic, leads, and revenue.

March 8-9, 2022: Master Classes (virtual)

June 14-15, 2022: SMX Advanced (virtual)

November 15-16, 2022: SMX Next (virtual)

Learn More About Our SMX Events

The MarTech Conference logo.

Discover time-saving technologies and actionable tactics that can help you overcome crucial marketing challenges.

Start Discovering Now: Spring (virtual)

September 28-29, 2022: Fall (virtual)

Learn More About Our MarTech Events

Webinars

Benchmark Your Social Media Performance For a Competitive Edge

Take a Crawl, Walk, Run Approach to Multi-Channel ABM

Content Comes First: Transform Your Operations With DAM

See More Webinars

Intelligence Reports

Enterprise SEO Platforms: A Marketer’s Guide

Enterprise Identity Resolution Platforms

Email Marketing Platforms: A Marketer’s Guide

Enterprise Sales Enablement Platforms: A Marketer’s Guide

Enterprise Digital Experience Platforms: A Marketer’s Guide

Enterprise Call Analytics Platforms: A Marketer’s Guide

See More Intelligence Reports

White Papers

The State of Influencer Pricing

How to Measure Influencer Performance

Reputation Management For Healthcare Organizations

Unlock the App Marketing Potential of QR Codes

Realising the power of virtual events for demand generation

See More Whitepapers

Receive daily search news and analysis.

Processing...Please wait.

Topics

  • SEO
  • PPC

Our Events

  • Search Marketing Expo - SMX
  • MarTech

About

  • About Us
  • Contact
  • Privacy
  • Marketing Opportunities
  • Staff

Follow Us

  • Facebook
  • Twitter
  • LinkedIn
  • Newsletters
  • RSS
  • Youtube

© 2022 Third Door Media, Inc. All rights reserved.