🌦️

【GAS自動化】「天気どうだっけ?」を撲滅!無料APIで3日分の予報をカレンダーに登録

に公開

序論:なぜ天気予報をカレンダーに登録したかったのか

日々のスケジュール管理はGoogleカレンダーに頼りきりですが、天気予報を確認するためだけに別のアプリを開くのが、地味に手間でした。そこで、「予定と一緒に天気情報も見られたら、もっとスムーズに動けるのに」と考えたのが、このプロジェクトのきっかけです。

目標は、「毎日午前0時に、向こう3日間の1時間ごとの天気予報を、自動でGoogleカレンダーに予定として登録する」というシステムの構築です。

この自動化を実現するために、Googleサービスと相性が良く、無料で実行できるGoogle Apps Script (GAS) を主なツールとして選びました。

試行錯誤 1:データソースを探す旅

システムを安定させるには、信頼できるデータソースの確保が欠かせません。このプロセスでは、AIアシスタントのGeminiに相談しながら、候補の選定と検証を繰り返しました。

1-1. 国内のAPI(livedoor互換)は断念

まず、手軽に試せる国内のAPIを探しました。APIキー不要の「天気予報 API(livedoor 天気互換)」を検討しましたが、Geminiにデータ構造を確認してもらったところ、提供されるのは日ごとの予報がメインであり、「1時間ごとの詳細な予報」という要件を満たせないことが判明しました。手軽さは魅力的でしたが、要件不一致のため断念。

1-2. スクレイピングは危険と判断し避ける

次に、詳細な1時間予報を得るため、Webサイトからのデータ抽出(スクレイピング)を検討しました。しかし、これはすぐに却下しました。

TOS違反: 多くのサイトで禁止されており、法的リスクやIPブロックのリスクがあるため、システムとして不適切です。たとえば、一時間ごとの天気予報で比較的有名な天気.jpでは利用規約にてスクレイピングが禁止されています。

第8条(禁止事項)
6. 本ウェブサイト及びアプリ内の情報をブラウザ・RSSリーダー以外のものを用いて取得し利用する行為

メンテナンス地獄: サイトのレイアウト変更に常にコードが依存するため、運用が不安定です。

安定運用のため、スクレイピングはきっぱりと選択肢から外しました。

1-3. Open-Meteoに出会う(最終採用)

最終的に、オープンデータソースを利用しているOpen-MeteoのAPIを採用しました。

決め手: 非商用利用は無料で、APIキーも不要。そして要件である1時間ごとの予報を提供しているため、最も適切な選択肢でした。GeminiにAPIリクエストの基本的な構造を尋ねながら、すぐにテストを開始し、データの取得に成功しました。

実装フェーズ:GASとOpen-Meteoで自動更新を実現

データソースを確保した後、GASで以下のロジックを構築しました。

2-1. APIリクエストと3日間のデータ取得

APIリクエストURLに forecast_days=3 と設定することで、今日から3日後までの72時間分のデータを取得しました。このデータには、時刻ごとの気温 (temperature_2m) と天気コード (weather_code) が含まれています。

2-2. 重複を許さない「削除→登録」ロジック

自動実行を毎日行う際、イベントが重複するのを防ぐのが一番重要です。この問題を解決するため、私は以下の「お掃除」ロジックを採用しました。

削除範囲の指定: 実行日(今日)の0:00から3日後の0:00までの期間を正確に設定。

全イベント削除: CalendarApp.getEvents() で取得した既存の天気予報イベントを全て削除。

新規登録: APIから取得した最新の72個の予報データを、1時間ごとの予定としてカレンダーに登録。

  // 2. 既存のイベントを3日間全て削除する(重複防止のため)
  const today = new Date();
  
  // 削除開始日: 今日の0:00:00
  const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
  
  // 削除終了日: 今日から2日後の23:59:59 (合計3日間の範囲)
  const threeDaysEnd = new Date(todayStart);
  threeDaysEnd.setDate(threeDaysEnd.getDate() + 3); // 3日後の0時(削除範囲の終点)

  const existingEvents = targetCalendar.getEvents(todayStart, threeDaysEnd);
  existingEvents.forEach(event => event.deleteEvent());
  Logger.log(`向こう3日間の既存イベント ${existingEvents.length}件を削除しました。`);

このロジックを毎日午前0時に実行するよう、GASの「トリガー」機能で設定しました。

完成

こうして完成したのが以下のコードです。
変数を天気予報を表示したい地点の緯度・経度および天気を表示させる専用のGoogleカレンダーの名前に変更すればすぐに動きます

/**
 * Open-Meteo APIから向こう3日間の1時間ごとの天気予報を取得し、Googleカレンダーに登録する関数
 */
