🎯 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

Como Raspagem de Shadow DOM Com Playwright e Scrapeless

Daniel Kim
Daniel Kim

Lead Scraping Automation Engineer

30-Jul-2026

TL;DR:

  • O Shadow DOM pode exibir texto que não existe nos filhos light DOM do elemento host. O JavaScript pode criar o conteúdo visível dentro de uma raiz de sombra após o carregamento do elemento personalizado.
  • Localizadores CSS e de texto do Playwright podem atravessar raízes de sombra abertas. O mesmo seletor falha com document.querySelector porque consultas DOM em nível de documento param no limite da sombra.
  • Raízes de sombra fechadas e XPath requerem expectativas diferentes. Localizadores do Playwright não atravessam raízes fechadas, e seletores XPath não suportam travessia automática do Shadow DOM.
  • A lógica de extração do Shadow DOM é independente do transporte do navegador. Verifique o fluxo de trabalho localmente e, em seguida, reutilize os mesmos localizadores por meio da conexão CDP Scrapeless quando um navegador hospedado ou saída gerenciada for necessária.
  • Gratuito para começar. Novas contas do Scrapeless incluem tempo de execução gratuito do Scraping Browser—inscreva-se em app.scrapeless.com.

Um componente Shadow DOM pode exibir texto que está ausente dos filhos light-DOM do elemento host. Este guia usa o Playwright para atravessar uma raiz de sombra aberta real, mostra por que um seletor em nível de documento não retorna nada e separa essa lógica de extração da conexão do Scrapeless Scraping Browser que executa o mesmo código em um navegador em nuvem.

O que o Shadow DOM muda para um Scraper

O Shadow DOM dá a um elemento personalizado uma subárvore encapsulada. O documento principal da página pode ver a tag host, mas as APIs DOM ordinárias não cruzam automaticamente daquele host para sua raiz de sombra.

Na demo pública do MDN usada abaixo, a fonte contém um host <popup-info> com um atributo data-text. Um script diferido atualiza essa tag, chama attachShadow({ mode: "open" }) e cria um span interno .info. O navegador exibe o texto de ajuda desse span. O HTML inicial não contém o span de forma alguma.

O guia Shadow DOM do MDN define a fronteira e a diferença entre raízes abertas e fechadas. A distinção é importante: uma raiz aberta expõe element.shadowRoot; uma raiz fechada retorna null através dessa API.

Por que usar o Playwright com Scrapeless

Os localizadores do Playwright atravessam raízes de sombra abertas para busca normal de CSS e texto. A documentação de localizadores do Playwright afirma duas exceções: o XPath não atravessa raízes de sombra, e raízes em modo fechado não são suportadas.

O código de travessia não depende de onde o Chromium está sendo executado. Um navegador local é suficiente para uma demonstração pública; o Scrapeless Scraping Browser se torna útil quando o alvo precisa de um navegador hospedado, impressão digital gerenciada, saída residencial ou uma sessão em nuvem persistente. O Playwright acessa esse navegador através da API connect_over_cdp.

Pré-requisitos

  • Python 3.10 ou versão posterior.
  • playwright 1.59.0 ou uma versão compatível atual.
  • Um runtime local do Chrome/Chromium para a queda executável totalmente abaixo.
  • Uma conta Scrapeless financiada e SCRAPELESS_API_KEY para a conexão em nuvem. A conta de verificação retornou o código 14500 para novas sessões de navegador, portanto, o bloco de conexão em nuvem está marcado como uma lacuna de pré-requisito, em vez de ser mostrado como bem-sucedido.

Instalação

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

O host de verificação já possui o Google Chrome em /usr/bin/google-chrome. Se o seu computador não tiver, instale um Chromium gerenciado pelo Playwright com o comando normal de configuração do navegador do projeto.

Conectar ao Scrapeless Scraping Browser

Nota: Essa conexão requer saldo de navegador Scrapeless financiado. A conta usada para a verificação final retornou saldo insuficiente, por favor, recarregue primeiro; execute com sua própria chave financiada. Os blocos de travessia após esta seção foram executados completamente no Chrome local contra a mesma página pública.

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]
    # Execute os passos de extração abaixo com esta página.
    browser.close()

