// Documentation: https://adssystem.store/scripts/final-url-health-check/ // Writes: Nothing. Read-only. // 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\./, '') : ''; }