用Python解析XML(查找特定文本的标签)

3

我被委托处理一个xml文件,以查找特定元素并将它们导出到csv文件中。

我具体遇到的问题是一些信息被保存在相同的标签中:

<name>text</name>
<value>value</value>

每个名称标签都包含不同的值,我只需要其中一些。我尝试使用以下代码循环遍历文件:
try:
        descr = member.find('.//name').text
        if descr == 'description':
            plugin.append(descr)
    except AttributeError:
        descr = 'Unknown'
        plugin.append(descr)

但它只返回“未知”。
我的整个代码如下(未完成):
import xml.etree.ElementTree as ET
import csv

tree = ET.parse('plugins.xml')
root = tree.getroot()

nessus_out = open('/home/rj/Documents/python/nessus_out.csv', 'w')

csvwriter = csv.writer(nessus_out)

for member in root.findall('nasl'):
    plugin = []

    id = member.find('script_id').text
    plugin.append(id)

    name = member.find('script_name').text
    plugin.append(name)

    family = member.find('script_family').text
    plugin.append(family)

    #for each in member.iterfind('nasl'):
    try:
        solution = member.xpath('.//name/text()')
        if solution == 'solution':
            plugin.append(solution)
    except AttributeError:
        solution = 'Unknown'
        plugin.append(solution)
    csvwriter.writerow(plugin)
nessus_out.close()

最终目标是搜索“solution”,并从其标签中获取相应的值。

XML结构如下:

nasl_plugins
nasl_plugins/nasl
nasl_plugins/nasl/filename
nasl_plugins/nasl/script_id
nasl_plugins/nasl/script_name
nasl_plugins/nasl/script_family
nasl_plugins/nasl/attributes/attribute/name
nasl_plugins/nasl/attributes/attribute/value

为丹尼尔:

XML片段:

<nasl>
<filename>fedora_2017-c3149b5fcb.nasl</filename>
<script_id>101028</script_id>
<script_name>Fedora 25 : xen (2017-c3149b5fcb)</script_name>
<script_version>$Revision: 1.5 $</script_version>
<script_copyright>This script is Copyright (C) 2017-2018 Tenable Network Security, Inc.</script_copyright>
<script_family>Fedora Local Security Checks</script_family>
<cves>
 <cve>CVE-2017-10911</cve>
 <cve>CVE-2017-10912</cve>
 <cve>CVE-2017-10913</cve>
 <cve>CVE-2017-10915</cve>
 <cve>CVE-2017-10916</cve>
 <cve>CVE-2017-10917</cve>
 <cve>CVE-2017-10918</cve>
 <cve>CVE-2017-10919</cve>
 <cve>CVE-2017-10920</cve>
 <cve>CVE-2017-10923</cve>
</cves>
<bids>
</bids>
<xrefs>
 <xref>FEDORA:2017-c3149b5fcb</xref>
 <xref>IAVB:2017-B-0074</xref>
</xrefs>
<dependencies>
 <dependency>ssh_get_info.nasl</dependency>
</dependencies>
<required_keys>
 <key>Host/local_checks_enabled</key>
 <key>Host/RedHat/release</key>
 <key>Host/RedHat/rpm-list</key>
</required_keys>
<attribute> 
  <name>plugin_type</name> 
  <value>local</value> 
</attribute> 
<attribute> 
  <name>plugin_modification_date</name> 
  <value>2018/02/02</value> 
</attribute> 
<attribute> 
  <name>stig_severity</name> 
  <value>I</value> 
</attribute> 
<attribute> 
  <name>cvss_base_score</name> 
  <value>10.0</value> 
</attribute> 
</attributes> 

我需要的是stig_severity、base_cvss_score等数值,因此我会搜索它们并将其移至下一行以获取数值。至于csv格式,我需要每个插件都在同一行上,格式如下: id,名称,家族,解决方案,描述,概要,基础CVSS分数,插件类型,stig严重性,然后下一个插件的数值在下一行中。

我也曾尝试使用lxml做一些事情,但最终放弃了并改用Java。如果你想要更复杂的xpath表达式,那是最好的选择。 - JGFMK
Java对我来说不是一个真正的选择,我曾尝试过用bash,但它处理大文件的能力非常差,至少对于我的情况是这样。 - bash_me_silly
1
这似乎是一个非常容易的问题,但没有XML示例很难回答。您能否请添加XML的最小示例以及您期望CSV应该是什么样子? lxml具有相当不错的xpath支持(1.0),所以这应该是很容易做到的。 - Daniel Haley
整个文件只是一个接着一个列出插件的列表,大多数标签都相同,我需要循环遍历它们,获取给定<name>的值并跳过如果不存在。然后将其全部导出到csv文件中。所有插件标签都以<nasl>标签开头,并且都包含script_id/name/family,但是剩余的标签(synopsis、plugin_type等)并不总是存在于所有插件中,因此我需要能够搜索它们但如果不存在则跳过。 - bash_me_silly
欢迎来到Stack Overflow!好问题,不错的开端。 :-) - Daniel Haley
1个回答

