上場企業の決算データを収集するとき、最初に考えることは「XBRL で取れれば理想だ」ということだ。XBRL は XML ベースの構造化フォーマットで、要素名・単位・スケールが明示されており、人手を介さずに数値を抽出できる。東証が定める標準タクソノミ(tse-ed-t)に準拠した決算短信なら、売上高・営業利益・純利益を数十行のコードで取り出せる。
しかし現実は厳しい。XBRL が存在しない・欠損している・形式が崩れているケースは、銘柄数が増えるほど必ず出てくる。XBRL だけに依存したシステムは思わぬタイミングで止まる。
課題: 古い銘柄・小規模 IR では XBRL が無い
東証上場企業は決算短信 XBRL の提出を義務付けられているが、いくつかの例外と現実的な問題がある。
グロース・スタンダードの小規模企業では整備が遅れている。XBRL ファイルが添付されていても、タクソノミのバージョンが古く、現在のパーサが前提とする要素名が存在しないことがある。tse-ed-t 2013年度版と2020年度版では構造が異なり、無条件に同じ解析ロジックを適用するとサイレントに空値が返ってくる。
過去データ(5年以上前)は PDF しかない。TDnet の一覧には XBRL リンクが存在しても、実際にアクセスすると 404 になるケースがある。データが古いほどこの問題は顕著だ。
XBRL は通期・四半期のメインデータをカバーするが、キャッシュフロー詳細やセグメント注記は PDF にしかない場合がある。決算短信本体は XBRL に収まっても、注記や別添資料は PDF 専用で開示される企業が多い。
アプローチ: XBRL 優先、欠損時は PDF パーサで補完
medallion では3層のフォールバック構造を採用している。
試みる順序:
1. XBRL パーサ(kessan_xbrl.py)
2. PDF パーサ(kessan_pdf.py)
3. テキスト抽出(quarterly_pdf.py — 四半期サマリ表を対象)
各パーサは同一の出力インターフェース(dict[str, float | None])を返す。呼び出し側は「どのパーサで取れたか」を意識せず、統一キー(Revenue・Op_Income など)を参照するだけでよい。フォールバックは fetcher/extractors/ ディレクトリ内の extract_earnings() に閉じており、各パーサの実装は独立している。
def extract_earnings(source: EarningsSource) -> EarningsResult:
for extractor in [xbrl_extractor, pdf_extractor, text_extractor]:
result = extractor.try_extract(source)
if result.has_core_fields():
return result
return EarningsResult.empty(source)
has_core_fields() は売上高・営業利益・純利益の3項目が揃っているかを確認する。これらが欠けたデータは後段の分析で誤りの原因になるため、中途半端に書き込むよりも空扱いにする判断をした。
信頼性のメタデータをデータ側に持たせる
フォールバックで補完されたデータは、精度が異なる。XBRL パーサで取得した数値は単位とスケールが要素属性として明示されているため信頼度が高い。PDF パーサは正規表現ベースの数値抽出で、テーブルの誤認識・改行をまたいだ数値の結合ミスが起きうる。
この差異を後段の分析で扱えるようにするため、スプレッドシートのデータ行には取得元(Source)と信頼スコア(Confidence)を持たせている。
| 列 | 値の例 | 意味 |
|---|---|---|
Source | xbrl / pdf / text / manual | データの取得元 |
Confidence | high / medium / low | 信頼性ランク |
Source=xbrl かつ Confidence=high のデータは自動的に正として扱う。Source=pdf のデータは分析前に目視確認フラグを立てるか、数値の外れ値チェックを挟む運用にしている。このメタデータをデータから分離すると「どこで補完したか」が見えなくなり、後からの品質管理が難しくなる。メタデータはデータと同じ行に持たせるのがシンプルで正しい。
テキストフォールバックの精度トレードオフ
テキスト抽出(3段目)は最もリスクが高い。PDF から pdfplumber でテキストを取り出し、表の数値を正規表現でパースするアプローチは、PDF の生成方式によって成否が大きく変わる。
スキャン PDF は対象外にしている。テキストレイヤーが存在しないスキャン PDF に対して OCR を走らせる選択肢はあるが、精度・コスト・処理時間のバランスを考えて現状は実装していない。Source=none として空扱いにし、手動補完の対象としてフラグを立てる。
テーブル構造が崩れる PDF では数値が隣の列にずれる。特に2段組レイアウトや縦書きが混在する資料で起きやすい。パーサは抽出した数値が「売上高として妥当なスケール(百万円で1億以上)か」を簡易バリデーションする。スケール外の値は抽出ミスとして除外し、Confidence=low でマークする。
テキストフォールバックで取得したデータは、後段の montage(動画制作・分析基盤)側では参照するが、最終判断の根拠には使わない設計にしている。表示用の数値として「おおよそ合っている」精度があれば十分な用途と、定量的な分析に使う精度が必要な用途を、Confidence フィールドで振り分ける。
運用してわかった効果と落とし穴
効果として最も実感するのは、データの連続性が保たれることだ。 XBRL が取れない銘柄があっても、パイプラインが止まらない。Source=pdf のデータで埋まっていても、スプレッドシートの行が空欄になるより「暫定値あり」の状態の方が後続の分析を始めやすい。特に時系列グラフや前年同期比の計算では、1点の欠損が系列全体を壊すことがある。
落とし穴として頻出したのは、サイレント失敗の見落としだ。 PDF パーサが一見成功しているように見えて、売上高が誤った桁で入るケースがあった。「1兆円企業」の売上高が「1,234」のまま入力されているのは、単位が「百万円」ではなく「円」や「千円」で誤認識されたためだ。スプレッドシート上での外れ値チェックを GAS で実装して、誤認識を翌日の確認タスクとして通知する仕組みを追加した。
もうひとつの落とし穴は XBRL と PDF で数値が食い違うケースだ。開示後に訂正が出た場合、TDnet の XBRL が更新されても PDF が古いまま残っていることがある。Source フィールドと取得日時(Fetched_at)の両方を持たせているため、「どちらが新しいか」を判定してより新しい側を優先するロジックが組める。これは事前には思いつかなかった使い方だった。
まとめ
XBRL 優先・PDF 補完・テキスト最終手段の3層フォールバックは、金融データ収集の現実的な解だ。設計のポイントをまとめる。
- フォールバックはインターフェースを統一し、呼び出し側に実装を漏らさない
- 信頼性メタデータ(Source / Confidence)はデータと同じ行に持たせる
- テキスト抽出は後段の用途に応じて使用可否を制御する
- 外れ値バリデーションをパーサ内に入れ、サイレント誤入力を防ぐ
関連記事: 決算短信 XBRL パーサの設計 — IFRS と JGAAP を統一スキーマに収める / launchd × TDnet — 決算速報を毎朝自動追従する金融データ基盤 / Yakumo の AI 駆動開発のリアル