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

自动复制覆盖文件夹数据:解决Google Apps Script复制大文件夹超时问题

解决Google Apps Script复制大文件夹超时问题的方案

核心问题原因

普通Google Apps Script的执行时长限制为6分钟(360秒),复制2GB以上的文件夹涉及大量文件遍历、API调用,极易触发超时。

具体解决方案

1. 时间驱动触发器分批次执行

通过记录复制进度,让脚本每次执行仅处理部分内容,剩余任务由下一次定时触发继续完成,避开单次执行的时长限制。

实现逻辑:

  • 用Script Properties存储复制进度(当前处理的文件夹ID、已处理的文件/文件夹ID列表等)
  • 创建每隔5分钟触发的时间驱动触发器(预留1分钟缓冲时间),直到所有内容复制完成

优化版示例代码:

function initCopy() {
  // 初始化复制任务,仅需执行一次
  const sourceFolderName = 'abc';
  const targetFolderId = "xxxxx";
  
  const sourceFolders = DriveApp.getFoldersByName(sourceFolderName);
  if (!sourceFolders.hasNext()) return;
  
  const sourceFolder = sourceFolders.next();
  const targetFolder = DriveApp.getFolderById(targetFolderId);
  
  // 存储初始进度到Script Properties
  const props = PropertiesService.getScriptProperties();
  props.setProperties({
    'sourceFolderId': sourceFolder.getId(),
    'targetFolderId': targetFolderId,
    'currentFolderId': sourceFolder.getId(),
    'processedFiles': JSON.stringify([]),
    'processedFolders': JSON.stringify([]),
    'isCompleted': 'false'
  });
  
  // 创建时间驱动触发器,每5分钟执行一次
  ScriptApp.newTrigger('continueCopy')
    .timeBased()
    .everyMinutes(5)
    .create();
  
  // 立即执行首次复制
  continueCopy();
}

function continueCopy() {
  const props = PropertiesService.getScriptProperties();
  const isCompleted = props.getProperty('isCompleted') === 'true';
  if (isCompleted) {
    // 任务完成,清理触发器和进度数据
    const triggers = ScriptApp.getProjectTriggers();
    triggers.forEach(trigger => {
      if (trigger.getHandlerFunction() === 'continueCopy') {
        ScriptApp.deleteTrigger(trigger);
      }
    });
    props.deleteAllProperties();
    return;
  }
  
  const sourceFolderId = props.getProperty('sourceFolderId');
  const targetFolderId = props.getProperty('targetFolderId');
  const currentFolderId = props.getProperty('currentFolderId');
  const processedFiles = JSON.parse(props.getProperty('processedFiles'));
  const processedFolders = JSON.parse(props.getProperty('processedFolders'));
  
  const sourceFolder = DriveApp.getFolderById(currentFolderId);
  const targetFolder = DriveApp.getFolderById(targetFolderId);
  
  // 每次处理20个文件(可根据实际情况调整数量)
  const files = sourceFolder.getFiles();
  let fileCount = 0;
  while (files.hasNext() && fileCount < 20) {
    const file = files.next();
    const fileId = file.getId();
    if (processedFiles.includes(fileId)) continue;
    
    // 替换目标文件夹中同名文件
    const existingFiles = targetFolder.getFilesByName(file.getName());
    if (existingFiles.hasNext()) {
      existingFiles.next().setTrashed(true);
    }
    file.makeCopy(file.getName(), targetFolder);
    
    processedFiles.push(fileId);
    fileCount++;
  }
  
  // 每次处理5个子文件夹(可根据实际情况调整数量)
  const folders = sourceFolder.getFolders();
  let folderCount = 0;
  let nextFolderId = null;
  while (folders.hasNext() && folderCount < 5) {
    const subFolder = folders.next();
    const folderId = subFolder.getId();
    if (processedFolders.includes(folderId)) continue;
    
    // 替换目标文件夹中同名子文件夹
    let targetSubFolder;
    const existingFolders = targetFolder.getFoldersByName(subFolder.getName());
    if (existingFolders.hasNext()) {
      existingFolders.next().setTrashed(true);
      targetSubFolder = targetFolder.createFolder(subFolder.getName());
    } else {
      targetSubFolder = targetFolder.createFolder(subFolder.getName());
    }
    
    // 记录子文件夹的映射关系(源ID -> 目标ID)
    props.setProperty(`folderMap_${folderId}`, targetSubFolder.getId());
    processedFolders.push(folderId);
    nextFolderId = folderId;
    folderCount++;
  }
  
  // 更新进度状态
  if (nextFolderId) {
    // 切换到下一个待处理子文件夹
    props.setProperty('currentFolderId', nextFolderId);
    props.setProperty('targetFolderId', props.getProperty(`folderMap_${nextFolderId}`));
  } else if (!files.hasNext()) {
    // 当前文件夹处理完成,回到父文件夹继续
    const parentFolders = sourceFolder.getParents();
    if (parentFolders.hasNext()) {
      const parentFolder = parentFolders.next();
      props.setProperty('currentFolderId', parentFolder.getId());
      // 获取父文件夹对应的目标文件夹ID
      const parentFolderId = parentFolder.getId();
      props.setProperty('targetFolderId', props.getProperty(`folderMap_${parentFolderId}`) || targetFolderId);
    } else {
      // 所有内容处理完成
      props.setProperty('isCompleted', 'true');
    }
  }
  
  // 保存更新后的进度
  props.setProperty('processedFiles', JSON.stringify(processedFiles));
  props.setProperty('processedFolders', JSON.stringify(processedFolders));
}

// 手动终止复制任务的函数
function stopCopy() {
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(trigger => {
    if (trigger.getHandlerFunction() === 'continueCopy') {
      ScriptApp.deleteTrigger(trigger);
    }
  });
  PropertiesService.getScriptProperties().deleteAllProperties();
}

2. 优化API调用降低耗时

  • 减少重复API请求:提前将目标文件夹内的文件/文件夹名称与ID缓存到对象中,避免反复调用getFilesByName和getFoldersByName
  • 启用Drive Advanced Service:相比DriveApp,该服务API调用效率更高,支持批量操作(启用方式:脚本编辑器 -> 资源 -> 高级Google服务 -> 开启Drive API)

3. 额外注意事项

  • 控制单次处理数量:复制大文件本身耗时较长,需根据文件大小调整单次处理的文件/文件夹数量,避免单次执行超时
  • 确保权限充足:脚本需获得Drive的读写权限
  • 跳过已处理内容:通过记录已处理的文件/文件夹ID,避免重复复制

内容的提问来源于stack exchange,提问作者Syed Hasan Sarwar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 05:15:36