0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「フォームから」が出ない?GAS初心者がInstagram自動保存ツールを作って分かった落とし穴と解決策

Last updated at Posted at 2026-01-17

Gemini_Generated_Image_aia0ivaia0ivaia0.png

はじめに

「Instagramの投稿URLをGoogleフォームに貼ったら、自動的にGoogleドライブへ保存される仕組みが欲しい」 そう思い立ってバイブコーディングで作り始めたました!
Gemini 3 無料枠

この記事では、私が開発中に躓いた「GASのトリガー問題」や「APIレスポンスの構造解析」など、試行錯誤の記録をまとめました。

開発環境

  • Google Apps Script (GAS)
  • Google フォーム
  • RapidAPI (instagram120)
  • Google ドライブ

躓きポイント1:トリガー設定に「フォームから」が出てこない

最初の壁は、フォーム送信時にスクリプトを動かすための設定でした。 Geminiは「イベントのソースを選択:フォームから」を選ぶと言ってるのに、私の画面には「スプレッドシートから」しか出てこない……。

原因と解決

これは、GASの作り方に原因がありました。

  • NG: Googleドライブの「新規」から直接GASファイルを作った(スタンドアロン・スクリプト)。
  • OK: Googleフォームの編集画面にある「︙(三点リーダー)」→「スクリプト エディタ」 から作成する(コンテナバインド・スクリプト)

GASは「どこから開いたか」で持っている権限や設定項目が変わるんですね。この違いを知っているだけで、躓きを防げます。

躓きポイント2:Cannot read properties of undefined (reading 'getResponse')

いざ実行!と思ったら、実行ログにこのエラー。 これは、プログラムが「2番目の回答(保存先など)」を取得しようとしているのに、実際のフォームには「URL」の質問1つしかなかったために発生していました。
実は最初は保存先をフォームで選択できるように2つ目にその質問を追加する予定でコーディングしてました。

// エラーになった箇所のイメージ  
const itemResponses = e.response.getItemResponses();  
const fullUrl = itemResponses[0].getResponse();  
const destination = itemResponses[1].getResponse(); // 2つ目の質問がないため、ここで undefined エラー!

将来的に項目を増やす予定でも、現時点でのフォームの質問数と、コード内のインデックス([0], [1])は厳密に合わせる必要があることを学びました。

躓きポイント3:APIのレスポンスが「配列」だった

今回使用したAPIはinstagram120 というRapidAPIにあるものを使用しました。
https://rapidapi.com/3205/api/instagram120

投稿データを取得すると、画像URLが1つだけ返ってくると思いきや、実際には 「複数枚投稿」に対応したリスト形式でデータが返ってきていました。

ログを確認するとこんな構造でした:
[ { "pictureUrl": "...", ... }, { "pictureUrl": "...", ... } ]

この構造を理解せずに単一のオブジェクトとして扱おうとしても、画像は見つかりません。forEach を使って、リストの中身を一つずつ取り出す処理に書き換えることで、複数枚投稿の全保存に対応できました。

実装のこだわりポイント

1. 日本時間(JST)への確実な変換

APIから返ってくる投稿日時はUTC(世界標準時)ですが、管理しやすいように日本時間に変換しました。 Utilities.formatDate(date, "JST", "yyyyMMdd_HHmm") を使うことで、夜中に投稿されたものも正しく「日本時間」でファイル名に反映されます。

2. 「上書き保存」ロジックの導入

同じURLを2回送ったとき、同じファイルが重複するのを防ぐため、「保存先に同名のファイルがあれば、古い方をゴミ箱へ移動してから新しく保存する」 という処理を追加しました。 これにより、1回目が失敗した時のリトライもスムーズになり、フォルダが散らかるのも防げます。

3. 拡張子 .jpg の明示

Googleドライブ上では拡張子がなくてもプレビューできますが、PCにダウンロードして整理したり、今後別のサービスと連携する際の利便性を考えて、あえて .jpg を付加する仕様にしました。

完成したコード (メインロジック)

/**  
 * フォーム送信時に実行されるメイン関数  
 */  
function onFormSubmit(e) {  
  const props = PropertiesService.getScriptProperties().getProperties();  
  const itemResponses = e.response.getItemResponses();  
  const fullUrl = itemResponses[0].getResponse().trim();  
    
  // 1. ショートコードの抽出  
  const shortcode = fullUrl.match(//(?:p|reels|reel)/([^/?#&]+)/)[1];

  // 2. APIリクエスト  
  const apiUrl = '[https://instagram120.p.rapidapi.com/api/instagram/mediaByShortcode](https://instagram120.p.rapidapi.com/api/instagram/mediaByShortcode)';  
  const options = {  
    "method": "post",  
    "contentType": "application/json",  
    "headers": {  
      "x-rapidapi-key": props.RAPID_API_KEY,  
      "x-rapidapi-host": "instagram120.p.rapidapi.com"  
    },  
    "payload": JSON.stringify({ "shortcode": shortcode })  
  };

  const response = UrlFetchApp.fetch(apiUrl, options);  
  const results = JSON.parse(response.getContentText()); // ここが配列 []

  const folder = DriveApp.getFolderById(props.DRIVE_FOLDER_ID);

  // 3. 取得した全画像をループ処理で保存  
  results.forEach((item, index) => {  
    const imgUrl = item.pictureUrl;  
    if (!imgUrl) return;

    const username = item.meta?.username || "unknown";  
    const date = new Date(item.meta?.takenAt * 1000);  
    const dateString = Utilities.formatDate(date, "JST", "yyyyMMdd_HHmm");  
      
    const fileName = `insta_${username}_${dateString}_${index + 1}.jpg`;  
      
    // 上書きロジック(同名ファイルをゴミ箱へ)  
    const existingFiles = folder.getFilesByName(fileName);  
    while (existingFiles.hasNext()) {  
      existingFiles.next().setTrashed(true);  
    }

    const blob = UrlFetchApp.fetch(imgUrl).getBlob();  
    folder.createFile(blob).setName(fileName);  
    console.log(`保存完了: ${fileName}`);  
  });  
}

振り返りと今後の展望

今回は「画像」に絞りましたが、APIレスポンスを詳しく解析すると動画URL(videoUrl)も含まれているようなので、それも含め今後は...

  • 動画(MP4)保存対応
  • エラー通知機能
  • APIの無料枠を使い切った際や、通信エラー時にLINEやメールで自分に通知する。
  • Googleフォトへの自動同期 or 直保存(Google Driveは画像閲覧しずらいので...)
  • インスタアカウントごとにフォルダを自動生成してそこに保存

辺りを検討しましたが気が向いたらまたやりましょう!
一旦は完成ということで!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?