【问题标题】:How to efficiently pre-process and parse JSON data?如何高效地预处理和解析 JSON 数据?
【发布时间】:2014-11-30 18:58:58
【问题描述】:

我们目前正在运行一个程序,该程序已经执行了一个小时(但仍未完成),因此我们想知道是否可以改进我们的程序代码,使其运行得更快。

我们的程序由两部分组成:首先我们需要更改字符串,因为我们使用 JSON 字典,并且所有项目的数据都有相似的键(“track”)——如果我们不这样做,输出只会给出第一首曲目。其次,我们需要将 JSON 数据打印到 csv 文件中。

JSON 文件的片段:(实际文件约为 900 mb)

{
 "VV":{
    "Version":1,
    "Data":[
       {
          "track":[
             {
                "time":"YYYY-MM-DDTHH:MM:SS:MS",
                "tl":{
                   "x":10,
                   "y":11
                },
                "br":{
                   "x":20,
                   "y":20
                }
             },
             {
                "time":"YYYY-MM-DDTHH:MM:SS:MS",
                "tl":{
                   "x":12,
                   "y":15
                },
                "br":{
                   "x":22,
                   "y":23
                }
             }
          ],
          "track":[
             {
                "time":"YYYY-MM-DDTHH:MM:SS:MS",
                "tl":{
                   "x":30,
                   "y":39
                },
                "br":{
                   "x":40,
                   "y":45
                }
             },
             {
                "time":"YYYY-MM-DDTHH:MM:SS:MS",
                "tl":{
                   "x":12,
                   "y":18
                },
                "br":{
                   "x":22,
                   "y":24
                }
             }
          ]
         }
      ]
   }
}

我们的代码的第一部分:

with open(r'filename.json') as json_file:
    fil = json_file.read()
    i = 0
    print i
    while ('track' in fil) :
        fil = fil.replace('track', 'tr'+str(i), 1)
        i = i + 1
    print i
    input_data = json.loads(fil)

data_d = input_data['VV']['Data'][0]

第二部分:

with open(r'output.csv', 'wb') as csv_file:
    writer = csv.writer(csv_file)
    i = 0
    for track, data in data_d.items():
    i = i+1 # Track

    for item in data:

        #TRACK
        item_values = []
        item_values.append(i)

        #DAY
        #print item['time']
        day = item['time'][8:10]
        item_values.append(day)

        #COORDINATEN
        item_values.append(item['tl']['x'])
        item_values.append(item['tl']['y'])
        item_values.append(item['br']['x'])
        item_values.append(item['br']['y'])

        #TIME
        time = item['time'][11:13]+item['time'][14:16]+item['time'][17:19]+item['time'][20:23]
        item_values.append(time)

        writer.writerow(item_values)

【问题讨论】:

  • 我正在尝试理解您的问题。首先,您需要将“track”替换为“tr”。其次,您需要转储一个 CSV 文件。你想优化这个过程。是这样吗?
  • 是的,这正是我们的问题。我们需要用 tr0、tr1、tr2 等替换轨道。因为我们需要相同的键。
  • 可以更改上游输出格式吗?例如,每行输出一个轨道(如推文流),而不是创建一个大型 json 对象
  • 我看到你的 JSON 对象包含多个名为 'track' 的字段,你能生成一个 JSON 对象来包含一个名为 'track' 的数组吗?
  • 我们无法更改 JSON 对象,因为我们不创建它,但我们只从外部方接收它。最小的 JSON 文件包含 300 万行,只是为了估计文件的大小。

标签: python json csv dictionary replace


【解决方案1】:

首先要测量当前代码的时间性能:从您的输入中提取一个小的代表性数据样本,以便您可以在几秒钟内(最多一分钟)运行基准测试。保存您的代码产生的输出,以测试您以后没有破坏任何东西。

首先我们需要更改字符串,因为我们使用 JSON 字典和 数据对所有项目都有相似的键(“track”)——如果我们不这样做 这样,输出只给出第一首曲目。

第二件事是避免更改字符串(删除代码的第一部分)。在最坏的情况下(如果您的 900M 文件实际上不是 json 文件,因为 json 格式不支持 json 对象中的重复名称:"When the names within an object are not unique, the behavior of software that receives such an object is unpredictable."),您可以使用适合大文件的 multidict() solution 之类的东西,例如,如果您使用python2然后避免.items()调用创建一个不必要的列表,你可以使用.iteritems()代替,避免复制dict(d),只需返回defaultdict

import json
from collections import defaultdict

