L’évolution de votre site peut amener certaines pages à disparaître ou encore à voir leurs URLs changer.
Or, vos campagnes ont peut-être en page de destination des pages pour lesquels l’URL ne répond plus… si ce n’est par une page d’erreur.

Contrôler le bon fonctionnement de chacun des liens de votre compte AdWords peut alors s’avérer chronophage.
Et si comme moi, vous gérez plusieurs comptes AdWords via un centre multicompte, les choses se compliquent d’avantage.

Les liens brisés, cassés ou en erreur sont des liens pour lesquels le contenu n’est plus directement disponible.
Ainsi, une page supprimée génère une erreur 404 lorsqu’on tente d’y accéder par son URL.
Un code mal conçu sur de votre serveur internet peut également causer l’indisponibilité d’une page.
Et enfin, une redirection permanente (code HTTP 301) occasionne un effet de saute-mouton, en plus de biais analytiques, que l’on peut éviter.

Dans cet article, je vous propose les deux cas :

  1. lister les liens qui retournent une erreur, dans votre compte AdWords
  2. lister les liens qui retournent une erreur, dans votre centre multicompte

Cette deuxième option est possible, grâce à une fonctionnalité d’AdWords permettant d’exécuter un script sur 50 comptes simultanément.

Vous voici bientôt paré et vous n’aurez plus à contrôler les liens brisés manuellement, puisqu’un script le fera pour vous automatiquement.

Script AdWords pour détecter les liens cassés avec un compte unique


/****************************
* 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 = [301,404,500];
var TO = ['email_1@example.com'/*,'email_2@example.com'*/];
var SUBJECT = 'Rapport de liens cassés - ' + _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 = "Il y a " + bad_entities.length + " URLs cassées. Voir la fichier joint pour les détails.";

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");
}

Script AdWords pour détecter les liens cassés avec un centre multicompte


