Почему xpath не работает при обработке документа XHTML с помощью lxml (в python)? - PullRequest
21 голосов
/ 18 ноября 2008

Я тестирую по следующему документу:

<?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>

Если я проанализирую документ с помощью lxml.html, я смогу получить IMG с xpath просто отлично:

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

Однако, если я проанализирую документ как XML и попытаюсь получить тег IMG, я получу пустой результат:

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

Я могу перейти к элементу напрямую:

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

Но, конечно, это не помогает мне обрабатывать произвольные документы. Я также ожидал, что смогу запросить etree, чтобы получить выражение xpath, которое будет напрямую идентифицировать этот элемент, что, технически, я могу сделать:

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

Но этот xpath, опять же, явно бесполезен для разбора произвольных документов.

Очевидно, я упускаю какую-то ключевую проблему здесь, но я не знаю, что это такое. Мое лучшее предположение состоит в том, что это как-то связано с пространствами имен, но единственное определенное пространство имен - это значение по умолчанию, и я не знаю, что еще мне нужно учитывать в отношении пространств имен.

Итак, что мне не хватает?

Ответы [ 3 ]

27 голосов
/ 18 ноября 2008

Проблема в пространствах имен. При анализе в формате XML тег img находится в пространстве имен http://www.w3.org/1999/xhtml, поскольку это пространство имен по умолчанию для элемента. Вы запрашиваете тег img без пространства имен.

Попробуйте это:

>>> tree.getroot().xpath(
...     "//xhtml:img", 
...     namespaces={'xhtml':'http://www.w3.org/1999/xhtml'}
...     )
[<Element {http://www.w3.org/1999/xhtml}img at 11a29e0>]
7 голосов
/ 18 ноября 2008

XPath считает все имена без префиксов находящимися в «пространстве имен» .

В частности, в спецификации сказано:

"QName в тесте узла раскрывается в расширенное имя с использованием объявлений пространства имен из контекста выражения. Аналогичным образом выполняется расширение для имен типов элементов в начальных и конечных тегах, за исключением того, что объявлено пространство имен по умолчанию. с xmlns не используется: если QName не имеет префикса, тогда URI пространства имен имеет значение null (то же самое, что и имена атрибутов, которые раскрываются). "

См. Эти два подробных объяснения проблемы и ее решения: здесь и здесь . Решение состоит в том, чтобы связать префикс (с используемым API) и использовать его для префикса любого нефиксированного имени в выражении XPath.

Надеюсь, это помогло.

Приветствия

Димитр Новатчев

2 голосов
/ 12 мая 2011

Если вы собираетесь использовать теги только из одного пространства имен, как я вижу в приведенном выше случае, вам гораздо лучше использовать lxml.objectify.

В вашем случае это будет похоже на

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

Вы можете получить доступ к узлам как

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

Хотя это может быть не очень полезно в html, оно может быть очень полезно в хорошо структурированном xml.

Для получения дополнительной информации, проверьте http://lxml.de/objectify.html

...