🎯 カスタマイズ可能で検出回避型のクラウドブラウザ。自社開発のChromiumを搭載し、ウェブクローラーAIエージェント向けに設計されています。👉今すぐ試す
ブログに戻ります

MutationObserverとPlaywrightを使って動的DOMの変化をキャプチャする

Isabella Garcia
Isabella Garcia

Web Data Collection Specialist

30-Jul-2026

TL;DR:

  • 動的ページは初期HTMLが読み込まれた後にDOMを変更することがあります。 スクレイパーは初回の応答を最終的なドキュメントとして扱うのではなく、使用可能な状態になるまで待つ必要があります。
  • MutationObserverはDOMの変化を記録します。 追加されたノード、削除されたノード、テキストの更新、または属性の遷移が必要な場合、トリガーアクションの前にオブザーバーをインストールします。
  • Playwrightのロケーターは、必要な状態が使用可能になるまで確認して待ちます。 状態の変化の起源をオブザーバーで追跡し、最終状態の準備にはロケーターを使用します。
  • バウンドオブザーバーは関連するサブツリーと変異タイプのみを監視すべきです。 小さい記録をシリアライズし、騒音の多い属性をフィルタリングし、期待される状態が現れた後は切断します。
  • ネットワークのインターセプションは、構造化された応答データが本当のターゲットである場合に通常はより良いです。 DOMの遷移自体が重要な場合にMutationObserverが最も役立ちます。
  • 同じ観察ワークフローがローカルでもScrapelessを通じても実行されます。 Scrapeless Scraping Browserに移行することで、ブラウザの作成が変更されますが、ページ側のオブザーバーは変更されません。
  • 無料で始められます。 新しいScrapelessアカウントには、無料のScraping Browserランタイムが含まれています—app.scrapeless.comでサインアップしてください。

はじめに:初期HTMLは始まりの状態に過ぎない

動的ページは初期HTMLが読み込まれた後にデータを挿入、表示、置き換え、または削除することができます。このガイドでは、公共のSeleniumフィクスチャをトリガーする前にバウンドMutationObserverをインストールし、正確なDOMの変化をキャプチャし、イベント駆動の観察をPlaywrightのロケーター待機と比較し、同じワークフローをScrapeless Scraping Browserに移行します。

MutationObserverがスクレイピングに追加するもの

MutationObserverはDOMのサブツリーに対する変更を報告します。子ノードの挿入/削除、属性、テキストの変更をページを再スキャンすることなく監視できます。MDNのMutationObserverリファレンスはコールバックと観察オプションを定義しています。

オブザーバーはデータが準備完了かどうかを自分自身で決定しません。変更事項を記録します。Playwrightのロケーターは特定の使用可能な要素を待つための明確な方法であり、オブザーバーの記録はその状態がどのように、いつ現れたかを説明します。

ScrapelessでPlaywrightを使用する理由

Playwrightは、トリガークリックの前にページにオブザーバーをインストールし、アクションを実行し、最終的なUI状態を待ち、コンパクトなイベントログを返すことができます。Scrapeless Scraping Browserは、同じページ側のスクリプトのためのホスティングされたChromiumセッションを提供します。

クラウドブラウザはconnect_over_cdpを使用します。ブラウザの生成のみが変更されます。

前提条件

  • Python 3.10以降。
  • playwright 1.59.0または互換性のある最新版。
  • 完全なライブ例のためのローカルChrome/Chromium。
  • クラウド接続のための有料ScrapelessアカウントとSCRAPELESS_API_KEY。検証アカウントは14500のコードを返したため、その接続はラベル付きの前提条件のギャップです。

インストール

bash Copy
python -m pip install "playwright==1.59.0"

Scrapeless Scraping Browserに接続する

注意:このブロックには有料のScraping Browser残高が必要です。最終的な検証アカウントは残高不足、まず再充電してくださいと返しました;以下の完全なオブザーバーワークフローはローカルChromeで実行されました。

python Copy
import os
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright

params = urlencode({
    "token": os.environ["SCRAPELESS_API_KEY"],
    "sessionTTL": 120,
    "proxyCountry": "US",
})
cdp_url = f"wss://browser.scrapeless.com/api/v2/browser?{params}"

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(cdp_url)
    page = browser.contexts[0].pages[0]
    # 下のオブザーバーをインストールして使用します。
    browser.close()

現在の接続パラメータについては、Scrapeless開発者ドキュメントを参照してください。

ステップ1 — ページの初期状態を確認する

Seleniumの公開フィクスチャは2つのボタンを公開しています。1つは1秒後に#box0を挿入し、もう1つは1秒後に隠れている#revealed入力を表示します:

python Copy
TARGET_URL = "https://www.selenium.dev/selenium/web/dynamic.html"

