【GAS】スプレッドシートに入力したキーワードで検索してヒットした画像をGoogleドライブに保存する
はじまり




概要
今回は、 Google Apps Script (GAS)を使って、 Google のサービスを自動化していきます。
実装する処理の目的は、アプリのロゴをなるべく自動で集めて楽をすることです。
そこで、 GAS を使用してスプレッドシートに入力されたキーワードに基づいて画像を検索し、見つかった最初の画像を Google ドライブに自動的に保存する処理を作っていきます。 どれぐらい行けそうなものか試してみましょう。
こんな風に入力して、

こんな風に画像が保存されることを期待します。

前準備
スクリプトを書く前に、諸々と準備が必要です。
- Custom Search API を有効にする。
- API キーを入手する。
- カスタム検索エンジン(Custom Search Engine)を設定する。
Custom Search APIを有効にする
まずは、 Google Cloud Platform のコンソール画面で、「Custom Search API」というものを有効にします。
「APIとサービス」>「ライブラリ」と遷移して、検索します。

そして、「Custom Search API」が有効になっていることを確認します。

APIキーを入手する
次に、この Custom Search API を利用するためのAPIキーを入手しましょう。
「APIとサービス」>「認証情報」と遷移した先の画面で、「認証情報を作成」をクリックして、「APIキー」を選択します。

API キーが作成されたら編集します。 デフォルトでは、プロジェクトで有効にしている全ての API にアクセスできるようになっていると思うので、 1 つの API キーで利用できる API を一つに限定させます。
「APIの制限」の部分で、「Custom Search API」だけを選択して保存します。

これで、この API キーは Custom Search API しか利用できないキーになりました。セキュリティセキュリティ。 「API Key」にある値は、後で使います。
カスタム検索エンジン(Custom Search Engine)を設定する
そして、この準備も必要です。
「カスタム検索エンジン」というものを作ることで、スクリプトで実行する検索方法を設定します。
まずは、以下のサイトに飛んで、「使ってみる」を選びます。

そして、カスタム検索エンジンを作ります。
ここで忘れてはならないのが、「検索設定」の部分で「画像検索」を ON にすることです!

ちゃんと「画像検索」が ON になっているカスタム検索エンジンが作成できたら、そのエンジンの「検索エンジンID」をメモります。後で使います。
スプレッドシートの準備
そうしたら、事前準備は終わったので、 GAS を実装していきましょう。
今回は、スプレッドシート上に入力したキーワードで検索するので、スプレッドシート上に入力部を準備します。
こんな感じの入力部分を作ります。今回は 2 列分作ります。

そして、冒頭のようにキーワードを並べていきます。
右側には、保存する画像ファイルの名前を入力します。拡張子は除いた形です。

