HTML XPath: ¿extracción de texto mezclado con varias tags?

Objetivo: extraer texto de un elemento en particular (por ejemplo, li), ignorando las diversas tags mixtas, es decir, aplanar al niño de primer nivel y simplemente devolver el texto concatenado de cada niño aplastado por separado.

Ejemplo:

CIA

  1. Central Intelligence Agency.
  2. Culinary Institute of America.

texto deseado:

  • Agencia Central de Inteligencia
  • Instituto Culinario de América

Excepto que las tags de anclaje que rodean evitan una recuperación simple.

Para devolver cada etiqueta li por separado, usamos el sencillo:

 //div[contains(@id,"mw-content-text")]/ol/li 

pero eso también incluye tags de anclaje circundantes, etc. Y

 //div[contains(@id,"mw-content-text")]/ol/li/text() 

devuelve solo los elementos de texto que son hijos directos de li, es decir, ‘Central’, ‘.’…

Parecía lógico entonces buscar elementos de texto de uno mismo y descendientes

 //div[contains(@id,"mw-content-text")]/ol/li[descendant-or-self::text] 

¡pero eso no devuelve nada!

¿Alguna sugerencia? Estoy usando Python, por lo que estoy abierto a usar otros módulos para el postproceso.

(Estoy usando el Scrapy HtmlXPathSelector que parece compatible con XPath 1.0)

Estabas casi allí. Hay un pequeño problema en :

 //div[contains(@id,"mw-content-text")]/ol/li[descendant-or-self::text] 

La expresión corregida es :

 //div[contains(@id,"mw-content-text")]/ol/li[descendant-or-self::text()] 

Sin embargo, hay una expresión más simple que produce exactamente la concatenación deseada de todos los nodos de texto bajo la li especificada:

 string(//div[contains(@id,"mw-content-text")]/ol/li) 

Creo que lo siguiente arrojaría el resultado correcto:

 //div[contains(@id,"mw-content-text")]/ol/li//text() 

Tenga en cuenta la doble barra antes del texto (). Esto significa que se deben devolver nodos de texto en cualquier nivel por debajo de li.

La concatenación de cadenas es complicada. Aquí hay una solución rápida usando lxml :

 >>> from lxml import etree >>> doc = etree.HTML("""

CIA

...
    ...
  1. Central Intelligence Agency.
  2. ...
  3. Culinary Institute of America.
  4. ...
... ...
""") >>> for element in doc.xpath('//div[@id="mw-content-text"]/ol/li'): ... print "".join(element.xpath('descendant-or-self::text()')) ... Central Intelligence Agency. Culinary Institute of America.

Tenga en cuenta que // tiene un rendimiento potencialmente malo / ejecución no deseada y debe evitarse siempre que sea posible, pero es difícil hacerlo con el fragmento HTML de ejemplo.