如何在Python中高效地解析大型JSON文件?

3

我有一个文件,其中包含一个JSON对象的数组。 该文件超过1GB,因此我无法一次性将其加载到内存中。 我需要解析每个单独的对象。 我尝试使用ijson,但这将把整个数组作为一个对象加载,实际上与简单的json.load()做的事情相同。

是否还有其他方法可以实现?

编辑:没关系,只需使用ijson.items()并将前缀参数设置为"item"


你最初是如何编写那个文件的? - a_guest
我没有;这是给我的。 - Kristína
2个回答

3

您可以仅解析JSON文件一次,以查找每个一级分隔符(即属于顶级对象的逗号)的位置,然后将文件分成由这些位置指示的部分。例如:

{"a": [1, 2, 3], "b": "Hello, World!", "c": {"d": 4, "e": 5}}
        ^      ^            ^        ^             ^
        |      |            |        |             |
     level-2   |         quoted      |          level-2
               |                     |
            level-1               level-1

在这里我们想要找到一级逗号, 分隔包含在顶层对象中的对象。我们可以使用一个生成器来解析JSON流并跟踪进入和退出嵌套对象。当它遇到未被引用的一级逗号时,会产生相应的位置。

def find_sep_pos(stream, *, sep=','):
    level = 0
    quoted = False  # handling strings in the json
    backslash = False  # handling quoted quotes
    for pos, char in enumerate(stream):
        if backslash:
            backslash = False
        elif char in '{[':
            level += 1
        elif char in ']}':
            level -= 1
        elif char == '"':
            quoted = not quoted
        elif char == '\\':
            backslash = True
        elif char == sep and not quoted and level == 1:
            yield pos

应用于上述示例数据,这将给出 list(find_sep_pos(example)) == [15, 37]

然后我们可以根据分隔符位置将文件分割成对应的部分,并通过 json.loads 分别加载每个部分:

import itertools as it
import json

with open('example.json') as fh:
    # Iterating over `fh` yields lines, so we chain them in order to get characters.
    sep_pos = tuple(find_sep_pos(it.chain.from_iterable(fh)))
    fh.seek(0)  # reset to the beginning of the file
    stream = it.chain.from_iterable(fh)
    opening_bracket = next(stream)
    closing_bracket = dict(('{}', '[]'))[opening_bracket]
    offset = 1  # the bracket we just consumed adds an offset of 1
    for pos in sep_pos:
        json_str = (
            opening_bracket
            + ''.join(it.islice(stream, pos - offset))
            + closing_bracket
        )
        obj = json.loads(json_str)  # this is your object
        next(stream)  # step over the separator
        offset = pos + 1  # adjust where we are in the stream right now
        print(obj)
    # The last object still remains in the stream, so we load it here.
    obj = json.loads(opening_bracket + ''.join(stream))
    print(obj)

你的解析器没有考虑到带引号的引号,比如 "foo\",\"bar" - chepner
@chepner 感谢你的提示。我已经更新了我的答案。 - a_guest

0

2个选项

  1. 使用类似JQ的工具在CLI中解析,然后将其带入Python进行进一步处理。

  2. 使用PySpark进行解析(社区数据砖块提供免费空间)

JQ如何使用


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接