Having the same search query leading to different ad groups can be bad news. The data about that query isn’t all in one place, making it harder to see how it is performing. It also gets in the way of managing bids: if some queries do badly, turning the bids down in one group can mean they just pop up again in a different group that now has the higher bid, continuing to waste your money.
And it can get in the way of relevancy. You might have a beautifully specific message and landing page for [sterilized standardized scientificized owl] but end up with the specific queries going to the +owl keyword with a more generic ad.
Unfortunately, it isn’t that easy to work out when this is happening by eye, but luckily, the tech team here at Brainlabs (my employer) has built a script that finds out when the same query is appearing within different ad groups. It will save a report to a Google Sheet, showing the queries from the last 30 days and the ad groups they appear in. It also shows some metrics, like Impressions and Cost, so you can see where they perform the best.
Running this script and using its awesome power of automation allows you to just drop negative keywords into the ad groups that you don’t want the query leading to, saving you time and improving your AdWords account.
A typical output will look something like this:
To use the script, first make a blank Google Sheet for the report.
Then head over to AdWords to set up a new script. After copying in the code below, you’ll need to make a few choices in the options:
- Set spreadsheetUrl to the URL of the sheet you’ve just made.
- If you want to ignore low-traffic queries, set impressionThreshold to a number. The script will only look at queries with more impressions than this threshold.
- Set to 0 to look at all available queries.
- Filter the campaigns to check with the two arrays, campaignNameContains and campaignNameDoesNotContain. For example, if campaignNameContains is
["Brand", "Generic"]
, then only campaigns with names containing “brand” or “generic” are included. If campaignNameDoesNotContain is["Shopping"],
then any campaigns with names containing “shopping” are ignored.- This is not case-sensitive.
- Leave blank,
[]
, to not exclude any campaigns. - If you need to put a double quote into campaignNameContains or campaignNameDoesNotContain, put a backslash before it.
- If ignorePausedCampaigns is true, then the script will only look at currently active campaigns. Set this to false if you want to also check in currently paused campaigns.
The script will always use the first sheet of the spreadsheet for the report, so if you want to run it multiple times, then move the data so it isn’t overwritten.
- If the script keeps timing out, you could run it multiple times using campaignNameContains and campaignNameDoesNotContain to look at different campaigns on each run.
- If you have copies of campaigns for different locations, use campaignNameContains to look only at one location at a time. For example, run the script once with campaignNameContains set to [“-USA”] to look at American campaigns, and then run again with campaignNameContains set to [“-UK”] to look at the British versions.
/** | |
* | |
* Duplicate Query Checker | |
* | |
* Creates a report detailing which searches are triggering multiple ad groups. | |
* | |
* Version: 1.0 | |
* Google AdWords Script maintained on brainlabsdigital.com | |
* | |
*/ | |
////////////////////////////////////////////////////////////////////////////// | |
// Options | |
var spreadsheetUrl = "https://docs.google.com/YOUR-SPREADSHEET-URL-HERE"; | |
// The URL of the Google Doc the results will be put into. | |
var impressionThreshold = 0; | |
// Only queries with more than this number of impressions will be looked at. | |
// Set as 0 to look at all available queries. | |
var campaignNameDoesNotContain = []; | |
// Use this if you want to exclude some campaigns. | |
// For example ["Display"] would ignore any campaigns with 'Display' in the name, | |
// while ["Display","Shopping"] would ignore any campaigns with 'Display' or | |
// 'Shopping' in the name. | |
// Leave as [] to not exclude any campaigns. | |
var campaignNameContains = []; | |
// Use this if you only want to look at some campaigns. | |
// For example ["Brand"] would only look at campaigns with 'Brand' in the name, | |
// while ["Brand","Generic"] would only look at campaigns with 'Brand' or 'Generic' | |
// in the name. | |
// Leave as [] to include all campaigns. | |
var ignorePausedCampaigns = true; | |
// Set this to true to only look at currently active campaigns. | |
// Set to false to also include campaigns that are currently paused. | |
////////////////////////////////////////////////////////////////////////////// | |
function main() { | |
var writeSpreadsheet = checkSpreadsheet(spreadsheetUrl, "the spreadsheet"); | |
var writeSheet = writeSpreadsheet.getSheets()[0]; | |
var campaignIds = getCampaignIds(ignorePausedCampaigns, campaignNameDoesNotContain, campaignNameContains); | |
var queries = getQueries(campaignIds); | |
writeReport(queries, writeSheet); | |
} | |
// Check the spreadsheet URL has been entered, and that it works | |
function checkSpreadsheet(spreadsheetUrl, spreadsheetName) { | |
if (spreadsheetUrl.replace(/[AEIOU]/g,"X") == "https://docs.google.com/YXXR-SPRXXDSHXXT-XRL-HXRX") { | |
throw("Problem with " + spreadsheetName + " URL: make sure you've replaced the default with a valid spreadsheet URL."); | |
} | |
try { | |
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl); | |
// Checks if you can edit the spreadsheet | |
var sheet = spreadsheet.getSheets()[0]; | |
var sheetName = sheet.getName(); | |
sheet.setName(sheetName); | |
return spreadsheet; | |
} catch (e) { | |
throw("Problem with " + spreadsheetName + " URL: '" + e + "'"); | |
} | |
} | |
// Get the IDs of campaigns which match the given options | |
function getCampaignIds(ignorePausedCampaigns, campaignNameDoesNotContain, campaignNameContains) { | |
var whereStatement = "WHERE "; | |
var whereStatementsArray = []; | |
var campaignIds = []; | |
if (ignorePausedCampaigns) { | |
whereStatement += "CampaignStatus = ENABLED "; | |
} else { | |
whereStatement += "CampaignStatus IN ['ENABLED','PAUSED'] "; | |
} | |
for (var i=0; i<campaignNameDoesNotContain.length; i++) { | |
whereStatement += "AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE '" + campaignNameDoesNotContain[i].replace(/"/g,'\\\"') + "' "; | |
} | |
if (campaignNameContains.length == 0) { | |
whereStatementsArray = [whereStatement]; | |
} else { | |
for (var i=0; i<campaignNameContains.length; i++) { | |
whereStatementsArray.push(whereStatement + 'AND CampaignName CONTAINS_IGNORE_CASE "' + campaignNameContains[i].replace(/"/g,'\\\"') + '" '); | |
} | |
} | |
for (var i=0; i<whereStatementsArray.length; i++) { | |
var campaignReport = AdWordsApp.report( | |
"SELECT CampaignId " + | |
"FROM CAMPAIGN_PERFORMANCE_REPORT " + | |
whereStatementsArray[i] + | |
"DURING LAST_30_DAYS"); | |
var rows = campaignReport.rows(); | |
while (rows.hasNext()) { | |
var row = rows.next(); | |
campaignIds.push(row['CampaignId']); | |
} | |
} | |
if (campaignIds.length == 0) { | |
throw("No campaigns found with the given settings."); | |
} | |
Logger.log(campaignIds.length + " campaigns found"); | |
return campaignIds; | |
} | |
/* | |
Downloads a searh query performance report | |
Stores data in an array. | |
Returns that array. | |
Builds array of Adgroups indexed by Query. | |
Structure: | |
Queries => [adGroups, CampaignId, ...], ...] | |
*/ | |
function getQueries(campaignIds){ | |
var queries = {}; | |
var report = AdWordsApp.report( | |
"SELECT Query, CampaignId, CampaignName, AdGroupId, AdGroupName, KeywordTextMatchingQuery, Impressions, Clicks, Cost, Conversions" + | |
" FROM SEARCH_QUERY_PERFORMANCE_REPORT " + | |
" WHERE " + | |
" CampaignId IN [" + campaignIds.join(",") + "]" + | |
" AND Impressions > " + impressionThreshold + " " + | |
" DURING LAST_30_DAYS"); | |
var rows = report.rows(); | |
while (rows.hasNext()) { | |
var row = rows.next(); | |
if (row['KeywordTextMatchingQuery'].indexOf("==") > -1){ //The 'keyword' is a product in a Shopping campaign | |
continue; | |
} | |
var metrics = [row['AdGroupId'], row['AdGroupName'], row['CampaignId'], row['CampaignName'], row['KeywordTextMatchingQuery'], row['Impressions'], row['Clicks'], row['Cost'], row['Conversions']] | |
if (typeof queries[row['Query']] == 'undefined'){ | |
queries[row['Query']] = [metrics]; | |
}else{ | |
queries[row['Query']].push(metrics); | |
} | |
} | |
for (var property in queries){ | |
if (queries[property].length ==1){ | |
delete queries[property]; | |
} | |
} | |
Logger.log(Object.keys(queries).length + ' Search Queries appear in two or more Ad Groups.'); | |
return queries; | |
} | |
/* | |
Goes through object writting each line to a sheet. | |
Search Terms are ordered by total impressions. | |
*/ | |
function writeReport(queries, writeSheet){ | |
writeSheet.clear(); | |
var queryTotalImpressions = {}; | |
for (var query in queries){ | |
var impressions = 0; | |
var metrics = queries[query]; | |
for (var j=0; j<metrics.length; j++){ | |
impressions += parseInt(metrics[j][5].replace(/,/g,""),10); | |
} | |
queryTotalImpressions[query] = impressions; | |
} | |
var orderedQueries = Object.keys(queries).sort(function (a, b) {return queryTotalImpressions[b] - queryTotalImpressions[a];}); | |
writeSheet.getRange(1, 1, 1, 10).setValues([["Search Term", "AdGroup Id", "AdGroup Name", "Campaign Id", "Campaign Name", "Triggered Keyword", "Impressions", "Clicks", "Cost", "Conversions"]]); | |
var vertical = 2; | |
var sizes = []; | |
for (var i in orderedQueries){ | |
sizes.push(queries[orderedQueries[i]].length); | |
} | |
for (var i in orderedQueries){ | |
var entry = orderedQueries[i]; | |
var currentArrays = queries[entry]; | |
var size = sizes[i]; | |
writeSheet.getRange(vertical, 1).setValue(entry); | |
writeSheet.getRange(vertical, 2, size, 9).setValues(currentArrays); | |
vertical += size ; | |
} | |
Logger.log('The data has been written to the sheet specified by URL provided'); | |
} |
The post Want to see if the same search query is appearing in different ad groups? appeared first on Search Engine Land.
from SEO Rank Video Blog http://ift.tt/2ken39R
via IFTTT
No comments:
Post a Comment