【问题标题】:Python - Convert Very Large (6.4GB) XML files to JSONPython - 将超大 (6.4GB) XML 文件转换为 JSON
【发布时间】:2013-10-10 02:37:01
【问题描述】:

基本上,我有一个 6.4GB 的 XML 文件,我想将其转换为 JSON,然后将其保存到磁盘。我目前正在运行带有 i7 2700k 和 16GB 内存的 OSX 10.8.4,并运行 Python 64 位(双重检查)。我收到一个错误,我没有足够的内存来分配。我该如何解决这个问题?

print 'Opening'
f = open('large.xml', 'r')
data = f.read()
f.close()

print 'Converting'
newJSON = xmltodict.parse(data)

print 'Json Dumping'
newJSON = json.dumps(newJSON)

print 'Saving'
f = open('newjson.json', 'w')
f.write(newJSON)
f.close()

错误:

Python(2461) malloc: *** mmap(size=140402048315392) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
Traceback (most recent call last):
  File "/Users/user/Git/Resources/largexml2json.py", line 10, in <module>
    data = f.read()
MemoryError

【问题讨论】:

  • 尝试readlines 而不是readread 方法返回一个字符串,一个字符串在内存中占用 连续 空间,并且内存中通常没有大的 (> 100mb) 连续空间可用。 readlines 将为您提供字符串列表,这对于大数据来说效果相对较好。
  • 对于这样的事情,python 的内存开销往往有点高......例如:from lxml import etree; e = etree.Element('x'); e.__sizeof__() 为或多或少的空元素返回 0x30 - 48 字节。 d = dict(); d.__sizeof__() 返回 0xf8。然后,您正在读取 xml,然后将其重新创建为 dict,基本上将其内存使用量增加了一倍。你必须找到一种增量方法。
  • 这里的一个谜是为什么mmap 报告要求的大小是 140,402,048,315,392。是的,这是一个大文件,但即使是 Python ;-) 应该认为它需要 140 万亿字节。

标签: python xml json


【解决方案1】:

许多 Python XML 库支持增量解析 XML 子元素,例如xml.etree.ElementTree.iterparsexml.sax.parse 在标准库中。这些函数通常称为“XML Stream Parser”。

您使用的 xmltodict 库也有流模式。我认为它可以解决您的问题

https://github.com/martinblech/xmltodict#streaming-mode

【讨论】:

  • xmltodict 的流式传输功能是可靠的,但是您必须编写大量代码来转换任意 XML 文档。您指定项目深度以启用流模式,然后它为该深度的每个元素调用您的处理程序。您必须处理根元素、正确结束输出等等。
【解决方案2】:

您不想一次性读取文件然后处理它,而是希望以块的形式读取它,并在加载每个块时对其进行处理。这是处理大型 XML 文件时相当常见的情况,并且由 Simple API for XML (SAX) 标准涵盖,该标准指定了用于解析 XML 流的回调 API - 它是 Python 标准库的一部分xml.sax.parsexml.etree.ETree 如上所述。

这是一个快速的 XML 到 JSON 转换器:

from collections import defaultdict
import json
import xml.etree.ElementTree as ET

def parse_xml(file_name):
    events = ("start", "end")
    context = ET.iterparse(file_name, events=events)

    return pt(context)

def pt(context, cur_elem=None):
    items = defaultdict(list)

    if cur_elem:
        items.update(cur_elem.attrib)

    text = ""

    for action, elem in context:
        # print("{0:>6} : {1:20} {2:20} '{3}'".format(action, elem.tag, elem.attrib, str(elem.text).strip()))

        if action == "start":
            items[elem.tag].append(pt(context, elem))
        elif action == "end":
            text = elem.text.strip() if elem.text else ""
            elem.clear()
            break

    if len(items) == 0:
        return text

    return { k: v[0] if len(v) == 1 else v for k, v in items.items() }

if __name__ == "__main__":
    json_data = parse_xml("large.xml")
    print(json.dumps(json_data, indent=2))

如果您正在查看大量 XML 处理,请查看 lxml 库,它在标准模块之外还有大量有用的东西,同时也更易于使用。

