You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何规避Google Apps脚本Maximum Execution Time Limit并实现断点续爬?

Google Drive文件列表脚本超时断点续跑方案

问题背景

现有脚本用于遍历Google Drive指定文件夹下的所有文件和文件夹,但因目录体量过大,频繁触发Maximum Execution Time Limit错误,且每次报错后需从头开始遍历,效率极低。需要修改脚本实现断点续跑,从最后一条已记录的位置继续执行。

核心思路

要实现断点续跑,关键是记录并恢复遍历进度:

  • 仅首次运行初始化表头,后续运行保留已有记录,避免重复清空表格
  • 用脚本属性保存当前遍历到的文件夹ID、路径,以及文件分页标记(pageToken)
  • 改用迭代+分页API替代递归,既避免调用栈溢出,又方便保存进度
  • 提前检测超时风险,保存当前进度后退出,下次运行自动读取进度继续

修改后的完整代码

function onOpen() {
  var SS = SpreadsheetApp.getActiveSpreadsheet();
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('List Files/Folders')
    .addItem('开始/继续遍历', 'startOrResumeTraversal')
    .addItem('重置遍历进度', 'resetTraversal')
    .addToUi();
};

// 开始或继续遍历流程
function startOrResumeTraversal(){
  var folderId = Browser.inputBox('输入目标文件夹ID', Browser.Buttons.OK_CANCEL);
  if (folderId === "") {
    Browser.msgBox('文件夹ID无效');
    return;
  }
  
  var sheet = SpreadsheetApp.getActiveSheet();
  var props = PropertiesService.getScriptProperties();
  
  // 首次运行初始化表头和进度
  if (sheet.getLastRow() === 0) {
    sheet.appendRow(["Full Path", "Name","Type" ,"Date", "URL", "Last Updated", "Description", "Size","Owner Email"]);
    var rootFolder = DriveApp.getFolderById(folderId);
    props.setProperties({
      rootFolderId: folderId,
      currentFolderPath: rootFolder.getName(),
      currentFolderId: folderId,
      filePageToken: "",
      folderQueue: JSON.stringify([folderId]) // 待遍历文件夹队列
    });
  }
  
  // 读取已保存的进度
  var progress = props.getProperties();
  var folderQueue = JSON.parse(progress.folderQueue);
  var currentFolderId = progress.currentFolderId;
  var filePageToken = progress.filePageToken;
  var currentFolderPath = progress.currentFolderPath;
  
  try {
    // 先处理当前文件夹未完成的文件遍历
    if (filePageToken !== "") {
      filePageToken = processFilesInFolder(currentFolderId, currentFolderPath, sheet, filePageToken);
      // 如果文件还没处理完且时间快到了,保存进度退出
      if (filePageToken !== "" && isTimeAlmostUp()) {
        saveProgress(props, progress.rootFolderId, currentFolderPath, currentFolderId, filePageToken, folderQueue);
        Browser.msgBox("时间即将耗尽,已保存进度,下次点击继续即可");
        return;
      }
    }
    
    // 遍历待处理文件夹队列
    while (folderQueue.length > 0 && !isTimeAlmostUp()) {
      var targetFolderId = folderQueue.shift();
      var targetFolder = DriveApp.getFolderById(targetFolderId);
      var targetFolderPath = progress.rootFolderId === targetFolderId ? targetFolder.getName() : currentFolderPath + "/" + targetFolder.getName();
      
      // 记录当前文件夹信息
      var folderData = [
        targetFolderPath,
        targetFolder.getName(),
        "Folder",
        targetFolder.getDateCreated(),
        targetFolder.getUrl(),
        targetFolder.getLastUpdated(),
        targetFolder.getDescription(),
        targetFolder.getSize()/1024,
        targetFolder.getOwner().getEmail()
      ];
      sheet.appendRow(folderData);
      
      // 处理当前文件夹下的文件
      filePageToken = processFilesInFolder(targetFolderId, targetFolderPath, sheet, "");
      
      // 如果文件未处理完,保存进度退出
      if (filePageToken !== "") {
        saveProgress(props, progress.rootFolderId, targetFolderPath, targetFolderId, filePageToken, folderQueue);
        Browser.msgBox("时间即将耗尽,已保存进度,下次点击继续即可");
        return;
      }
      
      // 将子文件夹加入待处理队列
      var childFolders = targetFolder.getFolders();
      while (childFolders.hasNext()) {
        folderQueue.push(childFolders.next().getId());
      }
      
      // 更新队列进度
      props.setProperty("folderQueue", JSON.stringify(folderQueue));
      currentFolderPath = targetFolderPath;
    }
    
    // 遍历完成,清空进度
    if (folderQueue.length === 0) {
      props.deleteAllProperties();
      Browser.msgBox("遍历完成!");
    }
    
  } catch (e) {
    if (e.toString().includes("Maximum execution time exceeded")) {
      // 超时后保存进度
      saveProgress(props, progress.rootFolderId, currentFolderPath, currentFolderId, filePageToken, folderQueue);
      Browser.msgBox("超时!已保存进度,下次点击继续遍历");
    } else {
      Logger.log(e.toString());
      Browser.msgBox("发生错误:" + e.toString());
    }
  }
};

