Web scraping in R, Part 2. Accélérer le processus avec le calcul parallèle et l'utilisation du package Rcrawler


Dans un article précédent , en utilisant l'analyse de scrapbooking, j'ai collecté des notes de films sur les sites IMDB et Kinopoisk et les ai comparées. Dépôt sur Github .


Le code a fait du bon travail, mais le grattage est souvent utilisé pour "gratter" non pas quelques pages, mais quelques milliers, et le code de l'article précédent ne convient pas pour un aussi "gros" grattage. Plus précisément, ce ne sera pas optimal. En principe, rien ne vous empêche de l'utiliser pour explorer des milliers de pages. Pratiquement parce que vous n’avez pas tellement de temps



Quand j'ai décidé d'utiliser scraping_imdb.R pour explorer 1000 pages


Optimisation du code. Une seule utilisation de la fonction read_html

Dans cet article, 100 liens vers les pages de la librairie Labyrinth seront utilisés pour vérifier le fonctionnement et la vitesse du code.


Un changement explicite qui peut accélérer le processus est une utilisation unique de la fonction de code "la plus lente" - read_html . Permettez-moi de vous rappeler qu'elle "lit" la page HTML. Dans la première version du code pour les sites de films, j'ai exécuté read_html chaque fois que j'avais besoin d'obtenir une certaine valeur (nom du film, année, genre, classement). Maintenant, les traces de cette «honte» ont été effacées de GitHuba, mais c'est le cas. Cela n'a aucun sens, car une variable créée à l'aide de read_html contient des informations sur la page entière et pour en obtenir différentes données, il suffit de html_nodes cette variable à la fonction html_nodes et de ne pas commencer à lire le HTML à chaque fois. Vous pouvez ainsi gagner du temps proportionnellement au nombre de valeurs que vous souhaitez obtenir. Du Labyrinthe, j'obtiens sept valeurs, respectivement, un code qui n'utilise qu'une seule lecture d'une page HTML fonctionnera environ sept fois plus rapidement. Pas mal! Mais avant d’accélérer à nouveau, je vais faire une digression et parler des points intéressants qui surviennent lors du grattage du site Web Labyrinth.


Caractéristiques du grattage de page dans le Labyrinthe

Dans cette partie, je n'aborderai pas la procédure d'obtention et de suppression des données mentionnée dans l'article précédent. Je ne mentionnerai que les moments que j'ai rencontrés pour la première fois lors de l'écriture de code pour le scrapbooking d'une librairie.


Tout d'abord, il convient de mentionner la structure. Elle n'est pas très à l'aise. En revanche, par exemple, sur le site de Read-Cities, les sections du genre avec des "filtres vides" ne donnent que 17 pages. Bien sûr, tous les 8011 livres du genre "Contemporary Foreign Prose" ne leur correspondent pas.


Par conséquent, je n'ai rien trouvé de mieux que de parcourir les liens https://www.labirint.ru/books/ **** avec un simple buste. Franchement, la méthode n'est pas la meilleure (ne serait-ce que parce que la plupart des livres "anciens" n'ont aucune information sauf le nom et sont donc pratiquement inutiles), donc si quelqu'un propose une solution plus élégante, je serai heureux. Mais j'ai découvert que sous le premier numéro fier du site Web du Labyrinthe se trouve un livre intitulé «Comment faire briller la lune» . Hélas, il est déjà impossible d'acheter ce magasin de connaissances.


Toutes les adresses pendant l'énumération peuvent être divisées en deux types:


  • Pages qui existent
  • Pages qui n'existent pas

Les pages existantes, à leur tour, peuvent être divisées en deux parties:


  • Pages contenant toutes les informations nécessaires
  • Pages qui ne contiennent pas toutes les informations nécessaires

Je me retrouve avec un tableau de données à sept colonnes:


  1. ISBN - Numéro de livre ISBN
  2. PRIX - prix du livre
  3. NAME - titre du livre
  4. AUTEUR - auteur du livre
  5. PUBLISHER - maison d'édition
  6. ANNÉE - année de publication
  7. PAGE - nombre de pages

Tout est clair avec les pages avec des informations complètes, elles ne nécessitent aucune modification par rapport au code des sites de films.


Quant aux pages sur lesquelles certaines données ne sont pas disponibles, ce n'est pas si simple avec elles. Une recherche sur la page ne renverra que les valeurs trouvées et la longueur de sortie diminuera du nombre d'éléments qu'elle ne trouvera pas. Cela brisera toute la structure. Pour éviter cela, une construction if ... else a été ajoutée à chaque argument, qui estime la longueur du vecteur obtenu après avoir utilisé la fonction html_nodes et si elle est nulle, elle retourne NA pour éviter de biaiser les valeurs.


  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 } })) 

