如果你經常在多份 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/ 後、下一個 / 前那段字串;貼到程式前可先用正規表達式驗證長度與字符集合。
延伸閱讀推薦:
