Как да използвате 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/

Добавих print(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() върху него. Редактирах основната публикация, за да посоча това. Новото съдържание е под текста Редактиране 1:. - person Kelp; 17.12.2010
comment
Моя грешка ... Не бях разгледал схемата на Amazon WS достатъчно добре, така че примерът ми не беше напълно пълен. Когато извършите първоначалното извикване на функцията iterfind, всеки резултат има директен достъп до дъщерния елемент, без да се налага повторна итерация. Може би ефикасен начин да се справите с това, което се опитвате, ще работи с нещо като това, което съм поставил в моята редакция 1 по-горе. - person jlmcdonald; 17.12.2010
comment
ГРЕШКА ... в края на този код, print(item) произвежда {'Author': 'Michael Sipser'} ... получава само първото дете на елемента, намерен от iterfind. - person John Machin; 17.12.2010
comment
Работех при предположението, че има множество елементи ‹ItemAttributes› и всеки има едно дете (предвид това, което беше въведено в полето за код в първоначалния пост). Решението на John Machin обработва по-правилно 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
Вместо намиране, направете намиране и вижте какво получавате. Освен това направете намиране на неговия родителски възел и вижте резултата. Според вашия XML трябва да можете да достигнете до точния възел. Дори аз трябваше да играя около намирането на възел няколко пъти. lxml урокът съдържа информация за намирането. - person Senthil Kumaran; 16.12.2010
comment
Хм, ако търся в някой от маркерите на възлите, всички излизат като None: dpaste.com/287206 - person Kelp; 16.12.2010
comment
Виждам, че получавате Element Object, трябва да имате ElementTree обект, за да използвате find. root = etree.parse(some_file_like) - person Senthil Kumaran; 16.12.2010
comment
Здравейте, използвах etree.ElementTree(), за да преобразувам моя Element Object в ElementTree, но все още не достигам: dpaste. com/287238 - person Kelp; 16.12.2010
comment
Мисля, че трябва да опитате предложението на jlmcdonald за включване на пространството от имена в метода за намиране. - person Senthil Kumaran; 16.12.2010