【问题标题】:Scraping XML files using R: xpathSApply使用 R 抓取 XML 文件:xpathSApply
【发布时间】:2016-08-01 16:18:51
【问题描述】:

我正在学习 R 来抓取大型 XML(最大 100mb),所以绝对不是专业人士。 XML 文件遵循非常严格的格式:每个节点都是一个交易,涉及卖家(一个或多个)、买家(一个或多个)和转移的股票类型(一个或多个)。每一个都有一个或多个详细信息(姓名、地址等)。这是一个匿名的 sn-p:

    <deals>
   <deal>
      <sellers>
         <seller>
            <name>Dave</name>
            <address>Street name</address>
            <city>New York, NY</city>
         </seller>
      </sellers>
      <buyers>
         <buyer>
            <name>John</name>
            <city>Denver, CO</city>
            <phone>123456789</phone>
         </buyer>
         <buyer>
            <name>Pete</name>
            <address>Avenue name</address>
            <city>Kansas, MI</city>
         </buyer>
      </buyers>
      <stocks>
         <stock>
            <id>GOOGL</id>
         </stock>
         <stock>
            <id>MSFT</id>
            <id>0000789019</id>
         </stock>
      </stocks>
   </deal>

   <deal>
      <sellers>
         <seller>
            <name>Linda</name>
            <city>Philadelphia, PA</city>
            <phone>876-543-210</phone>
         </seller>
         <seller>
            <name>Anne</name>
            <address>Road name</address>
         </seller>
      </sellers>
      <buyers>
         <buyer>
            <name>Monica</name>
            <address>Alley name</address>
            <city>Pensacola, CA</city>
         </buyer>
      </buyers>
      <stocks>
         <stock>
            <id>INTC</id>
            <id>0000050863</id>
         </stock>
         <stock>
            <id>DELL</id>
         </stock>
         <stock>
            <id>HPQ</id>
            <id>0000047217</id>
         </stock>
      </stocks>
   </deal>
</deals>

在尝试抓取数据时,问题出在“一个或多个”上。现在,我只想创建一个包含交易号(序列号)和卖家信息的数据框,并使用以下代码:

xmldata <- xmlRoot(xmlTreeParse("snippet.xml", useInternalNodes = TRUE))
seller_name <- xpathSApply(xmldata, "//deal/sellers/seller/name", xmlValue)
seller_address <- xpathSApply(xmldata, "//deal/sellers/seller/address", xmlValue)
seller_city <- xpathSApply(xmldata, "//deal/sellers/seller/city", xmlValue)
seller_phone <- xpathSApply(xmldata, "//deal/sellers/seller/phone", xmlValue)

不幸的是,这不起作用有两个原因。首先,我无法确定哪个卖家属于哪个交易。其次,由于许多细节是可选的(地址、城市、电话号码),向量的长度各不相同,我无法分辨街道名称或电话号码属于谁:

> seller_name
[1] "Dave"  "Linda" "Anne" 
> seller_address
[1] "Street name" "Road name"  
> seller_phone
[1] "876-543-210"

我尝试使用 for 循环遍历各个交易,但它太慢了。非常感谢任何帮助,谢谢!

【问题讨论】:

  • 感谢大家提供的所有出色解决方案!我将尝试实施以下建议并随时为您提供最新信息。
  • 首先,很抱歉更新缓慢,感谢您的帮助。不幸的是,事实证明 R 对于大型 XML 文件来说太慢了。最终我选择了另一种解决方案:将 XML 文件作为一个大字符串读取到 Python 中,然后将字符串拆分为交易。对于每笔交易,我选择了卖家、买家和股票(通过拆分子字符串)。然后我再次拆分这些子字符串(针对每个单独的卖家、买家和股票)并进行正则表达式匹配以获取名称、地址等。是的,你没看错:这是一大堆 for 循环,但是仍然比 R 快...

标签: xml r


【解决方案1】:

创建一个函数Value,它给出node[[name]]xmlValue,但如果结果为NULL,则返回NA。使用它创建一个函数getRow,它检索一行数据。最后将getRow 应用于 XML 输入,如图所示。

Value <- function(node, name) c(xmlValue(node[[name]]), NA)[1]
getRow <- function(node) sapply(c("name", "address", "city", "phone"), Value, node = node)

t(xpathSApply(xmldata, "//deal/sellers/seller", getRow))

给予:

     name    address       city               phone        
[1,] "Dave"  "Street name" "New York, NY"     NA           
[2,] "Linda" NA            "Philadelphia, PA" "876-543-210"
[3,] "Anne"  "Road name"   NA                 NA 

注意:为了将来的重现性,输入文件snippet.xml包含:

<?xml version="1.0" encoding="UTF-8"?>

<deals>
   <deal>
      <sellers>
         <seller>
            <name>Dave</name>
            <address>Street name</address>
            <city>New York, NY</city>
         </seller>
      </sellers>
      <buyers>
         <buyer>
            <name>John</name>
            <city>Denver, CO</city>
            <phone>123456789</phone>
         </buyer>
         <buyer>
            <name>Pete</name>
            <address>Avenue name</address>
            <city>Kansas, MI</city>
         </buyer>
      </buyers>
      <stocks>
         <stock>
            <id>GOOGL</id>
         </stock>
         <stock>
            <id>MSFT</id>
            <id>0000789019</id>
         </stock>
      </stocks>
   </deal>

   <deal>
      <sellers>
         <seller>
            <name>Linda</name>
            <city>Philadelphia, PA</city>
            <phone>876-543-210</phone>
         </seller>
         <seller>
            <name>Anne</name>
            <address>Road name</address>
         </seller>
      </sellers>
      <buyers>
         <buyer>
            <name>Monica</name>
            <address>Alley name</address>
            <city>Pensacola, CA</city>
         </buyer>
      </buyers>
      <stocks>
         <stock>
            <id>INTC</id>
            <id>0000050863</id>
         </stock>
         <stock>
            <id>DELL</id>
         </stock>
         <stock>
            <id>HPQ</id>
            <id>0000047217</id>
         </stock>
      </stocks>
   </deal>
