Geonode logo
Geonode Team

Geonode Team

Atualizado: 7 de outubro de 2026

Publicado: 2 de setembro de 2026

XPath contains(): Um guia completo com exemplos

`contains()` é a função XPath mais utilizada e menos compreendida. Parece um teste de subcadeia e só se comporta como tal quando lhe é fornecida uma cadeia de caracteres. Dado um conjunto de nós — que é o que a maioria das expressões produz —, utiliza silenciosamente apenas o primeiro nó e ignora os restantes. Este guia aborda essa armadilha, a expressão de correspondência de classes que ninguém acerta à primeira, e o tratamento de espaços em branco e maiúsculas/minúsculas que o XPath 1.0 torna mais difícil do que deveria ser.

Uma breve nota sobre o motivo pelo qual estamos a escrever isto. Somos a Geonode; vendemos proxies a pessoas que extraem dados, pelo que recebemos constantemente perguntas sobre seletores. A advertência relevante é curta: os erros nos seletores e os problemas com os proxies produzem sintomas completamente diferentes, e confundi-los faz perder horas. Um pedido bloqueado devolve uma página de desafio ou um estado de erro. Um contains() com falha devolve uma página perfeitamente válida e um resultado vazio. Se o HTML estiver presente e a sua expressão não encontrar nada, este artigo é o local certo e a rede está a funcionar bem.

A própria função

A especificação do XPath 1.0 é sucinta:

A função contains devolve «true» se a cadeia de caracteres do primeiro argumento contiver a cadeia de caracteres do segundo argumento; caso contrário, devolve «false».

Ambos os argumentos são cadeias de caracteres. Dois argumentos, um resultado booleano, distingue maiúsculas de minúsculas, sem caracteres curinga, sem expressões regulares.

//a[contains(@href, 'download')]
//div[contains(@class, 'product')]
//p[contains(text(), 'Price')]

O comportamento interessante reside inteiramente no que acontece quando o argumento fornecido não é uma cadeia de caracteres, o que acontece na maioria das vezes.

A armadilha do conjunto de nós: contains()

só vê o primeiro nó Este é o bug que faz com que «o meu XPath funcione em algumas páginas e noutras não», e a especificação explica-o com precisão:

Um conjunto de nós é convertido numa cadeia de caracteres, devolvendo o valor da cadeia de caracteres do nó que, no conjunto de nós, ocupa a primeira posição na ordem do documento. Se o conjunto de nós estiver vazio, é devolvida uma cadeia de caracteres vazia.

Assim, quando escreve algo que produz um conjunto de nós e o passa para contains() , o XPath descarta silenciosamente tudo, exceto o primeiro nó.

<div>
  <p>Introduction</p>
  <p>Price: £42</p>
  <p>Availability</p>
