精華筆記

· @aihub.tw

Google 全家桶自動化

Sheets 自動化:表單進來,報表自己長好

Sheets 自動化:表單進來,報表自己長好

你用 Google Form 做了一份回饋問卷,活動結束後表單積了 83 筆回應。打開連結的 Sheets,看到那 83 列密密麻麻的自由填答——有人寫「還不錯」,有人寫「等太久了啦」,有人空著。你想統計「多少人負面」,但自由填答沒辦法直接計算。最後你複製幾句到報告裡,打上「整體反應正面」,把視窗關掉。那 83 筆資料就在那邊,沒有真正被讀過。

癥結不是你懶,是流程缺了自動化。表單資料進 Sheets 這件事 Google 幫你做好了;但「資料進來以後的處理」,Google 原生功能只能靠你手動。這堂課用 n8n 把後半段接起來:資料進來 → 觸發流程 → AI 清洗分類 → 統計寫回 Sheets → 週報自動寄到你信箱。你只管看報告。

這堂課適合誰 適合:用 Google Form 收集問卷、報名、客戶回饋,想讓資料分析自動化的上班族或自由工作者(本課程屬進階應用專區)。需要基礎:會用電腦、有 Google 帳號、知道 n8n 基本操作即可。前置課:第 3 課(Gmail 自動化);Google Cloud 的 OAuth 操作第 3 課已建立基礎,本課快速帶過差異點。

這堂學什麼

  • Google Sheets OAuth2 設定:啟用 Sheets API、建憑證、設定 Redirect URI——跟第 3 課的 Gmail 操作同框架,本課重點在差異與常見錯誤
  • Form → Sheets Trigger 觸發鏈:為什麼 n8n 無法直接觸發 Google Form、Sheets Trigger 的輪詢機制怎麼運作
  • AI 資料清洗:用 n8n 的 Text Classifier 節點把自由填答自動轉成標準分類,清洗結果寫回同一張 Sheets
  • 自動週報:每週一用 Schedule Trigger 讀 Sheets 資料、AI 生成摘要、Gmail 寄出週報——全自動
  • Sheets 當輕量資料庫:三表分工設計(原始資料 / FAQ 知識庫 / 統計彙整),呼應 LINE OA 課 FAQ 表的結構

觀念一:為什麼不能直接觸發 Form

很多人第一次想做這件事,會去找「Google Form Trigger」——n8n 裡沒有這個節點,不是 n8n 的問題,是 Google Form 本身沒有 Webhook 機制。

真正可以觸發的是 Google Sheets Trigger。做法是:先在 Google Form 後台把「回應」連結到一張 Sheets(Form 內建功能,不用 n8n),以後每筆新回應會自動出現在那張 Sheets 的最後一列;n8n 的 Sheets Trigger 再定時檢查有沒有新列,有的話就觸發流程。

Google Form → Sheets → n8n 全自動觸發鏈

這條觸發鏈有一個特性要先知道:Sheets Trigger 是輪詢(polling),不是即時。預設每分鐘跑一次,填完表到 n8n 收到通知,中間最多延遲 1 分鐘。對回饋表單、報名表這完全可以接受;需要秒級即時的情境才需要改用 Apps Script Webhook,但那要寫程式碼,本課不展開。

第一關:Sheets OAuth2 設定

第 3 課已設定過 Gmail OAuth2,整個流程你應該有點印象。Sheets 的設定框架完全一樣,差別只在兩個地方:啟用的 API 換成 Google Sheets API,以及憑證類型選「Google Sheets OAuth2」。

如果你已在第 3 課建好 Google Cloud 專案,可以直接沿用同一個專案,追加啟用 Sheets API 即可;Redirect URI 不用重設,Sheets 和 Gmail 可以共用同一組 OAuth 用戶端。

Google Sheets OAuth2 設定五步驟

建立 Google Cloud 專案並啟用 Sheets API

console.cloud.google.com,如果第 3 課已建專案,從頂部選到那個專案即可。若是全新開始:

  1. 點「選取專案」→「新增專案」,名稱填 n8n-google-auto
  2. 建好後,左側選「APIs 和服務」→「程式庫」
  3. 搜尋 Google Sheets API,點進去按「啟用」

接著確認 OAuth 同意畫面已設定(第 3 課做過就不用重做)。如果是第一次,步驟同第 3 課:使用者類型選「外部」,把你自己的 Google 帳號加入測試使用者清單。

Google Drive API 一起啟用 n8n 的 Sheets 節點在讀取試算表清單時會用到 Drive API。建議在同一個步驟把 Google Drive API 也一併啟用,省得後面流程跑到一半跳出 403 錯誤。

