Получить список магазинов Whole Foods, используя rvest - PullRequest
0 голосов
/ 01 июля 2018

Я пытаюсь использовать rvest, чтобы получить список магазинов Whole Foods. Я успешно использовал эту технику, получая информацию из Википедии, ФИФА, Yahoo! Финансы и т. Д., Но эта «таблица» охватывает более одной страницы, но у нее все одинаковые URL Извините, я не лучший в HTML и не знаю правильного названия типа предмета.

Мой вопрос: как получить больше, чем просто первую страницу данных?

Дополнительные кредиты: мне нужен только почтовый индекс, который я понял, как это сделать. Если вы хотите бросить вызов себе и выяснить, как извлечь адрес, это может быть полезно! Проблема в том, что Адрес и Город объединены без разделителя. Я думал что-то вроде поиска верхнего регистра сразу после нижнего регистра, но есть случай, когда адрес заканчивается верхним регистром.

Код ниже:

get_markets <- function() {
    url <- "https://www.wholefoodsmarket.com/stores/list/state"
    xpath <- '//*[@id="block-views-store-locations-by-state- 
        state"]/div/div/div[3]'
    selector <- "#block-views-store-locations-by-state-state"

    tbl <- url %>%
      read_html() %>%
      html_nodes(css = selector) %>% # or xpath = xpath
      .[[1]] %>%
      html_text(trim = TRUE) %>%
      strsplit('\\s*\\n\\s*') %>%
      unlist() %>%
      .[-c(1:3)] %>%
      matrix(ncol = 7, byrow = TRUE) %>%
      as.data.frame(stringsAsFactors = FALSE) %>%
      mutate(
        zip = stringr::str_extract(V2, "(?<= )\\d{5}(?=U|-)")
      ) %>%
      select(store = V1, zip) %>%
      na.omit()
}

Ответы [ 2 ]

0 голосов
/ 02 июля 2018

Ссылки внизу страницы предоставляют страницы, которые вам нужно почистить. Если вы посмотрите на hrefs, они следуют шаблону: https://www.wholefoodsmarket.com/stores/list/state?page=1, state?page=2 и т. Д., Где первая страница - ?page=0, поэтому вы можете сгенерировать весь список с помощью paste и seq. * 1006. *

Перед очисткой, если вы заглянете в robots.txt сайта , он указывает задержку сканирования на 10 секунд, поэтому нам нужно добавить вызов Sys.sleep, чтобы замедлить скребок.

Стратегически для каждой страницы выберите все магазины, затем используйте итерацию по этим узлам и извлеките нужные фрагменты. Адрес отформатирован в несколько div с, поэтому вызов html_text для них отдельно и сворачивание его в строку, разделенную новой строкой, сделает его более удобным для использования.

Использование purrr для итерации (сила мурлыкания со списками и map_dfr делают его отличным дополнением к rvest),

library(purrr)
library(rvest)

# make URLs
urls <- paste0('https://www.wholefoodsmarket.com/stores/list/state?page=', 0:22)

# only read first three pages to show concept
pages <- map(urls[0:2], function(url){
    Sys.sleep(10)    # set crawl delay
    read_html(url)
})

# parse pages
stores <- pages %>% 
    map_dfr(function(page){
        page %>% 
            html_nodes('.views-row') %>%    # select store enclosing tags
            map(html_nodes, '.field-content') %>%    # select content nodes for each store
            keep(~length(.x) == 7) %>%    # discard non-store elements
            map_dfr(~list(title = html_text(.x[[2]]), 
                          address = html_nodes(.x[[3]], 'div') %>% 
                              map(html_text) %>% 
                              paste(collapse = '\n'), 
                          phone = html_text(.x[[4]]), 
                          hours = html_text(.x[[5]])))
    })

stores
#> # A tibble: 42 x 4
#>    title          address                  phone  hours                   
#>    <chr>          <chr>                    <chr>  <chr>                   
#>  1 Montgomery     "1450 Taylor Rd\n1450 T… 334.5… 8:00am - 9:00pm seven d…
#>  2 Mountain Brook "3100 Cahaba Village Pl… 205.9… 7:00 am to 10:00 pm. Se…
#>  3 Mobile         "3968 Airport Blvd\n396… 251-2… 8:00 a.m. to 9:00 p.m. …
#>  4 Hoover         "3780 Riverchase Villag… 205-7… 7:00am - 10:00pm Seven …
#>  5 Huntsville     "2501 Memorial Pkwy SW\… 256.8… 8:00am - 9:00pm seven d…
#>  6 Chandler       "2955 West Ray Road\n29… 480-8… "7:00am -10:00pm Seven …
#>  7 Sedona         "1420 West Hwy. 89A\n14… 928-2… 7:00am- Coffee/Juice ba…
#>  8 Scottsdale     "7111 East Mayo Bouleva… 480.5… 7:00am - 10:00pm Seven …
#>  9 Flagstaff      "320 S. Cambridge Lane\… 928-7… 7:00am - 9:00pm Seven d…
#> 10 Tempe          "5120 S. Rural Rd\n5120… 480.4… 7:00am - 10:00pm Seven …
#> # ... with 32 more rows

Отсюда вытащите почтовый индекс из адреса с помощью regex или анализатора адресов, если хотите.

0 голосов
/ 01 июля 2018

Разобрался. Ниже мое решение. Вероятно, лучшие из них там:

library(tidyverse)
library(rvest)

get_url <- function(page) {
  if (page == 0) {
    url <- "https://www.wholefoodsmarket.com/stores/list/state"
  } else {
    url <- paste0("https://www.wholefoodsmarket.com/stores/list/state?page=", page)
  }
  return(url)
}

get_last <- function() {
  url <- "https://www.wholefoodsmarket.com/stores/list/state"
  xpath <- '//*[@id="block-views-store-locations-by-state-state"]/div/div/div[4]/ul/li[11]/a'
  out <- url %>%
    read_html() %>%
    html_nodes(xpath = xpath) %>%
    html_text() %>%
    as.numeric()
}

get_markets <- function(page) {
  url <- get_url(page)
  xpath <- '//*[@id="block-views-store-locations-by-state-state"]/div/div/div[3]'
  selector <- "#block-views-store-locations-by-state-state"

  tbl <- url %>%
    read_html() %>%
    html_nodes(css = selector) %>%
    .[[1]] %>%
    html_text(trim = TRUE) %>%
    strsplit('\\s*\\n\\s*') %>%
    unlist() %>%
    .[-c(1:3)] %>%
    matrix(ncol = 7, byrow = TRUE) %>%
    as.data.frame(stringsAsFactors = FALSE) %>%
    mutate(
      zip = stringr::str_extract(V2, "(?<= )\\d{5}(?=U|-)")
      ) %>%
    select(store = V1, zip) %>%
    na.omit()

  return(tbl)
}

markets <- list()

for (i in 1:get_last()) {
  markets[[i]] <- get_markets(i-1)
}
markets <- Reduce(rbind, markets)
...