この記事は、以下のような方を想定しています。

  • 東証の上場維持基準・改善期間銘柄を定期的にチェックしている投資家の方
  • 個人開発でJPXなど公式サイトのデータを自動取得したい方
  • PythonでExcelスクレイピング・自動ダウンロードを学びたい方
  • pandasのヘッダー行検出やデータ正規化でハマった経験がある方

個人で運用しているサイトの一つに「上場廃止ウォッチ」というものがあります。 東京証券取引所(東証)が公表する「上場維持基準の改善期間」に該当する銘柄を毎週監視し、 どの銘柄がどの基準(株主数・流通株式比率・売買代金など)を満たせていないのか、 改善期限がいつまでなのかを一覧で確認できるツールです。

構築したサイトは以下の内容となり、Windowsタスクスケジューラを利用して毎日自動更新しています。 このシステムを作る過程で苦労した点、特に「JPXの公式データを安定して取得する」という地味だけれど一番ハマりやすい 部分について記事にしておきます。似たようなことを考えておられる方の参考になれば幸いです。

実際に稼働しているサイトはこちら

この記事で紹介しているシステムは現在も毎日自動更新しています。 興味のある方は実際の画面をご覧ください。

上場廃止ウォッチを見る →

下図のようなサイトになっています。

上場廃止ウォッチの画面

上場廃止銘柄には、改善期間という猶予期間が設けられており、 その期間中は東証のサイトで対象銘柄が一覧公開されています。投資をしていると、保有銘柄や気になっている銘柄が この一覧に載っていないかを定期的に確認したくなるのですが、公式サイトは毎週Excelを更新するだけで、 過去との比較や通知の仕組みは一切用意されていません。手作業でExcelを毎週ダウンロードして開き、 前回のファイルと比較する作業は、想像以上に時間がかかり長続きしませんでした。

作ったもの:上場廃止ウォッチ

システム化による作業効率化の比較

手作業で行っていた頃は、毎週合計で約25分の時間がかかっていましたが、システム化(自動化)したことで、 現在は週わずか5分(最終確認のみ)の作業に短縮されました。 毎日自動で処理は動いていますが、東京証券取引所(東証)が公表するサイクルが一週間毎なので、毎週の作業としていました。

作業工程 システム化前(手作業) システム化後(自動化)
JPXサイトへのアクセス・Excel DL 5分 Pythonで全自動処理
(手作業 0分)
過去Excelデータへの蓄積・転記 5分
前回データとの差分・変化の比較 10分
データ内容の最終的な目視確認 5分 5分(サイトを開いてチェックするだけ)
合計所要時間 約25分 約5分 (80%の時間短縮!)

なぜ自作したのか

改善期間に入った銘柄は、流通株式比率や売買代金などの基準を一定期間内に回復できなければ、最終的に 上場廃止のプロセスに進む可能性があります。逆に言えば、改善期間入りという事実自体が「この銘柄は基準割れリスクを 抱えている」という重要なシグナルであり、保有株の管理という意味でも見逃したくない情報でした。

JPXの公式ページは以下のURLで公開されていますが、表形式のExcelをその場でダウンロードするだけの単純な作りで、 過去ファイルとの比較や通知の機能はありません。

このページから毎週自動でExcelを取得し、過去データと突き合わせて差分を可視化する仕組みを、ChatGPTやClaudeを 活用しながら試行錯誤して組み上げました。一見シンプルな「Excelを毎週ダウンロードするだけ」の処理ですが、 実際に運用してみると、JPX側のページ構造に起因する細かなトラブルにいくつも遭遇しました。

最初の壁:ExcelのURLが毎週変わる

最初に当たった壁は、ダウンロード対象のExcelファイルのURLが固定ではなく、更新のたびに変わるという点でした。 ファイル名には更新日付が含まれており、例えばCompanies_ImprovementPeriod_JP_260624.xlsxのように 末尾が更新日に変わる形式です。これを毎回手で確認してURLを書き換えるのは自動化の意味がないため、 一覧ページのHTMLを取得し、正規表現でリンクを抜き出す処理を実装しました。

実際に使っているURL抽出処理(簡略化)

pattern = r'href="([^"]+Companies_ImprovementPeriod_JP_\d+\.xlsx)"'
match = re.search(pattern, resp.text)
if not match:
    raise RuntimeError("ExcelファイルのURLが見つかりませんでした。"
                       "JPXのページ構造が変わった可能性があります。")

下図のようにダウンロードしたいExcelファイルに日付が含まれています。

上場維持基準の改善期間入り銘柄のダウンロードサイト画面

