Почему 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, опять же, явно не полезен для разбора произвольных документов.
Очевидно, что у меня отсутствует ключевой вопрос здесь, но я не знаю, что это такое. Мое лучшее предположение заключается в том, что оно имеет какое-то отношение к пространствам имен, но единственным определяемым пространством имен является значение по умолчанию, и я не знаю, что еще мне нужно учитывать в отношении пространств имен.
Итак, что мне не хватает?
Ответы
Ответ 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