R中的Web抓取,第2部分。通过并行计算和使用Rcrawler软件包加快过程


上一篇文章中 ,使用剪贴簿解析,我从IMDB和Kinopoisk网站收集了电影评分,并进行了比较。 Github上的存储库。


该代码很好地完成了它的任务,但是抓取通常用于“抓取”而不是几页,而是三千页,并且上一篇文章中的代码不适用于这样的“大”抓取。 更确切地说,它不是最佳的。 原则上,几乎没有什么可以阻止您使用它来爬网数千个页面。 实际上,因为您没有太多时间



当我决定使用scraping_imdb.R抓取1000页时


代码优化。 一次使用read_html函数

在本文中,将使用100个指向迷宫书店页面的链接来检查代码的操作和速度。


可以加快处理速度的一项显式更改是一次性使用“最慢的”代码功能read_html 。 让我提醒您,她“阅读” HTML页面。 在电影网站代码的第一个版本中,每次需要获取一些值(电影名称,年份,类型,等级)时,我都会运行read_html 。 现在,这种“耻辱”的痕迹已从GitHuba中删除,但事实是这样。 这没有任何意义,因为使用read_html创建的变量包含有关整个页面的信息并从中获取不同的数据,将这个非常大的变量html_nodeshtml_nodes函数就足够了,而不必每次都开始读取HTML。 因此,您可以根据要获取的值的数量节省时间。 从迷宫中,我分别获得了七个值,仅使用一次读取HTML页面的代码将以大约七倍的速度运行。 还不错! 但是,在我再次“加速”之前,我将重点讨论从迷宫网站抓取时出现的有趣观点。


迷宫中的页面抓取功能

在这一部分中,我将不涉及上一篇文章中提到的获取和清除数据的过程。 我只会提及在编写用于剪贴簿的代码时首次遇到的那些瞬间。


首先,值得一提的是结构。 她不太舒服。 相反,例如,在Read-Cities网站上,带有“空滤镜”的流派部分仅给出17页。 当然,“当代外国散文”类型的所有8011本书籍都不适合它们。


因此,除了绕过一个简单的半身像的https://www.labirint.ru/books/****链接之外,我没有想到其他更好的方法。 坦白地说,这种方法不是最好的方法(如果仅仅是因为大多数“古老”书籍除了名称之外就没有其他信息,因此实际上是无用的),因此,如果有人提供更优雅的解决方案,我将感到高兴。 但是我发现,在迷宫网站上引以为傲的第一个数字下,是一本名为“如何制作月光”的书 。 las,现在已经不可能购买这个知识仓库。


枚举期间的所有地址可以分为两种类型:


  • 存在的页面
  • 不存在的页面

现有页面又可以分为两部分:


  • 包含所有必要信息的页面
  • 不包含所有必要信息的页面

我最终得到一个包含七列的数据表:


  1. ISBN-ISBN图书编号
  2. 价格-图书价格
  3. NAME-书名
  4. 作者-这本书的作者
  5. 出版商-出版社
  6. 年份-出版年份
  7. PAGE-页数

具有完整信息的页面使所有内容都清晰可见,与电影网站的代码相比,它们不需要任何更改。


至于某些数据不可用的页面,使用它们并不是那么简单。 页面上的搜索将仅返回找到的那些值,并且输出长度将减少找不到的元素的数量。 这将破坏整个结构。 为避免这种情况,在每个参数中添加了一个if ... else构造,该构造对使用html_nodes函数后获得的向量的长度进行评估,如果该向量等于零,则它会返回NA以避免对值产生偏倚。


  PUBLISHER <- unlist(lapply(list_html, function(n){ publishing <- if(n != "NA") { publishing_html <- html_nodes(n, ".publisher a") publishing <- if(length(publishing_html) == 0){ NA } else { publishing <- html_text(publishing_html) } } else { NA } })) 

但您可以在这里注意到多达两个if和多达两个其他if。 只有“内部” if.esle与上述问题的解决方案有关。 外部解决了页面不存在的问题。


