スポンサーリンク
FastAPIPython

〜株価分析アプリ開発の第3回:日経225の銘柄一覧を取得するAPIをFastAPIで実装する

FastAPI
この記事は約9分で読めます。

はじめに

このシリーズでは、FastAPIを使って実用的なAPIを構築する手順をステップ・バイ・ステップで紹介しています。 前回までに、FastAPIの基本構成やルーティング、テスト環境の整備を行いました。

第3回となる今回は、日経平均株価(Nikkei 225)の構成銘柄を一覧で取得するAPIを作成します。 実際のWebサイトからデータを取得し、JSON形式で提供するAPIを構築することで、スクレイピング・データ整形・API化の一連の流れがわかります。環境構築がまだの場合は、以下の記事に環境構築手順を紹介しています。

今回の目的

  • 日経平均株価の構成銘柄(Nikkei 225)をWebサイトから取得する
  • BeautifulSoupを使ってHTMLを解析し、銘柄情報を抽出する
  • FastAPIで /api/v1/nikkei225 エンドポイントを作成し、JSONで返す
  • SSL証明書エラーやBot対策など、実際のスクレイピングで発生する課題に対応する

対応した内容と解説

【1. スクレイピング対象の選定と課題】 対象URL:https://indexes.nikkei.co.jp/nkave/index/component?idx=nk225 JavaScriptで描画されるため、通常の requests では <table> が取得できない

【2. SSL証明書エラーの回避】 社内プロキシや自己署名証明書による CERTIFICATE_VERIFY_FAILED を回避するため、以下のように対応。

response = requests.get(url, headers=headers, verify=False)

さらに警告を非表示にするために以下を追加:

import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

【3. Bot対策の回避】
User-Agent を指定して、HTMLが正しく取得できるように対応。

headers = {
    'User-Agent': 'Mozilla/5.0'
}

【4. HTML構造の解析とデータ抽出】

  • div.idx-index-components を業種ごとのセクションとしてループ
  • h3.idx-section-subheading で業種名を取得
  • table > tbody > tr から銘柄コード・銘柄名・社名を抽出

【5. FastAPIへの組み込み】

  • services/nikkei_service.py にスクレイピング処理を実装
  • /api/v1/nikkei225 エンドポイントを追加してJSONで返却

プロジェクト構成(抜粋)

stock-api-project/
├── backend/
│   ├── app/
│   │   ├── api/
│   │   │   └── v1/
│   │   │       └── routes.py
│   │   ├── services/
│   │   │   └── nikkei_service.py
│   │   └── main.py
│   └── requirements.txt

ソースコード(主要ファイル)

【project.toml】

[project]
name = "backend"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "beautifulsoup4>=4.14.3",
    "fastapi>=0.128.0",
    "httpx>=0.28.1",
    "lxml>=6.0.2",
    "pydantic-settings>=2.12.0",
    "pyodbc>=5.3.0",
    "pytest>=9.0.2",
    "python-dotenv>=1.2.1",
    "requests>=2.32.5",
    "ruff>=0.14.13",
    "sqlalchemy>=2.0.45",
    "uvicorn[standard]>=0.40.0",
]

【main.py】

※特に変更なし

from fastapi import FastAPI
from app.api.v1.routes import router as api_router

app = FastAPI()

@app.get("/health")
def health_check():
    return {"status": "ok"}

app.include_router(api_router, prefix="/api/v1")

routes.py】

from fastapi import APIRouter
from app.services.nikkei_service import fetch_nikkei225_stocks

router = APIRouter()

@router.get("/nikkei225", tags=["Nikkei"])
def get_nikkei225():
    return fetch_nikkei225_stocks()

【nikkei_service.py】

import requests
from bs4 import BeautifulSoup
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def fetch_nikkei225_stocks():
    url = "https://indexes.nikkei.co.jp/nkave/index/component?idx=nk225"
    headers = {
        'User-Agent': 'Mozilla/5.0'
    }

    try:
        response = requests.get(url, headers=headers, verify=False, timeout=10)
        response.raise_for_status()
        response.encoding = response.apparent_encoding
    except requests.RequestException as e:
        raise RuntimeError(f"データ取得に失敗しました: {e}")

    return parse_nikkei225_html(response.text)

def parse_nikkei225_html(html_content):
    soup = BeautifulSoup(html_content, 'html.parser')
    sections = soup.find_all('div', class_='idx-index-components')

    if not sections:
        raise RuntimeError("銘柄情報セクションが見つかりませんでした。")

    all_stocks = []
    for section in sections:
        industry_tag = section.find('h3', class_='idx-section-subheading')
        if not industry_tag:
            continue
        industry = industry_tag.text.strip()

        table = section.find('table')
        if not table:
            continue

        rows = table.find('tbody').find_all('tr')
        for row in rows:
            cols = row.find_all('td')
            if len(cols) >= 3:
                code = cols[0].text.strip()
                brand_name = cols[1].text.strip()
                company_name = cols[2].text.strip()

                all_stocks.append({
                    "code": code,
                    "brand_name": brand_name,
                    "company_name": company_name,
                    "industry": industry
                })

    return all_stocks

上記ソースコードに修正してFastAPIを起動します。

URLに127.0.0.1:8000/api/v1/nikkei225を入力すると、以下のようにJSON形式で日経225の銘柄がJSON形式で取得できるようになりました。

おわりに

今回は、実際のWebサイトから日経225の構成銘柄を取得し、FastAPIでAPIとして公開するまでの流れを実装しました。 スクレイピング特有の課題(SSL・Bot対策・HTML構造の解析)に対応しながら、実務でも応用できるAPIの土台が完成しました。

次回は、取得した銘柄の株価を取得できるようにしたいと思います。

コメント

タイトルとURLをコピーしました