【问题标题】:Google Apps Script - Optimizing File Creation/ModificationGoogle Apps 脚本 - 优化文件创建/修改
【发布时间】:2021-06-09 06:37:02
【问题描述】:

我在此处查看了 Google 提供的以下有关如何优化现有 Google 脚本的文档: https://developers.google.com/apps-script/guides/support/best-practices

特别是,“使用批处理操作”部分似乎更适合我的用例,其中最佳策略是将所有读取“批处理”到一个操作中,然后在单独的操作中写入;不要在读写调用之间循环。

这是一个效率低下的代码示例,如上面的 url 所示:

  // DO NOT USE THIS CODE. It is an example of SLOW, INEFFICIENT code.
  // FOR DEMONSTRATION ONLY
  var cell = sheet.getRange('a1');
  for (var y = 0; y < 100; y++) {
    xcoord = xmin;
    for (var x = 0; x < 100; x++) {
      var c = getColorFromCoordinates(xcoord, ycoord);
      cell.offset(y, x).setBackgroundColor(c);
      xcoord += xincrement;
    }
    ycoord -= yincrement;
    SpreadsheetApp.flush();
  }

这是一个高效和改进的代码示例:

  // OKAY TO USE THIS EXAMPLE or code based on it.
  var cell = sheet.getRange('a1');
  var colors = new Array(100);
  for (var y = 0; y < 100; y++) {
    xcoord = xmin;
    colors[y] = new Array(100);
    for (var x = 0; x < 100; x++) {
      colors[y][x] = getColorFromCoordinates(xcoord, ycoord);
      xcoord += xincrement;
    }
    ycoord -= yincrement;
  }
  sheet.getRange(1, 1, 100, 100).setBackgroundColors(colors);

现在,对于我的特定用例:
我不想将值存储在数组中,然后将它们写入/修改它们作为与将它们读入数组的单独操作,而是创建多个 Google 文档来替换每个文档中的占位符。

对于上下文:
我正在编写一个脚本,该脚本读取学生电子表格,其中包含要为每个学生修改的文件,稍后将作为邮件合并发送。例如,有 3 个主文件。每个学生将拥有 3 个主文件的副本,用于.replaceText 占位符字段。

下面是我相关的sn-ps代码:

function filesAndEmails() {
  // Import the Spreadsheet application library.
  const UI = SpreadsheetApp.getUi();

  // Try calling the functions below; catch any error messages that occur to display as alert window.
  try {
    // Prompt and record user's email draft template.
    // var emailLinkID = connectDocument(
    //   UI, 
    //   title="Step 1/2: Google Document (Email Draft) Connection", 
    //   dialog=`What email draft template are you referring to? 
    //   This file should contain the subject line, name and body.
      
    //   Copy and paste the direct URL link to the Google Docs:`,
    //   isFile=true
    // );

    // TEST
    var emailLinkID = "REMOVED FOR PRIVACY";


    if (emailLinkID != -1) {
      // Prompt and record user's desired folder location to store generated files.
      // var fldrID = connectDocument(
      //   UI, 
      //   title="Step 2/2: Google Folder (Storage) Connection", 
      //   dialog=`Which folder would you like all the generated file(s) to be stored at?
        
      //   Copy and paste the direct URL link to the Google folder:`,
      //   isFile=false
      // );

      // TEST
      var fldrID = DriveApp.getFolderById("REMOVED FOR PRIVACY");

      // Retrieve data set from database.
      var sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_1);
      // Range of data must include header row for proper key mapping.
      var arrayOfStudentObj = objectify(sheet.getRange(3, 1, sheet.getLastRow()-2, 11).getValues());
      // Establish array of attachment objects for filename and file url.
      var arrayOfAttachObj = getAttachments();

      // Opportunities for optimization begins here.
      // Iterate through array of student Objects to extract each mapped key values for Google document insertion and emailing.
      // Time Complexity: O(n^3)
      arrayOfStudentObj.forEach(function(student) {
        if (student[EMAIL_SENT_COL] == '') {
          try {
            arrayOfAttachObj.forEach(function(attachment) {
              // All generated files will contain this filename format, followed by the attachment filename/description.
              var filename = `${student[RYE_ID_COL]} ${student[FNAME_COL]} ${student[LNAME_COL]} ${attachment[ATTACH_FILENAME_COL]}`;

              // Create a copy of the current iteration/file for the given student.
              var file = DocumentApp.openById(DriveApp.getFileById(getID(attachment[ATTACH_FILEURL_COL], isFile=false)).makeCopy(filename, fldrID).getId())
              
              // Replace and save all custom fields for the given student at this current iteration/file.
              replaceCustomFields(file, student);

            });

          } catch(e) {

          }
        }
      });
      UI.alert("Script successfully completed!");
    };
  } catch(e) {
    UI.alert("Error Detected", e.message + "\n\nContact a developer for help.", UI.ButtonSet.OK);
  };
}