根本没有最多麻烦的页面。 如果在缺少数据的页面上对值进行了移位,则在将read_html输入提交read_html不存在的页面时,该函数将read_html错误并且代码将停止执​​行。 因为 由于某种原因无法提前检测到此类页面,因此必须确保该错误不会停止整个过程。


possibly软件包的possibly功能将帮助我们完成此任务。 possibly功能( possibly quietly ,更safely )的含义是用适合我们的值替换副作用(例如错误)的打印输出。 possibly具有possibly(.f, otherwise)结构possibly(.f, otherwise)并且如果代码中发生错误,则使用默认值(否则),而不是停止执行。 在我们的例子中,它看起来像这样:


 book_html <- possibly(read_html, "NA")(n) 

n是我们抓取的网站页面的地址列表。 在输出中,我们得到一个长度为n的列表,其中来自现有页面的元素将以“正常”形式执行read_html功能,而来自不存在页面的元素将由字符向量“ NA”组成。 请注意,默认值必须是字符向量,因为将来我们会引用它。 如果我们只写NA ,就像在PUBLISHER代码部分中那样,那将是不可能的。 为避免混淆,您可以将NA的其他值更改为任何其他值。


现在回到获取发布者名称的代码。 外部(如果...)出于与内部相同的目的而需要,但是针对不存在的页面。 如果变量book_html等于“ NA”,则每个“ book_html ”值也都等于NA (这里您已经可以使用“ real” NA ,而不是符号冒名顶替者)。 因此,最后,我们获得了以下形式的表格:


书号价格姓名作者出版商年份页数
46653057703221488设置弦乐艺术作品“可爱的小狗”(30 * 30厘米)(DH6021)不适用姜猫2019年不适用
不适用不适用不适用不适用不适用不适用不适用
9785171160814273Arkady Averchenko:儿童趣味故事作者:Averchenko Arkady Timofeevich,艺术家:Vlasova Anna Yulievna小子2019年288

现在,随着抓取过程的加速而返回。


R中的并行计算。使用read_html函数时的速度比较和陷阱

默认情况下,R中的所有计算都在同一处理器内核上执行。 尽管这个不幸的核心正在冒汗,为我们从数千个页面“刮擦”数据,但其他同志却通过执行一些其他任务而“冷却”。 使用并行计算有助于吸引所有处理器内核来处理/接收数据,从而加快了处理速度。


我不会深入研究R上的并行计算的设计,例如,可以在此处阅读有关它们的更多信息。 我理解R上的并行性的方式是,通过指示的通过套接字相互交互的内核数,在单独的群集中创建R的副本。


我将告诉您使用并行计算时发生的错误。 最初,我的计划是这样的:使用并行计算,我得到了100个“已读” read_html页面的列表,然后在正常模式下,我只获得了我需要的数据。 最初,一切进展顺利:我得到了一个列表,在列表上花费的时间比正常模式R少得多。但是只有当我尝试与该列表进行交互时,我才收到错误消息:


 Error: external pointer is not valid 

结果,我了解了问题所在,通过在Internet上查看示例,此后,根据卑鄙的定律,我在小插图中发现了Henrik Bengtsson对未来软件包的解释。 事实是xml2包的XML函数是不可导出的对象。
) 这些对象“绑定”到此R会话,并且无法转移到另一个我尝试这样做的进程。 因此,在并行计算中启动的功能应包含“整个周期”的操作:读取HTML页面,接收和清理必要的数据。


创建并行计算本身不需要花费很多时间和代码。 您需要做的第一件事就是下载库。 Github存储库指示哪些方法需要哪些软件包。 在这里,我将展示使用parallel包的parLapply函数进行并行计算。 为此,只需运行doParallel (在这种情况下, parallel将自动启动)。 如果您突然不知道或忘记了处理器的内核数量,请检测一下detectCores可以帮助多少个内核detectCores


 # detectCores - ,     number_cl <- detectCores() 

