Как использовать lxml для захвата определенных частей XML-документа?

Я использую API Amazon для получения информации о книгах. Я пытаюсь использовать lxml для извлечения определенных частей документа XML, которые необходимы для моего приложения. Однако я не совсем уверен, как использовать lxml. Это насколько я понял:

root = etree.XML(response)

Чтобы создать объект etree для XML-документа.

Вот как выглядит XML-документ: http://pastebin.com/GziDkf1a На самом деле существует несколько "Элементов" , но я вставил только один из них, чтобы дать вам конкретный пример. Для каждого элемента я хочу извлечь заголовок и ISBN. Как мне сделать это с объектом etree, который у меня есть?

<ItemSearchResponse><Items><Item><ItemAttributes><Title>I want this info</Title></ItemAttributes></Item></Items></ItemSearchResponse

<ItemSearchResponse><Items><Item><ItemAttributes><ISBN>And I want this info</ISBN></ItemAttributes></Item></Items></ItemSearchResponse

По сути, я не знаю, как перемещаться по дереву с помощью моего объекта etree, и я хочу научиться этому.

Редактировать 1: я пробую следующий код:

tree = etree.fromstring(response)
for item in tree.iterfind(".//"+AMAZON_NS+"ItemAttributes"):
    print(item)
    print(item.items()) # Apparently, there is nothing in item.items()
    for key, value in item.items():
        print(key)
        print(value)

Но я получаю следующий вывод: http://dpaste.com/287496/

Я добавил печать (item.items()), и это просто пустой список. Хотя каждый предмет является Элементом, но по какой-то причине у них нет предметов.

Изменить 2: я могу использовать следующий код для получения нужной информации, но похоже, что у lxml должен быть более простой способ... (этот способ не кажется очень эффективным):

for item in tree.iterfind(".//"+AMAZON_NS+"ItemAttributes"):
    title_text = ""
    author_text = ""
    isbn_text = ""
    for isbn in item.iterfind(".//"+AMAZON_NS+"ISBN"):
        isbn_text = isbn.text
    for title in item.iterfind(".//"+AMAZON_NS+"Title"):
        title_text = title.text
    for author in item.iterfind(".//"+AMAZON_NS+"Author"):
        author_text = author.text
    print(title_text + " by " + author_text + " has ISBN: " + isbn_text)

person Kelp    schedule 16.12.2010    source источник
comment
Здравствуйте, привет, ответ, который вы приняли, не работает. Смотрите мой комментарий к этому ответу. Обратите внимание, что я предоставил проверенный рабочий ответ.   -  person John Machin    schedule 17.12.2010


Ответы (4)


Поскольку вы получаете весь ответ в виде одной большой строки XML, вы можете использовать метод 'fromstring' в lxml, чтобы преобразовать его в полный объект ElementTree. Затем вы можете использовать функцию findall (или на самом деле, поскольку вы хотите перебирать результаты, функцию iterfind), но есть одна загвоздка: XML-ответы Amazon имеют пространство имен, поэтому вы должны учитывать это, чтобы библиотеки lxml правильно его искать. Что-то вроде этого должно помочь:

root=etree.fromstring(responseFromAmazon)

# this creates a constant with the namespace in the form that lxml can use it
AMAZON_NS="{http://webservices.amazon.com/AWSECommerceService/2009-10-01}"

# this searches the tree and iterates over results, taking the namespace into account
for eachitem in root.iterfind(".//"+AMAZON_NS+"ItemAttributes"):
   for key,value in eachitem.items():
        if key == 'ISBN':
              # Do your stuff
        if key == 'Title':
              # Do your stuff

ИЗМЕНИТЬ 1

Посмотрите, работает ли это лучше:

root=etree.fromstring(responseFromAmazon)
AMAZON_NS="{http://webservices.amazon.com/AWSECommerceService/2009-10-01}"
item={}    
for attr in root.iterfind(".//"+AMAZON_NS+"ItemAttributes"):
     item[attr[0].tag.replace(AMAZON_NS,"")]=attr[0].text

Затем вы можете получить доступ к элементу ["Название"], элементу ["ISBN"] и т. д. по мере необходимости.

person jlmcdonald    schedule 16.12.2010
comment
Привет, по какой-то причине каждый элемент в цикле for возвращает пустой список только тогда, когда я запускаю для него .items(). Я отредактировал основной пост, чтобы указать это. Новое содержимое находится под текстом Edit 1:. - person Kelp; 17.12.2010
comment
Моя ошибка... Я недостаточно внимательно изучил схему Amazon WS, поэтому мой пример не был полностью завершен. Когда вы выполняете первоначальный вызов функции iterfind, каждый результат имеет прямой доступ к дочернему элементу без повторной итерации. Возможно, эффективный способ справиться с тем, что вы пытаетесь, будет работать с чем-то вроде того, что я добавил в свое редактирование 1 выше. - person jlmcdonald; 17.12.2010
comment
FAIL ... в конце этого кода print(item) выдает {'Author': 'Michael Sipser'} ... он получает только первый дочерний элемент элемента, найденного iterfind. - person John Machin; 17.12.2010
comment
Я работал, исходя из предположения, что существует несколько элементов ‹ItemAttributes›, и у каждого из них есть один дочерний элемент (учитывая, что это было введено в поле кода в исходном сообщении). Решение Джона Мачина более корректно обрабатывает XML-текст, вставленный в ссылку pastebin, поскольку оно обрабатывает несколько дочерних элементов ‹ItemAttributes›. - person jlmcdonald; 17.12.2010

