UPDATED: 2013-05-20: Based on a comment from a reader, the script now only checks active campaigns and adgroups and it also only checks each url once.
UPDATED: 2013-04-28: Based on a comment from a reader, I made some updates to this script which include adding the response code to the email and formatting the results as an attachment.
It happens to the best of us. Sometimes, we remove pages on our site or update links and forget to make the corresponding change our SEM accounts. So tonight I put together a quick script to run through all your ads and keywords and create an email report with any of them that return a 404 Not Found or a 500 Server Error response code. You can easily add more error codes to check for by adding them to the BAD_CODES array at the beginning of the script.
Thanks,
Russ
/**************************** * Find Broken Urls In Your Account * Version 1.1 * ChangeLog v1.1 * - Updated to only see Text Ads * Created By: Russ Savage * FreeAdWordsScripts.com ****************************/ function main() { // You can add more if you want: http://goo.gl/VhIX var BAD_CODES = [404,500]; var TO = ['email@example.com'/*,'email_address_2@example.com'*/]; var SUBJECT = 'Broken Url Report - ' + _getDateString(); var HTTP_OPTIONS = { muteHttpExceptions:true }; //Let's look at ads and keywords for urls var iters = [ //For Ad Level Urls AdWordsApp.ads() .withCondition("Status = 'ENABLED'") .withCondition("AdGroupStatus = 'ENABLED'") .withCondition("CampaignStatus = 'ENABLED'") .withCondition("Type = 'TEXT_AD'") .get(), //For Keyword Level Urls AdWordsApp.keywords() .withCondition("Status = 'ENABLED'") .withCondition("DestinationUrl != ''") .withCondition("AdGroupStatus = 'ENABLED'") .withCondition("CampaignStatus = 'ENABLED'") .get() ]; var already_checked = {}; var bad_entities = []; for(var x in iters) { var iter = iters[x]; while(iter.hasNext()) { var entity = iter.next(); if(entity.getDestinationUrl() == null) { continue; } var url = entity.getDestinationUrl(); if(url.indexOf('{') >= 0) { //Let's remove the value track parameters url = url.replace(/\{[0-9a-zA-Z]+\}/g,''); } if(already_checked[url]) { continue; } var response_code; try { Logger.log("Testing url: "+url); response_code = UrlFetchApp.fetch(url, HTTP_OPTIONS).getResponseCode(); } catch(e) { //Something is wrong here, we should know about it. bad_entities.push({e : entity, code : -1}); } if(BAD_CODES.indexOf(response_code) >= 0) { //This entity has an issue. Save it for later. bad_entities.push({e : entity, code : response_code}); } already_checked[url] = true; } } var column_names = ['Type','CampaignName','AdGroupName','Id','Headline/KeywordText','ResponseCode','DestUrl']; var attachment = column_names.join(",")+"\n"; for(var i in bad_entities) { attachment += _formatResults(bad_entities[i],","); } if(bad_entities.length > 0) { var options = { attachments: [Utilities.newBlob(attachment, 'text/csv', 'bad_urls_'+_getDateString()+'.csv')] }; var email_body = "There are " + bad_entities.length + " urls that are broken. See attachment for details."; for(var i in TO) { MailApp.sendEmail(TO[i], SUBJECT, email_body, options); } } } //Formats a row of results separated by SEP function _formatResults(entity,SEP) { var e = entity.e; if(typeof(e['getHeadline']) != "undefined") { //this is an ad entity return ["Ad", e.getCampaign().getName(), e.getAdGroup().getName(), e.getId(), e.getHeadline(), entity.code, e.getDestinationUrl() ].join(SEP)+"\n"; } else { // and this is a keyword return ["Keyword", e.getCampaign().getName(), e.getAdGroup().getName(), e.getId(), e.getText(), entity.code, e.getDestinationUrl() ].join(SEP)+"\n"; } } //Helper function to format todays date function _getDateString() { return Utilities.formatDate((new Date()), AdWordsApp.currentAccount().getTimeZone(), "yyyy-MM-dd"); }