2025, Dec 01 03:02

Как получить атрибуты из SOAP‑ответа с пространствами имён в lxml и XPath

Как получить атрибуты (например, Status) из SOAP/XML с пространствами имён: lxml, XPath и nsmap в Python. Пошаговый разбор с примерами XPath.

Извлечение атрибутов из SOAP‑ответов с XML‑пространствами имён нередко ставит в тупик даже опытных разработчиков. До текстовых узлов вроде SessionId добраться просто, а вот атрибуты на элементах с пространствами имён — например, Status у api:PlexViewResponse — требуют правильной связки сопоставления пространств имён и обращения к атрибутам. Ниже — как делать это последовательно с lxml.

Пример SOAP‑сообщения

<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
  <soap-env:Header />
  <soap-env:Body>
    <api:PlexViewResponse xmlns:api="http://alu.com/plexwebapi/api" Command="ACT-USER" SwitchName="" RequestId="ACTUSER1748031686781471283" SessionId="" CongestionIndicator="false" Status="SUCCESS">
      <SessionId>session_123456</SessionId>
    </api:PlexViewResponse>
  </soap-env:Body>
</soap-env:Envelope>

Первый подход, который работает для текста, но не для атрибутов

Обычно задают карту пространств имён и без проблем читают текстовый узел SessionId. Сложности начинаются, когда пытаешься тем же способом получить атрибут вроде Status.

# Задаём привязки пространств имён
ns_aliases = { 'api': 'http://alu.com/plexwebapi/api' }

# Читаем текст элемента SessionId
# Предположим, что soap_doc — уже разобранное дерево XML
sess_val = soap_doc.findall('.//SessionId', ns_aliases)[0].text

SessionId — это элемент с текстовым содержимым, поэтому .text срабатывает. Однако значение Status — это атрибут у api:PlexViewResponse, а атрибуты не читаются через .text и не находятся простым поиском по имени узла. Их нужно получать у объекта элемента через .get("ИмяАтрибута").

Почему так происходит

В XML элементы и атрибуты — разные типы узлов. Когда вы находите элемент, его текст читается через .text. Но если значение хранится в атрибуте, нужно сначала получить сам элемент, а затем обратиться к атрибуту методом .get. Кроме того, поскольку элемент находится в пространстве имён (api:PlexViewResponse), в поиске необходимо передать корректные привязки пространств имён.

Надёжный способ получить атрибуты с пространствами имён

Самый безопасный приём — собрать все привязки пространств имён, встречающиеся в документе, использовать их при поиске, а затем читать значение атрибута через .get.

from lxml import etree as lx

# Разберём SOAP‑документ из файла
xml_root = lx.parse("soap.xml").getroot()

def gather_prefixes(root_node):
    ns_map = {}
    for n in root_node.iter():
        ns_map.update(n.nsmap)
    return ns_map

ns_bindings = gather_prefixes(xml_root)
# При желании посмотреть, что собрали
# print(ns_bindings)

resp_el = xml_root.find(".//api:PlexViewResponse", namespaces=ns_bindings)
status_value = resp_el.get("Status")
print("PlexViewResponse Status:", status_value)

# Если в ответе может быть и атрибут Error
error_value = resp_el.get("Error")
print("PlexViewResponse Error:", error_value)

Этот код выводит ожидаемое значение Status (в примере выше это SUCCESS). Если присутствует Error, вы получите и его; если нет — .get вернёт None.

Альтернатива: выбор атрибута через XPath

Если предпочитаете XPath, сразу обращайтесь к оси атрибутов. Такой запрос лаконичен и явно показывает, что читается атрибут, а не текст элемента.

# Предположим, xml_tree — разобранное дерево lxml, а ns_bindings содержит карту пространств имён
attr_vals = xml_root.xpath("//api:PlexViewResponse/@Status", namespaces=ns_bindings)
status_value = attr_vals[0] if attr_vals else None
print("PlexViewResponse Status via XPath:", status_value)

Почему это важно

В интеграциях на SOAP атрибуты часто сигнализируют о результате и несут метаданные. Путать их с элементами — значит получать хрупкий код и тихие сбои. Корректная работа с пространствами имён и доступом к атрибутам делает интеграцию детерминированной и удобной для отладки, особенно когда в документе встречаются несколько префиксов или переопределения.

Что запомнить

Когда нужно значения вроде Status из api:PlexViewResponse, сначала разрешайте префиксы пространств имён, которые встречаются в XML, затем находите элемент с этими привязками и уже после этого читайте атрибут через .get. Для коротких однострочных решений используйте XPath, который выбирает атрибут напрямую. Придерживаясь этих приёмов, вы экономите время и избегаете тонких ошибок парсинга в продуктивных SOAP‑сценариях.