</deals>

【讨论】:

  • 如果您还想包含子值,例如,如果 的子组,您会怎么做>移动>。您是否必须解析数据两次,即一次用于“//deal/sellers/seller”,另一次用于“//deal/sellers/seller/phone”并合并两个结果,或者是否有直接的方法一次性完成?
【解决方案2】:

这里有两个问题:

  1. 绑定不同列数的行 --> 试试dplyrrbind.fill()
  2. 在提取卖家信息时丢失信息 --> 将您的 xml 分块到交易中,然后循环或遍历这些节点集块。

【讨论】:

    【解决方案3】:

    我想我的策略是使用xmlEventParse() 遍历文件,跟踪一个唯一标识符以指示不同的状态(例如,“交易”、“买家”、“卖家”)以及名称、地址等与该标识符相关联。

    我会使用environment() 来收集信息。

    uid <- 0L
    key <- new.env(parent=emptyenv())
    name <- new.env(parent=emptyenv())
    address <- new.env(parent=emptyenv())
    

    xmlEventParse() 允许处理节点的回调。回调作为命名的函数列表提供,其名称对应于触发回调的 xml 实体。因此,要开始,我可能会列出在观察到实体时触发的“处理程序”列表。处理程序只是增加唯一标识符并记录相应的状态

    handlers=list(deal=function(...) {
        uid <<- uid + 1L
        key[[as.character(uid)]] <- "deal"
    }, buyer=function(...) {
        uid <<- uid + 1L
        key[[as.character(uid)]] <- "buyer"
    }, seller=function(...) {
        uid <<- uid + 1L
        key[[as.character(uid)]] <- "seller"
    })
    

    'branches' 类似于处理程序,只是它们接收 xml 节点以进行进一步计算。这些用于提取叶级信息

    branches=list(name=function(node) {
        name[[as.character(uid)]] <- xmlValue(node)
    }, address=function(node) {
        address[[as.character(uid)]] <- xmlValue(node)
    })
    

    拥有一些我称之为“final”的函数也很有用,用于处理收集到的数据,特别是将每个环境强制转换为 data.frame

    final=list(key=function() {
        k <- as.list(key)
        data.frame(uid=as.integer(names(k)), value=as.character(k))
    }, name=function() {
        k <- as.list(name)
        data.frame(uid=as.integer(names(k)), name=as.character(k),
                   stringsAsFactors=FALSE)
    }, address=function() {
        k <- as.list(address)
        data.frame(uid=as.integer(names(k)), address=as.character(k),
                   stringsAsFactors=FALSE)
    })
    

    我将所有这些放在一个“工厂”中,我可以使用它来创建独立的实例来解析我的文件

    events_factory <- function() {
        uid <- 0L
        key <- new.env(parent=emptyenv())
        name <- new.env(parent=emptyenv())
        address <- new.env(parent=emptyenv())
    
        list(handlers=list(deal=function(...) {
                 uid <<- uid + 1L
                 key[[as.character(uid)]] <- "deal"
             }, buyer=function(...) {
                 uid <<- uid + 1L
                 key[[as.character(uid)]] <- "buyer"
             }, seller=function(...) {
                 uid <<- uid + 1L
                 key[[as.character(uid)]] <- "seller"
             }),
    
             branches=list(name=function(node) {
                 name[[as.character(uid)]] <- xmlValue(node)
             }, address=function(node) {
                 address[[as.character(uid)]] <- xmlValue(node)
             }),
    
             final=list(key=function() {
                 k <- as.list(key)
                 data.frame(uid=as.integer(names(k)), value=as.character(k))
             }, name=function() {
                 k <- as.list(name)
                 data.frame(uid=as.integer(names(k)), name=as.character(k),
                            stringsAsFactors=FALSE)
             }, address=function() {
                 k <- as.list(address)
                 data.frame(uid=as.integer(names(k)), address=as.character(k),
                            stringsAsFactors=FALSE)
             }))
    }
    

    并且在使用中代码看起来像

    library(XML)
    fname <- "~/Downloads/snippet.xml"
    e <- events_factory()
    invisible(xmlEventParse(fname, e$handlers, branches=e$branches))
    Reduce(function(x, y) merge(x, y, all.x=TRUE),
           lapply(e$final, do.call, list()))
    

    导致

    > Reduce(function(x, y) merge(x, y, all.x=TRUE),
    +        lapply(e$final, do.call, list()))
      uid  value   name     address
    1   1   deal   <NA>        <NA>
    2   2 seller   Dave Street name
    3   3  buyer   John        <NA>
    4   4  buyer   Pete Avenue name
    5   5   deal   <NA>        <NA>
    6   6 seller  Linda        <NA>
    7   7 seller   Anne   Road name
    8   8  buyer Monica  Alley name
    

    代码中有很多重复,所以可能是使它更紧凑的聪明方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多