使用R从不同网页刮取数据

时间:2017-09-27 11:38:49

标签: r csv web-scraping

我希望能够从网站上托管的众多表中获取数据。问题在于它们都在不同的网页上。

作为一个例子,here are links给英国的选区。如您所见,所有选区都在那里,每个选区都链接到一个单独的页面。如果你去一个单独的选区页面,你可以选择下载邮政编码的.csv文件,或者有一个html页面。

当各种数据源都在同一页面上时,我发现了很多关于如何执行此操作的解释,但是是否可以创建一个能够组合来自每个区域的邮政编码数据的数据文件?

例如,我使用以下代码获得了第一个区域Aberavon的数据,我在this question的答案中找到了一个版本。

library(XML)
library(RCurl)
install.packages("rlist")
library(rlist)

theurl <- getURL("https://www.doogal.co.uk/ElectoralConstituencies.php?constituency=W07000049",.opts = list(ssl.verifypeer = FALSE) )
tables <- readHTMLTable(theurl)
tables <- list.clean(tables, fun = is.null, recursive = FALSE)
n.rows <- unlist(lapply(tables, function(t) dim(t)[1]))

我通常使用R,所以很高兴知道如何使用R做,但是欣赏其他一些方法可能更适合并且很乐意尝试其他方法。我对数据抓取很陌生,所以如果这很明显我可能没有理解我读过的指令的局限性!

1 个答案:

答案 0 :(得分:0)

你需要卷起袖子并涂上一些肘部油脂。

在我扩展答案之前,我确实检查了网站的抓取策略,包括robots.txt文件。所述文件格式不正确:

User-agent: *
Disallow:

怀疑该网站的所有者意图在/之后有一个Disallow:,但没有说我们无法抓住。

我们需要的一些库:

library(rvest)
library(httr)
library(tidyverse)

我使用的库不同于你。如果你更喜欢坚持使用XMLRCurl套餐以及基础R,那么你需要调整这个等待另一个答案。

以下内容获取我们将为链接挖掘的初始页面数据:

res <- httr::GET("https://www.doogal.co.uk/ElectoralConstituencies.php")

pg <- httr::content(res, as="parsed")

如果您想要主页数据的CSV,请输入以下网址:

html_nodes(pg, "li > a[href*='CSV']") %>% 
  html_attr("href") %>% 
  sprintf("https://www.doogal.co.uk/%s", .) -> main_csv_url

现在我们需要链接到各个选区。我使用Chrome开发者工具检查了HTML页面内容,以找出CSS选择器:

constituency_nodes <- html_nodes(pg, "td > a[href*='constituency=']") 
constituency_names <- html_text(constituency_nodes)
constituency_ids <- gsub("^E.*=", "", html_attr(constituency_nodes, "href"))

请注意,我们只保存ID,而不是完整的网址。

我们会制作一个辅助功能来缩短时间。 httr::GET()让我们的行为像浏览器一样:

get_constituency <- function(id) {

  httr::GET(
    url = "https://www.doogal.co.uk/ElectoralConstituenciesCSV.php", 
    query = list(constituency = id)
  ) -> res

  httr::stop_for_status(res)

  res <- read.csv(text = httr::content(res), stringsAsFactors=FALSE)
  as_tibble(res)

}

然后,为所有选区调用我们的新功能。以下包括免费的进度条:

pb <- progress_estimated(3)
map_df(constituency_ids[1:3], ~{

  pb$tick()$print()

  Sys.sleep(5)

  get_constituency(.x)

}) -> postcodes_df

glimpse(postcodes_df)
## Observations: 10,860
## Variables: 12
## $ Postcode   <chr> "CF33 6PS", "CF33 6PT", "CF33 6PU", "CF33 6RA", "CF33 6R...
## $ In.Use.    <chr> "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "No", "Yes", "...
## $ Latitude   <dbl> 51.53863, 51.54013, 51.53815, 51.54479, 51.55091, 51.552...
## $ Longitude  <dbl> -3.700061, -3.699713, -3.690541, -3.684888, -3.673475, -...
## $ Easting    <int> 282191, 282219, 282850, 283259, 284066, 284886, 284613, ...
## $ Northing   <int> 183562, 183728, 183493, 184222, 184885, 185007, 183874, ...
## $ Grid.Ref   <chr> "SS821835", "SS822837", "SS828834", "SS832842", "SS84084...
## $ Introduced <chr> "7/1/1995 12:00:00 AM", "1/1/1980 12:00:00 AM", "1/1/198...
## $ Terminated <chr> "", "", "", "", "", "", "4/1/2002 12:00:00 AM", "", "1/1...
## $ Altitude   <int> 45, 47, 46, 76, 76, 131, 61, 27, 9, 7, 8, 8, 7, 8, 8, 8,...
## $ Population <int> 34, NA, 7, 33, 48, 11, NA, 10, NA, NA, NA, NA, NA, NA, N...
## $ Households <int> 12, NA, 3, 11, 19, 4, NA, 4, NA, NA, NA, NA, NA, NA, NA,...

注:

  • 我只做了3次迭代,因为我不需要这些数据。根据您的需要删除该限制。
  • 执行此操作时,请将延迟代码保留在那里。你有时间,这是他们的带宽&amp;你要滥用的CPU。
  • 上面的代码中有足够的数据可以将选区名称添加为数据框字段,但这是留给读者的工作