【问题标题】:How to set Pandas read_xml to specific node?如何将 Pandas read_xml 设置为特定节点?
【发布时间】:2021-08-11 11:21:35
【问题描述】:

我正在向 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>

这是我在 Jupyter Notebook 中漂亮打印后复制粘贴的 API 响应。

如果我只是通过 print(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 设置为查找“record”而不是“records”,甚至是“dc:title”,但到目前为止,我总是以添加 xpath 属性后立即显示错误消息。我究竟做错了什么?我怀疑它与正确的命名空间有关,但无法弄清楚它是什么......非常感谢任何帮助!

【问题讨论】:

  • 您问题中的示例 xml 格式不正确。您能否编辑问题并验证您有一个简化的、格式正确的 xml 示例以及该示例的预期输出?这将使尝试回答变得更加容易。
  • 您好,谢谢您,我现在已经在文本中包含了关于 xml-response 的解释并对其进行了一些编辑 - 它确实应该是格式正确的,但我没有复制整个内容,而是只是开始。
  • 还没有。首先,您仍然需要添加所需输出的样本。其次,您应该通过 XML 验证器 (for example, this one) 运行您的 xml 示例,并确保它没有显示错误。
  • 我会看看所需的输出,但我无法对 xml 本身做任何事情,因为这是我从 API 得到的?
  • “这是我从 API 中得到的”——不太可能;再次检查您的r1

标签: python pandas xml dataframe xpath


【解决方案1】:

作为read_xml()documentation says:

注意: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...

【讨论】:

  • 非常感谢您的回答 - 澄清一下:您认为 pd.read_xml 无法与这种特定的 xml 结构一起使用吗?我希望这比使用循环和单独提取标题、日期等更容易。
  • @ssp24 - 恐怕是这样。我实际上尝试了很长一段时间来使用read_xml(),但最终放弃了。我已经看到与深度嵌套的 html 和 read_html() 类似的问题。也许更聪明或更有经验的人可以想出一个办法。但正如注意到的那样,即使是文档也告诉你,有时你必须放弃......
  • 非常感谢您的尝试!我刚刚通过 xmltodict 找到了一个替代方案,它有助于生成比 read_xml 更好看的数据帧,但仍位于节点的顶层。有没有办法从 xml 中删除某个“级别”的节点?
  • @ssp24 可能,但这应该是一个单独问题的主题。同时,如果我们完成了这个,请不要忘记接受答案。
猜你喜欢
  • 2016-05-04
  • 2018-02-20
  • 2014-09-04
  • 1970-01-01
  • 2010-10-30
  • 1970-01-01
  • 1970-01-01
  • 2018-09-25
  • 2011-08-18
相关资源
最近更新 更多