Google 試算表:用檔案 ID 開啟試算表 openById()

 


如果你經常在多份 Google 試算表之間搬資料,openById() 會是那把關鍵的鑰匙。

它透過檔案 ID 精準鎖定目標,避免網址參數、縮網址或重新命名帶來的混亂。

本篇文章盡可能說明內容,從「去哪裡找 ID」開始,示範如何讀寫特定工作表、一次性批量寫入避免卡頓,最後補上排程與可安裝觸發器的設定。也會提醒幾個大家最容易踩的點:權限沒授好、把 gid 當成 ID、還有以為 OpenID 可以「開檔」。

看完你就能把日常報表與彙整流程自動化,省下大量手動時間。希望本篇文章能夠幫助到有需要的您。


目錄

{tocify} $title={目錄} 


為什麼要用 openById()?

當你的流程需要跨檔案讀寫、把資料彙整到總表、或從範本複製新檔再填入初始值,只靠「目前檔案」的 getActiveSpreadsheet() 不夠;你必須明確指定要操作的那個試算表。SpreadsheetApp.openById(id) 就是用來「以檔案 ID 開啟目標試算表」的 API。它回傳 Spreadsheet 物件,後續就能 .getSheetByName()、.getRange()、.appendRow() 等等。


核心觀念先釐清

1. 試算表 ID 與工作表 gid

試算表 ID:

        出現在網址 .../spreadsheets/d/<這段就是ID>/edit#gid=0 的那串字母數字,用來唯一標識整個檔案;openById() 就吃這個。

工作表 gid:

        網址 #gid= 後面的數字,代表的是檔案內某一張工作表,不是 openById() 要的參數。

如何快速找到 ID?看網址 /d/ 後到下一個 / 前那段字串。

2. 容器綁定 vs. 獨立專案

容器綁定(container-bound):

        從某份試算表開啟「擴充功能 → Apps Script」建立的專案;getActiveSpreadsheet() 指向的是當前這份。

獨立專案(standalone):

        從 script.google.com 建立,沒有「當前檔案」概念;因此幾乎都要靠 openById() 來鎖定目標。

3. 權限與授權(Scopes)

        跨檔案讀寫必然牽涉權限。Apps Script 會根據程式碼偵測所需範圍(Scopes),初次執行會跳出授權畫面。若你要額外取得 OpenID 身分權杖,必須手動在 appsscript.json 加上 openid 與(通常)userinfo.email 或 userinfo.profile。授權範圍的設計原則是最小必要權限。

4. openById() vs openByUrl()

        兩者都能開啟外部試算表:前者吃 ID,後者吃整段 URL。實務上建議統一用 ID,避免網址參數或縮網址造成誤判。


快速上手:從 0 到能讀寫外部試算表

步驟 1:準備目標試算表

        1.    開啟你要「被讀寫」的試算表。

        2.    複製網址 /d/ 後的那串 ID。

步驟 2:建立或開啟 Apps Script

        在來源檔(要執行腳本的檔案)裡,點【擴充功能 → Apps Script】建立專案。

        或到 script.google.com 建立獨立專案,將來透過 openById() 指到目標檔。

步驟 3:貼上最小可用範例

function readFromAnotherSpreadsheet() {
  const TARGET_ID = '在這裡貼上你的試算表ID';
  const ss = SpreadsheetApp.openById(TARGET_ID);   // 重點
  const sheet = ss.getSheetByName('Orders');       // 你的工作表名
  if (!sheet) throw new Error('找不到工作表 Orders');
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return [];                      // 沒有資料
  const values = sheet.getRange(2, 1, lastRow - 1, 5).getValues();
  Logger.log(values);
  return values;
}

第一次執行會跳出授權畫面,按指示授權後即可運作。授權機制與範圍偵測由 Apps Script 處理。

步驟 4:寫回目標試算表

function appendToLog() {
  const TARGET_ID = '你的試算表ID';
  const log = SpreadsheetApp.openById(TARGET_ID).getSheetByName('Log');
  if (!log) throw new Error('找不到工作表 Log');
  log.appendRow([new Date(), 'APPENDED', Session.getActiveUser().getEmail()]);
}

步驟 5:排程 / 事件觸發

需要自動化?建立可安裝觸發器(例如「每小時執行一次」、「表單送出時」),避免單純 onEdit(e) 這類簡易觸發器的權限限制。


實戰情境與範本

A. 跨檔彙整:分店月報 → 總表

function aggregateBranches() {
  const IDS = [
    '分店A_ID', '分店B_ID', '分店C_ID'
  ];
  const dest = SpreadsheetApp.getActive().getSheetByName('總表') || SpreadsheetApp.getActive().insertSheet('總表');
  dest.clearContents().appendRow(['分店','月份','營收','成本','毛利']);

  IDS.forEach(id => {
    const ss = SpreadsheetApp.openById(id);
    const sh = ss.getSheetByName('月報');
    if (!sh) return;
    const range = sh.getRange(2,1, sh.getLastRow()-1, sh.getLastColumn());
    const rows = range.getValues().map(r => [ss.getName()].concat(r));
    if (rows.length) dest.getRange(dest.getLastRow()+1, 1, rows.length, rows[0].length).setValues(rows);
  });
}


