扒一扒rvest的前世今生!

时间:2022-05-08
本文章向大家介绍扒一扒rvest的前世今生!,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

rvest包可能是R语言中数据抓取使用频率最高的包了,它的知名度和曝光度在知乎的数据分析相关帖子和回答中都很高。

甚至很多爬虫教程和数据分析课程在讲解R语言网络数据抓取时,也大多以该包为主。

坦白的说,rvest的确是一个很好地数据抓取工具,不过他的强项更多在于网页解析,这一点儿之前就有说到。

你可能惊艳于rvest强大的解析能力,有两套解析语法可选(Xpath、css),短短几个关键词路径就可以提取出来很重要的数据。

但肯定也遇到过有些网页明明数据就摆在那里,通过Chrome开发者工具(或者selectorgadget)也copy了css或者xpath路径,可就是没有返回值,或者总是返回chracter(0)、list(0)或者NULL。

老实说,这个情况真的不能怪rvest,这与rvest的功能定位有关。这里我们看一下rvest的GitHub主页上hadley对rvest的定位:

rvest helps you scrape information from web pages. It is designed to work with magrittr to make it easy to express common web scraping tasks, inspired by libraries like beautiful soup.

rvest旨在帮助我们从网页获取信息,通过植入magrittr的管道函数使得常见的网络抓取任务更加便捷,它的灵感来源于BeautifulSoup(注:这是一个Python非常有名并且强大的网页解析库)。

以下是我的个人愚见,这里的网络抓取存在一个前提,即你有权限直接通过URL获取完整网页(注意是完整网页)或者,你已经通过其他请求库(比如RCurl或者httr)获取了完整的网页,那么剩余的事情就交给rvest吧,它一定帮你办的妥妥的(前提css和xpath要熟练)。

当然rvest包允许你直接通过url访问html文档,但是这种访问方式是很脆弱的,因为没有任何伪装措施和报头信息,直接访问存在着很大的隐患。还记得之前讲异步加载的时候说过的,ajax技术将请求的的网络资源分成了html纯文档和js脚本,浏览器可以通过解析并执行js脚本来更新关键数据,而通过其他非浏览器终端发送的请求,通常情况下只能拿到纯文档(你可以看到一些script标签中引用的的.js脚本),并不具备解析js脚本的能力。

接下来扒一扒rvest包中主要函数的源码,给我以上的观点多一些充足的论据!

library("rvest")
library("magrittr")
library("xml2")
library("selectr")

rvest包的几个重要函数:

read_html()
html_nodes()
html_attrs()
html_text()
html_table()
htmm_session()

相信对于rvest包而言,你常用的函数不无外乎这几个,接下来我们对照着这几个函数的源码,一个一个剖析! read_html

function (x, encoding = "", ..., options = c("RECOVER", "NOERROR", 
    "NOBLANKS")) 
{
    UseMethod("read_html")
}
<environment: namespace:xml2>

你会发现,read_html函数直接调用的是xml2包中的read_html方法,rvest包作为请求器的脆弱性便在于此,它是一个I/0函数。脱俗一点说就是文件导入导出的操纵函数,与read_csv、read_xlsx、read_table属于同类。

在XML包中与之功能一致的函数是xmlParse/xmlTreeParse。xmlParse/xmlTreeParse函数也是仅仅作为RCurl请求包的解析函数使用的,很少有单独使用xmlParse请求并解析网页(太脆弱了,尽管它是支持直接从url获取并解析网页的)。

当然,这并不妨碍rvest包(read_html函数)直接从某些网站的URL中解析数据,很多静态网页并不会对网络请求做过多限制,比如不检查User-Agent,不做任何的数据隐藏,不限制数据权限等。

对于获取并解析网页而言,你可以直接加载xml2包,使用其read_html函数。

html_nodes

html_nodes函数可能是rvest包中封装的最为成功的函数了,就是这个函数提供给大家两套网页解析语法:xpath、css。看下它的源码吧!

html_nodes <- function(x, css, xpath) {
  UseMethod("html_nodes")
}
#' @export

html_nodes.default <- function(x, css, xpath) {
  xml2::xml_find_all(x, make_selector(css, xpath))
}

make_selector <- function(css, xpath) {
  if (missing(css) && missing(xpath))
      stop("Please supply one of css or xpath", call. = FALSE)
  if (!missing(css) && !missing(xpath))
      stop("Please supply css or xpath, not both", call. = FALSE)
  if (!missing(css)) {
      if (!is.character(css) && length(css) == 1)
      stop("`css` must be a string")
      selectr::css_to_xpath(css, prefix = ".//")
  } else {
      if (!is.character(xpath) && length(xpath) == 1)
      stop("`xpath` must be a string")
      xpath
    }
}

接下来给大家剖析这段html_nodes的源码,首先定义了一个基于S3类的泛型函数——html_nodes。这个泛型函数的模型行为是html_nodes.default。

html_nodes.default函数中,使用的是xml2包中的xml_find_all函数,这才是rvest包强大解析能力的核心底层实现。无论你传入的是css路径还是xpath路径,最终都是通过这个函数实现的。

xml_find_all函数中又使用了一个make_selector函数,他是一个选择器,即在css路径表达式和xpath选择。make_selector函数首先判断提供的解析语法参数是否完备,当你没有提供任何一个解析语法的时候(html_nodes()函数中除了doc文档之外,没有提供xpath或者css备选参数),抛出错误并中断操作:Please supply one of css or xpath,当同时提供了css和xpath参数时也会抛出错误并中断执行,Please supply css or xpath, not both。

