在for循环中使用iter时,Python lxml内存不足

时间:2019-07-01 12:58:00

标签: python xml memory lxml

我正在写一个解决方案,以便从文件中提取信息。 这些文件是通过Windows Event Utility命令中的其他脚本生成的(我不调用,只是接收文件进行解析):

wevtutil qe Application /q:"*[System[Provider[@Name='NameOfTheSourceApplication']]]" >> %FILE%

此命令将有关源应用程序的所有输出保存到转储文件中,最终每一行的每个 event 都有一个XML。我只关心EventDataTimeCreated SystemTime

示例输出:

<?xml version="1.0" encoding="UTF-8"?>
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
   <System>
      <Provider Name="" />
      <EventID Qualifiers="">0</EventID>
      <Level>4</Level>
      <Task>0</Task>
      <Keywords />
      <TimeCreated SystemTime="2018-10-02T11:19:41.000000000Z" />
      <EventRecordID />
      <Channel>Application</Channel>
      <Computer />
      <Security />
   </System>
   <EventData>
      DATA
      <Data />
   </EventData>
</Event>

转储文件完成后,文件可能会很大(6-7GB以上)。因此,我使用Linux iconv实用程序将源文件编码从UTF-16/UCS2-LE(wevutil的默认编码)更改为UTF-8,新编码减少了文件大小的一半。然后,我将grouper配方与一些简单的文件拆分功能结合使用,以便将较大的转储文件拆分为较小的文件:

def grouper(n, iterable, fillvalue=None):
   """Collect data into fixed-length chunks or blocks"""
   # grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx
   args = [iter(iterable)] * n
   return zlg(fillvalue=fillvalue, *args)

def splitter(fileobj,outName,ranoutName,rencode,wencode):
    with open(fileobj,"r",encoding='UTF-8',errors='replace') as f:
        for i, g in enumerate(grouper(n, f, fillvalue=''), 1):
            with open('{0}_{1}.xml'.format(i,outName), 'w',encoding=wencode) as fout:
                fout.writelines(g)
                print("Splitting file : %s" % (fileobj))

由于这些文件实际上不是XML文件,而是每行以命名空间格式格式化为xml,因此我将一个根标记一个接一个地添加到每个分割的文件中,稍后由lxml进行解析(glst代表“全局列表”)。

def rooter(glst):
    for logFiles in glst:
        oFile = open(logFiles,'r',encoding='utf-8')
        rFile = oFile.read()
        wFile = open(logFiles,'w',encoding='utf-8')
        wFile.write('<root>')
        wFile.write(rFile)
        wFile.write('</root>')
        oFile.close()
        wFile.close()

        print("Rooting XML : %s" % (logFiles))

然后,我只加载一个要在lxml中解析的XML文件:

def loadXml(fileobj):
    tree = etree.parse(fileobj)
    print("Processing : %s" % (fileobj))
    return tree

这是我的瓶颈,因为在我只寻找Event Data和我的Event Time时,我没有找到任何其他便捷的方法来有效地解析文件。找到数据后,我将发现附加到两个单独的列表中(一个用于事件数据,一个用于事件时间),随后我将其转换为简单的CSV文件,以便继续通过{{1}进行解析}。

此代码实际上适用于2GB以下的文件,但在解析2GB以上的文件时会完全用尽内存,我的解决方案必须在只有2-3GB可用RAM(Windows 64位台式机)的系统中运行。

pandas

我尝试在解析后手动def parser(tree,DataL,DataTimeL): for evts in tree.iter('{%s}EventData' % nameSpace): EvtData = evts.find('{%s}Data' % nameSpace).text DataL.append(EvtData) for evtSysTime in tree.iter('{%s}System' % nameSpace): eSysTime = evtSysTime.find('{%s}TimeCreated' % nameSpace).attrib DataTimeL.append(eSysTime) break gc.collect文件对象,但这似乎没有任何作用,并且python会继续建立内存直到PC崩溃。

del

CSV创建(zlg代表itertools-zip_longest):

def initParser(glst,DataL,DataTimeL):
    for file in glst:
     root = loadXml(file)
     parser(root,DataL,DataTimeL)
     gc.collect()
     del file

我已经尝试过使用TinyDB,ZODB,这听起来有些过分,但它太慢了,或者我做错了。手动将事件转储到CSV速度非常慢。 由于with open('LogOUT.csv', 'w', encoding="UTF-8", newline='') as cF: wr = csv.writer(cF) wr.writerow(("Event", "Event Time")) wr.writerows(zlg(EvtL,EvtTimeL)) 循环解析器功能实际上对于2GB以下的文件非常有效,因此我想找到一种安全高效地附加这些大列表的方法,而不会崩溃整个系统。

谢谢。

1 个答案:

答案 0 :(得分:0)

这是一个概念证明,它使用迭代器读取一个大文件,该文件由带有独立XML的行组成,并将特定字段提取到CSV文件中。对其进行修改以适合您的需求。

import csv
import itertools
import typing
from io import StringIO
from xml.etree import ElementTree
from xml.etree.ElementTree import Element


def grouper(iterable, n, fill=None) -> typing.Iterator:
    args = [iter(iterable)] * n
    return itertools.zip_longest(*args, fillvalue=fill)


def parse_event_xml(event_xml: str) -> dict:
    root: Element = ElementTree.fromstring(event_xml)
    namespaces = {'ns': 'http://schemas.microsoft.com/win/2004/08/events/event'}
    time_el = root.find('./ns:System/ns:TimeCreated', namespaces=namespaces)
    data_el = root.find('./ns:EventData', namespaces=namespaces)

    return {
        'Event Time': time_el.attrib['SystemTime'],
        'Event Data': data_el.text,
    }


def process_batch(batch: typing.Iterator[str], batch_filename: str) -> None:
    fields = ['Event Time', 'Event Data']
    with open(batch_filename, 'w', newline='') as bf:
        writer = csv.DictWriter(bf, fieldnames=fields)
        writer.writeheader()
        for item in batch:
            if not item:  # skip empty lines
                continue
            parsed = parse_event_xml(item)
            writer.writerow(parsed)


if __name__ == '__main__':
    xml_raw = '''<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'><System><Provider Name=''/><EventID Qualifiers=''>0</EventID><Level>4</Level><Task>0</Task><Keywords></Keywords><TimeCreated SystemTime='2018-10-02T11:19:41.000000000Z'/><EventRecordID></EventRecordID><Channel>Application</Channel><Computer></Computer><Security/></System><EventData>DATA<Data></Data></EventData></Event>
    <Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'><System><Provider Name=''/><EventID Qualifiers=''>0</EventID><Level>4</Level><Task>0</Task><Keywords></Keywords><TimeCreated SystemTime='2018-10-02T11:19:41.000000000Z'/><EventRecordID></EventRecordID><Channel>Application</Channel><Computer></Computer><Security/></System><EventData>DATA<Data></Data></EventData></Event>'''

    batch_size = 10  # lines / events

    # read the event stream
    # normally you'd use `with open(filename, encoding='utf-8')`
    # but here i'm reading from a string
    with StringIO(xml_raw) as f:
        for i, batch in enumerate(grouper(f, batch_size)):
            batch_filename = f'batch_{i}.csv'
            process_batch(batch, batch_filename)

调整parse_event_xml函数以提取所需的数据,这里我只使用了EventTimeEventData

此文件输出如下的csv文件:

Event Time,Event Data
2018-10-02T11:19:41.000000000Z,DATA
2018-10-02T11:19:41.000000000Z,DATA