Volver al blog

Captura cambios dinámicos en el DOM con MutationObserver y Playwright.

Isabella Garcia
Isabella Garcia

Web Data Collection Specialist

30-Jul-2026

TL;DR:

  • Las páginas dinámicas pueden cambiar el DOM después de que se haya cargado el HTML inicial. Un scraper debe esperar el estado utilizable en lugar de tratar la primera respuesta como el documento final.
  • MutationObserver registra cómo cambia el DOM. Instala el observador antes de la acción desencadenante cuando necesites nodos añadidos, nodos eliminados, actualizaciones de texto o transiciones de atributos.
  • El localizador de Playwright espera a confirmar cuando el estado requerido es utilizable. Usa el observador para la procedencia del cambio y los localizadores para la preparación del estado final.
  • Un observador acotado debe observar solo el subárbol relevante y los tipos de mutación. Serializa registros pequeños, filtra atributos ruidosos y desconéctate después de que aparezca el estado esperado.
  • La interceptación de red suele ser mejor cuando los datos de respuesta estructurados son el verdadero objetivo. MutationObserver es más útil cuando la transición del DOM en sí misma es importante.
  • El mismo flujo de observación funciona localmente y a través de Scrapeless. Pasar a Scrapeless Scraping Browser cambia la creación del navegador, no el observador del lado de la página.
  • Gratis para comenzar. Las nuevas cuentas de Scrapeless incluyen tiempo de ejecución gratuito de Scraping Browser: regístrate en app.scrapeless.com.

Introducción: El HTML Inicial Es Solo el Estado Comienzo

Una página dinámica puede insertar, revelar, reemplazar o eliminar datos después de que su HTML inicial se haya cargado. Esta guía instala un MutationObserver acotado antes de activar un fixture público de Selenium, captura los cambios exactos del DOM y compara la observación impulsada por eventos con las esperas de localizadores de Playwright antes de mover el mismo flujo de trabajo al Scrapeless Scraping Browser.

Lo Que MutationObserver Agrega al Scraping

MutationObserver informa sobre cambios en un subárbol del DOM. Puede observar la inserción/eliminación de elementos hijos, atributos y cambios de texto sin escanear repetidamente la página. La referencia de MutationObserver de MDN define la función de devolución de llamada y las opciones de observación.

Un observador no decide por sí mismo cuándo están los datos listos. Registra lo que cambió. Los localizadores de Playwright siguen siendo la forma más clara de esperar un elemento utilizable específico, mientras que los registros del observador explican cómo y cuándo apareció ese estado.

Por Qué Usar Playwright Con Scrapeless

Playwright puede instalar un observador en la página antes del clic desencadenante, realizar la acción, esperar el estado final de la interfaz de usuario y devolver un registro de eventos compacto. El Scrapeless Scraping Browser proporciona una sesión de Chromium hospedada para el mismo script del lado de la página.

El navegador en la nube utiliza connect_over_cdp. Solo cambia la creación del navegador.

Requisitos Previos

  • Python 3.10 o posterior.
  • playwright 1.59.0 o una versión actual compatible.
  • Chrome/Chromium local para el ejemplo en vivo completo.
  • Una cuenta de Scrapeless financiada y SCRAPELESS_API_KEY para la conexión en la nube. La cuenta de verificación devolvió el código 14500, por lo que esa conexión es una brecha de requisito etiquetada.

Instalar

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

Conectar al Scrapeless Scraping Browser

Nota: Este bloque necesita un saldo financiado de Scraping Browser. La cuenta de verificación final devolvió saldo insuficiente, por favor recarga primero; el flujo de trabajo completo del observador a continuación se ejecutó en Chrome local.

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]
    # Instalar y consumir el observador a continuación.
    browser.close()

Usa la documentación del desarrollador de Scrapeless para obtener los parámetros de conexión actuales.

Paso 1 — Inspeccionar el Estado Inicial de la Página

El fixture público de Selenium expone dos botones. Uno inserta #box0 después de un segundo; el otro revela un campo de entrada oculto #revealed después de un segundo:

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

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

El HTML inicial del fixture contiene el campo de entrada oculto pero no la caja. Esa diferencia es la razón por la que el observador debe observar tanto las mutaciones de la lista de hijos como las de atributos.

Paso 2 — Instalar el Observador Antes de la Acción

Almacena diccionarios simples en lugar de nodos del DOM. MutationRecord expone el tipo de mutación, el objetivo, el atributo cambiado y los nodos añadidos/eliminados.

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'],
    });
""")

El attributeFilter mantiene fuera de esta captura limitada atributos de clase, ARIA o de marco no relacionados.

Paso 3 — Activar los Cambios y Esperar un Estado Utilizable

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

La guía de auto-espera de Playwright explica por qué los localizadores son la verificación de preparación adecuada. El registro del observador es evidencia complementaria, no un reemplazo para una afirmación concreta de estado final.

Paso 4 — Desconectar el Observador y Leer los Registros

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

print("cambios:", changes)
print("conteo de cambios:", len(changes))
print("coincidencias de caja:", page.locator("#box0").count())
print("entrada visible:", page.locator("#revealed").is_visible())
text Copy
cambios: [{'type': 'added', 'id': 'box0', 'display': 'block'}, {'type': 'attribute', 'id': 'revealed', 'name': 'style', 'display': 'inline-block'}]
conteo de cambios: 2
coincidencias de caja: 1
entrada visible: True

Desconectar evita que cambios no relacionados en la página más tarde amplíen la lista de eventos. Un observador de larga duración siempre debe tener una condición de detención explícita.

¿Listo para observar una sesión de navegador alojada? Crea una cuenta gratuita en Scrapeless y reemplaza solo la creación del navegador.

Captura Ejecutable Completa

python Copy
from playwright.sync_api import sync_playwright

URL_OBJETIVO = "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(URL_OBJETIVO, wait_until="domcontentloaded")

    caja_antes = page.locator("#box0").count()
    entrada_antes = 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;
    }""")
    caja_despues = page.locator("#box0").count()
    entrada_despues = page.locator("#revealed").is_visible()

    assert caja_antes == 0 and entrada_antes is False
    assert changes == [
        {"type": "added", "id": "box0", "display": "block"},
{
    "type": "atributo",
    "id": "revelado",
    "nombre": "estilo",
    "mostrar": "bloque en línea",
},
]
assert box_after == 1 and input_after is True

