谷歌应用脚本遍历电子表格非常缓慢

9

第一次发帖,长期阅读 :)

我刚刚编写了我的第一个谷歌应用脚本,用于汇总来自14个电子表格的信息,每个表格具有2-30个工作表,并将其放入单个报告电子表格中。

脚本运行得很好,它检查单个列是否有数据,如果找到,则获取电子表格名称、工作表名称、该行的第一列数据以及检查列中的数据,并将其作为数据的子数组添加到数组中。

然后,它计算子数组的面积并将数据写入报告文件(即脚本运行的地方)。

我唯一的问题是脚本运行大约需要2分钟。

我想知道自己的方法是否低效,并希望有人能够审查脚本并让我知道是否犯了一些错误?

以下是脚本:

/**
* Retrieves all the rows in the active spreadsheet that contain data and logs the
* values for each row.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/

function getFaults() {
/** opens each spreadsheet for Liddon and examines the "Report/Replace" column "F"
if there is data there then  get the 
[Sheetname], [fault area (column "A" row relative to the "F" field found)] and the ["F" field  data]
 **/
var reportsheet = SpreadsheetApp.getActiveSheet();
var reportdata = []
var reportrow = 0

var liddonblocks = [ 
"1APshQevK7iZxhP7--zmtuM3K6dPTgTZjmNarQ6CEsV4", "1riCQMOa38jo4nCD4qjW1BFZKk5xpXFZiCXHzXpiYKIU",   "1NTKXmted1-U12MiqvCGRuYBdhPy1_eLiPn7v8_oVKFE", "1RKOJUNNi5TAg5dETZDtLjZOkUSheuguzmtdPelMclMI",
"1b5-fzCp0wzW8llpUc_6xi1iTFzsapZh9ASSFgDYt4WU", "1qJtY37K0zwoJcz7LdyHhWgkypRMP9LabBchNLM4Fgow",   "1yvf4W8-SkfTH-n-PdDNQeyEDEz-shzTe-Id57S_YB2M", "1ETZc1xeNGXU6ipb1XQiD8SiIyRXzZtiJfS4AClKroJk",
"1tJ5u3Hv0uz-n2cdw-QYixKnuMG9skvrUbz1UROhIm34", "1DjhmIdD0GrPxR-fv7pCPkIwIyfai5BHsK9GhT-Hcs3k", "15w39NZZIacD1OfiTWG1E3HmOhV0B_e2Jsuan_ySwf2Q" , "1cK2HBLEftYOZEkCcxs1TX1PxcJRiKTZpQrcsOfE4B1s",
"16W_bfMKk98wkLpEmm2Q68Ta_SrCA8EBarQyGF2yfm18","1_Z_tgF5UAfq3fxPsDEe40z2GZSehhL-u4hEuVszrbn8" ]

// loop through the spreadsheets
for (block = 0; block < liddonblocks.length; block++) { 
  //open the spreadsheet using the index from the liddonblocks list
  var ss = SpreadsheetApp.openById(liddonblocks[block]);
  //get all of the sheets within the spreadsheet
  var sheets = ss.getSheets();

//loop through each sheet in each spreadsheet using the length of the number of sheets in the     spreadsheet as the index
for (var sheetnum = 0; sheetnum < sheets.length; sheetnum++) {
  //get an array of all data in the sheet
  //assigns array in the form of: [[area, fault], [Bedroom, Broken Bed], [Bathroom, ]] 
  //where each sub-array is a row of data starting at row 1 eg: [[row1-col1, row1-col2...],[row2-col1, row2-col2...]...]
  data = sheets[sheetnum].getDataRange().getValues();
  //get the text name of the sheet
  name = sheets[sheetnum].getSheetName();

  // iterate over the data set and look for values in the  5th column, starting at row 7 to exclude the headers.
  // this is the column named "Report / Replace "
  for (var count = 7; count < data.length; count++) {
    if (data[count][5] != "" && data[count][5] != 0) {
      //if there is data in the 5th column of the row then append the following data to the reportdata array and a sub-array
      // [ sheetname, columnA, columnF ]
      reportdata[reportrow] = [ ss.getName(), name, data[count][0], data[count][5]]
      //increment the reportcount variable so any further hits on data in column 5 are created as sequentail sub-arrays in the reportdata array.
      reportrow++
    }
  }
}
}
//write the contents of reportdata to the console
var range = reportsheet.getRange(2,1,reportrow,reportdata[0].length);
range.setValues(reportdata);
}

