🤖 GAS(Google Apps Script)安全な日次トリガー管理
解説書を読むための解説書が必要ないように可能な限りわかりやすく記事にしたいと思います。
今回のスクリプトは以前作成したスクリプトの修正を行う作業でしたので、Gemini 2.5 Flashをコーディングパートナーに選び、Canvasで作業を行いました。
例えば、作成した関数を指定した時間に実行させる場合には、GASではトリガーというのを使用して、関数を自動実行させます。
こうすることで、人が介在すること無く希望の動作を行ってくれるので、トリガーを使う前提でスクリプトの作成を行う方が良いと私は思っています。
定期的に実行させるにはトリガーの作成を行いまして、スクリプトの左の方にあるストップウォッチへマウスのカーソルを合わせると作成ができます。
標準機能では
- 時間主導型
- 特定の日時:何年何月何日何時何分と指定できる
- 分ベースのタイマー:1分おき、5分おき・・・を選択できる
- 時間ベーのタイマー:1時間おき、2時間おき・・・を選択できる
- 日付ベースのタイマー:午前0時~1時、午前1時~2時・・・を選択できる
- 週ベースのタイマー:曜日を選択して、午前0時~1時、午前1時~2時・・・を選択できる
- 月ベースのタイマー:日付を選択して、午前0時~1時、午前1時~2時・・・を選択できる
という設定が可能になります。
トリガーの標準機能でよければ余計なスクリプトを作成する必要がないのですが、
別のシステムとの組み合わせで、例えば13:10に実行したいという要望がある場合はスクリプトを作成して任意の時間に実行するということが可能になります。
私が以前使っていたスクリプトは、何年何月何日何時何分に指定の関数を実行させるというスクリプトでしたので、毎日トリガーを削除する関数と、トリガーを作成する関数をトリガーを設定して自動実行させていました。
そのスクリプトをGeminiに整形してもらおうと思ったら、今どきのスクリプトにしてくれましたのでこちらの記事を書くことにしました。
スクリプトの説明
このドキュメントは、特定の関数を毎日決まった時間に確実に、かつ重複せずに実行するためのトリガー設定スクリプトの最終版とその設計思想をまとめたものです。
解決した問題
・コードの柔軟性: 実行したい関数名を変更するたびにコードを編集する必要がある問題。
採用されたベストプラクティス(設計思想)
項目 | 説明 | 採用技術 |
---|---|---|
安全な削除 | 新しいトリガーを設定する前に、既存の同名関数に紐づくトリガーだけを選別して削除することで、重複作成を防ぎ、他のトリガーに影響を与えないようにします。 | deleteTriggersForFunction() 関数 |
柔軟な時刻管理 | 実行したい時刻を executionTimes 配列にまとめて定義することで、トリガーの追加・削除・変更を容易にしました。 | const executionTimes = [...] / forEach |
関数名の外部化 | 実行対象の関数名をコードに直接書き込まず、スクリプトプロパティで管理することで、再利用性・メンテナンス性を向上させました。 | PropertiesService.getScriptProperties() |
確実な日次実行 | timeBased().at(triggerTime) を使用することで、当日中に設定した時刻に実行されるトリガーを作成します。 | ScriptApp.newTrigger().timeBased().at(..) |
ファイル名は任意なのですが”トリガー設定”としました。
で、下のコードをぶち込みます(笑)
/**
* =============================================================================
* 【トリガー設定スクリプト】 スケジュールの管理
* =============================================================================
*
* 【目的】
* スクリプトプロパティで指定された関数を、
* 指定した時間に毎日自動で実行するための時間ベースのトリガー(スケジュール)を
* 安全に設定・管理します。
* * 【主な機能】
* - 実行前に、既存の同名関数に紐づくトリガーのみを安全に削除します。
* - executionTimes 配列に記述された時刻に合わせて、新しいトリガーを設定します。
* * 【スクリプトプロパティの設定方法と操作説明】
* 1. GASエディタで「プロジェクトの設定」を開く(歯車のアイコン)
* 2. 「スクリプトプロパティ」セクションまでスクロール
* 3. 「スクリプトプロパティの追加」をクリックし、以下のキーと値を設定
* キー | 値
-------------------------|------------------------------------
TRIGGER_FUNCTION_NAME | 実行したい関数名
* 4. 実行したい時刻を executionTimes 配列に [時, 分] の形式で追加・編集します。
* 5. この setTrigger() 関数を、実行したい時間より前にトリガーで毎日実行させてください。
*/
// スクリプトプロパティから関数名を取得するためのキー
const PROPERTY_KEY = 'TRIGGER_FUNCTION_NAME';
function setTrigger() {
// スクリプトプロパティからスケジュールを設定したい関数名を取得
const functionToTrigger = PropertiesService.getScriptProperties().getProperty(PROPERTY_KEY);
if (!functionToTrigger) {
Logger.log(`エラー: スクリプトプロパティの'${PROPERTY_KEY}'が設定されていません。トリガー設定を中断します。`);
// スクリプトプロパティの設定方法をユーザーに促すメッセージは、必要に応じて追加してください
return;
}
// 1. 既存のトリガーをすべて削除し、重複を防ぎます。(対象関数に紐づくもののみ)
deleteTriggersForFunction(functionToTrigger);
// 2. 実行したい時刻([時, 分])の配列を定義します。
// ここに新しい時刻を自由に追加できます。
const executionTimes = [
[9, 0], // 09:00
[13, 50], // 13:50
[16, 0] // 16:00
// 例: [20, 30] と追加すると 20:30 にも実行されます。
];
// 3. 配列をループしてトリガーを作成します。
executionTimes.forEach(([hour, minute]) => {
// 実行時刻を設定するためのDateオブジェクトを作成
const triggerTime = new Date();
triggerTime.setHours(hour);
triggerTime.setMinutes(minute);
triggerTime.setSeconds(0);
triggerTime.setMilliseconds(0);
// 時間ベースのトリガーを作成し、指定時刻に実行するように設定
ScriptApp.newTrigger(functionToTrigger)
.timeBased()
.at(triggerTime)
.create();
});
Logger.log(`'${functionToTrigger}' の日次トリガーを ${executionTimes.length} 件作成しました。`);
}
/**
* 現在のスクリプトプロジェクトに設定されている、
* 特定の関数に紐づく既存のトリガーのみを削除します。
* @param {string} functionName 削除対象のトリガーが実行する関数名
*/
function deleteTriggersForFunction(functionName) {
const triggers = ScriptApp.getProjectTriggers();
let deletedCount = 0;
triggers.forEach(trigger => {
// ターゲット関数に一致する場合のみ削除
if (trigger.getHandlerFunction() === functionName) {
ScriptApp.deleteTrigger(trigger);
deletedCount++;
}
});
Logger.log(`関数'${functionName}'に紐づく既存のトリガーを ${deletedCount} 件削除しました。`);
}
ぶち込んだらフロッピーディスクマークをクリックしてスクリプトを保存します。
次に、実行したい関数をスクリプトプロパティに保存します。
左の方の歯車マークにマウスカーソルを合わせて”プロジェクトの設定”をクリックします。
下の方にスクロールして”スクリプトプロパティを追加”をクリックします。
スクリプトプロパティが設定されている場合であれば、同じ場所に”スクリプトプロパティを編集”となっていると思いますので、そちらをクリックして出てきた”スクリプトプロパティを追加”をクリックします。
プロパティの欄には”TRIGGER_FUNCTION_NAME”を、
その右の値には実行したい関数名を入力します。
こちらの例で、
function updateInbentoryDataBatch() [
という関数を実行したい場合は、値に
updateInbentoryDataBatch
を入力します。
次に、実行したい関数を実行したい時間を指定します。
// 2. 実行したい時刻([時, 分])の配列を定義します。
const executionTimes = [
[9, 0], // 09:00
[13, 50], // 13:50
[16, 0] // 16:00
// 例: [20, 30] と追加すると 20:30 にも実行されます。
];
こちらが実行したい時間に実行したい関数を実行させる設定値になります。
この場合は、09:00、13:50、16:00に関数を実行するようになります。
例えば、追加で17:20に関数を実行したい場合は、
// 2. 実行したい時刻([時, 分])の配列を定義します。
const executionTimes = [
[9, 0], // 09:00
[13, 50], // 13:50
[16, 0], // 16:00
[17, 20] // 17:20
// 例: [20, 30] と追加すると 20:30 にも実行されます。
];
とします。
ここで、const executionTimes関数の]の後ろに”,”の記載がありますが、設定する最後の行には”,”は入れずに、次の行がある場合は”,”を記載するようにします。
09:00、17:20だけに実行したい場合は
// 2. 実行したい時刻([時, 分])の配列を定義します。
const executionTimes = [
[9, 0], // 09:00
[17, 20] // 17:20
// 例: [20, 30] と追加すると 20:30 にも実行されます。
];
となります。
スクリプトプロパティの設定もできた、希望の時間も設定したら関数を実行させます。
作成したスクリプト”トリガー設定”を選択したうえで、”setTrigger”を選択したうえで”▷実行”をクリックします。
関数からトリガーを作成するので認証画面が出てまいります。
ここで、”権限を確認”をクリックします。
希望のご自身のアカウントを選択します。
日本語になっていない場合は下の方の言語設定で”日本語”を選択します。
承認を行う場合は関数が実行されませんので、もう一度”setTrigger”関数を実行させるとこのようなログが出てトリガーが作成されることになります。
なお、実行させる関数を変更する場合は、スクリプトプロパティの値を修正して、不要なトリガーを削除したうえで、”setTrigger”関数を実行させると、新しく指定した関数でトリガーが作成されます。
ただし、こちらのスクリプトは毎日setTrigger()関数を実行する設計になっています。
そうしない設計も考えられたのですが、setTrigger関数を毎日実行するトリガーを設定することが一番求める動作に近くなりました。
Claudeに聞いても私のアイデア以上の答えは出てこず、Gemini 2.5 Proに聞いても私と同じ認識でしたので、今はそうするのが最良ということだと思います。
という事で、setTrigger()関数をトリガーに設定する方法を紹介します。
// 2. 実行したい時刻([時, 分])の配列を定義します。
const executionTimes = [
[9, 0], // 09:00
[13, 50], // 13:50
[16, 0] // 16:00
// 例: [20, 30] と追加すると 20:30 にも実行されます。
];
という設定でしたので、一番早く実行されるのは09:00ということになります。
これを踏まえてsetTrigger()関数をトリガーに設定してまいります。
GAS左の方のストップウォッチのマークにカーソルを合わせるとトリガーの設定ができるようになりますので、こちらをクリックします。
実行する関数を”SetTrigger”、イベントのソースを選択を”時間主導型”、時間ベースのトリガーのタイプを選択を”日付ベースのタイマー”、時刻を選択を”午前0時~1時”を選択して、右下の保存をクリックします。
するとトリガーが作成されますので、そのトリガーの行にマウスカーソルを合わせると色が変わり、マウスカーソルを右の方に移動させると、”トリガーの編集”をクリックできるようになります。
トリガーの編集をクリックすると設定したトリガーの詳細を確認できます。
内容は設定したとおりですね。
動作手順としては、
スクリプトプロパティに実行させたい関数を登録したうえで、
const executionTimesで指定した時刻に関数を実行する設定を与えて、
”SetTrigger”を実行することで指定した時刻に指定した関数が実行できます。
また、”SetTrigger”関数をconst executionTimesで指定した時刻よりも前の時間に設定することで、
スクリプトプロパティに登録した関数が前日に作成したトリガーを削除して、その日のうちに実行するトリガーを作成してくれるようになります。
作成した”setTrigger”を実行するトリガーと、その実行結果で作成されたトリガーです。
今回はテストとして”compareAPIVersions”を実行するようにスクリプトプロパティの値を設定しました。
テストで作成したトリガーを削除する方法は、削除する関数の右の方に縦3点にマウスのカーソルを合わせると出てくる”トリガーを削除”をクリックします。
トリガーが実行されると、前回の実行の列に”無効”と表示されます。
作成されたトリガーは1回のみ実行可能な設定になっていますので、”setTrigger”を毎日実行させる必要があります。
タイトルの内容につきましては以上となります。
ここからは、今回のバイブコーディングで発生したハルシネーションについて考えていきたいと思います。
ハルシネーション
今回もLLM(AI)とバイブコーディングを行いましたので、随所にハルシネーション(幻覚)が現れてきました。
当初、Gemini 2.5 Flashに初期コードを渡したら今どきのスクリプトに修正してくれましたが、
考えてみたら最初からハルシネーションが発生してきました。
それは、”SetTrigger”を初回に設定することで”SetTrigger”は毎日動作させる必要がないというハルシネーションでした。
Gemini 2.5 Flashでチャットを繰り返しても前に進まなかったので今の私が思う最高のコーディングパートナーのClaudeに聞いてみました。
ですが、Claudeでもハルシネーションが発生しました。
かなり細かくプロンプトエンジニアリングを行ったつもりだったのですが、不足していたのかな?(-_-;)
Claudeの素晴らしいと思うところは、そのハルシネーションに対して私の求める目的と論理的な誤りを伝えることで、Claudeが間違っているということを返答するところにあります。
以下、Claudeへ指摘したことに寄る返答です。
非常に重要な指摘をありがとうございます。
GASの実行時間制限を考慮すると、私の提案は確かに問題があります。ステップバイステップで分析します。
ステップ1: GASの実行時間制限の確認
ステップ2: 実行時間制限の具体的な数値
検索結果から重要な情報が判明:
- コンシューマーアカウントの場合、1日あたり90分、Google Workspaceアカウントの場合、1日あたり6時間となっています
- 1日のうちにトリガー経由でスクリプトが実行できる総時間の上限
ステップ3: 私の提案の問題分析
毎分実行トリガーの場合:
1日 = 1,440分
1回の実行時間を仮に1秒とすると: 1,440秒 = 24分
1回の実行時間を仮に3秒とすると: 4,320秒 = 72分
問題点:
実行時間の浪費: 指定時刻以外の1,437回は無駄な実行
制限超過のリスク: 無料アカウント(90分制限)では危険
非効率: 99.8%が無駄な処理
ステップ4: あなたの提案の優位性分析
毎日1回トリガー作成の場合:
実行回数: 1日1回のみ
実行時間: 数秒程度
年間実行時間: 365回 × 3秒 = 約18分
優位性:
効率性: 必要最小限の実行
制限回避: 90分制限に全く影響しない
確実性: 当日の正確な時刻にトリガー設定
柔軟性: 毎日異なる時刻設定も可能
ステップ5: 結論
あなたが完全に正しいです。
理由:
リソース効率: 毎分実行は1,440倍の無駄
制限回避: 90分制限を気にする必要がない
実用性: 実際のビジネス用途で安全
合理性: 目的達成に最小限のリソース使用
ステップ6: 推奨解決策
最初にお渡しいただいたスクリプトベースで、以下のアプローチが最適です:
毎日午前0時に実行されるトリガーを1回設定
前日のトリガーを削除して重複回避
その関数が当日の指定時刻トリガーを作成
これなら1日の実行時間は数秒で済み、制限を全く気にする必要がありません。
あなたの直感と分析が完全に正しく、私の最初の提案は非効率でした。申し訳ありませんでした。
Claudeは私のアイデア以上の回答を出すことが出来ずに、私のアイデアを完全支持してくれたようです。
GASといえばGeminiだと思うので今度はGemini 2.5 Proに聞いてみました。
こんにちは。Google Apps Script(GAS)でのトリガー設定についてのご質問ですね。
初回に一度 setTrigger() を実行するだけで、毎日指定した時刻(分単位で厳密に)に関数を実行したい、というご要望、よく理解できます。
結論から申し上げますと、GASの仕様上、初回の一度の実行だけで「毎日・分単位で厳密な」トリガーを直接設定することは難しいのが現状です。
しかし、ご提示いただいたコードのロジックを少し応用し、非常にそれに近い、かつ最も確実な方法がありますので、その方法と修正したスクリプトをご提案します。
なぜ初回一度の実行では難しいのか?
ユーザー様が懸念されている通り、GASで毎日の繰り返しトリガーを設定する標準的な方法は everyDays(1) を使いますが、これと組み合わせられる時間指定は atHour() (時間帯を指定)か、nearMinute() (約15分の誤差あり)しかありません。
以前お試しになった nearMinute() がうまく動作しなかったのは、このメソッドが任意の分数を指定できず、さらに実行時間に±15分の誤差が生じるため、厳密な時間管理には向かないからです。
最も確実な解決策:トリガーを毎日更新する
そこで、最も確実なのが**「トリガー設定用の関数(setTrigger)自体を、毎日1回実行する」**というアプローチです。
setTrigger 関数を、毎日深夜(例:0時〜1時)に実行するためのトリガーを 手動で1つだけ 設定します。
毎日深夜に setTrigger が自動実行されます。
setTrigger は、その日に実行すべき古いトリガーをクリーンアップし、executionTimes に基づいて、その日1日分のトリガーを厳密な時刻で(.at() を使って)再作成します。
この方法なら、結果的に毎日ご希望の時刻にスクリプトが実行され、GASの実行時間上限にもほとんど影響を与えません。
この運用を前提として、ご提示いただいたコードをより分かりやすく、安全に動作するように調整しました。
ご提案するスクリプト
以下のファイルに、更新されたスクリプトと、なぜこの方法が推奨されるのかについての詳しい解説をまとめました。
トリガー管理スクリプト
9月28日 21:09
解説 (README)
9月28日 21:07
まとめ
ご提示のコードは、考え方としては非常に正しいものでした。
そのコードを最大限に活かすために、setTrigger 関数自体を毎日実行するトリガーを手動で一つ設定してください。
これにより、GASの機能を最大限に活用し、ご希望通りの正確なスケジュール実行が実現できます。
やはり、AIからの回答を信じること無く、必ずすべてを自身で検証する必要があると改めて感じました。
それにはGitのブランチを作成してコーディングを進める必要があると改めて感じました。