接下来,创建R的并行副本:


  # makePSOCKcluster -    R,    cluster <- makePSOCKcluster(number_cl) registerDoParallel(cluster) 

现在,我们正在编写一个函数,它将执行所需的所有过程。 我注意到 创建新的会话,其函数用于我们自己的函数的R数据包应写入函数主体。 在spider_parallel.R中,这会导致stringr包运行两次:首先获取页面地址,然后清除数据。


然后,该过程与使用通常的lapply函数几乎没有什么不同。 在parLapply我们提供地址列表,我们自己的函数以及唯一的变量,即包含我们创建的簇的变量。


 # parLapply -  lapply     big_list <- parLapply(cluster, list_url, scraping_parellel_func) #    stopCluster(cluster) 

仅此而已,现在还需要比较花费的时间。


串行和并行计算速度的比较

这将是最短的时间。 并行计算比平时快5倍:


无需并行计算即可提高检索速度


用户系统通过了
13.570.40112.84

使用并行计算提高速度


用户系统通过了
0.140.0512/21

说什么 并行计算可以节省大量时间,而不会在创建代码时造成任何困难。 随着原子核数目的增加,速度将几乎与它们的数目成比例地增加。 因此,通过一些更改,我们首先将代码加速了7倍(在每个步骤中都停止了对read_html计算),然后使用并行计算又对了5次进行了加速。 Github上的存储库中使用了parallelforeach软件包, 没有并行计算的Spider脚本。


Rcrawler软件包的概述。 速度比较。

还有其他几种方法可以在R中抓取HTML页面,但我将重点介绍Rcrawler软件包。 它与R语言中其他工具的区别在于可以对网站进行爬网。 您可以将相同名称的Rcrawler函数设置为Rcrawler地址,它将有条不紊地逐页绕过整个站点。 Rcrawler具有许多用于设置搜索的参数(例如,您可以按关键字搜索,站点的扇区(当站点包含大量页面时有用),搜索深度,忽略创建重复页面的URL参数等等。这些函数已经进行了并行计算,由参数no_cores (涉及的处理器内核数)和no_conn (并行请求数)指定。


对于我们的情况,从指定的地址抓取,有一个ContentScraper函数。 默认情况下,它不使用并行计算,因此您将需要重复上述所有操作。 我喜欢该函数本身-它提供了许多用于设置抓取功能的选项,并且在直观的层次上已广为人知。 同样在这里,您不能将if..else用于缺少页面或缺少值,例如 函数执行不会停止。


 #   ContentScraper: # CssPatterns -    CSS    . # ExcludeCSSPat -    CSS ,    . # ,   CSS     CSS ,    . # ManyPerPattern -  FALSE,       , #  .  TRUE,     ,   . # PatternsName -      .   #   c  ,      t_func <- function(n){ library(Rcrawler) t <- ContentScraper(n, CssPatterns = c("#product-title", ".authors", ".buying-price-val-number", ".buying-pricenew-val-number", ".publisher", ".isbn", ".pages2"), ExcludeCSSPat = c(".prodtitle-availibility", ".js-open-block-page_count"), ManyPerPattern = FALSE, PatternsName = c("title", "author", "price1", "price2", "publisher", "isbn", "page")) return(t) } 

但是,凭借所有积极的品质, ContentScraper函数的ContentScraper是非常严重的-工作速度。


无需并行计算的Rcrawler ContentScraper ContentScraper Rcrawler


用户系统通过了
47.470.29212.24

使用并行计算的Rcrawler ContentScraper ContentScraper Rcrawler


用户系统通过了
0.010.0067.97

因此,如果您需要绕过站点而无需先指定url地址以及少量页面,则应使用Rcrawler。 在其他情况下,低速将超过使用此软件包的所有可能优势。


如果有任何意见,建议和投诉,我将不胜感激
Github仓库链接
我的社交圈个人资料

Source: https://habr.com/ru/post/zh-CN464399/


All Articles