CHAPTER 8 ・ 仕事で使える形にする / Lesson 32

定期実行と通知の考え方

毎朝・毎月、自動で動かす。タスクスケジューラの使い方と、完了や失敗の知らせ方、秘密情報の扱いです。

目安 22分 ・ 自分のパソコンで操作する回

このレッスンが終わったら

「自動で動かすには、スケジューラに登録する。失敗に気づける仕組みと、パスワードをコードに直接書かない工夫が必要」と言える。

STEP 1

こんな場面、ありませんか?

毎月1日の朝に、前の月の売上レポートを作る仕事があります。Lesson 31 で、python report.py --month 2025-09 と打てば動く形になりました。それでも、毎月こうなりがちです。

  • 1日が休み明けで、うっかり忘れる
  • 担当者が休みの日に、誰も動かさない
  • 動かしたつもりが、途中でエラーになっていたことに、数日後に気づく

「決まった日時に、人の手を借りずに動かす」ことが、自動化の入り口です。ただし、自動で動くということは、誰も見ていない場所で動くということでもあります。そのため、次の3つを一緒に整えます。

  1. いつ動かすかスケジューラに登録する
  2. 動いたか分かるかログと、終了コード、通知
  3. 秘密を守れるかパスワードをコードに書かない
自動化は、「動かす」「気づく」「守る」の3点セットです。
STEP 2

手で動かせる形を、先に作る

自動化の前に、手で動かして、毎回同じ結果になることが必要です。スケジューラは、--month を毎月打ってはくれません。そこで、--month を省いたときは、前の月を自動で選ぶようにします。「今の日付」を使う部分は、日付を引数にして、動かして確かめられる形にしておきます。

前の月を求める関数コードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

10月1日の前の月は 2025-09、1月1日の前の月は、年をまたいで 2025-12 になります。これを、report.py の初期値に使います。(date.today() は、実行のたびに値が変わるため、このページでは動かしません)

parser.add_argument("--month", default=last_month(date.today()), help="省略すると、前の月")

次に、スケジューラが動かしやすいように、コマンドをまとめた バッチファイル(Windows で、コマンドを順に実行するファイル)を作ります。メモ帳で、次の内容を run_report.bat という名前で保存します。

@echo off
cd /d C:\Users\tanaka\python-work\sales-report
.venv\Scripts\python.exe report.py >> logs\run.log 2>&1
echo exit code: %errorlevel% >> logs\run.log

作業フォルダの中に、logs フォルダも作っておきます。保存した run_report.bat を、ダブルクリックして、手で動かしてみてください。logs\run.log に、実行の記録が残れば成功です。ここまでできてから、自動にします。

STEP 3

仕組みを読む

run_report.bat を、1行ずつ日本語にしてみる
@echo off実行中の命令を、画面にいちいち表示しない
cd /d C:\Users\tanaka\...\sales-report作業フォルダへ移動する(/d は、ドライブも切り替える印)
.venv\Scripts\python.exe report.py仮想環境のPythonで、report.py を動かす(有効化の操作は不要)
>> logs\run.log 2>&1画面に出る文字(エラーも)を、ログファイルの末尾に書き足す
echo exit code: %errorlevel%直前のコマンドの終了コードを、ログに書く

日本語にすると

作業フォルダへ移り、仮想環境のPythonで report.py を動かして、結果と終了コードを、ログの末尾に書き足してください。

ここで大事なのが、終了コードです。プログラムは終わるとき、OSに数字を1つ伝えます。0 は成功、0以外は失敗という決まりです。スケジューラやバッチファイルは、この数字で、成功か失敗かを知ります。Python では、main() が数を返し、最後に sys.exit() へ渡します。

if __name__ == "__main__":
    sys.exit(main())

ブラウザでは sys.exit() を使わず、main() が返す数字を見てみます。

成功は 0、失敗は 1 を返すコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

Mac や Linux では、cron(決まった時刻にコマンドを動かす仕組み)が、同じ役目をします。1行が、1つの予定です。

0 8 1 * * cd ~/sales-report && .venv/bin/python report.py >> logs/run.log 2>&1
0分(0分)
8時(8時)
1日(1日)
*月(毎月)
*曜日(どれでも)
cd ~/sales-report && ...動かすコマンド

ターミナルで crontab -e と打って開く画面に、この1行を書き足して保存します。Mac では、cron が許可されていない設定もあるので、社内の案内に従ってください。

STEP 4

やってみる:登録と、失敗の知らせ方

① タスクスケジューラに登録する(Windows)

タスクスケジューラは、Windows に入っている、自動実行の係です。次の手順で、run_report.bat を毎月1日の朝に動かします。画面の表記は、Windows のバージョンで少し違います。

  1. スタートメニューで「タスク スケジューラ」と入力して、開く。
  2. 右側の「基本タスクの作成」を押し、名前を「月次売上レポート」にする。
  3. トリガー(いつ動かすか)は「毎月」。開始の日時、月は「すべて」、日は「1」、時刻は朝8時にする。
  4. 操作は「プログラムの開始」。「プログラム/スクリプト」に run_report.bat のフルパス(C:\ から始まる場所)、「開始(オプション)」に作業フォルダを入れる。
  5. 完了を押す。一覧に出たタスクを右クリックして「実行する」を選び、試しに動かす。logs\run.log が増えたか確かめる。
  6. タスクの「プロパティ」で、次を確認する。「全般」…「ユーザーがログオンしているかどうかにかかわらず実行する」にするとき、パソコンのパスワード入力が要ります(会社の方針を確認)。「条件」…スリープ中でも動かすなら、「スリープ解除」にチェック。「設定」…「タスクがすでに実行中の場合」の規則を、「新しいインスタンスを開始しない」にする(二重実行の防止)。

