通过'ElementTree'在Python中解析带命名空间的XML

199 投票
8 回答
212287 浏览
提问于 2025-04-17 15:44

我有以下的XML文件,我想用Python的ElementTree来解析它:

<rdf:RDF xml:base="http://dbpedia.org/ontology/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:owl="http://www.w3.org/2002/07/owl#"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xmlns="http://dbpedia.org/ontology/">

    <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
        <rdfs:label xml:lang="en">basketball league</rdfs:label>
        <rdfs:comment xml:lang="en">
          a group of sports teams that compete against each other
          in Basketball
        </rdfs:comment>
    </owl:Class>

</rdf:RDF>

我想找到所有的owl:Class标签,然后提取里面所有rdfs:label的值。我正在使用以下代码:

tree = ET.parse("filename")
root = tree.getroot()
root.findall('owl:Class')

由于有命名空间,我遇到了以下错误。

SyntaxError: prefix 'owl' not found in prefix map

我尝试阅读了这个文档:http://effbot.org/zone/element-namespaces.htm,但由于上面的XML有多个嵌套的命名空间,我还是无法让它正常工作。

请告诉我如何修改代码,以找到所有的owl:Class标签。

8 个回答

46

注意: 这是一个关于Python的ElementTree标准库的有用回答,且不使用硬编码的命名空间。

要从XML数据中提取命名空间的前缀和URI,你可以使用ElementTree.iterparse这个函数,只解析命名空间开始事件(start-ns):

>>> from io import StringIO
>>> from xml.etree import ElementTree
>>> my_schema = u'''<rdf:RDF xml:base="http://dbpedia.org/ontology/"
...     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
...     xmlns:owl="http://www.w3.org/2002/07/owl#"
...     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
...     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
...     xmlns="http://dbpedia.org/ontology/">
... 
...     <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
...         <rdfs:label xml:lang="en">basketball league</rdfs:label>
...         <rdfs:comment xml:lang="en">
...           a group of sports teams that compete against each other
...           in Basketball
...         </rdfs:comment>
...     </owl:Class>
... 
... </rdf:RDF>'''
>>> my_namespaces = dict([
...     node for _, node in ElementTree.iterparse(
...         StringIO(my_schema), events=['start-ns']
...     )
... ])
>>> from pprint import pprint
>>> pprint(my_namespaces)
{'': 'http://dbpedia.org/ontology/',
 'owl': 'http://www.w3.org/2002/07/owl#',
 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
 'xsd': 'http://www.w3.org/2001/XMLSchema#'}

然后可以将这个字典作为参数传递给搜索函数:

root.findall('owl:Class', my_namespaces)
68

下面是如何使用lxml来处理这个问题,而不需要手动写入命名空间或在文本中查找它们(正如Martijn Pieters提到的那样):

from lxml import etree
tree = etree.parse("filename")
root = tree.getroot()
root.findall('owl:Class', root.nsmap)

更新

五年后,我仍然遇到这个问题的不同变种。lxml在我上面展示的情况下确实有帮助,但并不是在所有情况下都有效。评论者可能对合并文档时这种技术有合理的看法,但我觉得大多数人只是想简单地搜索文档。

这里有另一个案例,以及我是如何处理的:

<?xml version="1.0" ?><Tag1 xmlns="http://www.mynamespace.com/prefix">
<Tag2>content</Tag2></Tag1>

没有前缀的xmlns意味着没有前缀的标签会使用这个默认的命名空间。这意味着当你搜索Tag2时,需要包含命名空间才能找到它。然而,lxml会创建一个nsmap条目,键为None,我找不到搜索它的方法。所以,我创建了一个新的命名空间字典,如下所示:

namespaces = {}
# response uses a default namespace, and tags don't mention it
# create a new ns map using an identifier of our choice
for k,v in root.nsmap.iteritems():
    if not k:
        namespaces['myprefix'] = v
e = root.find('myprefix:Tag2', namespaces)
265

你需要给 .find()findall()iterfind() 这些方法提供一个明确的命名空间字典:

namespaces = {'owl': 'http://www.w3.org/2002/07/owl#'} # add more as needed

root.findall('owl:Class', namespaces)

前缀 会在你传入的 namespaces 参数中查找。这意味着你可以使用任何你喜欢的命名空间前缀;API 会把 owl: 这一部分分开,然后在 namespaces 字典中查找对应的命名空间网址,接着把搜索改为查找 XPath 表达式 {http://www.w3.org/2002/07/owl}Class。当然,你自己也可以使用相同的语法:

root.findall('{http://www.w3.org/2002/07/owl#}Class')

另外,可以查看 使用命名空间解析 XML 这一部分 的 ElementTree 文档。

如果你能切换到 lxml,情况会更好;这个库支持相同的 ElementTree API,但会在元素的 .nsmap 属性中为你收集命名空间,并且通常对命名空间的支持更强。

撰写回答