1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python + yfinance で日本株分析:データ欠損・429エラーを「自己修復」して止まらないスクリーナーを作る

1
Last updated at Posted at 2026-02-17

【完結編】10分の壁を突破せよ!全4,400銘柄の財務データ・価格・順位付けを 9分台で完遂する Turbo パイプラインの全貌
をアップしました。合わせて、閲覧いただけると幸いです。

1. はじめに

想定読者

  • Pythonで日本株のスクリーニングツールを自作したい個人開発者
  • 無料データソース(yfinance など)の NoneNaN に悩まされている方
  • 実運用で「429 Too Many Requests」やデータ不整合に直面し、処理が止まってしまう方

この記事のゴール

yfinance は非常に便利ですが、日本株データ(特に中小型株)は欠損が多く、そのままでは実用的なスクリーニングが困難です。
本記事では、データ欠損を自動で補完・算出し、実運用に耐えうるデータ品質を確保するための 「自己修復(Self-Healing)」アーキテクチャ を解説します。


2. 準備:キャッシュによる高速化と負荷軽減

スクリーニングで数千銘柄にアクセスする場合、毎回サーバーにリクエストを送るのは非効率であり、API制限(429エラー)の原因になります。
requests-cache を使い、ローカルにデータをキャッシュすることで、2回目以降の実行を爆速化します。

pip install yfinance tenacity pandas requests-cache

import yfinance as yf
from requests_cache import CachedSession

# キャッシュセッションの作成
# expire_after: キャッシュの有効期限(秒)。日次データなら数時間〜1日でOK
session = CachedSession('yfinance_cache', expire_after=3600 * 12)

# 使い方: Ticker初期化時に session を渡す
# ticker = yf.Ticker("7203.T", session=session)


3. 基礎:安定した取得のためのリトライとジッター

API取得の基本は、エラーを前提とした設計です。特に短時間の大量アクセス時には API レートリミットに抵触しやすいため、tenacity を使い、スパイクを避けるための「ジッター(揺らぎ)」を加えたリトライ処理を実装します。

import random
from tenacity import retry, stop_after_attempt, wait_exponential, wait_random

@retry(
    stop=stop_after_attempt(3),
    # 2〜10秒の指数バックオフに ±1秒のランダムな揺らぎを加える
    wait=wait_exponential(multiplier=1, min=2, max=10) + wait_random(0, 1),
    reraise=True
)
def fetch_info_safe(ticker):
    """yfinance info取得 (ジッター付きリトライ)"""
    try:
        return ticker.info
    except Exception as e:
        print(f"Error fetching info: {e}")
        return {}

[!TIP]
実装上の注意: ジッターを加えることで、複数スレッドからのリクエストが同時に再試行される「サンダリング・ハード(Thundering Herd)」問題を軽減できます。


4. 実装の要:安全な数値変換ユーティリティ

データの型が int, float, str, None と不安定なため、変換時に落ちないための小さなガードを用意しておくのがコツです。

def safe_float(x, default=None):
    """強固な数値変換・型チェック"""
    try:
        if x is None: return default
        if isinstance(x, str):
            x = x.replace(',', '').replace('%', '').strip()
        val = float(x)
        
        # 極端な外れ値(1兆倍など)やNaNをガード
        if val != val or abs(val) > 1e15: return default
        return val
    except (ValueError, TypeError):
        return default


5. 自己修復ロジック1:D/E比からの高速推定 (Fast Estimation)

info['equityRatio'](自己資本比率)が欠損していても、info['debtToEquity'](負債資本倍率)が取れている場合があります。この場合、以下の式で自己資本比率を逆算します。

$$自己資本比率 \approx \frac{1}{1 + \frac{D/E}{100}} \times 100$$

ここで注意が必要なのは、D/E比の単位です。データソースによっては「%表記(例: 150.0)」と「倍率表記(例: 1.5)」が混在することがあります。

def repair_equity_ratio(info):
    """D/E比から自己資本比率を推定"""
    de = safe_float(info.get("debtToEquity"))
    
    if de is not None and de >= 0:
        # 簡易判定: 10以上ならパーセント表記とみなして100で割る
        # (D/Eが10倍=1000%を超える企業は稀なため)
        if de > 10.0:
            de = de / 100.0
            
        return (1 / (1 + de)) * 100
    return None