page.goto(TARGET_URL, wait_until="domcontentloaded")
print("box before:", page.locator("#box0").count())
print("input visible before:", page.locator("#revealed").is_visible())
text Copy
box before: 0
input visible before: False

フィクスチャの初期HTMLには隠された入力が含まれていますが、ボックスは含まれていません。その違いが、オブザーバーが両方の子リストと属性変異を監視する必要がある理由です。

ステップ2 — アクションの前にオブザーバーをインストールする

プレーンの辞書をDOMノードの代わりに格納します。MutationRecordは、変異の種類、ターゲット、変更された属性、および追加/削除されたノードを公開します。

python Copy
page.evaluate("""
    window.capturedMutations = [];
    window.scrapeObserver = new MutationObserver(records => {
        for (const record of records) {
            for (const node of record.addedNodes) {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    capturedMutations.push({
                        type: 'added',
                        id: node.id,
                        display: getComputedStyle(node).display,
                    });
                }
            }
            if (record.type === 'attributes') {
                capturedMutations.push({
                    type: 'attribute',
                    id: record.target.id,
                    name: record.attributeName,
                    display: getComputedStyle(record.target).display,
                });
            }
        }
    });
    scrapeObserver.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['style'],
    });
""")

attributeFilterは、無関係なクラス、ARIA、またはフレームワーク属性をこの制限されたキャプチャから除外します。

ステップ3 — 変更をトリガーし、使用可能な状態を待つ

python Copy
page.click("#adder")
page.click("#reveal")
page.wait_for_selector("#box0")
page.locator("#revealed").wait_for(state="visible")

Playwrightの自動待機ガイダンスは、なぜロケーターが適切な準備確認であるかを説明します。オブザーバーログは補足的な証拠であり、具体的な最終状態のアサーションの代わりではありません。

ステップ4 — オブザーバーを切断し、レコードを読む

python Copy
changes = page.evaluate("""() => {
    scrapeObserver.disconnect();
    return capturedMutations;
}""")

print("changes:", changes)
print("change count:", len(changes))
print("box matches:", page.locator("#box0").count())
print("input visible:", page.locator("#revealed").is_visible())
text Copy
changes: [{'type': 'added', 'id': 'box0', 'display': 'block'}, {'type': 'attribute', 'id': 'revealed', 'name': 'style', 'display': 'inline-block'}]
change count: 2
box matches: 1
input visible: True

切断することで、後の無関係なページの変更がイベントリストを拡大するのを防ぎます。長期間持続するオブザーバーには、常に明示的なストップ条件が必要です。

ホストされたブラウザセッションを観察する準備はできましたか? 無料のScrapelessアカウントを作成し、ブラウザの作成のみを置き換えてください。

完全な実行可能なキャプチャ

python Copy
from playwright.sync_api import sync_playwright

TARGET_URL = "https://www.selenium.dev/selenium/web/dynamic.html"

with sync_playwright() as p:
    browser = p.chromium.launch(
        executable_path="/usr/bin/google-chrome",
        headless=True,
    )
    page = browser.new_page()
    page.goto(TARGET_URL, wait_until="domcontentloaded")

    box_before = page.locator("#box0").count()
    input_before = page.locator("#revealed").is_visible()

    page.evaluate("""
        window.capturedMutations = [];
        window.scrapeObserver = new MutationObserver(records => {
            for (const record of records) {
                for (const node of record.addedNodes) {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        capturedMutations.push({
                            type: 'added',
                            id: node.id,
                            display: getComputedStyle(node).display,
                        });
                    }
                }
                if (record.type === 'attributes') {
                    capturedMutations.push({
                        type: 'attribute',
                        id: record.target.id,
                        name: record.attributeName,
                        display: getComputedStyle(record.target).display,
                    });
                }
            }
        });
        scrapeObserver.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['style'],
        });
    """)

    page.click("#adder")
    page.click("#reveal")
    page.wait_for_selector("#box0")
    page.locator("#revealed").wait_for(state="visible")

    changes = page.evaluate("""() => {
        scrapeObserver.disconnect();
        return capturedMutations;
    }""")
    box_after = page.locator("#box0").count()
    input_after = page.locator("#revealed").is_visible()

    assert box_before == 0 and input_before is False
    assert changes == [
        {"type": "added", "id": "box0", "display": "block"},
{
    "type": "attribute",
    "id": "revealed",
    "name": "style",
    "display": "inline-block"
}
)

箱が 1 のとき、入力が True であることを確認します。