def multidict(ordered_pairs):
    """Convert duplicate key values to a list."""
    # read all values into list
    d = defaultdict(list)
    for k, v in ordered_pairs:
        d[k].append(v)

    # collapse list that has only 1 item
    for k, v in d.iteritems():
        if len(v) == 1:
            d[k] = v[0]
    return d 

with open('filename.json') as json_file:
    obj = json.load(json_file, object_pairs_hook=multidict)

每次更改后,再次测量时间性能并检查输出是否仍然正确。

为了可读性,你可以重写第二部分:

import csv

with open('output.csv', 'wb') as csv_file:
    writer = csv.writer(csv_file)
    for i, data in enumerate(data_d.itervalues(), start=1):
        for item in data:
            t = item['time']
            writer.writerow([
                #TRACK
                i,
                #DAY
                t[8:10],
                #COORDINATEN
                item['tl']['x'],
                item['tl']['y'],
                item['br']['x'],
                item['br']['y'],
                #TIME
                t[11:13]+t[14:16]+t[17:19]+t[20:23],
            ])

如果使用multidict() 代替字符串替换并没有提高时间性能,那么您可以尝试滥用multidict() 来更改输入格式而不将整个json 对象加载到内存中:

#!/usr/bin/env python2
import json
import sys
from collections import defaultdict

def write_tracks(ordered_pairs):
    # read all values into list
    d = defaultdict(list)
    for k, v in ordered_pairs:
        d[k].append(v)

    # collapse list that has only 1 item
    for k, v in d.iteritems():
        if k == 'track':
            for tracks in v: # print one track (in json format) per line
                print("\n".join(map(json.dumps, tracks)))
            break
        elif len(v) == 1:
            d[k] = v[0]
    else: # no tracks, return the constructed object
        return d

json.load(sys.stdin, object_pairs_hook=write_tracks) # write tracks

您可以从命令行使用它:

$ <filename.json python write_tracks.py | python convert_tracks.py >output.csv

convert_tracks.py 类似于:

#!/usr/bin/env python2
import csv
import json
import sys

def main():
    writer = csv.writer(sys.stdout)
    for i, line in enumerate(sys.stdin, start=1):
        try:
            item = json.loads(line)
        except ValueError:
             pass # ignore errors
        else:
            t = item['time']
            writer.writerow([
                #TRACK
                i,
                #DAY
                t[8:10],
                #COORDINATEN
                item['tl']['x'],
                item['tl']['y'],
                item['br']['x'],
                item['br']['y'],
                #TIME
                t[11:13]+t[14:16]+t[17:19]+t[20:23],
            ])

if __name__ == "__main__":
    main()

【讨论】:

  • @Marjolein:我已经为每行变体添加了一个轨道 (write_tracks())
  • @Marjolein:我已将代码更改为使用 stdin/stdout 以方便时间测量/测试。
【解决方案2】:

有两件事情让我眼前一亮,都在这一行:

while ('track' in fil) :

首先- 这个while 循环每次都会执行。如果您仅因此而遇到性能问题,我不会感到惊讶。每次完成替换时,它都会重新搜索整个字符串。这实在是太低效了。

由于您只是使用文本对象,而不是 JSON 对象,因此您最好使用带有函数替换的正则表达式,或者使用其他基于正则表达式的策略,它会一次性找到所有匹配项,然后执行它。像这样的:

i = 0
def sub_track(g):
    global i
    i += 1
    return "tr_%s" % i

RE_track = re.compile('track')
RE_track.sub(sub_track, data)

您也可以只使用sed 之类的非python 程序,然后创建一个替换所有匹配项的文件副本。

第二:我认为搜索裸词“track”并不明智。您可能会匹配实际数据。我至少会尝试将其作为 JSON 键匹配,并针对"track":[ 之类的字符串进行搜索/替换。

话虽如此,读取和操作一个 900MB 的文件会占用大量内存。我可能会尝试在命令行上使用sed 执行此操作,或者尝试找出一种方法在数据块中执行此操作并读取一行+一行一行地写入。我假设这都是一行,因此您不能使用 readlines() 遍历文件描述符,并且必须与字节范围有关。我以前从未处理过分析数据流,因此无法对此提供任何见解。

【讨论】:

    【解决方案3】:

    试试这样:-

    import json
    from pprint import pprint
    json_data=open(filename.json)
    data = json.load(json_data)
    pprint(data)
    json_data.close()
    

    【讨论】:

    • 很抱歉,这如何改进我们的程序代码?
    • 我认为这个答案不能解决 OP 的问题
    • 感谢您的所有 cmets。但是只是修改了json的解析。并且真的不确定这是否有助于操作
    猜你喜欢
    • 2021-05-12
    • 1970-01-01
    • 2019-07-19
    • 2018-05-21
    • 2017-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多