目次
Google Apps Script(GAS)を使って日々のスプレッドシート業務を自動化していると、社内システムや外部のクラウドサービスから最新のデータを取り込みたい場面が頻繁に出てきます。売上集計、在庫確認、為替レートの取得、あるいはタスク管理ツールの進捗取得など、外部サービスのデータをシートにインポートする際の中核技術となるのが、GAS 外部API GETリクエストの処理です。手作業でのCSVエクスポートや画面転記を無くすための第一歩となりますが、単純にURLを指定してデータを引いてくるだけでも、現場のスクリプトでは特有の詰まりどころがいくつも存在します。
レスポンスが取れているのに後続処理で止まる典型的な原因
外部APIにGETリクエストを投げる際、初心者から中級者まで最も引っかかりやすいのが「通信自体は成功しているのに、データを取り出そうとするとオブジェクトのプロパティが undefined になる、あるいは文字列操作でエラーを吐く」という現象です。ブラウザのコンソールや他のプログラミング言語でのAPI操作に慣れていると、通信ライブラリが戻り値を自動でJavaScriptオブジェクトに変換してくれる感覚に引きずられがちですが、GASの通信機能であるUrlFetchAppはそうした気の利いた自動変換を行いません。
サーバーから返ってくる生データは、あくまでHTTPレスポンスというパケットの塊であり、本文はただの文字列(テキスト)として扱われます。このテキストをプログラムで扱える配列やキー・バリューの辞書構造に展開する手続きを明示的に挟まないと、取得したデータの中身にアクセスすることはできません。さらに、API側がJSON形式で返す仕様であっても、エラー時にはHTMLのエラーページが返ってきたり、ステータスコードが200以外の数値で返ってきてパースに失敗したりといった例外が日常的に起こります。まずは最もプレーンな形で通信を行い、受け取ったテキストをデータ構造として復元する基本的なコードの骨格を見ていきます。
function fetchExternalApiData() {
const url = "https://api.example.com/data";
const response = UrlFetchApp.fetch(url);
const json = JSON.parse(response.getContentText());
Logger.log(json);
}
上記のコードは、外部のAPIにGETリクエストを送信してJSONを取得する最小単位の処理です。UrlFetchAppの役割は外部サイトやWeb APIと通信することに特化しており、fetchメソッドに接続先URLを渡すことでHTTPリクエストが実行されます。その結果としてHTTPResponseオブジェクトが返され、ここからgetContentText()を呼び出すことでレスポンス本文を生の文字列として抽出します。そしてJSON処理として、取得したテキストをJSON.parse()で変換して操作可能なJavaScriptオブジェクトへと落とし込みます。
この記述は非常にシンプルですが、業務で使い続けるスクリプトにするためには、通信の成否やデータの形式についていくつかの防護策を講じる必要があります。単発の実行でたまたま動いたとしても、相手先サーバーのメンテナンスや一時的なネットワーク断でスクリプト全体が赤エラーで止まってしまうと、その後のスプレッドシート書き込みやメール通知など後続の業務フロー全体が巻き添えを食らってしまうからです。
UrlFetchApp.fetchとgetContentTextが持つ通信の挙動
UrlFetchApp.fetch()を呼び出すと、GASは同期的にリクエストを発行し、相手サーバーからの応答が返ってくるまで処理を待機します。ここで意識しておくべき仕様は、デフォルトの設定では相手サーバーがHTTPステータスコードの400番台(クライアントエラー)や500番台(サーバーエラー)を返してきた場合、UrlFetchApp.fetch()そのものがGASの例外(Exception)を投げてスクリプトの実行を強制終了させてしまうという点です。
外部APIの連携では、認証トークンの期限切れで401が返ってきたり、指定したIDが存在せず404が返ってきたりすることは日常茶飯事です。例外で処理が落ちてしまうと、エラーレスポンスの中に含まれている詳細なエラーメッセージ(JSON形式で「なぜ失敗したのか」が書かれていることが多い本文)を取得することすらできなくなります。この挙動を回避し、ステータスコードに応じた分岐処理を行いたい場合は、第二引数にオプションオブジェクトを渡し、muteHttpExceptionsプロパティをtrueに設定するのが実務における定石となります。
また、getContentText()で取得するテキストの文字コードにも注意を払う必要があります。現代の主要なWeb APIはほぼすべてUTF-8でレスポンスを返しますが、歴史の長い社内システムや一部の国産APIではShift_JISで返してくるケースが稀にあります。getContentText()はデフォルトでUTF-8としてバイト列を解釈するため、異なる文字コードのAPIを叩くと文字化けが発生し、その後のJSON.parse()で構文エラーとなってスクリプトが止まります。getContentText(‘Shift_JIS’)のようにエンコーディングを明示できる引数が用意されている点も、通信の安定化を図る上で頭の片隅に置いておくべき知識です。
クエリパラメータの構築と認証情報の渡し方
GETリクエストでは、送信したい条件(取得件数、ページ番号、絞り込みの日付など)をURLの末尾に「?key=value&key2=value2」というクエリパラメータとして付与して送信するのがHTTPの標準的な作法です。GASでこれを組み立てる際、URL文字列を単純にテンプレートリテラルやプラス記号で連結してしまうコードをよく見かけますが、日本語や記号を含むパラメータを扱うとURLエンコードの不備によって通信エラーを引き起こす原因になります。
パラメータの値にスペースやスラッシュ、マルチバイト文字が含まれる可能性がある場合は、必ずencodeURIComponent()を介して安全な文字列に変換しなければなりません。複数のパラメータを動的に構築する場合、パラメータ用のオブジェクトを用意し、キーと値をループさせてURLクエリ文字列に成形するヘルパー処理を作っておくと、パラメータの追加や変更に強い構造になります。
さらに、多くの業務系APIでは認証が必要になります。GETリクエストであっても、URLに直接APIキーをクエリとして埋め込む方式と、HTTPリクエストヘッダーにBearerトークンや独自ヘッダーを設定して送信する方式の2パターンが主流です。セキュリティの観点からはヘッダーに含める方式が推奨されるケースが多く、UrlFetchApp.fetch()のオプションオブジェクトにあるheadersプロパティを使って設定します。スクリプト内にAPIキーを直接ベタ書きするのではなく、PropertiesService(スクリプトプロパティ)に退避させておき、実行時に呼び出す設計にしておくことで、スプレッドシートの共有設定を変更した際の情報漏洩リスクを未然に防ぐことができます。
API側のレートリミットを回避するキャッシュとシート保存の設計
GASで外部API連携を行う現場で最も深刻なトラブルになりやすいのが、落とし穴として挙げられるAPI側の制限(レートリミット)です。外部APIは無制限に叩けるわけではなく、「1分間に60回まで」「1日に1000回まで」といったリクエスト上限が厳密に設定されています。これを超過すると、APIサーバー側から「429 Too Many Requests」というエラーが返され、一定時間アクセスを完全に遮断(ブロック)されてしまいます。
特にスプレッドシート業務でありがちな失敗が、スプレッドシートの各行をループ処理しながら、1行ごとにUrlFetchApp.fetch()を実行してデータを取得・書き込みしていくパターンです。データが数十行程度なら偶然動いてしまいますが、行数が数百行に増えた瞬間にレートリミットに激突し、途中の行でスクリプトが停止してデータが中途半端に更新された壊れたシートが出来上がります。また、GAS自体の「1回の実行時間は最長6分まで」という制限にも引っかかりやすくなります。
頻繁に呼び出すとブロックされるため、データをキャッシュする等の対策が必要です。具体的には次のような多層的な対策を組み込みます。
1つ目は、GASが標準で提供しているCacheServiceの活用です。為替レートやマスタデータのように「1日に数回変われば十分」「数分間は同じデータを使って問題ない」という性質の情報であれば、UrlFetchApp.fetch()で取得した生のJSON文字列をCacheService.getScriptCache()に保存しておきます。2回目以降のリクエストではまずキャッシュを確認し、データが存在すればAPI通信を行わずにキャッシュからJSON.parse()して利用します。これにより、APIサーバーへの通信回数を劇的に減らすことができ、リクエスト制限を回避できるだけでなく、処理時間もミリ秒単位に短縮されます。
2つ目は、API側がバルク取得(一括取得)やページネーションをサポートしているかを確認し、可能な限り「1回のリクエストで複数件のデータをまとめて取得する」アプローチを取ることです。1件ずつ取得するのではなく、パラメータでlimit=100などを指定して一括で引き、GASのメモリ上でオブジェクトを処理した上で、最後にSpreadsheetAppのsetValues()を使ってシートへ一括書き込みを行います。通信回数を削減することは、レートリミット対策であると同時に、GAS特有の実行時間制限から逃れるための必須テクニックです。
3つ目は、どうしてもループ内で連続してAPIを叩かざるを得ない場合、Utilities.sleep()を用いて意図的な待機時間を設けることです。API仕様で「秒間2回まで」と決められているのであれば、ループの末尾にUtilities.sleep(1000)を挟むことで、安全マージンを取りながら通信を継続できます。これらを適切に組み合わせることで、自動実行トリガーが走っても途中で止まることのない、堅牢な外部API連携の業務ツールを維持できるようになります。

