通过“ ElementTree”在 Python 中使用名称空间解析 XML

我想使用 Python 的 ElementTree解析以下 XML:

<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标签。

186547 次浏览

您需要为 .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字典中查找相应的名称空间 URL,然后更改搜索以查找 XPath 表达式 {http://www.w3.org/2002/07/owl}Class。当然,你自己也可以使用相同的语法:

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

另请参阅 ElementTree 文档的 < em > 使用命名空间解析 XML

如果可以切换到 lxml,那就更好了; 这个库支持相同的 ElementTree API,但是在元素的 .nsmap属性中为您收集名称空间,并且通常具有更好的名称空间支持。

下面介绍如何使用 lxml 实现这一点,而不必对名称空间进行硬编码或扫描文本(正如 Martijn Pieters 提到的那样) :

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

更新 :

5年过去了,我仍然遇到这个问题的各种变化。Lxml 可以提供帮助,如上所示,但不是在所有情况下。当涉及到合并文档时,评论者可能有一个关于这种技术的有效观点,但是我认为大多数人在简单地搜索文档时遇到了困难。

下面是另一个案子,以及我是如何处理的:

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

没有前缀的 xmlns 意味着无前缀标记获得这个默认名称空间。这意味着在搜索 Tag2时,需要包含名称空间才能找到它。然而,lxml 创建了一个 nsmap 条目,其中的关键字是 Nothing,我找不到搜索它的方法。因此,我创建了一个新的名称空间字典,如下所示

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)

注意 : 这个答案对于 Python 的 ElementTree 标准库非常有用,不需要使用硬编码的名称空间。

要从 XML 数据中提取名称空间的前缀和 URI,可以使用 ElementTree.iterparse函数,只解析名称空间启动事件(开始) :

>>> 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)

我一直在使用类似的代码,并发现它总是值得阅读的文档... 一如既往!

Findall ()将只找到 当前标记的直接子级的元素。

尝试让代码使用以下内容可能是值得的,特别是如果您正在处理大而复杂的 xml 文件,因此也包含了子元素(等等)。 如果您知道自己的 xml 中的元素在哪里,那么我想应该没问题!我只是觉得这值得记住。

root.iter()

Ref: < a href = “ https://docs.python.org/3/library/xml.etree.elementtree.html # find-interest-element”rel = “ noReferrer”> https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements ”Element.findall ()只查找带有标记的元素,这些标记是当前元素的直接子元素。Find ()找到具有特定标记的第一个子元素,Element.text 访问元素的文本内容。Get ()访问元素的属性:

要获得名称空间的名称空间格式,例如 {myNameSpace},您可以执行以下操作:

root = tree.getroot()
ns = re.match(r'{.*}', root.tag).group(0)

这样,您可以在以后的代码中使用它来查找节点,例如使用字符串插值(Python 3)。

link = root.find(f"{ns}link")

我的解决方案是基于@Martijn Pieters 的评论:

register_namespace只影响序列化,而不影响搜索。

所以这里的技巧是使用不同的字典进行序列化和搜索。

namespaces = {
'': 'http://www.example.com/default-schema',
'spec': 'http://www.example.com/specialized-schema',
}

现在,注册所有用于解析和编写的名称空间:

for name, value in namespaces.iteritems():
ET.register_namespace(name, value)

为了搜索(find()findall()iterfind()) ,我们需要一个非空前缀。向这些函数传递一个修改后的 dictionary (这里我修改了原始 dictionary,但是这必须在注册名称空间之后才能执行)。

self.namespaces['default'] = self.namespaces['']

现在,来自 find()家族的函数可以使用 default前缀:

print root.find('default:myelem', namespaces)

但是

tree.write(destination)

不对默认命名空间中的元素使用任何前缀。

这基本上是 Davide Brunato 的答案,但是我发现他的答案有严重的问题,缺省名称空间是空字符串,至少在我的 python 3.6安装中是这样。我从他的代码中提炼出来的,对我有用的函数如下:

from io import StringIO
from xml.etree import ElementTree
def get_namespaces(xml_string):
namespaces = dict([
node for _, node in ElementTree.iterparse(
StringIO(xml_string), events=['start-ns']
)
])
namespaces["ns0"] = namespaces[""]
return namespaces

其中 ns0只是空名称空间的占位符,可以用任意随机字符串替换它。

如果我这样做:

my_namespaces = get_namespaces(my_schema)
root.findall('ns0:SomeTagWithDefaultNamespace', my_namespaces)

它还为使用默认名称空间的标记生成正确答案。