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

Estoy probando contra el siguiente documento de prueba:

    hi there      

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

 >>> root = lxml.html.fromstring(doc) >>> root.xpath("//img") [] 

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

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

Puedo navegar directamente al elemento:

 >>> tree.getroot().getchildren()[1].getchildren()[0]  

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

 >>> tree.getpath(tree.getroot().getchildren()[1].getchildren()[0]) '/*/*[2]/*' >>> tree.getroot().xpath('/*/*[2]/*') [] 

Pero ese xpath, de nuevo, obviamente no es útil para analizar documentos arbitrarios.

Obviamente me estoy perdiendo un problema clave aquí, pero no sé qué es. Mi mejor conjetura 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é otra cosa podría tener que considerar con respecto a los espacios de nombres.

Entonces, ¿qué me estoy perdiendo?

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

Prueba esto:

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

XPath considera que todos los nombres sin prefijo 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 tipo de elemento en las tags de inicio y final, excepto que el espacio de nombres predeterminado declarado con xmlns es no utilizado: si el 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: aquí y aquí . La solución es asociar un prefijo (con la API que se está utilizando) y usarlo para prefijar cualquier nombre sin prefijo en la expresión XPath.

Espero que esto haya ayudado.

Aclamaciones,

Dimitre Novatchev

Si va a usar tags de un solo espacio de nombres, como veo en el caso anterior, es mucho mejor usar lxml.objectify.

En tu caso seria como

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

Puedes 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 que no sea de gran ayuda en HTML, puede ser muy útil en XML bien estructurado.

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