🎯 Um navegador em nuvem personalizável e anti-detecção alimentado por Chromium desenvolvido internamente, projetado para rastreadores web e agentes de IA. 👉Experimente agora
De volta ao blog

Capture mudanças dinâmicas no DOM com MutationObserver e Playwright

Isabella Garcia
Isabella Garcia

Web Data Collection Specialist

30-Jul-2026

TL;DR:

  • Páginas dinâmicas podem alterar o DOM após o carregamento do HTML inicial. Um scraper deve aguardar o estado utilizável em vez de tratar a primeira resposta como o documento final.
  • MutationObserver registra como o DOM muda. Instale o observador antes da ação que provoca a mudança quando você precisar de nós adicionados, nós removidos, atualizações de texto ou transições de atributos.
  • O localizador do Playwright aguarda a confirmação quando o estado necessário é utilizável. Use o observador para a proveniência da mudança e localizadores para a prontidão do estado final.
  • Um observador restrito deve observar apenas a subárvore relevante e tipos de mutação. Serialize registros pequenos, filtre atributos barulhentos e desconecte após o estado esperado aparecer.
  • A interceptação de rede costuma ser melhor quando os dados de resposta estruturados são o alvo real. MutationObserver é mais útil quando a transição do DOM em si importa.
  • O mesmo fluxo de observação funciona localmente e através do Scrapeless. Mover-se para o Scrapeless Scraping Browser muda a criação do navegador, não o observador do lado da página.
  • Grátis para começar. Novas contas do Scrapeless incluem tempo de execução gratuito do Scraping Browser—inscreva-se em app.scrapeless.com.

Introdução: O HTML Inicial É Apenas o Estado Inicial

Uma página dinâmica pode inserir, revelar, substituir ou remover dados após seu HTML inicial ter sido carregado. Este guia instala um MutationObserver restrito antes de acionar um fixture público do Selenium, captura as mudanças exatas do DOM e compara a observação orientada a eventos com as esperas do localizador do Playwright antes de mover o mesmo fluxo de trabalho para o Scrapeless Scraping Browser.

O Que o MutationObserver Adiciona ao Scraping

MutationObserver relata mudanças em uma subárvore do DOM. Ele pode observar inserção/remocao de filhos, atributos e mudanças de texto sem escanear repetidamente a página. A referência do MutationObserver do MDN define a função de callback e opções de observação.

Um observador não decide quando os dados estão prontos por si só. Ele registra o que mudou. Os localizadores do Playwright permanecem a forma mais clara de aguardar um elemento utilizável específico, enquanto os registros do observador explicam como e quando aquele estado apareceu.

Por Que Usar Playwright Com Scrapeless

O Playwright pode instalar um observador na página antes do clique que provoca a ação, realizar a ação, aguardar o estado final da interface do usuário e retornar um log de eventos compacto. O Scrapeless Scraping Browser fornece uma sessão hospedada do Chromium para o mesmo script do lado da página.

O navegador na nuvem usa connect_over_cdp. Apenas a criação do navegador muda.

Pré-requisitos

  • Python 3.10 ou posterior.
  • playwright 1.59.0 ou uma versão compatível atual.
  • Chrome/Chromium local para o exemplo ao vivo completo.
  • Uma conta do Scrapeless financiada e SCRAPELESS_API_KEY para a conexão com a nuvem. A verificação da conta retornou o código 14500, então essa conexão é uma lacuna de pré-requisito rotulada.

Instalação

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

Conectar ao Scrapeless Scraping Browser

Nota: Este bloco precisa de saldo financiado do Scraping Browser. A conta de verificação final retornou saldo insuficiente, por favor recarregue primeiro; o fluxo completo do observador abaixo foi executado no 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]
    # Instale e utilize o observador abaixo.
    browser.close()

Use a documentação do desenvolvedor do Scrapeless para os parâmetros de conexão atuais.

Etapa 1 — Inspecionar o Estado Inicial da Página

O fixture público do Selenium expõe dois botões. Um insere #box0 após um segundo; o outro revela um input oculto #revealed após um segundo:

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

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

O HTML inicial do fixture contém o input oculto, mas não a caixa. Essa diferença é o motivo pelo qual o observador deve observar tanto as mutações da lista de crianças quanto as mutações de atributos.

Etapa 2 — Instalar o Observador Antes da Ação

Armazene dicionários simples em vez de nós DOM. MutationRecord expõe o tipo de mutação, alvo, atributo alterado e nós adicionados/removidos.

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

O attributeFilter mantém atributos de classe, ARIA ou de framework não relacionados fora dessa captura limitada.

Passo 3 — Acione as Mudanças e Aguarde um Estado Utilizável

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

A orientação de espera automática do Playwright explica por que os localizadores são a verificação correta de prontidão. O log do observador é uma evidência complementar, não um substituto para uma asserção de estado final concreta.

Passo 4 — Desconecte o Observador e Leia os Registros

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

Desconectar impede que mudanças não relacionadas na página ampliem a lista de eventos. Um observador de longa duração deve sempre ter uma condição de parada explícita.

Pronto para observar uma sessão de navegador hospedada? Crie uma conta gratuita no Scrapeless e substitua apenas a criação do navegador.