/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the readRows() function specified above.
* The onOpen() function, when defined, is automatically invoked whenever the
* spreadsheet is opened.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function onOpen() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var entries = [{
  name : "Update Report",
  functionName : "getFaults"
 }];
 spreadsheet.addMenu("Keble Scripts", entries);
};

4
依我之见,这段代码已经被优化了……无论你做什么,Google-Apps-Script 的速度都不是最快的。顺便说一句,考虑到表格数量,2分钟是合理的时间。 - Serge insas
3
苛刻一些:if (data[count][5] != "" && data[count][5] != 0)可以替换成if (data[count][5]),因为 ""0 都是“falsy”的(参见ECMAScript Language Spec,第9.2节)。这将减少每个循环中的一个比较,并覆盖 null 值...但与GAS服务调用相比不太明显。 - Mogsdad
谢谢Mogsdad - 有时我会在布尔值中迷失,我会修整一下的! :) - Random Chappy
将来,如果您想发“这段代码能用,但是您能帮我改进一下吗?”的问题,请发布到http://codereview.stackexchange.com/。 - Phrogz
2个回答

13

首先,请查看此页面上的信息。

我遇到的三个会严重减慢速度的问题是:

  1. 多次调用外部服务
  2. 在循环中调用SpreadsheetApp.flush()来强制更新工作表。
  3. 大量调用Range.getValue()

对于第一部分,尝试批处理调用。例如,如果您要请求本地货币和外国货币的汇率,请尝试在一个请求中发送一组查询。

对于第二部分,除非确实需要,否则不要调用此方法。

对于第三部分,请修改代码,以便可以调用此方法一次并使用其结果移动,而不是为同一结果在多个方法中调用它。

提示:为了避免修改许多方法参数,请在方法树调用的开头传递单个对象并填充其参数。这样,您就不必修改每个方法来添加\删除参数。这是所有编程语言的函数都应用的概念。


谢谢Muhammad,非常有用的技巧! - Random Chappy
此外,在循环中避免执行.getCell(i, j).getValue().getCell(i, j).setValue()。相反,获取循环外的所有范围值range.getValues()并在矩阵上进行迭代,因此,在循环之后,您可以使用修改后的矩阵值设置范围值(range.setValues(values))。我认为这与Google存储版本历史记录的事实有关... - Alexander Haroldo da Rocha

6

我同意Serge的评论,这段代码已经被优化得很好了,打开那么多电子表格需要一些时间。

我看到一个机会可以改进,但如果有任何影响,它可能对速度产生非常微小的影响。你可以将ss.getName()调用移出内部循环,而是在打开电子表格后将其分配给一个变量,然后在最内层循环中引用该变量。

请注意,根据我的经验,Google服务调用的速度往往有很大的差异,因此有时可能运行更快或更慢。您可以通过在脚本编辑器的“视图”菜单下查看执行记录来查看每个调用花费的时间。


我在想将getName调用移出数据值循环,这实际上会产生显著的差异。 - AdamL
1
你可能是对的AdamL,我的措辞可能有点过度保守。我不确定Google是否会在幕后缓存函数调用。后续对ss.getName()的调用可能比第一次更快,这就是为什么我不想假设这个改变会有很大的影响,但它确实可能会有。 - Cameron Roberts
谢谢Cameron,我会进行测试并报告结果 - 我不知道有执行记录,所以会记录下差异! - Random Chappy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接