Apps Script で「できない」壁にぶつかったことはありませんか
Google Workspace で AI 自動化を試みるとき、多くの方が最初に選ぶのは Apps Script です。手軽に始められるし、Gemini との統合機能も Google が用意してくれています。
ただ、少し複雑なことをしようとした瞬間に限界が見えてきます。外部ライブラリが使えない、実行時間の上限が 6 分、エラーハンドリングが不自由、ローカルでのデバッグが難しい——こういった制約に何度かぶつかってきた方も多いのではないでしょうか。
Apps Script を一切使わず、Python だけで Google Sheets の読み書きと Gemini API による AI 処理を連携させる方法を順を追って整理していきます。スケジューラに乗せてバッチ実行したり、FastAPI サーバーのエンドポイントとして組み込んだりと、自由度が一気に広がります。
実用例として、顧客フィードバック(自由記述テキスト)をシートから読み取り、Gemini がカテゴリ分類・感情分析・改善提案を行い、結果を同じシートに書き戻すパイプラインを構築します。
必要なもの・全体構成
必要なものを先に整理しておきます。
- Google Cloud プロジェクト(無料枠でOK)
- サービスアカウントと認証 JSON ファイル
- Google Sheets API の有効化
- Gemini API キー(Google AI Studio から取得)
- Python 3.10 以上
全体の流れはこうなります。
- サービスアカウントの作成と権限設定
- 対象スプレッドシートをサービスアカウントと共有
- Python で Sheets API 認証とデータ読み取り
- Gemini API で AI 処理(分類・要約・抽出)
- 結果をシートに書き戻す
サービスアカウントのセットアップ
Google Cloud Console(console.cloud.google.com)にアクセスし、プロジェクトを選択または新規作成します。
API の有効化
「API とサービス」→「ライブラリ」から以下を有効化します。
- Google Sheets API
- Google Drive API(共有ファイルへのアクセスに必要)
サービスアカウントの作成
「API とサービス」→「認証情報」→「認証情報を作成」→「サービスアカウント」と進みます。
名前は何でも構いません。ロールは「編集者」を付与しておくと Sheets の読み書き両方ができます。作成後、「キー」タブから「鍵を追加」→「JSON」を選択してキーファイルをダウンロードします。このファイルは .gitignore に追加して、絶対に Git にコミットしないようにしてください。
スプレッドシートとの共有
ダウンロードした JSON ファイルを開き、client_email の値(xxx@your-project.iam.gserviceaccount.com の形式)をコピーします。処理対象の Google スプレッドシートを開き、「共有」からそのメールアドレスを追加します。「編集者」権限で追加します。
Python 環境の構築
pip install google-auth google-auth-oauthlib google-api-python-client google-generativeaiプロジェクトディレクトリ構成はこのようにします。
sheets-gemini-pipeline/
├── credentials.json # サービスアカウントキー(gitignore必須)
├── pipeline.py # メイン処理
└── .env # GEMINI_API_KEY を記載
スプレッドシートからデータを読み取る
まず、Sheets API の認証とデータ読み取りを実装します。
import os
from google.oauth2 import service_account
from googleapiclient.discovery import build
# サービスアカウント認証
SCOPES = [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive.readonly",
]
def get_sheets_service():
"""Google Sheets API サービスオブジェクトを取得する"""
credentials = service_account.Credentials.from_service_account_file(
"credentials.json",
scopes=SCOPES,
)
return build("sheets", "v4", credentials=credentials)
def read_sheet(spreadsheet_id: str, range_name: str) -> list[list[str]]:
"""
スプレッドシートから指定範囲のデータを読み取る
Args:
spreadsheet_id: URL の /d/{ID}/ 部分
range_name: 例 "Sheet1\!A2:C100"(ヘッダー行を除いた範囲)
Returns:
2次元リスト。空の場合は []
"""
service = get_sheets_service()
result = (
service.spreadsheets()
.values()
.get(spreadsheetId=spreadsheet_id, range=range_name)
.execute()
)
return result.get("values", [])spreadsheet_id は、スプレッドシートの URL https://docs.google.com/spreadsheets/d/{ここの部分}/edit から取得できます。
Gemini API で AI 処理を実装する
次に、Gemini API を使った分析処理を実装します。ここでは顧客フィードバックを例に、カテゴリ分類と感情分析を行います。
import json
import google.generativeai as genai
import typing_extensions as typing
# 環境変数から API キーを読み込む
genai.configure(api_key=os.environ["GEMINI_API_KEY"])
model = genai.GenerativeModel("gemini-2.5-flash-preview")
# 出力スキーマを定義(TypedDict で型安全に)
class FeedbackAnalysis(typing.TypedDict):
category: str # バグ報告 / 機能要望 / 使い方の質問 / 称賛 / その他
sentiment: str # positive / neutral / negative
urgency: str # high / medium / low
summary: str # 30文字以内の要約
suggestion: str # 改善提案(簡潔に)
def analyze_feedback(feedback_text: str) -> FeedbackAnalysis:
"""
顧客フィードバックを Gemini で分析する
Args:
feedback_text: 分析対象のフィードバックテキスト
Returns:
FeedbackAnalysis 型の辞書
Raises:
ValueError: Gemini の応答が JSON パースできない場合
"""
prompt = f"""以下の顧客フィードバックを分析してください。
フィードバック:
{feedback_text}
分類ルール:
- category: バグ報告 / 機能要望 / 使い方の質問 / 称賛 / その他 のいずれか
- sentiment: positive / neutral / negative のいずれか
- urgency: high(即対応必要) / medium(近いうちに対応) / low(余裕あり)
- summary: 30文字以内で内容を要約
- suggestion: 具体的な改善提案(改善不要なら「対応不要」)"""
response = model.generate_content(
prompt,
generation_config=genai.GenerationConfig(
response_mime_type="application/json",
response_schema=FeedbackAnalysis,
),
)
return json.loads(response.text)response_schema に FeedbackAnalysis を指定することで、Gemini の出力が必ずこのスキーマに従った JSON になります。json.loads() でパースエラーが起きることがほぼなくなるため、パイプラインの信頼性が格段に上がります。
結果をシートに書き戻す
分析結果を元のスプレッドシートの別列に書き込みます。
def write_results(
spreadsheet_id: str,
results: list[FeedbackAnalysis],
start_row: int = 2,
start_col: str = "D",
) -> None:
"""
分析結果をスプレッドシートに書き込む
Args:
spreadsheet_id: 書き込み先のスプレッドシート ID
results: analyze_feedback() の結果リスト
start_row: データ書き込み開始行(デフォルト 2 = ヘッダー行の次)
start_col: 書き込み開始列(デフォルト D 列)
"""
service = get_sheets_service()
# 2次元リストに変換(API の expects する形式)
values = [
[
r["category"],
r["sentiment"],
r["urgency"],
r["summary"],
r["suggestion"],
]
for r in results
]
end_row = start_row + len(values) - 1
end_col = chr(ord(start_col) + 4) # D + 4列 = H
range_name = f"Sheet1\!{start_col}{start_row}:{end_col}{end_row}"
body = {"values": values}
service.spreadsheets().values().update(
spreadsheetId=spreadsheet_id,
range=range_name,
valueInputOption="USER_ENTERED", # 書式を Sheets が解釈する
body=body,
).execute()
print(f"✅ {len(values)} 件の結果を {range_name} に書き込みました")パイプライン全体を組み合わせる
個々の関数をつないで、エラーハンドリングを含む実用的なパイプラインに仕上げます。
import time
SPREADSHEET_ID = "あなたのスプレッドシートID"
INPUT_RANGE = "Sheet1\!C2:C1000" # C列: フィードバックテキスト
def run_pipeline() -> None:
"""フィードバック分析パイプラインのメイン処理"""
print("📊 スプレッドシートからデータを読み取り中...")
rows = read_sheet(SPREADSHEET_ID, INPUT_RANGE)
if not rows:
print("処理対象データがありませんでした")
return
# 空セルをスキップ
feedback_texts = [row[0].strip() for row in rows if row and row[0].strip()]
print(f"📝 {len(feedback_texts)} 件のフィードバックを処理します")
results = []
for i, text in enumerate(feedback_texts, 1):
print(f" [{i}/{len(feedback_texts)}] 処理中: {text[:30]}...")
try:
analysis = analyze_feedback(text)
results.append(analysis)
except Exception as e:
# 1件失敗しても全体を止めない
print(f" ⚠️ エラー(行 {i + 1}): {e}")
results.append({
"category": "エラー",
"sentiment": "unknown",
"urgency": "low",
"summary": "処理エラー",
"suggestion": str(e)[:50],
})
# レート制限対策: 1秒待機(60rpm の無料枠を超えないように)
if i % 10 == 0:
time.sleep(1)
print("📤 結果をスプレッドシートに書き込み中...")
write_results(SPREADSHEET_ID, results)
print("🎉 パイプライン完了!")
if __name__ == "__main__":
run_pipeline()実際に実行すると、スプレッドシートの D〜H 列にカテゴリ・感情・緊急度・要約・改善提案が自動で埋まっていきます。100件のフィードバックであれば 2〜3 分程度で完了します。
よくある実装上の詰まりポイント
認証エラー「Request had invalid authentication credentials」
サービスアカウントをスプレッドシートに共有するのを忘れているケースが大半です。credentials.json の client_email を確認し、対象シートの「共有」に追加されているか確認してください。
もう一つよくある原因は、SCOPES に Sheets API のスコープしか含めず、Drive API のスコープを省略しているケースです。共有ドライブ上のファイルにアクセスする場合は https://www.googleapis.com/auth/drive.readonly が必要になります。
Gemini のレスポンスが稀に崩れる
response_schema を指定していても、複雑なプロンプトでは稀にスキーマ外の応答が返ることがあります。json.loads() の周りを try/except で囲み、パースに失敗した行はスキップしてリトライキューに入れるのが安全です。
try:
result = json.loads(response.text)
except json.JSONDecodeError:
# フォールバック: テキストから手動パース or スキップ
result = {"category": "パースエラー", "sentiment": "unknown", ...}429 エラー(レート制限)
無料枠では 60 rpm(リクエスト/分)の制限があります。フィードバックが数百件ある場合は、10 件ごとに time.sleep(1) を挟むか、google-genai SDK の指数バックオフ機能を使うのが現実的です。
レート制限の詳細はGemini API 料金・クォータ管理ガイドも参考にしてください。
応用:スケジュール実行・サーバー統合
このスクリプトをそのまま cron や Cloud Scheduler に乗せれば、毎日自動で実行される定期バッチになります。
# crontab -e で設定例(毎日 9:00 JST に実行)
0 0 * * * cd /path/to/project && python pipeline.py >> logs/pipeline.log 2>&1FastAPI との組み合わせも自然です。
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
@app.post("/analyze")
async def trigger_analysis(background_tasks: BackgroundTasks):
background_tasks.add_task(run_pipeline)
return {"status": "started"}Webhook を受け取るたびに処理を走らせる、といった使い方も同じパターンで対応できます。Gemini API × FastAPI バックエンド実装ガイドでは、より本格的なサーバー構成を解説しています。
行数が増えたときに最初に壊れる場所
このパイプラインは数百行のシートでは快適に動きますが、数千行を超えたあたりから別の問題が出てきます。私が最初に踏んだのは、Gemini API のレート制限ではなく、Sheets API 側の書き込み回数の上限でした。
個人開発で書いた初版がまさにこれで、1行ずつ update を呼ぶ実装は、行数がそのままリクエスト数になります。処理そのものは成功していても、途中で 429 が返り、再実行すると最初の数百行を二重に処理することになります。
私自身が実際に効果を確認できたのは、次の3点です。
- 書き込みを
batchUpdateにまとめる。100行ごとにまとめるだけで、リクエスト数は 1/100 になります - 処理済み行にマーカー列を持たせる。再実行時にスキップできるようにしておくと、途中で落ちても安全です
- Gemini API 側の呼び出しには指数バックオフを入れる。単純な
sleep(1)の固定待機では、混雑時に抜けられません
このうち、実務でいちばん効いたのは2番目のマーカー列でした。冪等性が確保できていれば、途中で落ちること自体は怖くありません。個人開発では監視を手厚くできないぶん、「落ちても壊れない」設計に寄せるほうが結果的に楽になります。
システム指示の設計そのものを見直したい場合は「Gemini API のシステム指示とプロンプト設計」も参考になるはずです。
Apps Script と Python の使い分け
どちらが優れているというわけではなく、ユースケースによって使い分けるのが現実的だと思っています。
スプレッドシートを「主役」として人が手動で操作しながら AI を補助的に使うなら、Apps Script の方が断然楽です。セットアップも不要ですし、スプレッドシートのメニューから直接呼び出せます。
一方、スプレッドシートをデータストアとして扱い、AI 処理がメインの場合は Python が向いています。複数の外部 API を組み合わせる、処理時間が長い、エラーリカバリが必要、ローカルでのデバッグを繰り返したい——こういった場面では Python の自由度が活きてきます。
最初から完璧な設計を目指さず、まずシンプルなスクリプトで動かして、必要に応じてサーバー化・スケジュール化していくのが、個人開発者には現実的なアプローチではないでしょうか。
run_pipeline() を python pipeline.py で一度動かしてみることから始めてみてください。