【问题标题】:Skip over element in large XML file (python 3)跳过大型 XML 文件中的元素(python 3)
【发布时间】:2022-01-24 23:37:46
【问题描述】:

我是 xml 解析的新手,我一直在尝试找出一种跳过父元素内容的方法,因为有一个嵌套元素在其文本属性中包含大量数据(我无法更改这个文件是如何生成的)。这是 xml 的示例:

<root>
    <Parent>
        <thing_1>
            <a>I need this</a>
        </thing_1>
        <thing_2>
            <a>I need this</a>
        </thing_2>
        <thing_3>
            <subgroup>
                <huge_thing>enormous string here</huge_thing>
            </subgroup>
        </thing_3>
    </Parent>
    <Parent>
        <thing_1>
            <a>I need this</a>
        </thing_1>
        <thing_2>
            <a>I need this</a>
        </thing_2>
        <thing_3>
            <subgroup>
                <huge_thing>enormous string here</huge_thing>
            </subgroup>
        </thing_3>
    </Parent>
</root>

我已经尝试了 lxml.iterparse 和 xml.sax 实现来尝试解决这个问题,但没有骰子。这些是我在搜索中找到的大部分答案:

  1. 在iterparse中使用tag关键字。

    这不起作用,因为尽管 lxml 清理了后台的元素,但元素中的大文本仍然被解析到内存中,所以我得到了很大的内存峰值。

  2. 如果找到该元素的开始事件,则创建一个将其设置为 True 的标志,然后在解析中忽略该元素。

    这不起作用,因为元素在结束事件时仍被解析到内存中。

  3. 在到达特定元素的结束事件之前中断。

    当我到达元素时,我不能只是中断,因为我需要从多个这些元素中获取特定的子数据。

  4. 这是不可能的,因为流解析器仍然有一个结束事件并生成完整的元素。

    ...好的。

我目前正在尝试直接编辑 GzipFile 发送到 iterparse 的流数据,希望它甚至不知道该元素存在,但我遇到了问题。任何方向将不胜感激。

【问题讨论】:

  • 您的内存峰值有多大?文字有多大?
  • 您在这个问题上取得了进展吗?有任何解决方案有帮助吗?

标签: python-3.x xml xml-parsing lxml iterparse


【解决方案1】:

我认为您不能让解析器选择性地忽略它正在解析的 XML 的某些部分。这是我使用 SAX 解析器的发现...

我提取了您的示例 XML,将其放大到 400MB 以下,创建了一个 SAX 解析器,并以两种不同的方式针对我的 big.xml 文件运行它。

  • 对于直接的方法sax.parse('big.xml', MyHandler()),内存峰值为 12M。
  • 对于缓冲文件读取器方法,使用 4K 块 parser.feed(chunk),内存峰值为 10M。

然后我将大小翻倍,对于一个 800M 的文件,双向重新运行,内存使用峰值没有改变,~10M。 SAX 解析器似乎非常有效。

我针对您的示例 XML 运行了这个脚本来创建一些非常大的文本节点,每个节点 400M。

with open('input.xml') as f:
    data = f.read()

with open('big.xml', 'w') as f:
    f.write(data.replace('enormous string here', 'a'*400_000_000))

这是 big.xml 的大小(以 MB 为单位):

du -ms big.xml 
763     big.xml

这是我的 SAX ContentHandler,它仅在数据父级的路径以 thing_*/a 结尾时处理字符数据(根据您的示例,huge_thing 不合格)...

顺便说一句,非常感谢 l4mpithis answer 展示了如何缓冲您想要的字符数据:

from xml import sax

class MyHandler(sax.handler.ContentHandler):
    def __init__(self):
        self._charBuffer = []

        self._path = []

    def _getCharacterData(self):
        data = ''.join(self._charBuffer).strip()
        self._charBuffer = []
        return data.strip()  # remove strip() if whitespace is important

    def characters(self, data):
        if len(self._path) < 2:
            return

        if self._path[-1] == 'a' and self._path[-2].startswith('thing_'):
            self._charBuffer.append(data)

    def startElement(self, name, attrs):
        self._path.append(name)

    def endElement(self, name):
        self._path.pop()

        if len(self._path) == 0:
            return

        if self._path[-1].startswith('thing_'):
            print(self._path[-1])
            print(self._getCharacterData())

对于整个文件解析方法和分块阅读器,我得到:

thing_1
I need this
thing_2
I need this
thing_3

thing_1
I need this
thing_2
I need this
thing_3

由于我的简单逻辑,它正在打印thing_3,但subgroup/huge_thing中的数据被忽略了。

以下是我如何使用直截了当的parse() 方法调用处理程序:

handler = MyHandler()
sax.parse('big.xml', handler)

当我用 Unix/BSD time 运行它时,我得到:

/usr/bin/time -l ./main.py
...
        1.45 real         0.64 user         0.11 sys
...
            11027456  peak memory footprint

以下是我如何使用更复杂的分块读取器调用处理程序,使用 4K 块大小:

handler = MyHandler()
parser = sax.make_parser()
parser.setContentHandler(handler)

Chunk_Sz = 4096
with open('big.xml') as f:
    chunk = f.read(Chunk_Sz)
    while chunk != '':
        parser.feed(chunk)
        chunk = f.read(Chunk_Sz)
/usr/bin/time -l ./main.py
...
        1.85 real         1.65 user         0.19 sys
...
            10453952  peak memory footprint

即使是 512B 的块大小,它也不会低于 10M,但运行时间会增加一倍。

我很想知道你得到了什么样的表现。

【讨论】:

    【解决方案2】:

    您不能使用 DOM 解析器,因为它会根据定义将整个文档填充到 RAM 中。但基本上 DOM 解析器只是一个 SAX 解析器,它在通过 SAX 事件时创建一个 DOM。

    在创建自定义 SAX 解析器时,您实际上不仅可以创建 DOM(或您喜欢的任何其他内存表示),还可以开始忽略与文档中某些特定位置相关的事件。

    请注意解析需要继续,以便您知道何时停止忽略事件。但是解析器的输出不会包含这个不需要的大块数据。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-16
      相关资源
      最近更新 更多