// Final URL health check
// Collects every unique final URL on enabled ads and keywords, requests each one
// following redirects, and reports anything that does not return 200 or that
// ends up on a different host than it started on.
// Read-only: it reports, it does not pause anything.
var EMAIL = ''; // leave empty to log only
var MAX_URLS = 300; // scripts stop at 30 minutes; raise only if you have headroom
var USER_AGENT = 'Mozilla/5.0 (compatible; AdsScriptUrlCheck/1.0)';
function main() {
var urls = {};
collect(
'SELECT ad_group_ad.ad.final_urls ' +
'FROM ad_group_ad ' +
'WHERE ad_group_ad.status = "ENABLED" ' +
' AND ad_group.status = "ENABLED" ' +
' AND campaign.status = "ENABLED"',
function (r) { return r.adGroupAd && r.adGroupAd.ad ? r.adGroupAd.ad.finalUrls : null; },
urls
);
collect(
'SELECT ad_group_criterion.final_urls ' +
'FROM keyword_view ' +
'WHERE ad_group_criterion.status = "ENABLED" ' +
' AND ad_group.status = "ENABLED" ' +
' AND campaign.status = "ENABLED"',
function (r) { return r.adGroupCriterion ? r.adGroupCriterion.finalUrls : null; },
urls
);
var list = Object.keys(urls);
Logger.log(list.length + ' unique final URLs found.');
var bad = [];
var checked = 0;
for (var i = 0; i < list.length && checked < MAX_URLS; i++) {
var url = list[i];
checked++;
var result = check(url);
if (result) { bad.push(result); }
}
if (bad.length === 0) {
Logger.log('All ' + checked + ' URLs returned 200 on the same host.');
return;
}
var lines = ['url\tstatus\tnote'];
for (var k = 0; k < bad.length; k++) {
lines.push(bad[k].url + '\t' + bad[k].status + '\t' + bad[k].note);
}
var body = AdsApp.currentAccount().getName() + '\n' +
checked + ' URLs checked, ' + bad.length + ' need attention.\n\n' +
lines.join('\n');
Logger.log(body);
if (EMAIL) {
MailApp.sendEmail(EMAIL, 'Final URL check - ' + AdsApp.currentAccount().getName(), body);
}
}
function collect(query, extract, into) {
var rows = AdsApp.search(query);
while (rows.hasNext()) {
var final = extract(rows.next());
if (!final || !final.length) { continue; }
for (var i = 0; i < final.length; i++) {
var u = String(final[i]).split('#')[0];
if (u) { into[u] = true; }
}
}
}
function check(url) {
try {
var res = UrlFetchApp.fetch(url, {
muteHttpExceptions: true,
followRedirects: true,
validateHttpsCertificates: true,
headers: { 'User-Agent': USER_AGENT }
});
var status = res.getResponseCode();
if (status !== 200) {
return { url: url, status: status, note: 'non-200 response' };
}
var landed = res.getAllHeaders()['Location'];
if (landed && host(String(landed)) !== host(url)) {
return { url: url, status: status, note: 'redirected to ' + host(String(landed)) };
}
return null;
} catch (e) {
return { url: url, status: 'error', note: String(e).substring(0, 120) };
}
}
function host(url) {
var m = url.match(/^https?:\/\/([^\/\?]+)/i);
return m ? m[1].toLowerCase().replace(/^www\./, '') : '';
}