【问题标题】:Sort huge JSON file using bash or python使用 bash 或 python 对巨大的 JSON 文件进行排序
【发布时间】:2019-09-06 19:40:36
【问题描述】:

要求:我有一个 .gz 格式的 Json 文件。因此,压缩后的大小约为 500 MB。当我提取它时,json 文件几乎变成了大约 10 GB。提取的 JSON 文件逐行包含单个 JSON 对象。我想要的是使用任何 bash 脚本或 python 程序基于字段ps 对文件进行排序。

由于文件太大,不建议将其加载到内存中。因此,我使用 gzcat 和 cat bash 命令流式传输 JSON 数据,然后将它们通过管道传输到 jq 以进行排序。但要么系统在此过程中没有响应,要么我在 output.json 中得到空文件

>cat  sth2.json | parallel --pipe --group --block 1000M --recend '\n}\n' "jq -s -c 'sort_by(.ps) | .[]'"  > "output.json"
>gzcat  sth2.json.gz | parallel --pipe --group --block 1000M --recend '\n}\n' "jq -s -c 'sort_by(.ps) | .[]'"  > "output.json"

硬件: 16GB 内存, 酷睿 i5 处理器

示例 JSON 数据:-

{
    "ps":"abc"
    ....
}
{   
    "ps":"def"
    ......
}
{
    "ps":"abc"
    ....
}

预期输出

{
    "ps":"abc"
    ....
}
{   
    "ps":"abc"
    ....
}
{
    "ps":"def"
    ....
}

我不明白我做错了什么。谁能建议如何对如此巨大的 JSON 文件进行排序? 我关注的链接: https://github.com/joelpurra/jq-hopkok/tree/master/src/parallelism

另外,没有 Hadoop 的情况下,我可以通过任何 Map reduce 来做些什么吗?

方法 1:将数据流式传输到本地 Sqlite DB。

import sqlite3
import fileinput

PATH=".../sqlite-snapshot-201904101324/testDB.db"
insert_query="INSERT INTO feeds (data) VALUES (?)"

def db_connect(db_path=PATH):
    con = sqlite3.connect(db_path)
    return con

con = db_connect() # connect to the database
cur = con.cursor() # instantiate a cursor obj

record_count = 0
for line in fileinput.input():
    cur.execute(insert_query,(line,))

命令行:

>gzcat sth.json.gz | python insert.py

【问题讨论】:

  • 那么如何对 JSON 文件进行排序呢?有没有可用的实用程序或其他任何东西?
  • 是的,没错,我测试了大约 20 行,我可以看到它们。
  • 如果可以,例如使用排序键为行添加前缀,以便它们可以作为文本而不是 JSON 进行排序,然后 GNU 排序可以轻松地对 10GB 以上的文件进行排序,而无需将它们加载到内存中。
  • 我将“单个 JSON 对象逐行”解释为 JSONL
  • @Tomalak 在上面的评论中您提到使用 python 将 .gz 文件内容流式传输到 sqlite。您能否提供更多详细信息如何做到这一点

标签: python json bash sorting jq


【解决方案1】:

这是基于其中一个 cmets 中的建议的一种解决方案:

如果可以,例如使用排序键为行添加前缀,以便它们可以作为文本而不是 JSON 进行排序,然后 GNU sort 可以轻松地对 10GB 以上的文件进行排序,而无需将它们加载到内存中。 ——那个人

您可以使用 jq 执行以下操作:

jq -cr '"\(.ps)\t\(.)"' 

这将产生带有制表符分隔值的行,如下所示:

abc {"ps":"abc","x":0}
abc {"ps":"abc","x":1}

使用 -c 选项可确保将每一对(即排序键和对象)写入单行。

现在您可以轻松地对行进行排序,例如使用sort;然后使用例如cut 剥离 .ps 字段。

最后,如果你真的想格式化输出,你可以再次使用jq(例如jq .),关键是jq默认是面向流的。

警告

以上假设 .ps 值是无制表符的。如果不是这种情况,那么您可以使用不同的字段分隔符,或者:

jq -cr '([.ps] | @tsv) + "\t" + tostring'

【讨论】:

  • @peak 如果我错了请纠正我,但是当你排序时,整个 json 数据将被加载到内存中。
  • gzcat sth.json.gz | jq -cr '"\(.ps)\t\(.)"' | sort | cut -f 2 >> abc 这是我使用的命令。它的运行就像永远一样。
  • 使用 sort 的 -k 选项,这样您就只需要对一个键进行排序。也许使用 -S 选项进行微调也可能会有所帮助。您可能还想检查您的sort 是否足够最新。
  • 您可能还想了解 -T 选项以及 TMPDIR 的作用。
【解决方案2】:

您可以使用 base64 编码和解码来代替担心不同类型的边缘情况。

这种方法创建了两个以制表符分隔的列。第一列包含用于排序的值。第二个是base64格式的完整JSON。

排序后我们得到第二列和base64解码。

gzcat sth.json.gz | \
  | jq -cr '[(. | .ps | @text), (. | @base64)] | @tsv' \ # Create a TSV where the first column contains the value that should be sorted on.
  | sort -t$'\t' -k1,1 \ # Sort only on first column.
  | cut -f 2 \ # Get the base64 encoded JSON from seconds column.
  | base64 -d \ # Decode the base64 encoded JSON. (Ignores newlines)
  | jq -c . # Required to place each JSON on separate line.

【讨论】:

    猜你喜欢
    • 2011-11-28
    • 2021-02-15
    • 2015-09-03
    • 2016-06-10
    • 2017-09-01
    • 2020-11-21
    • 1970-01-01
    • 1970-01-01
    • 2012-02-08
    相关资源
    最近更新 更多