Rvest парсинг веб-страниц с помощью javascript

Я пытаюсь очистить ежедневный прогноз от Пять тридцать восемь с помощью rvest, но мой интересующий объект, похоже, является объектом javascript, который мне даже трудно найти, где и что искать. (Я плохо разбираюсь в CSS или Javascript, хотя последние пару дней пытался самообразоваться.)

Изучив элемент веб-страницы и селектор CSS, я выяснил следующее:

  • Место для поиска - <div id = "polling-avg-chart">, поэтому я попробовал

    library(rvest)
    url <- 
      "https://projects.fivethirtyeight.com/election-2016/national-primary-polls/democratic/"
    
    url %>% 
      read_html() %>% 
      html_nodes("#polling-avg-chart")
    

    без особого успеха. Вывод просто

    {xml_nodeset (1)}

    [1] <\div id = "polling-avg-chart"></div>\n

  • Результаты отдельных опросов в точках находятся в <g style = "clip-path: url("#line-clippoll_avg");"> ... </g>, где вы видите 502 местоположения в цифрах. Я предполагаю, что мне придется перевести cx и cy каждого узла в соответствующие проценты, что делает <g class = "flag-box" transform = "translate(30, 161.44093322753096)">...</g> и так далее.

  • Однако я не вижу базовых данных для линии прогноза, не точек.

  • Когда я позволяю своему курсору навести курсор на диаграмму, я вижу такие вещи, как изменение <line class = "hover-date-line hide-line">, и такие значения, как изменение <path class = "link" d = "M 0 171.40106812500002 C 15 171.40106812500002 15 170.94093803735575 30 170.94093803735575"></path>, и я предполагаю, что именно эти значения создают линию суточного прогноза.
  • Но где хранятся эти значения и как их преобразовать в такие вещи, как «49,1% Клинтон против 26,6% Сандерса», все еще остается для меня загадкой.

Я прочитал несколько других сообщений SO, таких как это, но ни один из них не казался применимым к этой конкретной проблеме. Как лучше всего получить прогнозируемые проценты в аккуратном фрейме данных?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
0
2 034
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Там диаграмма почти наверняка построена с использованием d3.js или оболочки поверх него. d3 отлично подходит для создания визуализаций данных на основе svg, потому что он помогает вам создавать шкалы для сопоставления значений (например, 40%) с местами размещения на экране (например, то, что вы видите, что-то вроде cx=100). Проблема в том, что вам нужно знать, что это за шкалы, чтобы вернуть базовые данные, и шкалы, вероятно, будут динамическими и изменяются в зависимости от размера экрана и т. д.

Вместо этого, поскольку данные находятся в таблице ниже, вы можете легко очистить их. Таблица находится внутри элемента div с идентификатором latest-polls и имеет класс t-polls.

Я использую html_node с селекторами CSS, html_table для преобразования таблицы в фрейм данных, очистки имен и превращения числовых столбцов в фактические числовые столбцы. Далее вы можете сделать еще кое-что, например отформатировать даты, но, надеюсь, это поможет вам начать.

library(tidyverse)
library(rvest)

url <- "https://projects.fivethirtyeight.com/election-2016/national-primary-polls/democratic/"

polls_df <- url %>% 
  read_html() %>%
  html_node("#latest-polls table.t-polls") %>%
  html_table() %>%
  setNames(c("new", "date", "pollster", "sample_n", "sample_type", names(.)[6:10]) %>% str_remove_all("\\W")) %>%
  mutate_at(vars(sample_n, Clinton, Sanders, OMalley), 
      function(x) str_remove_all(x, "\\D") %>% as.numeric())

head(polls_df)
#>   new           date                     pollster sample_n sample_type
#> 1   •     Jun. 10-13                 Selzer & Co.      486          LV
#> 2   •     Jun. 26-28                     Fox News      432          RV
#> 3   •     Jun. 18-20                       YouGov      390          LV
#> 4   •     Jun. 15-20              Morning Consult     1733          RV
#> 5   • Jun. 27-Jul. 1                Ipsos, online      142          LV
#> 6   •     Jun. 16-19 Opinion Research Corporation      435          RV
#>   weight      leader Clinton Sanders OMalley
#> 1   1.05  Clinton +2      45      43      NA
#> 2   0.91 Clinton +21      58      37      NA
#> 3   0.79 Clinton +13      55      42      NA
#> 4   0.79 Clinton +18      53      35      NA
#> 5   0.67 Clinton +41      70      29      NA
#> 6   0.66 Clinton +12      55      43      NA

