Tuesday, September 30, 2014

An AdWords Script To Make Exact Match

Sorry Google, we've written a script to reverse your "close variant matching."


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.
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
//Parameters for filtering by campaign name and AdGroup name. The filtering is case insensitive.
var AddCampaignNegative = true; // true of false //Leave blank, i.e. "", if you want this script to run over all campaigns and AdGroups.
var campaigns = {};
var campaignNameContains = ""; var adGroupNameContains = ""; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Pull a list of all exact match keywords in the account
var adGroups = {}; var exactKeywords = []; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// var report = AdWordsApp.report(
var rows = report.rows();
"SELECT AdGroupId, Id " + "FROM KEYWORDS_PERFORMANCE_REPORT " + "WHERE Impressions > 0 AND KeywordMatchType = EXACT " + "DURING LAST_7_DAYS"); 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
"WHERE CampaignName CONTAINS_IGNORE_CASE '" + campaignNameContains + "' " +
var report = AdWordsApp.report( "SELECT Query, AdGroupId, CampaignId, KeywordId, KeywordTextMatchingQuery, Impressions, MatchType " + "FROM SEARCH_QUERY_PERFORMANCE_REPORT " +
var adGroupId = parseInt(row['AdGroupId']);
"AND AdGroupName CONTAINS_IGNORE_CASE '" + adGroupNameContains + "' " + "DURING LAST_7_DAYS"); var rows = report.rows(); while (rows.hasNext()) { var row = rows.next(); var campaignId = parseInt(row['CampaignId']);
if(keyword !== searchQuery && matchType.indexOf("exact (close variant)") !== -1){
var keywordId = parseInt(row['KeywordId']); var searchQuery = row['Query']; var keyword = row['KeywordTextMatchingQuery']; var matchType = row['MatchType'].toLowerCase(); if(!campaigns.hasOwnProperty(campaignId)){ campaigns[campaignId] = [[], []];
adGroups[adGroupId][0].push(searchQuery);
} campaigns[campaignId][0].push(searchQuery); campaigns[campaignId][1].push(adGroupId + "#" + keywordId); if(!adGroups.hasOwnProperty(adGroupId)){ adGroups[adGroupId] = [[], []]; }
var campaignNegatives = [];
adGroups[adGroupId][1].push(adGroupId + "#" + keywordId); } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// //Parse data correctly var adGroupIds = []; var campaignIds = []; var adGroupNegatives = []; for(var x in campaigns){
campaignNegatives[campaignIds.indexOf(parseInt(x))].push(keywordText);
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){ } } } 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); }
while(campaignIterator.hasNext()){
} //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// //Create the new negative exact keywords var campaignResults = {}; var adGroupResults = {}; if(AddCampaignNegative){ var campaignIterator = AdWordsApp.campaigns() .withIds(campaignIds) .get();
campaign.createNegativeKeyword("[" + campaignNegatives[campaignIndex][i] + "]")
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++){ if(!campaignResults.hasOwnProperty(campaignName)){
var adGroupId = adGroup.getId();
campaignResults[campaignName] = []; } campaignResults[campaignName].push(campaignNegatives[campaignIndex][i]); } } } if(AddAdGroupNegative){ var adGroupIterator = AdWordsApp.adGroups() .withIds(adGroupIds) .get(); while(adGroupIterator.hasNext()){ var adGroup = adGroupIterator.next();
adGroupResults[adGroupName].push(adGroupNegatives[adGroupIndex][i]);
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] = []; } } } }
resultsString += "\n\n\n\nThe following negative keywords have been added to the following AdGroups:";
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// //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"); } 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 AddCampaignNegative = true; // true of false
var AddAdGroupNegative = true; // true or false
//Leave blank, i.e. "", if you want this script to run over all campaigns and AdGroups.
//Parameters for filtering by campaign name and AdGroup name. The filtering is case insensitive. 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 " +
"AND AdGroupName CONTAINS_IGNORE_CASE '" + adGroupNameContains + "' " +
"WHERE CampaignName CONTAINS_IGNORE_CASE '" + campaignNameContains + "' " + "DURING LAST_7_DAYS");
var adGroupId = parseInt(row['AdGroupId']);
var rows = report.rows(); while (rows.hasNext()) { var row = rows.next(); var campaignId = parseInt(row['CampaignId']);
var matchType = row['MatchType'].toLowerCase();
var keywordId = parseInt(row['KeywordId']); var searchQuery = row['Query']; var keyword = row['KeywordTextMatchingQuery'];

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 &amp;&amp; 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();
var campaign = campaignIterator.next();
while(campaignIterator.hasNext()){
var campaignName = campaign.getName();
var campaignId = campaign.getId();
for(var i = 0; i < campaignNegatives[campaignIndex].length; i++){
var campaignIndex = campaignIds.indexOf(campaignId);
if(!campaignResults.hasOwnProperty(campaignName)){
campaign.createNegativeKeyword("[" + campaignNegatives[campaignIndex][i] + "]") 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!

No comments:

Post a Comment