我正在研究Python 2.6.6,我正在努力解决一个问题。
我有一个大型JSON文件,其结构如下:
{"id":"12345","ua":[{"n":"GROUP_A","v":["true"]}]}
{"id":"12345","ua":[{"n":"GROUP_B","v":["true"]}]}
{"id":"54321","ua":[{"n":"GROUP_C","v":["true"]}]}
{"id":"54321","ua":[{"n":"GROUP_D","v":["true"]}]}
{"id":"54321","ua":[{"n":"GROUP_E","v":["true"]}]}
{"id":"98765","ua":[{"n":"GROUP_F","v":["true"]}]}
我需要合并id,因此它们将包含所有的GROUPS:
{"id":"12345","ua":[{"n":"GROUP_A","v":["true"]},{"n":"GROUP_B","v":["true"]}]}
{"id":"54321","ua":[{"n":"GROUP_C","v":["true"]},{"n":"GROUP_D","v":["true"]},{"n":"GROUP_E","v":["true"]}]}
{"id":"98765","ua":[{"n":"GROUP_F","v":["true"]}]}
我尝试使用'json'库,但我无法正确附加值。此外,我试图将它全部转换为字典,并将值(GROUPS)作为列表附加到键(id),但是因为我需要输出文件而无法打印它。
我可以使用bash来完成它,但解析所有信息并以所需格式重新排列它需要很长时间。
感谢任何帮助!
感谢。
答案 0 :(得分:5)
首先,让我们把JSON的东西拿走。
您的文件不是JSON结构,它是一堆独立的JSON对象。从您的示例中,它看起来像是每行一个对象。所以,让我们把它读到一个列表中:
with open('spam.json') as f:
things = [json.loads(line) for line in f]
然后我们将对此进行处理,然后将其写出来:
with open('eggs.json', 'w') as f:
for thing in new_things:
f.write(json.dumps(thing) + '\n')
现在,您没有要附加内容的JSON结构;你有一个dicts列表,你想创建一个新的dicts列表,将这些dicts合并在一起。
这是一种方法:
new_things = {}
for thing in things:
thing_id = thing['id']
try:
old_thing = new_things[thing_id]
except KeyError:
new_things[thing_id] = thing
else:
old_thing['ua'].extend(thing['ua'])
new_things = new_things.values()
有几种不同的方法可以简化这一点;我只是用这种方式编写它,因为它不会使用任何超出新手的技巧。例如,您可以通过排序和分组来完成:
def merge(things):
return {'id': things[0]['id'],
'ua': list(itertools.chain.from_iterable(t['ua'] for t in things))}
sorted_things = sorted(things, key=operator.itemgetter('id'))
grouped_things = itertools.groupby(sorted_things, key=operator.itemgetter('id'))
new_things = [merge(list(group)) for key, group in grouped_things]
我没有从你那里意识到你有数千万行的原始问题。所有上述步骤都需要将整个原始数据集加载到内存中,使用一些临时存储进行处理,然后将其写回。但是如果你的数据集太大,你需要找到一种方法来一次处理一行,并尽可能同时保留在内存中。
首先,要一次处理一行,您只需要将初始列表解析更改为生成器表达式,然后将其余代码移到with
语句中,如下所示:
with open('spam.json') as f:
things = (json.loads(line) for line in f)
for thing in things:
# blah blah
...在这一点上重写它可能就像这样容易:
with open('spam.json') as f:
for line in f:
thing = json.loads(line)
# blah blah
接下来,排序显然会在内存中构建整个排序列表,因此这里不可接受。但是如果你不进行排序和分组,整个new_things
结果对象必须同时处于活动状态(因为最后一个输入行可能必须合并到第一个输出行中)。
您的示例数据似乎已经按id
排序。如果你可以在现实生活中依靠它 - 或者只是指望总是按id
分组的行 - 只需跳过排序步骤,除了浪费时间和内存之外什么都不做,并使用分组解决方案。
另一方面,如果您不能指望按id
分组的行,那么实际上只有两种方法可以进一步减少内存:以某种方式压缩数据,或者将存储备份到磁盘
首先,Foo Bar User的解决方案构建了一个更简单,更小的数据结构(一个dict将每个id映射到其uas列表,而不是一个dicts列表,每个都有一个id和一个ua),这应该少花钱内存,我们可以一次一行转换为最终格式。像这样:
with open('spam.json') as f:
new_dict = defaultdict(list)
for row in f:
thing = json.loads(row)
new_dict[thing["id"]].extend(thing["ua"])
with open('eggs.json', 'w') as f:
for id, ua in new_dict.items(): # use iteritems in Python 2.x
thing = {'id': id, 'ua': ua}
f.write(json.dumps(thing) + '\n')
对于第二种,Python提供了一种使用dbm数据库的好方法,就像它是一个字典一样。如果您的值只是字符串,则可以使用anydbm
/ dbm
模块(或其中一个特定实现)。由于您的值是列表,因此您需要使用shelve
代替。
无论如何,虽然这会减少你的内存使用量,但它可能会减慢速度。在具有4GB RAM的计算机上,页面文件交换的节省可能会减少通过数据库的额外成本......但是在具有16GB RAM的计算机上,您可能只是增加开销而获得的收益非常小。您可能希望首先尝试使用较小的文件,以便在内存不存在问题时查看shelve
与dict
代码的速度有多慢。
或者,如果事情超出了你的记忆限制,你总是可以使用一个更强大的数据库,实际上可以对磁盘上的东西进行排序。例如(未经测试):
db = sqlite3.connect('temp.sqlite')
c = db.cursor()
c.execute('CREATE TABLE Things (tid, ua)')
for thing in things:
for ua in thing['ua']:
c.execute('INSERT INTO Things (tid, ua) VALUES (?, ?)',
thing['id'], ua)
c.commit()
c.execute('SELECT tid, ua FROM Things ORDER BY tid')
rows = iter(c.fetchone, None)
grouped_things = itertools.groupby(rows, key=operator.itemgetter(0))
new_things = (merge(list(group)) for key, group in grouped_things)
with open('eggs.json', 'w') as f:
for thing in new_things:
f.write(json.dumps(thing) + '\n')