スクリプトの実装
そして、やっとスクリプトを実装していきます。
処理の概要
まずは、処理の本流になります。
/** * @description Check whether the element is an empty value. * @param {any} element * @return {boolean}*/function checkEmpty(element) { return element !== undefined && element !== 0 && element !== null && element !== "";}
/** * @description Check whether empty element is existing. * @param {any[]} array * @return {boolean}*/function isEmptyExisting(array) { let empties = array.filter(element => !checkEmpty(element)); if(empties.length > 0){ return true; } return false;}
/** * @description Search by keywords specified in the sheet and save into the Google Drive. * @param {boolean} isRecording * @return {boolean}*/function searchToSaveImageToDrive(isRecording=true) { const folderIdToSaveLogoBySearching = FOLDER_ID_TO_SAVE_LOGO_BY_SEARCHING; const sheetName = SHEET_NAME_6TH; const startRow = ROW_INDEX_5TH_TO_START_OUTPUTING_RECORDS; const startColumn = COLUMN_INDEX_5TH_OF_FILE_LIST_TO_SAVE_IMAGE_BY_SEARCH;
console.log(`searchToSaveImageToDrive: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`); let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName); let records = sheet.getRange(startRow, startColumn, sheet.getLastRow() - startRow + 1, 2).getValues(); records = records.filter(values => !isEmptyExisting(values)); console.log(records); console.log(`searchToSaveImageToDrive: cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc`);
let gottenBlobs = records.map(rec => { return searchByKeywordToGetImages(rec[0], rec[1]); }); console.log(gottenBlobs); console.log(`searchToSaveImageToDrive: dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd`);
let saveImageFiles = gottenBlobs.map(blobInfo => { return createImageFilesFromBlobs(blobInfo, folderIdToSaveLogoBySearching); }); saveImageFiles = saveImageFiles.filter(value => checkEmpty(value)); console.log(saveImageFiles); console.log(`searchToSaveImageToDrive: ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg`);
const myPromise = new Promise((resolve, reject) => { recordLog(MY_SCRIPT_NAME, "searchToSaveImageToDrive", isRecording); }); console.log(`searchToSaveImageToDrive: iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii`);
let folderUrl = `https://drive.google.com/drive/folders/${folderIdToSaveLogoBySearching}`; displayHtml("index_html", "Saving Files By Searching Terminated...", `<h2>Done.</h2><p>${saveImageFiles.length} images saved in ${getHref(folderUrl, "this folder")}.</p>`); console.log(`searchToSaveImageToDrive: kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk`);
return true;}処理流れとしては、以下の2部構成です。
- 画像検索の処理
- 画像を取得して Google ドライブに保存する処理
画像検索の処理
まずは、gottenBlobsという配列に画像検索してヒットした画像の Blob オブジェクトを含む情報を格納していきます。
この行の処理で、スプレッドシートからキーワードとファイル名を取得します。
let records = sheet.getRange(startRow, startColumn, sheet.getLastRow() - startRow + 1, 2).getValues();また、checkEmpty()やisEmptyExisting()という関数は、nullや""(空文字)になっている要素を省くために使います。
/** * @description Check whether the element is an empty value. * @param {any} element * @return {boolean}*/function checkEmpty(element) { return element !== undefined && element !== 0 && element !== null && element !== "";}
/** * @description Check whether empty element is existing. * @param {any[]} array * @return {boolean}*/function isEmptyExisting(array) { let empties = array.filter(element => !checkEmpty(element)); if(empties.length > 0){ return true; } return false;}そしたら、この処理で Blob オブジェクトを含んだオブジェクトの配列を取得します。
let gottenBlobs = records.map(rec => { return searchByKeywordToGetImages(rec[0], rec[1]); });まずは、searchByKeywordToGetImages()の処理はこんな感じです。
/** * @description Search by specified keyword to get blob object. * @param {string} keyword * @param {string} gettingBlobName * @return {any{}}*/function searchByKeywordToGetImages(keyword, gettingBlobName){ const apiKey = API_KEY_TO_SEARCH_BY_CUSTOM_SEARCH_API; const searchEngineId = SEARCH_ENGINE_ID_TO_SAVE_IMAGE; let blob, extension, blobFullName; let keywordToSearch = keyword.replaceAll(" ", "+").replaceAll(" ", "+"); keywordToSearch = `${encodeURIComponent(keywordToSearch)}+logo`; let searchUrl = `https://www.googleapis.com/customsearch/v1?q=${keywordToSearch}&cx=${searchEngineId}&searchType=image&key=${apiKey}&num=1`; let response; try{ response = UrlFetchApp.fetch(searchUrl); }catch(error){ return {"blob": null, "name": null}; }
let results = JSON.parse(response.getContentText()); console.log(results); console.log(`searchByKeywordToGetImages: eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`);
if (results.items && results.items.length > 0) { let imageUrl = results.items[0].link; let imageResponse; try{ imageResponse = UrlFetchApp.fetch(imageUrl); }catch(error){ return {"blob": null, "name": null}; } blob = imageResponse.getBlob(); console.log(blob); console.log(`searchByKeywordToGetImages: gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg`);
// 元の画像のURLからファイルの拡張子を取得します extension = imageUrl.substring(imageUrl.lastIndexOf('.')); // 拡張子がある場合、クエリパラメーターまたは他のパスの一部を含まないようにします if (extension.indexOf('?') !== -1) { extension = extension.substring(0, extension.indexOf('?')); } // 拡張子らしき文字列が6文字以上もしくは拡張子が正しく取得できない場合は、デフォルトとして.jpgを使用 if (extension.length >= 6 || extension.indexOf('/') !== -1) { extension = '.jpg'; } blobFullName = `${gettingBlobName}${extension}` console.log(blobFullName); console.log(`searchByKeywordToGetImages: iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii`); } else { console.log('No images found for: ' + keywordToSearch); console.log(`searchByKeywordToGetImages: jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj`); } console.log(`searchByKeywordToGetImages: kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk`);
return {"blob": blob, "name": blobFullName};}画像検索をする流れは以下です。
- 半角スペースと全角スペースを考慮した検索文字列にする。
- その検索文字列に
logoというキーワードも追加して、ロゴが画像検索にヒットしやすくする。 - 検索用 URL が出来たら、
UrlFetchApp.fetch()を実行する。 - 検索結果から、最初にヒットした画像がある URL を取得する。
- 再び、
UrlFetchApp.fetch()を実行して、画像のデータを取得する。 - 取得した画像にファイル名を付ける。
Blobオブジェクトとファイル名を返す。
特に、「取得した画像にファイル名を付ける。」処理ですが、その処理は以下の流れです。
- ここではまず、元々の画像の拡張子を取得します。
- しかし、 URL から取得した拡張子にはクエリパラメータが含まれている可能性があるので、クエリパラメーターを削ります。
- そして、クエリパラメーターが削られたら、
.jpegとか.pngとか.webpといった拡張子が来ることを期待しますが、ファイル名に.が含まれていてちゃんと取得できない可能性があります。 - そこで、拡張子が 6 文字以上だったら、
.jpgで拡張子を決定します。(画像フォーマットだったら大体このくらいの長さでいいんじゃないでしょうか。)
.を考慮しないと、こんな画像ファイルが生まれてきてしまうので、

.の長さを考慮することにします。

これで、画像を保存する下地は出来ました。
画像を取得してGoogleドライブに保存する処理
画像オブジェクトを Google ドライブに保存します。
/** * @description Create image files into the Google Drive. * @param {any{}} blobInfo * @param {string} folderId * @return {DriveApp.File}*/function createImageFilesFromBlobs(blobInfo, folderId=FOLDER_ID_TO_SAVE_LOGO_BY_SEARCHING){ let folder = DriveApp.getFolderById(folderId); console.log(`createImageFilesFromBlobs: eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`);
if(Object.prototype.toString.call(blobInfo["name"]) === "[object Null]"){ return null; }
let fileName = blobInfo["name"]; let file = DriveApp.createFile(blobInfo["blob"].setName(fileName)); file.moveTo(folder); console.log(`createImageFilesFromBlobs: iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii`); console.log('Image saved to Drive with name: ' + fileName);
console.log(`createImageFilesFromBlobs: kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk`);
return file;}ここではもう、渡されたオブジェクトをファイルオブジェクトのプロパティに割り当てているだけになります。
これで、画像が目当ての Google ドライブフォルダの中に保存されました!

処理の本流にあるdisplayHtml()という処理では、何個のファイルが保存されたかを表示しています。
displayHtml("index_html", "Saving Files By Searching Terminated...", `<h2>Done.</h2><p>${saveImageFiles.length} images saved in ${getHref(folderUrl, "this folder")}.</p>`);
じゃあ、どれぐらいロゴが保存できるんだろう?
さて、処理が出来たところで、この画像検索による処理の精度が気になってきます。
Google ドライブで画像を一覧で眺めてみると・・・、

ふむふむ、なるほど・・・

まあ、画像検索のロゴを取得する精度としては、 50 ~ 60 点というところでしょうか。
個人的には、以下の点がまだ至っていないなあと思いました。
- 文字が入っていないロゴが欲しい。
- 少しマイナーなサービスだと厳しいかなあ。
- なんか画像じゃないファイルが混ざってる・・・(これは、画像検索処理で結果を複数取得して、先頭から画像かどうかで処理を分岐させれば回避できそうですね。)
- あと、画像の取得処理が遅い!(非同期にすれば全然速くなりそうです。)
まとめ
これで、スプレッドシートに入力されたキーワードで画像を検索し、ヒットした最初の画像を Google ドライブに自動で保存する機能が一旦完成しました。
本記事は、以下の流れで書かれました。
- Custom Search API の有効化
- API キーの取得
- カスタム検索エンジンの設定
- Google スプレッドシートの準備
- GAS スクリプトの実装(画像検索して画像を取得)
- GAS スクリプトの実装(取得した画像を Google ドライブに保存)
Google のポリシーと利用規約や、 API の使用制限、クォータなどにも気を付けて使ってみましょう。保存される画像の使用範囲や著作権にも注意ですね。(ちなみに、 Custom Search API は、1 日 100 リクエストより多くリクエストすると有料になります。)
おしまい


以上になります!
記事を共有
この記事が役に立ったなら、ぜひ他の人と共有してください!