Google 雲端硬碟:取得資料夾 getFolders()

 


整理雲端硬碟,最怕資料夾一大串,名字重複、層級又深,人工看一圈就天黑。其實用 Google Apps Script 的 DriveApp.getFolders(),就能把你有權限的資料夾一口氣掃出來,還能加條件篩選、寫進試算表、做權限盤點。

這篇從最基本的語法開始,帶你一步步把清單匯出、找出空資料夾、比對擁有者與協作者,最後再補上常見坑洞與效能避雷。寫好的範例可以直接貼上跑,不用先懂太多程式,照步驟做就能把硬碟變清爽。希望本文章能幫助到需要的您。


目錄

{tocify} $title={目錄} 


為什麼要認真學 getFolders()?

如果你在公司或個人日常要處理大量檔案,像是:

例行整理 Drive 資料夾、盤點舊案、抓出「空資料夾」;

針對所有資料夾做權限稽核、產出清單;

把每個資料夾底下檔案搬運、重新命名、或歸檔;

那你八成會用到 DriveApp 這個服務,而 getFolders() 就是打開「所有資料夾入口」的那把鑰匙。


快速認識 DriveApp.getFolders()

語法:DriveApp.getFolders()

回傳:FolderIterator(資料夾迭代器)

用途:遍歷你「有權限看到」的所有 Google 雲端硬碟資料夾(不含資源回收筒)。

如何取出資料夾:透過 while (it.hasNext()) { const folder = it.next(); } 一個個拿

小提醒:FolderIterator 不是陣列,不能用 forEach 或索引值 [i] 直接取。這是新手最常踩到的第一個坑。


建立專案與基本測試

1.    開啟 Apps Script 編輯器

在 Google 雲端硬碟中:新增 → 更多 → Google Apps Script。

2.    設定專案名稱

例如:Drive getFolders 全掃描工具。

3.    建立測試函式

function listAllFolders_basic() {
  const it = DriveApp.getFolders(); // 取得所有資料夾的迭代器
  let count = 0;
  while (it.hasNext()) {
    const f = it.next();
    Logger.log('[%s] %s', f.getId(), f.getName());
    count++;
  }
  Logger.log('Total folders: %s', count);
}


4.    授權並執行

第一次跑會跳出授權視窗,依指示允許即可。

5.    查看結果

在「執行」→「檢視記錄」看到輸出。

如果清單很多,建議不要單純用 Logger,改寫入試算表,效能與可讀性更好(下面有完整範例)。


getFolders() 周邊常用方法速查

在迭代每個 Folder 時,你通常會立刻用到:

    getName():資料夾名稱

    getId():資料夾 ID

    getUrl():資料夾連結

    getDateCreated() / getLastUpdated():建立與最後更新時間

    getOwner() / getEditors() / getViewers():擁有者與協作者

    getFolders():取得子資料夾(注意:這是 Folder 物件 的 getFolders(),可往下鑽)


實務範例

範例 1:把所有資料夾清單寫入 Google 試算表

需求:做一次盤點,產出可排序、可篩選的表格。

function exportAllFoldersToSheet() {
  const ss = SpreadsheetApp.create('Drive 資料夾清單(全域掃描)');
  const sh = ss.getActiveSheet();
  sh.clear();
  sh.appendRow(['Folder Name', 'Folder ID', 'URL', 'Owner Email', 'Created', 'Updated']);

  const it = DriveApp.getFolders();
  const rows = [];
  while (it.hasNext()) {
    const f = it.next();
    const owner = f.getOwner ? f.getOwner() : null; // 有些情境下可能沒有 owner(像共用雲端硬碟)
    rows.push([
      f.getName(),
      f.getId(),
      f.getUrl(),
      owner ? owner.getEmail() : '(N/A)',
      f.getDateCreated(),
      f.getLastUpdated()
    ]);
  }
  if (rows.length) sh.getRange(2, 1, rows.length, rows[0].length).setValues(rows);
  Logger.log('Done: ' + ss.getUrl());
}


為什麼不邊迭代邊 appendRow()?

因為一次性 setValues() 才是效能王道;逐列寫入很容易觸發執行時間上限。


範例 2:只列出「空資料夾」

需求:清出殭屍資料夾,為整理或刪除做準備。

function listEmptyFolders() {
  const it = DriveApp.getFolders();
  const empty = [];
  while (it.hasNext()) {
    const f = it.next();
    const hasSub = f.getFolders().hasNext();
    const hasFiles = f.getFiles().hasNext();
    if (!hasSub && !hasFiles) {
      empty.push([f.getName(), f.getId(), f.getUrl()]);
    }
  }
  const ss = SpreadsheetApp.create('空資料夾清單');
  const sh = ss.getActiveSheet();
  sh.appendRow(['Folder Name', 'Folder ID', 'URL']);
  if (empty.length) sh.getRange(2, 1, empty.length, 3).setValues(empty);
  Logger.log('Empty folders: ' + empty.length + ' | ' + ss.getUrl());
}


