R网络数据爬虫

2018-09-03

2018-09-03
R网络数据爬虫

1 一个具体案例


  今天我们来具体地看一个网络抓取的案例。联合国教科文组织是联合国的一个机构,职责包括对全世界自然和文化遗产的保护。迄今为止已有一千多个地点被列为世界遗产,其中大部分是像胡夫金字塔这样的人造建筑,但也包括像大堡礁这样的自然景观。遗憾的是,一些被划定为世界遗产的地点正在遭受人类活动的威胁。有哪些地点正在遭受威胁?它们分别在哪些地方?世界上是不是有一些地区的遗产比其他地区的遗产处于更加濒危的状态?遗产地面临濒危风险的原因有哪些?这些都是我们会在本案例中想要检验的问题。

  当科学家要掌握某个主题的基本情况时,他们一般首先会去维基百科查阅这个主题,打开世界遗产地的页面(http://en.wikipedia.org/wiki/List_of_World_Heritage_in_Danger) 我们会看到一份清单,其中列出了现在和以前濒危的遗产地。这份列表包含了名称、位置、遗产地面临威胁的种类、该地点被列入世界遗产的年份以及该地点被列入濒危师姐遗产的年份。虽然这张列表包含了有关遗产地的信息,但它们所在的位置或区域性聚集的情况并不是特别直观。相比用肉眼扫描列表,更有效的方法是在地图上标出每个遗产地的位置。

  为了把页面数据加载到R中,可以通过rvest包中的html_table()函数实现。

# install.packages("stringr")#安装软件包
# install.packages("XML")
# install.packages("maps")
#install.packages("rvest")
#install.packages("DT")
library(stringr)#加载软件包
## Warning: package 'stringr' was built under R version 3.4.4
library(XML)
## Warning: package 'XML' was built under R version 3.4.4
library(maps)
## Warning: package 'maps' was built under R version 3.4.4
library(rvest)
## Warning: package 'rvest' was built under R version 3.4.4
## Loading required package: xml2
## Warning: package 'xml2' was built under R version 3.4.4
## 
## Attaching package: 'rvest'
## The following object is masked from 'package:XML':
## 
##     xml
library(DT)
## Warning: package 'DT' was built under R version 3.4.4
heritage_parsed <- read_html("http://en.wikipedia.org/wiki/List_of_World_Heritage_in_Danger", encoding = "UTF-8")
tables <- html_table(heritage_parsed, fill = TRUE)#从heritage_parsed中取出所有表格

  这一步是告诉R,它要导入的数据是以HTML网页的形式出现的。R有能力解析HTML,也就是说,它知道在这种文件格式下的表格、标题或者其他元素的构成方式。这是通过htmlParse()函数调用的解析器起作用的。在下一步,我们要让R从解析出来的heritage_parsed对象中提取所有能找到的HTML表格,并保存到新的tables对象中。

  现在我们需要的所有信息都在tables对象里了。这个对象是html_table()函数在HTML网页里找到的所有表格的一份列表。在看过所有的表格后,我们甄选了感兴趣的那个表格并把它存到一个命名为danger_table的新表格里。该表格里的一些变量并不是我们所感兴趣的,所以我们筛选出的信息只包含了遗产地的名称、位置、遗产标准、列入遗产的年份以及列入濒危遗产的年份。

danger_table<-tables[[2]]
names(danger_table)#查看表格的列名
## [1] "Name"          "Image"         "Location"      "Criteria"     
## [5] "Areaha (acre)" "Year (WHS)"    "Endangered"    "Reason"       
## [9] "Refs"
#取出表格中的1, 3, 4, 6, 7列,其含义分别是名字、位置、标准分类、申遗成功时间、何时定为处于危险的时间
danger_table <- danger_table[, c(1, 3, 4, 6, 7)]
#为了方便,对列重命名
colnames(danger_table) <- c("name", "locn", "crit", "yins", "yend")
datatable(danger_table)
danger_table$name[1:3]#取几个遗产的名字查看
## [1] "Abu Mena"                        "Air and Ténéré Natural Reserves"
## [3] "Ancient City of Aleppo"

  我们需要进行一些简单的数据清理,这个步骤通常是把基于网络的内容导入R时所必须的。

#数据的初步整理和清洗
#对crit列按照自然和人文遗产重新编码
#Natural景观遗产编码为nat,否则为culture,即cul
danger_table$crit <- ifelse(
  str_detect(danger_table$crit, "Natural") ==TRUE, 
  "nat", 
  "cult"
  )
# 将申遗成功时间转为数值类型
danger_table$yins <- as.numeric(danger_table$yins)
danger_table$yins[1:3]
## [1] 1979 1991 1986

  从表格中可以看出濒危时间形式比较混乱,有横杠,有些一格中有好几个时间。那么为了准确地提取时间信息,我们必须采用某种更先进的文本处理工具,称为“正则表达式”,正则表达式的应用能够帮助我们对R准确描述我们感兴趣的信息看上去是什么样子的,然后让R据此去搜索和提取这些信息。

#按照正则表达式取出(我们只取出濒危时间,去掉横杠,并转化为数值型)
yend_clean <- unlist(
  str_extract_all(danger_table$yend, "[[:digit:]]4$")
  )
danger_table$yend[1:3]
## [1] "2001–" "1992–" "2013–"

  实际上,上述正则表达式并不OK,得到的是空字符串,因此我们需要手动处理濒危时间数据。

yend_clean1 <- unlist(str_extract(danger_table$yend, "\\d{4}–"))#这里的符号–要特别注意,我是运行danger_table$yend,将结果中的-复制粘贴过来的。
yend_clean <- unlist(str_extract_all(yend_clean1, "\\d{4}"))
str_extract_all是stringr包中的函数,看个例子,自己体会一下用法
str_extract_all函数的返回值是一个list,所以要unlist
shopping_list <- c("apples x4", "bag of flour", "bag of sugar", "milk x2")
str_extract(shopping_list, "\\d")
# [1] "4" NA  NA  "2"
str_extract(shopping_list, "[a-z]+")
# [1] "apples" "bag"    "bag"    "milk"  
str_extract_all(shopping_list, "[a-z]+")
# [[1]]
# [1] "apples" "x"     
# 
# [[2]]
# [1] "bag"   "of"    "flour"
# 
# [[3]]
# [1] "bag"   "of"    "sugar"
# 
# [[4]]
# [1] "milk" "x"   
length(yend_clean)
## [1] 54
length(danger_table$yend)
## [1] 54
danger_table$yend <- as.numeric(yend_clean)
danger_table$yend[1:3]
## [1] 2001 1992 2013

  locn表示位置,信息比较杂乱,我们在数据集里找三条数据作为例子,依然用正则表达式处理。

danger_table$locn[c(1, 3, 5)]
## [1] "EgyAbusir,<U+00A0>Egypt30°50′30″N 29°39′50″E<U+FEFF> / <U+FEFF>30.84167°N 29.66389°E<U+FEFF> / 30.84167; 29.66389<U+FEFF> (Abu Mena)"
## [2] "Aleppo Governorate, <U+00A0>Syria36°14′N 37°10′E<U+FEFF> / <U+FEFF>36.233°N 37.167°E<U+FEFF> / 36.233; 37.167<U+FEFF> (Ancient City of Aleppo)"
## [3] "Damascus Governorate, <U+00A0>Syria33°30′41″N 36°18′23″E<U+FEFF> / <U+FEFF>33.51139°N 36.30639°E<U+FEFF> / 33.51139; 36.30639<U+FEFF> (Ancient City of Damascus)"
reg_y <- "[/][ -]*[[:digit:]]*[.]*[[:digit:]]*[;]"
reg_x <- "[;][ -]*[[:digit:]]*[.]*[[:digit:]]*"
#下面再去掉无关的字符
y_coords <- str_extract(danger_table$locn, reg_y)
y_coords
##  [1] "/ 30.84167;"  "/ 18.283;"    "/ 36.233;"    "/ 32.51806;" 
##  [5] "/ 33.51139;"  "/ 36.33417;"  "/ 32.82500;"  "/ 32.63833;" 
##  [9] "/ 32.80528;"  "/ 35.45667;"  "/ -8.11111;"  "/ 31.70444;" 
## [13] "/ -19.58361;" "/ 11.417;"    "/ 34.78167;"  "/ 34.83194;" 
## [17] "/ -11.68306;" "/ 25.317;"    "/ 9.55389;"   "/ 4.000;"    
## [21] "/ 35.58806;"  "/ 31.52417;"  "/ 39.05000;"  "/ 48.200;"   
## [25] "/ 14.200;"    "/ -20.20833;" "/ -2.500;"    "/ 3.05222;"  
## [29] "/ 53.40667;"  "/ 9.000;"     "/ 34.39667;"  "/ 42.66111;" 
## [33] "/ 7.600;"     "/ 6.83972;"   "/ 13.000;"    "/ 2.000;"    
## [37] "/ 31.77667;"  "/ 15.35556;"  "/ 30.13333;"  "/ 13.90639;" 
## [41] "/ 31.71972;"  "/ 15.92694;"  "/ -14.467;"   "/ 15.74444;" 
## [45] "/ 24.833;"    "/ -2;"        "/ 34.200;"    "/ -9.00000;" 
## [49] "/ 34.55417;"  "/ 16.77333;"  "/ 16.28944;"  "/ 0.32917;"  
## [53] "/ -2.500;"    "/ 0.917;"
#负纬度表示位于南半球(S)的位置而负经度表示西半球(W)的位置.
#截取和加入到表中
y_coords <- as.numeric(str_sub(y_coords, 3, -2))
danger_table$y_coords <- y_coords
#其中str_sub(y_coords, 3, -2)的意思是,从第三个字符开始截取,到倒数第二个结束(保留必要的精度为2)
x_coords <- str_extract(danger_table$locn, reg_x)
x_coords <- as.numeric(str_sub(x_coords, 3, -1))
danger_table$x_coords <- x_coords
#删除danger_table中的locn列
danger_table$locn <- NULL
#为查看方便,取小数点后两位
round(danger_table$y_coords, 2)[1:3]
## [1] 30.84 18.28 36.23
round(danger_table$x_coords, 2)[1:3]
## [1] 29.66  8.00 37.17
#查看维度
dim(danger_table)
## [1] 54  6
head(danger_table)
##                                 name crit yins yend y_coords x_coords
## 1                           Abu Mena cult 1979 2001 30.84167 29.66389
## 2    Air and Ténéré Natural Reserves  nat 1991 1992 18.28300  8.00000
## 3             Ancient City of Aleppo cult 1986 2013 36.23300 37.16700
## 4              Ancient City of Bosra cult 1980 2013 32.51806 36.48167
## 5           Ancient City of Damascus cult 1979 2013 33.51139 36.30639
## 6 Ancient Villages of Northern Syria cult 2011 2013 36.33417 36.84417

  进行制图,看看这些不同的遗产,对人文和自然景观设置不同的点的形状

pch <- ifelse(danger_table$crit == "nat", 10, 11)
#对分属人文和自然景观的遗产设置不同的颜色参数
cols <- ifelse(danger_table$crit == "nat", 'deepskyblue4', 'brown1')
map("world", col = "maroon", 
    lwd = 0.5, 
    mar = c(0.1, 0.1, 0.1, 0.1),
    bg='seashell')
points(danger_table$x_coords, 
       danger_table$y_coords, 
       pch = pch,
       col=cols,
       cex=1.2,lwd=1.3
       )
#添加图例
#leg.txt中设置的是图例文本
leg.txt <- c("Natural", "Cultural")
#当取x=0,y=0的时候是位于图正中央
#text.col设置图例文字颜色
#cex设置图例区域大小
legend("topright", leg.txt, horiz = TRUE, 
       pch=c(10, 11),
       col = c('deepskyblue4', 'brown1'),
       text.col=c('deepskyblue4', 'brown1')
       )
box()#设置边框

table(danger_table$crit)
## 
## cult  nat 
##   38   16

  可以看出,很多濒危遗产地位于非洲、中东和西南亚,还有一些位于中南美洲。濒危文化遗产相对集中在中东和西南亚,濒危自然遗产在非洲更突出一点。还可以看出,濒危的文化遗产比自然遗产更多,可能是传承出现了问题,自然的变化是相对缓慢的。

  我们可以分析相关国家的政治、经济或环境状况导致这些遗产地濒危的可能性。虽然表格中的信息也许太过稀疏,不足以进行严谨地推理,但是我们起码可以分析某些时间趋势和教科文组织推行遗产地的潜在动机。制作某地点被列入世界遗产的年份和它被列入濒危世界遗产的年份的频率表。

#频数表
hist(danger_table$yend,
     freq = TRUE,
     xlab = "Year when site was put on the list of endangered sites",
     main = "")

  我们发现在最近几十年,濒危目录中的遗产地的增长频率加快了,但同时世界遗产地的总数也在增加。我们更关注的是在一个地点被列入世界遗产地之后被列入濒危清单所经过的时间,把濒危年份减去列入遗产年份,计算出这个值。

duration<-danger_table$yend-danger_table$yins
hist(duration,
     freq = TRUE,
     xlab = "Years it took to become an endangered site",
     main = "")

  其中很多地点在被认定为世界遗产之后很快地就进入了濒危清单。根据文化遗产或自然遗产的官方入选标准,濒危状态并非必要条件,相反濒危遗产地有可能会失去它们作为世界遗产的地位。那么问题是为什么他们在会很快失去世界遗产地位的风险下还能进入世界遗产名录呢?有人会猜测世界遗产委员会可能非常了解这些情况,也可能利用世界遗产名录作为对这些地点加强保护的一种政治手段。

2 参考文献


[1]Simon Munzert. 基于R语言的自动数据收集[M]. 机械工业出版社, 2016.

[2]https://www.cnblogs.com/xuanlvshu/p/6218224.html