[2026-01-13 修正] 「次回の実行予定時刻」が表示されるように変更し、また、ファイル名を変更しました (記事内の記述は修正済みです)。
[2026-01-14 追記] 使用例を追加し、使用例の記事 (以下) にリンクしました。
ひとり朝会・夕会システムをつくって AI と朝会・夕会している話 (基本設計と Python での実装例) - Qiita
生成 AI と作業していると、こちらから呼ばなくても動いてほしくなって、定期実行を予約したくなると思います。なので、Python ライブラリ schedule でタスクを定期実行するための便利クラスを scheduled_task.py に書きました。
もちろん schedule をそのまま使用しても便利だと思いますが (使用法の記事は検索すれば色々あります)、この便利クラスを通して定期実行すると以下が実現できます (特に 1., 2. のためにこの便利クラスを書きました)。
-
遅刻を許容できます。
- 例えば、「毎朝 9 時に朝会をやる」としていたのに、PC 起動 (定期実行開始) が 09:15 になってしまうことはあると思います。 → その場合でも「30 分までは遅刻してよい」としておけば起動時に朝会ができます (許容時間は秒で指定します)。
- 実装上は「次の実行時刻の 1 周期前がいつのはずだったか」をみて、現在時刻がそれから何秒後かをみて、遅刻幅が短ければタスクを実行しています。
- 例えば、「毎朝 9 時に朝会をやる」としていたのに、PC 起動 (定期実行開始) が 09:15 になってしまうことはあると思います。 → その場合でも「30 分までは遅刻してよい」としておけば起動時に朝会ができます (許容時間は秒で指定します)。
-
タスクごと、あるいは全タスクを、停止する時間帯 (ダウンタイム) を設定できます。
- 例えばある日は朝会の時間に Web 会議が入ったとき、会議中に PC から朝会開始の音楽がなると顰蹙を買う恐れがあります。→ Web 会議が入ったら停止時間帯を設定して再起動すれば実行を停止できます。土日祝に停止するなどもできます。
- 実装上は、schedule ライブラリは周期ごとに必ず叩きにくるので、便利クラス側でダウンタイム中なら本来の処理に進ませないようにブロックしています。
- とはいえ、あまり便利なインタフェースではありません (「
datetime.datetimeオブジェクトを受け取って、停止したい時間帯ならTrueを返す関数」をセルフサービスで記述いただく必要があります)。
- 例えばある日は朝会の時間に Web 会議が入ったとき、会議中に PC から朝会開始の音楽がなると顰蹙を買う恐れがあります。→ Web 会議が入ったら停止時間帯を設定して再起動すれば実行を停止できます。土日祝に停止するなどもできます。
-
「このタスクは n 回実行したら終わる」が実現できます。
- schedule ライブラリにこの機能はなく、もしやるならクラスを書いて「何回実行したか」を管理する必要があります。私の便利クラスはそれをやっています。
以下ではこの便利クラスの使い方を記します (実装をご理解の上、自己責任でご利用ください)。予めお手元の Python 環境に pip install schedule が必要です。
便利クラスの使い方
便利クラスのファイルを例えば ~/tools/lib/scheduled_task.py に配置します。また、隣に空ファイル __init__.py も作成します。
~/
└─ tools/
├─ lib/
│ ├─ __init__.py # 空ファイルを作成
│ └─ scheduled_task.py # ここ
└─ bin/
└─ run_scheduled_tasks.py
そして、~/tools/bin/run_scheduled_tasks.py に例えば以下のように記述します。
- 以下は動作確認用に、毎分 05 秒に朝会、毎分 25 秒に夕会を開始し、「朝会 (夕会) の時間ですよ」と標準出力するだけです。
- 動作確認用に、2026/01/13 09:49 ~ 09:50 を全タスクのダウンタイム (停止期間) とし、朝会も夕会も実行回数を 3 回とし、15 秒までの遅刻を許容しています。
- 以下では 5 秒おきにタスク実行タイミングか判定していますが、実際の多くの用途では 5 分おきなどでじゅうぶんだと思います (ただし想定時刻から最大 5 分ずれます) (元々 schedule ライブラリは厳密な時刻のタスク実行には対応していません)。
import pathlib
import sys
sys.path.append(pathlib.Path('~/tools/lib').expanduser().as_posix())
from scheduled_task import Task, TaskContainer
import datetime
class Asakai(Task):
task_name = '朝会'
def task(self): # 定期実行内容 (オーバーライド必須)
print('朝会の時間ですよ')
def schedule(self): # 定期実行スケジュール (オーバーライド必須)
# スケジュールの記法は schedule のドキュメントの以下を参照
# https://schedule.readthedocs.io/en/stable/examples.html#run-a-job-every-x-minute
# 実際は self.scheduler.every().day.at('09:15') など
# ここでは動作確認用に毎分 05 秒に朝会が始まるとする
return self.scheduler.every().minute.at(':05')
class Yuukai(Task):
task_name = '夕会'
def task(self):
print('夕会の時間ですよ')
def schedule(self):
# ここでは動作確認用に毎分 25 秒に朝会が始まるとする
return self.scheduler.every().minute.at(':25')
if __name__ == '__main__':
# もしどのタスクも停止したい時間帯があれば設定する
def downtime(self, now: datetime.datetime):
strptime = lambda s: datetime.datetime.strptime(s, '%Y-%m-%d %H:%M')
if strptime('2026-01-13 09:49') <= now < strptime('2026-01-13 09:50'):
return True
return False
TaskContainer.downtime = downtime # ダウンタイムをモンキーパッチする
# タスクを登録します
tasks = [
Asakai(max_count=3, lateness_limit=15), # 朝会 (3 回で終了、15 秒まで遅刻許容)
Yuukai(max_count=3, lateness_limit=15), # 夕会 (3 回で終了、15 秒まで遅刻許容)
]
container = TaskContainer(tasks, period=5) # 5 秒おきに実行判定 (適宜粗くしてよい)
container.run() # 定期実行開始
上記スクリプトの実行結果は以下のようになります。遅刻が許容されており、ダウンタイム中には「朝会 (夕会) の時間ですよ」と出力されないことがわかります。また、朝会と夕会を 3 回ずつ実行したら終了しています。
$ python tools/bin/run_scheduled_tasks.py
[TaskContainer] スケジュール実行を開始します
[Task] [朝会] 実行( 1回目): 2026-01-13 09:47:07 (2 秒遅刻)
朝会の時間ですよ
[Task] [朝会] 次回の実行予定: 2026-01-13 09:48:07
[Task] [夕会] 次回の実行予定: 2026-01-13 09:47:25
[Task] [夕会] 実行( 1回目): 2026-01-13 09:47:27
夕会の時間ですよ
[Task] [夕会] 次回の実行予定: 2026-01-13 09:48:27
[Task] [朝会] 実行( 2回目): 2026-01-13 09:48:07
朝会の時間ですよ
[Task] [朝会] 次回の実行予定: 2026-01-13 09:49:07 (ダウンタイム中)
[Task] [夕会] 実行( 2回目): 2026-01-13 09:48:27
夕会の時間ですよ
[Task] [夕会] 次回の実行予定: 2026-01-13 09:49:27 (ダウンタイム中)
[Task] [朝会] ダウンタイム中: 2026-01-13 09:49:07
[Task] [朝会] 次回の実行予定: 2026-01-13 09:50:07
[Task] [夕会] ダウンタイム中: 2026-01-13 09:49:27
[Task] [夕会] 次回の実行予定: 2026-01-13 09:50:27
[Task] [朝会] 実行( 3回目): 2026-01-13 09:50:07
朝会の時間ですよ
[Task] [朝会] 次回の実行予定はありません
[Task] [夕会] 実行( 3回目): 2026-01-13 09:50:27
夕会の時間ですよ
[Task] [夕会] 次回の実行予定はありません
[TaskContainer] すべてのスケジュール実行タスクが終了しました
タスクスケジューラへの設定方法 (Windows の場合)
Windows の場合、タスクスケジューラで PC 起動時にスクリプトを実行するよう登録しておけば PC 起動の度に自動で定期実行が開始します。以下のように登録できます。
- タスクスケジューラを開き、右側から「タスクの作成」をクリックすると、「タスクの作成」ダイアログが立ち上がります。
- 「全般」タブで名前を入力します (何でもよいです)。
- 「トリガー」タブで「新規」をクリック、「タスクの開始」を「スタートアップ時」にします。スタートアップした瞬間は慌ただしいと思うので遅延時間も 10 分など指定するとよいと思います。
- 「操作」タブで「新規」をクリック、「プログラムの開始」(デフォルト選択) を選択し、以下のようにスクリプトを指定します。パスは適切なものに変更ください。
-
記入例 1. この設定だとタスク終了後は自動でコンソールが閉じます。
- プログラム/スクリプト:
C:\Users\kazusa\AppData\Local\Programs\Python\Python313\python.exe - 引数の追加:
"C:\Users\kazusa\tools\bin\run_scheduled_tasks.py" - 開始:
C:\Users\kazusa\tools\bin
- プログラム/スクリプト:
-
記入例 2. この設定だとタスク終了後もコンソールが残ります。
- プログラム/スクリプト:
C:\Windows\System32\cmd.exe - 引数の追加:
/k python "C:\Users\kazusa\tools\bin\run_scheduled_tasks.py" - 開始:
C:\Users\kazusa\tools\bin
- プログラム/スクリプト:
-
記入例 1. この設定だとタスク終了後は自動でコンソールが閉じます。
使用例
コマンドラインでできることや Python からできることは基本的に何でもできますが、いくつかの例を示します。
使用例1. Obsidian のページを開いて音を鳴らす (Windows の場合)
以下のように記述すれば Obsidian のページを開いて音を鳴らすのを定期実行できます。
import pathlib
import sys
sys.path.append(pathlib.Path('~/tools/lib').expanduser().as_posix())
from scheduled_task import Task, TaskContainer
import subprocess
class TaskTest(Task):
task_name = 'テスト'
def task(self):
cmd_obsidian = [ # Obsidian (保管庫 Mercury の Hoge/ほげ.md) を開きます
"cmd", "/c", "start", "obsidian://vault/Mercury/Hoge/ほげ.md",
]
cmd_alerm = [ # アラームを鳴らします
"powershell.exe", "-Command",
"(New-Object Media.SoundPlayer 'C:\\Windows\\media\\Alarm01.wav').PlaySync()",
]
subprocess.run(cmd_obsidian, check=True)
subprocess.run(cmd_alerm, check=True)
def schedule(self):
return self.scheduler.every().minute.at(':00') # 動作確認用
if __name__ == '__main__':
tasks = [TaskTest(max_count=1)] # 動作確認用に 1 回のみ
task_container = TaskContainer(tasks, period=1)
task_container.run() # 定期実行開始
使用例2. 指定のディレクトリで Claude Code (ワンショット) を呼ぶ
標準モジュール subprocess をインポートして、以下のように実行すれば指定のディレクトリで Claude Code に依頼を出せます。--allowedTools=Write は書き込み許可時に必要なオプションです (「このファイルに追記してください」といった指示のとき必要です)。なお、Claude Code には Python インタフェースもありますが、今のところ以下でじゅうぶんなのでつかっていません。
target_dir = pathlib.Path('~/workspace/cookipedia/').expanduser()
prompt = 'ほげほげしてください。'
subprocess.run(
['claude', '--allowedTools=Write', '-p', prompt],
cwd=target_dir, check=True,
)
使用例3. 毎朝夕にひとり朝会・夕会を実施する
上記のスニペットを組合せれば、「毎朝、日報ファイルをテキスト編集ソフトで開いて音で通知、ユーザが記入し終わったら AI でフィードバックを追記する」といったことができます。具体例は以下の記事を参照ください。
備考
- schedule ライブラリには、スケジューリングにかかわらず即時実行する関数
schedule.Scheduler.run_all()がありますが、私の便利クラスを使う場合は.task()を呼べばじゅうぶんです。呼びたい場合は.run_all()も呼べます。
from scheduled_task import Task, TaskContainer
class Asakai(Task):
task_name = '朝会'
def task(self): # 定期実行内容 (オーバーライド必須)
print('朝会の時間ですよ')
def schedule(self): # 定期実行スケジュール (オーバーライド必須)
return self.scheduler.every().day.at('09:15')
if __name__ == '__main__':
task = Asakai(max_count=1)
if __name__ == '__main__':
# これで即時実行できます
Asakai().task()
# これでも即時実行はできます
task = Asakai()
task.register()
task.scheduler.run_all()