Mais comme vous pouvez le constater ici, jusqu'à deux ifs et autant que deux autres. Seuls les if..esle "internes" sont pertinents pour la solution au problème décrit ci-dessus. L'extérieur résout le problème des pages inexistantes.


Pages qui n'ont tout simplement pas le plus de problèmes. Si les valeurs sont décalées sur des pages avec des données manquantes, alors lorsque l'entrée read_html page inexistante, la fonction read_html erreur et le code cessera de s'exécuter. Parce que d'une manière ou d'une autre, il n'est pas possible de détecter ces pages à l'avance, il est nécessaire de s'assurer que l'erreur n'arrête pas tout le processus.


La fonction de possibly du possibly package nous aidera à cela. Le sens des fonctions possibly (en plus possibly quietly et safely ) est de remplacer la sortie imprimée des effets secondaires (par exemple, les erreurs) par une valeur qui nous convient. possibly a une structure possibly(.f, otherwise) et si une erreur se produit dans le code, au lieu d'arrêter son exécution, il utilise la valeur par défaut (sinon). Dans notre cas, cela ressemble à ceci:


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

n est une liste d'adresses des pages du site que nous avons grattées. En sortie, nous obtenons une liste de longueur n, dans laquelle les éléments des pages existantes seront sous la forme "normale" pour la fonction read_html , et les éléments des pages inexistantes seront constitués du vecteur de caractères "NA". Veuillez noter que la valeur par défaut doit être un vecteur de caractères, car à l'avenir, nous y ferons référence. Si nous écrivons seulement NA , comme dans la partie du code PUBLISHER, cela ne sera pas possible. Pour éviter toute confusion, vous pouvez changer la valeur par ailleurs de NA à toute autre.


Et maintenant revenons au code pour obtenir le nom de l'éditeur. Externe si ... sinon est nécessaire aux mêmes fins que interne, mais en ce qui concerne les pages inexistantes. Si la variable book_html est "NA", alors chacune des valeurs "grattées" est également égale à NA (ici vous pouvez déjà utiliser la "vraie" NA , plutôt qu'un imposteur symbolique). Donc, à la fin, nous obtenons un tableau de la forme suivante:


ISBNPRIXNOMAUTEURÉDITEURAnnéeLa page
46653057703221488Set String Art "Cute Puppy" (30 * 30 cm) (DH6021)NAChat roux2019NA
NANANANANANANA
9785171160814273Arkady Averchenko: Histoires amusantes pour les enfantsAuteur: Averchenko Arkady Timofeevich, Artiste: Vlasova Anna YulievnaEnfant2019288

Revenons maintenant à l'accélération du processus de raclage.


Calcul parallèle dans R. Comparaison de vitesse et pièges lors de l'utilisation de la fonction read_html

Par défaut, tous les calculs dans R sont effectués sur le même cœur de processeur. Et tandis que ce noyau malheureux transpire, "raclant" les données de milliers de pages pour nous, le reste de nos camarades "refroidissent" en effectuant d'autres tâches. L'utilisation de l'informatique parallèle aide à attirer tous les cœurs de processeur vers le traitement / la réception de données, ce qui accélère le processus.


Je n'entrerai pas en profondeur dans la conception de l'informatique parallèle sur R, vous pouvez en lire plus à ce sujet, par exemple ici . La façon dont j'ai compris le parallélisme sur R est de créer des copies de R dans des clusters séparés en fonction du nombre de noyaux indiqués qui interagissent entre eux via des sockets .


Je vais vous parler de l'erreur que j'ai commise lors de l'utilisation de l'informatique parallèle. Au départ, mon plan était le suivant: en utilisant le calcul parallèle, j'obtiens une liste de 100 pages "read" read_html , puis en mode normal, je reçois juste les données dont j'ai besoin. Au début, tout s'est bien passé: j'ai eu une liste, j'y ai passé beaucoup moins de temps qu'en mode normal R. Mais seulement quand j'ai essayé d'interagir avec cette liste, j'ai reçu une erreur:


 Error: external pointer is not valid 

En conséquence, j'ai réalisé quel était le problème, en regardant des exemples sur Internet, et après cela, selon la loi de la méchanceté, j'ai trouvé l'explication d'Henrik Bengtsson dans la vignette du futur paquet. Le fait est que les fonctions XML du package xml2 sont des objets non exportables.
) Ces objets sont «liés» à cette session R et ne peuvent pas être transférés vers un autre processus, ce que j'ai essayé de faire. Par conséquent, la fonction lancée dans le calcul parallèle devrait contenir un «cycle complet» d'opérations: lecture d'une page HTML, réception et nettoyage des données nécessaires.


