- 前回の記事で、「Coopの定期宅配の使い勝手が悪いので注文メールを任意の曜日に自動的に送信させる物を作った」まで行きました。
- 今回は、メール送信された物をGASを使ってGmailを読み込みgeminiを使ってスプシに記載していきます。
- 最終的にスプシで管理していきます。
試してみた
- gemini APIキーの取得
https://aistudio.google.com/api-keys
- GASで動かしているソース
// ▼▼▼【設定項目】ここから ▼▼▼
// 1. あなたのGemini APIキーを貼り付けてください
const GEMINI_API_KEY = "API_KEY";
// 2. 検索したいGmailの条件を指定してください
// 例: 'from:supermarket@example.com subject:"発送のお知らせ"'
// 特定のネットスーパーの送信元アドレスや件名で絞り込むと精度が上がります
const GMAIL_SEARCH_QUERY = 'subject:("【GCweb】" "カタログでご注文を受け付けている商品です。")';
// 3. 結果を書き込むシート名を指定してください
const SHEET_NAME = "シート1";
// ▲▲▲【設定項目】ここまで ▲▲▲
/**
* メインの処理を実行する関数
*/
function main() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
if (!sheet) {
SpreadsheetApp.getUi().alert(`シート「${SHEET_NAME}」が見つかりません。`);
return;
}
// 検索条件に合致し、まだ処理されていないメールのスレッドを取得
const threads = GmailApp.search(`${GMAIL_SEARCH_QUERY} -label:処理済み`);
if (threads.length === 0) {
console.log("新しいメールはありませんでした。");
return;
}
// 処理済みラベルがなければ作成
const labelName = "処理済み";
let label = GmailApp.getUserLabelByName(labelName);
if (!label) {
label = GmailApp.createLabel(labelName);
}
for (const thread of threads) {
const message = thread.getMessages()[0]; // スレッドの最初のメッセージを取得
const body = message.getPlainBody(); // メールの本文をテキストで取得
const date = message.getDate(); // メールの受信日を取得
console.log(`処理中のメール: ${message.getSubject()}`);
// Gemini APIに本文を渡して商品リストを抽出
const extractedItems = callGeminiAPI(body);
if (extractedItems && extractedItems.length > 0) {
// 抽出した商品リストをスプレッドシートに書き込む
for (const item of extractedItems) {
sheet.appendRow([date, item.name, item.price]);
}
console.log(`${extractedItems.length}件の商品をシートに書き込みました。`);
// 処理が終わったスレッドにラベルを付けて、次回から重複処理しないようにする
thread.addLabel(label);
} else {
console.log("メールから商品を抽出できませんでした。");
}
}
}
/**
* ★★★ 応答解析を強化した最終版 ★★★
* Gemini APIを呼び出して、メール本文から商品名と金額を抽出する関数
* @param {string} mailBody - 解析したいメールの本文
* @return {Array<{name: string, price: number}> | null} - 抽出した商品の配列
*/
function callGeminiAPI(mailBody) {
// このURLは、これまでの経緯から最も安定しているものです
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}`;
const prompt = `
以下のメール本文はネットスーパーの購入履歴です。
この中から「商品名」と「金額」を抽出し、以下のJSON形式の配列で出力してください。
金額は数値のみを抽出してください。商品ではない項目(送料、手数料、ポイントなど)は含めないでください。
出力形式:
[
{"name": "商品名1", "price": 金額1},
{"name": "商品名2", "price": 金_額2}
]
---
${mailBody}
---
`;
const payload = {
contents: [{
parts: [{ text: prompt }]
}]
};
const options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
muteHttpExceptions: true // エラー時も応答内容を確認するためtrueにする
};
try {
const response = UrlFetchApp.fetch(url, options);
const responseText = response.getContentText();
// Geminiからの応答がJSONを含むかチェック
const candidate = JSON.parse(responseText).candidates && JSON.parse(responseText).candidates[0];
const content = candidate && candidate.content;
const part = content && content.parts && content.parts[0];
const geminiText = part && part.text;
if (geminiText) {
// 応答テキストから```json ... ```で囲まれた部分を正規表現で抽出
const jsonMatch = geminiText.match(/```json\s*([\s\S]*?)\s*```/);
if (jsonMatch && jsonMatch[1]) {
// 抽出したJSON文字列だけをパースする
return JSON.parse(jsonMatch[1]);
} else {
// ```がない場合、直接パースを試みる
return JSON.parse(geminiText);
}
}
console.error("Geminiの応答から有効なテキスト部分を抽出できませんでした。応答内容:", responseText);
return null;
} catch (e) {
console.error("Gemini APIの呼び出し中、またはJSONの解析中にエラーが発生しました:", e);
// エラー発生時の生の応答内容をログに出力すると、デバッグに役立ちます
const errorResponse = UrlFetchApp.getLastResponse();
if (errorResponse) {
console.error("エラー発生時の生の応答:", errorResponse.getContentText());
}
return null;
}
}
実行結果

つまづきポイントとしては2点
- GAS実行時に下記の様にエラーっぽく見える
- 解決策としては、「詳細」を押して進める
