MutationObserverとPlaywrightを使って動的DOMの変化をキャプチャする
Web Data Collection Specialist
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以降。
playwright1.59.0または互換性のある最新版。- 完全なライブ例のためのローカルChrome/Chromium。
- クラウド接続のための有料Scrapelessアカウントと
SCRAPELESS_API_KEY。検証アカウントは14500のコードを返したため、その接続はラベル付きの前提条件のギャップです。
インストール
bash
python -m pip install "playwright==1.59.0"
Scrapeless Scraping Browserに接続する
注意:このブロックには有料のScraping Browser残高が必要です。最終的な検証アカウントは
残高不足、まず再充電してくださいと返しました;以下の完全なオブザーバーワークフローはローカルChromeで実行されました。
python
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
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
box before: 0
input visible before: False
フィクスチャの初期HTMLには隠された入力が含まれていますが、ボックスは含まれていません。その違いが、オブザーバーが両方の子リストと属性変異を監視する必要がある理由です。
ステップ2 — アクションの前にオブザーバーをインストールする
プレーンの辞書をDOMノードの代わりに格納します。MutationRecordは、変異の種類、ターゲット、変更された属性、および追加/削除されたノードを公開します。
python
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
page.click("#adder")
page.click("#reveal")
page.wait_for_selector("#box0")
page.locator("#revealed").wait_for(state="visible")
Playwrightの自動待機ガイダンスは、なぜロケーターが適切な準備確認であるかを説明します。オブザーバーログは補足的な証拠であり、具体的な最終状態のアサーションの代わりではありません。
ステップ4 — オブザーバーを切断し、レコードを読む
python
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
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
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
{
"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サイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。



