# Google Apps Script 能開發什麼?深入淺出完整應用指南 ![Google Apps Script 自動化工作流程](https://hackmd.io/_uploads/rylsP7OJOge.jpg) ## 前言:一個改變工作效率的真實故事 小華是一名行銷專員,每週都要花費 3 小時整理客戶回饋資料:從 Gmail 收集客戶意見、整理到 Google Sheets、更新客戶資料庫、然後發送感謝信件。這個重複性工作不僅耗時,還容易出錯。 直到他發現了 Google Apps Script(GAS),一切都改變了。現在,這個過程完全自動化:當客戶回饋信件進入 Gmail 時,系統自動擷取內容、分類整理到試算表、更新客戶資料、甚至自動發送個人化的感謝信件。原本 3 小時的工作,現在只需要 5 分鐘檢查結果。 這就是 Google Apps Script 的威力 —— 讓你的 Google Workspace 變成一個強大的自動化工作站。 ## 目錄 1. [什麼是 Google Apps Script?](#什麼是-google-apps-script) 2. [環境設置與入門](#環境設置與入門) 3. [六個實用開發範例](#六個實用開發範例) - [範例 1:自動發送 Gmail 郵件](#範例-1自動發送-gmail-郵件) - [範例 2:Google Sheets 資料處理與分析](#範例-2google-sheets-資料處理與分析) - [範例 3:Google Calendar 自動化排程](#範例-3google-calendar-自動化排程) - [範例 4:多服務串聯自動化](#範例-4多服務串聯自動化) - [範例 5:網頁爬蟲與資料蒐集](#範例-5網頁爬蟲與資料蒐集) - [範例 6:AI 服務整合(Gemini API)](#範例-6ai-服務整合gemini-api) 4. [性能優化技巧](#性能優化技巧) 5. [與其他自動化工具比較](#與其他自動化工具比較) 6. [2024-2025 最新趨勢](#2024-2025-最新趨勢) 7. [實用資源與學習路徑](#實用資源與學習路徑) 8. [總結與未來展望](#總結與未來展望) ## 什麼是 Google Apps Script? Google Apps Script(GAS)是 Google 提供的雲端腳本平台,基於 JavaScript 語言,讓你能夠自動化和擴展 Google Workspace 應用程式的功能。簡單來說,它就像是 Google 服務之間的「膠水」,能夠串聯 Gmail、Google Sheets、Google Drive、Google Calendar 等服務。 ![Google Apps Script 服務整合架構](https://hackmd.io/_uploads/rkxY7dkdxg.jpg) ### 核心特色 - **雲端執行**:無需安裝任何軟體,直接在瀏覽器中開發 - **深度整合**:與 Google Workspace 服務無縫整合 - **自動觸發**:支援時間觸發、事件觸發等多種執行方式 - **免費使用**:在合理使用範圍內完全免費 - **JavaScript 基礎**:使用熟悉的 JavaScript 語法 ### 適用場景 - 報表自動化生成 - 郵件批量處理 - 資料同步與備份 - 工作流程自動化 - API 整合與資料擷取 - 自訂功能擴展 ## 環境設置與入門 ### 開始使用 Google Apps Script 1. **訪問 Google Apps Script** - 前往 [script.google.com](https://script.google.com) - 使用 Google 帳號登入 2. **建立新專案** - 點選「新增專案」 - 選擇適當的專案名稱 3. **基本介面介紹** - 程式碼編輯器:撰寫 JavaScript 程式碼 - 執行功能:測試和除錯程式碼 - 觸發器管理:設定自動執行條件 - 權限管理:管理 API 存取權限 ### Hello World 範例 ```javascript /** * 第一個 Google Apps Script 程式 * 功能:在日誌中顯示 "Hello, World!" */ function helloWorld() { // 使用 console.log 在執行日誌中顯示訊息 console.log("Hello, World!"); // 使用 Logger.log 也可以記錄訊息(舊版方法) Logger.log("這是我的第一個 GAS 程式!"); } ``` ## 六個實用開發範例 ### 範例 1:自動發送 Gmail 郵件 這個範例展示如何使用 GAS 自動發送個人化郵件,適合用於客戶通知、生日祝福、或定期報告發送。 ```javascript /** * 自動發送 Gmail 郵件 * 功能:從 Google Sheets 讀取收件人資料,發送個人化郵件 */ function sendAutomatedEmails() { // 開啟指定的 Google Sheets const spreadsheetId = 'YOUR_SPREADSHEET_ID'; const sheet = SpreadsheetApp.openById(spreadsheetId).getActiveSheet(); // 取得資料範圍(假設 A 欄是姓名,B 欄是郵件地址,C 欄是狀態) const dataRange = sheet.getRange('A2:C'); const data = dataRange.getValues(); // 郵件模板設定 const emailTemplate = { subject: '感謝您的支持 - 個人化通知', htmlBody: ` <div style="font-family: Arial, sans-serif; max-width: 600px;"> <h2 style="color: #4285f4;">親愛的 {{name}},</h2> <p>感謝您一直以來的支持!</p> <p>這是一封透過 Google Apps Script 自動發送的個人化郵件。</p> <hr> <p style="color: #666; font-size: 12px;"> 此郵件由系統自動發送,請勿直接回覆。 </p> </div> ` }; // 遍歷每一列資料 data.forEach((row, index) => { const [name, email, status] = row; // 檢查是否已發送(避免重複發送) if (status !== '已發送' && email && name) { try { // 替換郵件模板中的個人化內容 const personalizedBody = emailTemplate.htmlBody.replace('{{name}}', name); // 發送郵件 GmailApp.sendEmail( email, emailTemplate.subject, '', // 純文字內容(可為空) { htmlBody: personalizedBody, name: '您的名稱或公司名稱' // 寄件者顯示名稱 } ); // 更新狀態為已發送 sheet.getRange(index + 2, 3).setValue('已發送'); console.log(`郵件已發送給:${name} (${email})`); // 加入延遲避免觸發 Gmail 限制 Utilities.sleep(1000); } catch (error) { console.error(`發送郵件給 ${name} 時出錯:`, error); sheet.getRange(index + 2, 3).setValue('發送失敗'); } } }); console.log('批量郵件發送完成!'); } /** * 設定定時觸發器 * 每天上午 9 點自動執行郵件發送 */ function createEmailTrigger() { ScriptApp.newTrigger('sendAutomatedEmails') .timeBased() .everyDays(1) .atHour(9) .create(); } ``` **使用步驟:** 1. 建立 Google Sheets,包含姓名、郵件地址、狀態三欄 2. 替換程式碼中的 `YOUR_SPREADSHEET_ID` 3. 執行 `createEmailTrigger()` 設定自動觸發 4. 測試 `sendAutomatedEmails()` 功能 ### 範例 2:Google Sheets 資料處理與分析 這個範例展示如何使用 GAS 進行資料清理、分析和報表生成。 ```javascript /** * Google Sheets 資料處理與分析 * 功能:自動清理資料、計算統計資料、生成報表 */ function processSpreadsheetData() { const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); const dataSheet = spreadsheet.getSheetByName('原始資料'); const reportSheet = getOrCreateSheet(spreadsheet, '分析報表'); // 取得原始資料 const dataRange = dataSheet.getDataRange(); const data = dataRange.getValues(); const headers = data[0]; const rows = data.slice(1); console.log(`開始處理 ${rows.length} 筆資料...`); // 資料清理 const cleanedData = cleanData(rows); console.log(`資料清理完成,有效資料:${cleanedData.length} 筆`); // 生成分析報表 generateReport(reportSheet, cleanedData, headers); console.log('資料處理與分析完成!'); } /** * 資料清理函數 * @param {Array} data - 原始資料陣列 * @return {Array} - 清理後的資料 */ function cleanData(data) { return data.filter(row => { // 移除空白列 if (row.every(cell => !cell)) return false; // 移除無效的郵件地址(假設第二欄是郵件) const email = row[1]; if (email && !isValidEmail(email)) return false; return true; }).map(row => { // 資料標準化 return row.map(cell => { if (typeof cell === 'string') { // 移除多餘空白 return cell.trim(); } return cell; }); }); } /** * 郵件格式驗證 * @param {string} email - 郵件地址 * @return {boolean} - 是否為有效郵件 */ function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * 生成分析報表 * @param {Sheet} sheet - 報表工作表 * @param {Array} data - 清理後的資料 * @param {Array} headers - 欄位標題 */ function generateReport(sheet, data, headers) { // 清除現有內容 sheet.clear(); // 設定報表標題 sheet.getRange('A1').setValue('資料分析報表'); sheet.getRange('A1').setFontSize(16).setFontWeight('bold'); // 基本統計資訊 const stats = [ ['生成時間', new Date()], ['總資料筆數', data.length], ['資料完整性', `${(data.length / (data.length + 100)) * 100}%`] // 假設統計 ]; // 寫入統計資訊 let currentRow = 3; stats.forEach(([label, value]) => { sheet.getRange(currentRow, 1).setValue(label); sheet.getRange(currentRow, 2).setValue(value); currentRow++; }); // 如果資料包含數值欄位,進行進階分析 if (data.length > 0) { currentRow += 2; sheet.getRange(currentRow, 1).setValue('詳細分析'); sheet.getRange(currentRow, 1).setFontWeight('bold'); currentRow++; // 假設第三欄是數值資料進行分析 const numericData = data .map(row => parseFloat(row[2])) .filter(num => !isNaN(num)); if (numericData.length > 0) { const average = numericData.reduce((sum, num) => sum + num, 0) / numericData.length; const max = Math.max(...numericData); const min = Math.min(...numericData); const analyticsData = [ ['平均值', average.toFixed(2)], ['最大值', max], ['最小值', min], ['資料範圍', `${min} - ${max}`] ]; analyticsData.forEach(([label, value]) => { sheet.getRange(currentRow, 1).setValue(label); sheet.getRange(currentRow, 2).setValue(value); currentRow++; }); } } // 美化報表格式 formatReport(sheet, currentRow); } /** * 報表格式美化 * @param {Sheet} sheet - 工作表 * @param {number} lastRow - 最後一列 */ function formatReport(sheet, lastRow) { // 設定欄寬 sheet.autoResizeColumns(1, 2); // 設定邊框 const range = sheet.getRange(1, 1, lastRow, 2); range.setBorder(true, true, true, true, true, true); // 設定交替列顏色 for (let i = 3; i <= lastRow; i += 2) { sheet.getRange(i, 1, 1, 2).setBackground('#f8f9fa'); } } /** * 取得或建立工作表 * @param {Spreadsheet} spreadsheet - 試算表物件 * @param {string} sheetName - 工作表名稱 * @return {Sheet} - 工作表物件 */ function getOrCreateSheet(spreadsheet, sheetName) { let sheet = spreadsheet.getSheetByName(sheetName); if (!sheet) { sheet = spreadsheet.insertSheet(sheetName); } return sheet; } /** * 設定定期資料處理觸發器 */ function setupDataProcessingTrigger() { // 每週一上午 8 點執行 ScriptApp.newTrigger('processSpreadsheetData') .timeBased() .onWeekDay(ScriptApp.WeekDay.MONDAY) .atHour(8) .create(); } ``` ### 範例 3:Google Calendar 自動化排程 這個範例展示如何自動建立行事曆事件、管理會議室預約、發送提醒等功能。 ```javascript /** * Google Calendar 自動化排程 * 功能:從 Sheets 讀取會議資料,自動建立行事曆事件 */ function createCalendarEvents() { const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); const sheet = spreadsheet.getSheetByName('會議安排'); // 取得會議資料(假設格式:標題、開始時間、結束時間、參與者、地點、說明) const dataRange = sheet.getRange('A2:G'); const meetings = dataRange.getValues(); // 取得預設行事曆 const calendar = CalendarApp.getDefaultCalendar(); meetings.forEach((meeting, index) => { const [title, startTime, endTime, attendees, location, description, status] = meeting; // 跳過空白列或已處理的會議 if (!title || status === '已建立') return; try { // 建立行事曆事件 const event = calendar.createEvent( title, new Date(startTime), new Date(endTime), { description: description || '', location: location || '', guests: attendees || '', sendInvites: true // 自動發送邀請 } ); // 設定提醒 event.addEmailReminder(30); // 30 分鐘前郵件提醒 event.addPopupReminder(15); // 15 分鐘前彈出提醒 // 更新狀態 sheet.getRange(index + 2, 7).setValue('已建立'); console.log(`已建立會議:${title}`); } catch (error) { console.error(`建立會議 "${title}" 時出錯:`, error); sheet.getRange(index + 2, 7).setValue('建立失敗'); } }); } /** * 會議室可用性檢查 * @param {string} roomEmail - 會議室郵件地址 * @param {Date} startTime - 開始時間 * @param {Date} endTime - 結束時間 * @return {boolean} - 是否可用 */ function checkRoomAvailability(roomEmail, startTime, endTime) { try { const calendar = CalendarApp.getCalendarById(roomEmail); if (!calendar) return false; const events = calendar.getEvents(startTime, endTime); return events.length === 0; // 沒有事件表示可用 } catch (error) { console.error(`檢查會議室 ${roomEmail} 可用性時出錯:`, error); return false; } } /** * 智能會議室預約 * 功能:自動尋找可用會議室並預約 */ function smartRoomBooking() { const sheet = SpreadsheetApp.getActiveSheet(); const dataRange = sheet.getRange('A2:H'); const meetings = dataRange.getValues(); // 會議室清單(實際使用時請替換為真實的會議室郵件) const meetingRooms = [ { name: '小會議室A', email: 'room-a@company.com', capacity: 6 }, { name: '大會議室B', email: 'room-b@company.com', capacity: 12 }, { name: '視訊會議室C', email: 'room-c@company.com', capacity: 8 } ]; meetings.forEach((meeting, index) => { const [title, startTime, endTime, attendees, requiredCapacity, , , status] = meeting; if (!title || status === '已預約') return; const participantCount = attendees ? attendees.split(',').length : 1; const capacity = requiredCapacity || participantCount; // 尋找合適的會議室 const availableRoom = findAvailableRoom( meetingRooms, new Date(startTime), new Date(endTime), capacity ); if (availableRoom) { try { // 預約會議室 const roomCalendar = CalendarApp.getCalendarById(availableRoom.email); roomCalendar.createEvent( `[預約] ${title}`, new Date(startTime), new Date(endTime), { description: `會議:${title}\n參與人數:${participantCount}`, guests: attendees || '' } ); // 更新會議室資訊 sheet.getRange(index + 2, 6).setValue(availableRoom.name); sheet.getRange(index + 2, 8).setValue('已預約'); console.log(`已為 "${title}" 預約 ${availableRoom.name}`); } catch (error) { console.error(`預約會議室失敗:`, error); sheet.getRange(index + 2, 8).setValue('預約失敗'); } } else { sheet.getRange(index + 2, 8).setValue('無可用會議室'); } }); } /** * 尋找可用會議室 * @param {Array} rooms - 會議室清單 * @param {Date} startTime - 開始時間 * @param {Date} endTime - 結束時間 * @param {number} requiredCapacity - 所需容量 * @return {Object|null} - 可用的會議室或 null */ function findAvailableRoom(rooms, startTime, endTime, requiredCapacity) { // 按容量排序,優先使用剛好符合需求的會議室 const suitableRooms = rooms .filter(room => room.capacity >= requiredCapacity) .sort((a, b) => a.capacity - b.capacity); for (const room of suitableRooms) { if (checkRoomAvailability(room.email, startTime, endTime)) { return room; } } return null; } /** * 會議提醒系統 * 功能:在會議前一天發送提醒郵件 */ function sendMeetingReminders() { const calendar = CalendarApp.getDefaultCalendar(); const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(0, 0, 0, 0); const dayAfterTomorrow = new Date(tomorrow); dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 1); // 取得明天的會議 const events = calendar.getEvents(tomorrow, dayAfterTomorrow); events.forEach(event => { const attendees = event.getGuestList(); if (attendees.length === 0) return; const reminderEmail = { subject: `會議提醒:${event.getTitle()}`, htmlBody: ` <div style="font-family: Arial, sans-serif;"> <h2>會議提醒</h2> <p><strong>會議主題:</strong>${event.getTitle()}</p> <p><strong>時間:</strong>${event.getStartTime().toLocaleDateString()} ${event.getStartTime().toLocaleTimeString()} - ${event.getEndTime().toLocaleTimeString()}</p> <p><strong>地點:</strong>${event.getLocation() || '未指定'}</p> <p><strong>說明:</strong></p> <p>${event.getDescription() || '無'}</p> <hr> <p style="color: #666; font-size: 12px;"> 此提醒由系統自動發送 </p> </div> ` }; // 發送給所有參與者 attendees.forEach(guest => { try { GmailApp.sendEmail( guest.getEmail(), reminderEmail.subject, '', { htmlBody: reminderEmail.htmlBody } ); } catch (error) { console.error(`發送提醒給 ${guest.getEmail()} 失敗:`, error); } }); }); console.log(`已發送 ${events.length} 個會議的提醒`); } ``` ### 範例 4:多服務串聯自動化 這個範例展示如何整合多個 Google 服務,建立完整的工作流程自動化。 ```javascript /** * 多服務串聯自動化:客戶回饋處理系統 * 整合 Gmail、Sheets、Drive、Calendar 的完整工作流程 */ /** * 主要工作流程 * 1. 監控 Gmail 新郵件 * 2. 提取客戶回饋內容 * 3. 分類並儲存到 Sheets * 4. 生成回覆郵件 * 5. 安排後續追蹤行程 * 6. 備份到 Drive */ function processCustomerFeedbackWorkflow() { console.log('開始執行客戶回饋處理工作流程...'); // 步驟 1: 監控並處理新的客戶回饋郵件 const newFeedback = monitorGmailFeedback(); if (newFeedback.length === 0) { console.log('沒有新的客戶回饋郵件'); return; } // 步驟 2: 處理每個回饋 newFeedback.forEach(feedback => { try { // 步驟 3: 儲存到試算表 const recordId = saveFeedbackToSheet(feedback); // 步驟 4: 分析情感並分類 const analysis = analyzeFeedbackSentiment(feedback.body); updateFeedbackAnalysis(recordId, analysis); // 步驟 5: 生成並發送回覆 if (analysis.requiresResponse) { sendAutoReply(feedback, analysis); } // 步驟 6: 安排後續追蹤 if (analysis.priority === 'high') { scheduleFollowUp(feedback, analysis); } // 步驟 7: 標記郵件為已處理 markEmailAsProcessed(feedback.thread); console.log(`成功處理來自 ${feedback.sender} 的回饋`); } catch (error) { console.error(`處理回饋時出錯:`, error); } }); // 步驟 8: 生成日報表 generateDailyReport(); console.log('客戶回饋處理工作流程完成'); } /** * 監控 Gmail 中的新客戶回饋 * @return {Array} 新的回饋郵件陣列 */ function monitorGmailFeedback() { // 搜尋特定標籤或主題的未讀郵件 const searchQuery = 'label:customer-feedback is:unread'; const threads = GmailApp.search(searchQuery, 0, 50); const feedbackEmails = []; threads.forEach(thread => { const messages = thread.getMessages(); messages.forEach(message => { if (message.isUnread()) { feedbackEmails.push({ thread: thread, message: message, sender: message.getFrom(), subject: message.getSubject(), body: message.getPlainBody(), receivedTime: message.getDate(), attachments: message.getAttachments() }); } }); }); return feedbackEmails; } /** * 儲存回饋到 Google Sheets * @param {Object} feedback - 回饋物件 * @return {string} 記錄 ID */ function saveFeedbackToSheet(feedback) { const sheet = getOrCreateFeedbackSheet(); const recordId = Utilities.getUuid(); const rowData = [ recordId, feedback.receivedTime, feedback.sender, feedback.subject, feedback.body, '', // 情感分析結果 '', // 優先級 '', // 處理狀態 '', // 回覆時間 '', // 備註 ]; sheet.appendRow(rowData); return recordId; } /** * 取得或建立客戶回饋工作表 * @return {Sheet} 工作表物件 */ function getOrCreateFeedbackSheet() { const spreadsheet = SpreadsheetApp.openById('YOUR_FEEDBACK_SPREADSHEET_ID'); let sheet = spreadsheet.getSheetByName('客戶回饋'); if (!sheet) { sheet = spreadsheet.insertSheet('客戶回饋'); // 設定標題列 const headers = [ '記錄ID', '接收時間', '寄件者', '主題', '內容', '情感分析', '優先級', '處理狀態', '回覆時間', '備註' ]; sheet.getRange(1, 1, 1, headers.length).setValues([headers]); sheet.getRange(1, 1, 1, headers.length).setFontWeight('bold'); } return sheet; } /** * 分析回饋情感和優先級 * @param {string} feedbackText - 回饋文本 * @return {Object} 分析結果 */ function analyzeFeedbackSentiment(feedbackText) { // 簡單的關鍵字分析(實際應用中可整合 AI API) const positiveKeywords = ['滿意', '好', '棒', '感謝', '讚', '優秀']; const negativeKeywords = ['不滿', '差', '爛', '問題', '錯誤', '投訴']; const urgentKeywords = ['緊急', '立即', '急', '重要', '嚴重']; const text = feedbackText.toLowerCase(); let sentiment = 'neutral'; let priority = 'normal'; let requiresResponse = false; // 情感分析 const positiveScore = positiveKeywords.filter(keyword => text.includes(keyword)).length; const negativeScore = negativeKeywords.filter(keyword => text.includes(keyword)).length; if (negativeScore > positiveScore) { sentiment = 'negative'; requiresResponse = true; } else if (positiveScore > negativeScore) { sentiment = 'positive'; } // 優先級判斷 if (urgentKeywords.some(keyword => text.includes(keyword)) || sentiment === 'negative') { priority = 'high'; requiresResponse = true; } return { sentiment, priority, requiresResponse, analysis: `情感:${sentiment}, 優先級:${priority}` }; } /** * 更新回饋分析結果 * @param {string} recordId - 記錄 ID * @param {Object} analysis - 分析結果 */ function updateFeedbackAnalysis(recordId, analysis) { const sheet = getOrCreateFeedbackSheet(); const data = sheet.getDataRange().getValues(); // 尋找對應的記錄 for (let i = 0; i < data.length; i++) { if (data[i][0] === recordId) { sheet.getRange(i + 1, 6).setValue(analysis.sentiment); sheet.getRange(i + 1, 7).setValue(analysis.priority); sheet.getRange(i + 1, 8).setValue('已分析'); break; } } } /** * 發送自動回覆 * @param {Object} feedback - 原始回饋 * @param {Object} analysis - 分析結果 */ function sendAutoReply(feedback, analysis) { let replyTemplate; if (analysis.sentiment === 'positive') { replyTemplate = ` <div style="font-family: Arial, sans-serif;"> <p>親愛的客戶,您好:</p> <p>感謝您給予我們正面的回饋!您的支持是我們持續改進的動力。</p> <p>如果您有任何其他建議,歡迎隨時與我們聯繫。</p> <br> <p>祝您順心!</p> <p>客服團隊</p> </div> `; } else if (analysis.sentiment === 'negative') { replyTemplate = ` <div style="font-family: Arial, sans-serif;"> <p>親愛的客戶,您好:</p> <p>非常感謝您的回饋,我們對造成您的不便深感抱歉。</p> <p>我們已將您的意見轉交給相關部門處理,會在 24 小時內有專人與您聯繫。</p> <p>再次為您帶來的困擾致歉,我們會持續改善服務品質。</p> <br> <p>祝您順心!</p> <p>客服團隊</p> </div> `; } if (replyTemplate) { feedback.thread.reply('', { htmlBody: replyTemplate, subject: `Re: ${feedback.subject}` }); console.log(`已發送自動回覆給:${feedback.sender}`); } } /** * 安排後續追蹤行程 * @param {Object} feedback - 回饋物件 * @param {Object} analysis - 分析結果 */ function scheduleFollowUp(feedback, analysis) { const calendar = CalendarApp.getDefaultCalendar(); const followUpDate = new Date(); followUpDate.setHours(followUpDate.getHours() + 24); // 24 小時後追蹤 const endTime = new Date(followUpDate); endTime.setHours(endTime.getHours() + 1); calendar.createEvent( `追蹤客戶回饋:${feedback.sender}`, followUpDate, endTime, { description: ` 客戶:${feedback.sender} 原始主題:${feedback.subject} 優先級:${analysis.priority} 情感:${analysis.sentiment} 回饋內容: ${feedback.body.substring(0, 200)}... `, reminders: [ { method: 'email', minutes: 60 }, { method: 'popup', minutes: 15 } ] } ); console.log(`已安排追蹤行程:${feedback.sender}`); } /** * 標記郵件為已處理 * @param {GmailThread} thread - Gmail 對話串 */ function markEmailAsProcessed(thread) { // 加上「已處理」標籤 const processedLabel = GmailApp.getUserLabelByName('已處理') || GmailApp.createLabel('已處理'); thread.addLabel(processedLabel); // 移除「customer-feedback」標籤以避免重複處理 const feedbackLabel = GmailApp.getUserLabelByName('customer-feedback'); if (feedbackLabel) { thread.removeLabel(feedbackLabel); } // 標記為已讀 thread.markRead(); } /** * 生成每日報表 */ function generateDailyReport() { const sheet = getOrCreateFeedbackSheet(); const today = new Date(); const todayStr = Utilities.formatDate(today, Session.getScriptTimeZone(), 'yyyy-MM-dd'); // 統計今日處理的回饋 const data = sheet.getDataRange().getValues(); const todayFeedback = data.filter(row => { if (row[1] instanceof Date) { const dateStr = Utilities.formatDate(row[1], Session.getScriptTimeZone(), 'yyyy-MM-dd'); return dateStr === todayStr; } return false; }); const stats = { total: todayFeedback.length, positive: todayFeedback.filter(row => row[5] === 'positive').length, negative: todayFeedback.filter(row => row[5] === 'negative').length, high_priority: todayFeedback.filter(row => row[6] === 'high').length }; // 發送報表給管理者 const reportEmail = ` <div style="font-family: Arial, sans-serif;"> <h2>客戶回饋日報表 - ${todayStr}</h2> <table border="1" style="border-collapse: collapse;"> <tr><td>總計</td><td>${stats.total}</td></tr> <tr><td>正面回饋</td><td>${stats.positive}</td></tr> <tr><td>負面回饋</td><td>${stats.negative}</td></tr> <tr><td>高優先級</td><td>${stats.high_priority}</td></tr> </table> <p>詳細資料請查看 <a href="${sheet.getParent().getUrl()}">客戶回饋試算表</a></p> </div> `; GmailApp.sendEmail( 'manager@company.com', // 請替換為實際的管理者郵件 `客戶回饋日報表 - ${todayStr}`, '', { htmlBody: reportEmail } ); } /** * 設定工作流程觸發器 */ function setupWorkflowTriggers() { // 每 30 分鐘檢查一次新郵件 ScriptApp.newTrigger('processCustomerFeedbackWorkflow') .timeBased() .everyMinutes(30) .create(); // 每天下午 6 點生成日報表 ScriptApp.newTrigger('generateDailyReport') .timeBased() .everyDays(1) .atHour(18) .create(); } ``` ### 範例 5:網頁爬蟲與資料蒐集 這個範例展示如何使用 GAS 進行網頁資料擷取和 API 整合。 ```javascript /** * 網頁爬蟲與資料蒐集 * 功能:自動擷取網站資料、API 整合、資料清理與儲存 */ /** * 主要資料蒐集工作流程 */ function dataCollectionWorkflow() { console.log('開始執行資料蒐集工作流程...'); try { // 1. 蒐集新聞資料 const newsData = collectNewsData(); // 2. 蒐集股價資料 const stockData = collectStockData(); // 3. 蒐集天氣資料 const weatherData = collectWeatherData(); // 4. 整合資料並儲存 const consolidatedData = { timestamp: new Date(), news: newsData, stocks: stockData, weather: weatherData }; saveDataToSheet(consolidatedData); // 5. 生成分析報告 generateAnalysisReport(consolidatedData); console.log('資料蒐集工作流程完成'); } catch (error) { console.error('資料蒐集過程中發生錯誤:', error); sendErrorNotification(error); } } /** * 蒐集新聞資料 * @return {Array} 新聞資料陣列 */ function collectNewsData() { const newsUrls = [ 'https://jsonplaceholder.typicode.com/posts', // 示例 API // 實際使用時請替換為真實的新聞 API ]; const allNewsData = []; newsUrls.forEach(url => { try { const response = UrlFetchApp.fetch(url, { method: 'GET', headers: { 'User-Agent': 'Google Apps Script Data Collector', 'Accept': 'application/json' }, muteHttpExceptions: true }); if (response.getResponseCode() === 200) { const data = JSON.parse(response.getContentText()); // 資料處理和清理 const processedData = data.slice(0, 5).map(item => ({ id: item.id, title: item.title, content: item.body, source: 'JSON Placeholder', timestamp: new Date() })); allNewsData.push(...processedData); } else { console.error(`取得新聞資料失敗,HTTP 狀態碼:${response.getResponseCode()}`); } } catch (error) { console.error('蒐集新聞資料時出錯:', error); } // 避免過於頻繁的請求 Utilities.sleep(1000); }); console.log(`成功蒐集 ${allNewsData.length} 筆新聞資料`); return allNewsData; } /** * 蒐集股價資料 * @return {Array} 股價資料陣列 */ function collectStockData() { // 使用 Google Finance 函數或外部 API const stockSymbols = ['AAPL', 'GOOGL', 'MSFT', 'TSLA']; const stockData = []; stockSymbols.forEach(symbol => { try { // 這裡使用模擬資料,實際使用時可整合真實的股價 API const simulatedPrice = Math.random() * 1000 + 100; const simulatedChange = (Math.random() - 0.5) * 20; stockData.push({ symbol: symbol, price: simulatedPrice.toFixed(2), change: simulatedChange.toFixed(2), changePercent: ((simulatedChange / simulatedPrice) * 100).toFixed(2), timestamp: new Date() }); // 實際的股價 API 呼叫範例: /* const apiKey = 'YOUR_API_KEY'; const url = `https://api.example.com/stock/${symbol}?apikey=${apiKey}`; const response = UrlFetchApp.fetch(url); const data = JSON.parse(response.getContentText()); // 處理回傳的資料... */ } catch (error) { console.error(`取得 ${symbol} 股價資料時出錯:`, error); } }); console.log(`成功蒐集 ${stockData.length} 檔股票資料`); return stockData; } /** * 蒐集天氣資料 * @return {Object} 天氣資料物件 */ function collectWeatherData() { const cities = ['台北', '台中', '高雄']; const weatherData = {}; cities.forEach(city => { try { // 這裡使用模擬資料,實際使用時可整合氣象 API weatherData[city] = { temperature: Math.floor(Math.random() * 15) + 20, // 20-35度 humidity: Math.floor(Math.random() * 30) + 60, // 60-90% condition: ['晴天', '多雲', '雨天'][Math.floor(Math.random() * 3)], timestamp: new Date() }; // 實際的天氣 API 呼叫範例: /* const apiKey = 'YOUR_WEATHER_API_KEY'; const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}`; const response = UrlFetchApp.fetch(url); const data = JSON.parse(response.getContentText()); // 處理回傳的資料... */ } catch (error) { console.error(`取得 ${city} 天氣資料時出錯:`, error); } }); console.log(`成功蒐集 ${Object.keys(weatherData).length} 個城市的天氣資料`); return weatherData; } /** * 網頁內容擷取器 * @param {string} url - 目標網址 * @return {string} 擷取的內容 */ function scrapeWebContent(url) { try { const response = UrlFetchApp.fetch(url, { method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0 (compatible; Google Apps Script)' }, followRedirects: true, muteHttpExceptions: true }); if (response.getResponseCode() !== 200) { throw new Error(`HTTP 錯誤:${response.getResponseCode()}`); } const htmlContent = response.getContentText(); // 簡單的 HTML 內容清理(移除標籤) const cleanContent = htmlContent .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // 移除 script .replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '') // 移除 style .replace(/<[^>]*>/g, '') // 移除 HTML 標籤 .replace(/\s+/g, ' ') // 合併空白字元 .trim(); return cleanContent; } catch (error) { console.error(`擷取網頁內容失敗(${url}):`, error); return null; } } /** * RSS Feed 解析器 * @param {string} rssUrl - RSS Feed URL * @return {Array} 解析後的文章陣列 */ function parseRSSFeed(rssUrl) { try { const response = UrlFetchApp.fetch(rssUrl); const xmlContent = response.getContentText(); // 使用正規表達式解析 RSS(簡化版本) const items = []; const itemRegex = /<item>(.*?)<\/item>/gs; const titleRegex = /<title><!\[CDATA\[(.*?)\]\]><\/title>|<title>(.*?)<\/title>/; const linkRegex = /<link>(.*?)<\/link>/; const descRegex = /<description><!\[CDATA\[(.*?)\]\]><\/description>|<description>(.*?)<\/description>/; const pubDateRegex = /<pubDate>(.*?)<\/pubDate>/; let match; while ((match = itemRegex.exec(xmlContent)) !== null) { const itemContent = match[1]; const titleMatch = titleRegex.exec(itemContent); const linkMatch = linkRegex.exec(itemContent); const descMatch = descRegex.exec(itemContent); const dateMatch = pubDateRegex.exec(itemContent); if (titleMatch && linkMatch) { items.push({ title: titleMatch[1] || titleMatch[2] || '', link: linkMatch[1] || '', description: descMatch ? (descMatch[1] || descMatch[2] || '') : '', pubDate: dateMatch ? new Date(dateMatch[1]) : new Date(), source: rssUrl }); } } return items; } catch (error) { console.error(`解析 RSS Feed 失敗(${rssUrl}):`, error); return []; } } /** * 儲存資料到 Google Sheets * @param {Object} data - 要儲存的資料物件 */ function saveDataToSheet(data) { const spreadsheet = SpreadsheetApp.openById('YOUR_DATA_SPREADSHEET_ID'); // 儲存新聞資料 if (data.news && data.news.length > 0) { const newsSheet = getOrCreateSheet(spreadsheet, '新聞資料'); data.news.forEach(news => { newsSheet.appendRow([ news.timestamp, news.source, news.title, news.content, news.id ]); }); } // 儲存股價資料 if (data.stocks && data.stocks.length > 0) { const stockSheet = getOrCreateSheet(spreadsheet, '股價資料'); data.stocks.forEach(stock => { stockSheet.appendRow([ stock.timestamp, stock.symbol, stock.price, stock.change, stock.changePercent ]); }); } // 儲存天氣資料 if (data.weather) { const weatherSheet = getOrCreateSheet(spreadsheet, '天氣資料'); Object.keys(data.weather).forEach(city => { const weather = data.weather[city]; weatherSheet.appendRow([ weather.timestamp, city, weather.temperature, weather.humidity, weather.condition ]); }); } console.log('資料已成功儲存到試算表'); } /** * 生成分析報告 * @param {Object} data - 資料物件 */ function generateAnalysisReport(data) { const reportContent = ` <div style="font-family: Arial, sans-serif;"> <h2>每日資料蒐集報告</h2> <p><strong>報告時間:</strong>${data.timestamp.toLocaleString()}</p> <h3>資料統計</h3> <ul> <li>新聞資料:${data.news ? data.news.length : 0} 筆</li> <li>股價資料:${data.stocks ? data.stocks.length : 0} 檔</li> <li>天氣資料:${data.weather ? Object.keys(data.weather).length : 0} 個城市</li> </ul> <h3>重點摘要</h3> ${generateSummary(data)} <hr> <p style="color: #666; font-size: 12px;"> 此報告由 Google Apps Script 自動生成 </p> </div> `; // 發送報告郵件 GmailApp.sendEmail( 'data-team@company.com', // 請替換為實際郵件地址 `每日資料蒐集報告 - ${Utilities.formatDate(data.timestamp, Session.getScriptTimeZone(), 'yyyy-MM-dd')}`, '', { htmlBody: reportContent } ); } /** * 生成資料摘要 * @param {Object} data - 資料物件 * @return {string} HTML 格式的摘要 */ function generateSummary(data) { let summary = ''; // 股價摘要 if (data.stocks && data.stocks.length > 0) { const gainers = data.stocks.filter(stock => parseFloat(stock.change) > 0); const losers = data.stocks.filter(stock => parseFloat(stock.change) < 0); summary += ` <h4>股市概況</h4> <p>上漲:${gainers.length} 檔,下跌:${losers.length} 檔</p> `; } // 天氣摘要 if (data.weather) { const temperatures = Object.values(data.weather).map(w => w.temperature); const avgTemp = temperatures.reduce((sum, temp) => sum + temp, 0) / temperatures.length; summary += ` <h4>天氣概況</h4> <p>平均溫度:${avgTemp.toFixed(1)}°C</p> `; } return summary; } /** * 取得或建立工作表 * @param {Spreadsheet} spreadsheet - 試算表物件 * @param {string} sheetName - 工作表名稱 * @return {Sheet} 工作表物件 */ function getOrCreateSheet(spreadsheet, sheetName) { let sheet = spreadsheet.getSheetByName(sheetName); if (!sheet) { sheet = spreadsheet.insertSheet(sheetName); // 根據不同類型設定標題列 let headers = []; switch (sheetName) { case '新聞資料': headers = ['時間戳記', '來源', '標題', '內容', 'ID']; break; case '股價資料': headers = ['時間戳記', '股票代碼', '價格', '變動', '變動%']; break; case '天氣資料': headers = ['時間戳記', '城市', '溫度', '濕度', '天氣狀況']; break; } if (headers.length > 0) { sheet.getRange(1, 1, 1, headers.length).setValues([headers]); sheet.getRange(1, 1, 1, headers.length).setFontWeight('bold'); } } return sheet; } /** * 錯誤通知 * @param {Error} error - 錯誤物件 */ function sendErrorNotification(error) { const errorReport = ` <div style="font-family: Arial, sans-serif;"> <h2 style="color: red;">資料蒐集錯誤通知</h2> <p><strong>錯誤時間:</strong>${new Date().toLocaleString()}</p> <p><strong>錯誤訊息:</strong>${error.message}</p> <p><strong>錯誤堆疊:</strong></p> <pre style="background: #f5f5f5; padding: 10px;">${error.stack}</pre> </div> `; GmailApp.sendEmail( 'admin@company.com', // 請替換為實際管理者郵件 '資料蒐集系統錯誤通知', '', { htmlBody: errorReport } ); } /** * 設定資料蒐集觸發器 */ function setupDataCollectionTriggers() { // 每小時執行一次資料蒐集 ScriptApp.newTrigger('dataCollectionWorkflow') .timeBased() .everyHours(1) .create(); } ``` ### 範例 6:AI 服務整合(Gemini API) ![Google Apps Script 開發場景](https://hackmd.io/_uploads/rywIQu1ull.jpg) 這個範例展示如何整合最新的 Gemini AI 服務,實現智能內容生成和分析功能。 ```javascript /** * Google Apps Script 整合 Gemini AI * 功能:智能內容生成、文本分析、自動化決策 */ // Gemini API 設定 const GEMINI_API_KEY = 'YOUR_GEMINI_API_KEY'; // 請到 Google AI Studio 取得 API Key const GEMINI_API_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent'; /** * 智能郵件回覆系統 * 使用 Gemini AI 分析郵件內容並生成個人化回覆 */ function intelligentEmailReplySystem() { console.log('開始執行智能郵件回覆系統...'); // 取得需要回覆的郵件 const threads = GmailApp.search('label:needs-reply is:unread', 0, 10); threads.forEach(thread => { const messages = thread.getMessages(); const latestMessage = messages[messages.length - 1]; if (latestMessage.isUnread()) { try { // 分析郵件內容 const emailAnalysis = analyzeEmailWithGemini(latestMessage); // 生成回覆 const reply = generateReplyWithGemini(latestMessage, emailAnalysis); // 發送回覆(可選擇手動審核) if (emailAnalysis.confidence > 0.8) { thread.reply(reply.content, { htmlBody: reply.htmlContent }); // 加上已回覆標籤 const repliedLabel = GmailApp.getUserLabelByName('AI-已回覆') || GmailApp.createLabel('AI-已回覆'); thread.addLabel(repliedLabel); console.log(`已自動回覆:${latestMessage.getSubject()}`); } else { // 信心度不足,標記為需要人工審核 const reviewLabel = GmailApp.getUserLabelByName('需要審核') || GmailApp.createLabel('需要審核'); thread.addLabel(reviewLabel); // 將建議回覆儲存到草稿 saveSuggestedReplyToDraft(thread, reply); console.log(`已準備草稿回覆:${latestMessage.getSubject()}`); } } catch (error) { console.error(`處理郵件回覆時出錯:`, error); } } }); } /** * 使用 Gemini AI 分析郵件內容 * @param {GmailMessage} message - Gmail 訊息物件 * @return {Object} 分析結果 */ function analyzeEmailWithGemini(message) { const prompt = ` 請分析以下郵件內容,並回傳 JSON 格式的分析結果: 寄件者:${message.getFrom()} 主旨:${message.getSubject()} 內容:${message.getPlainBody()} 請分析: 1. 郵件類型(inquiry、complaint、praise、request、other) 2. 緊急程度(1-5,5為最緊急) 3. 情感極性(positive、neutral、negative) 4. 主要議題 5. 建議的回覆語調(formal、friendly、apologetic、enthusiastic) 6. AI回覆信心度(0-1,1為最有信心) 請回傳格式如下的 JSON: { "type": "inquiry", "urgency": 3, "sentiment": "neutral", "topics": ["產品詢問", "價格"], "tone": "friendly", "confidence": 0.85 } `; try { const response = callGeminiAPI(prompt); const analysisText = response.candidates[0].content.parts[0].text; // 提取 JSON 內容 const jsonMatch = analysisText.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } else { throw new Error('無法解析 AI 回應'); } } catch (error) { console.error('郵件分析失敗:', error); return { type: 'other', urgency: 3, sentiment: 'neutral', topics: [], tone: 'friendly', confidence: 0.5 }; } } /** * 使用 Gemini AI 生成回覆內容 * @param {GmailMessage} message - 原始郵件 * @param {Object} analysis - 分析結果 * @return {Object} 回覆內容 */ function generateReplyWithGemini(message, analysis) { const prompt = ` 基於以下郵件和分析結果,生成一個專業的回覆: 原始郵件: 寄件者:${message.getFrom()} 主旨:${message.getSubject()} 內容:${message.getPlainBody()} 分析結果: 類型:${analysis.type} 情感:${analysis.sentiment} 語調:${analysis.tone} 主要議題:${analysis.topics.join(', ')} 請生成一個${analysis.tone}語調的回覆,內容應該: 1. 回應所有提到的議題 2. 保持專業但友善的態度 3. 如果是投訴,要表達歉意並提供解決方案 4. 如果是詢問,要提供有用的資訊 5. 包含適當的結尾和簽名 請同時提供純文字版本和 HTML 版本。 `; try { const response = callGeminiAPI(prompt); const replyText = response.candidates[0].content.parts[0].text; // 簡單的 HTML 格式化 const htmlContent = replyText .replace(/\n/g, '<br>') .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); return { content: replyText, htmlContent: `<div style="font-family: Arial, sans-serif;">${htmlContent}</div>` }; } catch (error) { console.error('生成回覆失敗:', error); return { content: '感謝您的來信,我們會儘快回覆您。', htmlContent: '<div style="font-family: Arial, sans-serif;">感謝您的來信,我們會儘快回覆您。</div>' }; } } /** * 智能內容摘要工具 * 自動摘要長篇文章或報告 */ function intelligentContentSummarizer() { const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); const sheet = spreadsheet.getSheetByName('待摘要內容'); if (!sheet) { console.log('找不到"待摘要內容"工作表'); return; } const data = sheet.getDataRange().getValues(); const headers = data[0]; const rows = data.slice(1); rows.forEach((row, index) => { const [title, content, summary, status] = row; // 跳過已處理的內容 if (status === '已摘要' || !content) return; try { console.log(`正在摘要:${title}`); const prompt = ` 請為以下內容生成一個簡潔的摘要(150-200字): 標題:${title} 內容:${content} 摘要應該: 1. 包含主要重點 2. 保持客觀中立 3. 使用繁體中文 4. 結構清晰 `; const response = callGeminiAPI(prompt); const summary = response.candidates[0].content.parts[0].text; // 更新摘要到試算表 sheet.getRange(index + 2, 3).setValue(summary); sheet.getRange(index + 2, 4).setValue('已摘要'); console.log(`完成摘要:${title}`); // 避免 API 限制 Utilities.sleep(1000); } catch (error) { console.error(`摘要"${title}"時出錯:`, error); sheet.getRange(index + 2, 4).setValue('摘要失敗'); } }); } /** * 智能數據分析助手 * 分析試算表數據並生成洞察報告 */ function intelligentDataAnalyst() { const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); const dataSheet = spreadsheet.getSheetByName('分析數據'); if (!dataSheet) { console.log('找不到"分析數據"工作表'); return; } // 取得數據 const data = dataSheet.getDataRange().getValues(); const headers = data[0]; const rows = data.slice(1); // 準備數據摘要 const dataStats = generateDataStatistics(data); const prompt = ` 請分析以下數據並生成洞察報告: 數據欄位:${headers.join(', ')} 數據筆數:${rows.length} 統計摘要: ${JSON.stringify(dataStats, null, 2)} 請提供: 1. 數據概覽 2. 主要趨勢和模式 3. 異常值或有趣的發現 4. 商業洞察和建議 5. 後續分析建議 報告應該專業但易懂,適合商業決策參考。 `; try { const response = callGeminiAPI(prompt); const analysisReport = response.candidates[0].content.parts[0].text; // 建立分析報告工作表 const reportSheet = getOrCreateSheet(spreadsheet, 'AI分析報告'); reportSheet.clear(); // 寫入報告 reportSheet.getRange('A1').setValue('AI 數據分析報告'); reportSheet.getRange('A1').setFontSize(16).setFontWeight('bold'); reportSheet.getRange('A3').setValue(analysisReport); reportSheet.getRange('A3').setWrap(true); // 美化格式 reportSheet.autoResizeColumns(1, 1); reportSheet.setColumnWidth(1, 800); console.log('AI 數據分析報告已生成'); } catch (error) { console.error('生成數據分析報告時出錯:', error); } } /** * 生成數據統計摘要 * @param {Array} data - 數據陣列 * @return {Object} 統計摘要 */ function generateDataStatistics(data) { const headers = data[0]; const rows = data.slice(1); const stats = {}; headers.forEach((header, colIndex) => { const values = rows.map(row => row[colIndex]).filter(val => val !== ''); if (values.length === 0) { stats[header] = { type: 'empty' }; return; } // 檢查是否為數值欄位 const numericValues = values.map(val => parseFloat(val)).filter(val => !isNaN(val)); if (numericValues.length > values.length * 0.8) { // 數值欄位統計 stats[header] = { type: 'numeric', count: numericValues.length, min: Math.min(...numericValues), max: Math.max(...numericValues), average: numericValues.reduce((sum, val) => sum + val, 0) / numericValues.length, median: getMedian(numericValues) }; } else { // 文字欄位統計 const uniqueValues = [...new Set(values)]; const valueCounts = {}; values.forEach(val => { valueCounts[val] = (valueCounts[val] || 0) + 1; }); stats[header] = { type: 'text', count: values.length, unique: uniqueValues.length, mostCommon: Object.keys(valueCounts).reduce((a, b) => valueCounts[a] > valueCounts[b] ? a : b ) }; } }); return stats; } /** * 計算中位數 * @param {Array} numbers - 數值陣列 * @return {number} 中位數 */ function getMedian(numbers) { const sorted = numbers.sort((a, b) => a - b); const middle = Math.floor(sorted.length / 2); if (sorted.length % 2 === 0) { return (sorted[middle - 1] + sorted[middle]) / 2; } else { return sorted[middle]; } } /** * 呼叫 Gemini API * @param {string} prompt - 提示內容 * @return {Object} API 回應 */ function callGeminiAPI(prompt) { const payload = { contents: [{ parts: [{ text: prompt }] }], generationConfig: { temperature: 0.7, topK: 40, topP: 0.95, maxOutputTokens: 2048, } }; const options = { method: 'POST', headers: { 'Content-Type': 'application/json', }, payload: JSON.stringify(payload), muteHttpExceptions: true }; const response = UrlFetchApp.fetch(`${GEMINI_API_URL}?key=${GEMINI_API_KEY}`, options); const responseCode = response.getResponseCode(); if (responseCode !== 200) { throw new Error(`Gemini API 錯誤:${responseCode} - ${response.getContentText()}`); } return JSON.parse(response.getContentText()); } /** * 將建議回覆儲存為草稿 * @param {GmailThread} thread - Gmail 對話串 * @param {Object} reply - 回覆內容 */ function saveSuggestedReplyToDraft(thread, reply) { const draft = thread.createDraftReply(reply.content, { htmlBody: reply.htmlContent }); console.log(`已建立草稿回覆:${thread.getFirstMessageSubject()}`); } /** * 取得或建立工作表 * @param {Spreadsheet} spreadsheet - 試算表物件 * @param {string} sheetName - 工作表名稱 * @return {Sheet} 工作表物件 */ function getOrCreateSheet(spreadsheet, sheetName) { let sheet = spreadsheet.getSheetByName(sheetName); if (!sheet) { sheet = spreadsheet.insertSheet(sheetName); } return sheet; } /** * 設定 AI 功能觸發器 */ function setupAITriggers() { // 每 2 小時檢查需要回覆的郵件 ScriptApp.newTrigger('intelligentEmailReplySystem') .timeBased() .everyHours(2) .create(); // 每天凌晨 2 點執行內容摘要 ScriptApp.newTrigger('intelligentContentSummarizer') .timeBased() .everyDays(1) .atHour(2) .create(); } ``` ## 性能優化技巧 在開發 Google Apps Script 應用時,性能優化是非常重要的考量。以下是一些實用的優化技巧: ### 1. 批量操作優化 ```javascript /** * 錯誤示範:逐一操作(效率低下) */ function inefficientMethod() { const sheet = SpreadsheetApp.getActiveSheet(); // 每次操作都會觸發一次 API 呼叫 for (let i = 1; i <= 100; i++) { sheet.getRange(i, 1).setValue(`資料 ${i}`); const value = sheet.getRange(i, 2).getValue(); sheet.getRange(i, 3).setValue(value * 2); } } /** * 正確示範:批量操作(高效率) */ function efficientMethod() { const sheet = SpreadsheetApp.getActiveSheet(); // 一次取得所有需要的資料 const range = sheet.getRange(1, 1, 100, 3); const values = range.getValues(); // 在記憶體中處理資料 for (let i = 0; i < values.length; i++) { values[i][0] = `資料 ${i + 1}`; values[i][2] = values[i][1] * 2; } // 一次寫入所有資料 range.setValues(values); } ``` ### 2. 快取機制 ```javascript /** * 使用快取減少重複計算和 API 呼叫 */ function useCache() { const cache = CacheService.getScriptCache(); const cacheKey = 'expensive_calculation_result'; // 嘗試從快取取得結果 let result = cache.get(cacheKey); if (result === null) { // 快取中沒有資料,執行計算 console.log('從快取中未找到資料,執行計算...'); result = performExpensiveCalculation(); // 將結果儲存到快取(有效時間 10 分鐘) cache.put(cacheKey, result, 600); } else { console.log('從快取中取得資料'); } return result; } function performExpensiveCalculation() { // 模擬耗時的計算 Utilities.sleep(2000); return Math.random().toString(); } ``` ### 3. 錯誤處理與重試機制 ```javascript /** * 強化的錯誤處理和重試機制 */ function robustOperation() { const maxRetries = 3; let retryCount = 0; while (retryCount < maxRetries) { try { // 執行可能失敗的操作 const result = riskyOperation(); return result; } catch (error) { retryCount++; console.error(`操作失敗(第 ${retryCount} 次嘗試):`, error.message); if (retryCount >= maxRetries) { // 達到最大重試次數,記錄錯誤並處理 console.error('操作最終失敗,已達到最大重試次數'); handleFinalError(error); throw error; } // 等待後重試(指數退避) const waitTime = Math.pow(2, retryCount) * 1000; console.log(`等待 ${waitTime}ms 後重試...`); Utilities.sleep(waitTime); } } } function riskyOperation() { // 模擬可能失敗的操作 if (Math.random() < 0.3) { throw new Error('隨機失敗'); } return '成功'; } function handleFinalError(error) { // 發送錯誤通知 GmailApp.sendEmail( 'admin@company.com', '系統錯誤通知', `操作失敗:${error.message}\n時間:${new Date()}` ); } ``` ### 4. 執行時間管理 ```javascript /** * 管理執行時間,避免觸發 6 分鐘限制 */ function longRunningTask() { const startTime = new Date().getTime(); const maxRunTime = 5 * 60 * 1000; // 5 分鐘限制 const tasks = generateTasks(); // 假設有很多任務要執行 const processingQueue = PropertiesService.getScriptProperties(); let currentIndex = parseInt(processingQueue.getProperty('currentIndex') || '0'); while (currentIndex < tasks.length) { // 檢查執行時間 if (new Date().getTime() - startTime > maxRunTime) { console.log('接近執行時間限制,儲存進度並安排下次執行'); processingQueue.setProperty('currentIndex', currentIndex.toString()); // 安排觸發器繼續執行 ScriptApp.newTrigger('longRunningTask') .timeBased() .after(60000) // 1 分鐘後繼續 .create(); return; } // 處理目前任務 processTask(tasks[currentIndex]); currentIndex++; } // 所有任務完成,清除進度 processingQueue.deleteProperty('currentIndex'); console.log('所有任務完成'); } function generateTasks() { return Array.from({ length: 1000 }, (_, i) => `任務 ${i + 1}`); } function processTask(task) { console.log(`處理:${task}`); Utilities.sleep(100); // 模擬處理時間 } ``` ## 與其他自動化工具比較 ### Google Apps Script vs. 其他自動化平台 | 特性 | Google Apps Script | Zapier | n8n | Power Automate | |------|-------------------|---------|-----|----------------| | **成本** | 免費(有限制) | 付費(免費版功能少) | 開源免費 | 付費 | | **Google 整合** | 原生深度整合 | 良好 | 良好 | 一般 | | **程式彈性** | 高(JavaScript) | 低(GUI操作) | 高(視覺化+程式碼) | 中(GUI操作) | | **學習曲線** | 中等 | 簡單 | 中等 | 簡單 | | **自訂能力** | 非常高 | 低 | 高 | 中 | | **執行限制** | 6分鐘/日配額 | 依方案 | 無限制 | 依方案 | ### 選擇建議 **選擇 Google Apps Script 當:** - 主要使用 Google Workspace 服務 - 需要深度自訂功能 - 預算有限 - 團隊有程式開發能力 **選擇其他工具當:** - 需要整合大量第三方服務 - 團隊缺乏程式開發經驗 - 需要複雜的工作流程視覺化 - 有充足的軟體預算 ## 2024-2025 最新趨勢 ### 1. Gemini AI 整合 Google 正在將 Gemini AI 深度整合到各種服務中,包括: - **Gemini CLI**:命令列工具,可與 Apps Script 配合使用 - **AI-powered 自動化**:智能郵件回覆、內容生成 - **多模態處理**:處理文字、圖片、影片的綜合自動化 ```javascript // 2025 年趨勢:智能工作流程 function futureWorkflow() { // 1. AI 驅動的郵件分類 classifyEmailsWithAI(); // 2. 自動生成報告 generateReportsWithAI(); // 3. 智能資料洞察 getDataInsightsWithAI(); // 4. 多語言自動翻譯 translateContentWithAI(); } ``` ### 2. 低程式碼/無程式碼趨勢 - **Google Workspace 外掛程式**更加視覺化 - **Apps Script 編輯器**增強的自動完成和建議功能 - **模板市場**提供更多預製解決方案 ### 3. 安全性增強 ```javascript // 新的安全性最佳實踐(2025) function secureScriptPractices() { // 1. 使用 PropertiesService 儲存敏感資料 const apiKey = PropertiesService.getScriptProperties().getProperty('API_KEY'); // 2. 實作權限檢查 if (!hasPermission(Session.getActiveUser().getEmail())) { throw new Error('權限不足'); } // 3. 資料加密處理 const encryptedData = encryptSensitiveData(data); // 4. 審計日誌 logActivity('data_access', Session.getActiveUser().getEmail()); } ``` ### 4. 雲端整合深化 - **BigQuery 整合**:直接查詢大數據 - **Cloud Functions 互動**:更強大的後端處理 - **Firebase 整合**:即時資料庫和認證 ## 實用資源與學習路徑 ### 官方資源 1. **Google Apps Script 官方文件** - [developers.google.com/apps-script](https://developers.google.com/apps-script) - 最權威的學習資源 2. **Google AI Studio** - [aistudio.google.com](https://aistudio.google.com) - 取得 Gemini API 金鑰 3. **Google Workspace 開發者中心** - [developers.google.com/workspace](https://developers.google.com/workspace) ### 學習路徑建議 #### 初學者(0-3個月) 1. JavaScript 基礎語法 2. Google Apps Script 環境熟悉 3. 基本的 Sheets 和 Gmail 操作 4. 簡單的自動化腳本 #### 中級(3-6個月) 1. 進階 API 使用(Calendar、Drive、Forms) 2. 錯誤處理和除錯技巧 3. 觸發器和工作流程設計 4. 第三方 API 整合 #### 高級(6個月以上) 1. 性能優化技巧 2. 大型專案架構設計 3. AI 服務整合 4. 企業級解決方案開發 ### 社群資源 1. **Stack Overflow** - 標籤:`google-apps-script` - 豐富的問題解答 2. **Reddit 社群** - r/GoogleAppsScript - 經驗分享和討論 3. **GitHub 專案** - 開源的 GAS 專案和程式庫 - 學習最佳實踐 ### 推薦工具 1. **clasp**:命令列工具,本地開發 GAS 2. **Apps Script Dashboard**:專案管理和監控 3. **Google Cloud Console**:進階功能和配置 ## 總結與未來展望 Google Apps Script 作為一個強大的雲端自動化平台,正在經歷快速的發展和進化。從基本的工作表操作到複雜的 AI 整合,GAS 為個人和企業提供了無限的可能性。 ### 核心價值 1. **無縫整合**:與 Google Workspace 的深度整合無人能及 2. **成本效益**:免費使用額度對中小型應用足夠 3. **靈活性**:JavaScript 基礎提供無限自訂可能 4. **易於部署**:雲端執行,無需伺服器管理 ### 未來發展方向 1. **AI 驅動自動化**:Gemini 等 AI 服務的深度整合 2. **低程式碼發展**:更多視覺化開發工具 3. **企業級功能**:增強的安全性和管理功能 4. **生態系統擴展**:更多第三方整合和模板 ### 建議行動 對於想要提升工作效率的人: - 從小型自動化專案開始 - 逐步學習 JavaScript 和 GAS API - 關注最新功能和最佳實踐 - 加入開發者社群交流經驗 對於企業組織: - 評估現有工作流程的自動化潛力 - 投資員工的 GAS 技能培訓 - 建立內部知識庫和最佳實踐 - 考慮與專業開發者合作 ### 最後的話 Google Apps Script 不僅僅是一個工具,它是連接想法與實現之間的橋樑。無論你是想要節省日常工作時間的上班族,還是要建立複雜自動化系統的開發者,GAS 都能提供你所需的能力。 隨著 AI 技術的發展,我們正站在自動化新時代的門檻上。現在學習和掌握 Google Apps Script,就是為未來的智能工作方式做準備。 記住:最好的自動化不是替代人類的判斷,而是釋放人類的創造力。讓 Google Apps Script 處理重複性工作,你則專注於更有價值的創新和策略思考。 --- **相關標籤:** #GoogleAppsScript #GAS #自動化 #GoogleWorkspace #JavaScript #工作流程 #生產力工具 #程式開發 **更新日期:** 2025年1月 **作者簡介:** 專業的技術內容創作者,專注於雲端自動化和工作效率提升解決方案。