// 处理单个文件夹内的文件,返回下一页token(若有剩余)
function processFilesInFolder(folderId, folderPath, sheet, pageToken) {
  var folder = DriveApp.getFolderById(folderId);
  var files;
  
  do {
    files = pageToken === "" ? folder.getFiles() : folder.getFilesContinuationToken(pageToken);
    
    while (files.hasNext() && !isTimeAlmostUp()) {
      var file = files.next();
      var fileData = [
        folderPath + "/" + file.getName(),
        file.getName(),
        "File",
        file.getDateCreated(),
        file.getUrl(),
        file.getLastUpdated(),
        file.getDescription(),
        file.getSize()/1024,
        file.getOwner().getEmail()
      ];
      sheet.appendRow(fileData);
    }
    
    pageToken = files.getContinuationToken();
    if (pageToken !== "" && isTimeAlmostUp()) {
      return pageToken;
    }
    
  } while (pageToken !== "" && !isTimeAlmostUp());
  
  return "";
};

// 检查是否即将超时(预留10秒保存进度)
function isTimeAlmostUp() {
  var executionTime = new Date().getTime() - ScriptApp.getScriptTime();
  return executionTime > (ScriptApp.getMaxExecutionTime() - 10) * 1000;
};

// 保存当前遍历进度
function saveProgress(props, rootFolderId, currentFolderPath, currentFolderId, filePageToken, folderQueue) {
  props.setProperties({
    rootFolderId: rootFolderId,
    currentFolderPath: currentFolderPath,
    currentFolderId: currentFolderId,
    filePageToken: filePageToken,
    folderQueue: JSON.stringify(folderQueue)
  });
};

// 重置遍历进度和表格
function resetTraversal() {
  var sheet = SpreadsheetApp.getActiveSheet();
  sheet.clear();
  PropertiesService.getScriptProperties().deleteAllProperties();
  Browser.msgBox("已重置进度和表格");
};

关键改进说明

  1. 进度持久化:使用PropertiesService保存遍历状态,包括待处理文件夹队列、当前文件夹信息、文件分页token,确保超时后能精准恢复
  2. 超时预判:isTimeAlmostUp函数提前10秒检测超时风险,预留足够时间保存进度
  3. 分页遍历:利用Drive API的续传token实现文件分页加载,避免一次性读取大量文件导致超时
  4. 队列替代递归:改用文件夹队列管理遍历顺序,避免递归调用栈溢出,同时更易控制进度
  5. 操作人性化:新增「重置遍历进度」菜单,方便用户重新开始任务

内容的提问来源于stack exchange,提问作者Hatem Husam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 09:24:20