Это протестировано для работы как с lxml.etree, так и с xml.etree.cElementTree под управлением Python 2.7.1.

import lxml.etree as ET
# Also works with cElementTree (included in recent standard CPythons).
# Use this import:
# import xml.etree.cElementTree as ET
t = ET.fromstring(xmlstring) # your data -- with 2 missing tags added at the end :-)
AMAZON_NS = "{http://webservices.amazon.com/AWSECommerceService/2009-10-01}"
# Find all ItemAttributes elements.
for ia in t.iter(AMAZON_NS+'ItemAttributes'):
    # An ItemAttributes element has *children* named ISBN, Title, Author, etc.
    # NOTE WELL: *children* not *attributes*
    for tag in ('ISBN', 'Title'):
        # Find the first child with that name ...
        elem = ia.find(AMAZON_NS+tag)
        print "%s: %r" % (tag, elem.text)

Вывод:

ISBN: '0534950973'
Title: 'Introduction to the Theory of Computation'

Если вы хотите создать словарь всех дочерних элементов узла ItemAttributes, потребуется лишь небольшая вариация:

import lxml.etree as ET
# Also works with cElementTree (included in recent standard CPythons).
# Use this import:
# import xml.etree.cElementTree as ET
from pprint import pprint as pp
t = ET.fromstring(xmlstring)
AMAZON_NS = "{http://webservices.amazon.com/AWSECommerceService/2009-10-01}"
TAGPOS = len(AMAZON_NS)
# Find all ItemAttributes elements.
for ia in t.iter(AMAZON_NS+'ItemAttributes'):
    item = {}
    # Iterate over all the children of the ItemAttributes node
    for elem in ia:
        # remove namespace stuff from key, remove extraneous whitepace from value
        item[elem.tag[TAGPOS:]] = elem.text.strip()
    pp(item)

и вывод:

{'Author': 'Michael Sipser',
 'Binding': 'Hardcover',
 'DeweyDecimalNumber': '511.35',
 'EAN': '9780534950972',
 'Edition': '2',
 'ISBN': '0534950973',
 'IsEligibleForTradeIn': '1',
 'Label': 'Course Technology',
 'Languages': '',
 'ListPrice': '',
 'Manufacturer': 'Course Technology',
 'NumberOfItems': '1',
 'NumberOfPages': '400',
 'PackageDimensions': '',
 'ProductGroup': 'Book',
 'ProductTypeName': 'ABIS_BOOK',
 'PublicationDate': '2005-02-15',
 'Publisher': 'Course Technology',
 'Studio': 'Course Technology',
 'Title': 'Introduction to the Theory of Computation',
 'TradeInValue': ''}
person John Machin    schedule 17.12.2010

Я бы рекомендовал сначала использовать pyaws. Тогда вам не придется беспокоиться о синтаксическом анализе XML. Если нет, вы можете использовать что-то для эффекта:

from lxml import etree

tree = etree.parse(xmlResponse)
tree.xpath('//ISBN')[0].text
person Dan    schedule 16.12.2010
comment
Привет, мне действительно нужно использовать несколько API, поэтому я пытаюсь найти общий способ обработки API одного веб-сайта (в данном случае Amazon), чтобы я мог применить его к другим. Попробую ваш метод и отпишусь! - person Kelp; 16.12.2010

from lxml import etree
root = etree.XML("YourXMLData")  
items = root.findall('.//ItemAttributes')
for eachitem in items:
    for key,value in eachitem.items():
        if key == 'ISBN':
              # Do your stuff
        if key == 'Title':
              # Do your stuff

Это один из способов сделать это. Вы можете поиграть с этим, при этом вместо загрузки XML в виде строки вы можете использовать метод синтаксического анализа. Но ключевой момент заключается в использовании метода find и его друзей, чтобы перейти к вашему конкретному узлу, а затем выполнить итерацию по словарю узла.

person Senthil Kumaran    schedule 16.12.2010
comment
Привет, когда я делаю items = root.findall('.//ItemAttributes'), я получаю только пустой список элементов. - person Kelp; 16.12.2010
comment
Вместо findall сделайте find и посмотрите, что вы получите. Кроме того, выполните поиск на его родительском узле и посмотрите результат. Согласно вашему XML, вы сможете добраться до точного узла. Даже мне пришлось несколько раз поиграться с поиском узлов. Учебник lxml содержит некоторую информацию о поиске. - person Senthil Kumaran; 16.12.2010
comment
Хм, если я ищу любой из тегов узлов, все они отображаются как None: dpaste.com/287206 - person Kelp; 16.12.2010
comment
Я вижу, вы получаете объект Element, у вас должен быть объект ElementTree, чтобы использовать find. корень = etree.parse(some_file_like) - person Senthil Kumaran; 16.12.2010
comment
Здравствуйте, я использовал etree.ElementTree() для преобразования моего объекта Element в ElementTree, но мне все еще не хватает: dpaste. ком/287238 - person Kelp; 16.12.2010
comment
Я думаю, вам следует попробовать предложение jlmcdonald о включении пространства имен в метод поиска. - person Senthil Kumaran; 16.12.2010