【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