強烈建議:刪除前先輸出清單並人工確認,避免誤刪。


範例 3:權限盤點(擁有者、編輯者、檢視者)

需求:做資安稽核,找出外部網域的協作者。

function auditFolderPermissions() {
  const it = DriveApp.getFolders();
  const rows = [['Folder Name', 'Folder ID', 'Role', 'Email']];
  while (it.hasNext()) {
    const f = it.next();

    // Owner(某些共用雲端硬碟資料夾可能沒有 owner)
    try {
      const owner = f.getOwner();
      if (owner) rows.push([f.getName(), f.getId(), 'OWNER', owner.getEmail()]);
    } catch (e) {
      rows.push([f.getName(), f.getId(), 'OWNER', '(N/A)']);
    }

    // Editors
    f.getEditors().forEach(u => rows.push([f.getName(), f.getId(), 'EDITOR', u.getEmail()]));
    // Viewers
    f.getViewers().forEach(u => rows.push([f.getName(), f.getId(), 'VIEWER', u.getEmail()]));
  }

  const ss = SpreadsheetApp.create('資料夾權限盤點');
  ss.getActiveSheet().getRange(1, 1, rows.length, rows[0].length).setValues(rows);
  Logger.log('Done: ' + ss.getUrl());
}


網域檢查:拉出清單後,用試算表公式或 Apps Script 再判斷 @yourcompany.com 以外的帳號。


範例 4:依條件篩選再處理(搭配 searchFolders)

getFolders() 是「全部」,若你只想找「名稱含關鍵字且未丟入資源回收筒」的資料夾,用 DriveApp.searchFolders(query) 更快:

function findFoldersByTitleContains(keyword) {
  const q = `title contains "${keyword}" and trashed = false`;
  const it = DriveApp.searchFolders(q);
  const list = [];
  while (it.hasNext()) {
    const f = it.next();
    list.push([f.getName(), f.getId(), f.getUrl()]);
  }
  Logger.log('Matched: ' + list.length);
  return list;
}


searchFolders 支援類似 Drive 搜尋語法(title、modifiedDate、trashed…),做精準篩選更有效率。


範例 5:階層向下鑽(從指定資料夾開始)

有時不想全域掃描,只想從某個根資料夾往下抓到最末層。

function traverseFromFolderId(rootId) {
  const root = DriveApp.getFolderById(rootId);
  const acc = [];
  function dfs(folder, path) {
    const curPath = path + '/' + folder.getName();
    acc.push([curPath, folder.getId(), folder.getUrl()]);
    const it = folder.getFolders();
    while (it.hasNext()) {
      dfs(it.next(), curPath);
    }
  }
  dfs(root, '');
  const ss = SpreadsheetApp.create('階層清單');
  ss.getActiveSheet().getRange(1, 1, acc.length, 3).setValues(acc);
  Logger.log('Depth result: ' + acc.length + ' | ' + ss.getUrl());
}


這種 DFS(深度優先)要留意遞迴深度與執行時間上限(Apps Script 一次約 6 分鐘);路徑很深就考慮分批執行。


範例 6:批次重新命名資料夾(安全加護版)

需求:把名稱中包含指定字串的資料夾做規則化命名(例如去掉前後空白、替換非法字元)。

