【问题标题】:Creating a dataframe from paragraph text scraped from website in R从 R 中的网站抓取的段落文本创建数据框
【发布时间】:2019-08-22 19:42:56
【问题描述】:

我正在尝试抓取一个网站,其中包含我想要的大量不同信息的段落。我让它完美地工作......但是,我不明白如何分解文本并创建一个数据框。

网址:Website I want Scraped

代码:

library(rvest)
url <- "https://www.state.nj.us/treasury/administration/statewide-support/motor-fuel-locations.shtml"

#Reading the HTML code from the website
webpage <- read_html(url)


p_nodes<-webpage%>%
  html_nodes(xpath = '//p')%>%
  html_text()

#replace multiple whitespaces with single space
p_nodes<- gsub('\\s+',' ',p_nodes)
#trim spaces from ends of elements
p_nodes <- trimws(p_nodes)
#drop blank elements
p_nodes <- p_nodes[p_nodes != '']

我希望数据框的外观如何:

我不确定这是否可能。我尝试分别提取每条信息,然后像这样制作数据框,但由于大部分信息都存储在 p 标签中,因此它不起作用。我将不胜感激任何指导。谢谢!

【问题讨论】:

  • 由于每一行确实不同,您将需要一些解析脚本来考虑许多不同的情况和例外情况。如果只需要运行一次,手动剪切会更快(从每一行创建一个字符串向量,然后使用rbind
  • 我建议在自定义函数中使用 html 标签和 regex,例如您可以使用tr~ tr+ tr p , td+ p 获取所有这些个人联系人,然后使用regex 提取您需要的信息。

标签: r web-scraping tidyverse rvest


【解决方案1】:

概念证明(基于我在评论中写的):

代码

lapply(c('data.table', 'httr', 'rvest'), library, character.only = T)

tags <- 'tr:nth-child(6) td , tr~ tr+ tr p , td+ p'
burl <- 'https://www.state.nj.us/treasury/administration/statewide-support/motor-fuel-locations.shtml'

url_text <- read_html(burl)

chunks <- url_text %>% html_nodes(tags) %>% html_text()

coordFunc <- function(chunk){
  patter_lat <- 'Longitude:.*(-[[:digit:]]{1,2}.[[:digit:]]{0,15})'
  ret <- regmatches(x = chunk, m = regexec(pattern = patter_lat, text = chunk))
  return(ret[[1]][2])
}

longitudes <- as.numeric(unlist(lapply(chunks, coordFunc)))

输出

# using 'cat' to make the output easier to read 
> cat(chunks[14])
Mt.    Laurel DOT
                  Rt. 38, East
                  1/4 mile East of Rt. 295
                  Mt. Laurel Open 24 Hrs
                  Unleaded / Diesel
                  856-235-3096Latitude:  39.96744662Longitude: -74.88930386 


> longitudes[14]
[1] -74.8893

如果你不强制 longitudes 成为 numeric,你会得到:

longitudes <- (unlist(lapply(chunks, coordFunc)))
> longitudes[14]
[1] "-74.88930386"

我选择经度作为概念验证,但您可以修改函数以在一次调用中提取所有相关位。为了获得正确的tag,您可以使用 SelectorGadget 扩展(对我来说在 Chrome 中运行良好)。大多数浏览器都允许您“检查元素”以获取html 标签。该函数可以在data.table 中返回提取的值,然后可以使用rbindlist 将其合并为一个。

您甚至可以通过编程方式推进页面以抓取整个网站 - 请务必查看使用政策(通常不赞成或仅限于抓取网站)。

编辑

整个网页的文本结构不同,因此您需要花更多时间检查可能发生的异常情况。

这是一个新函数,可以将每个块解析为单独的行,然后您可以尝试使用其他正则表达式来获得您想要的内容。

newfunc <- function(chunk){
  # Each chunk is a couple of lines. First, we split at '\r\n' using strsplit
  # the output is a list so we use 'unlist' to get a vector 
  # then use 'trimws' to remove whitespace around it - try out each of these functions
  # separately to understand what is going on. The final output here is a vector. 
  txt <- trimws(unlist(strsplit(chunk, '\r\n'))) 
  return(txt)
}

这会将每个块中包含的“文本”作为单独行的向量返回。看一下前20个chunk的行数,不一样的:

> unlist(lapply(chunks[1:20], function(z) length(newfunc(z))))
 [1] 5 6 5 7 5 5 5 5 5 4 1 6 6 6 5 1 1 1 5 6

解决这个问题的一个好方法是根据每个块中的文本行数放入一个条件语句,例如在newfunc 你可以添加:

if(length(txt) == 1){
return(NULL)
}

这是因为这是针对其中没有任何文本的条​​目。因为这是一个概念证明,所以我没有检查所有条目,但有一些简单的逻辑:

  1. 第一行通常是名称
  2. 坐标在最后一行
  3. 燃料可以是unleadeddiesel。您可以在这两个字符串上grep 以查看每个仓库提供的内容。例如grepl('diesel', newfunc(chunks[12]))
  4. 另一种方法是使用一组不同的 html 标签,例如所有坐标和营业时间均以粗体显示,并带有标签strong。您可以分别提取它们,然后使用正则表达式来获得您想要的。
  5. 您可以搜索24(Hrs|Hours) 以首先提取所有24 小时开放的站点,然后对其余站点使用选择性regex 以获取其运行时间。

大多数网络抓取没有简单的简单答案,您必须找到模式,然后在此基础上应用一些逻辑。只有在最结构化的网站上,您才能找到适用于整个页面/范围的内容。

【讨论】:

  • 感谢您的回答。有用。我只是不明白如何获取所有其他部分并制作一个不错的数据框。我不明白你是怎么得到这部分的:patter_lat
  • @NBE 您所指的部分是正则表达式,例如[[:digit:]] 告诉 R 我们期待一个数字。下面是正则表达式的一个很好的初学者参考:stat545.com/block022_regular-expression.htmlregmatchesregexec是稍微高级的函数。
  • 谢谢。我会试一试!所以一旦我弄清楚其他部分的正则表达式,我可以将它们组合起来并制作一个数据框?
  • @nbe 是的,我在回答中添加了更多细节。希望这会有所帮助,但您需要自己付出一些努力:) 如果答案有帮助,请投票或考虑接受。
