前幾天已經讓 SubWise 可以做到一件很有趣的事情:使用者只要用自然語言說「我今天午餐花了 120 元」,Gemini 就能理解這句話,並整理成結構化 JSON:
{
"type": "expense",
"category": "Food",
"amount": 120,
"item": "午餐",
"date": "2026-08-11",
"note": null
}
但做到這裡,還有一個很大的問題:資料去哪裡了?
如果每次程式結束之後,這些資料就消失,那我們做的就只是一個「很會聊天的 AI」。真正的記帳工具,至少要能做到:
使用者說話 → Gemini 理解 → Python 處理 → 資料儲存 → 之後還能查詢
所以今天 Day 10 的目標,就是讓 SubWise 第一次擁有「記憶」,主要完成三件事:連接 Google Sheets、儲存消費資料、儲存訂閱資料——並且如果訂閱已經存在,要能自動更新而不是重複新增。
如果要做正式產品,當然可以考慮 MySQL、PostgreSQL、MongoDB、Firebase、Supabase 這些方案,但這次專案有一個很重要的目標:輕量化開發。目前 SubWise 資料量非常小,也還在開發階段,Google Sheets 有幾個明顯優點:不需要自己架資料庫、可以直接用瀏覽器查看資料、人類可以直接閱讀、Python 可以透過 API 操作、非常適合做 MVP,對 Vibe Coding 初學者來說也容易理解。因此目前先把 Google Sheets 當成 SubWise 的「輕量資料庫」。
目前建立了兩個主要工作表:
| Date | Category | Amount | Note |
|---|---|---|---|
| 2026-08-11 | Food | 120 | 午餐 |
| 2026-08-11 | Transport | 50 | 搭捷運 |
| Service | Price | Billing Cycle | Next Billing Date | Status | Category | Note |
|---|---|---|---|---|---|---|
| Netflix | 390 | monthly | 2026-08-15 | Active | Subscription |
資料不再只存在 Gemini 的回答裡,而是真正被保存下來。
透過 Service Account 的 credentials 取得操作權限:
credentials = Credentials.from_service_account_file(
"credentials.json",
scopes=SCOPES
)
client = gspread.authorize(credentials)
spreadsheet = client.open("SubWise Database")
spreadsheet.worksheet("Expenses")
把 Gemini 和 Google Sheets 串起來,整個流程變成:
「我今天搭捷運花了50元」→ Gemini AI → JSON → Python 驗證資料 → Google Sheets
測試成功得到:
📦 Gemini JSON:{'type': 'expense', 'category': 'Transport', 'amount': 50, 'item': '搭捷運', 'date': '2026-08-11', 'note': None}
💾 嘗試寫入 Google Sheets...
✅ 消費資料已寫入 Google Sheets
🎉 記帳完成!
這一刻其實很有成就感,因為這代表 Gemini 已經開始能控制我們的資料流程。
第一次成功完成「AI → 資料庫」的完整流程。使用者只需要用自然語言描述消費,Gemini 負責理解與結構化,Python 再將資料寫入 Google Sheets。從這一步開始,SubWise 才真正開始具備「記帳」能力:
例如使用者只說「我今天花了 120 元」,Gemini 會回傳 category: null、item: null。Python 檢查到 category = None 時,就不會直接寫入資料庫,而是顯示:
⚠️ 缺少消費分類
⚠️ 記帳失敗,資料沒有寫入。
這個設計非常重要,因為 AI 很容易「自作主張」——如果 AI 自行猜測分類與項目,資料庫就會產生錯誤資料。原則是:AI 不確定,就不要猜,寧願留下 null,再讓之後的對話流程詢問使用者。
例如使用者說「我每個月訂 Netflix,月費 390 元,每月 15 號扣款」,Gemini 判斷 type = subscription 後,Python 就不會寫入 Expenses,而是改寫入 Subscriptions。
這裡遇到一個重要的開發問題:如果使用者重複講一次同樣的訂閱內容,單純用 append_row() 會產生兩筆重複資料。因此加入了「新增之前,先檢查是否已經存在相同訂閱」的邏輯——程式會先用 get_all_records() 取得目前的訂閱,逐筆比對服務名稱,如果找到相同的 Netflix,就進入更新流程而不是新增:
🔔 偵測到訂閱資料
💾 嘗試寫入 Google Sheets...
🔄 發現既有訂閱:Netflix
✅ 已更新第 2 列資料
🎉 訂閱建立完成!
這代表 SubWise 現在不只有 Create,也開始有 Update 能力。
SubWise 開始具備基本的資料管理能力。當系統發現 Netflix 已經存在時,不會再建立一筆重複資料,而是找到原本的資料列並更新。這也是今天從「單純寫入資料」往「管理資料」前進的重要一步:
NameError: name 'row' is not defined
迴圈裡使用 for row_number, record in enumerate(records, start=2) 定義的變數是 row_number,但更新資料時卻寫成 range_name=f"A{row}:G{row}",變數名稱對不起來。修正成 range_name=f"A{row_number}:G{row_number}" 後才成功。這也是 Vibe Coding 很常遇到的問題:AI 幫你產生程式碼很快,但你仍然需要看懂錯誤訊息。
gspread 套件的 DeprecationWarning
更新資料時出現警告,提示 worksheet.update() 的參數順序已經改變。雖然程式沒有直接失敗,但這提醒了我:第三方套件會持續更新,能跑不代表永遠都不需要維護。最後改用新版參數寫法解決:
worksheet.update(
range_name=f"A{row_number}:G{row_number}",
values=[[name, amount, billing_cycle, next_billing_date, "Active", category, note]]
)
| 項目 | 狀態 |
|---|---|
| Gemini AI | ✅ |
| Structured JSON | ✅ |
| 消費分類 | ✅ |
| 消費資料驗證 | ✅ |
| Google Sheets API | ✅ |
| 消費寫入 | ✅ |
| 訂閱寫入 | ✅ |
| 訂閱重複檢查 | ✅ |
| 訂閱更新 | ✅ |
| LINE Bot | 🚧 已完成前置 |
| 查詢資料 | ⏳ 下一階段 |
| 消費分析 | ⏳ |
| 訂閱提醒 | ⏳ |
目前 SubWise 的資料流程架構:
使用者 → LINE Bot → Gemini AI(自然語言理解)→ JSON 結構化
→ Expenses / Subscriptions → Google Sheets

今天解決的是「怎麼把 AI 理解的資料存起來?」,Day 11 要解決「怎麼把已經存起來的資料找回來?」,讓 SubWise 開始支援像「我這個月花多少錢?」「我有哪些訂閱?」「Netflix 什麼時候扣款?」這類查詢。當這些功能完成之後,SubWise 才會真正開始像一個「智慧記帳與訂閱管理管家」,這也是從 AI 記帳工具邁向 AI Agent 非常重要的一步。我們明天見!