建立 OAuth 用戶端,取得 Client ID 和 Client Secret

左側選「憑證」→「+ 建立憑證」→「OAuth 用戶端 ID」:

  • 應用程式類型:網頁應用程式
  • 名稱:n8n(或任意名稱)
  • 按「建立」

跳出的對話框把 Client IDClient Secret 都複製下來,對話框關掉就看不到 Secret 了。

在 n8n 建立憑證,貼回 Redirect URI

打開 n8n,左側選「Credentials」→「Add Credential」,搜尋選「Google Sheets OAuth2 API」:

  1. 把剛才的 Client ID 和 Client Secret 貼入
  2. n8n 頁面下方顯示的 OAuth Redirect URL 複製起來
  3. 回 Google Cloud Console → 憑證,找到剛建的 OAuth 用戶端,點編輯
  4. 「已授權的重新導向 URI」加入剛才複製的 Redirect URL,存檔

回到 n8n,點「Sign in with Google」完成授權。看到「Connection tested successfully」就完成了。


流程①:Form 接 Sheets,Sheets 觸發 n8n,AI 清洗資料

先讓 Google Form 連結到 Sheets

打開你的 Google Form(任何一個已有填答的,或新建一個測試用),頂部點「回應」分頁,右側有一個 Sheets 圖示按鈕「在試算表中查看回應」,按下去選「建立新的試算表」,Google 會自動建立一張連結好的 Sheets,之後每筆新填答都會自動附加一列。

這張 Sheets 的第一列很重要 Google Form 自動產生的 Sheets,第一列就是問題標題(欄位名稱)。n8n 的 Sheets Trigger 靠第一列判斷欄位名稱。不要刪除或修改第一列的內容,否則觸發器讀不到正確的欄位。

建立 n8n 工作流

新增 Google Sheets Trigger 節點

在 n8n 建立新的工作流,第一個節點選「Google Sheets Trigger」:

  • Credential:選剛設定好的 Google Sheets OAuth2
  • Event:Row Added(有新列加入時觸發)
  • Document:點選你的試算表名稱(會自動列出有權限的所有 Sheets)
  • Sheet Name:選「Form Responses 1」(Google Form 自動建立的工作表名稱)
  • Poll Times:預設 Every Minute(每分鐘輪詢一次)

存檔後按「Test workflow」,到 Google Form 送一筆測試填答,等最多 1 分鐘,看 Sheets Trigger 有沒有攔截到這筆新列的資料。成功的話,右側輸出欄位會列出所有問題欄位名稱和你剛填的值。

加入 Text Classifier 節點做 AI 分類

接在 Sheets Trigger 後面,加入「Text Classifier」節點(在 n8n 節點搜尋「Text Classifier」,它在 Langchain 分類下):

  • Text:填運算式,對應你 Form 裡的自由填答欄位,例如:
{{ $json["您的回饋意見"] }}
  • Categories:加入以下分類(名稱 + 描述):
名稱 描述
正向 回應內容整體肯定、滿意或讚美的留言
負向 回應包含抱怨、不滿、要求改進或評分偏低的內容
中立 沒有明顯情緒傾向、敘述性回應、或空白
痛點:等待 提及等待時間過長、速度慢、太慢等相關內容
痛點:品質 提及品質差、有問題、損壞、不符合預期等
痛點:價格 提及太貴、價格偏高、CP值低等
痛點:服務 提及服務態度差、客服慢、處理不佳等
  • Allow Multiple Classes Per Item:開啟(一筆回應可能同時是「負向」又是「痛點:等待」)

AI 資料清洗:自由填答轉成標準分類

Text Classifier 節點需要連接一個 AI 模型:在節點下方的「Model」連接點,加入「OpenAI Chat Model」(或 Gemini 等你已設定的模型),Text Classifier 會把文字丟給這個模型判斷。

Set 節點:整理分類結果

Text Classifier 輸出的分類結果是一個陣列,例如 ["負向", "痛點:等待"]。加入「Set」節點把它整理成好寫回 Sheets 的格式:

// 在 Set 節點的 JavaScript 欄位使用以下邏輯
// 或用 Code 節點直接處理

const classes = $json.classes; // Text Classifier 輸出的分類陣列

// 情緒類別:取第一個非痛點的分類
const sentiment = classes.find(c => ['正向','負向','中立'].includes(c)) || '中立';

