Geonode logo
Geonode Team

Geonode Team

Atualizado: 7 de outubro de 2026

Publicado: 2 de setembro de 2026

Seletores XPath vs. CSS: Guia com exemplos

Os seletores CSS são mais curtos e mais legíveis. O XPath é mais versátil. Esse resumo é preciso, mas, por si só, inútil, porque a diferença em termos de versatilidade é menor do que era, ao passo que a diferença em termos de legibilidade não o é. O que importa é saber o que cada um não consegue fazer especificamente, e existem apenas cerca de quatro casos desse tipo em cada lado. Este guia apresenta essa lista, além de explicar como a chegada do `:has()` alterou o panorama e o que não alterou.

O nosso interesse neste assunto é pequeno, mas vale a pena referi-lo: somos a Geonode e vendemos proxies a pessoas que realizam trabalhos de extração de dados, pelo que os seletores surgem constantemente nas conversas de apoio ao cliente. O que vale a pena referir antes da comparação é que nenhuma das opções influencia o facto de ser bloqueado ou não. Uma questão relacionada com o seletor e uma questão relacionada com a rede parecem idênticas à primeira vista — ambas resultam em «o meu scraper deixou de devolver dados» — mas não têm nada em comum. Se a página chegou intacta e a sua expressão não encontrou nada que correspondesse, trata-se de um problema do seletor e nenhuma decisão relativa à infraestrutura irá resolvê-lo.

A versão resumida

CSSXPath
Selecionar por classe, id, atributoSim, de forma simplesSim, de forma pouco prática
Relações de descendência e filiaçãoSimSim
Selecionar por conteúdo de textoNãoSim
Navegar para o pai ou antepassadoEm parte, através de :has()Sim, diretamente
Navegar para o irmão anteriorEm parte, através de :has()Sim, diretamente
Seleção posicionalFamília «:nth-child()»«position()», «last()», predicados
Funções de cadeia de caracteresNãoSim
Consultar XML com namespacesNãoSim
LegibilidadeMelhorPior
Ferramentas e suporte do navegadorUniversalUniversal para a versão 1.0

Duas linhas representam a maior parte do peso. O CSS não consegue selecionar com base no conteúdo de texto de todo, o que o exclui do padrão de extração mais comum — encontrar um valor pela etiqueta ao seu lado. E o XPath é mais difícil de ler, o que importa mais do que as pessoas admitem quando um colega tem de manter o seu código um ano depois.

O que o CSS faz melhor

A seleção de classes e atributos, e a diferença é enorme.

div.product-card
a[href^="https://"]
input[type="checkbox"]:checked
ul > li:first-child
section.content p:not(.footnote)

Os equivalentes em XPath são mais longos e, no caso das classes, verdadeiramente desagradáveis:

//div[contains(concat(' ', normalize-space(@class), ' '), ' product-card ')]
//a[starts-with(@href, 'https://')]
//ul/li[1]

O primeiro exemplo é a expressão típica para correspondência de classes, e existe porque @class

é uma única cadeia de caracteres separada por espaços e o XPath não reconhece tokens dentro dela. Um contains(@class, 'product-card')

simples também corresponde a product-card-large

e old-product-card

, pelo que a versão com espaços de preenchimento é a correta. Tem também quatro vezes o comprimento de div.product-card

e é consideravelmente mais difícil de analisar.

Se os teus critérios de seleção forem classes, IDs, atributos e relações estruturais, usa CSS. Isto abrange a grande maioria dos trabalhos reais de seleção, e escolher o XPath para isso significa pagar um custo em termos de legibilidade por funcionalidades que não está a utilizar.

O CSS também dispõe de melhores ferramentas. O inspetor de elementos de todos os navegadores produz seletores CSS de forma nativa, a maioria das estruturas de teste utiliza-os por predefinição e document.querySelectorAll

está disponível em todo o lado sem necessidade de ferramentas auxiliares.

O que o XPath faz melhor

Correspondência de texto, algo que o CSS não consegue fazer de todo:

//button[normalize-space()='Continue']
//a[contains(., 'Download')]
//dt[normalize-space()='Price']/following-sibling::dd[1]

