Geonode logo
Geonode Team

Geonode Team

Actualizado: 7 de octubre de 2026

Publicado: 2 de septiembre de 2026

Guía rápida de XPath: guía con ejemplos

XPath es un lenguaje de consulta para navegar por documentos XML y HTML. Tiene un núcleo reducido y un gran número de formas de utilizarlo incorrectamente. Esta es una guía de referencia organizada tal y como realmente la necesitas: primero la sintaxis, luego las funciones que conviene conocer, a continuación los patrones que se repiten con frecuencia y, por último, las trampas. Todo lo que aquí se recoge corresponde a XPath 1.0, que es la versión que implementan los navegadores, Selenium y la mayoría de las bibliotecas de análisis sintáctico.

Una breve nota sobre quién ha escrito esto. Somos Geonode y vendemos proxies a personas que extraen datos, por lo que XPath está relacionado con nuestro negocio, pero no forma parte de él. La advertencia relevante es que un problema de selector y un problema de proxy no se parecen en nada, y confundirlos supone una pérdida de horas: una solicitud bloqueada devuelve una página de desafío o un estado de error, mientras que una expresión errónea devuelve una página perfectamente válida y un resultado vacío. Si el HTML está ahí y tu XPath no encuentra nada, la red funciona correctamente y esta página es el lugar adecuado.

Sintaxis básica

ExpresiónSelecciona
/html/body/divRuta absoluta desde la raíz
//divCualquier elemento div en cualquier parte del documento
//div/pElementos p que sean hijos directos de un div
//div//pElementos p a cualquier profundidad dentro de un div
.El nodo de contexto actual
..El nodo padre del nodo de contexto
*Cualquier elemento
@hrefEl atributo «href»
//@hrefTodos los atributos «href» del documento
text()Los nodos de texto hijos del nodo de contexto
node()Cualquier nodo, incluidos los de texto y los comentarios
//a | //linkUnión: todo lo que coincida con cualquiera de las expresiones

Es fundamental interiorizar la distinción entre / y //. Una sola barra significa «hijo directo»; dos barras significan «descendiente a cualquier profundidad». //div/p no encuentra un párrafo envuelto en un section; //div//p sí lo encuentra.

Las rutas absolutas —/html/body/div[2]/div[1]/span— son las que genera la opción «copiar XPath» del navegador y las que dejan de funcionar en el siguiente rediseño. Es preferible partir de algo identificable y navegar de forma relativa.

Predicados

Los corchetes filtran un conjunto de nodos. Es ahí donde se lleva a cabo la mayor parte del trabajo útil.

