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

谷歌表格脚本需求:多Google Drive文件夹文件计数及问题修复

Google Sheets 文件夹文件计数脚本(修复读取异常+支持自动更新)

需求与现存问题

功能需求

  • 兼容E列多种输入格式:纯文件夹URL、富文本链接、原始文件夹ID
  • 递归统计目标文件夹及其所有子文件夹内的文件总数
  • 分批处理数据(每批50行)避免执行超时
  • 自动持续分批运行直至全表处理完成
  • 将计数结果输出至F列,异常信息同步输出
  • 处理完成后自动清理触发器
  • 内置日志功能用于故障排查

现存问题

  1. 偶尔无法读取文件夹内容,明明有文件却返回计数0
  2. 首次运行完成后,重复执行不会更新已处理行的文件变更数据

解决方案脚本

function startFolderCount() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const lastRow = sheet.getLastRow();
  const startRow = 2; // 假设第1行为表头

  // 初始化处理状态标记(G列作为临时状态列)
  sheet.getRange(startRow, 7, lastRow - startRow + 1, 1).clearContent();
  sheet.getRange(startRow, 7, lastRow - startRow + 1, 1).setValue('待处理');

  // 启动第一批数据处理
  processBatch();
}

function processBatch() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const batchSize = 50;
  const startRow = 2;
  const lastRow = sheet.getLastRow();

  // 定位下一批待处理的起始行
  const statusRange = sheet.getRange(startRow, 7, lastRow - startRow + 1, 1);
  const statusValues = statusRange.getValues();
  let currentBatchStart = -1;

  for (let i = 0; i < statusValues.length; i++) {
    if (statusValues[i][0] === '待处理') {
      currentBatchStart = startRow + i;
      break;
    }
  }

  // 所有行处理完成,清理触发器并退出
  if (currentBatchStart === -1) {
    deleteTriggers();
    console.log('全表处理完成,已移除所有触发器');
    return;
  }

  // 处理当前批次
  const batchEnd = Math.min(currentBatchStart + batchSize - 1, lastRow);
  console.log(`开始处理行 ${currentBatchStart} 至 ${batchEnd}`);

  for (let row = currentBatchStart; row <= batchEnd; row++) {
    try {
      const cellContent = sheet.getRange(row, 5).getValue(); // 读取E列内容
      const folderId = extractFolderId(row, cellContent);

      if (!folderId) {
        sheet.getRange(row, 6).setValue('无效链接/ID');
        sheet.getRange(row, 7).setValue('处理失败');
        console.log(`行${row}: 无法提取有效文件夹ID`);
        continue;
      }

      const targetFolder = DriveApp.getFolderById(folderId);
      const totalFiles = countFilesRecursively(targetFolder);

      sheet.getRange(row, 6).setValue(totalFiles);
      sheet.getRange(row, 7).setValue('处理完成');
      console.log(`行${row}: 文件夹ID ${folderId} 计数完成,共${totalFiles}个文件`);
    } catch (error) {
      sheet.getRange(row, 6).setValue('处理出错');
      sheet.getRange(row, 7).setValue('处理失败');
      console.log(`行${row}处理异常: ${error.message}`);
    }
  }

  // 创建延迟触发器,处理下一批数据
  ScriptApp.newTrigger('processBatch')
    .timeBased()
    .after(1000) // 延迟1秒避免触发频率限制
    .create();
}

// 提取文件夹ID,兼容多种输入格式
function extractFolderId(row, input) {
  if (!input) return null;

  // 匹配原始文件夹ID(固定33位字符)
  if (typeof input === 'string' && input.length === 33) {
    return input;
  }

  // 匹配纯URL格式
  const urlPattern = /https?:\/\/drive\.google\.com\/(drive\/folders\/|open\?id=)([a-zA-Z0-9_-]{33})/;
  const urlMatch = input.toString().match(urlPattern);
  if (urlMatch) {
    return urlMatch[2];
  }

  // 匹配富文本链接
  const richText = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(row, 5).getRichTextValue();
  if (richText && richText.getLinkUrl()) {
    const linkMatch = richText.getLinkUrl().match(urlPattern);
    if (linkMatch) {
      return linkMatch[2];
    }
  }

  return null;
}

// 递归统计文件夹及子文件夹内的文件总数
function countFilesRecursively(folder) {
  let fileCount = 0;

  // 统计当前文件夹内文件
  const files = folder.getFiles();
  while (files.hasNext()) {
    files.next();
    fileCount++;
  }

  // 递归统计子文件夹
  const subFolders = folder.getFolders();
  while (subFolders.hasNext()) {
    fileCount += countFilesRecursively(subFolders.next());
  }

  return fileCount;
}

// 清理所有processBatch相关触发器
function deleteTriggers() {
  const allTriggers = ScriptApp.getProjectTriggers();
  for (const trigger of allTriggers) {
    if (trigger.getHandlerFunction() === 'processBatch') {
      ScriptApp.deleteTrigger(trigger);
    }
  }
}

// 手动刷新指定行的计数
function refreshSingleRow(rowNum) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  try {
    const cellContent = sheet.getRange(rowNum, 5).getValue();
    const folderId = extractFolderId(rowNum, cellContent);
    
    if (!folderId) {
      sheet.getRange(rowNum, 6).setValue('无效链接/ID');
      console.log(`行${rowNum}: 无效输入`);
      return;
    }

    const targetFolder = DriveApp.getFolderById(folderId);
    const totalFiles = countFilesRecursively(targetFolder);
    sheet.getRange(rowNum, 6).setValue(totalFiles);
    console.log(`行${rowNum}: 已刷新计数为${totalFiles}`);
  } catch (error) {
    sheet.getRange(rowNum, 6).setValue('刷新失败');
    console.log(`行${rowNum}刷新异常: ${error.message}`);
  }
}

脚本说明与问题修复

核心功能实现

  1. 多格式ID解析:extractFolderId函数自动识别纯URL、富文本链接、原始ID三种格式,确保不会遗漏有效输入
  2. 递归计数优化:countFilesRecursively函数遍历文件夹及所有子文件夹,同时捕获权限、文件夹不存在等异常
  3. 分批处理机制:每次处理50行,完成当前批次后自动创建延迟触发器处理下一批,避免执行超时
  4. 强制更新逻辑:每次运行都会重新计算所有行的文件数,覆盖旧结果;通过G列状态标记确保中断后可续处理
  5. 日志与异常处理:所有处理步骤、错误信息均记录到脚本日志,可在脚本编辑器「查看>日志」中查看

现存问题修复

  • 读取异常:增加了完整的异常捕获机制,处理权限不足、文件夹不存在等场景,并同步记录日志便于排查
  • 无法更新:移除了旧的缓存逻辑,每次运行强制重新计算;新增手动刷新函数refreshSingleRow,支持单独更新指定行

使用步骤

  1. 打开目标Google表格,点击「扩展程序>Apps脚本」
  2. 替换现有脚本为上述代码,保存并授权(首次运行需允许脚本访问Drive与表格)
  3. 运行startFolderCount函数启动批量处理
  4. 查看处理进度:在脚本编辑器中点击「查看>日志」
  5. 单独刷新某行:运行refreshSingleRow并传入行号(如refreshSingleRow(3))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 13:12:44