In AdWords, broad keywords (with broad match modifier applied) are great. Broad match keywords let you enter the paid search auction without knowing the search queries in advance, as well as giving you a way of getting to difficult-to-predict long-tail users.
This is particularly powerful when used in a campaign structure that separates broad and exact keywords.
However, there’s a big problem with the way some account managers implement their broad match keywords. Because such keywords pay no heed to the order in which the words in a search query appear, it’s possible to have equivalent broad match keywords in your account.
This situation disaggregates your data and obscures the true picture of your account performance.
Example 1
Let’s look at an example where you have the following broad match keywords in your account:
- “+red +nike +shoes”
- “+nike +shoes +red”
- “+nike +red +shoes”
If a user searches for “buy red shoes that are nike,” it’s possible that any of those three keywords will be triggered by the search query. All three can be triggered by the same set of search queries — they’re equivalent. We can think of this trio of keywords as belonging to the same equivalent keyword group, since they are essentially duplicates.
Having equivalent keyword groups becomes problematic when looking at your keyword performance. This data is disaggregated across all the broad match keywords in an equivalent keyword group.
The true cost of the “+red +nike +shoes” keyword should take into account the cost of all the keywords which are in its equivalent keyword group (e.g., “+nike +shoes +red” and “+nike +red +shoes”).
Any statistics, such as ROI and cost, should be viewed across the equivalent keyword group, and any informed decision to change bids should take into account these aggregated statistics.
The Solution
At Brainlabs (my company), we have an AdWords script to overcome this problem so that you don’t end up with any broad match equivalent keyword groups.
The script will go through your account and determine which keywords are equivalents of each other and together form equivalent keyword groups.
For each equivalent keyword group, one broad match keyword will be selected based on performance — the “best of the bunch” — and will be given a label. All other broad match keywords in the equivalent keyword group will be given a different label.
Once all your keywords have been labeled up, you can simply keep your best-of-the-bunch broad keywords enabled and pause the rest.
Here’s an image to illustrate what will happen for the keywords mentioned above when the performance metric being used is the average CPC:
In order to use this script, simply copy the code below into AdWords scripts in your AdWords account. (If you’ve never run a script before, please read the Marketing Land series on AdWords Scripts first.)
You’ll need to configure a few options, and then you’ll be set. Select these options depending on your preferences and account setup:
- ACCOUNT_WIDE will determine whether your equivalent keyword groups can be made from keywords taken from a single campaign or from many campaigns. Set to false if you want the equivalent keyword groups to be made from keywords within the same campaign only.
- METRIC is the AdWords performance metric that determines which broad match keyword from the equivalent keyword group is going to be kept. The broad match keyword with the best metric will be kept. The metrics you can choose from are CTR, Quality Score,* impressions, average CPC and converted clicks.
- CAMPAIGN_INCLUDE_FILTER is a list of terms that will filter which campaigns you want to look at. A campaign must have one of the terms in this list contained in its name.
- CAMPAIGN_EXCLUDE_FILTER is a list of terms that will filter which campaigns you want to look at. A campaign cannot have any of the terms in the list contained in its name.
- DATE_RANGE is the date range over which to compare the metric you have chosen above.
- KEEP_LABEL is the name of the label you will use to label the broad match keywords that have the best metric stat (e.g., the highest Quality Score).
- PAUSE_LABEL is the name of the label to give to all the keywords in the equivalent keyword group that haven’t got the best metric stat.
If you find the script keeps timing out, it may be that your account is too big. Try running the script multiple times using CAMPAIGN_INCLUDE_FILTER and CAMPAIGN_EXCLUDE_FILTER to look at different campaigns each time.
*This is reported Quality Score (QS). Reported QS is the QS of searches that exactly match the keyword, rather than an aggregate of QS of searches the keyword has served ads for. This is not that useful for broad match keywords.
The Script
/** | |
* | |
* Broad-match keyword aggregator script | |
* This script will group equivalent broad match keywords and label based on performence | |
* | |
* Version: 1.1 | |
* Updated 2016-10-11: replaced 'ConvertedClicks' with 'Conversions' | |
* Google AdWords Script maintained by brainlabsdigital.com | |
* | |
**/ | |
function main(){ | |
var ACCOUNT_WIDE = false; | |
//Defines whether the script looks at campaign-level or account-level broad match duplicate keywords | |
var METRIC = "AverageCpc"; | |
//Select the metric which will determine which duplicate keyword will be kept, choose from "Ctr", "QualityScore", "Impressions", "Conversions", "AverageCpc" | |
var CAMPAIGN_INCLUDE_FILTER = []; // e.g var CAMPAIGN_INCLUDE_FILTER = ["hey", "jude"]; | |
//Campaign filter which will include any campaign with any of the included strings in the campaign name. Case insensitive matching | |
var CAMPAIGN_EXCLUDE_FILTER = []; // e.g var CAMPAIGN_EXCLUDE_FILTER = ["hey", "jude"]; | |
//Campaign filter which will exclude any campaign with any of the included strings in the campaign name. Case insensitive matching | |
var DATE_RANGE = "LAST_30_DAYS"; | |
//Choose one from TODAY, YESTERDAY, LAST_7_DAYS, THIS_WEEK_SUN_TODAY, THIS_WEEK_MON_TODAY, LAST_WEEK, LAST_14_DAYS, LAST_30_DAYS, LAST_BUSINESS_WEEK, LAST_WEEK_SUN_SAT, THIS_MONTH | |
var KEEP_LABEL = "DuplicateBroadKeyword_Enable"; | |
//Label one keyword from each duplicate group | |
var PAUSE_LABEL = "DuplicateBroadKeyword_Pause"; | |
//Label all keywords which don't have the best statistic from selected | |
labelDuplicates(ACCOUNT_WIDE, CAMPAIGN_INCLUDE_FILTER, CAMPAIGN_EXCLUDE_FILTER, DATE_RANGE, METRIC, KEEP_LABEL, PAUSE_LABEL); | |
} | |
function labelDuplicates(ACCOUNT_WIDE, CAMPAIGN_INCLUDE_FILTER, CAMPAIGN_EXCLUDE_FILTER, DATE_RANGE, METRIC, KEEP_LABEL, PAUSE_LABEL) { | |
//Create labels | |
AdWordsApp.createLabel(KEEP_LABEL); | |
AdWordsApp.createLabel(PAUSE_LABEL); | |
//Metric data-validation | |
var allowedMetrics = ["Ctr", "QualityScore", "Impressions", "Conversions", "AverageCpc"]; | |
var allowedMetrics_lowerCase = allowedMetrics.map(function (str){return str.toLowerCase()}); | |
var metricIndex = allowedMetrics_lowerCase.indexOf(METRIC.toLowerCase()); | |
if(metricIndex === -1){ | |
var error = "Metric '" + METRIC + "' not recognised, please set to one from '" + allowedMetrics.join("', '") + "'."; | |
Logger.log(error); | |
throw error; | |
return; | |
} | |
var METRIC = allowedMetrics[metricIndex]; | |
//Generate list of included campaigns | |
var includeCampaignIds = []; | |
var campaignIncludes = CAMPAIGN_INCLUDE_FILTER.map(function (str){return str.toLowerCase()}); | |
var campaignIterator = AdWordsApp.campaigns() | |
.withCondition("CampaignStatus = ENABLED") | |
.get(); | |
while(campaignIterator.hasNext()){ | |
var campaign = campaignIterator.next(); | |
var campaignId = campaign.getId(); | |
var campaignName = campaign.getName(); | |
var campaignNameLower = campaignName.toLowerCase(); | |
var flag = false; | |
for(var i = 0; i < campaignIncludes.length; i++){ | |
if(campaignNameLower.indexOf(campaignIncludes[i]) !== -1){ | |
flag = true; | |
break; | |
} | |
} | |
if(flag){ | |
includeCampaignIds.push(campaignId); | |
} | |
} | |
//Construct AWQL report query | |
var selectQuery = 'SELECT CampaignName, CampaignId, Id, AdGroupId, Criteria, ' + METRIC + ' '; | |
var fromQuery = 'FROM KEYWORDS_PERFORMANCE_REPORT '; | |
var whereQuery = "WHERE KeywordMatchType = BROAD AND AdNetworkType1 = SEARCH "; | |
if(includeCampaignIds.length > 0){ | |
whereQuery += "AND CampaignId IN [" + includeCampaignIds.join(",") + "] "; | |
} | |
for(var i = 0; i < CAMPAIGN_EXCLUDE_FILTER.length; i++){ | |
whereQuery += "AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE '" + CAMPAIGN_EXCLUDE_FILTER[i] + "' "; | |
} | |
var duringQuery = "DURING " + DATE_RANGE; | |
var query = selectQuery | |
+ fromQuery | |
+ whereQuery | |
+ duringQuery; | |
//Generate report | |
var report = AdWordsApp.report(query); | |
//Poll report rows | |
var campaignKeywords = {}; | |
var rows = report.rows(); | |
while(rows.hasNext()){ | |
var row = rows.next(); | |
var keywordId = row['Id']; | |
var adGroupId = row['AdGroupId']; | |
var campaignId = row['CampaignId']; | |
var keywordText = row['Criteria'].toLowerCase(); | |
var metricStat = parseFloat(row[METRIC].replace(/,/g, "")); | |
if(METRIC.toLowerCase() === "AverageCpc".toLowerCase()){ | |
if(metricStat > 0){ | |
metricStat = 1 / metricStat; | |
} | |
} | |
var stats = {metric: metricStat}; | |
if(ACCOUNT_WIDE) campaignId = 1; | |
if(typeof(campaignKeywords[campaignId]) === "undefined"){ | |
campaignKeywords[campaignId] = []; | |
} | |
campaignKeywords[campaignId].push(parseKeyword(keywordId,adGroupId,keywordText,stats)); | |
} | |
//Establish duplicate keyword groups | |
if (ACCOUNT_WIDE === true){ | |
var keywordGroups = {}; | |
} | |
for(var campaignId in campaignKeywords){ | |
if (ACCOUNT_WIDE === false) { | |
var keywordGroups = {}; | |
} | |
var campaignKeywordsList = campaignKeywords[campaignId]; | |
var keywordArray = []; | |
for(var keyword in campaignKeywordsList){ | |
keywordArray.push(campaignKeywordsList[keyword]["Text"]); | |
} | |
for(var keyword in campaignKeywordsList){ | |
var keywordText = campaignKeywordsList[keyword]["Text"]; | |
var firstIndex = keywordArray.indexOf(keywordText); | |
var lastIndex = keywordArray.lastIndexOf(keywordText); | |
//push the dupes into dupe groups | |
if(firstIndex !== lastIndex){ | |
if(typeof(keywordGroups[keywordText]) === "undefined") { | |
keywordGroups[keywordText]=[]; | |
} | |
keywordGroups[keywordText].push(campaignKeywordsList[keyword]); | |
} | |
} | |
if (ACCOUNT_WIDE === true) { | |
continue; | |
} | |
labelKeywords(keywordGroups, KEEP_LABEL, PAUSE_LABEL); | |
} | |
if (ACCOUNT_WIDE === true) { | |
labelKeywords(keywordGroups, KEEP_LABEL, PAUSE_LABEL); | |
} | |
} | |
function parseKeyword(keywordId,adGroupId,keywordText, stats){ | |
var keyword = {}; | |
keyword["KeywordId"] = keywordId; | |
keyword["AdGroupId"] = adGroupId; | |
keyword["Id"] = [adGroupId, keywordId]; | |
keyword["Text"] = orderKeyword(keywordText); | |
keyword["Stats"] = stats; | |
return keyword; | |
} | |
function orderKeyword(keywordText){ | |
//Splitting the words | |
var keywordTextArray = keywordText.trim().split(" "); | |
//Sort keyword components | |
var sortedKeywordComponents = keywordTextArray.sort(); | |
//Turn sorted strings into one word | |
var sortedKeyword = sortedKeywordComponents.join(" "); | |
return sortedKeyword; | |
} | |
function labelKeywords(keywordGroups, KEEP_LABEL, PAUSE_LABEL) { | |
for (var keywordText in keywordGroups) { | |
//cycle through each group to pick best of the bunch | |
var maxMetric = -1; | |
var bestKeyword = []; | |
for (var keyword in keywordGroups[keywordText]) { | |
if (parseFloat(keywordGroups[keywordText][keyword]["Stats"]["metric"]) > maxMetric) { | |
maxMetric = keywordGroups[keywordText][keyword]["Stats"]["metric"]; | |
bestKeyword[0] = keywordGroups[keywordText][keyword]; | |
} | |
} | |
var indexOfBest = keywordGroups[keywordText].indexOf(bestKeyword[0]); | |
keywordGroups[keywordText].splice(indexOfBest, 1); | |
//label all groups with pause/unpause labels | |
var keywordIterator = AdWordsApp.keywords().withIds([bestKeyword[0]["Id"]]).get(); | |
keywordIterator.next().applyLabel(KEEP_LABEL); | |
var keywordIdArray = []; | |
for (keyword in keywordGroups[keywordText]) { | |
keywordIdArray.push(keywordGroups[keywordText][keyword]["Id"]); | |
} | |
var keywordIterator = AdWordsApp.keywords().withIds(keywordIdArray).get(); | |
while (keywordIterator.hasNext()){ | |
var keyword = keywordIterator.next(); | |
keyword.applyLabel(PAUSE_LABEL); | |
} | |
} | |
} |
The post Here’s A Free AdWords Script That Pauses Your Duplicate Broad Keywords appeared first on Search Engine Land.
from SEO Rank Video Blog http://ift.tt/1QT9cyY
via IFTTT
No comments:
Post a Comment