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

Sud*_*dar 148 python xml elementtree xml-namespaces xml-parsing

我有以下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>
Run Code Online (Sandbox Code Playgroud)

我想找到所有owl:Class标签,然后提取其中所有rdfs:label实例的值.我使用以下代码:

tree = ET.parse("filename")
root = tree.getroot()
root.findall('owl:Class')
Run Code Online (Sandbox Code Playgroud)

由于命名空间,我收到以下错误.

SyntaxError: prefix 'owl' not found in prefix map
Run Code Online (Sandbox Code Playgroud)

我尝试在http://effbot.org/zone/element-namespaces.htm上阅读该文档,但由于上述XML具有多个嵌套命名空间,因此我仍然无法正常工作.

请告诉我如何更改代码以查找所有owl:Class标签.

Mar*_*ers 210

ElementTree对名称空间并不太聪明.您需要为.find(),findall()iterfind()方法提供显式命名空间字典.这没有记录得很好:

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

root.findall('owl:Class', namespaces)
Run Code Online (Sandbox Code Playgroud)

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

root.findall('{http://www.w3.org/2002/07/owl#}Class')
Run Code Online (Sandbox Code Playgroud)

如果你可以切换到lxml图书馆,事情会更好; 该库支持相同的ElementTree API,但.nsmap在元素的属性中为您收集名称空间.

  • @Jon:`register_namespace`只会影响序列化,而不会影响搜索. (13认同)
  • 为什么`register_namespace`不起作用? (8认同)
  • 谢谢.任何想法如何直接从XML获取命名空间,而不进行硬编码?或者我怎么能忽略它呢?我已经尝试过findall('{*} Class'),但它不适用于我的情况. (7认同)
  • 您必须自己扫描树中的"xmlns"属性; 如答案所述,`lxml`为你做这个,`xml.etree.ElementTree`模块没有.但是,如果您尝试匹配特定(已经硬编码)的元素,那么您还尝试匹配特定命名空间中的特定元素.该命名空间不会在文档之间进行任何更改,而不是元素名称.您也可以使用元素名称对其进行硬编码. (7认同)
  • 可能有用的小补充:当使用`cElementTree`而不是`ElementTree`时,`findall`不会将名称空间作为关键字参数,而是简单地作为普通参数,即使用`ctree.findall('owl:Class') ,名称空间)`. (5认同)
  • @Bludwarf:文档确实提到了它(现在,如果不是你写的时候),但是你必须仔细阅读它们.请参阅[使用命名空间解析XML](https://docs.python.org/2/library/xml.etree.elementtree.html#parsing-xml-with-namespaces)部分:有一个示例对比`findall的使用`没有然后使用`namespace`参数,但是参数没有作为[Element对象]中方法方法的参数之一被提及(https://docs.python.org/2/library/xml.etree .elementtree.html#element-objects)部分. (2认同)

Bra*_*Dre 55

以下是如何使用lxml执行此操作而无需对命名空间进行硬编码或扫描文本(如Martijn Pieters所提到的):

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

  • 完整的命名空间URL*是*您应该硬编码的命名空间标识符.本地前缀(`owl`)可以在文件之间更改.因此,做这个答案表明这是一个非常糟糕的主意. (2认同)
  • @MattiVirkkunen 如果 owl 定义可以从一个文件更改到另一个文件,我们是否应该使用每个文件中定义的定义而不是对其进行硬编码? (2认同)

Dav*_*ato 25

注意:这是一个对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#'}
Run Code Online (Sandbox Code Playgroud)

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

root.findall('owl:Class', my_namespaces)
Run Code Online (Sandbox Code Playgroud)

  • 这对于我们这些无法访问 lxml 并且不想硬编码命名空间的人很有用。 (2认同)

Bra*_*roy 7

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

root = tree.getroot()
ns = re.match(r'{.*}', root.tag).group(0)
Run Code Online (Sandbox Code Playgroud)

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

link = root.find(f"{ns}link")
Run Code Online (Sandbox Code Playgroud)


MJM*_*MJM 6

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

findall()只会找到当前标记的直接子元素.所以,不是全部.

在尝试使用以下代码时,可能值得您使用,特别是如果您正在处理大而复杂的xml文件,以便还包括子子元素(等).如果你知道你的xml中的元素在哪里,那么我想它会没事的!只是觉得这值得记住.

root.iter()
Run Code Online (Sandbox Code Playgroud)

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

  • 恕我直言,ElementTree 文档有点不清楚且容易误解。**有**可能获得所有后代。使用 `elem.findall(".//X")` 代替 `elem.findall("X")`。 (3认同)