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


163

我有以下要使用Python解析的XML 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标签。

Answers:


226

ElementTree对名称空间不太聪明。你需要给的.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')

如果可以切换到lxml,那就更好了;该库支持相同的ElementTree API,但会在.nsmap元素的属性中为您收集名称空间。


7
谢谢。知道如何在不进行硬编码的情况下直接从XML获取名称空间吗?或者我怎么能忽略它?我已经尝试过findall('{*} Class'),但在我的情况下将无法正常工作。
Kostanos

7
您必须xmlns自己在树上扫描属性;如答案中所述,lxml为您做到了,xml.etree.ElementTree模块却没有。但是,如果您尝试匹配特定的(已经硬编码的)元素,那么您还尝试匹配特定名称空间中的特定元素。该名称空间将不会在文档之间更改,而元素名称则不会更改。您也可以使用元素名称对其进行硬编码。
马丁·皮特斯

14
@Jon:register_namespace仅影响序列化,不影响搜索。
马丁·皮特斯

5
可能有用的小添加:使用cElementTree代替时ElementTreefindall不会将名称空间用作关键字参数,而只是将其用作常规参数,即use ctree.findall('owl:Class', namespaces)
egpbos 2014年

2
@Bludwarf:文档确实提到了它(现在,如果不是您写的时候),但是您必须仔细阅读它们。请参阅“ 使用命名空间解析XML”部分:这里有一个示例,对比了findall不带namespace参数然后带参数与参数的使用,但是在Element对象部分中,参数未作为方法方法的参数之一提及。
威尔逊F

57

这是使用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创建了一个以None为键的nsmap条目,我找不到搜索它的方法。所以,我像这样创建了一个新的命名空间字典

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)

3
完整的名称空间URL 您应该硬编码的名称空间标识符。本地前缀(owl)可以在文件之间更改。因此,按照此答案的建议进行操作确实不是一个好主意。
Matti Virkkunen

1
如果猫头鹰的定义可以在文件之间更改,那么@MattiVirkkunen就是这样,我们不应该使用每个文件中定义的定义而不是对其进行硬编码吗?
卢瓦克福雷-拉克鲁瓦

@LoïcFaure-Lacroix:通常,XML库会让您抽象出那一部分。您甚至不需要了解或关心文件本身中使用的前缀,只需定义自己的前缀以进行解析即可,也可以只使用完整的名称空间名称。
Matti Virkkunen '16

这个答案帮助我至少能够使用find函数。无需创建自己的前缀。我只是做了key = list(root.nsmap.keys())[0],然后将密钥添加为前缀:root.find(f'{key}:Tag2',root.nsmap)
Eelco van Vliet

30

注意:这是对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)

1
这对那些无法访问lxml且不想对命名空间进行硬编码的人来说非常有用。
delrocco '16

1
我得到了错误:ValueError: write to closed对于这一行filemy_namespaces = dict([node for _, node in ET.iterparse(StringIO(my_schema), events=['start-ns'])])。任何想法都想错吗?
Yuli

该错误可能与类io.StringIO有关,该类拒绝ASCII字符串。我已经使用Python3测试了我的食谱。将unicode字符串前缀'u'添加到示例字符串中,它也适用于Python 2(2.7)。
Davide Brunato '17

作为替代,dict([...])您也可以使用dict理解。
Arminius

取而代之的是StringIO(my_schema)您还可以放置XML文件的文件名。
JustAC0der

6

我一直在使用与此类似的代码,并发现它总是值得阅读文档...像往常一样!

findall()将只查找当前标签的直接子元素。所以,不是全部。

尝试使代码与以下代码一起使用可能会值得您投入,尤其是在处理大型而复杂的xml文件时,还包括子子元素(等)。如果您自己了解xml中元素的位置,那么我想就可以了!只是认为这值得记住。

root.iter()

参考:https : //docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements “ Element.findall()仅查找带有标签的元素,这些标签是当前元素的直接子元素。 Element.find()查找带有特定标记的第一个子元素,然后Element.text访问元素的文本内容。Element.get()访问元素的属性:“


6

以其名称空间格式获取名称空间,例如 {myNameSpace},可以执行以下操作:

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

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

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

0

我的解决方案基于@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())我们需要一个非空前缀。向这些函数传递一个修改后的字典(这里我修改了原始字典,但这必须在注册了名称空间之后才能进行)。

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

现在,该find()系列的功能可以与default前缀一起使用:

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

tree.write(destination)

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.