Python 将包含多个表的250GB JSON文件拆分为拼花地板
我有一个JSON文件,格式如下:Python 将包含多个表的250GB JSON文件拆分为拼花地板,python,json,dask,parquet,Python,Json,Dask,Parquet,我有一个JSON文件,格式如下: { "Table1": { "Records": [ { "Key1Tab1": "SomeVal", "Key2Tab1": "AnotherVal" }, {
{
"Table1": {
"Records": [
{
"Key1Tab1": "SomeVal",
"Key2Tab1": "AnotherVal"
},
{
"Key1Tab1": "SomeVal2",
"Key2Tab1": "AnotherVal2"
}
]
},
"Table2": {
"Records": [
{
"Key1Tab1": "SomeVal",
"Key2Tab1": "AnotherVal"
},
{
"Key1Tab1": "SomeVal2",
"Key2Tab1": "AnotherVal2"
}
]
}
}
根键是SQL数据库中的表名,对应的值是行。
我想将JSON文件拆分为单独的拼花文件,每个拼花文件代表一个表。
即表1.拼花
和表2.拼花
最大的问题是文件的大小阻止我将其加载到内存中。
因此,我尝试使用dask.bag来适应文件的嵌套结构
import dask.bag as db
from dask.distributed import Client
client = Client(n_workers=4)
lines = db.read_text("filename.json")
但是用行评估输出。take(4)
表明dask无法正确读取新行
('{\n', ' "Table1": {\n', ' "Records": [\n', ' {\n')
我试图寻找具体问题的解决方案,但运气不佳
是否有可能使用dask解决拆分问题,或者是否有其他工具可以完成此工作?如建议,请尝试此方法
这可能足够了,但我不确定如果没有足够的内存将整个生成的数据帧存储在内存中,它将如何运行
导入dask.dataframe作为dd
从dask.distributed导入客户端
client=client()
df=dd.read_json(“filename.json”)
df.to_parquet(“filename.parquet”,engine='pyarrow')
文件
如果Dask在单个系统上不分块处理文件(它可能不喜欢这样做,因为JSON以这种方式解析显然不友好..尽管很遗憾,我无法访问我的测试系统来验证这一点),并且系统内存无法处理这个巨大的文件,您可以通过创建一个大交换文件来扩展系统内存和磁盘空间 请注意,这将创建一个约300G的文件(增加
count
字段以获取更多信息),与内存相比,速度可能会非常慢(但可能仍然足够快,满足您的需要,特别是如果是一次性的)
#创建和配置交换文件
dd if=/dev/zero of=swapfile.img bs=10M计数=30000状态=进度
chmod 600 swapfile.img
mkswap swapfile.img
swapon swapfile.img
#
#运行内存贪婪任务
# ...
#确保流程已退出
#
#禁用并删除交换文件以回收磁盘空间
swapoff swapfile.img#可能会挂很长时间
rm swapfile.img
问题是,DASK默认情况下会在新行字符上拆分文件,并且不能保证这不会在一个表的中间。事实上,即使您做对了,您仍然需要操纵结果文本,为每个分区生成完整的JSON对象
_.compute()
[{'Table1': {'Records': [{'Key1Tab1': 'SomeVal', 'Key2Tab1': 'AnotherVal'},
{'Key1Tab1': 'SomeVal2', 'Key2Tab1': 'AnotherVal2'}]}},
{},
{'Table2': {'Records': [{'Key1Tab1': 'SomeVal', 'Key2Tab1': 'AnotherVal'},
{'Key1Tab1': 'SomeVal2', 'Key2Tab1': 'AnotherVal2'}]}},
{},
{},
{}]
例如:
def myfunc(x):
x = "".join(x)
if not x.endswith("}"):
x = x[:-2] + "}"
if not x.startswith("{"):
x = "{" + x
return [json.loads(x)]
db.read_text('temp.json',
linedelimiter="\n },\n",
blocksize=100).map_partitions(myfunc)
在本例中,我特意将blocksize设置为小于每个部分,以演示:每个分区将获得一个JSON对象,或者什么都没有
_.compute()
[{'Table1': {'Records': [{'Key1Tab1': 'SomeVal', 'Key2Tab1': 'AnotherVal'},
{'Key1Tab1': 'SomeVal2', 'Key2Tab1': 'AnotherVal2'}]}},
{},
{'Table2': {'Records': [{'Key1Tab1': 'SomeVal', 'Key2Tab1': 'AnotherVal'},
{'Key1Tab1': 'SomeVal2', 'Key2Tab1': 'AnotherVal2'}]}},
{},
{},
{}]
当然,在您的情况下,您可以立即对JSON执行某些操作,而不是返回它,或者您可以映射到下一个编写函数。处理大型文件时,成功的关键是将数据作为流处理,即在类似过滤器的程序中处理
JSON格式很容易解析。下面的程序逐字符读取输入字符(I/O应为bufferred),并将顶级JSON对象切割为单独的对象。它正确地遵循数据结构,而不是格式
演示程序只打印“--下一个输出文件--”,在这里应该实现真正的输出文件切换。空格剥离是作为奖励实现的
import collections
OBJ = 'object'
LST = 'list'
def out(ch):
print(ch, end='')
with open('json.data') as f:
stack = collections.deque(); push = stack.append; pop = stack.pop
esc = string = False
while (ch := f.read(1)):
if esc:
esc = False
elif ch == '\\':
esc = True
elif ch == '"':
string = not string
if not string:
if ch in {' ', '\t', '\r', '\n'}:
continue
if ch == ',':
if len(stack) == 1 and stack[0] == OBJ:
out('}\n')
print("--- NEXT OUTPUT FILE ---")
out('{')
continue
elif ch == '{':
push(OBJ)
elif ch == '}':
if pop() is not OBJ:
raise ValueError("unmatched { }")
elif ch == '[':
push(LST)
elif ch == ']':
if pop() is not LST:
raise ValueError("unmatched [ ]")
out(ch)
以下是我的testfile的示例输出:
{"key1":{"name":"John","surname":"Doe"}}
--- NEXT OUTPUT FILE ---
{"key2":"string \" ] }"}
--- NEXT OUTPUT FILE ---
{"key3":13}
--- NEXT OUTPUT FILE ---
{"key4":{"sub1":[null,{"l3":true},null]}}
原始文件是:
{
"key1": {
"name": "John",
"surname": "Doe"
},
"key2": "string \" ] }", "key3": 13,
"key4": {
"sub1": [null, {"l3": true}, null]
}
}
这种风格的东西可能是一个很好的中间解决方案(尽管更大的块大小,比如2x最大的db对象可能更合适,以防止一个对象出现在边界中)。。或者是一个具有精确的自定义解决方案—一旦文本块正确,您就可以处理数据,但这是完成工作最方便的方式。df=dd.read_json(“filename.json”)
确实可以一次性读取整个文件;唯一的例外是,如果您有行分隔的JSON,情况并非如此。