Alguma vez precisou de escrever um seletor CSS independente de classes? Se a sua resposta for não, bem, pode considerar-se com sorte. Se a resposta for sim, então a nossa Folha de Referência do XPath é o que precisa. A web está repleta de dados. Empresas inteiras dependem de reunir alguns desses dados para oferecer novos serviços ao mundo. As APIs são muito úteis, mas nem todos os sites têm APIs abertas. Às vezes, terá de obter o que precisa à moda antiga. Terá de criar um scraper para o site. Os sites modernos contornam o scraping renomeando as suas classes CSS. Como resultado, é melhor escrever seletores que dependam de algo mais estável. Neste artigo, aprenderá a escrever seletores com base no layout dos nós DOM da página.
Uma ficha de referência do XPath?
O que é o XPath e como posso experimentá-lo?
XPath significa XML Path Language. Utiliza uma notação de caminho (como nos URLs) para fornecer uma forma flexível de apontar para qualquer parte de um documento XML.
O XPath é utilizado principalmente em XSLT, mas também pode ser utilizado como uma forma muito mais poderosa de navegar pelo DOM de qualquer documento em linguagem semelhante a XML utilizando XPathExpression, como HTML e SVG, em vez de depender dos métodos Document.getElementById() ou Document.querySelectorAll(), das propriedades Node.childNodes e de outras funcionalidades do DOM Core. XPath | MDN (mozilla.org)
Uma notação de caminho?
<!DOCTYPE html>
<html lang="en">
<head>
<title>Nothing to see here</title>
</head>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
<div>
<h2>My Second Heading</h2>
<p>My second paragraph.</p>
<div>
<h3>My Third Heading</h3>
<p>My third paragraph.</p>
</div>
</div>
</body>
</html>There are two types of paths: relative and absolute
The unique path ( or absolute path ) to My third paragraph. is /html/body/div/div/p
A relative path to My third paragraph. is //body/div/div/p
For My Second Heading. => //body/div/h2For My first paragraph. => //body/p
Notice that I'm using //body. Relative paths use // to skip right to the desired element.
The usage of //<path> also implies that it should look for all occurrences of <path> in the document, regardless of what came before <path>.For example, //div/p returns both My second paragraph. and My third paragraph.
Pode testar este exemplo no seu navegador para ter uma melhor visão geral!
Cole o código num ficheiro .html e abra-o no seu navegador. Abra as ferramentas de programador e prima Control + F. Cole o localizador XPath na pequena barra de entrada e prima Enter.
Também pode obter o XPath de qualquer tag clicando com o botão direito do rato na guia «Elements» e selecionando «Copy XPath»
Repara como estou a alternar entre “O meu segundo parágrafo.” e “O meu terceiro parágrafo.”
Also, another important thing to know is that it is not necessary for a path to contain // in order to return multiple elements. Let's see what happens when I add another <p> in the last <div>.
/html/body/div/div/p is no longer an absolute path.
Se me acompanhou até aqui, parabéns, está no caminho certo para dominar o XPath. Agora está pronto para mergulhar nas partes divertidas.
Os parênteses retos
Pode usar os parênteses retos para selecionar elementos específicos.
In this case, //body/div/div[2]/p[3] only selects the last <p> tag.
Atributos
Também pode usar atributos para selecionar os seus elementos.
//body//p[@class="not-important"] => select all the <p> tags that are inside a <body> tag and have the "not-important" class.
//div[@id] => select all the <div> tags that have an id attribute.
//div[@class="p-children"][@id="important"]/p[3] => select the third <p> that is within a <div> tag that has both class="p-children" and id="important"
//div[@class="p-children" and @id="important"]/p[3] => same as above
//div[@class="p-children" or @id="important"]/p[3] => select the third <p> that is within a <div> that has class="p-children" or id="important"
Notice @ marks the start of an attribute
Funções
O XPath fornece um conjunto de funções úteis que pode utilizar dentro dos parênteses retos.
position() => returns the index of the elementEx: //body/div[position()=1] selects the first <div> in the <body>
last() => returns the last elementEx: //div/p[last()] selects all the last <p> children of all the <div> tags
count(element) => returns the number of elementsEx: //body/count(div) returns the number of child <div> tags inside the <body>
node() or * => returns any elementEx: //div/node() and //div/*=> selects all the children of all the <div> tags
text() => returns the text of the elementEx: //p/text() returns the text of all the <p> elements
concat(string1, string2) => merges string1 with string2
contains(@attribute, "value") => returns true if @attribute contains "value"
Ex://p[contains(text(),"I am the third child")] selects all the <p> tags that have the "I am the third child" text value.
starts-with(@attribute, "value") => returns true if @attribute starts with "value"ends-with(@attribute, "value") => returns true if @attribute ends with "value"
substring(@attribute,start_index,end_index)] => returns the substring of the attribute value based on two index values
Ex://p[substring(text(),3,12)="am the third"] => returns true if text() = "I am the third child"
normalize-space() => acts like text(), but it removes the trailing spacesEx: normalize-space(" example ") = "example"
string-length() => returns the length of the textEx: //p[string-length()=20] returns all the <p> tags that have the text length of 20
As funções podem ser um pouco difíceis de memorizar. Felizmente, o The Ultimate Xpath Cheat Sheet fornece exemplos úteis:
//p[text()=concat(substring(//p[@class="not-important"]/text(),1,15), substring(text(),16,20))]
//p[text()=<expression_return_value>] will select all the <p> elements that have the text value equal to the return value of the condition.
//p[@class="not-important"]/text() returns the text values of all the <p> tags that have class="not-important".
If there is only one <p> tag that satisfies this condition, then we can pass the return_value to the substring function.
substring(return_value,1,15) will return the first 15 characters of the return_value string.
substring(text(),16,20) will return the last 5 characters of the same
text() value that we used in //p[text()=<expression_return_value>].
Finally, concat() will merge the two substrings and create the return value of <expression_return_value>.
Aninhamento de caminhos
O XPath suporta o aninhamento de caminhos. Isso é fantástico, mas o que quero dizer exatamente com aninhamento de caminhos?
Let's try something new: /html/body/div[./div[./p]]
You can read it as "Select all the <div> sons of the <body> that have a <div> child. Also, the children must also be parents to a <p> element."
If you don't care about the father of the <p> element, you can write: /html/body/div[.//p]
This now translates to "Select all the div children of the body that have a <p> descendant"
In this particular example, /html/body/div[./div[./p]] and /html/body/div[.//p] yield the same result.
By now, I'm sure that you are wondering what is up with those dots in ./ and .//
The dot represents the self element. When used in a pair of brackets, it references the specific tag that opened them. Let's dive a little deeper.
In our example, /html/body/div returns two divs:<div class="no-content"> and <div class="content">
/html/body/div[.//p] translates to:
/html/body/div[1][/html/body/div[1]//p]and /html/body/div[2][/html/body/div[2]//p]
/html/body/div[2][/html/body/div[2]//p] is true, so it returns /html/body/div[2]
In our case, the dot ensures that /html/body/div and /html/body/div//p refer to the same <div>
Now let's look at what would have happened if it didn't.
/html/body/div[/html/body/div//p] would return both<div class="no-content"> and <div class="content">
Why? Because /html/body/div//p is true for both /html/body/div[1] and /html/body/div[2].
/html/body/div[/html/body/div//p] actually translates to "Select all the div children of the <body> if /html/body/div//p is true.
/html/body/div//p is true if the body has a <div> child, and that child has a <p> descendent". In our case, this statement is always true.
É uma pena que outros guias de referência do XPath não mencionem nada sobre o aninhamento. Considero-o fantástico. Permite-lhe analisar o documento em busca de diferentes padrões e voltar para devolver algo diferente. A única desvantagem é que escrever consultas desta forma pode tornar-se difícil de acompanhar. A boa notícia é que existem outras formas de o fazer.
Os eixos
Pode usar eixos para localizar nós em relação a outros nós de contexto.
Vamos explorar alguns deles.
Os Quatro Eixos Principais
//p/ancestor::div => selects all the divs that are ancestors of <p>
How I read it: Get all the <p> tags, for each <p> look through its ancestors. If you find <div> tags, select them.
//p/parent::div => selects all the <div> tags that are parents of <p>
How I read it: Get all the <p> tags and of all their parents, if the parent is a <div>, select it.
//div/child::p=> selects all the <p> tags that are children of <div> tags.
How I read it: Get all the <div> tags and their children, if the child is a <p>, select it.
//div/descendant::p => selects all the <p> tags that are descendants of <div> tags.
How I read it: Get all the <div> tags and their descendants, if the descendant is a <p>, select it.
Agora é hora de reescrever a expressão anterior:
/html/body/div[./div[./p]] is equivalent to /html/body/div/div/p/parent::div/parent::div
But /html/body/div[.//p] is NOT equivalent to /html/body/div//p/ancestor::div
The good news is that we can tweak it a little bit.
/html/body/div//p/ancestor::div[last()] is equivalent to /html/body/div[.//p]
Outros eixos importantes
//p/following-sibling::span => for each <p> tag, select its following <span> siblings.
//p/preceding-sibling::span => for each <p> tag, select its preceding <span> siblings.
//title/following::span => selects all the <span> tags that appear in the DOM after the <title>.
In our example, //title/following::span selects all the <span> tags in the document.
//p/preceding::div => selects all the <div> tags that appear in the DOM before any <p> tag. But it ignores ancestors, attribute nodes and namespace nodes.
In our case, //p/preceding::div only selects <div class="p-children"> and <div class="no_content">.
Most of the <p> tags are in <div class="content">, but this <div> is not selected because it is a common ancestor for them. As I mentioned, thepreceding axe ignores ancestors.
<div class="p-children"> is selected because it is not an ancestor for the <p> tags inside <div class="p-children" id="important">
Resumo
Parabéns, conseguiu. Acrescentou uma nova ferramenta à sua caixa de ferramentas de seleção! Se estiver a criar um web scraper ou a automatizar testes web, esta Folha de Referência do XPath vai ser-lhe útil! Se procura uma forma mais fácil de percorrer o DOM, está no sítio certo. Seja como for, vale a pena experimentar o XPath. Quem sabe, talvez descubra ainda mais casos de utilização para ele. O conceito de web scraping parece-lhe interessante? Pode contactar-nos aqui WebScrapingAPI - Contacto. Se quiser fazer web scraping, teremos todo o prazer em ajudá-lo ao longo do processo. Entretanto, considere experimentar o WebScrapingAPI - Produto gratuitamente.