Captura Executável Completa

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": "atributo",
            "id": "revelado",
            "name": "estilo",
            "display": "inline-block",
        },
    ]
    assert box_after == 1 and input_after is True

    print("caixa antes:", box_before)
    print("entrada visível antes:", input_before)
    print("mudanças:", changes)
    print("contagem de mudanças:", len(changes))
    print("caixa depois:", box_after)
    print("entrada visível depois:", input_after)
    browser.close()

A execução final captura exatamente um nó adicionado e uma mudança de atributo de estilo, e depois confirma de forma independente uma caixa e uma entrada visível.

O Que Você Recebe de Volta

O fluxo de trabalho completo retorna um log de mutação compacto junto com verificações de estado final independentes:

json Copy
{
  "antes": {
    "caixa_corresponde": 0,
    "entrada_visível": false
  },
  "mutações": [
    {
      "type": "adicionado",
      "id": "caixa0",
      "display": "block"
    },
    {
      "type": "atributo",
      "id": "revelado",
      "name": "estilo",
      "display": "inline-block"
    }
  ],
  "depois": {
    "caixa_corresponde": 1,
    "entrada_visível": true
  }
}

Os registros de mutação explicam como a página mudou, enquanto as verificações de localizador confirmam que os elementos finais são utilizáveis. Manter ambos evita que um evento observado seja confundido com uma transição de UI concluída.

Escolhendo o Sinal de Página Dinâmica Certo

Sinal Use-o Quando O Que Ele Retorna Limitação Principal
MutationObserver A sequência ou tipo de mudanças no DOM é importante Adições, remoções, textos ou mutações de atributos Requer escopo limitado e uma condição de parada explícita
Espera do localizador do Playwright Apenas o estado final utilizável é importante Um elemento pronto ou estado confirmado Não explica como a página alcançou esse estado
Captura de rede Dados estruturados chegam por meio de solicitações Payloads de solicitação e resposta Não descreve transformações posteriores do DOM
Polling limitado Nenhum evento, solicitação ou localizador estável existe Capturas de estado periódicas Verificações repetidas adicionam ruído e sobrecarga da página

Uma espera de localizador deve ser o primitivo de prontidão padrão. Adicione o MutationObserver quando a extração também precisar da proveniência da mudança e prefira a captura de rede quando a resposta estruturada for mais estável do que o DOM renderizado.

Modos Comuns de Falha do MutationObserver

O Primeiro Evento Está Ausente

O observador foi instalado após a ação. Registre-o antes de clicar ou navegar para a transição de estado.

A Captura Cresce Muito

Observe uma raiz menor, filtre atributos e armazene apenas os campos necessários. Nunca retenha nós do DOM em uma captura de longa duração.

Mudanças de Atributo se Repetem

Frameworks podem escrever o mesmo atributo várias vezes. Decida se você precisa de cada transição ou apenas do último valor, e então deduplice fora do callback.

O Observador Nunca Para

Desconecte-o assim que os registros esperados chegarem ou quando o prazo da tarefa expirar. Observação sem limite cria crescimento de memória ilimitado.

Conclusão

O MutationObserver lhe dá a proveniência para mudanças no DOM do lado do cliente, enquanto os localizadores do Playwright confirmam o estado final utilizável. Instale o observador antes da ação, serialize registros pequenos, observe apenas as classes de mutação relevantes e desconecte prontamente. Mover o fluxo de trabalho para o Scrapeless muda a criação do navegador sem alterar a captura de eventos.

Pronto para Capturar o Estado de Página Dinâmica?

Junte-se aos desenvolvedores que estão construindo fluxos de trabalho de extração baseados em navegador na comunidade Scrapeless: Discord · Telegram.

Comece com Scrapeless, revise os preços do Scrapeless e compare este fluxo de trabalho com interceptação de solicitações de rede quando a página expuser respostas estruturadas.

FAQ

Q: O MutationObserver é melhor do que as esperas do Playwright?

Eles respondem a perguntas diferentes. O observador registra o que mudou; uma espera de localizador confirma se o elemento ou estado que você precisa está pronto.

Q: Quando devo instalar o observador?

Antes da ação que pode acionar a mutação. Instalá-lo depois pode perder o único evento que você precisava.

Q: O MutationObserver consegue ver respostas de rede?

Não. Ele vê mutações do DOM. Use eventos de solicitação/resposta quando precisar de payloads de rede.

Q: Por que filtrar atributos observados?

Frameworks mudam muitos atributos. Um attributeFilter mantém a captura limitada e relevante.

Q: Devo armazenar objetos MutationRecord diretamente?
Não. Converta-os em pequenos objetos simples porque registros referenciam nós do DOM e não são uma saída serializada conveniente.

Q: Como posso parar um observador?

Chame disconnect() após os registros esperados chegarem ou em um prazo de tarefa delimitado.

Q: Observar uma página pública é sempre permitido?

Não. Revise os termos do site, colete apenas os dados públicos necessários, limite a sessão e o volume de solicitações, e siga a política de robots quando aplicável.

Na Scorretless, acessamos apenas dados disponíveis ao público, enquanto cumprem estritamente as leis, regulamentos e políticas de privacidade do site aplicáveis. O conteúdo deste blog é apenas para fins de demonstração e não envolve atividades ilegais ou infratoras. Não temos garantias e negamos toda a responsabilidade pelo uso de informações deste blog ou links de terceiros. Antes de se envolver em qualquer atividade de raspagem, consulte seu consultor jurídico e revise os termos de serviço do site de destino ou obtenha as permissões necessárias.

Artigos mais populares

Catálogo