```python
print("箱の前:", box_before)
print("入力の表示の前:", input_before)
print("変更:", changes)
print("変更の数:", len(changes))
print("箱の後:", box_after)
print("入力の表示の後:", input_after)
browser.close()

最終実行では、追加されたノード 1 個とスタイル属性の変更が正確にキャプチャされ、その後、1 つのボックスと 1 つの可視入力が確認されます。

返されるもの

完了したワークフローは、独立した最終状態の確認を伴う簡潔なミューテーションログを返します:

json Copy
{
  "before": {
    "box_matches": 0,
    "input_visible": false
  },
  "mutations": [
    {
      "type": "added",
      "id": "box0",
      "display": "block"
    },
    {
      "type": "attribute",
      "id": "revealed",
      "name": "style",
      "display": "inline-block"
    }
  ],
  "after": {
    "box_matches": 1,
    "input_visible": true
  }
}

ミューテーション記録は、ページがどのように変わったかを説明し、ロケーターのチェックは最終要素が使用可能であることを確認します。両方を保持することで、観察されたイベントが完了した UI 遷移と誤解されるのを防ぎます。

適切な動的ページシグナルの選択

シグナル 使用する時 返されるもの 主な制限
MutationObserver DOM の変更の順序や種類が重要な場合 追加、削除、テキスト、または属性のミューテーション 限定されたスコープが必要で、明示的な停止条件が必要
Playwright ロケーター待機 最終的な使用可能な状態だけが重要な場合 準備が整った要素または確認された状態 ページがその状態に達した経緯を説明しない
ネットワークキャプチャ 構造化されたデータがリクエストを通じて到着する場合 リクエストとレスポンスのペイロード 後の DOM 変換を説明しない
限界付きポーリング 安定したイベント、リクエスト、またはロケーターが存在しない場合 定期的な状態スナップショット 繰り返しのチェックがノイズやページ作業を追加

ロケータ待機はデフォルトの準備プリミティブであるべきです。抽出が変更の出所も必要な場合は MutationObserver を追加し、構造化されたレスポンスがレンダリングされた DOM よりも安定している場合はネットワークキャプチャを優先してください。

MutationObserver の一般的な失敗モード

最初のイベントが欠落している

アクションの後にオブザーバーがインストールされました。クリックするか状態遷移に移動する前に登録してください。

キャプチャが大きくなりすぎる

小さいルートを観察し、属性をフィルタリングし、必要なフィールドだけを保存します。長時間実行されるキャプチャで DOM ノードを保持することは決してしないでください。

属性変更が繰り返される

フレームワークは同じ属性を何度も書き込むことがあります。各遷移が必要か、最後の値だけが必要かを決定し、コールバックの外で重複を排除します。

オブザーバーが決して停止しない

期待するレコードが到着した後、またはタスクの期限が切れたときに切断します。オープンエンドの観察は無限のメモリ成長を引き起こします。

結論

MutationObserver はクライアントサイドの DOM 変更の出所を提供し、Playwright のロケーターは使用可能な最終状態を確認します。アクションの前にオブザーバーをインストールし、小さなレコードをシリアル化し、関連するミューテーションクラスのみを監視し、迅速に切断してください。ワークフローを Scrapeless に移動すると、イベントキャプチャは変更せずにブラウザの作成が変わります。

動的ページ状態をキャプチャする準備はできましたか?

Scrapeless コミュニティでブラウザベースの抽出ワークフローを構築している開発者に参加してください: Discord · Telegram

Scrapeless を始めるScrapeless の価格を確認し、このワークフローをネットワークリクエストのインタセプションと比較してください。

FAQ

Q: MutationObserver は Playwright の待機より優れていますか?

異なる質問に答えます。オブザーバーは何が変わったのかを記録し、ロケーター待機は必要な要素または状態が準備されていることを確認します。

Q: オブザーバーはいつインストールするべきですか?

ミューテーションをトリガーする可能性のあるアクションの前に。後でインストールすると、必要な唯一のイベントを見逃す可能性があります。

Q: MutationObserver はネットワークレスポンスを見ることができますか?

いいえ。DOM ミューテーションを見ます。ネットワークペイロードが必要なときはリクエスト/レスポンスイベントを使用してください。

Q: 観察された属性をフィルタリングする理由は何ですか?

フレームワークは多くの属性を変更します。 attributeFilter を使用すると、キャプチャを限定し、関連性を保つことができます。

Q: MutationRecord オブジェクトを直接保存すべきですか?

いいえ。それらを小さな単純なオブジェクトに変換してください。なぜなら、レコードがDOMノードを参照しており、便利なシリアライズされた出力ではないからです。

Q: どのようにオブザーバーを停止しますか?

期待されるレコードが到着した後、または限られたタスクの締切が来たら、disconnect()を呼び出してください。

Q: 公開ページを常に監視することは許可されていますか?

いいえ。サイトの利用規約を確認し、必要な公開データのみを収集し、セッションとリクエストの量を制限し、該当する場合はロボットポリシーに従ってください。

Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。

最も人気のある記事

カタログ