</div>
contains(//p, 'Price')      → false

Falso, porque //p é um conjunto de três nós; a conversão para cadeia de caracteres retém o primeiro — «Introduction» — e este não contém «Price». Os outros dois parágrafos nunca foram considerados.

A solução consiste em fazer com que o predicado se aplique a cada nó individualmente, em vez de converter um conjunto:

//p[contains(., 'Price')]   → the second paragraph

Aqui, contains() é avaliado uma vez para cada p , sendo . esse nó específico. Esta é a diferença entre perguntar «o conjunto contém isto?» e «quais os membros do conjunto que contêm isto?», sendo que normalmente é apenas a segunda opção que se pretende.

A mesma armadilha surge com text() , que também é um conjunto de nós:

//div[contains(text(), 'Price')]

text() devolve todos os filhos diretos do nó de texto, e a conversão para string seleciona o primeiro. Se o elemento tiver texto dividido por vários nós — o que acontece sempre que há marcação aninhada —, estará a testar apenas o primeiro fragmento.

.

versus text()

: a outra metade do mesmo problema A especificação define o valor de cadeia de caracteres de um elemento como:

a concatenação dos valores de cadeia de caracteres de todos os nós de texto descendentes do nó do elemento, na ordem do documento

Essa é a diferença crucial entre as duas formas.

<p>Total: <strong>£42.00</strong> including VAT</p>
contains(., '£42.00')          → true   (all descendant text, concatenated)
contains(text(), '£42.00')     → false  (first direct text node only: "Total: ")

.

abrange elementos aninhados. text()

considera apenas os filhos diretos e apenas o primeiro deles.

**Utilize .

por predefinição.** Corresponde ao que um leitor consideraria «o texto deste elemento» e mantém-se inalterado mesmo com alterações na marcação, como, por exemplo, quando alguém envolve um valor num <span>

.

**Utilize text()

deliberadamente** quando pretender excluir especificamente conteúdo aninhado — por exemplo, para corresponder a um rótulo sem corresponder ao texto dentro de um crachá ou dica de ferramenta filho.

Para «qualquer um dos nós de texto contém isto», a forma correta aplica o predicado aos próprios nós de texto:

//div[text()[contains(., 'Price')]]

Detalhado e correto.

Espaços em branco: «normalize-space()» não é opcional

O HTML verdadeiro é apresentado de forma organizada, e os espaços em branco fazem parte do valor da cadeia de caracteres.

<td>
    In stock
</td>

O valor da cadeia de caracteres dessa célula é «"\n In stock\n"», pelo que uma comparação exata com «'In stock'» falha.

A especificação define a correção:

A função normalize-space devolve a cadeia de caracteres do argumento com os espaços em branco normalizados, removendo os espaços em branco iniciais e finais e substituindo sequências de caracteres de espaço em branco por um único espaço.

//td[normalize-space() = 'In stock']
//td[contains(normalize-space(), 'In stock')]

Note-se que normalize-space() sem nenhum argumento opera sobre o nó de contexto, que é o que se pretende dentro de um predicado.

No caso específico de contains(), os espaços em branco têm menos importância nas extremidades e uma grande importância no meio. Uma pesquisa por 'In stock' falha quando comparada com "In stock", a menos que se normalize primeiro. Se adotar apenas um hábito nos seus seletores, que seja o de envolver as comparações de texto em normalize-space().

Como fazer a correspondência de classes corretamente

O uso incorreto mais comum de ``contains()`

e aquele que tem maior probabilidade de estar errado sem que se perceba.

//div[contains(@class, 'btn')]

`

Isso corresponde a ``class="btn"`

. Também corresponde a ``class="btn-primary"

, ``class="unbtn"

e ``class="sidebar-btn-group"

. Como ``@class

é uma cadeia separada por um único espaço e ``contains()

` é um simples teste de subcadeia, não tem em conta os limites das palavras.

A forma correta consiste em preencher tanto o atributo como o alvo com espaços, de modo a que apenas tokens completos correspondam:

//div[contains(concat(' ', normalize-space(@class), ' '), ' btn ')]

Interprete-o assim: pegue no atributo da classe, normalize os seus espaços brancos, envolva-o em espaços para que cada token fique delimitado por espaços em ambos os lados e procure o alvo rodeado por espaços. class="btn-primary"

torna-se " btn-primary "

, o que não contém " btn "

. class="icon btn large"

torna-se " icon btn large "

, o que contém.

É feio. Mas também está correto, e é o resultado final de qualquer código de scraping maduro. Envolva-o numa função auxiliar:

def has_class(name):
    return (f"contains(concat(' ', normalize-space(@class), ' '), ' {name} ')")

Quando precisar de duas classes:

//div[contains(concat(' ', normalize-space(@class), ' '), ' btn ')
      and contains(concat(' ', normalize-space(@class), ' '), ' primary ')]

Nessa altura, um seletor CSS — div.btn.primary

— é significativamente mais legível e faz exatamente o que é necessário. Se estiver a fazer correspondências com base em classes e nada mais, use CSS. O XPath justifica a sua utilização quando é necessária a correspondência de texto ou a navegação para trás, não para as coisas que o CSS já faz bem. Comparámos os dois em XPath preceding-sibling.

Distinção entre maiúsculas e minúsculas e a solução alternativa «translate()»

A expressão «contains()» distingue entre maiúsculas e minúsculas, e o XPath 1.0 não dispõe de uma função «lower-case()». Os navegadores e o Selenium implementam o XPath 1.0, pelo que esta limitação está presente precisamente onde mais importa.

A solução alternativa utiliza translate(), definida como devolvendo «a string do primeiro argumento com as ocorrências de caracteres da string do segundo argumento substituídas pelo caractere na posição correspondente na string do terceiro argumento»:

//p[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                          'abcdefghijklmnopqrstuvwxyz'), 'price')]

Transliteração caractere a caractere, apenas ASCII. Não converterá caracteres acentuados para minúsculas, a menos que se alargue ambas as cadeias para os abranger, o que se torna rapidamente complicado.

Existem duas opções melhores, quando disponíveis:

Corresponder a uma subcadeia insensível a maiúsculas e minúsculas. Se a página disser «Price» ou «PRICE», mas nunca «price», corresponder a 'rice' é pouco elegante, mas funciona. Frequentemente, esta é a resposta mais pragmática.

Utilizar uma biblioteca com um XPath mais avançado. Analisadores do lado do servidor, como o lxml, suportam extensões EXSLT, incluindo re:test() para expressões regulares, que lidam com maiúsculas e minúsculas e muito mais. Os navegadores não o fazem, pelo que um fragmento de código que encontrou para o lxml pode falhar no Selenium precisamente por esta razão.

