print libreria example etree python xml xhtml xpath lxml

libreria - ¿Por qué xpath no funciona al procesar un documento XHTML con lxml(en python)?



pip install lxml python (3)

Estoy probando contra el siguiente documento de prueba:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>hi there</title> </head> <body> <img class="foo" src="bar.png"/> </body> </html>

Si analizo el documento usando lxml.html, puedo obtener el IMG con un xpath muy bien:

>>> root = lxml.html.fromstring(doc) >>> root.xpath("//img") [<Element img at 1879e30>]

Sin embargo, si analizo el documento como XML y trato de obtener la etiqueta IMG, obtengo un resultado vacío:

>>> tree = etree.parse(StringIO(doc)) >>> tree.getroot().xpath("//img") []

Puedo navegar al elemento directamente:

>>> tree.getroot().getchildren()[1].getchildren()[0] <Element {http://www.w3.org/1999/xhtml}img at f56810>

Pero, por supuesto, eso no me ayuda a procesar documentos arbitrarios. También esperaría poder consultar etree para obtener una expresión xpath que identificará directamente este elemento, que, técnicamente, puedo hacer:

>>> tree.getpath(tree.getroot().getchildren()[1].getchildren()[0]) ''/*/*[2]/*'' >>> tree.getroot().xpath(''/*/*[2]/*'') [<Element {http://www.w3.org/1999/xhtml}img at fa1750>]

Pero ese xpath es, una vez más, obviamente no es útil para analizar documentos arbitrarios.

Obviamente me falta algo clave aquí, pero no sé qué es. Mi mejor suposición es que tiene algo que ver con los espacios de nombres, pero el único espacio de nombres definido es el predeterminado y no sé qué más podría tener que considerar con respecto a los espacios de nombres.

Entonces, ¿qué me estoy perdiendo?


El problema es el espacio de nombres. Cuando se analiza como XML, la etiqueta img se encuentra en el espacio de nombres http://www.w3.org/1999/xhtml ya que ese es el espacio de nombre predeterminado para el elemento. Usted está pidiendo la etiqueta img en ningún espacio de nombres.

Prueba esto:

>>> tree.getroot().xpath( ... "//xhtml:img", ... namespaces={''xhtml'':''http://www.w3.org/1999/xhtml''} ... ) [<Element {http://www.w3.org/1999/xhtml}img at 11a29e0>]


Si va a utilizar etiquetas de un solo espacio de nombres, como lo veo en el caso anterior, es mucho mejor que use lxml.objectify.

En tu caso sería como

from lxml import objectify root = objectify.parse(url) #also available: fromstring

Puede acceder a los nodos como

root.html body = root.html.body for img in body.img: #Assuming all images are within the body tag

Si bien puede no ser de gran ayuda en html, puede ser muy útil en xml bien estructurado.

Para obtener más información, consulte http://lxml.de/objectify.html


XPath considera que todos los nombres no prefijados están en "sin espacio de nombres" .

En particular, la especificación dice:

"Un QName en la prueba de nodo se expande en un nombre expandido usando las declaraciones de espacio de nombres del contexto de expresión. Esta es la misma forma en que se realiza la expansión para los nombres de tipos de elementos en las etiquetas de inicio y finalización, excepto que el espacio de nombres predeterminado declarado con xmlns no utilizado: si QName no tiene un prefijo, entonces el URI del espacio de nombres es nulo (esta es la misma forma en que se expanden los nombres de los atributos).

Vea esas dos explicaciones detalladas del problema y su solución: here y here . La solución es asociar un prefijo (con la API que se está utilizando) y usarlo para prefijar cualquier nombre no prefijado en la expresión XPath.

Espero que esto haya ayudado.

Aclamaciones,

Dimitre Novatchev