# Google Apps Script 能開發什麼?深入淺出完整應用指南

## 前言:一個改變工作效率的真實故事
小華是一名行銷專員,每週都要花費 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 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)

這個範例展示如何整合最新的 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月
**作者簡介:** 專業的技術內容創作者,專注於雲端自動化和工作效率提升解決方案。