【问题标题】:rvest web scraping with javascriptrvest 使用 javascript 进行网页抓取
【发布时间】:2018-10-27 03:36:14
【问题描述】:

我正在尝试使用rvestFiveThirtyEight 中获取每日预测,但我感兴趣的对象似乎是一个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]

\n
  • 以点表示的个人投票结果位于&lt;g style="clip-path: url("#line-clippoll_avg");"&gt; ... &lt;/g&gt;,您可以在其中看到 502 个位置的数字。我猜我必须将每个节点的cxcy 翻译成适当的百分比,由&lt;g class="flag-box" transform="translate(30, 161.44093322753096)"&gt;...&lt;/g&gt; 等完成。

  • 但是,我没有看到预测线的基础数据,而不是点。

  • 当我将光标悬停在图表上时,我看到 &lt;line class="hover-date-line hide-line"&gt; 等内容发生了变化,&lt;path class="link" d="M 0 171.40106812500002 C 15 171.40106812500002 15 170.94093803735575 30 170.94093803735575"&gt;&lt;/path&gt; 等值也发生了变化,我猜这些值是创建每日预测线的原因。李>
  • 但是这些值存储在哪里,以及如何将其转换回诸如“49.1% 克林顿与 26.6% 桑德斯”之类的内容对我来说仍然是一个谜。
  • 我确实阅读了其他一些 SO 帖子,例如 this,但似乎没有一个适用于这个特定问题。在简洁的数据框中获取预测百分比的最佳方法是什么?

    标签: javascript html css r rvest


    【解决方案1】:

    那里的图表几乎肯定是用 d3.js 或在它上面的包装器构建的。 d3 非常适合构建基于 svg 的数据可视化,因为它可以帮助您构建比例以将值(例如 40%)映射到屏幕上的位置(例如您所看到的,例如 cx=100)。问题是您需要知道这些比例是什么才能获取基础数据,并且比例可能是动态的并且会根据屏幕尺寸等变化。

    相反,由于数据在下表中,您可以轻松地抓取它。该表位于 ID 为 latest-pollsdiv 元素内,并有一个类 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 使用他们自己的模型从这些数据点估计预测,这不是数据的简单均值。尽管您的解决方案要优雅得多,但我已经抓取了基础民意调查数据。
    【解决方案2】:

    另一种方法是直接抓取资源。

    在您的浏览器中,打开开发者工具(Chrome/Chromium 中的 F12),前往“网络”,刷新 (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))
    

    【讨论】:

    • 太棒了!谢谢,那是救命稻草。只是一件事,我似乎在第二个代码块的bind_rows 部分得到了Error in bind_rows_(x, .id) : Argument 1 is a list, must contain atomic vectors
    • @Kim 你的packageVersion("dplyr") 是什么?我使用0.7.4
    • 嗯,同样的0.7.4。这实际上与 map 函数有关,因为直到第三行,在 bind_rows 之前,dat 最终成为一个空列表(NULL)——这不应该是,对吧? purrr 版本是 0.2.4。我的意思是我可以解决它,但我只是想知道为什么我会得到不同的结果。
    • 哈!在rvest 中有一个pluck。你是否碰巧在purrr 之后加载了rvestmap(purrr::pluck, "model")呢?
    • 啊,确实,这是罪魁祸首。无论如何,谢谢你的精彩回答。从现在开始,我将能够应用相同的原则。
    猜你喜欢
    • 2019-10-11
    • 2018-02-15
    • 2015-09-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多