这里我们介绍网页链接的提取以及简单的文本分析。
网页链接提取
前文已经提到现在很多网页都是js渲染过的,我们得用rdom才可以快速爬取。
我们要爬的网站是http://jnqx.jinan.gov.cn/col/col14936/index.html,大体是这个样子的:
我们要的是人影简报(用于反馈人工增雨情况)的链接。
导入库
一般总会用到这么几个库
> library(rdom)
> library(XML)
> library(tidyverse)
> library(rvest)
探索
先尝试一个网页
url <- 'http://jnqx.jinan.gov.cn/col/col14936/index.html?uid=25751&pageNum=1'
x <- rdom(url) %>% getNodeSet(path = "//a[@target = '_blank' and @ class = 'bt_linkb']")
详细介绍一下第二行:
大家知道rdom库就只有一个函数,我们把网页读进来之后再把它传给readHTMLTable(header = T)去分析(参考上一篇)。但是我们发现这个策略对于提取网页表格文字、数字内容非常好用,但是它不能返回超链接。这就麻烦了。
那怎么办呢?去guthub寻找源代码。https://github.com/cpsievert/rdom/blob/master/R/rdom.R
#' Return DOM of a website as HTML
#'
#' @param url A url or a local path.
#' @param css a string containing one or more CSS selectors separated by commas.
#' @param all logical. This controls whether \code{querySelector} or
#' \code{querySelectorAll} is used to extract elements from the page.
#' When FALSE, output is similar to \code{rvest::html_node}. When TRUE,
#' output is similar to \code{rvest::html_nodes}.
#' If \code{css} is missing, then this argument is ignored.
#' @param timeout maximum time to wait for page to load and render, in seconds.
#' @param filename A character string specifying a filename to store result
#' @export
#' @importFrom XML htmlParse
#' @importFrom XML xmlChildren
#' @importFrom XML getNodeSet
#'
#' @examples \dontrun{
#' library("rvest")
#' stars <- "http://www.techstars.com/companies/stats/"
#' # doesn't work
#' html(stars) %>% html_node(".table75") %>% html_table()
#' # should work
#' rdom(stars) %>% html_node(".table75") %>% html_table()
#' # more efficient
#' stars %>% rdom(".table75") %>% html_table()
#' }
#'
rdom <- function(url, css, all, timeout, filename) {
if (missing(url)) stop('Please specify a url.')
args <- list(
system.file('rdomjs/rdom.js', package = 'rdom'),
url,
# NA is a nice default since jsonlite::toJSON(NA) == null
css %||% NA,
all %||% FALSE,
timeout %||% 5,
filename %pe% NA
)
args <- lapply(args, jsonlite::toJSON, auto_unbox = TRUE)
phantom_bin <- find_phantom()
res <- if (missing(filename)) {
# capture output as a character vector
system2(phantom_bin, args = as.character(args),
stdout = TRUE, stderr = TRUE, wait = TRUE)
} else {
# ignore stdout/stderr and write to file
system2(phantom_bin, args = as.character(args),
stdout = FALSE, stderr = FALSE, wait = TRUE)
}
st <- attr(res, 'status')
if (!is.null(st)) stop(paste(res, '\n'))
p <- if (missing(filename)) {
XML::htmlParse(res, asText = TRUE)
} else {
XML::htmlParse(filename)
}
# If the result is a node or node list, htmlParse() inserts them into
# the body of a bare-bones HTML page.
if (!missing(css)) {
nodes <- XML::xmlChildren(XML::getNodeSet(p, '//body')[[1]])
if (length(nodes) == 1) nodes[[1]] else nodes
} else {
p
}
}
通过源代码,我们知道它还可以传递给XML库的getNodeSet函数。事实上,rdom返回的是"XMLInternalElementNode" "XMLInternalNode" "XMLAbstractNode",只要能接受这三种数据类型之一的函数就可以用。当然很多是不接受的。
我们研究getNodeSet怎么用,
nodeset <- getNodeSet(doc = , path = )
其中doc就是rdom的返回值,path比较麻烦,它是用xpath语法写的。可以参见XPath 语法,很简单。类似于正则表达式。
具体来看这个例子,
path = "//a[@target = '_blank' and @ class = 'bt_linkb']"
就是说要标签里面的而且参数target = '_blank' 、class = 'bt_linkb的。
你当然可以用正则表达式弄,不过麻烦点罢了。
看一下我们的成果:
> x[[1]]
2010人影简报第五期(总第95期)
基本快搞定了,最后来一波正则表达式把链接弄出来。然而出现了一个问题,当我们尝试把x[[1]]转化为字符串时,报错了:
> as.character(x[[1]])
Error in as.vector(x, "character") :
cannot coerce type 'externalptr' to vector of type 'character'
几个意思呢?说x[[1]]是一个外部指针,无法强制转化为字符串。
经过google上的一番折腾,应该这样处理指针:
capture.output(x[[1]])
这样就捕获了这个指针指向的内容。应该说,R语言中很少见这种事。
正则表达式就比较好办了,有一个套路:
m <- regexpr('/art/[0-9]*/[0-9]*/[0-9]*/art_[0-9]*_[0-9]*.html', c[j])
c[j] <- substr(c[j], m, attr(m, "match.length") - 1 + m)
全部程序如下:
s <- 0
L <- list()
url_2 <- 'http://jnqx.jinan.gov.cn'
for (k in 1:8) {
url <- 'http://jnqx.jinan.gov.cn/col/col14936/index.html?uid=25751&pageNum='
url <- paste(url, k, sep = '')
x <- rdom(url) %>% getNodeSet(path = "//a[@target = '_blank' and @ class = 'bt_linkb']")
c <- character(length(x))
for (j in 1:length(x)) {
c[j] <- capture.output(x[[j]])
m <- regexpr('/art/[0-9]*/[0-9]*/[0-9]*/art_[0-9]*_[0-9]*.html', c[j])
c[j] <- substr(c[j], m, attr(m, "match.length") - 1 + m)
}
c <- paste(url_2, c, sep = '')
for (i in 1:length(c)) {
webpage <- read_html(c[i], encoding="utf-8")
data_html <- html_nodes(webpage, '#zoom')
L[[s + i]] <- html_text(data_html)
}
s <- s + length(c)
}
事实上写爬虫也是个苦差事…
文本分析
源码在这里:
t <- character(length(L))
for (i in 1:length(L)) {
m <- regexpr('[0-9]*月[0-9]*日', L[[i]])
t[i] <- substr(L[[i]], m, attr(m, "match.length") - 1 + m)
}
t[20] <- '6月22日'
t[88] <- '5月17日'
t[89] <- '5月7日'
t[100] <- '11月3日'
t[108] <- '3月21日'
t[111] <- '11月17日'
t[114] <- '10月26日'
L[[130]] <- NULL
t <- t[-130]
r <- character(length(L))
for (i in 1:length(L)) {
m <- regexpr(''[0-9]+?\\.[0-9]毫米'', L[[i]])
r[i] <- substr(L[[i]], m, attr(m, "match.length") - 1 + m)
}
data <- data.frame(Time = t, result = r)
其实主要就是正则表达式,+?表示非贪婪匹配,也就是匹配到一开始那个就成功了。注意R语言字符串转义要用\\而不是\。
到此,下面这些文本就分析好了。剩下的该回归回归,该机器学习就学习。
总结
- 正则表达式要掌握好
- 有问题github上看源码
- StackExhcange要用好
- 掌握具体问题具体分析原则
- 不要轻易放弃熟悉的工具