A documentação do desenvolvedor do Scrapeless é a fonte atual para parâmetros de conexão. Uma vez que page exista, a busca no Shadow DOM é padrão do Playwright.

Inspecione o Host e Sua Raiz Aberta

O demo do componente MDN é pequeno e público:

python Copy
TARGET_URL = (
    "https://mdn.github.io/web-components-examples/"
    "popup-info-box-web-component/"
)

page.goto(TARGET_URL, wait_until="domcontentloaded")
page.wait_for_selector("popup-info .info")

host = page.locator("popup-info")
print("host light-DOM text:", repr(host.inner_text()))
print("modo shadow:", host.evaluate("element => element.shadowRoot.mode"))
text Copy
host light-DOM text: ''
modo shadow: open

O texto vazio do host não é uma evidência de que o componente não contém dados. Isso diz que o host não possui texto comum de filho. O nó real .info está abaixo do shadow root aberto criado após a atualização do elemento personalizado.

Prove que o Seletor de Documento Para na Fronteira

Execute uma consulta DOM simples do navegador a partir do documento:

python Copy
plain_match = page.evaluate(
    "document.querySelector('popup-info .info') !== null"
)
print("consulta do documento encontrou nó interno:", plain_match)
text Copy
consulta do documento encontrou nó interno: False

O seletor é um CSS válido. A falha vem do escopo: document.querySelector pesquisa a árvore do documento e não desce na árvore de sombra do host.

Deixe um Localizador Playwright Perfurar a Raiz Aberta

A cadeia de localizador comum do Playwright atravessa a raiz aberta:

python Copy
info = page.locator("popup-info .info")
record = {
    "host": "popup-info",
    "shadow_mode": host.evaluate("element => element.shadowRoot.mode"),
    "text": info.inner_text(),
    "source_url": page.url,
}

print("matches internos:", info.count())
print("texto de ajuda:", record["text"])
print("número de caracteres do texto:", len(record["text"]))
text Copy
matches internos: 1
texto de ajuda: Seu código de validação do cartão (CVC) é um recurso de segurança extra — ele é os últimos 3 ou 4 números na parte de trás do seu cartão.
número de caracteres do texto: 118

Esse texto é o valor real data-text do componente copiado no span do shadow-root. A contagem de caracteres inclui pontuação e espaços, então é uma boa proteção contra a extração acidental de um elemento vazio ou o texto alternativo do ícone.

Pronto para mover o mesmo localizador para um navegador hospedado? Crie uma conta gratuita no Scrapeless e mantenha cada linha após a criação do navegador inalterada.

Script de Verificação Local Completo

Este script executa todo o diagnóstico no Chrome local. Substituir launch(...) pela conexão CDP acima o move para o Scrapeless sem mudar a lógica de extração:

python Copy
from playwright.sync_api import sync_playwright

TARGET_URL = (
    "https://mdn.github.io/web-components-examples/"
    "popup-info-box-web-component/"
)

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")
    page.wait_for_selector("popup-info .info")

    host = page.locator("popup-info")
    info = page.locator("popup-info .info")
    host_text = host.inner_text()
    shadow_mode = host.evaluate("element => element.shadowRoot.mode")
    plain_match = page.evaluate(
        "document.querySelector('popup-info .info') !== null"
    )
    help_text = info.inner_text()

    assert shadow_mode == "open"
    assert not plain_match
    assert "código de validação do cartão" in help_text

    print("título da página:", page.title())
    print("host light-DOM text:", repr(host_text))
    print("modo shadow:", shadow_mode)
    print("consulta do documento encontrou nó interno:", plain_match)
    print("matches internos:", info.count())
    print("texto de ajuda:", help_text)
    print("número de caracteres do texto:", len(help_text))
    browser.close()

A execução prova a fronteira em vez de apenas mostrar um localizador bem-sucedido: o texto light-DOM está vazio, o seletor do documento é falso, a raiz relata open e o Playwright retorna uma string de ajuda de 118 caracteres.

O que as Raízes Abertas e Fechadas Significam

Raiz aberta