B. 以範本建立新月報並回填

function makeMonthlyFromTemplate(monthLabel) {
  const TEMPLATE_FILE_ID = '範本檔ID';
  const copyFile = DriveApp.getFileById(TEMPLATE_FILE_ID).makeCopy(`月報_${monthLabel}`);
  const newId = copyFile.getId();
  const ss = SpreadsheetApp.openById(newId);
  ss.getSheetByName('Config').getRange('B2').setValue(monthLabel);
  return copyFile.getUrl();
}

這個流程常見於「建立分身檔 → 填入月份、起訖日期、負責人」。openById() 是把剛複製的新檔打開後再寫設定。


C. 事件驅動寫入(可安裝 onEdit)

function onEditHandler(e) {  // 建議改用「可安裝觸發器」綁定這個 handler
  const TARGET_ID = '集中Log檔ID';
  const log = SpreadsheetApp.openById(TARGET_ID).getSheetByName('EditLog');
  const {range, user} = e;
  log.appendRow([new Date(), user?.getEmail?.(), range.getA1Notation(), range.getSheet().getName()]);
}

提醒:簡易觸發器(函式名直接叫 onEdit)在未授權狀態下會受限,跨檔存取常失敗;請改用可安裝觸發器(在 Apps Script 編輯器按鐘錶圖示新增)。


把「OpenID」這件事講清楚(避免與 openById 混淆)

有時你看到文件或討論串提到「OpenID / OIDC」,那是身分驗證標準,和打開試算表「by Id」完全不是同一件事。

若你的 Apps Script 需要取得OpenID Connect 身分權杖(ID Token),可呼叫:

function getIdToken() {
  const token = ScriptApp.getIdentityToken();
  Logger.log(token);
}

要讓上面這段成功,你必須在 appsscript.json 加上:


{
  "oauthScopes": [
    "openid",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/drive"
  ]
}

其中 openid 與 userinfo.* 是為了回傳使用者資訊;而讀寫試算表與雲端硬碟則對應 spreadsheets / drive 範圍。


常見錯誤與避雷

1.    把整段網址丟進 openById()

→ 參數只收「ID」,不是整段 URL。若硬塞 URL,會拋錯:找不到檔案或權限不足。用 openByUrl() 才能吃 URL,但建議習慣用 ID。

2.    把 gid 當成檔案 ID

→ gid 是單張工作表識別碼,不是檔案 ID。從 /d/ 後面那段才是檔案 ID。

3.    簡易觸發器跨檔失敗

→ onEdit / onOpen 這類簡易觸發器有權限限制,常見現象是「手動執行 OK,觸發器執行噴權限錯」。請改用可安裝觸發器。

4.    未授權或授權範圍不夠

→ 初次執行沒走完授權,或是你手動在 appsscript.json 設了過窄的 Scopes。檢查並加入必要範圍。

5.    把「OpenID」誤以為能開檔

→ OpenID / OIDC 是身分驗證(ScriptApp.getIdentityToken()),和 openById() 毫無關係;不要混用。

6.    目標檔未分享給執行者

→ 如果腳本以你的帳號執行,你要對目標檔有存取權。若是安裝在共用雲端或以外掛/網路應用程式方式執行,要確認執行身份(如:以擁有者執行、網域存取設定)。

7.    工作表名稱打錯或被改名

→ getSheetByName('...') 找不到就會得到 null。加上防呆判斷,或改以 index/ID 邏輯處理。

8.    大資料一次性寫入過慢

→ 逐列 appendRow() 會慢;建議把二維陣列整批 .setValues(),或先組好快取後一次寫回。

9.    配額(Quota)與逾時

→ 迴圈跨數十個檔案容易碰到執行時間上限。拆批次、加退避重試(exponential backoff),或改用排程分次跑。

10.    錯把使用者操作(UI)與伺服器操作混為一談

→ openById() 只是在伺服器端打開檔案供腳本操作,不會在你的瀏覽器「真的開啟」那份試算表;這是正常行為。


問題集

Q1:我把 URL 整段丟進 openById(),為什麼報錯?

A:因為它只吃 ID 字串。要嘛改成 openByUrl(url),要嘛抽出 ID 再用 openById()。建議統一用 ID。

Q2:onEdit(e) 可以跨檔寫入嗎?

A:用簡易觸發器常會因為權限不足失敗;請改「可安裝觸發器」綁定你的 handler。

Q3:我真的需要「OpenID」嗎?

A:大多數試算表自動化完全不需要。只有在你需要拿到使用者身分權杖(ID Token)去跟外部服務溝通時才需要 ScriptApp.getIdentityToken() 並加上 openid scope。

Q4:怎麼確認檔案 ID 取對?

A:看網址 /d/ 後、下一個 / 前那段字串;貼到程式前可先用正規表達式驗證長度與字符集合。


延伸閱讀推薦:

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