// 痛點標籤:把所有痛點類型合併
const painPoints = classes
  .filter(c => c.startsWith('痛點:'))
  .map(c => c.replace('痛點:', ''))
  .join('、') || '無';

return [{ json: { sentiment, painPoints } }];

Google Sheets 節點:把分類寫回同一列

這一步是把 AI 分析結果寫回原本的那筆 Sheets 資料。先在 Sheets 手動新增兩個欄位標題:AI_情緒AI_痛點(在第一列的最右邊追加)。

加入「Google Sheets」節點:

  • Operation:Append or Update Row(不是 Append Row——Append 會新增一列,你要的是更新原本那列)
  • Document / Sheet:和 Trigger 同一張
  • Matching Columns:選「時間戳記」(Timestamp)欄位作為識別鍵,確保更新的是正確那一列
  • Columns to Send:
    • AI_情緒{{ $json.sentiment }}
    • AI_痛點{{ $json.painPoints }}
一定要用 Append or Update Row,不是 Append Row Append Row 會在試算表最後新增一列——你的資料就變成兩列了。Append or Update Row 才會找到符合識別鍵的那列去更新欄位。這是這個流程最常見的錯誤,常見坑第 4 條有完整說明。

整條流程串好:Sheets Trigger → Text Classifier → Set → Google Sheets(更新列)。啟動後,每次有人填 Form,最多一分鐘後 Sheets 裡那筆資料的 AI_情緒AI_痛點 欄位就會自動填好。


流程②:自動週報

每週一早上自動讀 Sheets 資料,AI 生成上週回饋摘要,寄到你的 Gmail。

自動週報架構:Sheets 資料 → AI 摘要 → Gmail

Schedule Trigger:每週一早上九點

新建一條工作流,第一個節點選「Schedule Trigger」:

  • Mode:Cron
  • Cron Expression:0 9 * * 1(每週一早上 09:00)
  • Timezone:Asia/Taipei

Google Sheets 節點:讀本週資料

加入「Google Sheets」節點:

  • Operation:Get Many Rows
  • Document / Sheet:回應試算表
  • Filters:這裡可以用時間篩選,但 Sheets 節點不支援直接按日期過濾;先抓全部資料,再在後面的 Code 節點裡用 JavaScript 過濾最近 7 天。

Limit 先填 500(超過 500 筆週回應時,需改用分批彙整,屬進階情境,先不展開)。

Code 節點:過濾本週資料並計算統計

// 取得今天和7天前的時間
const now = new Date();
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);

// 過濾本週資料(時間戳記欄位名稱依你的 Form 設定調整)
const allRows = $input.all();
const weekRows = allRows.filter(item => {
  const ts = new Date(item.json['時間戳記']);
  return ts >= sevenDaysAgo && ts <= now;
});

// 統計情緒分類
const sentimentCount = { 正向: 0, 中立: 0, 負向: 0 };
const painPointCount = {};

weekRows.forEach(item => {
  const s = item.json['AI_情緒'] || '中立';
  if (sentimentCount[s] !== undefined) sentimentCount[s]++;

  const pp = item.json['AI_痛點'];
  if (pp && pp !== '無') {
    pp.split('、').forEach(p => {
      painPointCount[p] = (painPointCount[p] || 0) + 1;
    });
  }
});

// 痛點排行
const topPainPoints = Object.entries(painPointCount)
  .sort((a, b) => b[1] - a[1])
  .slice(0, 3)
  .map(([k, v]) => `${k}(${v}次)`)
  .join('、') || '本週無明顯痛點';

// 挑出代表原始留言(正面和負面各一句)
const findSample = (sentiment) => {
  const item = weekRows.find(r => r.json['AI_情緒'] === sentiment && r.json['您的回饋意見']);
  return item ? `「${item.json['您的回饋意見'].slice(0, 50)}」` : '(無代表留言)';
};

return [{
  json: {
    total: weekRows.length,
    sentimentCount,
    topPainPoints,
    positiveSample: findSample('正向'),
    negativeSample: findSample('負向'),
    weekLabel: `${sevenDaysAgo.toLocaleDateString('zh-TW')} ~ ${now.toLocaleDateString('zh-TW')}`
  }
}];

Basic LLM Chain:讓 AI 寫摘要和建議

加入「Basic LLM Chain」節點,連接你的 AI 模型(OpenAI 或 Google Gemini):

System Prompt:

你是一位客戶體驗分析師,請用繁體中文、專業但口語的風格,根據以下數字寫一份簡短的週報摘要,不要加多餘的標題格式,直接寫成自然段落(3-5 句),最後另起一段給出 1-2 條具體改善建議。

