静的版との違い

動的版と静的版の唯一の差分は 「HTML の取得方法」だ。 HTML を取得した後の解析ロジック(_extract_tag / _get_label)は PART 06 の static.py をそのまま流用できる。

静的版(requests + BS4)動的版(Playwright)
HTML 取得requests.get(url)Playwright でブラウザを起動し page.content()
JS 実行❌ しない✅ する
速度速い(0.5〜2 秒)遅い(3〜10 秒)
解析ロジック共通(_extract_tag

dynamic.py の実装

Python — extractor/dynamic.py(完全版)
"""dynamic.py — Playwright による動的ページ INPUT 抽出"""
from __future__ import annotations

from playwright.sync_api import sync_playwright, Page
from bs4 import BeautifulSoup

from .models import InputEntity
from .static import _extract_tag, _PARSER  # 解析ロジックを共有


def _get_html(url: str, wait_until: str = "networkidle", timeout: int = 30000) -> str:
    """Playwright でページを開き JS 実行後の HTML を返す"""
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto(url, wait_until=wait_until, timeout=timeout)
        html = page.content()
        browser.close()
    return html


def extract_dynamic(
    url: str,
    wait_until: str = "networkidle",
    timeout: int = 30000,
) -> list[InputEntity]:
    """
    動的ページから INPUT / SELECT / TEXTAREA 要素を抽出して InputEntity リストを返す。

    Args:
        url: 抽出対象ページの URL
        wait_until: ページ読み込み完了の判断条件
            "networkidle" — ネットワークが静止するまで待つ(デフォルト・最も確実)
            "domcontentloaded" — DOM 構築後(JS 実行前に戻る可能性あり)
            "load"            — load イベント後
        timeout: Playwright タイムアウト(ミリ秒)

    Returns:
        InputEntity のリスト
    """
    html = _get_html(url, wait_until=wait_until, timeout=timeout)
    soup = BeautifulSoup(html, _PARSER)

    results: list[InputEntity] = []
    for tag in soup.find_all(["input", "select", "textarea"]):
        entity = _extract_tag(soup, tag, url)
        if entity is not None:
            results.append(entity)

    return results

ページ読み込み完了の待ち方

動的ページで INPUT 要素が欠落する最大の原因は「まだ JS が終わっていない状態で HTML を取得すること」だ。 wait_until パラメータの選択が重要になる。

wait_until 値待機条件推奨場面
"networkidle"500ms 以上ネットワーク通信がない状態SPA・非同期フォーム(最も確実)
"load"load イベント発火通常の Web ページ
"domcontentloaded"DOM 構築完了静的に近いページの高速取得

⚠️ networkidle でも取得できない場合

無限スクロールや WebSocket を使うページでは networkidle が永遠に来ないことがある。その場合は page.wait_for_selector("input[name]") のように特定要素が現れるまで明示的に待機するほうが確実だ。

Python — 要素出現を待つ例
page.goto(url)
# "input[name]" を持つ要素が出現するまで最大 10 秒待機
page.wait_for_selector("input[name]", timeout=10000)
html = page.content()

決め手:どちらを使うか

📌 判断の手順

① まず extract_static を試す → ② INPUT 要素が 0 件、または明らかに少なければ extract_dynamic に切り替える。

requests は Playwright の 5〜10 倍速いため、静的で動くなら静的を優先する

ページの特徴推奨
サーバーサイドレンダリング(PHP・Java・Rails 等)静的版
React / Vue / Angular の SPA動的版
フォームが Ajax で後から挿入される動的版
ページソースに INPUT タグが含まれている静的版
ページソースに INPUT タグが含まれていない動的版

統合エントリポイント

呼び出し側が静的・動的を意識せずに使えるよう、 フラグで切り替えるファサード関数を用意する。

Python — extractor/__init__.py
from .static import extract_static
from .dynamic import extract_dynamic
from .models import InputEntity


def extract_inputs(url: str, dynamic: bool = False, **kwargs) -> list[InputEntity]:
    """
    URL から INPUT 要素を抽出するファサード関数。

    Args:
        url: 抽出対象 URL
        dynamic: True なら Playwright(JS 実行あり)、False なら requests(高速)
        **kwargs: 各抽出関数に渡す追加引数

    Returns:
        InputEntity のリスト
    """
    if dynamic:
        return extract_dynamic(url, **kwargs)
    return extract_static(url, **kwargs)

次の章では…

PART 08 では抽出した InputEntity リストを「フォーム仕様書の自動生成」「テストケースのインプット生成」に活用する方法を解説します。

→ PART 08 — 応用へ