当你提供css参数时(因为这里是位置参数,所以除了 第一个参数是html文档之外,只提供一个未命名参数会被当做css参数处理,想要使用xpath参数必须显式声明——xpath=”path”)。函数会判断css参数是否合法,不合法则会报错,合法之后,会使用selectr包中的css_to_xpath函数将css路径表达式转换为xpath语法,然后输出,当你提供的是xptah路径时(需需显式声明参数名称),首先校验xpath是否合法,不合法则报错,合法则返回xptah路径。

所以以上的核心要点有两个:

  • 在html_nodes函数中,最终的解析函数是xml2中的xml_find_all函数,它的功能类似于XML包中的XpathAapply函数或者getNodest函数。
  • 在html_nodes函数中,一切都是xpath,即便你提供的是css路径,也会先被转化为xpath之后再使用xml_find_all函数进行处理。

html_attrs

好了接下来该讲html_attrs函数了,还是看源码:

function (x) 
{
    xml2::xml_attrs(x)
}
<environment: namespace:rvest>

仍然是,直接调用的xml2包中的xml_attrs函数,就是从节点中批量提取属性值。

html_text

function (x, trim = FALSE) 
{
    xml2::xml_text(x, trim = trim)
}
<environment: namespace:rvest>

调用的xml2包中的xml_text函数,提取节点文本。

html_table

function (x, header = NA, trim = TRUE, fill = FALSE, dec = ".") 
{
    UseMethod("html_table")
}
<environment: namespace:rvest>

html_table函数是做了一些高级的封装,但是底层仍然时通过xml2::xml_find_all实现的,它将table标签提取出来之后,又做了一些清洗整理。

源码在这里: https://github.com/hadley/rvest/blob/master/R/table.R

html_session

htmm_session可以实现一些简单的回话维持和cookie管理功能,但是该包的源文档并没有给出任何实际案例,网络上类似资料也极少。它的底层是通过封装httr包中的handle函数来实现的,这算是rvest包的较为高级功能了,里面确实封装了一些真正的GET请求、POST请求构造类型。但是平时能用到的人估计不多。我看了下源码,回头乖乖去看httr文档去了。

源码在这里:

https://github.com/hadley/rvest/blob/master/R/session.R

至此,主要的rvest函数都撸完一个遍了,这里给rvest做一个小结吧:

它的高级请求功能依托于httr(当然你可以直接使用httr来构造请求)。 解析器依托于xml2包中的xml_find_all函数实现。 解析语法有css和xpath可选,但是最终都会转换为xpath进行解析。 借助magrittr包来做管道优化,实现代码简化与效率提升。

如果要跟Rcurl和XML组合来一个对比,我觉得这里可以这么比。

########################################################################################################
包类型                 RCurl                             httr
--------------------------------------------------------------------------------------------------------
请求        GET请求       getURL/getFrom                           GET
                          getBinaryURL/writeBin(二进制)            writeBin(二进制)
            POST请求      postFrom(支持四种常规参数编码类型)         POST(支持四种常规参数编码类型)
            application/x-www-form-urlencoded     
            multipart/form-data
            application/json
            text/xml
--------------------------------------------------------------------------------------------------------
解析(html/xml)            XML::xmlParse/XML::htmlParse             rvest::read_html/xml
                                                                   xml2::read_html/xml
            --------------------------------------------------------------------------------------------
            Xpath         XML::xpathSApply/XML::getNodeSet         rvest::html_nodes/xml2::xml_find_all                                     
                          XML::xmlGetAttr                          rvest::html_attrs/xml2::xml_attrs
                          XML::xmlValue                            rvest::html_text/xml2::xml_text
            ---------------------------------------------------------------------------------------------
            CSS           selectr::querySelectorAll                selectr::querySelectorAll
---------------------------------------------------------------------------------------------------------
高级回话管理               curl<-getCurlHandle                      curl<-handle() 
---------------------------------------------------------------------------------------------------------
json返回值                jsonlite::fromJSON                       jsonlite::fromJSON
#########################################################################################################

如果你想了解更多关于css路径表达式,xpath解析的相关知识,可以去W3c学习全套的技术标准,也可以参考以下这几篇文章:

左手用R右手Python系列16——XPath与网页解析库

左手用R右手Python系列17——CSS表达式与网页解析

R语言数据抓取实战——RCurl+XML组合与XPath解析

左手用R右手Python系列——模拟登陆教务系统

Python网络数据抓取实战——Xpath解析豆瓣书评

左手用R右手Python——CSS网页解析实战

左手用R右手Python系列——模拟登陆教务系统

如果想了解抓包流程和json返回值处理,可以参考以下几篇内容:

网易云课堂Excel课程爬虫思路

左手用R右手Pyhon系列——趣直播课程抓取实战

Python数据抓取与可视化实战——网易云课堂人工智能与大数据板块课程实战

R语言网络数据抓取的又一个难题,终于攻破了!

R语言爬虫实战——网易云课堂数据分析课程板块数据爬取

R语言爬虫实战——知乎live课程数据爬取实战

以上便是,你可以在整个R语言的爬虫相关工具体系中找到rvest的位置,他是xml2包的进一步高级封装和替代。

至于浏览器驱动的网络请求,在R语言中,有Rwebdriver包和Rselenium包可以支持,并且支持大部分主流浏览器(IE、Chrome、Firfox、PlantomJS)。

当你看到这个R语言爬虫工具列表时,你肯定会很惊讶,哇塞,原来R语言的爬虫功能这么强大耶,的确如此,太多的高级功能只是无人问津罢了。

R语言缺的就是没有像Python中那么强大的可以构建工程项目用的框架,比如Scrapy这种的。

往期案例数据请移步本人GitHub: https://github.com/ljtyduyu/DataWarehouse/tree/master/File