静的版との違い
動的版と静的版の唯一の差分は 「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 の実装
"""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]") のように特定要素が現れるまで明示的に待機するほうが確実だ。
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 タグが含まれていない | 動的版 |
統合エントリポイント
呼び出し側が静的・動的を意識せずに使えるよう、 フラグで切り替えるファサード関数を用意する。
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 リストを「フォーム仕様書の自動生成」「テストケースのインプット生成」に活用する方法を解説します。