6. 自己修復ロジック2:財務諸表からの精密修復 (Deep Repair)

PER (trailingPE) が取れない場合、より低速ですが確実な 財務三表(financials / balance_sheet) を直接参照します。

実行コストとトレードオフ

財務三表の取得は info よりも通信量が多く、実行時間が数秒伸びます。

[!IMPORTANT]
運用のコツ: 全銘柄に対して一律で Deep Repair を実行すると、処理時間が膨大になるだけでなく、確実に Rate Limit (429) に抵触します。
if per is None: のように「本当に必要な場合のみ」発動させるガード句が必須です。

実装例

まず、複数候補のキーからデータを安全に抽出するユーティリティを用意します。

import pandas as pd

def get_value_from_df(df, keys, default=None):
    """DataFrameの行(index)から複数の候補キーで値を探索"""
    if df is None or df.empty:
        return default
        
    for k in keys:
        if k in df.index:
            # 最新決算期の値を取得(列の先頭)
            series = df.loc[k]
            val = series.iloc[0] if hasattr(series, 'iloc') else series
            return val if val == val else default # NaNチェック
    return default

これを利用して、PERを精密に修復します。ここで stock.financials が空のDataFrameを返すケースもケアします。

import logging

def repair_per_deep(stock, info):
    per = safe_float(info.get("trailingPE"))
    price = safe_float(info.get("currentPrice"))
    
    # PERが欠損しており、かつ現在値がある場合のみ、重い処理(Deep Repair)を実行
    if per is None and price:
        try:
            # プロパティアクセス時に通信が発生するためtry-catch必須
            inc = stock.financials
            bs = stock.balance_sheet
            
            # DataFrameが空でないかチェック
            if inc is not None and not inc.empty and bs is not None and not bs.empty:
                # フィールド名の揺れ("Net Income" vs "NetIncome"等)を吸収
                net_income = get_value_from_df(inc, ["Net Income", "NetIncome", "Net Income Loss"])
                shares = get_value_from_df(bs, ["Ordinary Shares Number", "Shares Issued", "Share Issued"])
                
                if net_income and shares and shares > 0:
                    eps = net_income / shares
                    if eps > 0:
                        per = price / eps
                        logging.info(f"Deep Repair Success: Calculated PER {per:.2f}")
        except Exception as e:
            logging.warning(f"Deep repair failed for {stock.ticker}: {e}")
            
    return per


7. 修復による効果:Before / After

このロジックにより「判定不能」だった有望銘柄がどのように救済されるかの視覚的イメージです。

項目 修復前(取得不可) 修復後(救済成功) 修復の手段
銘柄A (中小型) PER: None PER: 12.4 FinancialsからEPSを算出
銘柄B (新興) 自己資本比率: None 自己資本比率: 45.2% D/E比から逆算
銘柄C (優待株) 配当利回り: None 利回り: 3.2% dividendRate / price で再計算

8. まとめと法務上の注意点

まとめ

  1. APIは万能ではない: 欠損を前提とした「粘り強い」ロジックこそがツールの価値を決める。
  2. 階層型アプローチ: info (高速) → 推定 (中速) → 財務三表 (低速) の順で呼び出しを最適化する。
  3. キャッシュの活用: requests-cache を挟むだけで、開発効率と安定性が劇的に向上する。

データの取り扱いと法務について

  • 日本株の指定: yfinance で日本株を取得する際は、ティッカーシンボルに .T を付ける必要があります(例: トヨタ自動車 → 7203.T)。
  • 利用規約の遵守: yfinance は Yahoo Finance の非公式APIを利用しています。データの商用利用や再配布については、Yahooの規約および各証券取引所の規定を必ず確認してください。
  • 一次ソースの重要性: 自動投資判断を行う場合、最終的な確認は EDINET や適時開示情報(TDnet)などの一次ソース、あるいは信頼性の高い有料データ提供サービスを併用することを強く推奨します。

おわりに

これで、どれだけスクレイピングしても(ほぼ)止まらない堅牢なスクリーナーの基礎が完成しました。
あとは好みの指標でフィルタリングするだけです。楽しい投資開発ライフを!

注意: 投資は自己責任です。本記事のプログラムは投資助言ではありません。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?