Esse último padrão — encontrar a etiqueta e obter o valor adjacente — é a pedra angular da extração de páginas estruturadas, e não existe nenhuma expressão CSS para o efeito, porque o CSS não tem acesso ao conteúdo de texto. É precisamente esta capacidade que faz com que o XPath continue a ser utilizado na extração de bases de código que, de outra forma, utilizariam CSS em toda a sua extensão.

Navegação por antepassados:

//span[@class='price']/ancestor::div[contains(@class,'card')][1]

Subir a partir de um valor até ao contentor que o contém. :has()

oferece ao CSS uma forma de o fazer, com as limitações abordadas abaixo.

Lógica posicional relativa ao conteúdo:

//h2[normalize-space()='Specifications']/following-sibling::table[1]

A primeira tabela após um título específico. :nth-child()

conta posições entre elementos irmãos; não consegue expressar «após o elemento cujo texto é X».

Funções de cadeia de caracteres. normalize-space()

, substring-before()

, translate()

e as restantes permitem-lhe realizar operações dentro da expressão. normalize-space()

, em particular, é quase essencial, uma vez que o HTML real é formatado e uma comparação exata de texto com "\n In stock\n"

irá falhar.

XML com namespaces. Se estiver a consultar XML em vez de HTML — um mapa do site, um feed RSS, uma resposta SOAP —, o CSS não é a ferramenta adequada. O XPath foi concebido para isso, e o tratamento de namespaces faz parte da sua conceção.

Como o :has() mudou as coisas

A evolução mais significativa nesta comparação, e muitos artigos são anteriores a ela.

A documentação da MDN descreve o seletor :has() como representando «um elemento se qualquer um dos seletores relativos que são passados como argumento corresponder a pelo menos um elemento quando ancorado a este elemento», proporcionando «uma forma de selecionar um elemento pai ou um elemento irmão anterior em relação a um elemento de referência». O seu estado de referência é «amplamente disponível», sendo suportado em todos os navegadores desde dezembro de 2023.

Assim, o CSS pode agora expressar coisas que anteriormente não conseguia:

div.card:has(span.sold-out)        /* a card containing a sold-out marker */
h1:has(+ p)                        /* an h1 immediately followed by a p */
li:has(~ li.active)                /* an li with a later active sibling */
tr:has(td.error)                   /* a row containing an error cell */

Isto abrange uma parte significativa daquilo para que as pessoas precisavam anteriormente do XPath — seleção de elementos pai e condicionamento a um elemento irmão.

Estão documentadas três limitações que vale a pena conhecer. :has() «não pode ser aninhado dentro de outro :has()». Os pseudo-elementos «não são seletores válidos dentro de :has()» e não são âncoras válidas para o mesmo. E a sua especificidade é «a especificidade do seletor mais específico nos seus argumentos», correspondendo ao comportamento de :is() e :not().

O limite mais importante para a extração não consta dessa lista: :has() continua a não conseguir corresponder a texto. div:has(span) funciona; div:has(span:contains('Price')) não existe, porque :contains() não é um seletor CSS padrão. Foi proposto e abandonado, e os navegadores não o implementam. Assim, o padrão rótulo-valor permanece no domínio do XPath, independentemente de :has().

Traduções lado a lado

A forma mais rápida de compreender as vantagens e desvantagens é ver a mesma intenção expressa das duas formas.

IntençãoCSSXPath
Elemento com uma classediv.card//div[contains(concat(' ',normalize-space(@class),' '),' card ')]
Elemento com um id#main//*[@id='main']
Atributo existea[href]//a[@href]
Atributo começa pora[href^="/docs"]//a[starts-with(@href,'/docs')]
Atributo contéma[href*="pdf"]//a[contains(@href,'pdf')]
Filho diretoul > li//ul/li
Qualquer descendentediv p//div//p
Primeiro filholi:first-child//li[1]
Último filholi:last-child//li[last()]
Enésimo filholi:nth-child(3)//li[3]
Próximo irmãoh2 + p//h2/following-sibling::p[1]
Qualquer irmão posteriorh2 ~ p//h2/following-sibling::p
Negaçãop:not(.footnote)//p[not(contains(@class,'footnote'))]
Pai de uma correspondênciadiv:has(> span.price)//span[contains(@class,'price')]/..
Contém textonão é possível//p[contains(., 'Price')]
Texto exatonão é possível//p[normalize-space()='Price']
Valor junto a um rótulonão é possível//dt[normalize-space()='Price']/following-sibling::dd[1]