function createWeatherEvents() {

  // ===========================================
  // 【要変更の定数】
  // ===========================================
  const LAT = 35.68;                      // あなたの地域の緯度
  const LON = 139.69;                     // あなたの地域の経度
  const CALENDAR_NAME = "今日の1時間予報"; // 作成したカレンダー名
  // ===========================================
  
  // APIのエンドポイントURLを生成
  // ★修正点:forecast_days=3 に変更し、向こう3日間の予報を取得します。
  const url = 
    `https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&hourly=temperature_2m,weather_code&timezone=Asia%2FTokyo&forecast_days=3`;

  // 1. カレンダーを特定する
  const calendar = CalendarApp.getCalendarsByName(CALENDAR_NAME);
  if (calendar.length === 0) {
    Logger.log("エラー:指定されたカレンダーが見つかりません: " + CALENDAR_NAME);
    return;
  }
  const targetCalendar = calendar[0];

  // 2. 既存のイベントを3日間全て削除する(重複防止のため)
  const today = new Date();
  
  // 削除開始日: 今日の0:00:00
  const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
  
  // 削除終了日: 今日から2日後の23:59:59 (合計3日間の範囲)
  const threeDaysEnd = new Date(todayStart);
  threeDaysEnd.setDate(threeDaysEnd.getDate() + 3); // 3日後の0時(削除範囲の終点)

  const existingEvents = targetCalendar.getEvents(todayStart, threeDaysEnd);
  existingEvents.forEach(event => event.deleteEvent());
  Logger.log(`向こう3日間の既存イベント ${existingEvents.length}件を削除しました。`);

  try {
    // 3. APIからデータを取得し、JSONを解析する
    const response = UrlFetchApp.fetch(url);
    const json = JSON.parse(response.getContentText());
    
    // 4. 1時間ごとのデータを抽出
    const hourlyData = json.hourly;
    const times = hourlyData.time;
    const temperatures = hourlyData.temperature_2m;
    const weatherCodes = hourlyData.weather_code;

    // 5. カレンダーに1時間ごとのイベントとして登録
    const now = new Date();
    
    for (let i = 0; i < times.length; i++) {
      const startTime = new Date(times[i]);
      
      // 現在時刻より過去の予報はスキップ
      if (startTime < now) {
        continue;
      }
      
      const endTime = new Date(startTime.getTime() + (60 * 60 * 1000)); // 終了時刻は1時間後
      
      const temp = temperatures[i].toFixed(1); // 気温
      const wmoCode = weatherCodes[i];        // 天気コード
      const weatherText = convertWmoCode(wmoCode); // 天気コードを日本語に変換

      // イベントのタイトルと詳細を作成
      const title = `予報: ${weatherText} / ${temp}°C`;
      const description = `WMOコード: ${wmoCode}`;

      // カレンダーにイベントを作成
      targetCalendar.createEvent(title, startTime, endTime, {description: description});
      Logger.log(`イベント作成: ${Utilities.formatDate(startTime, targetCalendar.getTimeZone(), "MM/dd HH:mm")} - ${title}`);
    }
    
  } catch (e) {
    Logger.log("エラーが発生しました: " + e.toString());
  }
}

/**
 * Open-MeteoのWMO天気コードを日本語のテキストに変換する関数(簡略版)
 */
function convertWmoCode(code) {
  switch (code) {
    case 0: return "☀️ 快晴";
    case 1:
    case 2: return "🌤️ 晴れときどき曇り";
    case 3: return "☁️ 曇り";
    case 45: 
    case 48: return "🌫️ 霧";
    case 51:
    case 53:
    case 55: return "☔️ 霧雨";
    case 61:
    case 63:
    case 65: return "🌧️ 雨";
    case 71:
    case 73:
    case 75: return "🌨️ 雪";
    case 80:
    case 81:
    case 82: return "⛈️ 強い雨";
    case 95: return "⚡️ 雷雨";
    default: return "その他の天気";
  }
}

運用上の注意点:セキュリティ対策の実施

このスクリプトは、カレンダーの予定を「削除・編集」する広範な権限を必要とします。セキュリティ上のリスクを最小限に抑えるため、以下の対策を実施しました。

1. GAS専用アカウントの分離

普段使いのGoogleアカウントとは別に、GASの実行専用のGoogleアカウント(GAS専用アカウント)を作成しました。

このGAS専用アカウントでスクリプトを作成・実行し、専用カレンダーも作成します。

万が一、スクリプトに不具合や問題が発生しても、普段使用しているメールやドライブといった重要なデータにアクセスされない、安全な環境で運用できています。

2. 初回実行時の「警告」を突破する

GASを初めて実行し、カレンダーへのアクセス権を要求する際、Googleから「このアプリは Google で確認されていません」という警告が表示されます。これは、自身で作成したプログラムであるため表示されるものであり、以下の手順で進める必要があります。

「権限を確認」→ アカウントを選択 → 「詳細」 → **「[プロジェクト名](安全ではないページ)に移動」**を選択し、権限を許可します。

3. 普段使いのアカウントへのカレンダー共有

GAS専用アカウントで作成した天気予報カレンダーを、自分の普段使いのアカウントに共有することで、日々の確認は最も安全な環境で行えるようにしました。

結論:今回の学びと今後の方針

今回の学び
この自動化プロジェクトを通じて、以下の重要な教訓を得ました。

APIの選定基準: 「手軽さ」よりも、**「データの粒度」「利用規約の遵守」「安定性」**を最優先すべきであること。Open-Meteoの採用により、この基準を満たせました。

セキュリティ設計の原則: GASのような強力なツールを使う際は、実行環境(GAS専用アカウント)と個人データ(普段使いのアカウント)を明確に分離することが、リスク管理の基本であること。

今後の展望

このシステムは現在非常に快適に動作していますが、実用性をさらに高めていきます。

通知機能の追加: 特定の時間帯に雨や雪が予報された場合、普段使いのSlackやGmailに自動で通知を飛ばす機能を追加する。

詳細情報の統合: 湿度や風速などの情報もAPIから取得し、カレンダーの予定の詳細情報として追記することで、服選びや洗濯の判断に役立てる。

Discussion