【解决方案2】:

你可以使用 tidyverse 包(stringr、tibble、purrr)

library(rvest)
library(tidyverse)
url <- "https://www.state.nj.us/treasury/administration/statewide-support/motor-fuel-locations.shtml"
#Reading the HTML code from the website
webpage <- read_html(url)
p_nodes<-webpage%>%
  html_nodes(xpath = '//p')%>%
  html_text()
# Split on new line
l = p_nodes %>% stringr::str_split(pattern = "\r\n")
var1 = sapply(l, `[`, 1) # replace var by the name you want
var2 = sapply(l, `[`, 2)
var3 = sapply(l, `[`, 3)
var4 = sapply(l, `[`, 4)
var5 = sapply(l, `[`, 5)
t = tibble(var1,var2,var3,var4,var5) # make tibble
t = t %>% filter(!is.na(var2)) # delete useless lines
purrr::map_dfr(t,trimws) # delete blanks

【讨论】:

  • 感谢您的回答。这正是我一直在寻找的......但是,var 3 到 var 5 搞砸了。我该如何解决这个问题?
  • 它有一堆信息聚集在一起,而不是像上图那样在单独的列中
猜你喜欢
  • 2019-08-25
  • 1970-01-01
  • 2019-08-17
  • 1970-01-01
  • 2021-04-05
  • 2016-01-17
  • 2018-12-19
  • 1970-01-01
  • 2014-07-06
相关资源
最近更新 更多