
在上一篇文章中 ,使用剪贴簿解析,我从IMDB和Kinopoisk网站收集了电影评分,并进行了比较。 Github上的存储库。
该代码很好地完成了它的任务,但是抓取通常用于“抓取”而不是几页,而是三千页,并且上一篇文章中的代码不适用于这样的“大”抓取。 更确切地说,它不是最佳的。 原则上,几乎没有什么可以阻止您使用它来爬网数千个页面。 实际上,因为您没有太多时间

当我决定使用scraping_imdb.R抓取1000页时
代码优化。 一次使用read_html
函数
在本文中,将使用100个指向迷宫书店页面的链接来检查代码的操作和速度。
可以加快处理速度的一项显式更改是一次性使用“最慢的”代码功能read_html
。 让我提醒您,她“阅读” HTML页面。 在电影网站代码的第一个版本中,每次需要获取一些值(电影名称,年份,类型,等级)时,我都会运行read_html
。 现在,这种“耻辱”的痕迹已从GitHuba中删除,但事实是这样。 这没有任何意义,因为使用read_html
创建的变量包含有关整个页面的信息并从中获取不同的数据,将这个非常大的变量html_nodes
到html_nodes
函数就足够了,而不必每次都开始读取HTML。 因此,您可以根据要获取的值的数量节省时间。 从迷宫中,我分别获得了七个值,仅使用一次读取HTML页面的代码将以大约七倍的速度运行。 还不错! 但是,在我再次“加速”之前,我将重点讨论从迷宫网站抓取时出现的有趣观点。
迷宫中的页面抓取功能
在这一部分中,我将不涉及上一篇文章中提到的获取和清除数据的过程。 我只会提及在编写用于剪贴簿的代码时首次遇到的那些瞬间。
首先,值得一提的是结构。 她不太舒服。 相反,例如,在Read-Cities网站上,带有“空滤镜”的流派部分仅给出17页。 当然,“当代外国散文”类型的所有8011本书籍都不适合它们。
因此,除了绕过一个简单的半身像的https://www.labirint.ru/books/****链接之外,我没有想到其他更好的方法。 坦白地说,这种方法不是最好的方法(如果仅仅是因为大多数“古老”书籍除了名称之外就没有其他信息,因此实际上是无用的),因此,如果有人提供更优雅的解决方案,我将感到高兴。 但是我发现,在迷宫网站上引以为傲的第一个数字下,是一本名为“如何制作月光”的书 。 las,现在已经不可能购买这个知识仓库。
枚举期间的所有地址可以分为两种类型:
现有页面又可以分为两部分:
我最终得到一个包含七列的数据表:
- ISBN-ISBN图书编号
- 价格-图书价格
- NAME-书名
- 作者-这本书的作者
- 出版商-出版社
- 年份-出版年份
- 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
,而不是符号冒名顶替者)。 因此,最后,我们获得了以下形式的表格:
现在,随着抓取过程的加速而返回。
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倍:
无需并行计算即可提高检索速度
使用并行计算提高速度
说什么 并行计算可以节省大量时间,而不会在创建代码时造成任何困难。 随着原子核数目的增加,速度将几乎与它们的数目成比例地增加。 因此,通过一些更改,我们首先将代码加速了7倍(在每个步骤中都停止了对read_html
计算),然后使用并行计算又对了5次进行了加速。 Github上的存储库中使用了parallel
和foreach
软件包, 没有并行计算的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
使用并行计算的Rcrawler ContentScraper ContentScraper
Rcrawler
因此,如果您需要绕过站点而无需先指定url地址以及少量页面,则应使用Rcrawler。 在其他情况下,低速将超过使用此软件包的所有可能优势。
如果有任何意见,建议和投诉,我将不胜感激
Github仓库链接
我的社交圈个人资料