GAS×外部API連携入門|JSONの取得と処理をマスター
Google Apps Script(GAS)のUrlFetchAppを使えば、外部APIからデータを取得して、スプレッドシートに自動記録することができます。
目次
外部API連携とは
なぜGASで外部API連携が必要なのか
GAS単体でも様々なことができますが、外部APIと連携することで可能性が大きく広がります。
| GAS単体 | 外部API連携 |
|---|---|
| スプレッドシート操作 | 天気予報データの取得 |
| メール送信 | 為替レート取得・記録 |
| カレンダー操作 | ChatGPT/AIでテキスト生成 |
| フォーム処理 | Slack/LINE通知 |
外部API連携でできること
- 情報収集の自動化: 天気・株価・為替・ニュースを定期取得
- 業務効率化: ChatGPTで文章生成、翻訳APIで多言語対応
- 通知システム: Slack/LINE/Discordへの自動通知
- データ連携: Notion/Airtable等のサービスと同期
UrlFetchApp.fetch()の基本
UrlFetchAppとは
UrlFetchAppは、GASで外部URLにHTTPリクエストを送るためのサービスです。これを使って外部APIからデータを取得します。
基本構文
// 最もシンプルな形
const response = UrlFetchApp.fetch(url);
// オプション付き
const response = UrlFetchApp.fetch(url, options);
シンプルなGETリクエスト
/**
* 外部APIからデータを取得する基本形
*/
function basicFetch() {
const url = 'https://api.example.com/data';
// APIを呼び出し
const response = UrlFetchApp.fetch(url);
// レスポンスをテキストで取得
const text = response.getContentText();
// JSONとして解析
const json = JSON.parse(text);
Logger.log(json);
}
UrlFetchApp主要メソッド一覧
| メソッド | 説明 | 戻り値 |
|---|---|---|
fetch(url) |
URLにGETリクエスト | HTTPResponse |
fetch(url, options) |
オプション付きリクエスト | HTTPResponse |
fetchAll(requests) |
複数リクエストを並列実行 | HTTPResponse[] |
getRequest(url, options) |
リクエスト情報を取得(デバッグ用) | Object |
HTTPResponseの主要メソッド
| メソッド | 説明 | 戻り値 |
|---|---|---|
getContentText() |
レスポンス本文(テキスト) | String |
getResponseCode() |
HTTPステータスコード | Number |
getHeaders() |
レスポンスヘッダー | Object |
getBlob() |
バイナリデータ取得 | Blob |
JSONデータの取得と解析
JSON.parseの基本
APIからのレスポンスは多くの場合JSON形式です。JSON.parse()でJavaScriptオブジェクトに変換します。
function parseJsonExample() {
// APIレスポンス(文字列)
const jsonText = '{"name": "山田太郎", "age": 30, "city": "東京"}';
// JSONをオブジェクトに変換
const obj = JSON.parse(jsonText);
// プロパティにアクセス
Logger.log(obj.name); // 山田太郎
Logger.log(obj.age); // 30
Logger.log(obj.city); // 東京
}
ネストしたJSONの扱い方
function parseNestedJson() {
const jsonText = `{
"weather": {
"temperature": 25,
"humidity": 60
},
"forecast": [
{"day": "今日", "temp": 25},
{"day": "明日", "temp": 28}
]
}`;
const data = JSON.parse(jsonText);
// ネストしたプロパティへのアクセス
Logger.log(data.weather.temperature); // 25
// 配列へのアクセス
Logger.log(data.forecast[0].day); // 今日
Logger.log(data.forecast[1].temp); // 28
// 配列をループ処理
data.forecast.forEach(item => {
Logger.log(`${item.day}: ${item.temp}°C`);
});
}
実践例①:天気APIから情報取得
Open-Meteo API(無料・登録不要)
Open-Meteoは、無料で登録不要で使える天気APIです。練習に最適です。
/**
* 天気情報を取得してログに出力
* API: Open-Meteo(無料・登録不要)
*/
function getWeatherData() {
// 東京の座標
const latitude = 35.6762;
const longitude = 139.6503;
// APIのURL
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true&timezone=Asia%2FTokyo`;
try {
// APIを呼び出し
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response.getContentText());
// 現在の天気を取得
const weather = data.current_weather;
Logger.log('========== 現在の天気 ==========');
Logger.log(`気温: ${weather.temperature}°C`);
Logger.log(`風速: ${weather.windspeed} km/h`);
Logger.log(`風向: ${weather.winddirection}°`);
Logger.log(`天気コード: ${weather.weathercode}`);
Logger.log(`取得時刻: ${weather.time}`);
return weather;
} catch (error) {
Logger.log('エラー: ' + error.message);
return null;
}
}
天気コードの変換
/**
* WMO天気コードを日本語に変換
*/
function getWeatherDescription(code) {
const weatherCodes = {
0: '快晴',
1: '晴れ',
2: '一部曇り',
3: '曇り',
45: '霧',
48: '着氷性の霧',
51: '弱い霧雨',
53: '霧雨',
55: '強い霧雨',
61: '弱い雨',
63: '雨',
65: '強い雨',
71: '弱い雪',
73: '雪',
75: '強い雪',
95: '雷雨'
};
return weatherCodes[code] || '不明';
}
/**
* 天気情報を取得して日本語で表示
*/
function getWeatherWithDescription() {
const latitude = 35.6762;
const longitude = 139.6503;
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true&timezone=Asia%2FTokyo`;
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response.getContentText());
const weather = data.current_weather;
const description = getWeatherDescription(weather.weathercode);
Logger.log(`東京の天気: ${description}(${weather.temperature}°C)`);
}
実践例②:為替レートAPIから取得
ExchangeRate-API(無料枠あり)
/**
* 為替レートを取得(USD基準)
* API: exchangerate-api.com(無料枠: 月1,500リクエスト)
*/
function getExchangeRate() {
// 無料API(登録不要版)
const url = 'https://api.exchangerate-api.com/v4/latest/USD';
try {
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response.getContentText());
// 日本円のレートを取得
const jpy = data.rates.JPY;
const eur = data.rates.EUR;
const gbp = data.rates.GBP;
Logger.log('========== 為替レート(USD基準)==========');
Logger.log(`1 USD = ${jpy} JPY`);
Logger.log(`1 USD = ${eur} EUR`);
Logger.log(`1 USD = ${gbp} GBP`);
Logger.log(`更新日時: ${data.date}`);
return data.rates;
} catch (error) {
Logger.log('エラー: ' + error.message);
return null;
}
}
/**
* 指定した通貨ペアのレートを取得
* @param {string} from - 元の通貨(例: 'USD')
* @param {string} to - 変換先の通貨(例: 'JPY')
*/
function getRate(from, to) {
const url = `https://api.exchangerate-api.com/v4/latest/${from}`;
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response.getContentText());
return data.rates[to];
}
複数通貨のレートを一括取得
/**
* 主要通貨のレートを一覧表示
*/
function getAllMajorRates() {
const url = 'https://api.exchangerate-api.com/v4/latest/JPY';
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response.getContentText());
const majorCurrencies = ['USD', 'EUR', 'GBP', 'CNY', 'KRW', 'AUD'];
Logger.log('========== 1円あたりの為替レート ==========');
majorCurrencies.forEach(currency => {
const rate = data.rates[currency];
Logger.log(`1 JPY = ${rate.toFixed(6)} ${currency}`);
});
}
実践例③:外部APIの結果をスプレッドシートに記録
天気データを毎日記録
/**
* 天気データをスプレッドシートに記録
* トリガー設定: 毎日午前9時に実行
*/
function recordWeatherToSheet() {
// スプレッドシートを取得(URLのIDを設定)
const SHEET_ID = 'YOUR_SPREADSHEET_ID'; // ← 自分のスプレッドシートIDに置換
const ss = SpreadsheetApp.openById(SHEET_ID);
const sheet = ss.getSheetByName('天気記録') || ss.insertSheet('天気記録');
// ヘッダーがなければ作成
if (sheet.getLastRow() === 0) {
sheet.appendRow(['日時', '気温(°C)', '風速(km/h)', '天気コード', '天気']);
}
// 天気データを取得
const latitude = 35.6762;
const longitude = 139.6503;
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true&timezone=Asia%2FTokyo`;
try {
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response.getContentText());
const weather = data.current_weather;
// 天気コードを日本語に変換
const description = getWeatherDescription(weather.weathercode);
// データを記録
const now = new Date();
sheet.appendRow([
Utilities.formatDate(now, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm'),
weather.temperature,
weather.windspeed,
weather.weathercode,
description
]);
Logger.log('天気データを記録しました');
} catch (error) {
Logger.log('エラー: ' + error.message);
}
}
為替レートを毎日記録
/**
* 為替レートをスプレッドシートに記録
* トリガー設定: 毎日午前10時に実行
*/
function recordExchangeRateToSheet() {
const SHEET_ID = 'YOUR_SPREADSHEET_ID'; // ← 自分のスプレッドシートIDに置換
const ss = SpreadsheetApp.openById(SHEET_ID);
const sheet = ss.getSheetByName('為替記録') || ss.insertSheet('為替記録');
// ヘッダーがなければ作成
if (sheet.getLastRow() === 0) {
sheet.appendRow(['日時', 'USD/JPY', 'EUR/JPY', 'GBP/JPY']);
}
// 為替データを取得(JPY基準に変換)
const url = 'https://api.exchangerate-api.com/v4/latest/USD';
try {
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response.getContentText());
const usdJpy = data.rates.JPY;
const eurJpy = data.rates.JPY / data.rates.EUR;
const gbpJpy = data.rates.JPY / data.rates.GBP;
// データを記録
const now = new Date();
sheet.appendRow([
Utilities.formatDate(now, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm'),
usdJpy.toFixed(2),
eurJpy.toFixed(2),
gbpJpy.toFixed(2)
]);
Logger.log('為替データを記録しました');
} catch (error) {
Logger.log('エラー: ' + error.message);
}
}
トリガー設定(自動実行)
/**
* 毎日午前9時に天気記録を実行するトリガーを設定
*/
function createDailyTrigger() {
// 既存のトリガーを削除
const triggers = ScriptApp.getProjectTriggers();
triggers.forEach(trigger => {
if (trigger.getHandlerFunction() === 'recordWeatherToSheet') {
ScriptApp.deleteTrigger(trigger);
}
});
// 新しいトリガーを作成
ScriptApp.newTrigger('recordWeatherToSheet')
.timeBased()
.atHour(9)
.everyDays(1)
.inTimezone('Asia/Tokyo')
.create();
Logger.log('トリガーを設定しました');
}
HTTPヘッダーの設定方法
POSTリクエストの基本
/**
* POSTリクエストの基本形
*/
function postRequest() {
const url = 'https://api.example.com/data';
const payload = {
name: '山田太郎',
email: 'yamada@example.com'
};
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
};
const response = UrlFetchApp.fetch(url, options);
const result = JSON.parse(response.getContentText());
return result;
}
optionsの主要パラメータ
| パラメータ | 説明 | 例 |
|---|---|---|
method |
HTTPメソッド | ‘get’, ‘post’, ‘put’, ‘delete’ |
contentType |
コンテンツタイプ | ‘application/json’ |
payload |
送信データ | JSON.stringify(obj) |
headers |
HTTPヘッダー | { ‘Authorization’: ‘Bearer xxx’ } |
muteHttpExceptions |
エラーでも例外を投げない | true/false |
followRedirects |
リダイレクトを追跡 | true/false |
validateHttpsCertificates |
SSL証明書を検証 | true/false |
認証ヘッダーの設定
/**
* Bearer認証を使ったAPIリクエスト
*/
function fetchWithAuth() {
const url = 'https://api.example.com/protected';
const apiKey = PropertiesService.getScriptProperties().getProperty('API_KEY');
const options = {
method: 'get',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
muteHttpExceptions: true // エラー時も詳細を取得
};
const response = UrlFetchApp.fetch(url, options);
// ステータスコードを確認
const statusCode = response.getResponseCode();
if (statusCode !== 200) {
Logger.log(`エラー: ${statusCode}`);
Logger.log(response.getContentText());
return null;
}
return JSON.parse(response.getContentText());
}
カスタムヘッダーの例
/**
* 複数のカスタムヘッダーを設定
*/
function fetchWithCustomHeaders() {
const url = 'https://api.example.com/data';
const options = {
method: 'post',
contentType: 'application/json',
headers: {
'Authorization': 'Bearer your-api-key',
'X-Request-ID': Utilities.getUuid(), // ユニークID
'X-Custom-Header': 'custom-value',
'Accept-Language': 'ja-JP'
},
payload: JSON.stringify({ data: 'test' })
};
const response = UrlFetchApp.fetch(url, options);
return JSON.parse(response.getContentText());
}
APIキーの安全な管理方法
スクリプトプロパティを使う(推奨)
/**
* APIキーをスクリプトプロパティに保存
* ※この関数は一度だけ実行する
*/
function setApiKey() {
const props = PropertiesService.getScriptProperties();
props.setProperty('WEATHER_API_KEY', 'your-api-key-here');
props.setProperty('EXCHANGE_API_KEY', 'your-api-key-here');
Logger.log('APIキーを保存しました');
}
/**
* APIキーを使ってリクエスト
*/
function fetchWithApiKey() {
// スクリプトプロパティからAPIキーを取得
const apiKey = PropertiesService.getScriptProperties().getProperty('WEATHER_API_KEY');
if (!apiKey) {
throw new Error('APIキーが設定されていません。setApiKey()を実行してください。');
}
const url = `https://api.example.com/data?apikey=${apiKey}`;
const response = UrlFetchApp.fetch(url);
return JSON.parse(response.getContentText());
}
やってはいけない例
// ❌ コードに直接APIキーを書く(危険!)
const API_KEY = 'sk-xxxxxxxxxxxxxxxx'; // GitHubに公開されると悪用される
// ✅ スクリプトプロパティから取得(安全)
const API_KEY = PropertiesService.getScriptProperties().getProperty('API_KEY');
複数のAPIキーを管理
/**
* 設定クラスでAPIキーを一元管理
*/
const CONFIG = {
getApiKey: function(service) {
const keys = {
openai: 'OPENAI_API_KEY',
weather: 'WEATHER_API_KEY',
exchange: 'EXCHANGE_API_KEY'
};
const propName = keys[service];
if (!propName) {
throw new Error(`未知のサービス: ${service}`);
}
const key = PropertiesService.getScriptProperties().getProperty(propName);
if (!key) {
throw new Error(`${service}のAPIキーが設定されていません`);
}
return key;
}
};
// 使用例
function useMultipleApis() {
const openaiKey = CONFIG.getApiKey('openai');
const weatherKey = CONFIG.getApiKey('weather');
}
エラーハンドリング
基本的なエラーハンドリング
/**
* 堅牢なAPI呼び出し
*/
function robustApiFetch(url, options = {}) {
try {
// muteHttpExceptionsでエラー時も詳細を取得
options.muteHttpExceptions = true;
const response = UrlFetchApp.fetch(url, options);
const statusCode = response.getResponseCode();
const content = response.getContentText();
// ステータスコードをチェック
if (statusCode >= 200 && statusCode < 300) {
// 成功
return {
success: true,
data: JSON.parse(content),
statusCode: statusCode
};
} else {
// APIエラー
return {
success: false,
error: content,
statusCode: statusCode
};
}
} catch (error) {
// ネットワークエラー等
return {
success: false,
error: error.message,
statusCode: null
};
}
}
// 使用例
function testRobustFetch() {
const result = robustApiFetch('https://api.open-meteo.com/v1/forecast?latitude=35.68&longitude=139.65¤t_weather=true');
if (result.success) {
Logger.log('成功: ' + JSON.stringify(result.data));
} else {
Logger.log(`エラー (${result.statusCode}): ${result.error}`);
}
}
リトライ機能付きfetch
/**
* リトライ機能付きAPI呼び出し
* @param {string} url - APIのURL
* @param {Object} options - リクエストオプション
* @param {number} maxRetries - 最大リトライ回数
*/
function fetchWithRetry(url, options = {}, maxRetries = 3) {
options.muteHttpExceptions = true;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = UrlFetchApp.fetch(url, options);
const statusCode = response.getResponseCode();
// 成功
if (statusCode >= 200 && statusCode < 300) {
return JSON.parse(response.getContentText());
}
// 429(レート制限)は待機してリトライ
if (statusCode === 429) {
Logger.log(`レート制限。${attempt}回目のリトライを待機中...`);
Utilities.sleep(2000 * attempt); // 徐々に待機時間を増やす
continue;
}
// その他のエラー
throw new Error(`HTTPエラー: ${statusCode}`);
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
Logger.log(`エラー発生。${attempt}回目のリトライ...`);
Utilities.sleep(1000 * attempt);
}
}
}
エラーコード別の対応
/**
* ステータスコード別のエラー処理
*/
function handleApiError(statusCode, responseText) {
switch (statusCode) {
case 400:
Logger.log('リクエストエラー: パラメータを確認してください');
Logger.log('詳細: ' + responseText);
break;
case 401:
Logger.log('認証エラー: APIキーを確認してください');
break;
case 403:
Logger.log('アクセス禁止: 権限を確認してください');
break;
case 404:
Logger.log('見つかりません: URLを確認してください');
break;
case 429:
Logger.log('リクエスト過多: 時間をおいて再試行してください');
break;
case 500:
case 502:
case 503:
Logger.log('サーバーエラー: API提供側の問題です。しばらく待ってください');
break;
default:
Logger.log(`不明なエラー (${statusCode}): ${responseText}`);
}
}
複数リクエストの効率化
fetchAllで並列リクエスト
/**
* 複数のAPIを並列で呼び出し
*/
function fetchMultipleApis() {
const requests = [
{ url: 'https://api.exchangerate-api.com/v4/latest/USD' },
{ url: 'https://api.open-meteo.com/v1/forecast?latitude=35.68&longitude=139.65¤t_weather=true' },
{ url: 'https://randomuser.me/api/' }
];
// 並列で実行(パフォーマンス向上)
const responses = UrlFetchApp.fetchAll(requests);
responses.forEach((response, index) => {
const data = JSON.parse(response.getContentText());
Logger.log(`API ${index + 1}: ${JSON.stringify(data).substring(0, 100)}...`);
});
}
まとめ
今回学んだこと
| 項目 | ポイント |
|---|---|
| UrlFetchApp | GASで外部APIを呼び出すサービス |
| JSON.parse | APIレスポンスをオブジェクトに変換 |
| エラーハンドリング | muteHttpExceptionsでエラー詳細を取得 |
| APIキー管理 | スクリプトプロパティに保存 |
コピペで使えるテンプレート
/**
* 汎用API呼び出しテンプレート
*/
function apiCallTemplate(url, method = 'get', payload = null) {
const options = {
method: method,
muteHttpExceptions: true
};
// APIキーが必要な場合
const apiKey = PropertiesService.getScriptProperties().getProperty('API_KEY');
if (apiKey) {
options.headers = { 'Authorization': `Bearer ${apiKey}` };
}
// POSTの場合
if (payload) {
options.contentType = 'application/json';
options.payload = JSON.stringify(payload);
}
try {
const response = UrlFetchApp.fetch(url, options);
const statusCode = response.getResponseCode();
if (statusCode >= 200 && statusCode < 300) {
return JSON.parse(response.getContentText());
} else {
Logger.log(`APIエラー (${statusCode})`);
return null;
}
} catch (error) {
Logger.log('エラー: ' + error.message);
return null;
}
}
次のステップ
- ChatGPT APIと連携してAI機能を追加
- Slack/LINE APIで通知システムを構築
- Google APIs(Sheets, Drive, Calendar)との連携
関連記事
- GAS×ChatGPT連携入門 – AI APIとの連携
- GAS×Slack通知の実装方法 – 通知APIの活用
- API活用入門|仕組みから実践まで – APIの基礎知識
コメント