【问题标题】:parse large XML in loop python在循环python中解析大型XML
【发布时间】:2020-01-23 12:33:51
【问题描述】:

我想用以下结构解析 XML (~1GB):

<Publication creationDateTime="04-AUG-2019 05:22:07">
  <holds>
    <hold>
      <recordType>Standard</recordType>
      <isEnroute>true</isEnroute>
      <holdName>NANLANG</holdName>
      <holdTime>10</holdTime>
      <inbound>
        <courseValue>170</courseValue>
      </inbound>
      <min>
        <altitude>7874</altitude>
      </min>
    </hold>
    <hold>
      <recordType>Standard</recordType>
      <holdName>ZILINA LOM</holdName>
      <holdTime>10</holdTime>
      <inbound>
        <courseValue>243</courseValue>
      </inbound>
      <max>
        <isFlightLevel>true</isFlightLevel>
        <altitude>85</altitude>
      </max>
      <min>
        <altitude>4500</altitude>
      </min>
    </hold>
  </holds>
</Publication>

我已经清楚最有效的方法是使用lxml.etree iterparse method

我需要将每个标签解析为变量,然后插入数据库。 问题是我没有掌握迭代“head”标签(例如保持)并插入数据库的方式,我的代码示例如下:

class Avia:
    def __init__(self, **kwargs):
        for attr in kwargs.keys():
            self.__dict__[attr] = kwargs[attr]

context = ET.iterparse('test.xml')

def xml_fast_iter(context):
    for event, elem in context:
        if elem.tag == 'holdName':
            hold_name = elem.text
        elif elem.tag == 'holdTime':
            hold_time = elem.text
        elif elem.tag == 'courseValue':
            course = float(elem.text)
        elif elem.tag == 'isEnroute':
            hold_enr = elem.text
        # ...

        elem.clear()
        for ancestor in elem.xpath('ancestor-or-self::*'):
            if ancestor.tag == 'min':
                bottom = alt
            if ancestor.tag == 'max':
                top = alt

            while ancestor.getprevious() is not None:
                del ancestor.getparent()[0]

        if elem.tag == 'hold':
            hold_type = 'TER'
            if hold_enr:
                hold_type = 'ENR'
            outbound = course + 180 if course + 180 < 360 else course - 180
            holdPattern = Avia(name=hold_name, time=hold_time, course=course, outbound=outbound, type=hold_type, bottom=bottom, top=top)
            prop_dict = holdPattern.__dict__
            print(prop_dict)
    del context

当尝试打印时,我显然得到了第二个对象的hold_type = 'ENR',因为hold_enr 对第一个对象是正确的,并且当第二个对象没有这个键时它没有改变......当试图分配None对于for event, elem in context: 之后的所有变量,我将得到除最后一个之外的所有值=无,因为它们循环遍历每个元素。

解析所有键和初始化对象的正确方法是什么?也许我的方式完全错误?

初始化后给变量赋值None是否正确? (那么hold_type是正确的)

【问题讨论】:

    标签: python-3.x for-loop xml-parsing lxml


    【解决方案1】:

    此时还要监听'start' 事件并初始化您的变量。

    使用可以按原样传递给Avia()dict 很方便,使用生成器函数(即yield hold)也很方便。

    def xml_fast_iter(xmlfile):
        context = ET.iterparse(xmlfile, events=('start', 'end'))
    
        for event, elem in context:
            if event == 'start':
                if elem.tag == 'hold':
                    hold = {
                        'name': 'define',
                        'time': 'all',
                        'course': 'defaults',
                        'outbound': 'here',
                        'type': 'TER',
                        'bottom': '',
                        'top': '',
                    }
                    max = {
                        'altitude': 0
                    }
                    min = {
                        'altitude': 0
                    }
                if elem.tag == 'max':
                    minmax = max
                if elem.tag == 'min':
                    minmax = min
            else:
                if elem.tag == 'holdName':
                    hold['name'] = elem.text
                elif elem.tag == 'holdTime':
                    hold['time'] = elem.text
                elif elem.tag == 'courseValue':
                    course = float(elem.text)
                    hold['course'] = course
                    hold['outbound'] = course + 180 if course + 180 < 360 else course - 180
                elif elem.tag == 'isEnroute':
                    hold['type'] = 'ENR'
                elif elem.tag == 'altitude':
                    minmax['altitude'] = int(elem.text)
                elif elem.tag == 'hold':
                    hold['bottom'] = min['altitude']
                    hold['top'] = max['altitude']
                    yield hold
    

    用法:

    for hold in xml_fast_iter('test.xml'):
        holdPattern = Avia(**hold)
        prop_dict = holdPattern.__dict__
        print(prop_dict)
    

    请注意,我对 'max''min' 所做的只是对您需要什么的猜测,但它有助于展示您如何处理上下文数据,而不必求助于像 ./ancestor-or-self::* 这样的 XPath,相比之下会慢很多。

    【讨论】:

    • 哇...真优雅。如果 xml 中存在其他类型的对象(例如 等),我必须在“开始”事件中添加模式字典,然后将其填充到“结束”中?
    • hmm 似乎对于其他类型的对象,我需要编写自己的函数 - 用于获取 yield holdyield obstacles 等。对象具有完全不同的键......我说的对吗?
    • 这取决于您的输入数据的结构。当obstacles&lt;hold&gt; 元素的一个属性时,在你进行的同时构建它是有意义的,并且只在最后做yield hold
    • 不,&lt;obstacles&gt; 不是&lt;hold&gt; 的属性,这和&lt;holds&gt; 是同一级别
    • 在这种情况下...嗯。如果您只想快速解析,则不执行 yield 并在循环内返回调用 Avia(**hold) 即可。如果您还想要可测试性(即“能够在不更改任何其他内容的情况下读取 XML 文件”)我可能会创建一个 class Hold 和一个 class Obstacle,使用它们而不是非描述性的字典,并且然后在它们经过时将它们交给它们。然后我会将for item in xml_fast_iter(...):if isinstance(item, Hold): 之类的东西结合使用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-12
    • 2014-06-18
    • 1970-01-01
    • 2020-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多