Mac / Linux では、前のSTEPの cron の1行を登録するだけです。

② 失敗に気づく仕組みを作る

誰も見ていない場所で失敗したら、誰かに知らせる必要があります。段階は3つあります。

  • ログ:run.log に、何が起きたかを残す(Lesson 12)。
  • 終了コード:失敗したら 0 以外を返す。タスクスケジューラの「前回の実行結果」でも確認できる。
  • 通知:完了や失敗を、メールなどで知らせる。

メール送信は、SMTPという決まりごとで行い、Python では smtplib を使います。次のコードは、このページでは動かしません。実際に送るには、社内のメールサーバーの情報が必要です。

import logging
import os
import smtplib
from email.message import EmailMessage


def send_mail(subject, body):
    msg = EmailMessage()
    msg["Subject"] = subject
    msg["From"] = "report@example.co.jp"
    msg["To"] = os.environ["REPORT_MAIL_TO"]
    msg.set_content(body)

    with smtplib.SMTP("smtp.example.co.jp", 587) as server:
        server.starttls()
        server.login(os.environ["REPORT_MAIL_USER"], os.environ["REPORT_MAIL_PASSWORD"])
        server.send_message(msg)


def main():
    try:
        run_report()
        send_mail("月次レポート:完了", "正常に終わりました。")
        return 0
    except Exception:
        logging.exception("月次レポートで失敗しました")
        send_mail("月次レポート:失敗", "ログ(logs\\run.log)を確認してください。")
        return 1

ふだんは except Exception を避けますが、ここは最上位の1か所だけです。ログに全文を残し、通知し、終了コード 1 で終わるので、握りつぶしてはいません。

③ パスワードを、コードに書かない

上のコードの os.environ["REPORT_MAIL_PASSWORD"] は、パスワードを環境変数から読んでいます。コードに直接書くと、コードを人に見せたり、共有フォルダに置いたりしたときに、パスワードも一緒に漏れます。

悪い例:コードに書く

server.login("report@example.co.jp", "Sakura2025!")

良い例:環境変数から読む

server.login(os.environ["REPORT_MAIL_USER"], os.environ["REPORT_MAIL_PASSWORD"])
読む側のコードに、パスワードそのものは出てきません。

環境変数は、Windows では、スタートメニューで「環境変数」と入力し、「アカウントのユーザー環境変数を編集」から、名前と値を登録します。登録したあとは、開いていたコマンドプロンプトを一度閉じて、開き直すと読めます。ブラウザでは、設定されていない場合の動きだけ確かめられます。

環境変数が未設定のときコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

os.environ["名前"] は、なければ KeyError で止まります。os.environ.get("名前") は、なければ None(または指定した初期値)を返します。「設定漏れに気づいて止めたい」ときは前者、「なくても動く値」には後者を使います。

環境変数のほかに、.env(イーエヌブイ)という設定ファイルに書き、python-dotenv という道具で読む方法もあります。この .env ファイルは、他の人に渡さない・共有フォルダに置かない・バージョン管理(Git)の対象から外す(.gitignore に .env と書く)のが約束です。

STEP 5

つまずきやすい点を確かめる

自動実行は、手で動かしているときには起きない失敗をします。先回りして確認しましょう。

落とし穴何が起きるか対策
パソコンが寝ている・電源が切れている予定の時刻に動かないスリープ解除、取りこぼしを後で実行する設定。重要なものは、共有のパソコンで動かす
ネットワークドライブ(Z: など)自動実行では、ドライブの割り当てが見えない\\server\share\... の形(UNCパス)で書く
作業フォルダが違うファイルが見つからないcd /d と、タスクの「開始(オプション)」で指定する
パスワードの変更「ログオンしているかにかかわらず」のタスクが、動かなくなる変更したら、タスクの資格情報も更新する
二重実行前の実行が終わらないうちに、次が始まり、同じファイルを壊す「新しいインスタンスを開始しない」、または下のロックファイル
月初が休日元データが、まだそろっていない前日の入力が終わる時刻の後にする。営業日の判定は Lesson 03

二重実行は、コードの側でも防げます。実行の最初に report.lock というファイルを新しく作り(mode="x" は、すでにあればエラーになる、Lesson 29 の書き方)、作れなければ「実行中」と判断して、やめます。

ロックファイルで、二重実行を防ぐコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

2回目の起動は、はじかれました。finally があるので、集計が途中で失敗しても、ロックファイルは削除されます(消し忘れると、次回から動かなくなるため)。

最後に、登録前の確認リストです。自分の仕事で、順に確かめてください。

  • □ 手で動かして、毎回同じ結果になる(--month を省いても動く)
  • □ 試しに動かして、run.log に記録が残る
  • □ 失敗したとき、終了コードが 0 以外になり、通知かログで気づける
  • □ パスワードやAPIキーは、コードに書かず、環境変数から読んでいる
  • □ ネットワークドライブは、UNCパスで書いてある
  • □ 会社のルールで、自動実行・メール送信を許可されている
STEP 6

仕事ならどう使う?

理解チェック自動で動かしたプログラムが失敗したとき、スケジューラやバッチファイルが失敗に気づくための仕組みは?

理解チェックメール送信用のパスワードの扱いとして、適切なのは?