先說結論
OK,直接講重點。Google Apps Script (GAS) 很強大,而且免費,但它不是讓你無限使用的。最重要的觀念不是去背那些密密麻麻的配額 (Quota) 數字,而是從一開始寫 code 的時候,就要有「這個腳本總有一天會撞到牆」的心理準備。
所以,寫法上就要懂得繞路、懂得自我管理。這篇筆記就是要把這些「牆」在哪裡,還有怎麼「繞路」給整理出來。
為什麼官方文件看不懂?帳號類型是關鍵
很多人第一個卡關的點,就是去查 Google 的官方文件,然後覺得頭很痛。一下說這個可以用 100 次,一下又說可以用 1500 次。欸,到底是多少?
問題出在,你的「Google 帳號類型」會決定你的配額等級。這點超重要,但文件裡寫得不太直觀。簡單分成兩種:
- 個人帳號 (Consumer): 就是你我都有的 `@gmail.com` 結尾的免費帳號。
- Google Workspace 帳號: 以前叫 G Suite,就是公司或學校付費買的,用自己網域名稱結尾的帳號 (例如 `user@yourcompany.com`)。早期免費的 Workspace 帳號也算在這一類,但配額比較接近個人帳號。
它們的差別,直接看表格最清楚。我整理了幾個最常撞到的限制。
| 服務項目 | 個人帳號 (@gmail.com) | Google Workspace 帳號 (付費版) |
|---|---|---|
| 單次腳本執行時間 | 6 分鐘 / 次 | 一般是 6 分鐘,但某些 Workspace 版本 (例如 Enterprise) 可以申請延長到 30 分鐘。 |
| 每日觸發器總執行時間 | 90 分鐘 / 天 | 基本上是 1~6 小時 / 天,看你的 Workspace 版本等級。差很多! |
MailApp.sendEmail 每日額度 |
一天只能寄給 100 個收件者。注意喔,是「收件者」,不是「封信」。一封信寄給 10 個人就用掉 10 個額度了。 | 一天 1,500 個收件者。量級完全不同,要做自動化報表或通知,這個就很關鍵。 |
UrlFetchApp.fetch 每日呼叫次數 |
20,000 次 / 天。聽起來很多,但如果你的腳本是高頻率去抓 API 資料,就要小心。 | 100,000 次 / 天。基本上不太會碰到。 |
| 建立文件 (Docs, Sheets, Slides) | 250 個 / 天。 | 1,500 個 / 天。 |
你看,光是寄信跟觸發器總時間,付費帳號的額度就大超多。很多時候你寫的腳本沒問題,只是你的帳號等級不夠力而已。這跟美國那邊開發者社群討論的狀況很像,他們很早就習慣區分 Workspace 跟個人帳號的開發策略,但在台灣比較多個人開發者,所以更容易撞到免費帳號的牆。
最常見的幾個「撞牆」點
除了帳號等級,有幾個限制是所有人都會遇到的,算是 GAS 的天花板。
腳本執行時間:6 分鐘的詛咒
這大概是排名第一的殺手。任何一個 Apps Script 腳本,從開始執行到結束,一次最多就是 6 分鐘(對,大部分帳號都是)。只要超過 6 分鐘,Google 就會強制中止你的腳本,然後丟出一個 `Exceeded maximum execution time` 的錯誤。
什麼情況會超過 6 分鐘?
- 處理好幾千列的 Google Sheet 資料。
- 在一個迴圈裡,對每個項目都做很複雜的操作,例如呼叫外部 API、建立檔案等。
- 網路不穩,導致 `UrlFetchApp` 等待回應的時間過長。
怎麼辦?唯一的解法就是「分批處理 (Batching)」。不要想著一次做完,要把大任務拆成好幾個小任務。這點後面案例會細講。
服務呼叫次數:API 不是讓你無限 call
就像上面表格看到的,`MailApp`、`UrlFetchApp` 這些跟外部溝通的服務,都有每日呼叫次數限制。你可能會想,「一天一萬次很多啊」,但如果你在一個 `for` 迴圈裡面寫了 `UrlFetchApp.fetch()`,然後這個迴圈要跑一千次,裡面又不小心寫錯多跑了幾次... 很快就用完了。
寫 Code 的時候,養成習慣用 `try...catch` 把這些 API 呼叫包起來,至少出錯時你可以在 Log 裡看到原因,而不是腳本莫名其妙停掉。
function fetchSomeData() {
try {
// 這裡放你的 UrlFetchApp 或 MailApp 等等的 code
let response = UrlFetchApp.fetch("https://api.example.com/data");
Logger.log(response.getContentText());
} catch (e) {
// 如果碰到配額限制或其他錯誤,e.message 通常會寫原因
Logger.log("抓取資料失敗: " + e.message);
// 你可以在這裡加上錯誤通知,例如寄信給自己
// MailApp.sendEmail("your-email@gmail.com", "腳本錯誤", "錯誤訊息:" + e.message);
// 但要注意,如果 MailApp 本身就超額,這段也會失敗
}
}
案例:一個自動寄信腳本的進化史
講理論很空,我們直接用一個「從 Google Sheet 讀取名單並寄送客製化郵件」的腳本來當例子。
版本 1.0:天真爛漫版
拿到需求,直接一個迴圈搞定。讀取每一列,然後 `MailApp.sendEmail`。
這在 50 人以下的名單運作得很好。但有一天,名單長到 200 人... 腳本就死了。免費帳號一天只能寄 100 個。GG。
版本 2.0:分批處理 + 狀態儲存
這才是專業的做法。核心精神是:**自己記住上次做到哪,然後讓腳本可以重複被觸發。**
我們會用到一個很重要的內建服務:`PropertiesService`。它就像是腳本的專屬小抽屜,可以讓你存一些簡單的文字資料,就算腳本執行結束,資料也還在。
作法變成:
- 用 `PropertiesService` 讀取一個我們自己設定的 `lastProcessedRow` 變數。如果第一次跑,就從第 2 列開始(跳過標題)。
- 一樣寫迴圈,但這次加上一個計數器,一次只處理 50 封信就好(確保不會超時,也給每日配額留點彈性)。
- 迴圈跑完後,把「最後處理到的那一列的行數」用 `PropertiesService` 存起來。
- 檢查工作是否全部做完。如果還沒,就動態建立一個 5 分鐘後執行的「觸發器」,讓它再跑一次同一個 function。
- 如果全部做完了,就把 `lastProcessedRow` 清掉,並且刪掉那個自動建立的觸發器。
這樣一來,不管名單有多長,你的腳本都能像螞蟻搬家一樣,一次搬一點,最終把任務完成。這就是設計「可恢復性 (resumable)」腳本的思路。
觸發器 (Triggers) 的隱形殺手
觸發器是 GAS 自動化的靈魂,但也充滿了陷阱。
- 每日總時長限制: 個人帳號是 90 分鐘/天。這不是指單一觸發器,而是你帳號下「所有」觸發器執行的時間總和。如果你有一個每 5 分鐘跑一次、每次跑 2 分鐘的腳本,那你一天只能跑 45 次,之後的觸發器就會失敗。
- `onEdit(e)` 的限制: 這是一個很方便的簡易觸發器,當試算表被編輯時會自動執行。但它能做的事情非常受限,例如它不能呼叫 `MailApp` 或任何需要授權的服務。如果你需要這些功能,必須手動建立「可安裝的 (installable)」`onEdit` 觸發器。
- 觸發器會「默默死掉」: 有時候,特別是時間驅動的觸發器,可能會因為某些 Google 後台的原因執行失敗,而且你完全不會收到通知。所以,重要的自動化流程,最好在腳本的開頭和結尾加上日誌紀錄 (例如寫入另一個 Sheet),方便你追蹤它到底有沒有乖乖工作。官方文件有提到失敗會寄信通知,但實務上...嗯,別太指望它。
我該升級 Workspace 嗎?決策思路
這其實是個商業問題,不是技術問題。
問自己幾個問題:
- 這個自動化腳本是公司營運的核心流程嗎?如果它停了,公司會不會少賺錢或多花人力成本?
- 你花在「繞過」免費版限制的時間,換算成時薪,是不是已經超過升級 Workspace 的費用了?
- 你需要更可靠的錯誤通知和更專業的技術支援嗎?
如果答案是「是」,那就別猶豫了。升級到 Google Workspace Starter 或 Standard 版本,光是 `MailApp` 1500 封/天和觸發器總時長變成 1 小時/天以上,就能解決 80% 的問題。
但如果這只是你的個人專案,或是偶爾跑一次的小工具,那學會跟免費版的限制共存,也是一種有趣的挑戰。
常見錯誤與修正
整理幾個在 Log 裡最常看到的錯誤訊息跟處理方向。
- `"Service invoked too many times for one day: email."`
原因: `MailApp` 或 `GmailApp` 的每日額度用完了。 就是上面說的 100/1500 限制。
修正: 檢查你的迴圈,不要在一次執行中寄太多信。把寄送工作分散到一整天,或用我們案例中的分批處理法。 - `"Exceeded maximum execution time"`
原因: 腳本跑超過 6 分鐘被強制中止了。
修正: 優化你的演算法,減少不必要的讀寫操作。最終極的解法還是分批處理 (`PropertiesService` + 觸發器)。 - `"Authorization is required to perform that action."`
原因: 腳本嘗試執行一個它沒有被授權的動作。最常發生在簡易觸發器 (simple triggers) 像 `onOpen(e)` 或 `onEdit(e)` 身上。
修正: 從腳本編輯器手動執行一次那個 function,畫面會跳出授權請求,同意之後,可安裝的觸發器 (installable trigger) 就能正常運作了。 - `"Exception: The starting row of the range is too small."`
原因: 你用 `getSheetByName('...').getRange(0, 1)` 這種方式去選取儲存格。在 Apps Script 裡,行 (row) 跟欄 (column) 的編號都是從 `1` 開始,不是 `0`。
修正: 這是新手常見錯誤,記得 `getRange(1, 1)` 才是 A1 儲存格。
說了這麼多,其實重點就是,寫 Apps Script 不能像寫單機程式那樣,要時時把「雲端」、「共享」、「限制」這幾個關鍵字放在心上。設計得好,它就是超強的免費自動化神器;沒考慮周全,它就會變成不定時炸彈。
你最常碰到的是哪一種限制呢?或是有沒有什麼繞過限制的獨門妙招?在下面分享一下吧!