0

看起来你想要的一些值是nasl的直接子元素,而另一些则在attributes/attribute中。

你可以有两个列表(或元组); 一个包含确切的元素名称,另一个包含确切的属性名称(attribute/name)。

注意:这可能听起来有点混乱,因为在这种情况下,“属性名称”实际上是一个名为“attribute”的元素,它具有一个名为“name”的子元素,而不是真正的XML属性名为“name”。

将这些元组合并将为您提供CSV中的所有字段。您可以使用此构建包含所有字段的字典,并将默认值设置为Unknown

然后,您可以迭代两个元组以构建两种不同类型的XPaths。如果元素存在,则在字典中更新文本值。否则,该值保持为Unknown

例如...

XML输入(test.xml)

<nasl_plugins>
    <nasl>
        <filename>fedora_2017-c3149b5fcb.nasl</filename>
        <script_id>101028</script_id>
        <script_name>Fedora 25 : xen (2017-c3149b5fcb)</script_name>
        <script_version>$Revision: 1.5 $</script_version>
        <script_copyright>This script is Copyright (C) 2017-2018 Tenable Network Security,
            Inc.</script_copyright>
        <script_family>Fedora Local Security Checks</script_family>
        <cves>
            <cve>CVE-2017-10911</cve>
            <cve>CVE-2017-10912</cve>
            <cve>CVE-2017-10913</cve>
            <cve>CVE-2017-10915</cve>
            <cve>CVE-2017-10916</cve>
            <cve>CVE-2017-10917</cve>
            <cve>CVE-2017-10918</cve>
            <cve>CVE-2017-10919</cve>
            <cve>CVE-2017-10920</cve>
            <cve>CVE-2017-10923</cve>
        </cves>
        <bids> </bids>
        <xrefs>
            <xref>FEDORA:2017-c3149b5fcb</xref>
            <xref>IAVB:2017-B-0074</xref>
        </xrefs>
        <dependencies>
            <dependency>ssh_get_info.nasl</dependency>
        </dependencies>
        <required_keys>
            <key>Host/local_checks_enabled</key>
            <key>Host/RedHat/release</key>
            <key>Host/RedHat/rpm-list</key>
        </required_keys>
        <attributes>
            <attribute>
                <name>plugin_type</name>
                <value>local</value>
            </attribute>
            <attribute>
                <name>plugin_modification_date</name>
                <value>2018/02/02</value>
            </attribute>
            <attribute>
                <name>stig_severity</name>
                <value>I</value>
            </attribute>
            <attribute>
                <name>cvss_base_score</name>
                <value>10.0</value>
            </attribute>
        </attributes>
    </nasl>
</nasl_plugins>

Python 3.x

import csv
from lxml import etree

elem_names = ('script_id', 'script_name', 'script_family')
attr_names = ('solution', 'description', 'synopsis', 'cvss_base_score', 'plugin_type',
              'stig_severity')
field_names = elem_names + attr_names

with open('test.csv', 'w', newline='', encoding='utf8') as xml_data_to_csv:

    csv_writer = csv.DictWriter(xml_data_to_csv, fieldnames=field_names, 
                                quoting=csv.QUOTE_ALL)

    csv_writer.writeheader()

    tree = etree.parse('test.xml')

    for nasl in tree.xpath('.//nasl'):
        # Build a dict containing all of the "field_names" with default values of "Unknown".
        values = {key: 'Unknown' for key in field_names}

        # Process the direct children of "nasl".
        for elem_name in elem_names:
            for child in nasl.xpath(f'*[self::{elem_name}]'):
                values[child.tag] = child.text

        # Process attribute with matching attribute names.
        for attr_name in attr_names:
            for val in nasl.xpath(f'attributes/attribute[name="{attr_name}"]/value'):
                values[attr_name] = val.text

        csv_writer.writerow(values)

输出(test.csv)

"script_id","script_name","script_family","solution","description","synopsis","cvss_base_score","plugin_type","stig_severity"
"101028","Fedora 25 : xen (2017-c3149b5fcb)","Fedora Local Security Checks","Unknown","Unknown","Unknown","10.0","local","I"

Daniel,你真是个绝对的冠军..非常非常感谢..我可以看出来,如果没有你的帮助,我自己对lxml的理解是不够的。 - bash_me_silly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接