前回(第1回)でJPXの信用残データをPythonで取得・PKLに蓄積する仕組みを作りました。今回はそれを毎日自動で動かす仕組みを整えます。Windowsのタスクスケジューラ+batファイルを使った自動実行で、何度かハマったポイントを記録しておきます。

batファイルで自動実行する基本構成

Pythonスクリプト(jpx_margin_collector.py)を毎日定刻に実行するために、batファイルを経由させてタスクスケジューラに登録しました。batファイルを挟む理由は、ログ出力とエラーハンドリングをバッチ側で管理したかったからです。

最初に作ったbatファイルはこのようなものでした。

@echo off
setlocal
cd /d %~dp0
set LOGFILE=%~dp0jpx_margin_collector_bat.log

echo [%DATE% %TIME%] ===== pipeline start ===== >> "%LOGFILE%"

where py >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
    echo [%DATE% %TIME%] ERROR: Python not found >> "%LOGFILE%"
    exit /b 1
)

py "%~dp0jpx_margin_collector.py" >> "%LOGFILE%" 2>&1

if %ERRORLEVEL% EQU 0 (
    echo [%DATE% %TIME%] SUCCESS >> "%LOGFILE%"
) else (
    echo [%DATE% %TIME%] ERROR code=%ERRORLEVEL% >> "%LOGFILE%"
)

exit /b %ERRORLEVEL%

ポイントは cd /d %~dp0 でbatファイル自身のディレクトリに移動していること、%~dp0 でパスを絶対指定していること、標準出力・エラー出力を両方ログに書き出していることです。

タスクスケジューラの落とし穴:作業ディレクトリ問題

batをダブルクリックで実行すると正常に動くのに、タスクスケジューラから実行するとデータが蓄積されないという問題が発生しました。ログを確認すると、こんな状態になっていました。

[2026/04/20 17:40:00] ===== pipeline start =====
JPXサイトからURLを取得中...
URL: https://www.jpx.co.jp/.../mtdailyk2026041700.xls
ダウンロード完了: jpx_margin_data\xls\mtdailyk2026041700.xls
データ公表日: 2026-04-17
取得件数: 268件
CSV保存: jpx_margin_data\csv\margin_20260417.csv
PKL保存: jpx_margin_data\margin_history.pkl (268行)
完了! 合計履歴: 268行

「既存履歴:」の行がありません。これは load_history() がPKLを見つけられなかったことを意味します。

原因は作業ディレクトリ(カレントディレクトリ)の違いです。タスクスケジューラから実行すると作業ディレクトリが C:\Windows\System32 などになるため、スクリプト内の相対パスでPKLを探すと別の場所を参照してしまいます。

根本的な解決策

スクリプト側でパスを絶対パスにします。__file__ を基準にすることで、どこから実行されても同じ場所を参照できます。

from pathlib import Path

# 修正前(相対パス)
DATA_DIR = Path("jpx_margin_data")

# 修正後(スクリプト自身の場所を基準)
DATA_DIR = Path(__file__).parent / "jpx_margin_data"

batファイル側で cd /d %~dp0 を書いていても、Pythonスクリプト内で相対パスを使うと同じ問題が起きます。スクリプト側を修正するのが根本解決です。

ERRORLEVELが取れない問題と正しいbatの書き方

最初のbatでは goto :check_result を使ってエラーチェックを分岐させていました。しかしこれだと ERRORLEVEL がリセットされてしまい、Pythonがエラーで終了してもSUCCESSとログに書かれてしまうことがありました。

REM NG: gotoを挟むとERRORLEVELがリセットされる場合がある
py "%BASEDIR%jpx_margin_collector.py" >> "%LOGFILE%" 2>&1
goto :check_result

:check_result
if %ERRORLEVEL% EQU 0 ( ... )

goto は不要です。Pythonの実行直後に if %ERRORLEVEL% で判定するだけでOKです。また、pause や「完了しました」のメッセージはタスクスケジューラ実行時には誰も見ないので、ログだけに絞るのがシンプルです。

