バイブコーディング×ノーコードで名刺管理アプリを構築してみた【第3編】GAS×GeminiでOCR自動化
巷にあふれる名刺管理アプリを参考に、AppSheetとGAS×Geminiを組み合わせてOCR自動化を実現。画像から自動で名刺情報を抽出・登録する仕組みを徹底解説。
OCR自動化に挑戦した理由
名刺管理の現場では、名刺の情報を手入力するのが非常に面倒です。そこで、画像から文字を読み取って自動で必要な項目を抽出・記録する仕組みを作れないかと考えました。
どうやって構築したか?
AppSheet上に保存した名刺画像(表・裏)のURLを、Google Apps Script(GAS)で処理し、Gemini APIを使ってOCRと項目抽出を行う構成です。得られた情報はスプレッドシートのK列〜Q列に書き込み、完了フラグを「済」として記録します。
全体の処理フロー
- 名刺画像がスプレッドシートにアップロードされる
- GASでURLから画像を取得
- Gemini APIでOCR処理を実行
- 取得したテキストから「氏名」「メール」「会社名」などをJSON形式で抽出
- 各カラムに自動で書き込み、完了フラグを設定
使用したGASコード
以下が、実際に使用したコードです(長文のため折りたたみ)。
※シートIDやAPIキー、フォルダIDは各自の環境に合わせて差し替えてください。
クリックしてコードを見る
/**
* 名刺交換ログ1行を処理:OCR→項目抽出→K〜Q列書き込み
* (gemini-2.0-flash-lite モデル使用版)
*/
function processBusinessCardRowWithGemini() {
const sheetId = 'XXXX';
const folderId = 'YYYY';
const sheetName = '名刺交換ログ';
const apiKey = 'ZZZZ';
const ss = SpreadsheetApp.openById(sheetId);
const sheet = ss.getSheetByName(sheetName);
const lastRow = sheet.getLastRow();
const headers = sheet.getRange(1,1,1,sheet.getLastColumn())
.getValues()[0].map(h=>h.toString().trim());
const idx = name => headers.indexOf(name);
const frontImgCol = idx("名刺画像(表)");
const backImgCol = idx("名刺画像(裏)");
const ocrFrontCol = idx("OCRテキスト_表");
const ocrBackCol = idx("OCRテキスト_裏");
const statusCol = idx("OCR済");
const colK = 11, colL = 12, colM = 13,
colN = 14, colO = 15, colP = 16, colQ = 17;
const folder = DriveApp.getFolderById(folderId);
for (let r = 2; r <= lastRow; r++) {
const row = sheet.getRange(r,1,1,sheet.getLastColumn()).getValues()[0];
if (row[statusCol] === "済") continue;
const frontUrl = row[frontImgCol], backUrl = row[backImgCol];
if (!frontUrl && !backUrl) continue;
let ocrF="", ocrB="";
if (frontUrl) {
const blob = getImageBlobFromUrl(frontUrl, folder);
if (blob) ocrF = ocrWithGemini(blob, apiKey);
}
if (backUrl) {
const blob = getImageBlobFromUrl(backUrl, folder);
if (blob) ocrB = ocrWithGemini(blob, apiKey);
}
if (ocrF) sheet.getRange(r, ocrFrontCol+1).setValue(ocrF);
if (ocrB) sheet.getRange(r, ocrBackCol+1).setValue(ocrB);
const extracted = extractBusinessCardFieldsFromText(ocrF, apiKey);
if (extracted["氏名"]) sheet.getRange(r, colK).setValue(extracted["氏名"]);
if (extracted["メールアドレス"]) sheet.getRange(r, colL).setValue(extracted["メールアドレス"]);
if (extracted["会社名"]) sheet.getRange(r, colM).setValue(extracted["会社名"]);
if (extracted["部署"]) sheet.getRange(r, colN).setValue(extracted["部署"]);
if (extracted["役職"]) sheet.getRange(r, colO).setValue(extracted["役職"]);
if (extracted["電話番号(代表)"]) sheet.getRange(r, colP).setValue(extracted["電話番号(代表)"]);
if (extracted["電話番号(個人)"]) sheet.getRange(r, colQ).setValue(extracted["電話番号(個人)"]);
sheet.getRange(r, statusCol+1).setValue("済");
SpreadsheetApp.flush();
}
}
function getImageBlobFromUrl(url, folder) {
const fileName = url.split('/').pop();
const files = folder.getFilesByName(fileName);
if (files.hasNext()) return files.next().getBlob();
Logger.log("ファイル未検出: " + fileName);
return null;
}
function ocrWithGemini(blob, apiKey) {
const base64Image = Utilities.base64Encode(blob.getBytes());
const payload = {
contents:[{ parts:[{ inlineData:{ mimeType:blob.getContentType(), data:base64Image }}]}],
generationConfig:{ temperature:0.2, maxOutputTokens:2048 }
};
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=${apiKey}`;
const res = UrlFetchApp.fetch(url, {
method:"post", contentType:"application/json", payload:JSON.stringify(payload)
});
return JSON.parse(res.getContentText())?.candidates?.[0]?.content?.parts?.[0]?.text || "";
}
function extractBusinessCardFieldsFromText(text, apiKey) {
const prompt = `
次のOCRテキストから、以下の項目を抽出してください:
氏名、メールアドレス、会社名、部署、役職、電話番号(代表)、電話番号(個人)
【重要】
- 必ずJSON形式でのみ返してください。
- 空の値は "" としてください。
- JSON以外は出力しないでください。
{
"氏名": "",
"メールアドレス": "",
"会社名": "",
"部署": "",
"役職": "",
"電話番号(代表)": "",
"電話番号(個人)": ""
}
OCRテキスト:
${text}
`;
const payload = {
contents:[{ parts:[{ text:prompt }]}],
generationConfig:{ temperature:0.2, maxOutputTokens:1024 }
};
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=${apiKey}`;
const res = UrlFetchApp.fetch(url, {
method:"post", contentType:"application/json", payload:JSON.stringify(payload)
});
let responseText = JSON.parse(res.getContentText())?.candidates?.[0]?.content?.parts?.[0]?.text || "";
responseText = responseText.trim()
.replace(/^```[a-zA-Z]*\r?\n/, '')
.replace(/\r?\n```$/, '')
.trim();
try {
return JSON.parse(responseText);
} catch (e) {
Logger.log("JSON parse error: " + e);
Logger.log("レスポンス(クリーニング後): " + responseText);
return {};
}
}
まとめと次回予告
今回は、名刺画像から情報を自動抽出し、スプレッドシートに反映させるOCR自動化の手順をご紹介しました。ノーコード×GAS×LLMの組み合わせで、名刺管理の手間は大きく減らせます。
次回は、何のアプリを作ろうかな~
0 件のコメント:
コメントを投稿