名刺画像をアップするだけでシートに自動転記する仕組みづくり!
みなさんは名刺管理はどうしていますか?専用アプリで撮影したり、ファイルに保管したりと様々な方法がありますが、手間や検索性に課題を感じることも多いはずです。
「急ぎで名刺の情報をシートに記載したい💦」と依頼を受けたので、
名刺のデータや画像をGoogle Driveにアップするだけで自動的にシートに情報が入る仕組みをつくりました。
完成イメージ
システム概要
・GoogleDriveに名刺画像をアップロード
・Google Vision APIを使用しOCR処理により名刺上の文字を認識
・ChatGPT APIを活用し、抽出したテキストを「氏名、会社名、役職」など、必要な項目を自動で分類・整理
1.Google Drive に名刺画像をアップロード
①Google Drive名刺フォルダを作成
Google Drive内に、名刺画像を保存するためのフォルダを作成します。
次に【名刺】フォルダ内に【登録済み】フォルダを作成してください。
(シートへ転記が終わった画像の移動先)
②【名刺】フォルダに画像をアップロード
(推奨:なるべく鮮明な画像を使用)
2.スプレッドシートの準備
①名刺データ保存用のスプシを作成
シートのヘッダー行には「会社名」「部署名」「役職」「氏名」「電話番号」など、名刺から抽出する情報に対応する項目を設定します。
シート名を「名刺データ」に設定
②一つの目のスクリプト作成
メニューの「拡張機能」から「Apps Script」をクリック
③Google Cloud Vision API の設定
GASエディターが開けたら、左メニューバーにある【プロジェクトの設定】をクリック
【スクリプトプロパティを追加】を選択
【スクリプトプロパティ】から以下のプロパティを追加してください。
Google Cloud API Keyを発行する手順
【参考記事】https://zenn.dev/tmitsuoka0423/articles/get-gcp-api-key
④コードを貼り付け
※以下をご自身の情報に書き換えて、以下のコードをスクリプトへ貼り付け。
const SOURCE_FOLDER_ID = "Google Driveに作成した名刺フォルダのID";
画像を格納するGoogle Driveの名刺フォルダのリンク/folders/この部分
以下のコードをスクリプトに貼り付け
// 名刺画像処理スクリプト
// Google Vision APIを使用して名刺画像からテキストを抽出し、スプレッドシートに保存します
/**
* メイン関数:指定フォルダ内の名刺画像を処理します
* OCR処理を実行し、処理済みファイルを完了フォルダに移動します
*/
function scanBusinessCards() {
// 設定値
const SOURCE_FOLDER_ID = "Google Driveに作成した名刺フォルダのID";
const COMPLETED_FOLDER_NAME = "登録済み";
const TARGET_SHEET_NAME = "名刺データ";
// リソースの初期化
const sourceFolder = DriveApp.getFolderById(SOURCE_FOLDER_ID);
const completedFolder = sourceFolder.getFoldersByName(COMPLETED_FOLDER_NAME).next();
const files = sourceFolder.getFiles();
const visionApiKey = PropertiesService.getScriptProperties().getProperty("VISION_API_KEY");
const targetSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(TARGET_SHEET_NAME);
Logger.log(`処理開始: フォルダID ${SOURCE_FOLDER_ID}`);
// 各画像ファイルの処理
while (files.hasNext()) {
const currentFile = files.next();
const imageData = currentFile.getBlob();
Logger.log(`処理中の名刺: ${currentFile.getName()}`);
// OCR分析の実行
const extractedText = performOcrAnalysis(visionApiKey, imageData);
if (extractedText) {
Logger.log(`テキスト抽出成功: ${currentFile.getName()}`);
saveToSpreadsheet(targetSheet, extractedText);
// 完了フォルダへ移動
moveToCompletedFolder(currentFile, completedFolder, sourceFolder);
} else {
Logger.log(`警告: テキストが検出されませんでした: ${currentFile.getName()}`);
}
}
Logger.log("名刺処理が正常に完了しました");
}
/**
* Google Vision APIを使用してOCRを実行
* @param {string} apiKey - Google Vision APIキー
* @param {Blob} imageData - 処理対象の画像データ
* @returns {string|null} 抽出されたテキストまたはnull
*/
function performOcrAnalysis(apiKey, imageData) {
const endpoint = `https://vision.googleapis.com/v1/images:annotate?key=${apiKey}`;
const requestPayload = {
"requests": [{
"image": {
"content": Utilities.base64Encode(imageData.getBytes())
},
"features": [{
"type": "TEXT_DETECTION"
}]
}]
};
const requestOptions = {
"method": "post",
"contentType": "application/json",
"payload": JSON.stringify(requestPayload)
};
try {
const apiResponse = UrlFetchApp.fetch(endpoint, requestOptions);
const responseData = JSON.parse(apiResponse.getContentText());
Logger.log("Vision API処理完了");
const textResults = responseData.responses[0].textAnnotations;
return textResults?.length > 0 ? textResults[0].description : null;
} catch (error) {
Logger.log(`Vision APIエラー: ${error.message}`);
return null;
}
}
/**
* 抽出されたテキストをスプレッドシートに保存
* @param {Sheet} sheet - 対象シート
* @param {string} extractedText - 保存するテキスト
*/
function saveToSpreadsheet(sheet, extractedText) {
const textLines = extractedText.split('\n');
const formattedRow = new Array(12).fill(""); // 空の行を初期化
// OCR結果全体を参照用に保存
sheet.appendRow([extractedText]);
// 特定のフィールドを抽出
textLines.forEach(line => {
// フィールド抽出機能の改善
const processField = (fieldName, index) => {
if (line.includes(fieldName)) {
const [_, value] = line.split(":");
formattedRow[index] = value?.trim() || "";
}
};
processField("氏名", 0);
processField("会社名", 1);
// 必要に応じて他のフィールドを追加
});
// 空でない行のみ追加
if (formattedRow.some(cell => cell !== "")) {
sheet.appendRow(formattedRow);
}
}
/**
* 処理済みファイルを完了フォルダに移動
* @param {File} file - 移動するファイル
* @param {Folder} targetFolder - 移動先フォルダ
* @param {Folder} sourceFolder - 移動元フォルダ
*/
function moveToCompletedFolder(file, targetFolder, sourceFolder) {
targetFolder.addFile(file);
sourceFolder.removeFile(file);
Logger.log(`ファイルを完了フォルダに移動: ${file.getName()}`);
}
【Ctl + S】で保存。
⑤スクリプト実行
上部バーにある「実行」をクリック
⑥アクセスを承認
初めてそのスクリプトを実行する場合は権限の確認が必要です。
そのため、『権限を確認』を押します。
【高度】または【詳細】(英語の場合は【(Advanced)】)をクリック
「無題のプロジェクト(安全ではないページ)に移動」をクリック
「許可」をクリック
スプシに戻り以下のように情報が転記されていたら成功です!✨✨
次にChatGPT OpenAIの APIを活用し、抽出したテキストを「氏名、会社名、役職」など、必要な項目を自動で分類・整理していきます。
3.ChatGPTで名刺データを自動分類
①OpenAIのAPIを設定
GASエディターを開き、左メニューバーにある【プロジェクトの設定】をクリック
【スクリプトプロパティを追加】を選択
【スクリプトプロパティ】から以下のプロパティを追加してください。
Google Cloud API Keyを発行する手順 【参考記事】https://qiita.com/kurata04/items/a10bdc44cc0d1e62dad3
スクリプトプロパティは以下二つ存在している状態ですね。
VISION_API_KEY: Google Cloud APIキーを入力
OPENAI_API_KEY: OpenAIのAPIキーを入力
①二つ目のスクリプトを作成
二つ目のスクリプトを作成するので、
【+】マークをクリックして【スクリプト】を選択。ぺージが新規追加されるので、適宜ファイル名を書いてください。
今回2つ目のファイル名は例:「OpenAIAPI_Data_Column」
以下のコードをスクリプトへ貼り付け。
// 名刺情報分析スクリプト
// ChatGPTを使用して名刺情報を分析・分類します
function analyzeBusinessCards() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const dataRange = sheet.getRange(`A2:A${sheet.getLastRow()}`);
const cardData = dataRange.getValues();
Logger.log("名刺分析開始");
cardData.forEach((row, index) => {
const cardInfo = row[0];
const currentRow = index + 2;
// 行の処理が必要かチェック
const isProcessed = sheet.getRange(`B${currentRow}:L${currentRow}`)
.getValues()[0]
.some(cell => cell !== "");
if (cardInfo && !isProcessed) {
const analyzedData = analyzeWithChatGPT(sheet, cardInfo);
if (analyzedData) {
updateSpreadsheetRow(sheet, currentRow, analyzedData);
}
}
});
Logger.log("名刺分析完了");
}
function analyzeWithChatGPT(sheet, cardInfo) {
const apiKey = PropertiesService.getScriptProperties().getProperty("OPENAI_API_KEY");
const endpoint = "https://api.openai.com/v1/chat/completions";
// ヘッダーからテンプレートを作成
const headers = sheet.getRange("A1:L1").getValues()[0];
const template = headers.reduce((acc, header) => {
acc[header] = "";
return acc;
}, {});
// プロンプトを修正して、必ずJSONで返すように指示
const promptMessage = {
"role": "system",
"content": "You must respond with valid JSON only, no explanatory text. Format should exactly match the template provided."
};
const userMessage = {
"role": "user",
"content": `Template: ${JSON.stringify(template)}\n\nExtract information from this business card and respond in the exact JSON format of the template: ${cardInfo}`
};
const requestOptions = {
"method": "post",
"headers": {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
"payload": JSON.stringify({
"model": "gpt-4",
"messages": [promptMessage, userMessage],
"max_tokens": 300, // (適宜調整)名刺データの場合、通常これで十分
"temperature": 0.3 // (適宜調整)低め、少ないトークンで、予測可能で安定した出力
})
};
try {
const response = UrlFetchApp.fetch(endpoint, requestOptions);
const json = JSON.parse(response.getContentText());
const content = json.choices[0].message.content.trim();
// 応答が有効なJSONかどうかを確認
try {
return JSON.parse(content);
} catch (parseError) {
Logger.log(`JSON解析エラー: ${content}`);
Logger.log(`エラー詳細: ${parseError.message}`);
return null;
}
} catch (error) {
Logger.log(`ChatGPT APIエラー: ${error.message}`);
return null;
}
}
function updateSpreadsheetRow(sheet, rowIndex, analyzedData) {
if (!analyzedData) return;
const headers = sheet.getRange("A1:L1").getValues()[0];
headers.forEach((header, index) => {
const cell = sheet.getRange(rowIndex, index + 1);
cell.setValue(analyzedData[header] || "");
});
}
function setApiKey() {
const apiKey = "OPENAI_API_KEY";
PropertiesService.getScriptProperties().setProperty("OPENAI_API_KEY", apiKey);
}
【Ctl + S】で保存。
②アクセスを承認
初めてそのスクリプトを実行する場合は権限の確認が必要です。
そのため、『権限を確認』を押します。
【高度】または【詳細】(英語の場合は【(Advanced)】)をクリック
「無題のプロジェクト(安全ではないページ)に移動」をクリック
「許可」をクリック
スプシに戻り以下のように情報が自動分類されていたら成功です!✨✨
まとめ
実装時の注意点:APIキーは必ずスクリプトプロパティで管理
システムの制限事項:
・OCR精度は画像品質に依存
・API使用量が増えると課金が必要になります
(例)
→Google Cloud Vision API=無料枠: 月1000件まで
→ChatGPT API(GPT-4): 入力1,000トークンあたり約$0.03(約4.5円)
例: 1枚の名刺処理で約200トークンを使用する場合
月100枚の名刺: 約$0.60(約90円)※あくまでも目安です
急なトラブルが発生した場合や、Web開発・AI連携についてのご相談があれば、ぜひお気軽にお声掛けください。意見交換も大歓迎です。
最後まで読んでくださり、ありがとうございました🌱少しでも参考になる箇所があればそーっと♡で教えてください✨大変励みになります!