Tuesday, June 13, 2017

How to check if you’ve screwed up your AdWords account, v.2

If you can believe it, since publishing our first version of this script a year ago, Google has gone and changed AdWords again. And here we were thinking it would just stay the same forever — guess we were wrong!

New AdWords stuff means new potential for screw-ups, hence the 2017 revamp. Who knows? Maybe we’ll do a new one every year (definitely don’t hold us to that, though).

So, what’s new since this time last year? Well, we could mention the exact match thing, but maybe it’s better just to take a deep breath and put that one to the side for now. Otherwise, the biggest new addition is the launch of expanded text ads (ETAs). And, seeing as as we had to update the script for ETAs, we thought we’d also include sitelinks and callouts, just to be nice.

So, without further ado, here’s our updated script for blunder-proof Google ads!

(Legal Disclaimer: Brainlabs takes no responsibility for any blunders taking place after said script has been downloaded.)

To use the script, copy the code below into your AdWords account, and then change the following settings:

  • spreadsheetUrl is the URL of a Google Doc spreadsheet, which the results will be copied into. Create a blank spreadsheet and put the URL in here.
  • Enter into the array campaignNameDoesNotContain a list of phrases contained in campaign names which you would like to exclude. These have to be in quote marks and separated by commas — for example, [“Display,” “Shopping”] to exclude all campaigns with names containing “display” or “shopping.”
  • Enter into the array campaignNameContains a list of phrases contained in campaign names which you would like to include. Like campaignNameDoesNotContain, these should be in quote marks and comma separated — for example, [“Brand,” “Generic”] to include only campaigns with names containing “brand” or “generic.”
    • You can leave campaignNameDoesNotContain and campaignNameContains blank, [], to include all campaigns.
    • They are not case-sensitive.
    • If you need to put a double quote in, put a backslash before it.
    • If your account is large and the script keeps timing out, you could try running the script multiple times, using campaignNameContains or campaignNameDoesNotContain to look at different campaigns each time.
  • If ignorePausedCampaigns is true, then the script will only look at currently active campaigns. Set them to false if you want to look at currently paused campaigns.
    • You can use this to check new campaigns before they go live.
  • If checkKeywords is true, then the script will check your keywords.
  • If checkAdText is true, then the script will check your text ads for certain phrases (past years and seasonal events by default).
  • If checkSpelling is true, then the script will see if some common English misspellings appear in your text ads.
    • If the script times out when you try to run it, you could try setting two of the above three settings to false and one to true, so that you do one of the checks at a time.
  • If checkExtensions is true, then the script will check your callouts and sitelinks for  specified phrases (if checkAdText is true) and spelling errors (if checkSpelling is true).
  • If checkAllExtensions is false, then the script will check the extensions that have impressions with your filtered campaigns. (This is because the AdWords Scripts report won’t show campaign info if there aren’t impressions.) If it is true, then all callouts and extensions will be checked, regardless of whether they are attached to anything or have impressions.
  • checkAdsFor is a list of pieces of text used to check your ads (if checkAdText is true). If an ad contains any of the specified text, it will be logged in the spreadsheet. We’ve suggested the past few years and a few holidays, but you could remove any you don’t want or add your own — they just have to be in quotation marks and separated by commas.
    • Guess what? This isn’t case-sensitive.
    • Using the power of regular expressions, the script will only check for whole words — if you’re checking for “Easter,” it won’t pick up ads that say “Eastern” or “feaster.” It will pick up “Easter’s.”
    • The script looks at ad text as a whole, rather than line by line. That means that if you put “Black Friday” in checkAdsFor, it will spot if an ad says “Black” at the end of headline 1 and “Friday” at the start of the description.

We’ve also stuck in an Advanced Option: misspellingsSheetUrl is a Google Sheet where we’ve included the most commonly misspelled English words (based on Wikipedia’s wonderfully useful list). If you’d like to use a different list — to reflect misspellings of your industry’s terms or for other languages — you can replace the URL with your own spreadsheet, and replace misspellingsSheetName with the name of the tab in your spreadsheet containing the misspellings. Just make sure it’s formatted the same: misspellings in column A and possible corrections in column B.

