Python 和 libxml2:如何使用 XPATH 遍历 XML 节点

4 投票
4 回答
9441 浏览
提问于 2025-04-16 02:01

我在从一个XML树中获取信息时遇到了问题。

我的XML结构是这样的:

<?xml version="1.0"?>
<records xmlns="http://www.mysyte.com/foo">
  <record>
    <id>first</id>
    <name>john</name>
    <papers>
      <paper>john_1</paper>
      <paper>john_2</paper>
    </papers>
  </record>
  <record>
    <id>second</id>
    <name>mike</name>
    <papers>
      <paper>mike_a</paper>
      <paper>mike_b</paper>
    </papers>
  </record>
  <record>
    <id>third</id>
    <name>albert</name>
    <papers>
      <paper>paper of al</paper>
      <paper>other paper</paper>
    </papers>
  </record>
</records>

我想提取的数据元组大概是这样的:

[{'code': 'first', 'name': 'john'}, 
 {'code': 'second', 'name': 'mike'}, 
 {'code': 'third', 'name': 'albert'}]

现在我写了这段Python代码:

try:
  doc = libxml2.parseDoc(xml)
except (libxml2.parserError, TypeError):
  print "Problems loading XML"

ctxt = doc.xpathNewContext()
ctxt.xpathRegisterNs("pre", "http://www.mysyte.com/foo")

record_nodes = ctxt.xpathEval('/pre:records/pre:record')

for record_node in record_nodes:
  id = record_node.xpathEval('id')[0].content
  name = record_node.xpathEval('name')[0].content
  ret_list.append({'code': id, 'name': name})

我的问题是,我没有得到任何结果,感觉在遍历节点时对XPATH的使用有点问题。

我还尝试了这些用于获取id和name的XPATH:

/id
/name
/record/id
/record/name
/pre:id
/pre:name

等等,但依然没有结果(顺便说一下,如果我在子查询中使用前缀,就会出错)。

有没有什么建议?

4 个回答

0

如果可以切换到 lxml,这里有一种方法可以做到:

import lxml.etree as le
root=le.XML(content)
result=[]
namespaces={'pre':'http://www.mysyte.com/foo'}
for record in root:
    id=record.xpath('pre:id',namespaces=namespaces)[0]
    name=record.xpath('pre:name',namespaces=namespaces)[0]
    result.append({'code':id.text,'name':name.text})
print(result)
# [{'code': 'first', 'name': 'john'}, {'code': 'second', 'name': 'mike'}, {'code': 'third', 'name': 'albert'}]

基于 Dimitre Novatchev 的 XPath 表达式,你可以这样做:

id_name_nodes = iter(ctxt.xpathEval('/pre:records/pre:record/*[self::pre:id or self::pre:name]'))

ret_list=[]
for id,name in zip(id_name_nodes,id_name_nodes):
    ret_list.append({'code':id.content,'name':name.content})
print(ret_list)

这段 libxml2 的代码依赖于每条记录都有一个 id 和 name。如果缺少 idname,那么 ret_list 就会把错误的 id 和 name 配对在一起,而不会报错。相比之下,lxml 的代码在同样的情况下会抛出一个错误。

0

你可以用一个XPath表达式选择所有需要的元素

/pre:records/pre:record/*[self::pre:id or self::pre:name]

然后只需在Python中处理这些选中的节点。

7

这里有个建议。请注意 setContextNode() 这个方法:

import libxml2

xml = "test.xml"
doc = libxml2.parseFile(xml) 

ctxt = doc.xpathNewContext() 
ctxt.xpathRegisterNs("pre","http://www.mysyte.com/foo") 

ret_list = []
record_nodes = ctxt.xpathEval('/pre:records/pre:record') 

for node in record_nodes:
    ctxt.setContextNode(node)
    _id = ctxt.xpathEval('pre:id')[0].content
    name = ctxt.xpathEval('pre:name')[0].content
    ret_list.append({'code': _id, 'name': name}) 

print ret_list

撰写回答