http://lxml.de/tutorial.html

【讨论】:

  • 对于那些最终得到这个答案的人,不要像我一样白痴,并使用 action == 'start' 中的 elem.text 值。它可能看起来有效,但解析器不保证你会得到文本。偶尔你会得到 None 值,然后你就知道为什么了。
  • 此外,虽然这个答案是我了解 ElementTree 的一个很好的开始,但可以通过在 break 之前添加一个 elem.clear() 来进一步减少它的内存使用量。
  • 这似乎将整个 JSON 保存在内存中。如何沿途发出块以进一步减少内存,并消除对处理文件大小的任何限制?
  • 一个很好的问题,虽然我没有经验的答案,但这个库看起来允许您将 JSON 编写为流:pypi.org/project/jsonstreams
【解决方案3】:

这是一个 Python3 脚本,用于使用 xmltodict 的流功能将特定结构的 XML 转换为 JSON。该脚本在内存中保留的很少,因此对输入的大小没有限制。这做了很多假设,但可能会让你开始,你的里程会有所不同,希望这会有所帮助。

#!/usr/bin/env python3
"""
Converts an XML file with a single outer list element
and a repeated list member element to JSON on stdout.
Processes large XML files with minimal memory using the
streaming feature of https://github.com/martinblech/xmltodict
which is required ("pip install xmltodict").

Expected input structure (element names are just examples):
  <mylist attr="a">
    <myitem name="foo"></myitem>
    <myitem name="bar"></myitem>
    <myitem name="baz"></myitem>
  </mylist>

Output:
  {
    "mylist": {
      "attr": "a",
      "myitem": [
        {
          "name": "foo"
        },
        {
          "name": "bar"
        },
        {
          "name": "baz"
        }
      ]
    }
  }
"""
import json
import os
import sys
import xmltodict


ROOT_SEEN = False


def handle_item(path, element):
    """
    Called by xmltodict on every item found at the specified depth.
    This requires a depth >= 2.
    """
    # print("path {} -> element: {}".format(path, element))
    global ROOT_SEEN
    if path is None and element is None:
        # after element n
        print(']')  # list of items
        print('}')  # outer list
        print('}')  # root
        return False
    elif ROOT_SEEN:
        # element 2..n
        print(",")
    else:
        # element 1
        ROOT_SEEN = True
        print('{')  # root
        # each path item is a tuple (name, OrderedDict)
        print('"{}"'.format(path[0][0]) + ': {')  # outer list
        # emit any root element attributes
        if path[0][1] is not None and len(path[0][1]) > 0:
            for key, value in path[0][1].items():
                print('"{}":"{}",'.format(key, value))
        # use the repeated element name for the JSON list
        print('"{}": ['.format(path[1][0]))  # list of items

    # Emit attributes and contents by merging the contents into
    # the ordered dict of attributes so the attr appear first.
    if path[1][1] is not None and len(path[1][1]) > 0:
        ordict = path[1][1]
        ordict.update(element)
    else:
        ordict = element
    print(json.dumps(ordict, indent=2))
    return True


def usage(args, err=None):
    """
    Emits a message and exits.
    """
    if err:
        print("{}: {}".format(args[0], err), file=sys.stderr)
    print("Usage: {} <xml-file-name>".format(args[0]), file=sys.stderr)
    sys.exit()


if __name__ == '__main__':
    if len(sys.argv) != 2:
        usage(sys.argv)
    xmlfile = sys.argv[1]
    if not os.path.isfile(xmlfile):
        usage(sys.argv, 'Not found or not a file: {}'.format(xmlfile))
    with open(xmlfile, 'rb') as f:
        # Set item_depth to turn on the streaming feature
        # Do not prefix attribute keys with @
        xmltodict.parse(f, item_depth=2, attr_prefix='', item_callback=handle_item)
    handle_item(None, None)

【讨论】:

    猜你喜欢
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 2019-01-28
    • 2012-06-17
    • 2017-12-14
    • 2018-07-14
    • 2013-01-28
    • 2017-06-18
    相关资源
    最近更新 更多