【问题标题】:Scrape object from html with rvest使用 rvest 从 html 中抓取对象
【发布时间】:2022-01-15 23:42:45
【问题描述】:

我是使用 r 进行网络抓取的新手,我正在尝试获取可能不是文本的每日更新对象。网址是 here 和我想提取页面末尾的每日情况表。这个对象的类是

class="aem-GridColumn aem-GridColumn--default--12 aem-GridColumn--offset--default--0"

我对 html 和 css 并没有真正的经验,所以如果您有任何有用的资源或关于如何从网页中提取对象的建议,我将不胜感激,因为 SelectorGadget 在这种情况下表示“未找到有效路径。 "

【问题讨论】:

    标签: html r web-scraping rvest


    【解决方案1】:

    我们可以使用vroom包转换从Daily situation update获取的文本

    library(rvest)
    library(vroom)
    
    url = 'https://covid19.public.lu/en.html'
    df = url %>%
      read_html() %>% 
      html_nodes('.cmp-gridStat__item-container') %>% 
      html_text2()
    
    vroom(df, delim = '\\n', col_names = F)
    
    # A tibble: 22 x 1
       X1                                     
       <chr>                                  
     1 369 People tested positive for COVID-19
     2 Per 100.000 inhabitants: 58,13         
     3 Unvaccinated: 91,20  
    

    编辑:

    html_elementhtml_elemnts

    html_elemnts (html_nodes) 的噘嘴是,

    [1] "369 People tested positive for COVID-19\n\nPer 100.000 inhabitants: 58,13\n\nUnvaccinated: 91,20\n\nVaccinated: 41,72\n\nRatio Unvaccinated / Vaccinated: 2,19\n\n "
    [2] "4 625 Number of PCR tests performed\n\nPer 100.000 inhabitants: 729\n\nPositivity rate in %: 7,98\n\nReproduction rate: 0,97"                                       
    [3] "80 Hospitalizations\n\nNormal care: 57\nIntensive care: 23\n\nNew deaths: 1\nTotal deaths: 890"                                                                     
    [4] "6 520 Vaccinations per day\n\nDose 1: 785\nDose 2: 468\nComplementary dose: 5 267"                                                                                  
    [5] "960 315 Total vaccines administered\n\nDose 1: 452 387\nDose 2: 395 044\nComplementary dose: 112 884" 
    

    html_element (html_node)`的那个是

    [1] "369 People tested positive for COVID-19\n\nPer 100.000 inhabitants: 58,13\n\nUnvaccinated: 91,20\n\nVaccinated: 41,72\n\nRatio Unvaccinated / Vaccinated: 2,19\n\n "
    

    如您所见,html_nodes 返回与节点关联的所有值,而html_node 仅返回第一个节点。因此,前者会为您获取所有真正有用的节点。

    html_texthtml_text2

    html_text2 保留字符串中的中断,通常是 \n\b。这些在处理字符串时很有帮助。

    更多信息在rvest 文档中, https://cran.r-project.org/web/packages/rvest/rvest.pdf

    【讨论】:

    • 非常感谢您,我想我会选择您的解决方案!你能告诉我 html_nodes 和 html_node 函数有什么区别吗? html_text2 和 html_text 有什么区别?
    • 检查修改后的答案。
    【解决方案2】:

    我想知道您是否可以从他们的任何公共 API 中获取相同的数据。如果您只是想要一个包含该表的 pdf(以及许多其他有用信息表),您可以使用 API 进行提取。

    如果您想要作为 DataFrame(类似于网页),您可以编写一个用户定义的函数,在 pdftools 的帮助下,从 pdf 中重建表格。付出更多的努力,但由于您已经使用rvest 获得了其他答案,我想我会看看这个。我查看了tabularize,但这并不是特别有效。

    很有可能,您可以将几个 API 数据集放在一起以获得完整内容,而无需解析我使用的 pdf 出版物,例如有一个 Excel 电子表格提供案例编号。

    注意网页中有一些底部计算未包含在下面。我只处理了pdf中的测试信息表。


    报道记者:

    https://data.public.lu/en/datasets/covid-19-rapports-journaliers/#_ https://download.data.public.lu/resources/covid-19-rapports-journaliers/20211210-165252/coronavirus-rapport-journalier-10122021.pdf

    API 数据集:

    https://data.public.lu/api/1/datasets/#


    library(tidyverse)
    library(jsonlite)
    ## https://data.library.virginia.edu/reading-pdf-files-into-r-for-text-mining/
    # install.packages("pdftools")
    library(pdftools)
    
    r <- jsonlite::read_json("https://data.public.lu/api/1/datasets/#")
    report_index <- match(TRUE, map(r$data, function(x) x$slug == "covid-19-rapports-journaliers"))
    latest_daily_covid_pdf <- r$data[[report_index]]$resources[[1]]$latest # coronavirus-rapport-journalier
    
    filename <- "covd_daily.pdf"
    
    download.file(latest_daily_covid_pdf, filename, mode = "wb")
    
    get_latest_daily_df <- function(filename) {
      
      data <- pdf_text(filename)
    
      text <- data[[1]] %>% strsplit(split = "\n{2,}")
    
      web_data <- text[[1]][3:12]
    
      df <- map(web_data, function(x) strsplit(x, split = "\\s{2,}")) %>%
        unlist() %>%
        matrix(nrow = 10, ncol = 5, byrow = T) %>%
        as_tibble()
    
      colnames(df) <- text[[1]][2] %>%
        strsplit(split = "\\s{2,}") %>%
        map(function(x) gsub("(.*[a-z])\\d+", "\\1", x)) %>%
        unlist()
    
      title <- text[[1]][1] %>%
        strsplit(split = "\n") %>%
        unlist() %>%
        tail(1) %>%
        gsub("\\s+", " ", .) %>%
        gsub(" TOTAL", "", .)
    
      colnames(df)[2:3] <- colnames(df)[2:3] %>% paste(title, ., sep = " ")
      colnames(df)[4:5] <- colnames(df)[4:5] %>% paste("TOTAL", ., sep = " ")
      colnames(df)[1] <- "Metric"
    
      clean_col <- function(x) {
        gsub("\\s+|,", "", x) %>% as.numeric()
      }
    
      clean_col2 <- function(x) {
        gsub("\n", " ", gsub("([a-z])(\\d+)", "\\1", x))
      }
    
      df <- df %>% mutate(across(.cols = -c(colnames(df)[1]), clean_col),
        Metric = clean_col2(Metric)
      )
    
      return(df)
    }
    
    
    View(get_latest_daily_df(filename))
    

    输出:


    替代:

    如果您只是想提取项目然后进行处理,您可以将每一列提取为列表中的一个项目。替换 br 元素,使其中的内容以逗号分隔的列表结尾:

    library(rvest)
    library(magrittr)
    library(stringi)
    library(xml2)
    
    page <- read_html("https://covid19.public.lu/en.html")
    xml_find_all(page, ".//br") %>% xml_add_sibling("span", ",") #This method from https://stackoverflow.com/a/46755666 @hrbrmstr
    xml_find_all(page, ".//br") %>% xml_remove()
    
    columns <- page %>% html_elements(".cmp-gridStat__item")
    
    map(columns, ~ .x %>%
      html_elements("p") %>%
      html_text(trim = T) %>%
      gsub("\n\\s{2,}", " ", .)
      %>%
      stri_remove_empty())
    

    【讨论】:

    • 这太棒了!如果您有时间详细说明在这两个脚本中使用 map 函数,我将不胜感激。谢谢你的回答!
    • map 只是将用户定义的函数应用于列表。在这种情况下,列表是 5 个 "columns"
    【解决方案3】:

    可能有一种更优雅的方法可以有效地做到这一点,但是当我需要这样的蛮力时,我会尝试将其分解成小部分。

    1. 使用 httr 库获取原始 html。
    2. 使用 stringr 库中的 str_extract 从 html 中提取特定的数据。
    3. 我同时使用positive lookbehind and lookahead 正则表达式来获取我需要的确切数据。它基本上采用“?的形式
    library(httr)
    library(stringr)
    
    r <- GET("https://covid19.public.lu/en.html")
    html<-content(r, "text")
    
    normal_care=str_extract(html, regex("(?<=Normal care: ).+?(?=<br>)"))
    intensive_care=str_extract(html, regex("(?<=Intensive care: ).+?(?=</p>)"))
    

    【讨论】:

    • 感谢您的回答,但您能否详细说明一下正则表达式。我如何从这个

      普通护理:57
      重症护理:23

      中确定我需要表达什么?\n

    【解决方案4】:

    在不涉足编写网络爬虫业务的情况下,我认为这应该对您有所帮助:

    library(rvest)
    url = 'https://covid19.public.lu/en.html'
    source = read_html(url)
    selection = html_nodes( source , '.cmp-gridStat__item-container' ) %>% html_node( '.number' ) %>% html_text() %>% toString()
    

    【讨论】:

    • 哇,好酷
    • 谢谢!我认为您的回答作为资源也很有帮助!
    猜你喜欢
    • 2018-03-13
    • 1970-01-01
    • 1970-01-01
    • 2020-04-19
    • 1970-01-01
    • 2018-04-19
    • 2023-03-21
    • 2018-03-20
    • 1970-01-01
    相关资源
    最近更新 更多