User Message:

本週回收回饋筆數: {{ $json.total }}
情緒分布: 正向 {{ $json.sentimentCount.正向 }} 筆、中立 {{ $json.sentimentCount.中立 }} 筆、負向 {{ $json.sentimentCount.負向 }} 筆
主要痛點: {{ $json.topPainPoints }}
正面代表留言: {{ $json.positiveSample }}
負面代表留言: {{ $json.negativeSample }}
統計週期: {{ $json.weekLabel }}

Gmail 節點:寄出週報

確認你已建好 Gmail OAuth2 憑證(第 3 課)。加入「Gmail」節點:

  • Resource:Message
  • Operation:Send
  • To:你的信箱@gmail.com(或團隊信箱)
  • Subject:📊 本週回饋摘要 — {{ $('Code').item.json.weekLabel }}
  • Message Type:HTML
  • Message:
<h2>本週回饋摘要</h2>
<p><strong>統計週期:</strong>{{ $('Code').item.json.weekLabel }}</p>
<p><strong>總填答數:</strong>{{ $('Code').item.json.total }} 筆</p>
<h3>情緒分布</h3>
<ul>
  <li>正向:{{ $('Code').item.json.sentimentCount.正向 }} 筆</li>
  <li>中立:{{ $('Code').item.json.sentimentCount.中立 }} 筆</li>
  <li>負向:{{ $('Code').item.json.sentimentCount.負向 }} 筆</li>
</ul>
<h3>AI 摘要與建議</h3>
<p>{{ $json.text }}</p>
<hr>
<p style="color:gray;font-size:12px">由 n8n Google Sheets 自動化生成 · 如需查看原始資料請至 Sheets</p>

儲存並啟動。第一次先把 Schedule Trigger 換成 Manual Trigger 手動測一次,確認信件格式正常再換回定時觸發。


Sheets 當輕量資料庫

把 Sheets 只當「表單收集工具」太浪費了。用好三表分工,Sheets 可以撐起相當多的自動化情境,也是 LINE OA 課 FAQ 表的基礎結構。

Sheets 三表分工:原始回應、FAQ 知識庫、統計彙整

三張工作表的分工:

工作表 1「原始回應」:Form 連結到這裡,每筆原始資料都在,n8n 的 AI 清洗結果也寫回這裡(AI_情緒AI_痛點 兩個欄位)。這張表只有新增,不刪,是系統的事實來源。

工作表 2「FAQ 知識庫」:欄位固定為 ID / 關鍵字 / 標準回覆。n8n 在 LINE OA 課的流程會讀這張表——收到問題先比對關鍵字,有對應 FAQ 就回標準答覆,找不到才送 AI 生成。這張表由你手動維護。

工作表 3「統計」:放公式,不放原始資料。用 COUNTIF 統計各情緒分類的筆數,用 COUNTIFS 做多條件統計(例如本週且負向)。公式指向工作表 1,資料更新後統計自動刷新,不需要 n8n 介入。

幾個用 Sheets 當資料庫的實用原則:

  1. 第一列永遠是欄位標題,不要合併儲存格,不要讓第一列留空
  2. AI 寫入欄統一加前綴 AI_,一眼就能分辨哪些是原始回應、哪些是系統加工
  3. n8n 的識別鍵選「時間戳記」欄位,Google Form 自動填這欄,值通常唯一,適合當 key 來做 Append or Update Row
  4. 資料量超過 5,000 列時考慮換真實資料庫(PostgreSQL/Supabase),Sheets 輪詢效能到這個量級會明顯下降

Google 內建 Gemini vs n8n 路線

2026 年 Google Workspace 已整入 Gemini:Sheets 右上角的 Ask Gemini 側欄可用自然語言描述規則產生公式、批次填欄位(fill a range),也能叫 Gemini 直接建樞紐分析表或圖表,適合一次性的整批清洗。n8n 的優勢在持續自動:每次有新填答就自動跑,不需要手動觸發。兩個工具不衝突,一次性的整批清洗用內建 Gemini,定期持續自動化用 n8n。


常見坑

坑 1:Sheets Trigger 一直沒觸發,但 Form 確實有新填答

症狀:你填了表,等了兩分鐘,n8n 還是無反應。

先確認兩件事:一,Google Form 的「回應」分頁右上角有沒有出現 Sheets 圖示(如果沒出現,代表 Form 尚未連結任何 Sheets,點圖示新建連結)。二,到 n8n 這條工作流的「Executions」頁面看看有沒有執行記錄——有的話表示 Trigger 有跑,只是沒抓到資料,可能是 Sheets 欄位名稱對不上;沒有執行記錄,表示工作流沒有被啟動,確認右上角「Active」開關是否打開。