ExpresiónSelecciona
//div[1]El primer div entre sus hermanos, por cada nodo padre
(//div)[1]El primer div de todo el documento
//div[last()]El último div entre sus hermanos
//div[position() < 4]Los tres primeros
//a[@href]Enlaces que tienen un atributo «href»
//a[@href='/about']Enlaces con ese mismo «href»
//div[@class and @id]Elementos con ambos atributos
//p[text()]Párrafos con al menos un elemento hijo de tipo «text-node»
//div[p]Elementos «div» que contienen al menos un elemento hijo de tipo «p»
//div[not(@hidden)]Elementos «div» sin un atributo «hidden»
//td[.='42'][@class='qty']Dos predicados, aplicados en secuencia

** La diferencia entre «//div[1]» y «(//div)[1]» es la confusión más habitual en XPath.** El primero es un predicado que se aplica por cada elemento padre, por lo que selecciona el primer «div» bajo cada elemento padre que tenga uno —potencialmente muchos nodos—. El segundo recopila todos los divs en un conjunto de nodos según el orden del documento y toma el primero, que es exactamente un nodo. Ambos son útiles; no son intercambiables.

Los predicados se encadenan, y cada uno se aplica al resultado del anterior. //td[@class='price'][1] significa «el primero entre las celdas con la clase price», leyéndose de izquierda a derecha.

Ejes

Los ejes se desplazan en relación con el nodo de contexto. La mayoría de los usuarios utilizan tres y, en ocasiones, necesitan los demás.

EjeSelecciona
child::Hijos directos: el valor por defecto, que suele omitirse
descendant::Todos los descendientes a cualquier profundidad
parent::El padre
ancestor::Todos los antepasados hasta la raíz
ancestor-or-self::Antepasados más el propio nodo
following-sibling::Hermanos posteriores
preceding-sibling::Hermanos anteriores
following::Todo lo que viene después en el orden del documento, excluidos los descendientes
preceding::Todo lo anterior, excluyendo a los antepasados
attribute::Atributos — abreviados como @
self::El propio nodo

Cuatro de ellos son ejes inversos — ancestor, ancestor-or-self, preceding y preceding-sibling — y en ellos, la numeración de posiciones va hacia atrás. preceding-sibling::p[1] es el párrafo anterior más cercano, no el primero del documento. Encuadra la expresión entre paréntesis para obtener, en su lugar, el orden del documento. Ya tratamos este tema en detalle en XPath preceding-sibling.

following y preceding son mucho más amplios y mucho más lentos que sus homólogos «sibling», y excluyen a los antepasados y descendientes, respectivamente. Recurre a ellos solo cuando la relación sea realmente laxa.

Funciones de cadenas

Las más utilizadas.

FunciónQué hace
contains(a, b)Devuelve «True» si a contiene b como subcadena
starts-with(a, b)Devuelve «True» si a comienza por b
normalize-space(s)Recorta y elimina los espacios en blanco internos
string-length(s)Recuento de caracteres
substring(s, start, len)Subcadena, indexada a partir de 1
substring-before(a, b)Todo lo que hay antes de la primera aparición de b
substring-after(a, b)Todo lo que hay después de la primera aparición de b
translate(s, from, to)Sustitución carácter por carácter
concat(a, b, ...)Une cadenas
string(node-set)Solo el valor de cadena del primer nodo

Tres notas que evitan errores reales.

substring() utiliza una indexación que empieza por 1. substring('hello', 1, 3) devuelve hel. A todo el mundo le pasa alguna vez que se equivoca con esto.

normalize-space() debería ser tu envoltorio predeterminado para cualquier comparación de texto. El HTML real se formatea de forma legible, por lo que el valor de cadena de una celda suele ser "\n In stock\n" en lugar de "In stock". Sin ningún argumento, opera sobre el nodo de contexto.

En XPath 1.0 no existen ends-with(), lower-case() ni expresiones regulares. Para ignorar las mayúsculas y minúsculas, la solución estándar es utilizar translate() con los caracteres alfabéticos explícitos. Las bibliotecas del lado del servidor, como lxml, admiten extensiones EXSLT, entre ellas re:test(); los navegadores y Selenium no las admiten.

Funciones numéricas y booleanas

FunciónHace
count(node-set)Número de nodos
position()Posición del nodo de contexto
last()Tamaño del conjunto de nodos de contexto
number(s)Convierte a un número
sum(node-set)Suma los valores numéricos
round(), floor(), ceiling()Tal y como se indica
not(expr)Negación booleana
boolean(expr)Convierte a booleano
true(), false()Valores booleanos literales

Los operadores de comparación son =, !=, <, >, <=, >=, junto con and y or para la combinación. Ten en cuenta que, en contextos XML, < debe escaparse como &lt;, por lo que a veces se ven expresiones escritas con position() &lt; 4.

Una sutileza que conviene conocer: comparar un conjunto de nodos con un valor es una prueba existencial. //p = 'Price' es verdadero si cualquier párrafo es igual a «Price». Eso es a menudo lo que se busca dentro de un predicado y casi nunca lo que se busca en el nivel superior.

Patrones que aparecen constantemente

Las expresiones que realmente funcionan.

Coincidir correctamente con una clase — el simple contains(@class, 'btn')

también coincide con btn-primary

y unbtn

:

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

Buscar un valor por su etiqueta — el requisito de extracción más común que existe:

//dt[normalize-space()='Price']/following-sibling::dd[1]
//th[normalize-space()='Weight']/following-sibling::td[1]
//td[preceding-sibling::td[1]='SKU']

Buscar una fila por una celda y, a continuación, seleccionar otra columna:

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

Buscar un contenedor por algo que haya en su interior:

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

Buscar el primer elemento después de un encabezado:

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

Coincidencia de texto sin distinción entre mayúsculas y minúsculas:

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

Excluir en lugar de incluir — a menudo resulta más claro:

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

Coincidir con cualquiera de dos tipos de elementos:

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

Omitir valores vacíos:

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

Extraer un atributo de un elemento coincidente:

//a[normalize-space()='Download']/@href

Las trampas

Clasificadas según la frecuencia con la que hacen perder tiempo a los usuarios.

La conversión de un conjunto de nodos a una cadena solo toma el primer nodo. contains(//p, 'Price') convierte todo el conjunto de nodos //p en una cadena tomando el primer párrafo e ignorando el resto. En su lugar, aplica el predicado a cada nodo: //p[contains(., 'Price')]. Este es el error más común en XPath.

. y text() son diferentes. El valor de cadena de un elemento es la concatenación de todos sus nodos de texto descendientes; text() devuelve solo los hijos directos que son nodos de texto, y la conversión a cadena toma el primero de ellos. Utiliza . a menos que quieras excluir específicamente el contenido anidado.

Numeración de eje inverso. preceding-sibling::td[1] es el más cercano, no el primero.

Espacios en blanco. //td[.='In stock'] falla con HTML formateado. normalize-space() lo soluciona.

Distinción entre mayúsculas y minúsculas. En XPath 1.0 todo distingue entre mayúsculas y minúsculas, incluidos los nombres de los elementos en los documentos XML.

Espacios de nombres predeterminados. En XML con un espacio de nombres predeterminado, //item no coincide con nada; debes registrar un prefijo y utilizarlo. Los analizadores de HTML suelen ahorrarte este paso; los de XML no.

Rutas absolutas de las herramientas de desarrollo del navegador. Codifican la estructura exacta en un momento concreto y dejan de funcionar ante cualquier cambio.

Coincidencia excesiva con «contains()». Al buscar «Price», también se encuentra «Historic Price». Utiliza «normalize-space() = 'Price'» cuando quieras indicar igualdad.

Inyección de cadenas no fiables. Interpolar la entrada del usuario en una expresión es una inyección de XPath. Utiliza el enlace de variables cuando tu biblioteca lo permita. XPath 1.0 no dispone de caracteres de escape para las comillas dentro de un literal de cadena, por lo que un valor que contenga ambos caracteres de comilla requiere concat().

XPath 1.0 frente a versiones posteriores

Es importante saberlo, ya que es posible que un fragmento de código que encuentres en Internet no funcione donde lo necesitas.

XPath 1.0 es lo que implementan los navegadores a través de document.evaluate, lo que utiliza Selenium y lo que ofrece la API común de lxml. Cuenta con las funciones enumeradas anteriormente y nada más.

XPath 2.0 y 3.1 añaden expresiones regulares (matches(), replace()), funciones de mayúsculas y minúsculas (upper-case(), lower-case()), ends-with(), tipos de secuencia, expresiones for y mucho más. Están disponibles en procesadores XSLT 2.0+ y en algunas herramientas XML, pero no en los navegadores.

La regla práctica: si una expresión utiliza una función que no aparece en las tablas anteriores, comprueba si tu entorno la admite antes de depurar por qué falla. «Funciona en un probador de XPath en línea, pero no en Selenium» suele deberse casi siempre a esto.

En cuanto a las carencias de XPath 1.0, la solución suele estar en tu lenguaje de programación. Extrae los datos con XPath y, a continuación, aplica una expresión regular en Python o JavaScript, donde también podrás inspeccionar qué elementos han coincidido.

Cómo escribir selectores que resistan un rediseño

Una hoja de referencia te indica lo que es posible; esta sección trata sobre qué opciones elegir, ya que la diferencia entre un selector que dura un año y otro que deja de funcionar el próximo martes radica exclusivamente en dónde lo anclas.

Basate en el significado, no en la posición. (//table)[3]/tr[2]/td[4] codifica la forma exacta de la página en un momento dado. Si alguien añade una tabla encima, todos los números serán incorrectos —sin que se note—, porque la expresión sigue coincidiendo con algo. //th[normalize-space()='Weight']/following-sibling::td[1] codifica una relación que sobrevive a los cambios de orden, ya que la etiqueta se mueve junto con el valor.

Prefiere los atributos estables a los generados. Los identificadores (ID) y los atributos «data-*» elegidos por un desarrollador son mucho más duraderos que los nombres de clase, que cambian cada vez que alguien modifica el estilo. Muchos marcos de trabajo front-end modernos generan nombres de clase con hash — css-1x9dj2k — que cambian en cada compilación; basarse en ellos garantiza que se produzcan errores.

Comprueba si hay datos estructurados incrustados antes de escribir cualquier selector. Muchísimas páginas incluyen JSON-LD en un bloque <script type="application/ld+json">, ya que impulsa las funciones de búsqueda. Analizarlo es mucho más estable que analizar el HTML renderizado, ya que está diseñado para ser leído por máquinas y sobrevive por completo a los rediseños visuales. Dedicar treinta segundos a comprobarlo puede ahorrarte una tarde de mantenimiento de selectores.

Verifica la estructura de lo que extraes. Este es el hábito que distingue un proceso que falla de forma evidente de uno que falla de forma silenciosa. Si un precio debe ajustarse a un patrón de divisa, compruébalo. Si una página de categoría nunca ha tenido menos de veinte artículos, haz que un número inferior a veinte se considere un error en lugar de un resultado. Un selector que empieza a coincidir con el elemento equivocado produce datos plausibles, bien formados, pero erróneos, y no se lanza ninguna excepción en ningún momento.

Mantén los selectores en un solo lugar. Dispersas por el código, cuarenta cadenas XPath suponen cuarenta problemas de mantenimiento distintos. Reunidas en un único módulo con nombres, constituyen un mapa de aquello de lo que dependes, y actualizarlas tras un rediseño lleva una hora en lugar de un día.

Y realiza pruebas con el HTML guardado. Almacenar una copia de cada página que analizas significa que, cuando un selector deja de funcionar, puedes comparar el marcado antiguo con el nuevo y ver exactamente qué ha cambiado. Volver a cargar la página para depurarla es más lento, consume ancho de banda y puede que te muestre una página diferente a la que dio error.

Cuándo utilizar CSS en su lugar

XPath es más potente, pero menos legible. CSS es la opción predeterminada adecuada para gran parte de las tareas de selección.

Utiliza CSS cuando: selecciones por clase, identificador, atributo o relación de descendencia. div.card > p.price es más claro que el equivalente en XPath, cuenta con mejor compatibilidad con las herramientas y, por lo general, es más rápido.

Utiliza XPath cuando: necesites realizar una coincidencia con contenido de texto, algo que CSS no puede hacer en absoluto; necesites navegar hasta un elemento padre o antepasado; necesites lógica posicional relativa a elementos hermanos de una forma que :nth-child no puede expresar; o estés realizando consultas en XML en lugar de HTML.

Ten en cuenta que CSS ha reducido en parte esta brecha. :has() ofrece selección condicional de elementos hermanos y descendientes en los navegadores modernos, por lo que ahora se puede expresar dt:has(+ dd). Lo que CSS aún no puede hacer es seleccionar por texto, y la coincidencia de texto es precisamente lo que requiere el patrón de etiqueta-valor.

Mezclar ambos en una misma base de código está bien y es sensato: CSS para el 90 % de los casos sencillos, XPath para el 10 % más complicado.

Preguntas frecuentes

¿Para qué se utiliza XPath?

Para navegar y seleccionar nodos en documentos XML y HTML. En la práctica, se utiliza para el scraping web, la automatización de pruebas de navegadores y la consulta de archivos de configuración y de datos XML. Permite expresar relaciones —padres, hermanos, contenido de texto— que los selectores CSS no pueden expresar.

¿Cuál es la diferencia entre / y // en XPath?

Una sola barra selecciona los hijos directos; dos barras seleccionan a los descendientes a cualquier profundidad. //div/p coincide con párrafos cuyo padre inmediato es un div, mientras que //div//p coincide con párrafos en cualquier lugar dentro de un div.

¿Por qué mi XPath contains() no devuelve ningún resultado?

Lo más probable es que se deba a que le has pasado un conjunto de nodos. XPath convierte un conjunto de nodos en una cadena tomando el primer nodo en el orden del documento y descartando el resto, por lo que contains(//p, 'x') solo examina el primer párrafo. Escribe //p[contains(., 'x')] en su lugar.

¿Cómo selecciono por clase en XPath?

Utiliza //div[contains(concat(' ', normalize-space(@class), ' '), ' name ')], que rellena el atributo para que solo coincidan los tokens completos. Un simple contains(@class, 'btn') también coincide con btn-primary. Si solo estás seleccionando por clases, un selector CSS es más claro y, por defecto, más correcto.

¿Admite XPath expresiones regulares?

No en XPath 1.0, que es lo que implementan los navegadores y Selenium. XPath 2.0 y versiones posteriores añaden matches() y replace(), y las bibliotecas del lado del servidor, como lxml, admiten re:test() de EXSLT. Para la automatización de navegadores, extrae los datos con XPath y aplica la expresión regular en tu lenguaje de programación.

¿Cuál es la diferencia entre //div[1] y (//div)[1]?

//div[1] aplica el predicado por elemento padre, seleccionando el primer div bajo cada elemento padre que tenga uno —posiblemente muchos nodos—. (//div)[1] recopila todos los divs en el orden del documento y toma el primero, lo que supone exactamente un nodo.

¿El XPath distingue entre mayúsculas y minúsculas?

Sí, en todos los casos: nombres de elementos, nombres de atributos y comparaciones de cadenas. XPath 1.0 no dispone de la función «lower-case()», por lo que la coincidencia sin distinción entre mayúsculas y minúsculas requiere el uso de «translate()» con letras mayúsculas y minúsculas explícitas.

¿Debería utilizar selectores XPath o CSS?

CSS para clases, identificadores, atributos y relaciones de descendencia: es más claro y cuenta con mejor compatibilidad. XPath cuando necesites realizar coincidencias en el contenido de texto, navegar hacia antepasados o expresar lógica posicional que CSS no puede. Es habitual utilizar ambos en una misma base de código.

Conclusión

El núcleo útil de XPath es reducido: «//» para buscar en cualquier lugar, los predicados entre corchetes para filtrar, «@» para los atributos, unas cuantas funciones de cadena y los ejes de hermanos para navegar en relación con algo que puedas identificar.

Hay tres hábitos que evitan la mayor parte de los problemas. Envuelve las comparaciones de texto en normalize-space(), ya que el HTML real está formateado y una coincidencia exacta fallará. Aplica contains() dentro de un predicado para que se evalúe por nodo, ya que la conversión a cadena de un conjunto de nodos toma silenciosamente solo el primero. Y es preferible anclar en texto o identificadores en lugar de hacerlo en la posición, ya que la estructura cambia y el texto, por lo general, no.

Por último, recuerda para qué versión estás escribiendo. Los navegadores y Selenium te ofrecen XPath 1.0 —sin expresiones regulares, sin lower-case(), sin ends-with()— y una expresión que funcione en un probador en línea puede no utilizar ninguna de ellas y, aun así, fallar por alguna razón que se encuentre más abajo en la lista. Cuando necesites lo que le falta a la versión 1.0, extrae la información con XPath y haz el resto en tu lenguaje de programación.