function normalizeFolderNames() {
  const it = DriveApp.getFolders();
  let changed = 0;
  while (it.hasNext()) {
    const f = it.next();
    const oldName = f.getName();
    let newName = oldName.trim();
    newName = newName.replace(/[\\/:*?"<>|]/g, '_'); // 避免跨系統同步問題

    if (newName !== oldName) {
      try {
        f.setName(newName);
        changed++;
        Logger.log('Rename: "%s" → "%s"', oldName, newName);
      } catch (e) {
        Logger.log('Skip (no permission?): %s | %s', oldName, e.message);
      }
    }
  }
  Logger.log('Done. Changed: ' + changed);
}


命名衝突:若同層已有同名資料夾,可能會丟錯或被拒;真要規模化命名,建議在新名稱後加序號或時間戳。


效能最佳化與穩定性策略

大批量掃描最常遇到的是「跑不完」。以下是穩定密技:

1.    控制輸出節奏

集中資料到陣列,最後一次 setValues();切勿在迴圈中頻繁寫入。

2.    分段執行 / 續跑機制

透過 PropertiesService 記錄「上次處理到哪個資料夾 ID」,下次從該點續跑。

或把 ID 清單先寫到試算表,分批讀取處理。

3.    避免無謂的深度遍歷

若只要頂層資訊,就不要對每個資料夾再 getFolders();層層往下很耗時。

4.    優先用 searchFolders(query)

有條件就先篩再掃,減少母體規模。

5.    錯誤容忍

try/catch 包覆敏感操作(改名、移動、改權限),記錄錯誤、不因個案中斷。

6.    時限意識

Apps Script 單次執行約 6 分鐘;超過就拆段或搭配觸發器(time-driven trigger)分批跑。

7.    記錄與回報

每 N 筆打一次 Logger.log() 或寫進暫存工作表,便於診斷。


權限、共用雲端硬碟與安全考量

1.    你只能看到你「有權限」的資料夾

getFolders() 只會遍歷你能存取的範圍。若覺得數量少,通常是權限不足。

2.    共用雲端硬碟(Shared drives)注意

某些操作(像擁有者概念)與個人雲端不同,getOwner() 可能回傳不到人。請以錯誤容忍方式處理,或改用 Advanced Drive Service(Drive API v3) 做更細緻的權限查詢。

3.    回收筒不在其中

getFolders() 不含資源回收筒內容;要查回收筒或更複雜條件,用 searchFolders() 或 Drive API 查詢。

4.    敏感動作要二次驗證

刪除、搬運、改權限等,都先「產生清單 → 人工審核 → 再執行」;別直接在同一支程式裡一把做完。


常見錯誤與雷點

1.    把 FolderIterator 當陣列用

錯誤:DriveApp.getFolders().forEach(...) 或 it[i]。

對策:用 while (it.hasNext()) { const f = it.next(); }。

2.    逐列 appendRow() 十萬火急寫表格

症狀:執行時間暴增,超時失敗。

對策:先把資料 push 到陣列,最後 setValues() 一次寫入。

3.    忽略共用雲端硬碟差異

症狀:getOwner() 拋錯或回傳 null。

對策:加 try/catch,或在資料欄用 (N/A) 區別;更進階用 Drive API v3。

4.    期望 getFolders() 有排序或分頁

事實:沒有直接排序;你要自己收集成陣列再排序。

對策:例如 rows.sort((a,b) => a[0].localeCompare(b[0])) 依名稱排序。

5.    想用關鍵字模糊搜卻用 getFoldersByName()

事實:getFoldersByName(name) 是精準比對。

對策:使用 searchFolders('title contains "xxx" and trashed = false')。

6.    大量改名/搬運未做衝突與例外處理

症狀:個別資料夾失敗導致整批中斷。

對策:封裝 try/catch、記錄失敗名單、最後統整報表。

7.    直接刪除空資料夾

風險:有時看起來「空」,但其實有隱藏檔或即將同步的內容。

對策:先輸出清單,人工檢查後再刪;或等 1–2 天確認無人使用再執行。

8.    忽略配額與速率限制

症狀:一段時間後開始丟 429/Rate Limit 或時間上限。

對策:分段執行、加上 Utilities.sleep(100~300) 做輕量節流、縮小掃描範圍。



問題集

Q1:getFolders() 會包含共用雲端硬碟嗎?

A:它會遍歷你能看見的資料夾(包含某些共用雲端硬碟場景),但權限模型有差異,像 getOwner() 可能取不到。對共用雲端硬碟的進階查詢或管理,建議改用 Advanced Drive Service (Drive API v3)。

Q2:能不能只掃描我雲端硬碟某個母資料夾?

A:可以,用「範例 5」從 getFolderById() 取得根資料夾後,往下 DFS 或 BFS 即可。

Q3:要怎麼找「名稱包含 XXX」的資料夾?

A:searchFolders('title contains "XXX" and trashed = false'),效率遠勝全域掃描再過濾。

Q4:怎麼避免超時?

A:分批、先篩後掃、集中寫表、必要時加觸發器定時跑,並加入續跑機制。


可直接複用的輔助工具片段

(1) 陣列批量寫入工具

function writeRows_(sheet, startRow, startCol, rows) {
  if (!rows.length) return;
  sheet.getRange(startRow, startCol, rows.length, rows[0].length).setValues(rows);
}


(2) 續跑機制(記錄最後處理的資料夾 ID)

function resumeToken_(val) {
  const props = PropertiesService.getScriptProperties();
  if (typeof val === 'string') { props.setProperty('LAST_FOLDER_ID', val); }
  return props.getProperty('LAST_FOLDER_ID');
}


(3) 輕量節流

function throttle_(ms) {
  Utilities.sleep(ms || 150);
}


參考指令小抄

取得所有資料夾迭代器:DriveApp.getFolders()

名稱精準比對:DriveApp.getFoldersByName('Name')

條件搜尋(推薦):DriveApp.searchFolders('title contains "X" and trashed = false')

取得子資料夾:folder.getFolders()

其他常用:getName()/getId()/getUrl()/getDateCreated()/getLastUpdated()/getEditors()/getViewers()


張貼留言 (0)
較新的 較舊