@echo off
setlocal
cd /d %~dp0

set LOGFILE=%~dp0jpx_margin_collector_bat.log

echo [%DATE% %TIME%] ===== pipeline start ===== >> "%LOGFILE%"

where py >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
    echo [%DATE% %TIME%] ERROR: Python not found >> "%LOGFILE%"
    exit /b 1
)

py "%~dp0jpx_margin_collector.py" >> "%LOGFILE%" 2>&1

if %ERRORLEVEL% EQU 0 (
    echo [%DATE% %TIME%] SUCCESS >> "%LOGFILE%"
) else (
    echo [%DATE% %TIME%] ERROR code=%ERRORLEVEL% >> "%LOGFILE%"
)

exit /b %ERRORLEVEL%

ログで原因を特定する

自動実行が正常に動いているかは、ログファイルを確認するのが一番確実です。正常なログはこのようになります。

[2026/04/20 17:40:00] ===== pipeline start =====
JPXサイトからURLを取得中...
URL: https://www.jpx.co.jp/.../mtdailyk2026041700.xls
ダウンロード完了: jpx_margin_data\xls\mtdailyk2026041700.xls
データ公表日: 2026-04-17
取得件数: 268件
CSV保存: jpx_margin_data\csv\margin_20260417.csv
既存履歴: 273行 / 日付: [2026-04-16]
PKL保存: jpx_margin_data\margin_history.pkl (541行)
完了! 合計履歴: 541行
  PKL: jpx_margin_data\margin_history.pkl
  CSV: jpx_margin_data\csv\margin_20260417.csv
[2026/04/20 17:40:05] SUCCESS

「既存履歴: 273行」という行があれば、前日のデータを正しく読み込めています。この行がなければ作業ディレクトリの問題です。

PKL→CSV変換を自動化に組み込む

HTMLダッシュボード(後の記事で解説)はブラウザで動くため、PKLを直接読めません。CSVに変換する必要があります。最初は別スクリプト(pkl_to_csv.py)として切り出していましたが、毎日の実行に自動で組み込む方が管理が楽なので、jpx_margin_collector.py の末尾に追記しました。

# collect() 関数の最後に追加
df_combined.to_csv(
    DATA_DIR / "history.csv",
    index=False,
    encoding="utf-8-sig"
)
print(f"  CSV(全履歴): {DATA_DIR / 'history.csv'}")

これで毎日の自動実行時に PKL と CSV の両方が最新状態に保たれます。

なぜ utf-8-sig なのか

Windows の Excel でCSVを開いたときに文字化けしないよう、BOM付きUTF-8(utf-8-sig)で出力しています。Pythonやブラウザで読み込む場合は通常の utf-8 でも問題ありません。

なお、最初に「既存のPKLからCSVを変換したい」というケースでは、コマンドプロンプトで以下のように実行できます。Windowsの python3 -c はセミコロン区切りで複数行を渡すと引用符の扱いでエラーになりやすいため、小さなスクリプトファイルに書いて実行する方が確実です。

# pkl_to_csv.py
import pickle
import pandas as pd

with open("jpx_margin_data/margin_history.pkl", "rb") as f:
    df = pickle.load(f)

df.to_csv(
    "jpx_margin_data/history.csv",
    index=False,
    encoding="utf-8-sig"
)
print(f"{len(df)}行を出力しました")
cd /d E:\temp\stocksoftware\jpx_margin_data
py pkl_to_csv.py

2つの「更新日」を区別する設計

ダッシュボードのヘッダに「更新日」を表示しているのですが、実は2種類の日付を区別することにしました。

表示ラベル内容取得元
更新: システム最終処理日(batが動いた日) JavaScriptの new Date()(ページロード時の当日)
データ最新: CSVに含まれる最新のデータ公表日 CSVの「データ公表日」列の最大値

この2つを区別することで、「システムは動いているが、JPXがまだデータを公開していない」「週末で更新がない」といった状況を判断しやすくなります。