Ao ler esta tabela, o padrão é claro. Para tudo o que está acima da linha «pai», o CSS é mais curto e mais claro, e optar pelo XPath é optar pela verbosidade sem qualquer vantagem. Para as três linhas na parte inferior, não existe sequer uma coluna de CSS.

Duas traduções merecem uma segunda análise. A linha da classe apresenta o contraste mais acentuado em toda a comparação — nove caracteres contra cerca de setenta — e a versão em XPath não é excessiva por si só, porque a forma abreviada contains(@class,'card') corresponde, de facto, a mais do que card-large e discard. E a linha «primeiro filho» esconde uma armadilha: li:first-child e //li[1] coincidem aqui, mas //li[1] é um predicado aplicado por pai, pelo que seleciona o primeiro li em todas as listas do documento. O CSS é inequívoco; o XPath necessita de (//li)[1] se se pretender apenas um.

Um exemplo misto realista

Eis como isto se traduz na prática, ao extrair uma página de produto.

# CSS for the structural work — clear and adequate
cards = tree.cssselect('div.product-grid > article.product-card')

for card in cards:
    name  = card.cssselect('h3.product-name')[0].text_content().strip()
    image = card.cssselect('img.product-image')[0].get('src')

    # XPath for the label-value pairs, which CSS cannot express
    price = card.xpath(
        ".//dt[normalize-space()='Price']/following-sibling::dd[1]"
    )[0].text_content().strip()

    stock = card.xpath(
        ".//span[contains(., 'in stock') or contains(., 'out of stock')]"
    )

Há três aspetos que vale a pena copiar.

O «.» inicial em cada expressão XPath. «.//dt» pesquisa dentro do cartão atual; «//dt» pesquisaria todo o documento a partir da raiz, devolvendo o primeiro rótulo correspondente na página para cada cartão. Este é um dos erros mais comuns no código de extração mista e produz o mesmo valor para todos os registos — plausível, uniforme e errado.

CSS sempre que o CSS for suficiente. A grelha e a seleção de cartões, o título, a imagem. Escrever isso em XPath aumentaria o comprimento e diminuiria a clareza.

XPath apenas quando necessário. O preço é identificado pelo seu rótulo e o estado do stock pelo seu texto. Nenhum deles é expressável em CSS, e ambos são a razão pela qual o XPath se encontra no ficheiro.

O resultado é um scraper em que cada expressão é tão curta quanto possível, e um leitor consegue ver à primeira vista quais as partes que dependem da estrutura e quais as que dependem do conteúdo — o que também constitui um mapa do que irá deixar de funcionar quando o site for alterado.

Desempenho

Normalmente irrelevante, ocasionalmente decisivo.

O CSS é, em geral, mais rápido nos navegadores, porque os motores otimizam fortemente a correspondência de seletores — trata-se de uma etapa crítica no processo de renderização. O XPath segue um modelo de avaliação mais geral.

A diferença raramente é relevante na escala em que a maioria das pessoas trabalha. Selecionar a partir de uma única página algumas centenas de vezes é insignificante em ambos os casos; a solicitação de rede domina por ordens de magnitude.

Onde é que faz diferença:

Os eixos preceding e following são genuinamente dispendiosos. Eles percorrem todo o documento antes ou depois do nó de contexto. Dentro de um ciclo que abrange muitos nós, isso torna-se quadrático. Se uma expressão XPath for visivelmente lenta, verifique se utiliza algum desses — preceding-sibling e following-sibling estão limitados pelo número de nós irmãos e funcionam bem.

// no início de uma expressão aninhada volta a pesquisar a partir da raiz. //div//span é mais dispendioso do que parece, e .//span dentro de um ciclo sobre divs é o que normalmente se pretende.

