by_row против построчной итерации

Кто-нибудь знает, в чем разница между by_row и rowwise? Я пытаюсь очистить 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")

Для каждого URL-адреса (или строки) я хотел бы очистить веб-страницу, используя следующую функцию:

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

Мурлыкающий подход:

#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)
    

person petergensler    schedule 04.04.2017    source источник
comment
Я бы рассматривал ваши ссылки как вектор и сопоставлял их: my_dat$link %>% map(read_html) %>% ...   -  person Thomas K    schedule 05.04.2017
comment
Единственная проблема заключается в том, что когда у меня есть много ссылок, которые я хочу очистить, я продолжаю получать ошибки тайм-аута. Смотрите обновленный код.   -  person petergensler    schedule 05.04.2017
comment
Взгляните на purrr::safely и purrr::transpose.   -  person Thomas K    schedule 05.04.2017


Ответы (1)


Вот как могут выглядеть предложения @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)) 

Столбцы html и brand_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() при создании переменных-списков».

Так что нет, ни один из них "не предназначен для этой задачи", они были бы лишними.

person Aurèle    schedule 05.04.2017
comment
Спасибо за такое подробное объяснение, так как я понял ранее, что rowwise — это не совсем то, что я хотел сделать, лол. Я видел некоторые решения, которые возможно использовать, но это выглядело несколько отрывочно. У меня есть около 7000 URL-адресов, которые мне нужно просмотреть, так что это очень полезно. - person petergensler; 07.04.2017
comment
Должны ли вы объединять несколько команд карты, используя канал magrittr? Мне очень нравится идея использования карты с каналом, но я немного не уверен, что ~{ } делают с командой карты - person petergensler; 28.05.2017
comment
Вы можете либо объединить map(), как это сделал я, либо объединить все в одну. Думаю, это дело вкуса. Чтобы узнать значение тильды ~, см., например, purrr виньетки. По сути, это сокращенная анонимная функция с помощью универсальной записи формулы. - person Aurèle; 31.05.2017
comment
для полноты этот код намного проще понять, но не такой подробный, как ваш ответ: getBrewerName <- function(df){ possibly(df$scrapedname <- df %>% extract2("link") %>% map(read_html) %>% map(html_nodes, xpath = '//*[(@id = "_brand4")]//span') %>% map(html_text) %>% unlist , NA) } - person petergensler; 26.09.2017