¿Alguna vez has tenido que escribir un selector CSS independiente de clases? Si tu respuesta es no, pues puedes considerarte afortunado. Si la respuesta es sí, entonces nuestra hoja de referencia de XPath es lo que necesitas. La web está repleta de datos. Hay empresas enteras que dependen de reunir parte de ellos para ofrecer nuevos servicios al mundo. Las API son de gran utilidad, pero no todos los sitios web tienen API abiertas. A veces, tendrás que conseguir lo que necesitas a la antigua usanza. Tendrás que crear un rastreador para el sitio web. Los sitios web modernos evitan el rastreo renombrando sus clases CSS. Como resultado, es mejor escribir selectores que se basen en algo más estable. En este artículo, aprenderás a escribir selectores basados en la estructura de nodos DOM de la página.
¿Una hoja de referencia de XPath?
¿Qué es XPath y cómo lo pruebo?
XPath son las siglas de XML Path Language. Utiliza una notación de ruta (como en las URL) para proporcionar una forma flexible de apuntar a cualquier parte de un documento XML.
XPath se utiliza principalmente en XSLT, pero también puede emplearse como una forma mucho más potente de navegar por el DOM de cualquier documento en un lenguaje similar a XML utilizando XPathExpression, como HTML y SVG, en lugar de depender de los métodos Document.getElementById() o Document.querySelectorAll(), las propiedades Node.childNodes y otras características del núcleo del DOM. XPath | MDN (mozilla.org)
¿Una notación de ruta?
<!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.
¡Puedes probar este ejemplo en tu navegador para tener una mejor visión general!
Pega el código en un archivo .html y ábrelo con tu navegador. Abre las herramientas de desarrollador y pulsa Control + F. Pega el localizador XPath en la pequeña barra de entrada y pulsa Intro.
También puedes obtener el XPath de cualquier etiqueta haciendo clic con el botón derecho del ratón sobre ella en la pestaña Elementos y seleccionando «Copiar XPath».
Fíjate en cómo cambio entre «Mi segundo párrafo» y «Mi tercer párrafo».
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.
Si me has seguido hasta aquí, enhorabuena, vas por buen camino para dominar XPath. Ya estás listo para sumergirte en lo divertido.
Los corchetes
Puedes utilizar los corchetes para seleccionar elementos específicos.
In this case, //body/div/div[2]/p[3] only selects the last <p> tag.
Atributos
También puedes utilizar atributos para seleccionar tus 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
Funciones
XPath ofrece un conjunto de funciones útiles que puedes utilizar dentro de los corchetes.
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
Las funciones pueden resultar un poco difíciles de recordar. Por suerte, la hoja de referencia definitiva de XPath ofrece ejemplos útiles:
//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>.
Anidamiento de rutas
XPath admite el anidamiento de rutas. Eso está muy bien, pero ¿qué quiero decir exactamente con «anidamiento de rutas»?
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.
Es una pena que otras hojas de referencia de XPath no mencionen nada sobre el anidamiento. A mí me parece increíble. Te permite escanear el documento en busca de diferentes patrones y volver para devolver otra cosa. El único inconveniente es que escribir consultas de esta manera puede resultar difícil de seguir. La buena noticia es que hay otras formas de hacerlo.
Los ejes
Puedes utilizar ejes para localizar nodos en relación con otros nodos del contexto.
Exploremos algunos de ellos.
Los cuatro ejes principales
//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.
Ahora es el momento de reescribir la expresión 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]
Otros ejes 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">
Resumen
Enhorabuena, lo has conseguido. ¡Has añadido una nueva herramienta a tu caja de herramientas de selección! Si estás creando un rastreador web o automatizando pruebas web, ¡esta hoja de referencia de XPath te resultará muy útil! Si buscas una forma más fluida de recorrer el DOM, estás en el lugar adecuado. En cualquier caso, vale la pena probar XPath. Quién sabe, quizá descubras aún más casos de uso para él. ¿Te parece interesante el concepto de web scraping? Puedes ponerte en contacto con nosotros aquí: WebScrapingAPI - Contacto. Si quieres extraer datos de la web, estaremos encantados de ayudarte en el proceso. Mientras tanto, considera probar WebScrapingAPI - Producto de forma gratuita.




