Volver al blog
Guías
Mihai MaximLast updated on Mar 31, 20268 min read

La guía definitiva de XPath. Cómo escribir fácilmente selectores potentes.

La guía definitiva de XPath. Cómo escribir fácilmente selectores potentes.

¿Una hoja de referencia de XPath?

¿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.

¿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/h2
For 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 element
Ex: //body/div[position()=1] selects the first <div> in the <body>

last() => returns the last element
Ex: //div/p[last()] selects all the last <p> children of all the <div> tags

count(element) => returns the number of elements
Ex: //body/count(div) returns the number of child <div> tags inside the <body>

node() or * => returns any element
Ex: //div/node() and //div/*=> selects all the children of all the <div> tags

text() => returns the text of the element
Ex: //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 spaces
Ex: normalize-space(" example ") = "example"

string-length() => returns the length of the text
Ex: //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, the
preceding 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.

Acerca del autor
Mihai Maxim, Desarrollador Full Stack @ WebScrapingAPI
Mihai MaximDesarrollador Full Stack

Mihai Maxim es desarrollador full stack en WebScrapingAPI, donde colabora en todas las áreas del producto y ayuda a crear herramientas y funciones fiables para la plataforma.

Empieza a crear

¿Estás listo para ampliar tu recopilación de datos?

Únete a más de 2000 empresas que utilizan WebScrapingAPI para extraer datos de la web a escala empresarial sin ningún gasto de infraestructura.