この方式にしたことで、JPX側が更新するたびに新しいURLを自動で検出できるようになりました。ただし 「リンクの命名規則が変わったら一発で落ちる」という脆さは残るため、URLが取得できなかった場合は 例外を出して処理全体を止め、ログにエラーメッセージを残すようにしています。サイレントに失敗して 古いデータのまま動き続けてしまうのが一番怖いパターンだと考え、あえて止める設計にしました。

さらに、ダウンロードしたファイルが前回と完全に同一かどうかをバイト単位で比較し、変化がなければ 後続の処理(PKL更新やHTML生成)を全てスキップするようにしています。JPXは週の何れか(ほぼ火曜日)に 更新することが多いのですが、どちらの曜日に更新されるかは週によって変動するため、毎日実行する タスクスケジューラを仕込んでおき、変化がない場合は早期リターンで済ませる設計にしました。

ヘッダー行自動検出の実装

URLの問題を解決した後、次にハマったのがExcelのヘッダー行の位置です。JPXが配布するExcelは、 年によって(あるいは更新のタイミングによって)先頭に説明文や空行が挿入されていることがあり、 pandas.read_excel()で固定のskiprowsを指定すると、ある週は正しく読めても 別の週ではヘッダー行がデータ行として読み込まれてしまう、という不安定な状態になっていました。

解決方法:skiprowsを総当たりで試す

skiprowsを0〜3まで順番に試し、「コード」「証券コード」などのキーワードを含む列が 見つかった時点のパターンを採用する、という単純な総当たり方式にしました。

for skip_rows in [0, 1, 2, 3]:
    try:
        df = pd.read_excel(path, skiprows=skip_rows, dtype=str)
        code_col = next(
            (c for c in df.columns if any(
                kw in str(c) for kw in ["コード", "証券コード", "code", "Code"])),
            None
        )
        if code_col:
            break
    except Exception:
        continue
else:
    df = pd.read_excel(path, dtype=str)
    code_col = df.columns[0]

厳密にヘッダー位置を判定するロジックを書くよりも、こうした「ダメなら次を試す」という総当たり方式の方が、 結果的にJPX側の細かな仕様変更にも耐えやすいと感じています。列名についても「コード」「銘柄」「市場」 などのキーワードを含むかどうかで判定する作りにしており、列の順序が入れ替わっても影響を受けないようにしています。

ハマったポイント:コード列のゼロ落ち

もう一つ、実際にデータがおかしくなって気付いたトラブルが、証券コードの先頭ゼロ落ちです。 dtype=strを指定して読み込んでいたつもりでも、Excel側でセルが数値として保存されているケースがあり、 その場合は4桁のコードの先頭ゼロが落ちて3桁の数字として読み込まれてしまうことがありました。 たとえば本来「0768」のはずのコードが「768」になってしまうと、後続のマージ処理や表示処理で 一致しない別の銘柄として扱われてしまいます。

対処:strip + zfill + 数字4桁チェック

df["code"] = df["code"].astype(str).str.strip().str.zfill(4).str[:4]
df = df[df["code"].str.match(r"^\d{4}$")]  # 数字4桁のみ残す

読み込んだ値を一度文字列化してからzfill(4)で先頭ゼロを補完し、最後に正規表現で 「数字4桁のみ」という条件でフィルタする、という二段構えにしました。これにより、数値として読まれたコードも 文字列として読まれたコードも、最終的には同じ4桁文字列に統一できるようになっています。 地味な処理ですが、これを入れる前は週によって数件のレコードが「コード不一致」でデータから消えてしまうという 不具合が起きており、原因が判明するまで地味に時間がかかったポイントでした。

次回の記事では、このあと実際に最も苦労した「差分が正しく取れない」問題と、それを解決するために スナップショットJSON方式からPKLによる累積DB方式へ作り変えた経緯を紹介します。また、運用中に 見つけたFTPアップロード処理の落とし穴についても実例を交えて解説します。

FAQ

上場維持基準の改善期間とは何ですか?

東証の上場維持基準(株主数、流通株式比率、流通株式時価総額、売買代金、時価総額など)を下回った銘柄に対し、 一定期間内に基準を回復するよう求める制度です。期間内に改善できないと上場廃止の対象になります。

JPXの改善期間データはどこで確認できますか?

JPX公式サイトの改善期間該当銘柄ページでExcel形式で公開されています。 ただしファイルのURLは更新ごとに変わるため、ページ内のリンクをスクレイピングして都度取得する必要があります。

pandasでExcelのヘッダー行がずれる問題への対処法は?

skiprowsを0〜3など複数パターンで試し、「コード」等のキーワードを含む列が見つかった時点の パターンを採用するという総当たり方式が実用的です。