2

我可以使用 lxml 来完成我想做的大部分事情,尽管阅读令人困惑的示例和教程很困难。简而言之,我能够读取外部 xml 文件并通过 lxml 将其导入正确的树状格式。

为了证明这一点,如果我输入:

print(etree.tostring(myXmlTree, pretty_print= True, method= "xml") )

我得到以下输出:

<net xmlns="http://www.arin.net/whoisrws/core/v1" xmlns:ns2="http://www.arin.net/whoisrws/rdns/v1" xmlns:ns3="http://www.arin.net/whoisrws/netref/v2" termsOfUse="https://www.arin.net/whois_tou.html">
 <registrationDate>2006-08-29T00:00:00-04:00</registrationDate>
 <ref>http://whois.arin.net/rest/net/NET-79-0-0-0-1</ref>
 <endAddress>79.255.255.255</endAddress>
 <handle>NET-79-0-0-0-1</handle>
 <name>79-RIPE</name>
 <netBlocks>
  <netBlock>
   <cidrLength>8</cidrLength>
   <endAddress>79.255.255.255</endAddress>
   <description>Allocated to RIPE NCC</description>
   <type>RN</type>
   <startAddress>79.0.0.0</startAddress>
  </netBlock>
 </netBlocks>
 <orgRef name="RIPE Network Coordination Centre" handle="RIPE">http://whois.arin.net/rest/org/RIPE</orgRef>
 <comment>
  <line number="0">These addresses have been further assigned to users in</line>
  <line number="1">the RIPE NCC region. Contact information can be found in</line>
  <line number="2">the RIPE database at http://www.ripe.net/whois</line>
 </comment>
 <startAddress>79.0.0.0</startAddress>
 <updateDate>2009-05-18T07:34:02-04:00</updateDate>
 <version>4</version>
</net>

好的,这对人类消费很好,但对机器没有用。如果我想要特定的元素,比如 xml 中的开始和结束 IP 地址,我可以输入:

ns = myXmlTree.nsmap.values()[0]
myXmlTree.findall("{" + ns + "}startAddress")[0].text
myXmlTree.findall("{" + ns + "}endAddress")[0].text

我会收到:

'79.0.0.0'
'79.255.255.255'

但是我仍然需要以人类的身份查看 xml 文件才能知道那里有哪些元素。相反,我希望能够检索特定级别的所有元素的名称,然后自动遍历该级别。因此,例如,我想做类似的事情:

myElements = myXmlTree.findallelements("{" + ns + "}")

它会给我一个返回值,例如:

['registrationDate', 'ref', 'endAddress', 'handle', 'name', 'netBlocks', 'orgRef', 'comment', 'startAddress', 'updateDate', 'version']

如果它可以告诉我元素的整个结构,包括嵌套结构,那就特别棒了。

我敢肯定有办法,否则就没有意义了。

提前致谢!!

PS,我知道我可以迭代并遍历所有迭代的列表。我希望 lxml 中已经有一个包含这些数据的方法。如果迭代是唯一的方法,我想那没关系……对我来说似乎很笨拙。

4

1 回答 1

5

相信你正在寻找element.xpath()

XPath不是lxml由许多处理 XML 的事物支持的 XML 文档中选择节点的通用查询语言引入的概念。把它想象成类似于 CSS 选择器的东西,但更强大(也更复杂一些)。请参阅XPath 语法

您的文档使用命名空间 - 我暂时忽略它,并在文章末尾解释如何处理它们,因为这样可以使示例更具可读性。(但它们不会按原样用于您的文档)。

所以,例如,

tree.xpath('/net/endAddress')

将直接选择节点<endAddress>79.255.255.255</endAddress>下方的元素。<net />但不是<endAddress />里面的<netBlock>

XPath 表达式

tree.xpath('//endAddress')

但是会选择<endAddress />文档中任何位置的所有节点。

您当然可以进一步查询使用 XPath 表达式返回的节点:

netblocks = tree.xpath('/net/netBlocks/netBlock')
for netblock in netblocks:
    start = netblock.xpath('./startAddress/text()')[0]
    end = netblock.xpath('./endAddress/text()')[0]
    print "%s - %s" % (start, end)

会给你

79.0.0.0 - 79.255.255.255

请注意,它.xpath()总是返回一个选定节点的列表——所以如果你只想要一个,请考虑到这一点。

您还可以按属性选择元素:

comment = tree.xpath('/net/comment')[0]
line_2 = comment.xpath("./line[@number='2']")[0]

这将从第一条评论中选择<line />元素。number="2"

您还可以自己选择属性:

numbers = tree.xpath('//line/attribute::number')

['0', '1', '2']

要获取您上次询问的元素名称列表,您可以执行以下操作:

names = [node.tag for node in tree.xpath('/net/*')]

['registrationDate', 'ref', 'endAddress', 'handle', 'name', 'netBlocks', 'orgRef', 'comment', 'startAddress', 'updateDate', 'version']

但考虑到 XPath 的强大功能,最好只查询文档以获取您想从中了解的内容,具体或松散,只要您认为合适。

现在,命名空间。正如您所注意到的,如果您的文档使用 XML 名称空间,您需要在很多地方考虑到这一点,XPath 也不例外。查询命名空间文档时,您将xpath()命名空间映射传递给该方法,如下所示:

NSMAP = {'ns':  'http://www.arin.net/whoisrws/core/v1',
         'ns2': 'http://www.arin.net/whoisrws/rdns/v1',
         'ns3': 'http://www.arin.net/whoisrws/netref/v2'}

names = [node.tag for node in tree.xpath('/ns:net/*', namespaces=NSMAP)]

在许多其他地方,您可以通过使用命名空间映射中的字典键lxml来指定默认命名空间。None不幸xpath()的是,这将引发异常

TypeError: empty namespace prefix is not supported in XPath

因此,不幸的是,您必须在 XPath 表达式中的每个节点名称前面加上ns:(或您选择将该名称空间映射到的任何名称)。

有关 XPath 语法的更多信息,请参阅例如W3Schools Xpath 教程中的XPath 语法页面。

要开始使用 XPath,在众多XPath 测试人员之一中摆弄您的文档也很有帮助。此外,Firefox 的 Firebug 插件或 Google Chrome 检查器允许您显示所选元素的(或者更确切地说,其中之一)XPath。

于 2013-10-18T21:12:47.457 回答