爬虫基础:Rcurl与XML包
爬虫是一种利用代码(例如:R code或Python code)模拟浏览器访问(下载)页面并根据HTML结构筛选获取所需信息的一种工具。在R里面我们通常用Rcurl包实现前一半的功能(模拟浏览器访问页面),用XML包完成后一半功能(通过HTML树结构筛选提取信息)。
Rcurl包访问页面
第一步:模拟浏览器行为
若想用R语言模拟浏览器行为,就必须伪装报头。实际上,当我们用浏览器访问一个网页的时候,浏览器会像网页服务器发送一些指令。
# 构造请求报头
cust_header = c(`User-Agent` = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0",
Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
`Accept-Language` = "en-us", Connection = "keep-alive")
第二步:模拟访问
这个过程其实就是把指定的网页临时下载下来。两行代码就可以实现。
library(RCurl)
library(XML)
# 读取拉手深圳美食搜索
start_url = "http://shenzhen.lashou.com/cate/meishi"
# 读取PageSouce
pagesource <- getURL(start_url, httpheader = cust_header, .encoding = "utf-8")
XML包树结构筛选提取信息
第三步:整理HTML结构
使用XML中的htmlTreeParse函数解析刚才得到的webpage变量(其中储存了HTML所爬取网页的代码)生成标准的HTML树形结构并赋值给pagetree变量。之后便可在XML中对该变量进行各种操作了。
pagetree <- htmlTreeParse(pagesource, encoding = "GB2312", error = function(...) {}, useInternalNodes = TRUE, trim = TRUE)# 注意encoding
第四步:定位节点
在这里我们需要一点HTML的基础知识。HTML下的代码实际上是一个树级结构,是不同标签的层级嵌套。每一个标签以<x>开始,以</x>结束。举个例子:
<ul>
<li>
<div class="first">
<a href="https://ask.hellobi.com/www.baidu.com" title="百度">链接1</a>
</div>
</li>
<li>
<div class="first">
<a href="https://ask.hellobi.com/www.sina.com" title="新浪">链接2</a>
</div>
</li>
<body>与</body>之间的内容为网页的正文部分,如果我们想获取两个<a>标签中的正文(“链接1”,“链接2”),可以利用如下代码进行定位,其中text()代表的是获取标签内的文本。乍一看,是不是和文件路径很像?
ul/li/div/a/text()
此外,我们也可以用利用两个具有相同的class属性值直接从开始定位,也即
div[@class='first']/a/text() 这两个写法在XML包中都是适用的。
一般会利用chrome浏览器右键->查看源代码,也可以对想要查看的内容点击右键->检查,这时候我们如果右键某一行,就可以鼠标右键->copy->selector或Xpath直接复制相应的层级定位。需要注意的是,我们还需将复制来的定位代码更换成XML包所能识别的格式。比如用属性值用单引号,“//”表示所有该类型节点等。
node<-getNodeSet(pagetree, "//div[contains(@class,"goods")]//a[@class="goods-name"]//text()")
goods_name<-sapply(node,xmlValue)
其他细节点
对于多页面的解析
解析页数
# 解析页数
parseTotalPage <- function(pagesource) {
doc <- htmlParse(pagesource)
as.numeric(sapply(getNodeSet(doc, "//div[@class=\"page\"]/a[last()-1]/text()"),
xmlValue))}
构造一个提取函数提取信息
# 解析页面内容,获取门店名称、描述、优惠价,门店价
parseContent <- function(pagesource) {
doc <- htmlParse(pagesource)
goods_name <- sapply(getNodeSet(doc, "//div[contains(@class,\"goods\")]//a[@class=\"goods-name\"]//text()"),
xmlValue)
goods_text <- sapply(getNodeSet(doc, "//div[contains(@class,\"goods\")]//a[@class=\"goods-text\"]//text()"),
xmlValue)
price <- sapply(getNodeSet(doc, "//div[contains(@class,\"goods\")]//span[@class=\"price\"]/text()"),
xmlValue)
org_price <- sapply(getNodeSet(doc, "//div[contains(@class,\"goods\")]//span[@class=\"money\"]/del/text()"),
xmlValue)
result <- data.frame(goods_name, goods_text, price, org_price)}
循环读取每一页面信息
# 获取总页数和第一页内容
total_page <- parseTotalPage(pagesource)pageresults <- parseContent(pagesource)# 生成2-n页urlpage = 1:(total_page - 1)url_list = ""url_list[page] = paste0("http://shenzhen.lashou.com/cate/meishi/page", page +
1)
# 循环读取url,并进行下载解析
for (url in url_list) {
pagesource <- getURL(url, httpheader = cust_header, .encoding = "utf-8")
pageresult <- parseContent(pagesource)
pageresults <- rbind(pageresults, pageresult)}
# 输出结果到文件
write.table(pageresults, "result.txt", row.names = FALSE)
字符编码的改变
# 查看encod编码类型
library(stringi)stri_enc_detect(names)
# 转换编码类型names <- iconv(name2, "UTF-8", "gb2312")
删除冗余信息,字符串清理
to_remove <- paste(c("\n", "^\\s+|\\s+$"), collapse = "|")
faddress <- gsub(to_remove, "", faddress)
最后给出了XML里常用的节点定位方法与代码
# XML实例
library(XML)
url = "http://www.w3school.com.cn/example/xmle/books.xml"
doc = xmlTreeParse(url, useInternal = TRUE)
top = xmlRoot(doc)
# 选取所有 title 节点
nodes = getNodeSet(top, "/bookstore/book/title")
# 选取第一个 book 的 title
nodes1 = getNodeSet(top, "/bookstore/book[1]/title")
# 选取 price 节点中的所有文本
price = getNodeSet(doc, "/bookstore/book/price/text()")
# 选取价格高于 35 的 price 节点
price1 = getNodeSet(doc, "/bookstore/book[price>35]/price")
getNodeSet(doc, "/bookstore/book[1]")
# 选取属于 bookstore 子元素的最后一个 book 元素。
getNodeSet(doc, "/bookstore/book[last()]")
# 选取最前面的两个属于 bookstore 元素的子元素的 book 元素
getNodeSet(doc, "/bookstore/book[position()<3]")
# 选取所有拥有名为 lang 的属性的 title 元素。
getNodeSet(doc, "//title[@lang]")
# 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性
getNodeSet(doc, "//title[@lang='eng']")
# 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。getNodeSet(doc, "/bookstore/book[price>35.00]")
# 选取 bookstore 元素中的 book 元素的所有 title 元素,且 price# 元素的值须大于 35.00。
getNodeSet(doc, "/bookstore/book[price>35.00]/title")
# 选取 book 元素的所有 title 和 price 元素
getNodeSet(doc, "//book/title | //book/price")
# 选取文档中的所有 title 和 price 元素
getNodeSet(doc, "//title | //price")
# 选取 bookstore 元素的所有子元素
getNodeSet(doc, "/bookstore/*")
# 选取所有带有属性的 title 元素
getNodeSet(doc, "//title[@*]")
# 选择所有属性lang的值
unlist(getNodeSet(doc, "//title/@lang"), use.names = FALSE)
# title结点下的所有文本
getNodeSet(doc, "//title/text()")
运用rvest包提取数据
Rcurl和XML包进行爬虫,这个组合虽然功能强大,但是经常会出一点意想不到的小问题。于是更便捷的Rvest包登场,它真正的实现了快速爬取数据的目的。
运用SelectorGadget插件快速定位
安装
首先安装SelectorGadget插件,点击<https://cran.r-project.org/web/packages/rvest/vignettes/selectorgadget.html> 中的Installation,SelectorGadget链接。或<http://selectorgadget.com/> Chrome 扩展。
使用
点击插件后,在网页最下面会启动对话框,同时会有highlight(黄色)框框随鼠标滑动,当点击需要提取的信息时,选择框会变为绿色,SelectorGadget对话框将产生minimal CSS 选择语句。同时匹配语句的元素也会变黄,当再次点击黄色元素时,选择器变为红色,表示排除所选元素。当点击未标黄元素时,表示增加相应的元素,SelectorGadget对话框会随选择元素而发生变化。点击XPath时,会自动根据选择元素自动转换为xpath语法。
rvest爬取数据
以爬取豆瓣数据为例:
# 查看书的信息
library(rvest)
web <- read_html("https://book.douban.com/top250?icn=index-book250-all", encoding = "UTF-8")
positions <- web %>%
html_nodes("p.pl") %>%
html_text()
# 查看书的题目
position <- web %>%
html_nodes(".pl2 a") %>%
html_text()
# 查看encod编码类型
library(stringi)
stri_enc_detect(position)
# 转换编码类型
position <- iconv(position, "UTF-8", "gb2312")
# 字符串清理 去掉多余的空格,“\n”等信息
to_remove <- paste(c("\n", "^\\s+|\\s+$"), collapse = "|")position <- gsub(to_remove, "", position)
第一行是加载Rvest包。第二行是用“read_html函数读取网页信息(类似Rcurl里的getURL),在这个函数里只需写清楚网址和编码(一般就是UTF-8)即可第三行是获取节点信息。用%>%符号进行层级划分。web就是之前存储网页信息的变量,所以我们从这里开始,然后html_nodes()函数获取网页里的相应节点。在下面代码里我简单的重现了原网页里的一个层级结构。可以看到,实际上我们要爬取的信息在25个class属性为pl的<p>标签里的文本。
[清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元
而对于这样的结构,在htmlnodes()函数里的写法就是简单的"p.pl",其中“.”表示class属性的值,如果是id属性则用“#”,如果大家学过CSS选择器就很好理解了,是完全一致的。
最后我们用html_text()函数表示获取文本信息,否则返回的是整个<p>
标签。总体上用以下一行代码就可以实现:
position <- web %>%
html_nodes("p.pl") %>%
html_text()
比较与XML获取节点的方法(如下行代码),其实二者是异曲同工的,只不过将“/”分隔换为了“%>%”,同时个别写法有些许调整。
node <- getNodeSet(pagetree, "//p[@class='pl']/text()")
最终如果我们打印出这个变量的内容,就会发现和上篇文章中的爬取内容是一致的。
Rvest这个包的说明文档里给出了一些其他例子:
ateam <- read_html("http://www.boxofficemojo.com/movies/?id=ateam.htm")
# 两行代码分别获取了ateam这个网页里<center>标签里<td>的全部内容和<center>标签里<font>的全部内容
ateam %>% html_nodes("center") %>% html_nodes("td")
ateam %>% html_nodes("center") %>% html_nodes("font")
# 官方例子中还给出了获取特定序位的html标签的方法,用到了magrittr包里的extract2函数;以下两行代码都可以获得该网页中第一个<table>标签(由extract2(1)或`[[`(1)获取)中的所有<img>标签里的内容。
library(magrittr)
ateam %>% html_nodes("table") %>% extract2(1) %>% html_nodes("img")
ateam %>% html_nodes("table") %>% 1[[]] %>% html_nodes("img")
# 同理我们也可以获得网页里前两个<table>标签储存的所有<img>标签里的内容。
ateam %>% html_nodes("table") %>% 1:2[] %>% html_nodes("img")
ateam %>% html_nodes("table") %>% extract(1:2) %>% html_nodes("img")
参考资料:
1.知乎:【数据获取】爬虫利器Rvest包 <https://zhuanlan.zhihu.com/p/22940722?refer=rdatamining>
2.利用RCurl包完成自己感兴趣的团购信息【批量】抓取<http://blog.csdn.net/jiabiao1602/article/details/40856975>
3. Hadley Wickham,Selectorgadget <https://cran.rproject.org/web/packages/rvest/vignettes/selectorgadget.html>
4. CRAN其他相关资料
要想获取分析代码,可查看原文,进入本人的GitHubhttps://github.com/Alven8816查看下载,或通过本人邮箱yuwenhuajiayou@sina.cn与本人联系
”乐享数据“个人公众号,不代表任何团体利益,亦无任何商业目的。任何形式的转载、演绎必须经过公众号联系原作者获得授权,保留一切权力。欢迎关注“乐享数据”。