attachShadow({mode: "open"}) deixa host.shadowRoot disponível. Localizadores do Playwright podem percorrê-lo com seletores de CSS/texto. Raízes abertas aninhadas são tratadas da mesma forma, embora cada componente possa carregar em um cronograma diferente.

Raiz fechada

attachShadow({mode: "closed"}) faz host.shadowRoot retornar null. Os localizadores normais do Playwright não a atravessam. Não apresente injeção de script ou instrumentação de navegador como uma solução universal: uma raiz fechada é uma fronteira de encapsulamento explícita, e mudar o código da página pode alterar o comportamento que você pretendia medir.

Conteúdo Slotado

Um <slot> exibe filhos light-DOM dentro de um layout de árvore de sombra. Os nós atribuídos ainda pertencem ao DOM de luz do host, então sua propriedade e comportamento do seletor diferem de elementos criados dentro da raiz de sombra. Inspecione o DOM ao vivo antes de decidir qual lado possui os dados.

Modos Comuns de Falha do Shadow DOM

O host existe, mas o localizador interno expira

O elemento personalizado pode não ter sido atualizado ainda, seu JavaScript pode ter falhado ou o seletor pode direcionar para uma raiz fechada. Espere pelo elemento interno como o verdadeiro marcador de prontidão e inspecione os erros do console antes de mudar os seletores.

XPath não encontra nada

Use um localizador CSS ou de texto. A travessia de sombra documentada do Playwright não se aplica a seletores XPath.

Um componente aninhado retorna texto parcial

Localize o elemento estável mais profundo em vez de chamar inner_text() em um host grande. Isso evita a mistura de texto de slots, rótulos visualmente ocultos e saída de componentes aninhados.

O HTML fonte não tem nó interno

Isso é normal quando o JavaScript constrói a árvore de sombra. Use um navegador. Um parser HTTP apenas vê a resposta da fonte e não pode observar uma subárvore criada após o carregamento.

Conclusão

A raspagem de Shadow DOM é um problema de escopo antes de ser um problema de seletor. Comprove qual árvore possui o nó, espere o elemento personalizado ser atualizado e use os localizadores CSS/texto do Playwright para raízes abertas. Mantenha a fronteira da raiz fechada honesta. O transporte do navegador é separado: execute localmente para uma superfície de teste pública, depois mude a criação do navegador para o endpoint CDP do Scrapeless quando o alvo precisar de um navegador anti-detecção hospedado ou saída gerenciada.

Comece com Scrapeless, revise os preços do Scrapeless e leia o que o Protocolo DevTools do Chrome controla para a arquitetura CDP circundante.

FAQ

P: O Playwright pode raspar uma raiz de Shadow DOM aberta?

Sim. Os localizadores CSS e de texto do Playwright ultrapassam raízes de sombra abertas, então um localizador como page.locator("popup-info .info") pode alcançar o nó interno.

P: Por que document.querySelector falha no mesmo seletor?

document.querySelector pesquisa na árvore do documento e para no host da sombra. Você deve consultar através da raiz aberta você mesmo ou usar um localizador do Playwright que realize essa travessia.

P: O Playwright pode ultrapassar uma raiz de sombra fechada?

Não, não através de seu comportamento normal de localização. Uma raiz fechada retorna null de host.shadowRoot e é uma fronteira documentada como não suportada.

P: O XPath funciona dentro do Shadow DOM?

Não. A orientação oficial de localizadores do Playwright diz que seletores XPath não ultrapassam raízes de sombra; use localizadores CSS ou de texto.

P: Como eu lido com raízes de sombra abertas aninhadas?

Encadeie localizadores em direção ao elemento interno estável e espere por esse elemento. O Playwright pode cruzar várias raízes abertas, mas cada elemento personalizado pode ser atualizado de forma assíncrona.

P: Preciso de um navegador para raspagem de Shadow DOM?

Sim, quando o JavaScript cria a árvore de sombra. Uma resposta HTTP simples contém a marcação e scripts do host, não a subárvore ao vivo produzida após a execução.

P: A raspagem de Shadow DOM é legal?

O mecanismo DOM não decide permissão. Revise os termos do alvo e diretivas de robôs, use dados públicos, limite o volume de solicitações e busque aconselhamento legal para coleta sensível ou comercial.

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