:has() pode ser dispendioso em documentos de grande dimensão, uma vez que o motor tem de avaliar o seletor interno para cada candidato. Adequado para extração; algo a ter em conta numa folha de estilo aplicada a uma página de grande dimensão.

Para a análise do lado do servidor com lxml ou similar, a diferença é suficientemente pequena para que a legibilidade seja o fator decisivo.

Problemas de disponibilidade e versão

A armadilha que leva à situação em que «funciona no testador online, mas não no meu código».

Os navegadores implementam o XPath 1.0 através de document.evaluate, e o Selenium utiliza o motor do navegador. As funcionalidades do XPath 2.0 e 3.1 — matches(), replace(), lower-case(), ends-with(), sequências — não estão disponíveis nesse contexto. Um fragmento de código que utilize qualquer uma delas irá falhar.

O lxml implementa o XPath 1.0 na sua API comum, além de extensões EXSLT, incluindo re:test() para expressões regulares. Assim, uma expressão baseada em expressões regulares que funcione em Python não funcionará no Selenium.

O suporte a CSS nas bibliotecas de análise é normalmente feito através de uma camada de tradução que converte CSS para XPath internamente. Isso funciona bem para os seletores comuns e menos bem para os mais recentes — o suporte a :has() em analisadores do lado do servidor varia consideravelmente, e um seletor que funciona num navegador pode não funcionar no seu scraper.

Regra prática: teste os seus seletores no ambiente em que serão executados, e não na consola de um navegador ou numa ferramenta online. As duas surpresas mais comuns são as funções do XPath 2.0 que falham no Selenium e os seletores do tipo «:has()» que falham num analisador de Python.

Escolher na prática

Um procedimento de decisão que resolve praticamente todos os casos.

Comece com CSS. É mais legível, dispõe de melhores ferramentas e é adequado para a seleção por classe, id, atributo e estrutura — o que corresponde à maioria das seleções.

Mude para o XPath quando precisar de fazer a correspondência com base no texto. Esta é a principal razão e é decisiva: nenhuma construção CSS lê conteúdo de texto.

Mude para o XPath para navegação por antepassados para além do que o :has() abrange, particularmente quando precisar de um antepassado específico vários níveis acima, em vez de uma correspondência condicional num pai conhecido.

Mude para o XPath para XML. Os namespaces e as operações relacionadas com a ordem dos documentos são exatamente para isso que foi concebido.

Utilize ambos numa única base de código. Isto é normal e sensato, em vez de ser um compromisso. Um scraper que utilize CSS para os 90% mais simples e o XPath para os 10% mais complexos é mais fácil de manter do que um que se limite a apenas uma das opções.

E um hábito que vale mais do que qualquer uma das escolhas: verifique se existem dados estruturados incorporados antes mesmo de escrever um seletor. Muitas páginas contêm JSON-LD num bloco <script type="application/ld+json"> porque este alimenta funcionalidades de pesquisa, e a análise desses dados é significativamente mais estável do que a análise de marcação renderizada. Resiste a reformulações que quebram todos os seletores da página.

O que nenhum dos dois resolve

Ambos são frágeis da mesma forma, e é esta a parte que custa tempo real.

Os seletores estruturais falham silenciosamente. Alguém insere uma coluna e td[3] passa a corresponder a uma célula diferente. Não é gerado qualquer erro; os seus dados estão errados sem que se perceba. Baseie-se em texto ou identificadores sempre que possível e verifique a forma do que extrai — se um preço deve parecer um preço, verifique-o.

Nenhum deles lida com conteúdo renderizado pelo cliente. Se a marcação for montada por JavaScript após o carregamento, ambos não encontram nada no HTML inicial. Esse é um problema de obtenção de dados que requer um navegador ou a API subjacente, não um problema do seletor.

Nenhum deles sobrevive a uma reformulação por si só. A solução é manter os seletores num único local, para que a atualização após uma alteração demore uma hora em vez de um dia, e armazenar o HTML que analisou para poder comparar o antigo com o novo quando algo deixar de funcionar.

E nenhum deles é afetado pela forma como a página foi obtida. É aqui que entramos: se o documento estiver intacto e a sua expressão não corresponder a nada, nenhuma alteração na infraestrutura altera a resposta.