La création de l'informatique parallèle ne prend pas beaucoup de temps et de lignes de code. La première chose dont vous avez besoin est de télécharger les bibliothèques. Le référentiel Github indique quels packages sont nécessaires pour quelles méthodes. Ici, je vais montrer le calcul parallèle en utilisant la fonction parLapply du package parallel . Pour ce faire, il suffit d'exécuter doParallel (le parallel démarrera automatiquement dans ce cas). Si vous ne connaissez pas soudainement ou avez oublié le nombre de cœurs de votre processeur, détectez le nombre de detectCores qui vous aideront detectCores


 # detectCores - ,     number_cl <- detectCores() 

Ensuite, créez des copies parallèles de R:


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

Nous écrivons maintenant une fonction qui fera toutes les procédures dont nous avons besoin. Je note que depuis de nouvelles sessions sont créées. Les paquets R dont les fonctions sont utilisées dans notre propre fonction doivent être écrits dans le corps de la fonction. Dans spider_parallel.R, cela provoque l' stringr package stringr deux fois: d'abord pour obtenir les adresses de page, puis pour effacer les données.


Et puis la procédure n'est presque pas différente de l'utilisation de la fonction de lapply habituelle. Dans parLapply nous fournissons une liste d'adresses, notre propre fonction et, le seul ajout, une variable avec les clusters que nous avons créés.


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

C'est tout, maintenant il reste à comparer le temps passé.


Comparaison de la vitesse de calcul série et parallèle

Ce sera le point le plus court. L'informatique parallèle était 5 fois plus rapide que d'habitude:


Vitesse de raclage sans utiliser de calcul parallèle


l'utilisateurle systèmeréussi
13,570,40112,84

Vitesse de raclage à l'aide de l'informatique parallèle


l'utilisateurle systèmeréussi
0,140,0521/12

Que dire? L'informatique parallèle peut vous faire gagner beaucoup de temps sans créer de difficultés lors de la création du code. Avec une augmentation du nombre de noyaux, la vitesse augmentera presque proportionnellement à leur nombre. Ainsi, avec quelques modifications, nous avons accéléré le code 7 fois en premier (arrêtez de calculer read_html à chaque étape), puis 5 autres de plus, en utilisant des calculs parallèles. Les scripts Spider sans calcul parallèle, utilisant les packages parallel et foreach , sont dans le référentiel sur Github.


Un petit aperçu du package Rcrawler . Comparaison de vitesse.

Il existe plusieurs autres façons de supprimer les pages HTML dans R, mais je me concentrerai sur le package Rcrawler . Sa caractéristique distinctive des autres outils du langage R est la possibilité d' explorer des sites. Vous pouvez définir la fonction Rcrawler du même nom sur l'adresse du Rcrawler et elle contournera méthodiquement, page par page, l'ensemble du site. Rcrawler a de nombreux arguments pour configurer la recherche (par exemple, vous pouvez rechercher par mots clés, secteurs du site (utile lorsque le site se compose d'un grand nombre de pages), profondeur de recherche, ignorer les paramètres d'URL qui créent des pages en double, et bien plus encore. les fonctions ont déjà été établies des calculs parallèles, qui sont spécifiés par les arguments no_cores (le nombre de cœurs de processeur impliqués) et no_conn (le nombre de requêtes parallèles).


Pour notre cas, en grattant des adresses spécifiées, il existe une fonction ContentScraper . Il n'utilise pas l'informatique parallèle par défaut, vous devrez donc répéter toutes les manipulations que j'ai décrites ci-dessus. J'ai aimé la fonction elle-même - elle offre de nombreuses options pour configurer le grattage et est bien comprise à un niveau intuitif. Ici aussi, vous ne pouvez pas utiliser if..else pour les pages manquantes ou les valeurs manquantes, comme l'exécution de la fonction ne s'arrête pas.


 #   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) } 

Mais avec toutes les qualités positives, la fonction ContentScraper a un ContentScraper très sérieux - la vitesse de travail.


Vitesse de ContentScraper Rcrawler ContentScraper sans calcul parallèle


l'utilisateurle systèmeréussi
47,470,29212.24

Rcrawler ContentScraper Scraping Rcrawler Using Parallel Computing


l'utilisateurle systèmeréussi
0,010,0067,97

Donc, Rcrawler doit être utilisé si vous devez contourner le site sans spécifier au préalable les adresses URL, ainsi qu'avec un petit nombre de pages. Dans d'autres cas, une vitesse lente l'emportera sur tous les avantages possibles de l'utilisation de ce package.


Je serais reconnaissant pour tous commentaires, suggestions, plaintes
Lien vers le référentiel Github
Profil de mon cercle

Source: https://habr.com/ru/post/fr464399/


All Articles