このプロジェクトについて
EDINET(有価証券報告書等)の開示データをもとに、日本株の財務指標・投資判断スコアを日次で算出・公開しているサービスです。
公開サイトはこちら →
https://stocks.happy-life-design.net
前回の記事はこちら →
https://qiita.com/rightcross-ae86/items/21c1fb3c73f462e904c2
今回やったこと
前提: 前回の記事の続き
前回、決算情報の外部参考サイトに元々存在していた「発表日」列を取得する処理を追加し、全社(4,442社)を対象にバックフィルを実行しました。新規280件・更新44,089件を反映し、完了時点では問題なく終わったと考えていました。
発覚: 完了ログを精査したら消えていた更新があった
念のため完了ログを見直したところ、DB書き込みエラーが3件記録されていました。個別に調査した結果、そのうち1件(ある銘柄のBPS表示値が桁違いの異常値になっていたことによるエラー)が原因で、同じ未コミットバッチ内で正常に取得できていた32社分の更新までデータベースのロールバックに巻き添えで消えていたことが分かりました。ログ上は「警告なし=正常取得」だったにもかかわらず、DB側には反映されていない、という食い違いから発覚しました。
原因: 500件ごとのバッチcommit設計そのものの弱点
# Before: 500件ごとにまとめてcommit。1件でも例外が出るとバッチ全体を巻き戻す
for i, row in enumerate(rows):
try:
cur.execute(upsert_sql, row)
except Exception:
conn.rollback() # ← 直前のcommit以降、未コミット分を全て巻き戻す
continue
if i % 500 == 0:
conn.commit()
1件のエラーで、直前のcommitから数えて最大499件分の正常な更新がまとめて巻き戻される仕様でした。今回はたまたま発表日列の全社バックフィルという大規模処理で発覚しましたが、原因自体は以前からある既存コードの構造的な弱点で、通常の週次データ更新でも同様の巻き添えが気づかれないまま起きていた可能性があります。
対応
- 消えた32社の復旧: 対象ticker(8202〜8301)を個別に再スクレイピング。31社は正常に復旧できましたが、残り1社は参照元サイトに決算データページ自体が存在せず(通常株式ではなく出資証券のため決算短信が無い)、恒久的に取得不可と判明しました。これはロールバック問題とは別事由です。
-
エラー原因のBPS異常値対策: 参照元サイト側の表示不備とみられる桁違いの値(本来1,300〜4,700円水準のところ
17,828,720,000.00等)に対し、DB列の許容範囲を超える値は保存せず警告ログを残すガードを追加 - バッチcommit設計の恒久修正: 500件ごとのバッチcommitを1件ごとのcommitに変更
# After: 1件ごとにcommit。エラーが起きても他の正常な行には影響しない
for row in rows:
try:
cur.execute(upsert_sql, row)
conn.commit()
except Exception:
conn.rollback() # ← 巻き戻る範囲はこの1件のみ
continue
検証
修正後、意図的にエラーを含む銘柄と正常な銘柄を同じクロールに混在させて実行しました。エラーが発生した銘柄は内部で失敗を検知しつつ、それ以外の銘柄は正常に更新されることを実データで確認しています。
併せて、同じ調査の過程で別のパイプラインにも同種の設計(バッチcommit)が残っていることが分かったため、今後横展開で見直す予定です。
「ログ上は正常」と「DBに実際に反映されている」は別の話だと痛感した一件でした。気になる点・ご質問があればコメントでお知らせください。