Perguntas frequentes

O XPath é melhor do que os seletores CSS?

Nenhum dos dois é melhor em geral. O XPath é mais versátil — consegue corresponder texto, navegar até antepassados e utilizar funções de cadeia de caracteres. O CSS é mais legível e tem melhor suporte por parte das ferramentas. Utilize o CSS por predefinição e o XPath para os casos específicos que o CSS não consegue expressar.

Os seletores CSS podem selecionar por texto?

Não. Não existe um seletor CSS padrão para conteúdo de texto — o seletor «:contains()» foi proposto, mas nunca foi adotado, e os navegadores não o implementam. Esta é a maior lacuna em termos de funcionalidades e a principal razão pela qual o XPath continua a ser utilizado em trabalhos de extração.

O :has() substitui o XPath?

Em parte. Permite a seleção de elementos pai e a correspondência condicional entre irmãos no CSS, funcionalidades que anteriormente só eram possíveis com o XPath. Não adiciona a correspondência de texto, pelo que o padrão «rótulo-valor» continua a necessitar do XPath. Além disso, não permite aninhamento e o suporte nas bibliotecas de análise do lado do servidor varia.

O que é mais rápido, o XPath ou o CSS?

O CSS é geralmente mais rápido nos navegadores porque a correspondência de seletores é otimizada para a renderização. A diferença é normalmente irrelevante quando comparada com o tempo de rede. Onde o XPath se torna verdadeiramente lento é nos eixos preceding e following, que percorrem todo o documento.

Posso utilizar o XPath 2.0 no Selenium?

Não. Os navegadores implementam o XPath 1.0 através de document.evaluate, e o Selenium utiliza o motor do navegador. Funções como matches(), lower-case() e ends-with() não estão disponíveis. Esta é a razão habitual pela qual um fragmento de código funciona num testador online e falha num conjunto de testes.

Como seleciono um elemento pai?

No XPath, parent:: ou ... No CSS, :has() fornece uma forma condicional — div:has(> span.price) seleciona o div em vez do span. Para um antepassado específico vários níveis acima, o eixo ancestor:: do XPath é mais direto.

Devo usar CSS ou XPath para web scraping?

Ambos, na mesma base de código. CSS para a seleção de classes, IDs e atributos, o que representa a maior parte. XPath quando for necessário corresponder texto — encontrar um valor pela sua etiqueta é o caso típico e não tem equivalente em CSS.

Qual é mais estável quando um site sofre alterações?

Nenhuma das duas, por natureza — a estabilidade depende do que se utiliza como âncora, e não da linguagem utilizada. A âncora em texto ou num atributo «data-*» resiste a uma reformulação; a âncora na posição não resiste a nada. Ambas as linguagens permitem fazer qualquer uma das opções.

Conclusão

A comparação resume-se a duas assimetrias. O CSS não consegue «ler» texto e o XPath é mais difícil de ler. Tudo o resto são pormenores.

Isso torna a regra de trabalho simples: opte por defeito pelo CSS, porque a maioria das seleções é feita por classe, id ou atributo e o CSS expressa isso de forma clara. Recorra ao XPath no momento específico em que precisar de algo que só ele oferece — acima de tudo, a correspondência de texto, seguida da navegação por antepassados e das funções de cadeias de caracteres. É normal misturá-los, e uma base de código que utilize cada um naquilo em que se destaca é mais fácil de manter do que uma que tenha escolhido apenas um lado. O

:has() reduziu significativamente a diferença e vale a pena adotá-lo onde for adequado: seleção de pais e condições de irmãos em CSS simples, amplamente disponível desde o final de 2023. Tenha apenas em atenção que não lida com texto e que o suporte dos analisadores do lado do servidor é menos uniforme do que o suporte dos navegadores.

Por isso, verifique o seu ambiente antes de confiar em qualquer expressão. O XPath 1.0 nos navegadores e no Selenium, os extras do EXSLT no lxml, o suporte variável a :has() nas bibliotecas de análise — a surpresa mais comum com os seletores não é um erro de sintaxe, mas sim uma funcionalidade que existe noutro local que não aquele onde a está a executar.