// データ最新取得日 = CSVの最新データ公表日
document.getElementById("data-date").textContent =
  dates[dates.length - 1] || "-";

// 更新日 = システム最終処理日(ページを開いた日 = 今日)
const today = new Date();
const yy  = today.getFullYear();
const mm  = String(today.getMonth() + 1).padStart(2, '0');
const dd  = String(today.getDate()).padStart(2, '0');
document.getElementById("update-time").textContent =
  `${yy}-${mm}-${dd}`;

完成した自動実行フロー全体像

ここまでの修正を加えた後の自動実行フローをまとめます。

毎日の自動実行フロー

  1. タスクスケジューラが指定時刻(例:17:40)にbatファイルを起動
  2. batが cd /d %~dp0 でbat自身のディレクトリに移動
  3. batが py jpx_margin_collector.py を実行、出力をログファイルに追記
  4. スクリプトがJPXページをスクレイピングしてXLSのURLを取得
  5. XLSをダウンロード・パース、PKLに日次蓄積
  6. PKLをCSVに変換(history.csv)して同フォルダに保存
  7. batがERRORLEVELを確認してSUCCESS/ERRORをログに記録

ファイル配置の最終形

stocksoftware/
└── jpx_margin_data/
    ├── jpx_margin_collector.py   # メインスクリプト
    ├── run_jpx_margin_collector.bat  # 自動実行bat
    ├── jpx_margin_collector_bat.log  # 実行ログ
    ├── margin_history.pkl        # 全履歴PKL
    ├── history.csv               # HTMLビューア用CSV
    ├── margin_viewer.html        # ダッシュボードHTML
    ├── xls/                      # ダウンロードしたXLS
    └── csv/                      # 日付別CSV

第3回では、蓄積したデータを使って需給シグナルを計算するロジックと、HTMLダッシュボードの構築について解説します。

第3回:需給シグナル計算とHTMLダッシュボードの構築

実際のタスクスケジューラ設定は下図のようなイメージです。

信用残ウォッチのタスクスケジューラ設定画面

FAQ

タスクスケジューラでは動かないのに、batファイルをダブルクリックすると正常に動くのはなぜですか?

原因の多くは作業ディレクトリ(カレントディレクトリ)の違いです。タスクスケジューラから起動すると C:\Windows\System32 が基準になる場合があり、相対パスで指定したファイルを見つけられません。bat側で cd /d %~dp0 を実行し、Python側でも Path(__file__).parent を基準に絶対パスを組み立てることで、実行場所に依存しない構成にできます。

batファイルでPythonのエラーを正しく検知するにはどうすればよいですか?

Pythonの実行直後に ERRORLEVEL を確認するのが基本です。途中で goto や別のコマンドを実行すると終了コードが変わる場合があるため、py script.py の直後に if %ERRORLEVEL% EQU 0 で判定し、ログへSUCCESSまたはERRORを書き出す方法がおすすめです。

PKLファイルだけ保存すればCSVは不要ではありませんか?

Pythonだけで利用するならPKLでも十分ですが、ブラウザ上のHTMLやJavaScriptからはPKLを直接読み込めません。そのためWeb画面で利用する場合はCSVへの変換が必要になります。本記事では毎日の収集処理の最後にCSVも自動生成し、Pythonとブラウザの両方で同じデータを利用できるようにしています。

CSVを utf-8-sig で保存している理由は何ですか?

Windows版ExcelでCSVを直接開くと、通常のUTF-8では日本語が文字化けする場合があります。utf-8-sig(BOM付きUTF-8)で保存しておくと、Excelでも文字化けせず開けます。一方、Pythonやブラウザから利用するだけであれば通常のUTF-8でも問題ありません。

ログファイルにはどのような内容を残すべきですか?

少なくとも実行開始時刻、ダウンロードしたファイル名、取得件数、保存先、Pythonの標準出力・標準エラー出力、終了コード(SUCCESS/ERROR)は記録しておくことをおすすめします。タスクスケジューラでは画面にエラーが表示されないため、ログを充実させておくと原因の切り分けが非常に容易になります。