/******************************************
* Monitor Broken Links Using MCC Level Scripts
* Version 1.3
* Changelog v1.3
* - Added previous version of report api to script until
* I update my urls.
* Changelog v1.2
* - Fixing INVALID_PREDICATE_ENUM_VALUE
* Changelog v1.1
* - Stopped timeouts
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
var SCRIPT_NAME = 'Broken Url Checker';
var LOG_LEVEL = 'error'; //change this to debug if you want more logging
var NOTIFY = ['email@example.com'];
var SPREADSHEET_PREFIX = 'Broken Url Details'; // A timestamp is appended
var NOTIFY_ON_ERROR = ['email+error@example.com'];
var STRIP_QUERY_STRING = true; //Drop everything after the ? in the url to speed things up. If your URLs need params to be functionnal, turn this to "false"
var REPORT_ON_REDIRECTS = true; //If you want to be able to track 301s and 302, turn this on with the value "true"
var VALID_RESPONSE_CODES = [200]; // If you want to include 301 and 302 in the valid response codes replace the value by "[200,301,302]"
var URLS_CHECKED_FILE_NAME = 'UrlsAlreadyChecked-'+AdWordsApp.currentAccount().getCustomerId()+'.json';
var DONE_LABEL_PREFIX = 'All Urls Checked - ';

function main() {
MccApp.accounts().withLimit(50).executeInParallel('checkUrls', 'reportResults');
}

function checkUrls() {
try {
debug('Processing account: '+AdWordsApp.currentAccount().getName());

debug('Checking to see if we finished processing for today.');
var dateStr = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), 'yyyy-MM-dd');
var finishedLabelName = DONE_LABEL_PREFIX+dateStr;
var alreadyDone = AdWordsApp.labels().withCondition("Name = '"+finishedLabelName+"'").get().hasNext();
if(alreadyDone) {
info('All urls have been checked for today.');
return '';
}
var labelIter = AdWordsApp.labels().withCondition("Name STARTS_WITH '"+DONE_LABEL_PREFIX+"'").get();
while(labelIter.hasNext()) { labelIter.next().remove(); }

debug('Checking for previous urls.');
var urlsAlreadyChecked = readValidUrlsFromJSON();
info('Found '+Object.keys(urlsAlreadyChecked).length+' urls already checked.');

var toReportKeywords = [];
var toReportAds = [];
var didExitEarly = false;
var keywordUrls = getKeywordUrls();
for(var key in keywordUrls) {
var kwRow = keywordUrls[key];
var url = cleanUrl(kwRow.DestinationUrl);
verifyUrl(kwRow,url,urlsAlreadyChecked,toReportKeywords);
if(shouldExitEarly()) { didExitEarly = true; break; }
}
if(!didExitEarly) {
var adUrls = getAdUrls();
for(var i in adUrls) {
var adRow = adUrls[i];
if(adRow.CreativeDestinationUrl) {
var url = cleanUrl(adRow.CreativeDestinationUrl);
verifyUrl(adRow,url,urlsAlreadyChecked,toReportAds);
}
if(adRow.ImageAdUrl) {
var url = cleanUrl(adRow.CreativeDestinationUrl);
verifyUrl(adRow,url,urlsAlreadyChecked,toReportAds);
}
if(shouldExitEarly()) { didExitEarly = true; break; }
}
}
var returnData = {
accountId : AdWordsApp.currentAccount().getCustomerId(),
accountName : AdWordsApp.currentAccount().getName(),
uniqueUrlsChecked : Object.keys(urlsAlreadyChecked).length,
brokenKeywords : toReportKeywords,
brokenAds : toReportAds,
didExitEarly : didExitEarly
};
if(didExitEarly) {
writeValidUrlsToJSON(urlsAlreadyChecked);
} else {
AdWordsApp.createLabel(finishedLabelName, 'Label created by '+SCRIPT_NAME, '#C0C0C0');
writeValidUrlsToJSON({});
}
return JSON.stringify(returnData);
} catch(e) {
// This error handling helps notify you when things don't work out well.
error(e);
if(MailApp.getRemainingDailyQuota() >= NOTIFY_ON_ERROR.length) {
var acctName = AdWordsApp.currentAccount().getName();
var acctId = AdWordsApp.currentAccount().getCustomerId();
for(var i in NOTIFY_ON_ERROR) {
info('Sending mail to: '+NOTIFY_ON_ERROR[i]);
MailApp.sendEmail(NOTIFY_ON_ERROR[i], 'ERROR: '+SCRIPT_NAME+' - '+acctName+' - ('+acctId+')', e);
}
} else {
error('Quotas email dépassé pour la journée.');
}
return '';
}

function shouldExitEarly() {
return (AdWordsApp.getExecutionInfo().getRemainingTime() < 60); } function verifyUrl(row,url,urlsAlreadyChecked,toReport) { if(!urlsAlreadyChecked[url]) { info('Checking url: ' + url); var urlCheckResults = checkUrl(url); if(!urlCheckResults.isValid) { row['cleanUrl'] = url; row['responseCode'] = urlCheckResults.responseCode; toReport.push(row); } urlsAlreadyChecked[url] = urlCheckResults; } else { if(!urlsAlreadyChecked[url].isValid) { row['cleanUrl'] = url; row['responseCode'] = urlsAlreadyChecked[url].responseCode; toReport.push(row); } } } function checkUrl(url) { var retVal = { responseCode : -1, isValid: false }; var httpOptions = { muteHttpExceptions:true, followRedirects:(!REPORT_ON_REDIRECTS) }; try { retVal.responseCode = UrlFetchApp.fetch(url, httpOptions).getResponseCode(); retVal.isValid = isValidResponseCode(retVal.responseCode); } catch(e) { warn(e.message); //Something is wrong here, we should know about it. retVal.isValid = false; } return retVal; } function isValidResponseCode(resp) { return (VALID_RESPONSE_CODES.indexOf(resp) >= 0);
}

//Clean the url of query strings and valuetrack params
function cleanUrl(url) {
if(STRIP_QUERY_STRING) {
if(url.indexOf('?')>=0) {
url = url.split('?')[0];
}
}
if(url.indexOf('{') >= 0) {
//Let's remove the value track parameters
url = url.replace(/\{[^\}]*\}/g,'');
}
return url;
}

//Use the reporting API to pull this information because it is super fast.
//The documentation for this is here: http://goo.gl/IfMb31
function getKeywordUrls() {
var OPTIONS = { includeZeroImpressions : true, apiVersion: 'v201409' };
var cols = ['CampaignId','CampaignName',
'AdGroupId','AdGroupName',
'Id','KeywordText','KeywordMatchType',
'IsNegative','DestinationUrl','Impressions'];
var report = 'KEYWORDS_PERFORMANCE_REPORT';
var query = ['select',cols.join(','),'from',report,
'where CampaignStatus = ENABLED',
'and AdGroupStatus = ENABLED',
'and Status = ENABLED',
'during','LAST_7_DAYS'].join(' ');
var results = {};
var reportIter = AdWordsApp.report(query, OPTIONS).rows();
while(reportIter.hasNext()) {
var row = reportIter.next();
if(row.IsNegative === 'true') { continue; }
if(!row.DestinationUrl) { continue; }
if(row.KeywordMatchType === 'Exact') {
row.KeywordText = ['[',row.KeywordText,']'].join('');
} else if(row.KeywordMatchType === 'Phrase') {
row.KeywordText = ['"',row.KeywordText,'"'].join('');
}
var rowKey = [row.CampaignId,row.AdGroupId,row.Id].join('-');
results[rowKey] = row;
}
return results;
}

//Use the reporting API to pull this information because it is super fast.
//The documentation for this is here: http://goo.gl/8RHTBj
function getAdUrls() {
var OPTIONS = { includeZeroImpressions : true, apiVersion: 'v201409' };
var cols = ['CampaignId','CampaignName',
'AdGroupId','AdGroupName',
'AdType',
'Id','Headline','Description1','Description2','DisplayUrl',
'CreativeDestinationUrl','ImageAdUrl','Impressions'];
var report = 'AD_PERFORMANCE_REPORT';
var query = ['select',cols.join(','),'from',report,
'where CampaignStatus = ENABLED',
'and AdGroupStatus = ENABLED',
'and Status = ENABLED',
'during','TODAY'].join(' ');
var results = {};
var reportIter = AdWordsApp.report(query, OPTIONS).rows();
while(reportIter.hasNext()) {
var row = reportIter.next();
if(!row.CreativeDestinationUrl || !row.ImageAdUrl) { continue; }
var rowKey = [row.CampaignId,row.AdGroupId,row.Id].join('-');
if(row.ImageAdUrl !== '--') {
row.ImageAdUrl = ('https://tpc.googlesyndication.com/pageadimg/imgad?id='+row.ImageAdUrl);
} else {
row.ImageAdUrl = '';
}
results[rowKey] = row;
}
return results;
}

//This function quickly writes the url data to a file
//that can be loaded again for the next run
function writeValidUrlsToJSON(toWrite) {
var file = getFile(URLS_CHECKED_FILE_NAME,false);
file.setContent(JSON.stringify(toWrite));
}

//And this loads that stored file and converts it to an object
function readValidUrlsFromJSON() {
var file = getFile(URLS_CHECKED_FILE_NAME,false);
var fileData = file.getBlob().getDataAsString();
if(fileData) {
return JSON.parse(fileData);
} else {
return {};
}
}
}

//This is the callback function that collects all the data from the scripts
//that were run in parallel on each account. More details can be found here:
// http://goo.gl/BvOPZo
function reportResults(responses) {
var summaryEmailData = [];
var dateTimeStr = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), 'yyyy-MM-dd HH:m:s');
var spreadsheetName = SPREADSHEET_PREFIX+' - '+dateTimeStr;
for(var i in responses) {
if(!responses[i].getReturnValue()) { continue; }
var res = JSON.parse(responses[i].getReturnValue());
var sheetUrl = writeResultsToSpreadsheet(res,spreadsheetName);
summaryEmailData.push({accountId:res.accountId,
accountName:res.accountName,
didExitEarly:res.didExitEarly,
uniqueUrlsChecked:res.uniqueUrlsChecked,
numBrokenKeywords:res.brokenKeywords.length,
numBrokenAds:res.brokenAds.length,
sheetUrl: sheetUrl});
}
if(summaryEmailData.length > 0) {
sendSummaryEmail(summaryEmailData);
}

function writeResultsToSpreadsheet(res,name) {
var file = getFile(name,true);
var spreadsheet;
var maxRetries = 0;
while(maxRetries < 3) {
try {
spreadsheet = SpreadsheetApp.openById(file.getId());
break;
} catch(e) {
maxRetries++;
Utilities.sleep(1000);
}
}
if(!spreadsheet) { throw 'Could not open file: '+name; }
if(spreadsheet.getSheetByName('Sheet1')) {
spreadsheet.getSheetByName('Sheet1').setName(res.accountId);
}
var sheet = spreadsheet.getSheetByName(res.accountId);
if(!sheet) {
sheet = spreadsheet.insertSheet(res.accountId, spreadsheet.getSheets().length);
}
var toWrite = [['Type','Clean Url','Response Code','Campaign Name','AdGroup Name','Text','Full Url']];
for(var i in res.brokenKeywords) {
var row = res.brokenKeywords[i];
toWrite.push(['Keyword',
row.cleanUrl,
row.responseCode,
row.CampaignName,
row.AdGroupName,
row.KeywordText,
row.DestinationUrl]);
}
for(var i in res.brokenAds) {
var row = res.brokenAds[i];
toWrite.push([row.AdType,
row.cleanUrl,
row.responseCode,
row.CampaignName,
row.AdGroupName,
(row.Headline) ? [row.Headline,row.Description1,row.Description2,row.DisplayUrl].join('|') : '',
(row.CreativeDestinationUrl) ? row.CreativeDestinationUrl : row.ImageAdUrl]);
}
var lastRow = sheet.getLastRow();
var numRows = sheet.getMaxRows();
if((numRows-lastRow) < toWrite.length) { sheet.insertRowsAfter(lastRow,toWrite.length-numRows+lastRow); } var range = sheet.getRange(lastRow+1,1,toWrite.length,toWrite[0].length); range.setValues(toWrite); if((sheet.getMaxColumns() - sheet.getLastColumn()) > 0) {
sheet.deleteColumns(sheet.getLastColumn()+1, sheet.getMaxColumns() - sheet.getLastColumn());
}
file = DriveApp.getFileById(spreadsheet.getId());
try {
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
} catch(e) {
file.setSharing(DriveApp.Access.DOMAIN_WITH_LINK, DriveApp.Permission.VIEW);
}
//This gives you a link directly to the spreadsheet sheet.
return (spreadsheet.getUrl() + '#gid=' + sheet.getSheetId());
}

//This function builds the summary email and sends it to the people in
//the NOTIFY list
function sendSummaryEmail(summaryEmailData) {
var subject = SCRIPT_NAME+' Summary Results';
var body = subject;
var htmlBody = ''+subject;
htmlBody += '<br/ >Should strip query strings: '+STRIP_QUERY_STRING;
htmlBody += '<br/ >Report on redirects: '+REPORT_ON_REDIRECTS;
htmlBody += '<br/ >Valid response codes: '+VALID_RESPONSE_CODES;
htmlBody += '<br/ ><br/ >';
htmlBody += '

'; htmlBody += ''; htmlBody += '';
htmlBody += '
Nom du compte';
htmlBody += '
Exited Early';
htmlBody += '
Unique Urls Checked';
htmlBody += '
# Broken Keyword Urls';
htmlBody += '
# Broken Ad Urls';
htmlBody += '
Full Report';
htmlBody += '';
for(var i in summaryEmailData) {
var row = summaryEmailData[i];
htmlBody += '
';
}
htmlBody += '
Id du compte
'+ row.accountId + ' ' + row.accountName + ' ' + row.didExitEarly + ' ' + row.uniqueUrlsChecked + ' ' + row.numBrokenKeywords + ' ' + row.numBrokenAds + ' ' + 'Show Details' + '
'; htmlBody += '<br/ >'; htmlBody += Utilities.formatDate(new Date(),AdWordsApp.currentAccount().getTimeZone(),'MMMM dd, yyyy @ hh:mma z'); htmlBody += '. Completed. '+Object.keys(summaryEmailData).length+' Accounts checked.'; htmlBody += ' '; var options = { htmlBody : htmlBody }; for(var i in NOTIFY) { MailApp.sendEmail(NOTIFY[i], subject, body, options); } } }   //This function finds a given file on Google Drive //If it does not exist, it creates a new file //if isSpreadsheet is set, it will create a new spreadsheet //otherwise, it creates a text file. function getFile(fileName,isSpreadsheet) { var maxRetries = 0; var errors = []; while(maxRetries < 3) { try { var fileIter = DriveApp.getFilesByName(fileName); if(!fileIter.hasNext()) { info('Could not find file: '+fileName+' on Google Drive. Creating new file.'); if(isSpreadsheet) { return SpreadsheetApp.create(fileName); } else { return DriveApp.createFile(fileName,''); } } else { return fileIter.next(); } } catch(e) { errors.push(e); maxRetries++; Utilities.sleep(1000); } } if(maxRetries == 3) { throw errors.join('. '); } } //Some functions to help with logging var LOG_LEVELS = { 'error':1, 'warn':2, 'info':3, 'debug':4 }; function error(msg) { if(LOG_LEVELS['error'] <= LOG_LEVELS[LOG_LEVEL]) { log('ERROR',msg); } } function warn(msg) { if(LOG_LEVELS['warn'] <= LOG_LEVELS[LOG_LEVEL]) { log('WARN' ,msg); } } function info(msg) { if(LOG_LEVELS['info'] <= LOG_LEVELS[LOG_LEVEL]) { log('INFO' ,msg); } } function debug(msg) { if(LOG_LEVELS['debug'] <= LOG_LEVELS[LOG_LEVEL]) { log('DEBUG',msg); } } function log(type,msg) { Logger.log(type + ' - ' + msg); }

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Fill out this field
Fill out this field
Veuillez saisir une adresse de messagerie valide.
Vous devez accepter les conditions pour continuer

La modération des commentaires est activée. Votre commentaire peut prendre un certain temps avant d'apparaître.

Menu