print("caja antes:", box_before)
print("entrada visible antes:", input_before)
print("cambios:", changes)
print("conteo de cambios:", len(changes))
print("caja después:", box_after)
print("entrada visible después:", input_after)
browser.close()

La ejecución final captura exactamente un nodo añadido y un cambio de atributo de estilo, luego confirma de manera independiente una caja y una entrada visible.

Lo Que Obtienes de Vuelta

El flujo de trabajo completado devuelve un registro de mutaciones compacto junto con verificaciones de estado final independientes:

json Copy
{
  "antes": {
    "caja_coincide": 0,
    "entrada_visible": false
  },
  "mutaciones": [
    {
      "tipo": "añadido",
      "id": "caja0",
      "mostrar": "bloque"
    },
    {
      "tipo": "atributo",
      "id": "revelado",
      "nombre": "estilo",
      "mostrar": "bloque en línea"
    }
  ],
  "después": {
    "caja_coincide": 1,
    "entrada_visible": true
  }
}

Los registros de mutación explican cómo cambió la página, mientras que las verificaciones del localizador confirman que los elementos finales son utilizables. Mantener ambos evita que un evento observado se confunda con una transición de interfaz de usuario completada.

Elegir la Señal de Página Dinámica Correcta

Señal Usarla Cuando Lo Que Devuelve Limitación Principal
MutationObserver La secuencia o tipo de cambios en el DOM importa Mutaciones añadidas, eliminadas, de texto o de atributos Requiere un alcance limitado y una condición de paro explícita
Espera de localizador de Playwright Solo el estado final utilizable importa Un elemento listo o estado confirmado No explica cómo la página alcanzó ese estado
Captura de red Datos estructurados llegan a través de solicitudes Carga útil de solicitud y respuesta No describe transformaciones posteriores del DOM
Polling limitado No existe un evento, solicitud o localizador estable Instantáneas periódicas del estado Comprobaciones repetidas añaden ruido y trabajo a la página

Una espera de localizador debe ser la primitiva de preparación predeterminada. Agrega MutationObserver cuando la extracción también necesita la procedencia de cambios, y prefiere la captura de red cuando la respuesta estructurada es más estable que el DOM representado.

Modos de Falla Comunes de MutationObserver

Falta el Primer Evento

El observador fue instalado después de la acción. Regístralo antes de hacer clic o navegar a la transición de estado.

La Captura Crece Demasiado

Observa una raíz más pequeña, filtra atributos y almacena solo los campos que necesitas. Nunca retengas nodos DOM en una captura de larga duración.

Cambios de Atributo Repetidos

Los marcos pueden escribir el mismo atributo varias veces. Decide si necesitas cada transición o solo el último valor, luego deduplica fuera de la devolución de llamada.

El Observador Nunca Se Detiene

Desconéctalo una vez que lleguen los registros esperados o cuando expire el plazo de la tarea. La observación abierta crea un crecimiento de memoria sin límites.

Conclusión

MutationObserver te brinda procedencia para cambios en el DOM del lado del cliente, mientras que los localizadores de Playwright confirman el estado final utilizable. Instala el observador antes de la acción, serializa registros pequeños, observa solo las clases de mutación relevantes y desconéctate rápidamente. Mover el flujo de trabajo a Scrapeless cambia la creación del navegador sin cambiar la captura de eventos.

¿Listo para Capturar el Estado de Página Dinámica?

Únete a los desarrolladores que construyen flujos de trabajo de extracción basados en navegadores en la comunidad de Scrapeless: Discord · Telegram.

Comienza con Scrapeless, revisa los precios de Scrapeless, y compara este flujo de trabajo con intercepción de solicitudes de red cuando la página exponga respuestas estructuradas.

FAQ

P: ¿Es MutationObserver mejor que las esperas de Playwright?

Responden a preguntas diferentes. El observador registra lo que cambió; una espera de localizador confirma que el elemento o estado que necesitas está listo.

P: ¿Cuándo debo instalar el observador?

Antes de la acción que puede desencadenar la mutación. Instalarlo después puede perder el único evento que necesitabas.

P: ¿Puede MutationObserver ver respuestas de red?

No. Ve mutaciones en el DOM. Usa eventos de solicitud/respuesta cuando necesites cargas útiles de red.

P: ¿Por qué filtrar atributos observados?

Los marcos cambian muchos atributos. Un attributeFilter mantiene la captura limitada y relevante.

P: ¿Debería almacenar objetos MutationRecord directamente?

No. Convierte los registros a objetos simples porque los registros hacen referencia a nodos DOM y no son una salida serializada conveniente.

P: ¿Cómo detengo un observador?

Llama a disconnect() después de que lleguen los registros esperados o en un plazo de tarea limitado.

P: ¿Se permite siempre observar una página pública?

No. Revisa los términos del sitio, recopila solo los datos públicos necesarios, limita la sesión y el volumen de solicitudes, y sigue la política de robots donde sea aplicable.

En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.

Artículos más populares

Catalogar