#python #pandas #xml #dataframe #xpath
Вопрос:
Я отправляю запрос в API и получаю ответ xml, который я хотел бы проанализировать в фрейм данных. Недавно я наткнулся на опцию pd.read_xml, и до сих пор у меня было несколько попыток, но, похоже, я не могу заставить ее работать должным образом.
Мой xml выглядит примерно так:
<?xml version="1.0" encoding="UTF-8"?>
<html>
<body>
<searchretrieveresponse xmlns="http://www.loc.gov/zing/srw/">
<version>
1.1
</version>
<numberofrecords>
1
</numberofrecords>
<records>
<record>
<recordschema>
oai_dc
</recordschema>
<recordpacking>
xml
</recordpacking>
<recorddata>
<dc xmlns="http://www.openarchives.org/OAI/2.0/oai_dc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dnb="http://d-nb.de/standards/dnbterms" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dc:title>
[Erg.-H.]. Myst IV Revelation
</dc:title>
<dc:date>
2004
</dc:date>
<dc:identifier xmlns:tel="http://krait.kb.nl/coop/tel/handbook/telterms.html" xsi:type="tel:ISBN">
978-3-8272-9125-7 kart. : EUR 16.95, EUR 17.50 (AT)
</dc:identifier>
<dc:identifier xmlns:tel="http://krait.kb.nl/coop/tel/handbook/telterms.html" xsi:type="tel:ISBN">
3-8272-9125-9 kart. : EUR 16.95, EUR 17.50 (AT)
</dc:identifier>
<dc:identifier xsi:type="dnb:IDN">
97274004X
</dc:identifier>
<dc:format>
32 S.
</dc:format>
<dc:relation>
http://d-nb.info/973086416
</dc:relation>
</dc>
</recorddata>
<recordposition>
1
</recordposition>
</record>
</records>
<nextrecordposition>
2
</nextrecordposition>
<echoedsearchretrieverequest>
<version>
1.1
</version>
<query>
978-3-8272-9125-7
</query>
<xquery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true">
</xquery>
<recordschema>
oai_dc
</recordschema>
</echoedsearchretrieverequest>
</searchretrieveresponse>
</body>
</html>
Это копия ответа API, вставленная после того, как я красиво напечатал ее в блокноте Jupyter.
Если я просто распечатаю ответ с помощью печати(r1.content) Я получаю следующее:
b'<?xml version="1.0" encoding="UTF-8"?>n<searchRetrieveResponse xmlns="http://www.loc.gov/zing/srw/"><version>1.1</version><numberOfRecords>1</numberOfRecords><records><record><recordSchema>oai_dc</recordSchema><recordPacking>xml</recordPacking><recordData><dc xmlns:dnb="http://d-nb.de/standards/dnbterms" xmlns="http://www.openarchives.org/OAI/2.0/oai_dc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">n <dc:title>[Erg.-H.]. Myst IV Revelation</dc:title>n <dc:date>2004</dc:date>n <dc:identifier xmlns:tel="http://krait.kb.nl/coop/tel/handbook/telterms.html" xsi:type="tel:ISBN">978-3-8272-9125-7 kart. : EUR 16.95, EUR 17.50 (AT)</dc:identifier>n <dc:identifier xmlns:tel="http://krait.kb.nl/coop/tel/handbook/telterms.html" xsi:type="tel:ISBN">3-8272-9125-9 kart. : EUR 16.95, EUR 17.50 (AT)</dc:identifier>n <dc:identifier xsi:type="dnb:IDN">97274004X</dc:identifier>n <dc:format>32 S.</dc:format>n <dc:relation>http://d-nb.info/973086416</dc:relation>n</dc></recordData><recordPosition>1</recordPosition></record></records><nextRecordPosition>2</nextRecordPosition><echoedSearchRetrieveRequest><version>1.1</version><query>978-3-8272-9125-7</query><xQuery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/><recordSchema>oai_dc</recordSchema></echoedSearchRetrieveRequest></searchRetrieveResponse>'
У меня был некоторый успех со следующим кодом:
df = pd.read_xml(r1.content, namespaces={"xmlns":"http://www.openarchives.org/OAI/2.0/oai_dc/",
"dc": "http://purl.org/dc/elements/1.1/",
"dnb": "http://d-nb.de/standards/dnbterms",
"xsi": "http://www.w3.org/2001/XMLSchema-instance"})
Однако, похоже, это учитывает только верхний уровень, поскольку я получаю такой фрейм данных, как этот:
version numberOfRecords record nextRecordPosition query xQuery recordSchema
0 1.1 NaN NaN NaN None NaN None
1 NaN 15315.0 NaN NaN None NaN None
2 NaN NaN NaN NaN None NaN None
3 NaN NaN NaN 11.0 None NaN None
4 1.1 NaN NaN NaN Händel NaN oai_dc
Поскольку меня интересуют фактические «записи», которые возвращает API, я попробовал следующее:
df = pd.read_xml(r1.content, xpath='.//records', namespaces={"xmlns":"http://www.openarchives.org/OAI/2.0/oai_dc/",
"dc": "http://purl.org/dc/elements/1.1/",
"dnb": "http://d-nb.de/standards/dnbterms",
"xsi": "http://www.w3.org/2001/XMLSchema-instance"})
но я получаю ошибку: ValueError: xpath does not return any nodes. Be sure row level nodes are in xpath. If document uses namespaces denoted with xmlns, be sure to define namespaces and use them in xpath.
В конце концов, мне нужен, особенно для ответов, содержащих несколько записей, фрейм данных, в котором будет указано содержимое записей. Так что это должно выглядеть примерно так:
dc:title dc:date dc:identifier
0 [Erg.-H.]. Myst IV Revelation 2004 978-3-8272-9125-7 kart. : EUR 16.95, EUR 17.50 (AT)
Насколько я знаю, я добавил все пространства имен, а также попытался настроить xpath для поиска «записи» вместо «записей» или даже «dc:заголовок», но до сих пор я всегда получаю сообщение об ошибке, как только добавляю атрибут xpath. Что я делаю не так? Я подозреваю, что это как-то связано с правильным пространством имен, но не могу понять, что это такое… любая помощь очень ценится!
Комментарии:
1. Пример xml в вашем вопросе не очень хорошо сформирован. Можете ли вы отредактировать вопрос и убедиться, что у вас есть упрощенный, хорошо сформированный образец xml и ожидаемые результаты этого образца? Это значительно облегчит попытку ответить.
2. Здравствуйте и спасибо, теперь я включил в текст объяснение xml-ответа и немного отредактировал его — он действительно должен быть хорошо сформирован, но я скопировал не все, а только начало.
3. Ещё нет. Во-первых, вам все равно нужно добавить образец требуемого вывода. Во-вторых, вы должны запустить свой образец xml через XML-валидатор ( например, этот ) и убедиться, что он не содержит ошибок.
4. Я посмотрю необходимые выходные данные, но я ничего не могу сделать с самим xml, потому что это то, что я получаю от API?
5. «это то, что я получаю от API» — очень маловероятно; проверьте
r1
еще раз.
Ответ №1:
Как read_xml()
говорится в документации:
Примечание: Анализатор etree поддерживает ограниченное количество выражений XPath. Для более сложных XPath используйте lxml, который требует установки.
К сожалению, мне кажется, что это одна из тех ситуаций, когда требуется «более сложный» xpath… Итак, давайте использовать lxml:
from lxml import etree
import pandas as pd
rec = """[your xml response above]"""
doc = etree.XML(rec.encode())
#now to deal with those pesky namespaces
ns = {"x":"http://www.loc.gov/zing/srw/", "y":"http://purl.org/dc/elements/1.1/","z":"http://www.w3.org/2001/XMLSchema-instance"}
#we can now look for the data
rows = []
targets = doc.xpath('//x:record',namespaces=ns)
for target in targets:
title = target.xpath('//y:title',namespaces=ns)
date = target.xpath('//y:date',namespaces=ns)
identifier = target.xpath('//y:identifier[@z:type="tel:ISBN"][1]',namespaces=ns)
rows.append([title[0].text.strip(),date[0].text.strip(),identifier[0].text.strip()])
#and, finally, create the dataframe
columns = ['dc:title','dc:date','dc:identifier']
pd.DataFrame(rows,columns=columns)
Выход:
dc:title dc:date dc:identifier
0 [Erg.-H.]. Myst IV Revelation 2004 978-3-8272-9125-7 kart. : EUR 16.95, EUR 17.50...
Комментарии:
1. Большое вам спасибо за ваш ответ — итак, чтобы уточнить: вы думаете, что невозможно будет использовать pd.read_xml с этой конкретной xml-структурой? Я надеялся, что это будет более простой вариант, чем использовать цикл и извлекать заголовок, дату и т. Д. Отдельно.
2. @ssp24 — Боюсь, что да. На самом деле я довольно долго пытался это сделать,
read_xml()
но в конце концов сдался. Я видел подобные проблемы с глубоко вложенным html иread_html()
. Может быть, кто-нибудь поумнее или более опытный сможет придумать способ. Но, как я заметил, даже документация говорит вам, что иногда вам приходится сдаваться…3. Большое вам спасибо за попытку! Я только что нашел альтернативу с помощью xmltodict, которая помогает создавать более привлекательный фрейм данных, чем read_xml, но все еще на верхнем уровне узлов. Есть ли способ удалить определенный «уровень» узлов из xml?
4. @ssp24 Возможно, но это должно быть предметом отдельного вопроса. Тем временем, если мы закончим с этим вопросом, не забудьте принять ответ, пожалуйста.