坑 2:OAuth 授權彈跳視窗出現「這個應用程式未通過 Google 驗證」

這個應用程式尚未通過 Google 驗證
您嘗試存取的應用程式尚未通過 Google 的安全審核。

這不是錯誤,是警告。OAuth 同意畫面在「測試中」狀態時 Google 會顯示這個提示。點「Advanced」→「Go to n8n (unsafe)」繼續即可,你是自己的應用連自己的帳號,完全沒問題。個人自用建議就留在測試狀態,不需要特別升成正式版。

坑 3:redirect_uri_mismatch 錯誤

Error 400: redirect_uri_mismatch
The redirect URI in the request did not match a registered redirect URI.

與第 3 課 Gmail 一樣:Google Cloud Console 裡的 Redirect URI 必須和 n8n 憑證頁面顯示的 URL 完全一致,多一個斜線或 http/https 不對都不行。從 n8n 複製貼上,不要手打。沿用第 3 課 OAuth 用戶端時,也要確認 URI 清單裡有對應這個 Sheets 憑證的 URL。

坑 4:AI 分類寫回 Sheets 後,資料變成兩列(Append 用錯了)

症狀:每次流程跑完,Sheets 的最後面多了一列只有 AI_情緒AI_痛點 的新列,原始回應那列沒有被更新。

原因:Google Sheets 節點的 Operation 選了「Append Row」,這個操作是「在最後追加新列」。應該改成「Append or Update Row」,並在「Matching Columns」設定識別鍵(時間戳記欄位),它才會找到原本那列更新,而不是新增。

坑 5:Code 節點的日期過濾結果永遠是空的

症狀:週報跑完 total 是 0,信裡什麼都沒有。

最常見原因是時間戳記格式不是 JavaScript 能直接解析的格式。Google Form 寫入 Sheets 的格式通常是 2026/7/4 上午10:23:45(繁中系統),new Date() 對這種格式解析可能失敗,你需要先轉換:

// 如果時間戳記格式是 "2026/7/4 上午10:23:45"
// 先統一轉成標準格式
function parseTimestamp(ts) {
  if (!ts) return new Date(0);
  // 把 "上午" / "下午" 移除,改用 24 小時制
  let cleaned = ts
    .replace('上午', 'AM')
    .replace('下午', 'PM')
    .replace(/\//g, '-');
  return new Date(cleaned);
}

const ts = parseTimestamp(item.json['時間戳記']);

先在 Code 節點裡印出幾筆時間戳記的實際值(用 console.log),確認格式再調整解析邏輯。


作業

  1. 必做:完成 Google Sheets OAuth2 設定,在 n8n 建一個只有 Sheets Trigger 的工作流,確認它能正確攔截 Google Form 新填答(哪怕後面什麼都沒接)。這一步通了,其他節點都是積木組裝。
  2. 必做:把流程①(Form → AI 分類 → 寫回 Sheets)完整跑起來。填 5 筆測試回應,觀察 Sheets 裡的 AI_情緒 欄位被正確填入。如果有分類錯誤,回去調整 Text Classifier 裡那個分類的「描述」欄位——描述越清楚,分類越準。
  3. 必做:在你的 Sheets 手動新增第三張工作表「統計」,用 =COUNTIF(原始回應!D:D,"正向") 這個公式驗證你的資料通了沒(注意工作表名稱替換成你的實際名稱)。看到計算結果出來,代表三表結構建對了。
  4. 選做:把流程②(週報)用 Manual Trigger 手動跑一次,看週報信件格式。如果 AI 摘要語氣或格式不對,修改 System Prompt 直到你滿意。修好了再換回 Schedule Trigger 設定每週一早上自動寄。

下一課預告

Sheets 搞定,你手上已有一張會自動分類、自動統計的資料表。下一個問題:工作上最耗時的事情往往不是資料整理,而是「會議」——開會前要收集資料、準備議題;開完要整理紀錄、追蹤待辦。第 5 課「日曆與文件:會議前自動準備、會議後自動記錄」,我們串接 Google Calendar 和 Google Docs,讓 n8n 在會議前半小時自動拉出相關資料、會議結束後根據你的筆記自動整理會議紀錄發給所有人。你的開會方式,從此不一樣。

#Google Sheets#Google Forms#n8n#Google 自動化#AI 分析#OAuth

← 回所有文章