解析对象数组时保存 Google 应用脚本状态,稍后从中断处继续

Save Google App Script state while parsing an object array and continue where left off later on

我正在使用这个简单的 google 应用程序脚本来解析所有可用的 Google 站点并转储各个页面的 html 内容。有很多页所以脚本最终会 运行 进入 6 分钟的时间限制。

是否有可能以某种方式使用 PropertiesService 来保存当前进度(尤其是在数组循环中)并在稍后停止的地方继续?

    var sites = SitesApp.getAllSites("somedomain.com");
    var exportFolder = DriveApp.getFolderById("a4342asd1242424folderid-");
            
            // Cycle through all sites
        for (var i in sites){
              var SiteName = sites[i].getName();
              var pages = sites[i].getAllDescendants();
              // Create folder in Drive for each site name
              var siteFolder = exportFolder.createFolder(SiteName)
        
        for (var p in pages){
                // Get page name and url
                var PageUrl = pages[p].getUrl();
             
               
                //Dump the raw html content in the text file
                var htmlDump = pages[p].getHtmlContent();
                siteFolder.createFile(PageUrl+".html", htmlDump)
                
              }
        }

我可以想象如何使用属性服务将当前行号存储在电子表格中,并从中断的地方继续。但是,如何使用包含 Sites 或 Pages?

等对象的数组来完成此操作?

如果您能够在 6 分钟内处理 1 个网站的所有页面,那么您可以尝试先将网站名称保存在 sheet 或 props 中,具体取决于数量。并继续处理每个 运行 的 n 个站点。也可以尝试 SitesApp.getAllSites(domain, start, max) 并在递增后将起始值保存在 props 中。

如果您不能在 6 分钟内处理它们,可以对页面执行类似的操作。 SitesApp.getAllDescendants(options)

将对象与属性服务一起使用

根据 quotas the maximum size of something you can store in the properties service is 9kb. With a total of 500kb. So if your object is less than this size, it should be no problem. That said, you will need to convert the object to a string with JSON.stringify() and when you retrieve it, use JSON.parse.

在 运行 时间限制附近工作

解决限制的常用方法是围绕属性服务和触发器构建一个进程。本质上你让脚本跟踪时间,如果它开始需要很长时间,你让它保存它的位置然后创建一个触发器以便脚本 运行s 在 10 秒(或多长时间)后再次出现你想要的),例如:

function mainJob(x) {
  
  let timeStart = new Date()
  console.log("Starting at ", timeStart)
  
  for (let i = x; i < 500000000; i++){ // NOTE THE i = x
    
    // MAIN JOB INSTRUCTIONS
    let j = i
    // ...
  
    // Check Time
    let timeCheck = new Date()
    if (timeCheck.getTime() - timeStart.getTime() > 30000) {
      console.log("Time limit reached, i = ", i)
      
      // Store iteration number
      PropertiesService
          .getScriptProperties()
          .setProperty('PROGRESS', i)
      
      console.log("stored value of i")
      
      // Create trigger to run in 10 seconds.
      ScriptApp.newTrigger("jobContinue")
          .timeBased()
          .after(10000)
          .create()
      
      console.log("Trigger created for 10 seconds from now")
      return 0
    }
  }
  
  // Reset progress counter
  PropertiesService
          .getScriptProperties()
          .setProperty('PROGRESS', 0)
  
  console.log("job complete")
}

function jobContinue() {
  
  console.log("Restarting job")
  
  previousTrigger = ScriptApp.getProjectTriggers()[0]
  ScriptApp.deleteTrigger(previousTrigger)
  console.log("Previous trigger deleted")
  
  triggersRemain = ScriptApp.getProjectTriggers()
  console.log("project triggers", triggersRemain)
  
  let progress = PropertiesService
                   .getScriptProperties()
                   .getProperty('PROGRESS')
  
  console.log("about to start main job again at i = ", progress)
  
  mainJob(progress)
  
}

function startJob() {
  mainJob(0)
}

说明

  • 这个脚本只有一个 for 循环,有 5 亿次迭代,其中它将 i 分配给 j,这只是一个可能超过 运行 的长作业的示例] 限时.
  • 脚本通过调用函数 startJob 启动,该函数调用 mainJob(0).
  • mainJob之内
    • 它首先创建一个 Date 对象来获取 mainJob 的开始时间。
    • 它采用参数 0 并使用它来将 for 循环初始化为 0,就像通常初始化 for 循环一样。
    • 在每次迭代结束时,它都会创建一个新的 Date 对象来与 mainJob 开始时创建的对象进行比较。在示例中,它设置为查看脚本是否已 运行ning 30 秒,这显然可以延长但保持在限制以下。
    • 如果超过 30 秒,它会将 i 的值存储在属性服务中,然后在 10 秒内创建到 运行 jobContinue 的触发器。
  • 10 秒后,函数 jobContinuei 的值调用属性服务,并使用从属性服务返回的值调用 mainJob
  • jobContinue 还会删除刚刚创建的触发器以保持干净。
  • 这个脚本在新项目中应该运行原样,试试吧!当我 运行 它时,它需要大约 80 秒,所以它第一次 运行s,创建一个触发器,再次 运行s,创建一个触发器,再次 运行s然后最终完成 for 循环。

参考资料