Como Raspagem de Shadow DOM Com Playwright e Scrapeless
Lead Scraping Automation Engineer
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.querySelectorporque 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.
playwright1.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_KEYpara 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
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
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
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
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
plain_match = page.evaluate(
"document.querySelector('popup-info .info') !== null"
)
print("consulta do documento encontrou nó interno:", plain_match)
text
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
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
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
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.



