自动复制覆盖文件夹数据:解决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
相关产品推荐
相关产品推荐

