在開發 Telegram Bot 時,我們總是需要某種形式的資料儲存方案。雖然傳統資料函式庫常見的選擇,但在某些情境下可能過於複雜。特別是當我們不需要 ACID(原子性、一致性、隔離性、永續性)交易,與希望能透過簡單的介面手動修改資料時,Google Sheets 其實是個相當實用的替代方案。
今天玄貓要分享如何開發一個測驗機器人,運用 Google Sheets 的不同工作表來儲存題目和答案。選擇將所有資料整合在同一個試算表檔案中,主要是為了讓使用者能更方便地存取和管理資料。
系統架構設計
在開始實作之前,我們必須先規劃資料結構。由於這是一個測驗系統,每個問題都會有一個正確答案和三個錯誤選項。因此,我們需要儲存:
- 題目內容
- 四個選項
- 正確答案的標記
題目資料結構
我們採用以下欄位設定:
- A 欄:題目文字
- B 欄:正確答案
- C、D、E 欄:錯誤選項
這樣的設計有幾個優點:
- 清楚區分正確與錯誤答案
- 避免選項重複
- 方便使用 Google Sheets 的資料驗證功能
作答記錄設計
為了追蹤使用者的作答狀況,我們另外建立了記錄工作表,包含以下欄位:
- 使用者 ID
- 題目內容
- 使用者答案
- 正確答案
- 時間戳記
雖然這樣的設計看似有資料重複的問題,但這樣做有其必要性。讓玄貓解釋為什麼要採用這種看似冗餘的設計:
- 效能考量:直接儲存完整資訊可以減少查詢次數,不需要每次都去參照題目工作表
- 歷史記錄完整性:即使原始題目後來被修改,我們仍保有作答當時的完整資訊
- 資料分析便利性:方便後續進行資料分析,不需要在多個工作表間建立關聯
系統佈署策略
為了簡化佈署流程,我們選擇使用雲端服務 Amvera 來佈署機器人。這個選擇有幾個優勢:
- 簡化的更新流程:透過 Git 整合,只需要幾個指令就能完成更新
- 省去伺服器管理:比起自行管理 VPS 來得簡單許多
- 成本效益:提供免費額度供初期測試使用
Google Sheets 作為資料函式庫勢
在實際開發過程中,玄貓發現使用 Google Sheets 作為資料儲存方案有許多優點:
- 視覺化管理:使用者可以直接在試算表介面中編輯資料,不需要特殊工具
- 即時協作:多人可同時編輯內容,適合團隊協作
- 版本控制:Google Sheets 內建的版本歷史功能可追蹤所有變更
- 資料驗證:可使用試算表的資料驗證功能確保資料正確性
- 操作門檻低:大多數使用者都熟悉試算表操作
當然,這種方案也有其限制,例如不適合需要高併發處理或複雜交易的場景。但對於中小型的機器人專案來說,這是個相當實用的選擇。
實作細節與最佳實踐
資料存取安全性
在使用 Google Sheets 作為資料函式庫安全性是一個重要考量。玄貓建議採取以下措施:
存取許可權控制:
- 使用服務帳號(Service Account)進行認證
- 最小化許可權原則,只給予必要的讀寫許可權
- 定期輪換存取金鑰
資料驗證機制:
- 實作輸入資料的格式檢查
- 設定欄位的資料類別限制
- 建立資料完整性檢查機制
效能最佳化策略
雖然 Google Sheets 不是為了高效能設計的資料函式庫我們可以透過一些技巧來最佳化效能:
批次處理:
- 盡可能使用批次讀寫操作
- 減少 API 呼叫次數
- 實作資料快取機制
資料結構最佳化:
- 適當分割工作表以控制資料量
- 避免過度複雜的公式運算
- 定期清理過期資料
錯誤處理與監控
穩定的運作需要完善的錯誤處理機制:
例外處理:
- 實作重試機制
- 記錄詳細的錯誤資訊
- 設計錯誤回復流程
監控機制:
- 追蹤 API 配額使用狀況
- 監控資料存取效能
- 設定異常警告機制
程式碼實作範例
以下是一個簡單的 Python 實作範例:
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build
import pandas as pd
class GoogleSheetsDB:
def __init__(self, credentials_path, spreadsheet_id):
self.credentials = Credentials.from_service_account_file(
credentials_path,
scopes=['https://www.googleapis.com/auth/spreadsheets']
)
self.service = build('sheets', 'v4', credentials=self.credentials)
self.spreadsheet_id = spreadsheet_id
def read_questions(self, sheet_name):
result = self.service.spreadsheets().values().get(
spreadsheetId=self.spreadsheet_id,
range=f'{sheet_name}!A:E'
).execute()
values = result.get('values', [])
if not values:
return []
return [
{
'question': row[0],
'correct_answer': row[1],
'wrong_answers': row[2:5]
}
for row in values[1:] # 跳過標題列
]
def save_answer(self, user_id, question, user_answer, correct_answer):
values = [[
user_id,
question,
user_answer,
correct_answer,
pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')
]]
self.service.spreadsheets().values().append(
spreadsheetId=self.spreadsheet_id,
range='Answers!A:E',
valueInputOption='RAW',
insertDataOption='INSERT_ROWS',
body={'values': values}
).execute()
** **
初始化設定:
- 建立 GoogleSheetsDB 類別,接受認證檔案路徑和試算表 ID
- 設定 Google Sheets API 的存取憑證和服務物件
讀取題目方法:
read_questions
方法從指定工作表讀取題目資料- 將原始資料轉換成結構化的字典格式
- 跳過標題列,確保只處理實際題目資料
儲存答案方法:
save_answer
方法記錄使用者的作答資訊- 自動加入時間戳記
- 使用 API 的 append 方法新增記錄
這個實作展現瞭如何以結構化與可維護的方式處理 Google Sheets 資料。玄貓在實務經驗中發現,良好的程式碼組織和錯誤處理對於建立穩定的系統至關重要。
Google Sheets 作為輕量級資料函式庫有其獨特優勢。透過適當的設計和實作,它能夠有效支援許多中小型專案的需求。重要的是要理解其限制,並在適當的場景中使用它。這種方案特別適合需要頻繁手動更新資料,與對效能要求不高的應用場景。
在進行問卷調查或測驗管理時,Google 試算表是一個常用與方便的工具。今天玄貓要和大家分享如何使用 Python 建立一個與 Google 試算表互動的系統,特別適用於需要自動化收集和處理問捲回覆的場景。
系統架構設計考量
在設計這個系統時,玄貓特別注意到資料完整性和安全性的重要性。當試算表內容被修改時,相關的回覆資料也會同步更新,這可能會影響最終的統計結果。因此,我們需要建立一個穩健的機制來確保資料的可靠性。
系統的主要功能將專注於回覆收集,而資料的驗證則交由 Google 試算表的內建功能處理。這樣的設計可以讓系統更加專注與高效。
使用 Python 操作試算表
環境設定與認證
首先需要安裝 gspread
套件,這是一個強大的 Google 試算表 API 操作工具:
pip install gspread
接著需要設定認證檔案。玄貓建議建立一個 config.py
來管理設定資訊:
CREDENTIALS_FILENAME = "credentials.json"
QUESTIONS_SPREADSHEET_URL = "https://docs.google.com/spreadsheets/d/your-spreadsheet-id"
BOT_TOKEN = "your-bot-token"
建立核心操作類別
讓我們建立一個 Quizzer
類別來處理與試算表的互動:
import gspread
from config import CREDENTIALS_FILENAME, QUESTIONS_SPREADSHEET_URL
from random import shuffle
from datetime import datetime
class Quizzer:
def __init__(self, question_spreadsheet_url=QUESTIONS_SPREADSHEET_URL):
self.account = gspread.service_account(filename=CREDENTIALS_FILENAME)
self.spreadsheet = self.account.open_by_url(question_spreadsheet_url)
self.topics = {elem.title: elem.id for elem in self.spreadsheet.worksheets()}
self.answers = self.spreadsheet.get_worksheet_by_id(self.topics.get("Results"))
程式碼解密
讓我們逐項解析這段程式碼的重要元素:
初始化設定:
self.account
:使用認證檔案建立服務帳號連線self.spreadsheet
:開啟指定的試算表檔案self.topics
:建立工作表標題和 ID 的對應字典self.answers
:取得用於儲存回覆的工作表
安全性考量:
- 認證資訊存放在獨立的 JSON 檔案中
- 使用服務帳號而非個人帳號,提高安全性
- 設定檔與主程式分離,方便管理和更新
效能最佳化:
- 使用字典儲存工作表對應關係,提升查詢效率
- 預先載入回覆工作表,避免重複開啟
玄貓在實作過程中發現,這樣的架構設計不僅讓程式碼更容易維護,也大幅提升了系統的執行效率。特別是在處理大量回覆時,預先載入的設計可以明顯減少 API 呼叫次數。
核心功能實作準備
在實作具體功能之前,我們需要先確保系統的基礎架構穩固。玄貓建議在開始處理資料之前,先實作一些基本的檢查機制:
def validate_connection(self):
try:
self.spreadsheet.sheet1
return True
except Exception as e:
print(f"連線驗證失敗:{str(e)}")
return False
這個簡單的驗證方法可以確保我們與 Google 試算表的連線狀態正常,避免在執行關鍵操作時才發現連線問題。
在建立好這些基礎架構後,我們就可以開始實作更複雜的功能,像是資料的讀取、寫入和處理等。接下來,玄貓將帶領大家一步實作這些功能,確保系統的穩定性和可靠性。
Google Sheet測試系統的進階功能實作
在這個部分,玄貓將帶領大家探討如何實作一個完整的 Google Sheet 測試系統。這個系統不僅能管理測試題目,還能追蹤使用者的作答紀錄。讓我們從程式碼解析開始:
首先,我們來看核心的題目管理功能:
class QuizManager:
def get_topics(self):
# 取得所有測試主題,排除 Results 頁面
return {
key: value
for key, value in self.topics.items()
if key != "Results"
}
def get_question_by_topic(self, topic_name):
# 根據主題取得題目
if topic_name in self.topics:
worksheet = self.spreadsheet.get_worksheet_by_id(
self.topics.get(topic_name)
)
return worksheet.get_all_records()
return []
def questions_and_answers(self, topic_name):
# 整理題目格式並隨機打亂答案順序
questions = self.get_question_by_topic(topic_name)
result = []
for elem in questions:
answers = [
elem["correct_answer"],
elem["wrong_answer_1"],
elem["wrong_answer_2"],
elem["wrong_answer_3"]
]
shuffle(answers)
new_format = {
"question": elem["question"],
"correct_answer": elem["correct_answer"],
"answers": answers
}
result.append(new_format)
return result
** **
get_topics
方法:- 使用字典推導式過濾掉名為 “Results” 的工作表
- 回傳所有可用的測試主題
get_question_by_topic
方法:- 接收主題名稱作為引數
- 透過工作表 ID 取得對應的題目資料
- 使用
get_all_records()
取得完整的題目記錄
questions_and_answers
方法:- 重新格式化題目資料結構
- 將正確答案與錯誤答案合併並隨機打亂順序
- 建立新的題目格式,包含問題、正確答案和混合後的答案選項
接著是記錄使用者回答的功能:
def write_answer_to_result_cell(self, user_id, question, answer, correct_answer):
# 取得目前答案列表的最後一列
index = len(list(filter(None, self.answers.col_values(1)))) + 1
# 寫入新的答案記錄
self.answers.update(
f"A{index}:E{index}",
[[user_id, question, answer, correct_answer, f"{datetime.now()}"]]
)
** **
- 使用
filter
和col_values
計算目前已有的答案數量 - 在最後一列之後新增一筆記錄
- 記錄內容包含:
- 使用者 ID
- 問題內容
- 使用者的答案
- 正確答案
- 作答時間戳記
為了實作 Telegram 機器人整合,我們使用 aiogram 框架:
import asyncio
from aiogram import Bot, Dispatcher, F, Router, types
from aiogram.filters.command import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
# 建立基本元件
router = Router()
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
** **
使用
asyncio
處理非同步操作從
aiogram
引入必要的元件:Bot
:機器人主要例項Dispatcher
:訊息分發器Router
:訊息路由器FSMContext
和StatesGroup
:狀態管理相關元件
建立三個核心元件:
router
:負責訊息路由分發bot
:主要的機器人例項dp
:處理訊息分發的分派器
這個系統的設計充分考慮到了可擴充套件性和維護性。透過清晰的類別結構和方法分離,讓程式碼更容易理解和擴充。同時,非同步設計確保了在處理大量使用者時的效能表現。
在實際應用中,這個系統能夠有效地管理測試題目、追蹤使用者作答,並提供即時的互動體驗。對於需要建立線上測驗或問答系統的開發者來說,這是一個很好的參考架構。
在開發聊天機器人時,狀態管理是一個關鍵挑戰。玄貓今天要分享如何運用有限狀態機(Finite State Machine,FSM)的概念來開發一個人工智慧問答機器人,這種方法能有效管理機器人的對話流程與狀態轉換。
核心架構設計
狀態機建構
首先,玄貓建立了一個問答管理器和狀態定義:
quizzer = Quizzer() # 建立問答管理物件
class CurrentQuiz(StatesGroup):
start = State() # 初始狀態
choosing_test = State() # 選擇測驗狀態
question = State() # 問答狀態
這個狀態機設計反映了玄貓多年開發經驗中的最佳實踐 - 將複雜的對話流程拆分為清晰的狀態,每個狀態都有其明確的職責。
輔助功能實作
為了最佳化使用者經驗,玄貓實作了兩個關鍵的輔助功能:
def create_keyboard(options):
"""動態生成客製化鍵盤介面"""
return types.ReplyKeyboardMarkup(
keyboard=[[types.KeyboardButton(text=f"{elem}")] for elem in options]
)
async def ask_question(message: types.Message, state: FSMContext):
"""處理問題傳送邏輯"""
data = await state.get_data()
question = data["current_question"]
keyboard = create_keyboard(question["answers"])
await message.answer(question["question"], reply_markup=keyboard)
await state.update_data(current_question=question)
await state.update_data(choosing_test=data["choosing_test"][1:])
狀態處理器實作
接下來是核心的狀態處理邏輯:
@router.message(CurrentQuiz.start)
@router.message(Command("start"))
async def cmd_start(message: types.Message, state: FSMContext):
keyboard = create_keyboard(quizzer.get_topics().keys())
await message.answer(
"歡迎使用 AI 問答機器人!\n請從以下主題中選擇一個開始測驗:",
reply_markup=keyboard
)
await state.set_state(CurrentQuiz.choosing_test)
@router.message(CurrentQuiz.choosing_test, F.text.in_(quizzer.get_topics().keys()))
async def start_quizz(message: types.Message, state: FSMContext):
chosen_test_title = message.text
choosing_test = quizzer.questions_and_answers(message.text)
await state.update_data(
choosing_test=choosing_test,
current_question=choosing_test[0]
)
await message.answer(f"已選擇主題:{chosen_test_title}")
await state.set_state(CurrentQuiz.question)
await ask_question(message, state)
程式碼解密
讓玄貓為各個關鍵部分進行詳細解說:
Quizzer類別初始化:
- 建立問答管理器,負責存取與管理題函式庫
- 提供介面與資料函式庫部資源互動
狀態定義:
start
:初始狀態,使用者首次進入或重置時的狀態choosing_test
:選擇測驗階段,等待使用者選擇題目類別question
:問答階段,進行實際的問答互動
鍵盤生成功能:
create_keyboard
函式動態生成符合使用者當前情境的按鍵選項- 採用 Telegram Bot API 的 ReplyKeyboardMarkup 實作客製化介面
問題處理邏輯:
ask_question
函式整合了問題傳送、狀態更新等核心功能- 使用 FSMContext 管理對話狀態,確保資料的一致性
狀態處理器:
- 使用裝飾器語法優雅地處理不同狀態的訊息
- 實作狀態轉換邏輯,確保對話流程的順暢性
在實際開發過程中,玄貓發現這種根據 FSM 的設計模式特別適合處理複雜的對話流程。它不僅讓程式碼更容易維護,也大幅提升了系統的可擴充套件性。當需要新增功能時,只需要定義新的狀態和相應的處理邏輯即可。
這套架構的另一個優點是狀態管理的清晰性。每個狀態都有明確的職責和轉換規則,這讓除錯和功能擴充套件變得更加直觀。在處理大型對話系統時,這種清晰的結構特別重要。
建置Telegram機器人的狀態管理與答題流程
在實作Telegram機器人時,玄貓發現狀態管理是一個關鍵課題。讓我們來看如何有效管理使用者的答題過程與狀態追蹤:
async def handle_quiz_selection(message: types.Message, state: FSMContext):
# 儲存測驗題目到狀態中
choosing_test = get_quiz_questions() # 取得測驗題目
await state.update_data(
choosing_test=choosing_test,
current_question=choosing_test[0]
)
# 開始詢問第一個問題
await ask_question(message, state)
@router.message(CurrentQuiz.question)
async def handle_answer(message: types.Message, state: FSMContext):
# 取得目前狀態資料
data = await state.get_data()
# 記錄使用者答案
quizzer.save_user_answer(
username=message.from_user.username,
question=data["current_question"]["question"],
user_answer=message.text,
correct_answer=data["current_question"]["correct_answer"]
)
# 檢查是否還有未回答的問題
remaining_questions = data["choosing_test"][1:] # 移除目前問題
if remaining_questions:
# 更新狀態並詢問下一題
await state.update_data(
choosing_test=remaining_questions,
current_question=remaining_questions[0]
)
await ask_question(message, state)
else:
# 測驗結束,清除狀態
await state.clear()
await message.answer(
"測驗已完成!",
reply_markup=create_keyboard(["選擇新的測驗"])
)
await state.set_state(CurrentQuiz.start)
這段程式碼展示了幾個重要的設計概念:
狀態管理:使用FSMContext來追蹤使用者的答題進度,確保題目按序呈現。
資料持久化:每個答案都會即時儲存到資料函式庫免資料遺失:
quizzer.save_user_answer(
username=message.from_user.username,
question=data["current_question"]["question"],
user_answer=message.text,
correct_answer=data["current_question"]["correct_answer"]
)
- 流程控制:根據剩餘題目數量決定下一步動作:
- 若還有題目,更新狀態並詢問下一題
- 若題目結束,清除狀態並回傳初始選單
最後,啟動機器人的主程式碼:
async def main():
# 註冊路由
dp.include_router(router)
# 啟動輪詢
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())
為了確保機器人的穩定執行,玄貓建議將其佈署到雲端平台。以Amvera為例,它提供了簡單的佈署流程:
- 建立專案並設定環境
- 準備必要的設定檔案
- 產生dependencies清單(建議手動編寫而非使用pip freeze,以減少不必要的依賴)
- 使用網頁介面或Git進行佈署
這樣的佈署方式可以確保機器人24/7持續運作,不受本地電腦狀態影響。在實際專案中,玄貓常會新增監控與錯誤處理機制,確保服務的穩定性。
佈署時要特別注意幾個關鍵點:
- 環境變數的正確設定
- 依賴套件的版本控制
- 錯誤處理與日誌記錄
- 資源使用限制的設定
這些細節都會影響機器人的執行效能與穩定性。良好的佈署實踐可以大幅減少維護成本,提高服務品質。
在開發 Telegram Bot 的過程中,選擇合適的資料儲存方案至關重要。今天玄貓要分享一個有趣的技術方案:使用 Google Sheets 作為 Telegram Bot 的資料函式庫個方案特別適合小型專案或原型開發,讓非技術人員也能輕鬆管理資料。
為何選擇 Google Sheets 作為資料函式庫在多年的開發經驗中,玄貓發現傳統資料函式庫有時過於複雜。對於簡單的問答機器人或資料收集應用,使用 Google Sheets 作為資料儲存具有以下優勢:
- 零維護成本
- 使用者友善的介面
- 即時協作功能
- 強大的試算表功能
- 完整的 API 支援
技術架構設計
核心元件
開發這個系統時,玄貓採用了以下技術堆積積疊:
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build
import telebot
import os
# 設定 Google Sheets API 認證
SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly']
SERVICE_ACCOUNT_FILE = 'credentials.json'
# 初始化 Telegram Bot
bot = telebot.TeleBot(os.getenv('BOT_TOKEN'))
資料存取層設計
在設計資料存取層時,玄貓特別注重效能與可維護性:
class GoogleSheetsHandler:
def __init__(self):
self.creds = Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
self.service = build('sheets', 'v4', credentials=self.creds)
self.sheet = self.service.spreadsheets()
def get_data(self, spreadsheet_id, range_name):
result = self.sheet.values().get(
spreadsheetId=spreadsheet_id,
range=range_name
).execute()
return result.get('values', [])
** **
Credentials.from_service_account_file()
: 負責載入 Google API 的認證資訊,確保我們有許可權存取指定的試算表build('sheets', 'v4', credentials=self.creds)
: 初始化 Google Sheets API 客戶端get_data()
方法封裝了資料讀取邏輯,使用 API 存取指定範圍的資料
機器人指令處理
在實作指令處理時,玄貓採用了模組化的設計方式:
@bot.message_handler(commands=['start'])
def handle_start(message):
user_id = message.from_user.id
response = "歡迎使用問答機器人!\n請輸入您的問題,我會為您尋找答案。"
bot.reply_to(message, response)
@bot.message_handler(func=lambda message: True)
def handle_question(message):
question = message.text.lower()
answer = find_answer_in_sheets(question)
if answer:
bot.reply_to(message, answer)
else:
bot.reply_to(message, "抱歉,我找不到相關的答案。")
** **
@bot.message_handler(commands=['start'])
: 處理使用者傳送 /start 指令的情況handle_start()
: 傳送歡迎訊息,介紹機器人功能handle_question()
: 處理一般文字訊息,在 Google Sheets 中查詢對應答案find_answer_in_sheets()
: 實作問答邏輯,將使用者問題與試算表中的資料進行比對
佈署與維運建議
在實際佈署過程中,玄貓建議採用以下最佳實踐:
- 使用環境變數管理敏感資訊
- 實作錯誤處理與日誌記錄
- 定期備份 Google Sheets 資料
- 設定適當的快取機制
- 監控 API 使用配額
效能最佳化策略
在開發過程中,玄貓發現了幾個關鍵的效能最佳化點:
class CachedGoogleSheetsHandler:
def __init__(self, cache_duration=300): # 5分鐘快取
self.cache = {}
self.cache_duration = cache_duration
self.last_update = {}
def get_cached_data(self, spreadsheet_id, range_name):
cache_key = f"{spreadsheet_id}:{range_name}"
current_time = time.time()
if (cache_key in self.cache and
current_time - self.last_update[cache_key] < self.cache_duration):
return self.cache[cache_key]
data = self.get_data(spreadsheet_id, range_name)
self.cache[cache_key] = data
self.last_update[cache_key] = current_time
return data
** **
cache_duration
: 設定快取有效期,避免頻繁讀取 Google Sheetsget_cached_data()
: 實作快取邏輯,先檢查快取是否有效- 使用
time.time()
追蹤快取時間,確保資料及時更新 - 透過
cache_key
區分不同的資料請求
在實際運作中,這套系統展現出優異的擴充套件性與維護性。透過 Google Sheets 的直觀介面,內容管理者可以輕鬆更新問答資料,而技術團隊則專注於最佳化機器人的效能與使用者經驗。這種靈活的架構特別適合需要頻繁更新內容的應用場景。
經過多次迭代與實戰測試,玄貓發現這個解決方案不僅降低了開發和維護成本,更為非技術團隊提供了友善的內容管理介面。透過適當的快取策略和錯誤處理機制,系統能夠穩定處理大量使用者請求,同時保持較低的營運成本。