对于任何不知道的人,他们最终都以Beta形式出现在这里。 AdWords脚本现已发布 在MCC级别可用。如果您想使用这些Beta功能,您所需要做的就是 在这里申请 然后等待Google小组为您提供访问权限。
那么,MCC级脚本有什么新功能?完整的细节在 Google Developers页面,但这是一个摘要。现在,您可以使用MccApp对象在“帐户”上启动选择器。仅当您在帐户级别运行统计信息时,它才与其他选择器相同。 在处理每个帐户时,一旦使用MccApp.select()设置了要使用的帐户,一切便会像以前一样工作。
您可能想利用的一个新功能是executeInParallel(),它允许您同时在多达50个帐户中执行相同的代码。因此,您可以启动报告脚本以在所有帐户中运行,然后收集结果并发送一封电子邮件或将其存储到单个电子表格中。此外,由于使用了30分钟的执行时间来在每个帐户上运行代码,并且需要30分钟的时间来收集来自回调函数的结果,因此使用此方法,脚本现在最多可以运行60分钟。
为了让您开始使用新的MccApp对象,我想我应该选择最受欢迎的一篇文章并将其重写以在MCC级别上运行。 在帐户中查找损坏的网址 很好地说明了如何利用新的executeInParallel()函数来改善对MCC的监视。
该脚本的工作原理与以前的脚本非常相似,但是具有一些附加功能。 该脚本每天检查一次您所有的关键字和广告网址。安装此脚本时,应该安排它每小时运行一次,以防万一在指定的时间内无法处理大量帐户,它可以从中断的地方继续处理。它使用标签在内部进行控制。
此外,每次运行时,此脚本的结果都存储在新的电子表格中。我遇到的最后一个问题是,在我有机会查看之前,脚本会覆盖电子表格中的值。这消除了该问题。 可通过如下所示的摘要电子邮件访问该电子表格,每行均包含指向该帐户结果的电子表格标签的链接。
![]() |
脚本中的电子邮件示例 |
另外,请快速注意一下,三月底结婚后,我又回到了前途,所以寻找更频繁的发布时间表。我在 捷克布尔诺营销节 在十月底,所以我期待与任何有能力的人见面。
谢谢,
拉斯
谢谢,
拉斯
/****************************************** * 使用MCC级别脚本监视断开的链接 * Version 1.5 * Changelog v1.5 * - Additional fixes from 复制 and paste 错误 * Changelog v1.4 * - Fixed INVALID_QUERY error * Changelog v1.3 * - Added previous version of report api to script until * I 更新 my 网址. * Changelog v1.2 * - Fixing INVALID_PREDICATE_ENUM_VALUE * Changelog v1.1 * - Stopped timeouts * Created By: 拉斯 Savage * FreeAdWordsScripts.com ******************************************/ var SCRIPT_NAME = 'Broken Url Checker'; var LOG_LEVEL = 'error'; //change this to debug if you want more logging var NOTIFY = ['']; var SPREADSHEET_PREFIX = 'Broken Url Details'; // A timestamp is appended var NOTIFY_ON_ERROR = [' ']; var STRIP_QUERY_STRING = true; //Drop everything after the ? in the url to speed things up var REPORT_ON_REDIRECTS = true; //If you want to be able to track 301s and 302, turn this on var VALID_RESPONSE_CODES = [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 网址 have been checked for today.'); return ''; } var 标签Iter = AdWordsApp.labels().withCondition("Name STARTS_WITH '"+DONE_LABEL_PREFIX+"'").get(); while(labelIter.hasNext()) { 标签Iter.next().remove(); } debug('Checking for previous 网址.'); var 网址AlreadyChecked = readValidUrlsFromJSON(); info('Found '+Object.keys(urlsAlreadyChecked).length+' 网址 already checked.'); var toReportKeywords = []; var toReportAds = []; var didExitEarly = false; var 关键词Urls = getKeywordUrls(); for(var key in 关键词Urls) { var kwRow = 关键词Urls[key]; var final_urls = kwRow.FinalUrls.split(';'); for(var i in final_urls) { var url = cleanUrl(final_urls[i]); verifyUrl(kwRow,url,urlsAlreadyChecked,toReportKeywords); if(shouldExitEarly()) { didExitEarly = true; break; } } } if(!didExitEarly) { var 广告 Urls = getAdUrls(); for(var i in 广告 Urls) { var 广告 Row = 广告 Urls[i]; if(adRow.CreativeFinalUrls) { var final_urls = 广告 Row.CreativeFinalUrls.split(';'); for(var x in final_urls) { var url = cleanUrl(final_urls[x]); 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 创造d by '+SCRIPT_NAME, '#C0C0C0'); writeValidUrlsToJSON({}); } return JSON.stringify(returnData); } catch(e) { // This error handling helps 通知 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('Out of 电子邮件 quota for the day. Sending a carrier pigeon.'); } 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'] = 网址AlreadyChecked[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 去掉 the value track parameters url = url.replace(/\{[^\}]*\}/g,''); } return url; } //Use the 报告 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 }; var cols = ['CampaignId','CampaignName', 'AdGroupId','AdGroupName', 'Id','Criteria','KeywordMatchType', 'IsNegative','FinalUrls','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.FinalUrls) { continue; } if(row.KeywordMatchType === 'Exact') { row.Criteria = ['[',row.Criteria,']'].join(''); } else if(row.Criteria === 'Phrase') { row.Criteria = ['"',row.Criteria,'"'].join(''); } var rowKey = [row.CampaignId,row.AdGroupId,row.Id].join('-'); results[rowKey] = row; } return results; } //Use the 报告 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 }; var cols = ['CampaignId','CampaignName', 'AdGroupId','AdGroupName', 'AdType', 'Id','Headline','Description1','Description2','DisplayUrl', 'CreativeFinalUrls','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.CreativeFinalUrls) { continue; } var rowKey = [row.CampaignId,row.AdGroupId,row.Id].join('-'); 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 兑换s 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 呼叫back function that collects all the data from the 剧本 //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 电子表格Name = 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 电子表格; 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 = 电子表格.getSheetByName(res.accountId); if(!sheet) { sheet = 电子表格.insertSheet(res.accountId, 电子表格.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.Criteria, row.FinalUrls]); } 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.CreativeFinalUrls]); } 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 电子表格 sheet. return (spreadsheet.getUrl() + '#gid=' + sheet.getSheetId()); } //This function builds the summary 电子邮件 and sends it to the people in //the NOTIFY list //This function builds the summary 电子邮件 and sends it to the people in //the NOTIFY list function sendSummaryEmail(summaryEmailData) { var subject = SCRIPT_NAME+' Summary Results'; var body = subject; var htmlBody = '<html><body>'+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 += '<table border="1" width="95%" style="border-collapse:collapse;">'; htmlBody += '<tr>'; htmlBody += '<td align="left"><b>Acct Id</b></td>'; htmlBody += '<td align="left"><b>Acct Name</b></td>'; htmlBody += '<td align="left"><b>Exited Early</b></td>'; htmlBody += '<td align="center"><b>Unique Urls Checked</b></td>'; htmlBody += '<td align="center"><b># Broken Keyword Urls</b></td>'; htmlBody += '<td align="center"><b># Broken Ad Urls</b></td>'; htmlBody += '<td align="center"><b>Full Report</b></td>'; htmlBody += '</tr>'; for(var i in summaryEmailData) { var row = summaryEmailData[i]; htmlBody += '<tr><td align="left">'+ row.accountId + '</td><td align="left">' + row.accountName + '</td><td align="left">' + row.didExitEarly + '</td><td align="center">' + row.uniqueUrlsChecked + '</td><td align="center">' + row.numBrokenKeywords + '</td><td align="center">' + row.numBrokenAds + '</td><td align="left"><a href="'+row.sheetUrl+'">' + 'Show Details' + '</a></td></tr>'; } htmlBody += '</table>'; 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 += '</body></html>'; 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 创造s a new file //if isSpreadsheet is set, it will 创造 a new 电子表格 //otherwise, it 创造s a text file. function getFile(fileName,isSpreadsheet) { var maxRetries = 0; var 错误 = []; 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 错误.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); }