/**
 * Replaces all fields specified by 'attributesArray' given student's file.  
 * @param   {Object}  file    A single file object used to replace all custom fields with.
 * @param   {Object}  student A single student object that contains all custom field attributes.
 */
function replaceCustomFields(file, student) {
  // Iterate through each student's attribute (first name, last name, etc.) to change each field.
  attributesArray.forEach(function(attribute) {
    file.getBody()
      .replaceText(attribute, student[attribute]);
  });

  // Must save and close file to finalize changes prior to moving onto next student object.
  file.saveAndClose();
}

/**
 * Processes the attachments sheet for filename and file ID.
 * @return  {Array}           An array of attachment file objects.
 */
function getAttachments() {
  var files = SpreadsheetApp.getActive().getSheetByName(SHEET_2);
  return objectify(files.getRange(1, 1, files.getLastRow(), 2).getValues());
}

/**
 * Creates student objects to contain the object attributes for each student based on 
 * the header row.
 * @param   {Array}   array   A 2D heterogeneous array includes the header row for attribute key mapping.
 * @return  {Array}           An array of student objects.
 */
function objectify(array) {
  var keys = array.shift();
  var objects = array.map(function (values) {
      return keys.reduce(function (o, k, i) {
          o[k] = values[i];
          return o;
      }, {});
  });
  return objects;
}

总结一下我的代码,我将学生的 Google 电子表格读取为对象数组,因此每个学生都有他们的名字、姓氏、电子邮件等属性。我对文件附件做了同样的事情包括每个学生。目前,forEach 循环遍历每个学生对象,创建主文件的副本,替换每个文件中的占位符文本,然后将它们保存在文件夹中。最终,我将使用MailApp 将这些文件发送给每个学生。但是,由于通过为每个学生创建文件副本进行重复的外部调用,执行时间非常慢是可以理解的......

TLDR
当我的用例需要多个 DriveApp 调用来创建文件的所述副本以进行修改时,是否仍然可以使用“批处理操作”优化我的代码?与将原始值读入数组并在以后的操作中修改它们相反,我认为我不能简单地将文档对象存储到数组中,然后在稍后阶段修改它们。想法?

【问题讨论】:

    标签: arrays google-apps-script google-sheets optimization mailmerge


    【解决方案1】:

    您可以使用batchUpdateGoogle Docs API

    请参阅Tanaike's answer 以了解请求对象的外观。

    您现在需要在您的 Apps 脚本中做的就是为多个文件构建对象。

    注意:

    • 您还可以通过更新进一步优化您的代码:
    var arrayOfStudentObj = objectify(sheet.getRange(3, 1, sheet.getLastRow()-2, 11).getValues();
    

    进入:

    // what column your email confirmation is which is 0-index
    // assuming column K contains the email confirmation (11 - 1 = 10)
    var emailSentColumn = 10;
    // filter data, don't include rows with blank values in column K
    var arrayOfStudentObj = objectify(sheet.getRange(3, 1, sheet.getLastRow()-2, 11).getValues().filter(row=>row[emailSentColumn]));
    
    • 这样,您可以删除下面的条件if (student[EMAIL_SENT_COL] == '') { 并减少循环次数。

    资源:

    【讨论】:

    • 感谢您的宝贵建议!对于我的特定用例/代码,奇怪的是,过滤器提示的执行速度比我当前使用if (student[EMAIL_SENT_COL] == '') 设置的要慢。但是,我同意提供数组的“清理”版本将删除条件。对于 batchUpdate,我不理解“为多个文件构建对象”的目标——你能扩展一下吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-22
    相关资源
    最近更新 更多