Se te vires a escrever um «translate()» extenso, isso é um sinal de que já ultrapassaste o XPath 1.0 para esta tarefa.

As funções de cadeias de caracteres relacionadas: «

contains()

» faz parte de um pequeno conjunto, e as outras são frequentemente mais precisas.

** «starts-with()

»** — «retorna true se a cadeia de caracteres do primeiro argumento começar com a cadeia de caracteres do segundo argumento». É mais específica do que «contains()

» e, consequentemente, menos propensa a correspondências excessivas:

//a[starts-with(@href, 'https://')]

Não existe «ends-with()

» no XPath 1.0. A solução alternativa utiliza substring()

e string-length()

, e é suficientemente incómoda para que, normalmente, seja preferível uma abordagem diferente.

**substring-before()

e substring-after()

** — o primeiro «retorna a substring da string do primeiro argumento que precede a primeira ocorrência da string do segundo argumento... ou a string vazia se a string do primeiro argumento não contiver a do segundo». Útil para dividir um valor dentro da expressão:

substring-after(//span[@class='price'], '£')

**normalize-space()

** — já abordado acima, e aquele que deve utilizar com mais frequência.

**translate()

** — conversão de maiúsculas/minúsculas e também remoção de caracteres, atribuindo-lhes o valor nulo:

translate(., ',', '')

**string-length()

** — filtragem de valores vazios ou truncados:

//td[string-length(normalize-space()) > 0]

É na combinação destas funções que reside o seu valor:

//tr[contains(normalize-space(td[1]), 'Weight')]/td[2]

A segunda célula de qualquer linha cuja primeira célula mencione «Weight», sem distinção entre maiúsculas e minúsculas.

Padrões que surgem constantemente

As expressões abaixo abrangem a maior parte do trabalho real de extração e vale a pena tê-las à mão.

Encontrar o valor ao lado de um rótulo. O requisito mais comum na extração de páginas estruturadas:

//dt[contains(normalize-space(), 'Price')]/following-sibling::dd[1]
//th[contains(normalize-space(), 'Weight')]/following-sibling::td[1]

Repare no [1]

— sem ele, following-sibling::td

devolve todas as células subsequentes na linha, e o seu código seleciona silenciosamente a primeira, enquanto o utilizador assume que era a única.

Encontrar um link pelo seu texto visível em vez do seu href:

//a[contains(normalize-space(), 'Download')]

Mais robusto do que a correspondência com o URL quando este é um identificador com hash, e mais frágil quando o site é traduzido. Escolha de acordo com o que muda com mais frequência.

Encontrar um contentor por algo que esteja no seu interior:

//div[contains(concat(' ', normalize-space(@class), ' '), ' card ')][.//span[contains(., 'Sold out')]]

Dois predicados em sequência: um cartão, contendo um span que mencione «Esgotado». Isto combina bem e é mais fácil de ler do que tentar expressá-lo numa única condição.

Excluir em vez de incluir. Muitas vezes, é a formulação mais clara:

//tr[not(contains(@class, 'header'))]
//li[not(contains(normalize-space(), 'Advertisement'))]

**Corresponder a um botão, quer seja um button

ou um a

:**

//*[self::button or self::a][contains(normalize-space(), 'Continue')]

Encontrar a linha que contém um valor e selecionar uma coluna diferente:

//tr[td[contains(normalize-space(), 'SKU-1234')]]/td[3]

Leia de fora para dentro: linhas que tenham uma célula que mencione o SKU e, em seguida, a terceira célula dessa linha. Este é o padrão de pesquisa em tabela, e resiste à reordenação de colunas muito melhor do que um índice absoluto em toda a tabela.

Proteja-se contra correspondências vazias. Um predicado que filtra os espaços em branco não tem qualquer custo e evita uma série de confusões a jusante:

//td[string-length(normalize-space()) > 0][contains(., 'Ltd')]

Quando «contains()» não é a ferramenta adequada

Quando se pretende igualdade. «contains(., 'Price')» também corresponde a «Historic Price» e «Price excluding VAT». Se pretender exatamente esse rótulo, utilize «normalize-space() = 'Price'». A correspondência excessiva ocorre de forma silenciosa — o seu código seleciona o primeiro resultado e nunca fica a saber que havia três.

Quando está a fazer correspondências de classes e nada mais. O CSS faz isso de forma correta e legível. Veja acima.

Quando precisa de uma expressão regular. O XPath 1.0 não tem nenhuma. Extraia com contains() se for necessário e, em seguida, aplique uma expressão regular na sua linguagem de programação, onde também pode ver o que correspondeu.

Quando existe um identificador utilizável. Um id, um atributo data ou JSON-LD incorporado é mais estável do que qualquer correspondência de texto. O texto é conteúdo, e o conteúdo muda — uma reformulação, uma tradução ou uma revisão de texto invalida um seletor baseado em correspondência de texto e nada o avisa.

Quando a cadeia de caracteres provém de uma entrada do utilizador. Interpolar texto não confiável numa expressão XPath é uma injeção de XPath. Utilize a ligação de variáveis da sua biblioteca sempre que esta existir e faça o escape adequadamente quando não existir — especialmente com aspas, uma vez que o XPath 1.0 não tem uma sequência de escape para uma aspa dentro de um literal de cadeia de caracteres e tem de utilizar concat() para criar uma.

Perguntas frequentes

O que faz a função contains() no XPath?

Devolve «true» quando o primeiro argumento de cadeia de caracteres contém o segundo como uma subcadeia. Ambos os argumentos são cadeias de caracteres, a comparação distingue maiúsculas de minúsculas e não há caracteres curinga nem expressões regulares. A sua função habitual na extração é corresponder um elemento por parte do seu texto ou valor de atributo.

Porque é que o meu contains() no XPath não encontra nada?

Na maioria das vezes, porque lhe passou um conjunto de nós. O XPath converte um conjunto de nós numa cadeia de caracteres, pegando no primeiro nó na ordem do documento e ignorando o resto; por isso, contains(//p, 'x') examina sempre apenas o primeiro parágrafo. Em vez disso, aplique o predicado por nó: //p[contains(., 'x')].

Qual é a diferença entre contains(.) e contains(text())? `

.`` utiliza o valor de cadeia de caracteres do elemento, que a especificação define como a concatenação de todos os nós de texto descendentes — pelo que acede à marcação aninhada. ``text()`` devolve os nós de texto filhos diretos, e a conversão para cadeia de caracteres considera apenas o primeiro. Utilize .`, a menos que pretenda excluir especificamente o conteúdo aninhado.

Como posso corresponder uma classe com o XPath?

Utilize contains(concat(' ', normalize-space(@class), ' '), ' name '), que preenche o atributo de forma a que apenas tokens completos correspondam. Um simples contains(@class, 'btn') também corresponde a btn-primary e unbtn. Se estiver a corresponder apenas a classes, um seletor CSS é mais claro e, por predefinição, mais correto.

O método contains() do XPath distingue maiúsculas de minúsculas?

Sim, e o XPath 1.0 não dispõe da função lower-case(). A solução alternativa padrão é translate(), com letras maiúsculas e minúsculas explícitas, que suporta apenas caracteres ASCII. Bibliotecas do lado do servidor, como o lxml, suportam expressões regulares EXSLT; os navegadores e o Selenium não.

Como utilizo a função contains() com várias condições?

Combine predicados com and e or: //div[contains(@class, 'card') and contains(., 'In stock')]. Cada contains() é um teste booleano separado, avaliado em relação ao mesmo nó de contexto.

O XPath tem uma função «começa por» ou «termina por»?

Existe o starts-with(), que é preferível ao contains() sempre que for adequado, uma vez que resulta em menos correspondências em excesso. Não existe um ends-with() no XPath 1.0 — a solução alternativa utiliza o substring() com o string-length() e é tão pouco prática que, normalmente, é preferível adotar uma abordagem diferente.

Por que é que o contains() corresponde a mais elementos do que o esperado?

Porque se trata de um teste de subcadeia sem noção de limites de palavras. contains(., 'Price') corresponde também a «Historic Price» e «Price excluding VAT». Utilize normalize-space() = 'Price' para igualdade ou a expressão «padded-concat» para tokens de classe.

Conclusão:

contains() é simples na sua especificação, mas está repleto de armadilhas na utilização, e quase todas elas têm origem numa única fonte: o XPath converte um conjunto de nós numa cadeia de caracteres, retirando o primeiro nó e descartando o resto. Essa única regra explica por que razão contains(//p, 'x') dá uma resposta errada com toda a certeza, por que razão contains(text(), 'x') não deteta texto dividido entre nós e por que razão a mesma expressão funciona numa página e falha na seguinte.

Os hábitos que permitem evitar isso são poucos. Aplique contains() dentro de um predicado, para que seja avaliado por cada nó. Utilize . em vez de text(), a menos que tenha um motivo para o contrário. Envolva as comparações de texto em normalize-space(), porque o HTML real é formatado de forma legível. E para a correspondência de classes, utilize a expressão «padded-concat» ou — melhor ainda — utilize um seletor CSS, que foi concebido exatamente para essa função.

Reserve o XPath para aquilo que só ele faz: corresponder texto e navegar para trás. Essas são capacidades reais sem equivalente em CSS, e valem a pena a sintaxe. Utilizá-lo para selecionar div.card é pagar o custo sem colher o benefício.