Подтвердить что ты не робот

Почему xpath не работает при обработке документа XHTML с помощью lxml (в python)?

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

<?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, опять же, явно не полезен для разбора произвольных документов.

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

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

4b9b3361

Ответ 1

Проблема заключается в пространствах имен. Когда анализируется как 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>]

Ответ 2

XPath считает, что все неподписанные имена находятся в "без пространства имен" .

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

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

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

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

Приветствия,

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

Ответ 3

Если вы собираетесь использовать теги только из одного пространства имен, как я вижу в приведенном выше примере, вам гораздо лучше использовать 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