AdWord 脚本导出到 BigQuery“空响应”
Posted
技术标签:
【中文标题】AdWord 脚本导出到 BigQuery“空响应”【英文标题】:AdWord Script Export to BigQuery "Empty Response" 【发布时间】:2016-04-22 04:16:55 【问题描述】:使用以下 AdWords 脚本导出到 BigQuery,BigQuery.Jobs.insert 导致脚本因“空响应”而终止。呼叫没有得到响应的任何原因?
var ACCOUNTS = ['xxx','xxx'];
var CONFIG =
BIGQUERY_PROJECT_ID: 'xxx',
BIGQUERY_DATASET_ID: 'xxx',
// Truncate existing data, otherwise will append.
TRUNCATE_EXISTING_DATASET: true,
TRUNCATE_EXISTING_TABLES: true,
// Back up reports to Google Drive.
WRITE_DATA_TO_DRIVE: false,
// Folder to put all the intermediate files.
DRIVE_FOLDER: 'Adwords Big Query Test',
// Default date range over which statistics fields are retrieved.
DEFAULT_DATE_RANGE: '20140101,20140105',
// Lists of reports and fields to retrieve from AdWords.
REPORTS: [NAME: 'KEYWORDS_PERFORMANCE_REPORT',
CONDITIONS: 'WHERE Impressions>0',
FIELDS: 'AccountDescriptiveName' : 'STRING',
'Date' : 'STRING',
'CampaignId' : 'STRING',
'CampaignName' : 'STRING',
'AdGroupId' : 'STRING',
'AdGroupName' : 'STRING',
'Id' : 'STRING',
'Criteria' : 'STRING',
'KeywordMatchType' : 'STRING',
'AdNetworkType1' : 'STRING',
'AdNetworkType2' : 'STRING',
'Device' : 'STRING',
'AveragePosition' : 'STRING',
'QualityScore' : 'STRING',
'CpcBid' : 'STRING',
'TopOfPageCpc' : 'STRING',
'Impressions' : 'STRING',
'Clicks' : 'STRING',
'ConvertedClicks' : 'STRING',
'Cost' : 'STRING',
'Conversions' : 'STRING'
],
RECIPIENT_EMAILS: [
'xxx',
]
;
function main()
createDataset();
for (var i = 0; i < CONFIG.REPORTS.length; i++)
var reportConfig = CONFIG.REPORTS[i];
createTable(reportConfig);
folder = getDriveFolder();
// Get an account iterator.
var accountIterator = MccApp.accounts().withIds(ACCOUNTS).withLimit(10).get();
var jobIdMap = ;
while (accountIterator.hasNext())
// Get the current account.
var account = accountIterator.next();
// Select the child account.
MccApp.select(account);
// Run reports against child account.
var accountJobIds = processReports(folder, account.getCustomerId());
jobIdMap[account.getCustomerId()] = accountJobIds;
waitTillJobsComplete(jobIdMap);
sendEmail(jobIdMap);
function createDataset()
if (datasetExists())
if (CONFIG.TRUNCATE_EXISTING_DATASET)
BigQuery.Datasets.remove(CONFIG.BIGQUERY_PROJECT_ID,
CONFIG.BIGQUERY_DATASET_ID, 'deleteContents' : true);
Logger.log('Truncated dataset.');
else
Logger.log('Dataset %s already exists. Will not recreate.',
CONFIG.BIGQUERY_DATASET_ID);
return;
// Create new dataset.
var dataSet = BigQuery.newDataset();
dataSet.friendlyName = CONFIG.BIGQUERY_DATASET_ID;
dataSet.datasetReference = BigQuery.newDatasetReference();
dataSet.datasetReference.projectId = CONFIG.BIGQUERY_PROJECT_ID;
dataSet.datasetReference.datasetId = CONFIG.BIGQUERY_DATASET_ID;
dataSet = BigQuery.Datasets.insert(dataSet, CONFIG.BIGQUERY_PROJECT_ID);
Logger.log('Created dataset with id %s.', dataSet.id);
/**
* Checks if dataset already exists in project.
*
* @return boolean Returns true if dataset already exists.
*/
function datasetExists()
// Get a list of all datasets in project.
var datasets = BigQuery.Datasets.list(CONFIG.BIGQUERY_PROJECT_ID);
var datasetExists = false;
// Iterate through each dataset and check for an id match.
if (datasets.datasets != null)
for (var i = 0; i < datasets.datasets.length; i++)
var dataset = datasets.datasets[i];
if (dataset.datasetReference.datasetId == CONFIG.BIGQUERY_DATASET_ID)
datasetExists = true;
break;
return datasetExists;
function createTable(reportConfig)
if (tableExists(reportConfig.NAME))
if (CONFIG.TRUNCATE_EXISTING_TABLES)
BigQuery.Tables.remove(CONFIG.BIGQUERY_PROJECT_ID,
CONFIG.BIGQUERY_DATASET_ID, reportConfig.NAME);
Logger.log('Truncated dataset %s.', reportConfig.NAME);
else
Logger.log('Table %s already exists. Will not recreate.',
reportConfig.NAME);
return;
// Create new table.
var table = BigQuery.newTable();
var schema = BigQuery.newTableSchema();
var bigQueryFields = [];
// Add account column to table.
var accountFieldSchema = BigQuery.newTableFieldSchema();
accountFieldSchema.description = 'AccountId';
accountFieldSchema.name = 'AccountId';
accountFieldSchema.type = 'STRING';
bigQueryFields.push(accountFieldSchema);
// Add each field to table schema.
var fieldNames = Object.keys(reportConfig.FIELDS);
for (var i = 0; i < fieldNames.length; i++)
var fieldName = fieldNames[i];
var bigQueryFieldSchema = BigQuery.newTableFieldSchema();
bigQueryFieldSchema.description = fieldName;
bigQueryFieldSchema.name = fieldName;
bigQueryFieldSchema.type = reportConfig.FIELDS[fieldName];
bigQueryFields.push(bigQueryFieldSchema);
schema.fields = bigQueryFields;
table.schema = schema;
table.friendlyName = reportConfig.NAME;
table.tableReference = BigQuery.newTableReference();
table.tableReference.datasetId = CONFIG.BIGQUERY_DATASET_ID;
table.tableReference.projectId = CONFIG.BIGQUERY_PROJECT_ID;
table.tableReference.tableId = reportConfig.NAME;
table = BigQuery.Tables.insert(table, CONFIG.BIGQUERY_PROJECT_ID,
CONFIG.BIGQUERY_DATASET_ID);
Logger.log('Created table with id %s.', table.id);
function tableExists(tableId)
// Get a list of all tables in the dataset.
var tables = BigQuery.Tables.list(CONFIG.BIGQUERY_PROJECT_ID,
CONFIG.BIGQUERY_DATASET_ID);
var tableExists = false;
// Iterate through each table and check for an id match.
if (tables.tables != null)
for (var i = 0; i < tables.tables.length; i++)
var table = tables.tables[i];
if (table.tableReference.tableId == tableId)
tableExists = true;
break;
return tableExists;
function processReports(folder, accountId)
var jobIds = [];
// Iterate over each report type.
for (var i = 0; i < CONFIG.REPORTS.length; i++)
var reportConfig = CONFIG.REPORTS[i];
Logger.log('Running report %s for account %s', reportConfig.NAME,
accountId);
// Get data as csv
var csvData = retrieveAdwordsReport(reportConfig, accountId);
// If configured, back up data.
if (CONFIG.WRITE_DATA_TO_DRIVE)
var fileName = reportConfig.NAME + '_' + accountId;
folder.createFile(fileName, csvData, MimeType.CSV);
Logger.log('Exported data to Drive folder ' +
CONFIG.DRIVE_FOLDER + ' for report ' + fileName);
// Convert to Blob format.
var blobData = Utilities.newBlob(csvData, 'application/octet-stream');
// Load data
var jobId = loadDataToBigquery(reportConfig, blobData);
jobIds.push(jobId);
return jobIds;
function retrieveAdwordsReport(reportConfig, accountId)
var fieldNames = Object.keys(reportConfig.FIELDS);
var report = AdWordsApp.report(
'SELECT ' + fieldNames.join(',') +
' FROM ' + reportConfig.NAME + ' ' + reportConfig.CONDITIONS +
' DURING ' + CONFIG.DEFAULT_DATE_RANGE);
var rows = report.rows();
var csvRows = [];
// Header row
csvRows.push('AccountId,'+fieldNames.join(','));
// Iterate over each row.
while (rows.hasNext())
var row = rows.next();
var csvRow = [];
csvRow.push(accountId);
for (var i = 0; i < fieldNames.length; i++)
var fieldName = fieldNames[i];
var fieldValue = row[fieldName].toString();
var fieldType = reportConfig.FIELDS[fieldName];
/* Strip off % and perform any other formatting here.
if ((fieldType == 'FLOAT' || fieldType == 'INTEGER') &&
fieldValue.charAt(fieldValue.length - 1) == '%')
fieldValue = fieldValue.substring(0, fieldValue.length - 1);
*/
// Add double quotes to any string values.
if (fieldType == 'STRING')
fieldValue = fieldValue.replace(',', ''); //Handle fields with comma in value returned
fieldValue = fieldValue.replace('"', ''); //Handle fields with double quotes in value returned
fieldValue = fieldValue.replace('+', ''); //Handle fields with "+" in value returned
fieldValue = '"' + fieldValue + '"';
csvRow.push(fieldValue);
csvRows.push(csvRow.join(','));
Logger.log('Downloaded ' + reportConfig.NAME + ' for account ' + accountId +
' with ' + csvRows.length + ' rows.');
return csvRows.join('\n');
function getDriveFolder()
var folders = DriveApp.getFoldersByName(CONFIG.DRIVE_FOLDER);
// Assume first folder is the correct one.
if (folders.hasNext())
Logger.log('Folder name found. Using existing folder.');
return folders.next();
return DriveApp.createFolder(CONFIG.DRIVE_FOLDER);
function loadDataToBigquery(reportConfig, data)
function guid()
function s4()
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
return s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4();
var makeId = guid();
var job =
jobReference:
jobId: makeId
,
configuration:
load:
destinationTable:
projectId: CONFIG.BIGQUERY_PROJECT_ID,
datasetId: CONFIG.BIGQUERY_DATASET_ID,
tableId: reportConfig.NAME
,
skipLeadingRows: 1,
ignoreUnknownValues: true,
allowJaggedRows: true,
allowLargeResults: true
;
var insertJob = BigQuery.Jobs.insert(job, CONFIG.BIGQUERY_PROJECT_ID, data);
Logger.log('Load job started for %s. Check on the status of it here: ' +
'https://bigquery.cloud.google.com/jobs/%s', reportConfig.NAME,
CONFIG.BIGQUERY_PROJECT_ID);
return job.jobReference.jobId;
function waitTillJobsComplete(jobIdMap)
var complete = false;
var remainingJobs = [];
var accountIds = Object.keys(jobIdMap);
for (var i = 0; i < accountIds.length; i++)
var accountJobIds = jobIdMap[accountIds[i]];
remainingJobs.push.apply(remainingJobs, accountJobIds);
while (!complete)
if (AdWordsApp.getExecutionInfo().getRemainingTime() < 5)
Logger.log('Script is about to timeout, jobs ' + remainingJobs.join(',') +
' are still incomplete.');
remainingJobs = getIncompleteJobs(remainingJobs);
if (remainingJobs.length == 0)
complete = true;
if (!complete)
Logger.log(remainingJobs.length + ' jobs still being processed.');
// Wait 5 seconds before checking status again.
Utilities.sleep(5000);
Logger.log('All jobs processed.');
function getIncompleteJobs(jobIds)
var remainingJobIds = [];
for (var i = 0; i < jobIds.length; i++)
var jobId = jobIds[i];
var getJob = BigQuery.Jobs.get(CONFIG.BIGQUERY_PROJECT_ID, jobId);
if (getJob.status.state != 'DONE')
remainingJobIds.push(jobId);
return remainingJobIds;
似乎正在引发“空响应”错误:
var insertJob = BigQuery.Jobs.insert(job, CONFIG.BIGQUERY_PROJECT_ID, data);
已经尝试了很多调整,但答案对我来说似乎并不明显。感谢您的帮助!
【问题讨论】:
在我看来,BigQuery.Jobs.insert 上没有错误!你为什么这么认为?你到底遇到了什么错误? 有两个原因:-如果您关注日志,它将是最后一次成功日志条目之后的下一个函数-它专门调用该行:创建ID为xxx的数据集。创建了 id 为 xxx 的表。找到文件夹名称。使用现有文件夹。为帐户 xxx 运行报告 KEYWORDS_PERFORMANCE_REPORT 为帐户 xxx 下载了 KEYWORDS_PERFORMANCE_REPORT,共 52284 行。将数据导出到 Drive 文件夹 Adwords Big Query Test for report KEYWORDS_PERFORMANCE_REPORT_xxx 空响应(第 396 行) 第 396 行是:var insertJob = BigQuery.Jobs.insert(job, CONFIG.BIGQUERY_PROJECT_ID, data); 为了安全起见 - 你可以尝试将 projectId 添加到 job 的 jobReference - jobReference: projectId: CONFIG.BIGQUERY_PROJECT_ID, jobId: makeId, 刚刚试了一下,得到了同样的错误信息。 有另一个建议,但它太大而无法评论,所以我把它作为答案 - 即使在空中射击 :o( 【参考方案1】:我可能是错的,但是 - 我认为 jobId 的问题是由于 guid() 函数的问题 - 缺少“+”号。
function guid()
function s4()
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
return s4() + s4() + s4() + s4() + s4() s4() + s4() + s4();
为什么不使用 Response 中的 jobId,如下所示?
var job =
configuration:
load:
destinationTable:
projectId: CONFIG.BIGQUERY_PROJECT_ID,
datasetId: CONFIG.BIGQUERY_DATASET_ID,
tableId: reportConfig.NAME
,
skipLeadingRows: 1,
ignoreUnknownValues: true,
allowJaggedRows: true,
allowLargeResults: true
;
var insertJob = BigQuery.Jobs.insert(job, CONFIG.BIGQUERY_PROJECT_ID, data);
Logger.log('Load job started for %s. Check on the status of it here: ' +
'https://bigquery.cloud.google.com/jobs/%s', reportConfig.NAME,
CONFIG.BIGQUERY_PROJECT_ID);
return insertJob.jobReference.jobId;
添加
在这种情况下,我建议记录 jobId (makeId = guid()) 并通过以下链接获取工作状态https://cloud.google.com/bigquery/docs/reference/v2/jobs/get#try-it
输入 ProjectId 和 JobId,您至少会看到您的工作发生了什么!
【讨论】:
我已经发现 + 号错误并更正了它,但仍然收到错误。此外,我使用的初始方法是按照您显示的那样检索 jobId,但我收到了该错误,因此我尝试使用此方法作为故障排除步骤,但它也不起作用。 我很想检查作业状态,但它甚至没有触及 BigQuery 项目,这就是为什么我真的相信特定插入函数上发生错误的原因。似乎是因为该操作未执行而得到“空响应”,但我无法弄清楚是什么原因造成的。 明白,这就是为什么我建议将 projectId 明确添加到 jobReference 的原因。 作为下一个(假设 CONFIG.BIGQUERY_PROJECT_ID 设置正确) - 我将在 var jobId = loadDataToBigquery(reportConfig, blobData) 中跟踪 blobData【参考方案2】:AdWords 会为空值添加一个“--”。如果您将报告字段定义为字符串以外的任何内容(例如,浮点数、整数等),则插入将失败,因为它无法将破折号转换为浮点数或整数。
尝试将所有字段设置为字符串,看看是否能解决问题。
【讨论】:
【参考方案3】:您是否尝试将 WRITE_DATA_TO_DRIVE 参数设置为 true 以确认报告导出成功?结果有多大?尝试插入大于 10MB(约 25k 行,取决于列)时,我得到了同样的错误。如果导出到 Google Drive 的文件看起来不错,您可以在retrieveAdwordsReport 中的 while 循环中添加条件以限制文件大小。 https://groups.google.com/forum/#!forum/adwords-scripts 上还有一个帖子提到了包含 AdNetworkType 列时的问题:https://groups.google.com/forum/#!searchin/adwords-scripts/adnetworktype2%7Csort:relevance/adwords-scripts/yK57JHCt3Cw/Cl1SjFaQBQAJ。
限制结果大小:
var processedRows = 0;
// Iterate over each row.
while (rows.hasNext() && ++processedRows < 5000)
var row = rows.next();
var csvRow = [];
csvRow.push(accountId);
if (processedRows % 1000 == 0)
Logger.log('Processed %s rows.',processedRows);
...
【讨论】:
恭喜您的第一个答案!您可以在此答案中添加代码示例吗?以上是关于AdWord 脚本导出到 BigQuery“空响应”的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 google app 脚本更新 adword-customizer 数据。 (以前上传的源导致错误)