Понятно ... как жаль. Дело в том, что FiveThirtyEight использует свою собственную модель для оценки прогноза по этим точкам данных, что не является простым средним значением данных. Я уже соскребал базовые данные опросов, хотя ваше решение намного элегантнее.

Kim 17.05.2018 05:45
Ответ принят как подходящий

Другой способ - получить ресурс напрямую.

В браузере откройте Инструменты разработчика (F12 в Chrome / Chromium), перейдите в «Сеть», обновите (F5) и найдите то, что выглядит как красиво отформатированный JSON. Найдя его, мы копируем адрес ссылки (щелкните правой кнопкой мыши ресурс> Копировать адрес ссылки).

library(httr)
library(tidyr)
library(purrr)
library(dplyr)
library(ggplot2)

url <- "https://projects.fivethirtyeight.com/election-2016/national-primary-polls/USA.json"

r <- GET(url)

Там есть все данные. Веса тоже, так что вы, вероятно, можете пересчитать эти средние значения. Данные, представленные на графике, находятся в "model":

dat <- 
  jsonlite::fromJSON(content(r, as = "text")) %>% 
  map(purrr::pluck, "model") %>% 
  bind_rows(.id = "party") %>% 
  mutate_all(readr::parse_guess)

# # A tibble: 5,288 x 5
#    party candidate_name state forecastdate poll_avg
#    <chr> <chr>          <chr> <date>          <dbl>
#  1 D     Sanders        USA   2016-07-01       36.5
#  2 D     Clinton        USA   2016-07-01       55.4
#  3 D     Sanders        USA   2016-06-30       37.0
#  4 D     Clinton        USA   2016-06-30       54.6
#  5 D     Sanders        USA   2016-06-29       37.0
#  6 D     Clinton        USA   2016-06-29       54.9
#  7 D     Sanders        USA   2016-06-28       37.2
#  8 D     Clinton        USA   2016-06-28       54.4
#  9 D     Sanders        USA   2016-06-27       37.4
# 10 D     Clinton        USA   2016-06-27       53.9
# # ... with 5,278 more rows

Воспроизвести графики:

dat %>% 
  filter(candidate_name %in% c("Clinton", "Kasich", "Sanders", "Trump")) %>% 
  ggplot(aes(forecastdate, poll_avg)) +
  geom_line(aes(col = candidate_name)) +
  facet_wrap(~party)

Если вы хотите интерактивности:

library(dygraphs)
library(htmltools)

foo <- dat %>% 
  filter(candidate_name %in% c("Clinton", "Kasich", "Sanders", "Trump")) %>% 
  split(.$party) %>% 
  map(~ {
    select(.x, forecastdate, candidate_name, poll_avg) %>% 
      spread(candidate_name, poll_avg) %>% 
      {xts(.[-1], .[[1]])} %>%
      dygraph(group = "poll-model") %>% 
      dyRangeSelector()
  })

browsable(tagList(foo))

Отлично! Спасибо, это палочка-выручалочка. Только одна вещь, кажется, я получаю Error in bind_rows_(x, .id) : Argument 1 is a list, must contain atomic vectors в части bind_rows второго блока кода.

Kim 19.05.2018 23:45

@Kim Какой у тебя packageVersion("dplyr")? Пользуюсь 0.7.4.

Aurèle 19.05.2018 23:49

Хм, тот же 0.7.4. На самом деле это что-то с функцией map, потому что до третьей строки, до bind_rows, dat заканчивается как пустой список (NULL) --- этого не должно быть, верно? Версия purrr - 0.2.4. Я имею в виду, что могу обойти это, но мне просто интересно, почему я получаю другой результат.

Kim 19.05.2018 23:52

(та же версия). Что дает x <- jsonlite::fromJSON(content(r, as = "text")) ; names(x) ; names(x$D)? А с map(`[[`, "model")?

Aurèle 20.05.2018 00:01
names(x) дает [1] "D" "R", names(x$D) дает [1] "model" "polls" "distributions". Последний map( [[, "model") работал. Интересно, почему.
Kim 20.05.2018 00:12

Ха! В pluck есть rvest. Вы случайно не загрузили rvest после purrr? А как насчет map(purrr::pluck, "model")?

Aurèle 20.05.2018 00:14

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

Kim 20.05.2018 00:19

Другие вопросы по теме