by_row vs rowwise iteration

时间:2017-04-04 22:29:54

标签: r dplyr purrr

有谁知道by_rowrowwise之间有什么区别?我试图抓住3个简单的网站,我似乎无法使用任何一种方法,所以我不确定我是否只是使用purr / dplyr错误。

数据:

structure(list(beer_brewerid = c("8481", "3228", "10325"), link = 
c("https://www.ratebeer.com/beer/8481/",  "https://www.ratebeer.com/beer/3228/", "https://www.ratebeer.com/beer/10325/" ), scrapedname = c("", "", "")), .Names = c("beer_brewerid",  "link", "scrapedname"), row.names = c(NA, 3L), class = "data.frame")

对于每个网址(或行),我想使用以下功能抓取网页:

dplyr approach:
    table %>%
      rowwise() %>%
      read_html() %>%
      extract2(2) %>%
      html_nodes("#_brand4 span") %>%
      html_text()

Purr方法:

#Apply function to each row
table %>%
  by_row(..f = parserows(), collate = c("rows"), .to = "scrapedname")

#Takes in row 
parserows = function(){
      read_html() %>%
      extract2(., 2) %>%
      html_nodes("#_brand4 span") %>%
      html_text()
}

purr方法中,我一直收到错误,其中缺少x而没有默认值。这个值不应该来自行号吗?否则,我将编写一个for循环,指定行号所在的索引。

使用这个magrittr管道,我的代码不断出现超时错误。所以:

  1. 使用purr / dplyr迭代我的df中的所有元素时,如何避免超时错误?如果是这样,我是否应该考虑使用trycatch或某种错误处理机制来捕获错误?

  2. rowwise / by_row真的意味着这项任务吗?我认为这些函数是针对一行中的每个元素进行迭代的,这不是我想要解决的问题。感谢。

    output = table$link %>%
    extract() %>%
    map(read_html) %>%
    html_nodes(row,"#_brand4 span") %>%
    html_text(row)
    

1 个答案:

答案 0 :(得分:5)

以下是@Thomas K的建议:

首先只使用purrr

library(purrr)
library(dplyr)
library(httr)
library(xml2)
library(rvest)

table$link %>%
  purrr::set_names() %>% 
  map(read_html) %>%
  map(html_node, "#_brand4 span") %>%
  map(html_text)

# $`https://www.ratebeer.com/beer/8481/`
# [1] "Föroya Bjór"
# 
# $`https://www.ratebeer.com/beer/3228/`
# [1] "King Brewing Company"
# 
# $`https://www.ratebeer.com/beer/10325/`
# [1] "Bavik-De Brabandere"

(注意不需要使用html_nodes(复数),而不是html_node(单数)。

混合dplyr / purrr替代方案,可让您将每个html文档保存在整洁的数据框中,如果您需要重复使用它们:

res <-
  table %>% 
  mutate(html = map(link, read_html),
         brand_node = map(html, html_node, "#_brand4 span"),
         scrapedname = map_chr(brand_node, html_text)) 

htmlbrand_node列存储为外部指针,并且不是非常适合打印的,所以这里是没有它们的结果数据框:

select(res, - html, - brand_node)

#   beer_brewerid                                 link          scrapedname
# 1          8481  https://www.ratebeer.com/beer/8481/          Föroya Bjór
# 2          3228  https://www.ratebeer.com/beer/3228/ King Brewing Company
# 3         10325 https://www.ratebeer.com/beer/10325/  Bavik-De Brabandere

glimpse(res)

# Observations: 3
# Variables: 5
# $ beer_brewerid <chr> "8481", "3228", "10325"
# $ link          <chr> "https://www.ratebeer.com/beer/8481/", "https://www.ratebeer.com/beer/3228/", "https://www.ratebeer.com/beer/10325/"
# $ scrapedname   <chr> "Föroya Bjór", "King Brewing Company", "Bavik-De Brabandere"
# $ html          <list> [<html lang="en">, <html lang="en">, <html lang="en">]
# $ brand_node    <list> [<span itemprop="name">, <span itemprop="name">, <span itemprop="name">]

对于超时问题,您也可以根据@Thomas K的评论简单地将read_html包裹在safely()possibly()中(这确实是tryCatch的替代品):

safe_read_html <- possibly(read_html, otherwise = read_html("<html></html>"))

但是为了解决你在服务器上过于努力的(可能的)实际问题,我建议httr::RETRY()让你以“指数退避时间”重试:

safe_retry_read_html <- possibly(~ read_html(RETRY("GET", url = .x)), otherwise = read_html("<html></html>"))

抓取时的一个好习惯是在服务器上真正轻柔,因此您甚至可以在每次请求之前手动添加偏移时间,例如Sys.sleep(1 + runif(1))

table$link %>%
  c("https://www.wrong-url.foobar") %>% 
  purrr::set_names() %>% 
  map(~ {
    Sys.sleep(1 + runif(1))
    safe_retry_read_html(.x)
  }) %>%
  map(html_node, "#_brand4 span") %>%
  map_chr(html_text)

#  https://www.ratebeer.com/beer/8481/  https://www.ratebeer.com/beer/3228/ 
#                        "Föroya Bjór"               "King Brewing Company" 
# https://www.ratebeer.com/beer/10325/         https://www.wrong-url.foobar 
#                "Bavik-De Brabandere"                                   NA 

最后,您有关于by_row() / rowwise()的单独问题 首先,请注意by_row已从purrr的开发版本中删除,并移至单独的包purrrlyr,无论如何它已被弃用,建议“使用组合:tidyr::nest(); dplyr::mutate(); purrr::map()

help("rowwise")开始,rowwise主要用于“创建列表变量时用于do()的结果”。

所以,不,不是“真的意味着这个任务”,它们将是多余的。