使用 R 将大型 jsonl 文件读入 SQLite 数据库

Reading a large jsonl file into an SQLite database using R

这是我的第一个问题,请见谅!

我正在尝试访问 54GB .jsonl 文件中的 Twitter 数据。我无论如何都不是程序员,因此非常感谢替代解决方案、意见或建议。

在我目前的尝试中,我尝试使用 stream_in().

将文件直接流式传输到 SQLite 数据库中
library(DBI)
library(jsonlite)
library(RSQLite)

#I created the sqlite database
tweetdb <- dbConnect(RSQLite::SQLite(), "tweet.sqlite")
dbDisconnect(tweetdb)
unlink("tweetdb.sqlite")

#now I'm trying to stream data directly into the database
dbWriteTable(tweetdb, "all_tweets", stream_in(file("all_tweet_ids.jsonl")))

该代码似乎有效,但我确信有更好、更快的方法来执行此操作。目前,RStudio 说 opening file input connection. Found 870000 records... 在相当快地上升到大约 720000 条记录后,它停留在该数字大约一个小时,然后转到 870000,现在再次卡住。

我的最终目标是拥有一个数据库,data.frame,data.table,或数据的任何表示,以便我可以访问其中的一部分,构建子集,操作它,并工作用它。 SQL 数据库将是可取的 - 我相信 - 因为我正在使用 MacBook 并且我无法获得更多的计算能力。我正在努力对这些推文进行文本分析。

我想知道您是否 运行 遇到了内存问题。因为您使用的是 stream_in,我将假设(这是此答案的要求)数据采用 ndjson 格式(换行符分隔 json)。这样,您可以将文件拆分为多个较小的文件(也许只是两个文件),然后单独读取它们,从而允许垃圾收集(内存管理)工作。

命令行(不是 R)split 会将一个文本文件拆分为多个文件。它在我见过的每个 linux 系统上都可用;在 windows 上,在 Windows 的 Git 内;我在我的 mac 上找到了它(尽管我没有对它进行 R 开发)。

我将从一个简单的 10 行 ndjson 文件开始:

dat <- data.frame(num=1:10)
jsonlite::stream_out(dat) # this will be the single file's contents
# {"num":1}
# {"num":2}
# {"num":3}
# {"num":4}
# {"num":5}
# {"num":6}
# {"num":7}
# {"num":8}
# {"num":9}
# {"num":10}
# Complete! Processed total of 10 rows.

jsonlite::stream_out(dat, file("quux.ndjson"))
# opening file output connection.
# Complete! Processed total of 10 rows.
# closing file output connection.

现在我可以将该文件拆分为(在本例中)两个文件:

list.files(pattern = "^quux.")
# [1] "quux.ndjson"
system2("split", c("--lines", "8", "quux.ndjson", "quux.ndjson."))
list.files(pattern = "^quux.")
# [1] "quux.ndjson"    "quux.ndjson.aa" "quux.ndjson.ab"
system2("wc", c("-l", list.files(pattern = "^quux.")))
#  10 quux.ndjson
#   8 quux.ndjson.aa
#   2 quux.ndjson.ab
#  20 total

关于split

  • --lines 是单个文件中的最大行数。在您的情况下,您可能需要 500000 左右,但更小应该不会有什么坏处。
  • 第一个quux.ndjson为输入文件名
  • 第二个quux.ndjson.(注意后面的.)是用于输出文件的前缀;如果你不提供这个,那么文件将被命名为 xaaxabxac,...这主要是为了美观,使用你喜欢的任何前缀

然后我 运行 wc -l 只是为了显示每个文件的行数。不是必需的,只是示范性的。我们可以进一步证明拆分

readLines("quux.ndjson.ab")
# [1] "{\"num\":9}"  "{\"num\":10}"

从这里开始,您应该能够遍历文件并做任何您想做的事情,也许

# note the updated pattern
for (fn in list.files(pattern = "^quux.ndjson.")) {
  dbWriteTable(tweetdb, "all_tweets", stream_in(file(fn)))
}

(未经测试)。

由于我没有可用于测试的“大型”数据,因此有一个想法:根据执行此操作的频率,您可能 考虑调用 gc() 在每个 stream_in 之间。虽然它是自动的并且应该自己处理事情,但我想知道这是否会对您的流程产生任何影响。 gc 将强制进行垃圾回收,取消分配未引用的对象。我觉得没必要,只是一个想法。