如果你剛接觸 Google Apps Script,看到一堆 ID、scope、quota 就頭暈,先別急。SlidesApp.openByUrl() 其實超直覺:只要有簡報的分享網址,就能直接取得 Presentation 物件,後續任何新增、修改、讀取資訊都能用程式搞定。
本文不走硬派文件風,從「貼上一個連結」開始示範,教你如何標準化網址、完成授權、確認權限,再用幾段簡單的程式碼完成常見任務:批次替換佔位文字、把某張投影片搬到另一份簡報、以及自動產生播放連結。文末附上常見錯誤與處理方式,讓你少走彎路。希望本篇文章能夠幫助到需要的您。
目錄
{tocify} $title={目錄}
openByUrl() 是什麼?能解決什麼痛點?
在 Google Apps Script(以下簡稱 GAS)的 Slides 服務裡,SlidesApp.openByUrl(url) 用來依據簡報的分享網址打開並取得 Presentation 物件。取得物件後,你就能用各種 API 操作簡報:新增投影片、插入圖片、替換文字、讀取頁面尺寸、抓選取內容等等。這個方法與 openById(id) 並列,是最常見的進入點之一。官方文件明確記載 openByUrl(url) 會回傳 Presentation,需要 Slides 的 OAuth 授權範圍。
典型使用情境
從表單應答、試算表或外部系統,只存了分享網址(沒有額外解析 ID)— 用 openByUrl() 直接打開最直覺。
內容作業流程中,同事丟一個簡報連結到聊天室,要用腳本批次替換標題/尾頁版權/品牌色。
教學或範本系統只發佈「可複製用的 URL」,腳本接到 URL 後先 openByUrl(),接著搬頁、套母片、或複製到個人雲端。
小對照:openById(id) 與 openByUrl(url) 在能力上相同,差異只在輸入來源(你手上是 ID 還是 URL)。官方 Slides 服務說明同列出兩者。
前置準備與授權重點
授權範圍(Scopes):執行會要求 https://www.googleapis.com/auth/presentations,由 GAS 自動偵測與提示同意。也可在 appsscript.json 手動聲明更窄的範圍(例如搭配進階服務時)。
配額與限制:Apps Script 對 Slides 服務有一般性配額(如每日/每分鐘限制),而 Slides API 也有讀寫每分鐘的 Request 上限;若超過,常見是「429 Too Many Requests」,建議採指數退避。
權限模型:腳本執行者必須對目標簡報有存取權(至少讀取;若要寫入則需可編輯)。若透過自訂函數(Spreadsheet 的單元格函數)呼叫需要授權的服務,會因安全性限制直接報「沒有權限」,建議改從選單或觸發器執行。
一步一步操作(從零開始)
步驟 1:準備簡報網址
到 Google 簡報按右上角【分享】→ 設定權限(建議先與你的帳戶共享「可編輯」),複製網址,例如:
https://docs.google.com/presentation/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/edit
(/d/<ID>/edit 結構即可,後方 query 參數不是必需。)
步驟 2:開啟 Apps Script 專案
進 Google 雲端硬碟 → 新增 → 更多 → Apps Script,或在試算表/文件中用「擴充功能 → Apps Script」。
步驟 3:撰寫最小可行程式碼
function openSlidesByUrl_demo() {
const url = 'https://docs.google.com/presentation/d/你的ID/edit';
const presentation = SlidesApp.openByUrl(url); // 取得 Presentation 物件
Logger.log(`Title: ${presentation.getName()}`);
Logger.log(`Slide count: ${presentation.getSlides().length}`);
步驟 4:首次授權
按執行 → 依流程同意存取 Slides。授權細節源於 GAS 的 Scopes 機制。
步驟 5:驗證結果
打開「執行記錄 / 日誌」確認標題、頁數正確。接著你就能進一步做新增頁、版面套用等操作。Presentation 類別支援大量方法(例如取得頁高寬、選取頁、編輯者清單等)。
實務範例:從 URL 打開、批次替換、另存副本
範例 A:用 URL 打開後,批次替換文字
function replaceTextFromUrl(url, findText, replaceText) {
const pres = SlidesApp.openByUrl(url);
const slides = pres.getSlides();
slides.forEach(slide => {
const shapes = slide.getShapes();
shapes.forEach(shape => {
const textRange = shape.getText();
if (textRange) {
textRange.replaceAllText(findText, replaceText);
}
});
});
}
範例 B:把來源簡報的指定頁,複製到目前簡報
自 2018 年起,SlidesApp 已內建複製投影片能力,無須自己走低階 API。
function copyFirstSlide(srcUrl) {
const src = SlidesApp.openByUrl(srcUrl);
const srcFirst = src.getSlides()[0];
const dst = SlidesApp.getActivePresentation();
dst.insertSlide(0, srcFirst);
}
範例 C:開啟後「直接進簡報播放模式」連結
function getPresentLink(url) {
const pres = SlidesApp.openByUrl(url);
const presentUrl = `https://docs.google.com/presentation/d/${pres.getId()}/present`;
Logger.log(presentUrl); // 可丟到郵件、聊天室、或生成 QR Code
}
openByUrl() vs openById():怎麼選?
資料來源:你手上是網址就 openByUrl();是ID就 openById(),能力等同。
韌性:URL 若含 /copy、/present 等路徑,建議正規化為 /d/<ID>/edit 再打開(避免非標準路徑導致錯誤)。
維護性:若後續要儲存 ID 到資料庫,第一次 openByUrl() 後可直接 getId() 萃取與持久化。
常見錯誤與雷點
1. Exception: You do not have permission to call SlidesApp.openByUrl.
可能原因:
你正從 試算表「自訂函數」 內呼叫。出於安全性考量,自訂函數不能調用需要授權的服務;請改用自訂選單、按鈕、或安裝型觸發器執行。
尚未授權或授權撤銷;或腳本後來新增了更寬的 scope,需要重新同意。自 2025 年起 IDE 也引入更細的 OAuth 同意體驗,更新程式會再要一次授權。
2. Exception: The parameters (String) don't match the method signature for SlidesApp.openByUrl.
原因:傳入不是有效的簡報網址。請確認:
網址必須是 Google Slides 的 https://docs.google.com/presentation/d/<ID>/...;不要貼到檔案的分享對話框內嵌頁或「檔案預覽器」的轉址。
3. We're sorry, a server error occurred. Please wait a bit and try again. 或 429 Too Many Requests
原因:達到每分鐘 API/服務配額。對批次作業加入分段與退避(例如處理 50 張投影片就 Utilities.sleep(500))。Slides API 官方說明超量會回 429,建議指數退避。
4. Exception: You do not have access to this presentation.
原因:執行者無存取權。請確認共用對象含「執行腳本的人/服務帳戶」,或把腳本改為以執行者身分執行(在你的 Workspace 政策允許下)。
5. 自動化流程中,偶發授權再次彈出
原因:你的變更動到 scopes;或使用者曾在「Google 帳戶 → 安全性 → 第三方存取」撤銷權限。
最佳實務(避免踩雷、讓腳本更耐用)
1. 標準化 URL
拿到任何分享連結,先轉成 /d/<ID>/edit 格式再 openByUrl():
function normalizeSlidesUrl(url) {
const m = url.match(/\/presentation\/d\/([a-zA-Z0-9_-]+)/);
if (!m) throw new Error('不是有效的簡報網址');
return `https://docs.google.com/presentation/d/${m[1]}/edit`;
}
2. 授權明確化
專案設定開啟 appsscript.json,必要時手動設定最小範圍(尤其 Add-on 或要通過資安審查的專案)。官方建議理解並維持最小授權。
3. 別用自訂函數呼叫 Slides 服務
所有需要 OAuth 的服務都不該從試算表自訂函數觸發,改用選單、側欄 UI 或安裝型觸發器。
4. 配額友善
批次處理大量投影片時:
以「頁、元件」為單位分段處理,加入 sleep 或退避。
重複讀寫盡量合併(一次取 getSlides() 後重用)。
超量時的錯誤處理加上 retry。Slides/Apps Script 的配額文件可作為參照基準。
5. 權限前置檢查
第一次跑流程前,安排「健康檢查」:嘗試讀標題、寫入註解,提早暴露權限問題。
6. URL 與 ID 的雙軌支援
封裝成「我給 URL 或 ID 都能打開」的 helper,減少未來搬遷成本:
function openPresentation(input) {
if (/^https?:\/\//i.test(input)) return SlidesApp.openByUrl(normalizeSlidesUrl(input));
return SlidesApp.openById(input);
}
延伸操作:拿到 Presentation 之後能做什麼?
頁面尺寸(做版面自動排版會用到):getPageWidth()、getPageHeight()。
選取內容(互動工具/教學插件常見):getSelection().getCurrentPage()。
管理編輯者:讀取可編輯者清單(結合流程稽核)。
以上皆屬 Presentation 或 Slides 服務的成員能力。
完整實戰:從表單回應接到簡報 URL → 套版 → 生成播放連結
/**
* 1) 從表單或試算表收到簡報URL
* 2) 打開簡報,依表單欄位替換品牌名/日期/講者
* 3) 生成 /present 播放連結(給講者上台即用)
*/
function buildDeckFromUrl(url, brand, speaker, dateStr) {
const pres = SlidesApp.openByUrl(normalizeSlidesUrl(url)); // 步驟化保險
const slides = pres.getSlides();
slides.forEach(slide => {
slide.getShapes().forEach(shape => {
const text = shape.getText();
if (!text) return;
text.replaceAllText('{{BRAND}}', brand);
text.replaceAllText('{{SPEAKER}}', speaker);
text.replaceAllText('{{DATE}}', dateStr);
});
});
// 做一頁封面如果沒有
if (slides.length === 0) pres.appendSlide(SlidesApp.PredefinedLayout.TITLE);
// 產生播放連結
const presentUrl = `https://docs.google.com/presentation/d/${pres.getId()}/present`;
Logger.log(`播放連結:${presentUrl}`);
}
複製投影片、替換文字等高頻任務,GAS 原生 Slides 服務已內建方法,維護成本低。
問題集
Q1:openByUrl() 與 openById(),哪個比較快?
兩者都只是「取得同一份檔案的入口」,差異極小。若你已經在流程中持久化了 ID,就用 openById(),少一步解析。
Q2:可不可以從自訂函數(試算表儲存格)直接 openByUrl()?
不建議且多半會被擋下。需要授權的服務(Slides、Drive…)不允許於自訂函數中執行;請改走選單、側欄或觸發器。
Q3:遇到配額(429)要怎麼辦?
控制速率、分批、指數退避;官方明確建議超量時要回退並重試。
Q4:為什麼跑一陣子又跳出要重新授權?
腳本更新需求的 scope 變了、或使用者撤銷過權限。2025 年起 IDE 的細粒度授權也更常見於變更後再詢問。
Q5:我只拿得到像 /copy 或 /present 的網址,能用嗎?
先用上文的 normalizeSlidesUrl() 轉回 /d/<ID>/edit 結構,再 openByUrl(),穩定度較高。官方文件示例亦以 /d/<ID>/edit 為準。
