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

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

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

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
21
0
7 907
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

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

Цитирование из codepeak.net/lxml/xpathxslt.html << При желании вы можете предоставить аргумент ключевого слова namespaces, который должен быть словарем, отображающим префиксы пространства имен, используемые в выражении XPath, в URI пространства имен >>

Cristian Ciupitu 14.06.2009 18:50

Если вы хотите выполнить поиск с помощью компактных выражений xpath в пространстве имен по умолчанию корневого элемента, вы можете использовать уловку, которая работает для xhtml или других схем, например: nsmap = {'h': tree.getroot().nsmap[None]}; elem.xpath('//h:img', namespaces=nsmap, что упрощает компактное написание запроса.

mkj 11.03.2014 17:49

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

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

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

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

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

Ваше здоровье,

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

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

Другие вопросы по теме