/**
*
* AdWords Script for keyword and ad checking.
* Checks keyword text for punctuation suggesting the wrong match type, checks
* broad match keywords for missing BMM. Checks ad, sitelink and callout text
* for text that suggests ads are out-of-date (like previous years and seasonal
* events) and for common English spelling mistakes.
*
* Version: 2.1
* Updated 2017-01-05: changed 'CreativeApprovalStatus' to 'CombinedApprovalStatus'
* Google AdWords Script maintained by 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 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 include campaigns that had impressions but are currently paused.
var checkKeywords = true;
// Set this to true to look at keyword text for errors like missing BMM.
var checkAdText = true;
// Set this to true to look at ad text for errors like previous years.
var checkSpelling = true;
// Set this to true to check ad text for some common spelling errors.
var checkExtensions = true;
// Set this to true to check the text of sitelinks and callouts for text and
// spelling errors (if those are enabled above).
var checkAllExtensions = false;
// Set this to true to check the text of all of your sitelinks and callouts.
// If this is false, only extensions that have had impressions with the filtered
// campaigns will be checked.
var checkAdsFor = ["2014", "2015", "2016", "Easter", "Christmas", "New Year"];
// This is the text that the script will look for in ad copy.
// Feel free to add more!
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Advanced Options
var misspellingsSheetUrl = "https://docs.google.com/spreadsheets/d/1Z2Fg_F8WhmY8Ey5Bv4Zuk8vcTW7A2EoVwMbGVz7TNms/edit#gid=0";
// This spreadsheet has the list of English spelling errors, used if checkSpelling
// is true.
var misspellingsSheetName = "Main";
//This is the name of the sheet in the above spreadsheet
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Functions
function main() {
var spreadsheet = checkSpreadsheet(spreadsheetUrl, "the spreadsheet");
var sheet = spreadsheet.getSheets()[0];
var campaignIds = getCampaignIds();
if (checkKeywords) {
keywordChecking(campaignIds, sheet);
Logger.log("Finished keyword checks.");
}
if (checkAdText) {
adTextChecking(campaignIds, sheet);
Logger.log("Finished ad text checks.");
}
if (checkSpelling) {
adSpellingChecking(campaignIds, sheet);
Logger.log("Finished common misspelling checks.");
}
}
// 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
if (spreadsheetName == "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() {
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 adTextReport = AdWordsApp.report(
"SELECT CampaignId " +
"FROM CAMPAIGN_PERFORMANCE_REPORT " +
whereStatementsArray[i] +
"DURING LAST_30_DAYS");
var rows = adTextReport.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;
}
// Prints an array of rows into the spreadsheet
function printRows(sheet, title, headers, rows) {
try {
var printArray = [];
sheet.getRange("R" + (sheet.getLastRow()+2) + "C1").setValue(title);
if (rows.length == 0) {
sheet.appendRow(["No issues found"]);
Logger.log("Nothing to output for '" + title + "'");
return;
}
sheet.appendRow(headers);
for (var i=0; i < rows.length; i++) {
printArray.push(rows[i]);
}
var lastRow = sheet.getLastRow();
sheet.getRange("R" + (lastRow + 1) + "C1:R" + (lastRow+printArray.length)
+ "C" + (printArray[0].length) ).setValues(printArray);
Logger.log("Printed " + rows.length + " rows for '" + title + "'");
} catch (e) {
Logger.log("Printing rows '" + title + "' failed: " + e);
}
}
function keywordChecking(campaignIds, sheet) {
try {
var broadMissingPlusses = [];
var nonBroadWithPlusses = [];
var nonExactWithBrackets = [];
var nonPhraseWithQuotes = [];
var keywordMatchReport = AdWordsApp.report(
"SELECT CampaignName, AdGroupName, Criteria, KeywordMatchType " +
"FROM KEYWORDS_PERFORMANCE_REPORT " +
"WHERE AdGroupStatus = ENABLED AND Status = ENABLED AND IsNegative = FALSE " +
"AND CampaignId IN [" + campaignIds.join(",") + "] " +
"DURING LAST_30_DAYS");
var keywordMatchRows = keywordMatchReport.rows();
while (keywordMatchRows.hasNext()) {
var keywordMatchRow = keywordMatchRows.next();
if (keywordMatchRow["KeywordMatchType"].toLowerCase() === "broad") {
if(keywordMatchRow["Criteria"].indexOf("+") <0) {
//if the broad KW is entirely missing +s
broadMissingPlusses.push([keywordMatchRow["CampaignName"],keywordMatchRow["AdGroupName"],"'"+keywordMatchRow["Criteria"],keywordMatchRow["KeywordMatchType"]]);
} else {
var words = keywordMatchRow["Criteria"].split(" ");
var missingPlus = false;
for (var j=0; j<words.length; j++) {
if (words[j].substr(0,1) != "+") {
missingPlus = true;
break;
}
}
if (missingPlus) {
broadMissingPlusses.push([keywordMatchRow["CampaignName"],keywordMatchRow["AdGroupName"],"'"+keywordMatchRow["Criteria"],keywordMatchRow["KeywordMatchType"]]);
}
}
} else {
// If the keyword is not broad
if(keywordMatchRow["Criteria"].indexOf("+") > -1) {
nonBroadWithPlusses.push([keywordMatchRow["CampaignName"],keywordMatchRow["AdGroupName"],"'"+keywordMatchRow["Criteria"],keywordMatchRow["KeywordMatchType"]]);
}
}
if (keywordMatchRow["KeywordMatchType"].toLowerCase() != "exact") {
if(keywordMatchRow["Criteria"].indexOf("[") > -1 || keywordMatchRow["Criteria"].indexOf("]") > -1) {
nonExactWithBrackets.push([keywordMatchRow["CampaignName"],keywordMatchRow["AdGroupName"],"'"+keywordMatchRow["Criteria"],keywordMatchRow["KeywordMatchType"]]);
}
}
if (keywordMatchRow["KeywordMatchType"].toLowerCase() != "phrase") {
if(keywordMatchRow["Criteria"].indexOf('"') > -1) {
nonPhraseWithQuotes.push([keywordMatchRow["CampaignName"],keywordMatchRow["AdGroupName"],"'"+keywordMatchRow["Criteria"],keywordMatchRow["KeywordMatchType"]]);
}
}
}//end while
var headers = ["Campaign", "Ad Group", "Keyword", "Match"];
printRows(sheet, "Broad Match Keywords Missing +s", headers, broadMissingPlusses);
printRows(sheet, "Non-Broad Match Keywords With +s", headers, nonBroadWithPlusses);
printRows(sheet, "Non-Exact Match Keywords With [ or ]", headers, nonExactWithBrackets);
printRows(sheet, 'Non-Phrase Match Keywords With "s', headers, nonPhraseWithQuotes);
} catch (e) {
Logger.log("Keyword checking failed: " + e);
}
} // end function keywordChecking
function adTextChecking(campaignIds, sheet) {
try {
//
var adLines = ['Headline', 'Description1', 'Description2', 'DisplayUrl'];
var adsWithBadText = [];
var etaLines = ['HeadlinePart1', 'HeadlinePart2', 'Description', 'Path1', 'Path2'];
var etasWithBadText = [];
var patterns = [];
var charactersToEscape = ["\\", "/", ".", "?", "+", "*", "^", "$", "[", "]", "(", ")", "{", "}"];
for (var k=0; k<checkAdsFor.length; k++) {
var cleanedText = checkAdsFor[k].toLowerCase();
for (var i=0; i<charactersToEscape.length; i++) {
cleanedText = cleanedText.replace(charactersToEscape[i],"\\" + charactersToEscape[i]);
}
patterns.push( RegExp("(^|\\W)" + cleanedText + "($|\\W)"));
}
var adTextReport = AdWordsApp.report(
"SELECT CampaignName, AdGroupName, Headline, HeadlinePart1, HeadlinePart2, Description, Description1, Description2, DisplayUrl, Path1, Path2, AdType " +
"FROM AD_PERFORMANCE_REPORT " +
"WHERE AdGroupStatus = ENABLED AND Status = ENABLED " +
"AND AdType IN [TEXT_AD, EXPANDED_TEXT_AD] AND CombinedApprovalStatus != DISAPPROVED " +
"AND CampaignId IN [" + campaignIds.join(",") + "] " +
"DURING LAST_30_DAYS");
var rows = adTextReport.rows();
while (rows.hasNext()) {
var row = rows.next();
if (row["AdType"] == "Text ad")
{
var textFound = checkAd(row, adLines, patterns);
if (textFound.length > 0) {
adsWithBadText.push([row["CampaignName"], row["AdGroupName"], row['Headline'],
row['Description1'], row['Description2'], row['DisplayUrl'],
textFound.join(", ")]);
}
}
else
{
var textFound = checkAd(row, etaLines, patterns);
if (textFound.length > 0) {
etasWithBadText.push([row["CampaignName"], row["AdGroupName"], row['HeadlinePart1'],
row['HeadlinePart2'], row['Description'], row['Path1'], row['Path2'],
textFound.join(", ")]);
}
}
} // end while
var headers = ["Campaign", "Ad Group", 'Headline', 'Description 1', 'Description 2',
'Display Url', 'Problematic Text'];
var etaHeaders = ["Campaign", "Ad Group", 'Headline 1', 'Headline 2', 'Description',
'Path 1', 'Path 2', 'Problematic Text'];
printRows(sheet, "Ad Copy With Problematic Text", headers, adsWithBadText);
printRows(sheet, "ETA Ad Copy With Problematic Text", etaHeaders, etasWithBadText);
if (checkExtensions)
{
var calloutLines = ["1"];
var sitelinkLines = ["1", "3", "4", "5"];
var calloutsWithBadText = [];
var sitelinksWithBadText = [];
var extReport = AdWordsApp.report(
"SELECT AttributeValues, PlaceholderType " +
"FROM PLACEHOLDER_FEED_ITEM_REPORT " +
"WHERE PlaceholderType IN [1, 17] " +
"AND Status = ENABLED " +
(checkAllExtensions ? "" : "AND CampaignId IN [" + campaignIds.join(",") + "] ") +
"DURING LAST_30_DAYS");
var rows = extReport.rows();
while (rows.hasNext()) {
var row = rows.next();
var values = JSON.parse(row.AttributeValues);
if (row.PlaceholderType == 17)
{
var textFound = checkAd(values, calloutLines, patterns);
if (textFound.length > 0) {
calloutsWithBadText.push([values["1"], textFound.join(", ")]);
}
}
else
{
var textFound = checkAd(values, sitelinkLines, patterns);
if (textFound.length > 0) {
sitelinksWithBadText.push([values["1"], removeUndefined(values["3"]), removeUndefined(values["4"]),
values["5"], textFound.join(", ")]);
}
}
} // end while
var sitelinkHeaders = ["Sitelink", "Description 1", "Description 2", "Sitelink URL", "Problematic Text"];
var calloutHeaders = ["Callout", "Problematic Text"];
printRows(sheet, "Sitelinks With Problematic Text", sitelinkHeaders, sitelinksWithBadText);
printRows(sheet, "Callouts With Problematic Text", calloutHeaders, calloutsWithBadText);
}
} catch (e) {
Logger.log("Ad text checking failed: " + e);
}
} // function adTextChecking
function checkAd(ad, adLines, patterns) {
var adCopy = "";
for (var j=0; j < adLines.length; j++) {
adCopy += " " + ad[adLines[j]];
}
adCopy = adCopy.toLowerCase();
var textFound = [];
for (var k=0; k < checkAdsFor.length; k++) {
if (adCopy.match(patterns[k])) {
textFound.push(checkAdsFor[k]);
}
}
return textFound;
}
function removeUndefined(str) {
return (str == undefined ? "None":str);
}
function adSpellingChecking(campaignIds, sheet) {
try {
var misspellingsSpreadsheet = checkSpreadsheet(misspellingsSheetUrl, "the misspelling spreadsheet");
var misspellingsSheet = misspellingsSpreadsheet.getSheetByName(misspellingsSheetName);
var misspellings = misspellingsSheet.getRange(2, 1, misspellingsSheet.getLastRow()-1, 2).getValues()
for (var k=0; k<misspellings.length; k++) {
misspellings[k][0] = " " + misspellings[k][0] + " ";
}
var adLines = ['Headline', 'Description1', 'Description2', 'DisplayUrl'];
var etaLines = ['HeadlinePart1', 'HeadlinePart2', 'Description', 'Path1', 'Path2'];
var adsWithBadText = [];
var etasWithBadText = [];
var adTextReport = AdWordsApp.report(
"SELECT CampaignName, AdGroupName, Headline, HeadlinePart1, HeadlinePart2, Description, Description1, Description2, DisplayUrl, Path1, Path2, AdType " +
"FROM AD_PERFORMANCE_REPORT " +
"WHERE AdGroupStatus = ENABLED AND Status = ENABLED " +
"AND AdType IN [TEXT_AD, EXPANDED_TEXT_AD] AND CombinedApprovalStatus != DISAPPROVED " +
"AND CampaignId IN [" + campaignIds.join(",") + "] " +
"DURING LAST_30_DAYS");
var rows = adTextReport.rows();
while (rows.hasNext()) {
var row = rows.next();
if (row.AdType == "TEXT_AD")
{
var [textFound, didYouMean] = spellCheck(row, adLines, misspellings);
if (textFound.length > 0) {
adsWithBadText.push([row["CampaignName"], row["AdGroupName"], row['Headline'],
row['Description1'], row['Description2'], row['DisplayUrl'],
textFound.join(", "), didYouMean.join(", ")]);
}
}
else
{
var [textFound, didYouMean] = spellCheck(row, etaLines, misspellings);
if (textFound.length > 0) {
etasWithBadText.push([row["CampaignName"], row["AdGroupName"], row['HeadlinePart1'],
row['HeadlinePart2'], row['Description'], row['Path1'], row['Path2'],
textFound.join(", "), didYouMean.join(", ")]);
}
}
} // end while
if (checkExtensions)
{
var calloutLines = ["1"];
var sitelinkLines = ["1", "3", "4", "5"];
var calloutsWithBadText = [];
var sitelinksWithBadText = [];
var extReport = AdWordsApp.report(
"SELECT AttributeValues, PlaceholderType " +
"FROM PLACEHOLDER_FEED_ITEM_REPORT " +
"WHERE PlaceholderType IN [1, 17] " +
"AND Status = ENABLED " +
(checkAllExtensions ? "" : "AND CampaignId IN [" + campaignIds.join(",") + "] ") +
"DURING LAST_30_DAYS");
var rows = extReport.rows();
while (rows.hasNext()) {
var row = rows.next();
var values = JSON.parse(row.AttributeValues);
if (row.PlaceholderType == 17)
{
var [textFound, didYouMean] = spellCheck(values, calloutLines, misspellings);
if (textFound.length > 0) {
calloutsWithBadText.push([values["1"], textFound.join(", "), didYouMean.join(", ")]);
}
}
else
{
var [textFound, didYouMean] = spellCheck(values, sitelinkLines, misspellings);
if (textFound.length > 0) {
sitelinksWithBadText.push([values["1"], removeUndefined(values["3"]), removeUndefined(values["4"]),
values["5"], textFound.join(", "), didYouMean.join(", ")]);
}
}
} // end while
}
var headers = ["Campaign", "Ad Group", 'Headline', 'Description 1', 'Description 2',
'Display Url', 'Possible Misspelling', 'Possible Corrections'];
var etaHeaders = ["Campaign", "Ad Group", 'Headline 1', 'Headline 2', 'Description',
'Path 1', 'Path 2', 'Possible Misspelling', 'Possible Corrections'];
var sitelinkHeaders = ["Sitelink", "Description 1", "Description 2", 'Possible Misspelling', 'Possible Corrections'];
var calloutHeaders = ["Callout", 'Possible Misspelling', 'Possible Corrections'];
printRows(sheet, "Ad Copy With Possible Misspellings", headers, adsWithBadText);
printRows(sheet, "ETA Ad Copy With Possible Misspellings", etaHeaders, etasWithBadText);
if (checkExtensions) {
printRows(sheet, "Sitelinks With Possible Misspellings", sitelinkHeaders, sitelinksWithBadText);
printRows(sheet, "Callouts With Possible Misspellings", calloutHeaders, calloutsWithBadText);
}
} catch (e) {
Logger.log("Ad spell checking failed: " + e);
}
} // function adSpellingChecking
function spellCheck(row, adLines, misspellings) {
var adCopy = " ";
for (var j=0; j<adLines.length; j++) {
adCopy += " " + row[adLines[j]];
}
adCopy += " ";
adCopy = adCopy.toLowerCase();
adCopy = adCopy.replace(/(_|[^\w\-'0-9])/g," ");
var textFound = [];
var didYouMean = [];
for (var k=0; k<misspellings.length; k++) {
if (adCopy.indexOf(misspellings[k][0]) > -1) {
textFound.push(misspellings[k][0].trim());
didYouMean.push(misspellings[k][1]);
}
}
return [textFound, didYouMean];
}

The post How to check if you’ve screwed up your AdWords account, v.2 appeared first on Search Engine Land.

No comments:

Post a Comment