MENU

GAS×外部API連携入門|JSONの取得と処理をマスター

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}&current_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}&current_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}&current_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&current_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&current_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)との連携


関連記事

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次