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

Parsing XML with namespace in Python via 'ElementTree'

提问人:Sudar 提问时间:2/13/2013 最后编辑:stovflSudar 更新时间:7/19/2021 访问量:206418

问:

我有以下 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:Classrdfs: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

python 解析 xml-namespaces elementtree

评论

1赞 mzjn 7/20/2021
从 Python 3.8 开始,命名空间通配符可以与 和 一起使用。请参见 stackoverflow.com/a/62117710/407651find()findall()findtext()

答:

265赞 Martijn Pieters 2/13/2013 #1

您需要为 和 方法提供显式命名空间字典:.find()findall()iterfind()

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

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

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

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

另请参阅 ElementTree 文档的“使用命名空间解析 XML”部分

如果你能切换到 lxml,事情会更好;该库支持相同的 ElementTree API,但在元素的属性中为您收集命名空间,并且通常具有高级命名空间支持。.nsmap

评论

10赞 Kostanos 11/27/2013
谢谢。知道如何直接从 XML 获取命名空间,而无需对其进行硬编码吗?或者我怎么能忽略它?我已经尝试过findall('{*}Class'),但它在我的情况下不起作用。
7赞 Martijn Pieters 11/28/2013
您必须自己扫描树中的属性;如答案中所述,为您执行此操作,但模块不会。但是,如果您尝试匹配特定(已硬编码)元素,则您也在尝试匹配特定命名空间中的特定元素。该命名空间在文档之间的更改不会比元素名称的更改更多。你也可以用元素名称对其进行硬编码。xmlnslxmlxml.etree.ElementTree
15赞 Martijn Pieters 8/20/2014
@Jon:只影响序列化,不影响搜索。register_namespace
5赞 egpbos 9/30/2014
可能有用的小补充:当使用 instead of 时,不会将命名空间作为关键字参数,而只是作为普通参数,即 use .cElementTreeElementTreefindallctree.findall('owl:Class', namespaces)
2赞 Wilson F 6/19/2016
@Bludwarf:文档确实提到了它(现在,如果不是你写的时候),但你必须仔细阅读它们。请参阅使用命名空间解析 XML 部分:有一个示例将 without 和 then 与参数的用法进行了对比,但在 Element 对象部分中,该参数未作为方法方法的参数之一提及。findallnamespace
69赞 Brad Dre 11/8/2014 #2

以下是使用 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>

没有前缀的 xmln 意味着无前缀标记获取此默认命名空间。这意味着当您搜索 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赞 Matti Virkkunen 3/19/2016
完整的命名空间 URL 您应该硬编码的命名空间标识符。本地前缀 () 可以因文件而异。因此,按照这个答案的建议去做是一个非常糟糕的主意。owl
2赞 Loïc Faure-Lacroix 8/1/2016
@MattiVirkkunen,如果 owl 定义可以从一个文件更改到另一个文件,我们难道不应该使用每个文件中定义的定义而不是对其进行硬编码吗?
1赞 Matti Virkkunen 8/5/2016
@LoïcFaure-Lacroix:通常 XML 库会让你把这部分抽象出来。您甚至不需要知道或关心文件本身中使用的前缀,您只需定义自己的前缀即可进行解析,或者仅使用完整的命名空间名称。
0赞 Eelco van Vliet 12/10/2019
这个答案帮助我至少能够使用查找功能。无需创建自己的前缀。我刚刚做了 key = list(root.nsmap.keys())[0],然后将键添加为前缀:root.find(f'{key}:Tag2', root.nsmap)
46赞 Davide Brunato 5/24/2016 #3

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

要从 XML 数据中提取命名空间的前缀和 URI,您可以使用函数,仅解析命名空间启动事件 (start-ns):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)

评论

2赞 delrocco 6/6/2016
这对于我们这些无法访问 lxml 且不想对命名空间进行硬编码的人来说很有用。
1赞 Yuli 2/20/2017
我收到错误:对于这一行.任何想法都想错吗?ValueError: write to closedfilemy_namespaces = dict([node for _, node in ET.iterparse(StringIO(my_schema), events=['start-ns'])])
0赞 Davide Brunato 2/21/2017
该错误可能与类 io 有关。StringIO,拒绝 ASCII 字符串。我已经用 Python3 测试了我的食谱。将 unicode 字符串前缀“u”添加到示例字符串中,它也适用于 Python 2 (2.7)。
0赞 Arminius 11/2/2017
代替你也可以使用字典理解。dict([...])
1赞 tjwrona1992 1/15/2021
这正是我想要的!谢谢!
7赞 MJM 8/16/2016 #4

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

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

尝试让您的代码使用以下内容可能是值得的,尤其是在您处理大型复杂 xml 文件时,以便也包含子子元素(等)。 如果你知道自己的元素在你的xml中的位置,那么我想它会很好!只是觉得这值得记住。

root.iter()

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

评论

3赞 mzjn 12/9/2021
恕我直言,ElementTree 文档有点不清楚且容易误解。可以获得所有后代。而不是 ,请使用 。elem.findall("X")elem.findall(".//X")
7赞 Bram Vanroy 10/1/2018 #5

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

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

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

link = root.find(f"{ns}link")
1赞 peter.slizik 5/30/2019 #6

我的解决方案基于 @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)

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

3赞 Maarten Derickx 4/8/2021 #7

这基本上是 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

where 只是空命名空间的占位符,您可以将其替换为您喜欢的任何随机字符串。ns0

如果我这样做:

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

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