iterparse无法解析某个字段,而其他类似字段正常

2 投票
1 回答
1301 浏览
提问于 2025-04-17 14:35

我在用Python的iterparse来解析一个nessus扫描的XML结果文件(.nessus文件)。但是在解析时,遇到了一些意外的记录,导致解析失败,而类似的记录却能正常解析。

这个XML文件的结构大致是这样的:里面有很多记录,比如下面这个:

<ReportHost>
  <ReportItem>
    <foo>9.3</foo>
    <bar>hello</bar>
  </ReportItem>
  <ReportItem>
     <foo>10.0</foo>
     <bar>world</bar>
</ReportHost>
<ReportHost>
   ...
</ReportHost>

换句话说,就是有很多主机(ReportHost),每个主机有很多要报告的项目(ReportItem),而这些项目又有几个特征(foobar)。我想生成每个项目一行,并列出它的特征。

解析在文件中间的某一行失败了,这一行很简单(在这个例子中,foo的值是cvss_base_score)。

<cvss_base_score>9.3</cvss_base_score>

而在此之前,大约有200行类似的记录都能正常解析。

下面是相关的代码片段——它设置了一些上下文标记(inReportHostinReportEvent),这些标记告诉我当前在XML文件的哪个结构中,并根据上下文来分配或打印值。

import xml.etree.cElementTree as ET
inReportHost = False
inReportItem = False

for event, elem in ET.iterparse("test2.nessus", events=("start", "end")):
    if event == 'start' and elem.tag == "ReportHost":
        inReportHost = True
    if event == 'end' and elem.tag == "ReportHost":
        inReportHost = False
        elem.clear()
    if inReportHost:
        if event == 'start' and elem.tag == 'ReportItem':
            inReportItem = True
            cvss = ''
        if event == 'start' and inReportItem:
            if event == 'start' and elem.tag == 'cvss_base_score':
                cvss = elem.text
        if event == 'end' and elem.tag == 'ReportItem':
            print cvss
            inReportItem = False

有时候,cvss的值会是None(在cvss = elem.text赋值之后),尽管在文件的早些地方,相同的条目都能正常解析。

如果我在赋值后加上类似下面的代码:

if cvss is None: cvss = "0"

那么后面很多cvss就能正确赋值(但有些仍然是None)。

当我把导致解析错误的<ReportHost>...</reportHost>提取出来,放到程序里运行时,它又能正常工作(也就是说,cvss被赋值为9.3,正如预期的那样)。

我现在搞不清楚代码哪里出错了,因为在一大堆类似的记录中,有些能正确处理,有些却不行(有些记录是完全相同的,但处理结果却不同)。我也找不到那些失败记录有什么特别之处——之前和之后的相同记录都能正常处理。

1 个回答

5

来自 iterparse() 文档 的内容:

注意:iterparse() 只保证在发出“开始”事件时,它已经看到了开始标签的“>”字符,因此属性是已定义的,但文本和尾部属性的内容在那时是未定义的。对于元素的子元素也是如此;它们可能存在,也可能不存在。如果你需要一个完整的元素,请寻找“结束”事件。

去掉 inReport* 变量,只在“结束”事件时处理 ReportHost,这样可以确保它已经完全解析。使用 ElementTree API 从当前的 ReportHost 元素中获取必要的信息,比如 cvss_base_score

为了节省内存,可以这样做:

import xml.etree.cElementTree as etree

def getelements(filename_or_file, tag):
    context = iter(etree.iterparse(filename_or_file, events=('start', 'end')))
    _, root = next(context) # get root element
    for event, elem in context:
        if event == 'end' and elem.tag == tag:
            yield elem
            root.clear() # preserve memory

for host in getelements("test2.nessus", "ReportHost"):
    for cvss_el in host.iter("cvss_base_score"):
        print(cvss_el.text)

撰写回答