r - xml到数据框重复节点attr
问题描述
我正在尝试将一个巨大的 .xml 文件转换为数据框。它看起来像这样(但有数百万这样的 <Cli 集群):
<Cli Cd="11300000029" CoobAss="0.00" CoobRec="0.00">
<Op Mod="0202" VincME="N">
<Venc v165="4934.84" v170="4856.16"/>
</Op>
<Op Mod="1901" VincME="N">
<Venc v20="22877.77"/>
</Op>
</Cli>
<Cli Cd="11400000029" CoobAss="0.00" CoobRec="0.00">
<Op Mod="0204" VincME="N">
<Venc v165="5000.10"/>
</Op>
<Op Mod="1902" VincME="N">
<Venc v20="32000.22"/>
</Op>
</Cli>
每个 <Cli 可以有多个 <Op,每个 <Op 可以有多个 <Venc。
预期结果是:
tibble::tribble(
~Cd, ~CoobAss, ~CoobRec, ~Mod, ~VincME, ~v165, ~v170, ~v20,
"11300000029", "0.00", "0.00", "0202", "N", "4934.84", "4856.16", NA,
"11300000029", "0.00", "0.00", "1901", "N", NA, NA, "22877.77",
"11400000029", "0.00", "0.00", "0204", "N", "5000.10", NA, NA,
"11400000029", "0.00", "0.00", "1902", "N", NA, NA, "32000.22"
)
#> # A tibble: 4 x 8
#> Cd CoobAss CoobRec Mod VincME v165 v170 v20
#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 11300000029 0.00 0.00 0202 N 4934.84 4856.16 <NA>
#> 2 11300000029 0.00 0.00 1901 N <NA> <NA> 22877.77
#> 3 11400000029 0.00 0.00 0204 N 5000.10 <NA> <NA>
#> 4 11400000029 0.00 0.00 1902 N <NA> <NA> 32000.22
由reprex 包(v0.3.0)于 2020 年 10 月 1 日创建
我可以在不同的数据帧中获得 Cli、Op 和 Venc,但我不知道如何将它们像那样放在一起。
编辑:创建一个大文件
为了进行性能测试,您可以复制数据以增加文件大小。更改n
为所需大小:
xml = c('
<Cli Cd="11300000029" CoobAss="0.00" CoobRec="0.00">
<Op Mod="0202" VincME="N">
<Venc v165="4934.84" v170="4856.16"/>
</Op>
<Op Mod="1901" VincME="N">
<Venc v20="22877.77"/>
</Op>
</Cli>
<Cli Cd="11400000029" CoobAss="0.00" CoobRec="0.00">
<Op Mod="0204" VincME="N">
<Venc v165="5000.10"/>
</Op>
<Op Mod="1902" VincME="N">
<Venc v20="32000.22"/>
</Op>
</Cli>
')
n = 3
data = do.call("rbind", replicate(n, xml, simplify = FALSE))
write(data, "xml.xml")
解决方案
也许只是遍历所有Venc
节点并找到每个Venc
节点及其父节点(即Op
)和祖父节点(即Cli
)的属性?这里的关键是,尽管您有许多Cli
s、Op
s 或Venc
s,但树结构确保每个孩子只有一个父母/祖父母。对此,我们可以只做一个向后搜索。尝试这个:
library(rvest)
library(xml2)
library(purrr)
map_dfr(
html_nodes(xml, xpath = "//Venc"),
function(x) c(html_attrs(html_node(x, xpath = "ancestor::Cli")), html_attrs(html_node(x, xpath = "parent::Op")), html_attrs(x))
)
这里, xml
是一个由 .xml_document
返回的对象xml2::read_xml
。
更新
我使用规范测试了以下代码n = 1,000,000
,它创建了一个 424MB 的 XML 文档。在我的笔记本电脑上,完成所有所需的计算大约需要 20 分钟。
library(xml2)
library(rvest)
library(data.table)
library(tibble)
# xml should be replaced with your `xml_document` object
xml_ls <- as_list(html_nodes(xml, xpath = "//Cli"))
unpack_attrs <- function(x) {
f <- function(i) {
attrs <- attributes(i)
if (length(i) > 0L) i <- list(. = `attributes<-`(i, NULL))
c(i, `[[<-`(attrs, "names", NULL))
}
rbindlist(lapply(x, f), fill = TRUE)
}
recur_unpack_attrs <- function(xml_tree) {
recur_ <- function(dt, out) {
init <- dt[, unpack_attrs(.)]
if ("." != names(init)[[1L]]) {
out[, names(init) := init]
return(NULL)
}
out[, (names(init)[-1L]) := init[, -1L]]
recur_(init, out)
}
start <- unpack_attrs(xml_tree)
result <- start[, -1L]
recur_(start, result)
as_tibble(result)
}
res <- recur_unpack_attrs(xml_ls)
CPU 时间
user system elapsed
971.05 107.61 1150.56
试试看。让我知道结果。
第二次也是最后一次更新
我重新编写了大部分代码以获得更稳定的性能。我还删除了对rvest
. 但是,我不能永远这样做,因为我还有其他优先事项要处理。抱歉,这是我对您问题的最后一次更新。
library(xml2)
library(data.table)
library(tibble)
unpack_attr <- function(xi, i) {
attrs <- attributes(xi)
if (length(xi) < 1L || is.null(attrs[["names"]]) ) {
xi <- list(. = NA)
} else {
xi <- list(. = `attributes<-`(xi, NULL))
}
c(xi, `[[<-`(attrs, "names", NULL))
}
unpack_attrs <- function(x, ids = NULL) {
out <- list()
i <- 1L
while (i <= length(x)) {
out[[i]] <- unpack_attr(x[[i]])
i <- i + 1L
}
names(out) <- ids
rbindlist(out, fill = TRUE, idcol = TRUE)
}
recur_unpack_attrs <- function(xml_tree) {
out <- unpack_attrs(xml_tree)[, .id := as.character(.I)]
nms <- names(out)[-1:-2]
out_nms <- nms
while (!all(is.na(out[["."]]))) {
last <- copy(out)
out <- out[, unpack_attrs(., .id)]
tmp <- names(out)
conf <- match(out_nms, tmp, 0L); conf <- conf[conf > 0L]
if (length(conf) > 0L) tmp[conf] <- paste0(tmp[conf], "_", conf - 2L + length(out_nms))
out_nms <- c(out_nms, tmp[-1:-2])
names(out) <- tmp
out[last, (nms) := mget(paste0("i.", nms)), on = ".id"][, .id := as.character(.I)]
nms <- names(out)[-1:-2]
}
out[rowSums(is.na(out)) < length(out) - 1L, ..out_nms]
}
# replace xml with your xml_document object or "data.xml" the your file path
xml <- read_xml("data.xml")
xml_ls <- as_list(xml_find_all(xml, xpath = "//Cli"))
index <- seq_len(length(xml_ls))
tasks <- split(index, (index - 1L) %/% 50000L)
res <- as_tibble(rbindlist(lapply(tasks, function(task) recur_unpack_attrs(xml_ls[task])), fill = TRUE))
上面的代码在包含 500,000 个重复 (437MB) 以下内容的示例 xml 文档上运行良好:
<Cli Cd="11300000029" CoobAss="0.00" CoobRec="0.00">
<Op Mod="0202" VincME="N">
<Venc v165="4934.84" v170="4856.16"/>
</Op>
<Op Mod="1901" VincME="N">
<Venc v20="22877.77"/>
</Op>
</Cli>
<Cli Cd="11400000029" CoobAss="0.00" CoobRec="0.00">
<Op Mod="0204" VincME="N">
<Venc v165="5000.10"/>
</Op>
<Op Mod="0201" VincME="N">
<Venc v165="we000.10"/>
<Venc v165="we000.10"/>
<Venc v165="we000.10"/>
</Op>
<Op Mod="1902" VincME="N">
<Venc v20="32000.22"/>
</Op>
</Cli>
<Cli><Op><Venc v165="400.0"/></Op></Cli>
<Cli></Cli>
<Cli><Venc/></Cli>
<Cli><Venc v165="4343000.10"/><Venc v20="4343000.10"/></Cli>
<Cli>
</Cli>
<Cli><Op><Venc/></Op></Cli>
<Cli Cd="11400000024" CoobAss="0.00" CoobRec="0.00"/>
<Cli Cd="11400000024" CoobAss="0.00" CoobRec="0.00">
<Op Mod="4757" VincME="N"/>
</Cli>
性能(很明显,从 xml_document 对象到 R 列表的转换是瓶颈,但运行时间仍然可以接受)
> system.time({
+ xml_ls <- as_list(xml_find_all(xml, xpath = "//Cli"))
+ })
user system elapsed
1206.36 28.74 1250.89
>
> system.time({
+ index <- seq_len(length(xml_ls))
+ tasks <- split(index, (index - 1L) %/% 50000L)
+ res <- as_tibble(rbindlist(lapply(tasks, function(task) recur_unpack_attrs(xml_ls[task])), fill = TRUE))
+ })
user system elapsed
161.03 12.68 175.41
由于您的 xml 结构没有组织好,我必须做出几个关键假设:
您的所有数据都存储为标签属性而不是文本内容(即没有这样的东西
<Cli>blah blah</Cli>
)每个
<OP>
or<Venc>
必须驻留在<Cli>
. 但是,可以有没有属性的标签(例如<Cli><Op><Venc v165="4343000.10"/></Op></Cli>
)或缺少非<Cli>
层的标签(例如<Cli><Venc v165="4343000.10"/></Cli>
)。没有属性的标签将被丢弃。
拆包过程从最外层(即
<Cli>
)开始到最内层(即<Venc>
)。每次解压图层时,都会根据在该图层中找到的属性名称创建新列。这意味着如果存在以下情况,那么我们将看到冲突。
<Cli><Venc v160="333"></Cli>
<Cli><Op><Venc v160="434"></Op></Cli>
在上述情况下,v160
将在第一个解包时创建一个名为的列,但稍后将在第二个解包时<Cli>
尝试创建相同的v160
列。第二个将被赋予一个新名称以避免冲突。<Op>
<Cli>
v160
像这样的输出只是表明列名冲突。您必须手动决定如何合并这些列(例如v165
和v165_8
)
# A tibble: 6,000,000 x 10
Cd CoobAss CoobRec Mod VincME v165 v20 v165_8 v170 v20_10
<chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 11300000029 0.00 0.00 0202 N NA NA 4934.84 4856.16 NA
2 11300000029 0.00 0.00 1901 N NA NA NA NA 22877.77
3 11400000029 0.00 0.00 0204 N NA NA 5000.10 NA NA
最后但同样重要的是,R 的指针保护堆栈的默认最大大小为50000
. data.table::rbindlist
在加入列表之前预先分配指向列表中每个元素的指针,以最大限度地提高速度。然而,这也意味着我们必须在为该函数提供一个大列表(超过 50000 个元素)时进行内存管理。这就是为什么我们需要这一行tasks <- split(index, (index - 1L) %/% 50000L)
来将一个大列表绑定成不超过限制的小任务。
如果这次代码再次失败,那么您可能不得不向其他人寻求帮助。对于那个很抱歉。
推荐阅读
- mysql - mySQL 中的多个条件
- spring - 使用 setTarget 和 ProxyFactory(Object target) 方法为同一对象创建不同的 Aop 代理
- angular - Angular:绑定输入变量的常用方法
- reactjs - 在我现有的项目中,我使用了 componentWillMount()。现在它已被弃用。所以我该怎么做?
- android - 如何防止设备在科尔多瓦关闭
- python - 在 python 中的同一工作簿中创建 Excel 工作表
- reference - 返回不可变引用后面的可变引用,传递给函数
- python - 如何计算代码在python中运行了多少次
- authorize.net - Authorize.net eCheck 交易是否有金额限制?
- mqtt - 在一次传输中发送多个(键值)(所有相同的传感器)?