Web scraping en R, Parte 2. Acelerando el proceso con computación paralela y usando el paquete Rcrawler


En un artículo anterior , usando el análisis de álbumes de recortes, recopilé clasificaciones de películas de los sitios IMDB y Kinopoisk y las comparé. Repositorio en Github .


El código hizo un buen trabajo en su tarea, pero el raspado a menudo se usa para "raspar" no un par de páginas, sino un par de tres mil, y el código del artículo anterior no es adecuado para un raspado tan "grande". Más precisamente, no será óptimo. En principio, prácticamente nada le impide usarlo para rastrear miles de páginas. Prácticamente, porque no tienes tanto tiempo



Cuando decidí usar scraping_imdb.R para rastrear 1000 páginas


Optimización de código. Un solo uso de la función read_html

En este artículo, se utilizarán 100 enlaces a las páginas de la librería Labyrinth para verificar el funcionamiento y la velocidad del código.


Un cambio explícito que puede acelerar el proceso es el uso de una sola vez de la función de código "más lenta": read_html . Déjame recordarte que ella "lee" la página HTML. En la primera versión del código para sitios de películas, ejecuté read_html cada vez que necesitaba obtener algún valor (nombre de la película, año, género, calificación). Ahora las huellas de esta "vergüenza" han sido borradas de GitHuba, pero lo es. Esto no tiene sentido, porque la variable creada usando read_html contiene información sobre toda la página y para obtener diferentes datos de ella, es suficiente para html_nodes esta variable a la función html_nodes y no comenzar a leer HTML cada vez. Por lo tanto, puede ahorrar tiempo en proporción a la cantidad de valores que desea obtener. Del Laberinto, obtengo siete valores, respectivamente, el código que usa solo una lectura de una página HTML funcionará aproximadamente siete veces más rápido. No esta mal! Pero antes de "acelerar" de nuevo, me desviaré y hablaré sobre los puntos interesantes que surgen cuando salgo del sitio web de Labyrinth.


Características del raspado de página en el laberinto

En esta parte, no tocaré el procedimiento para obtener y borrar los datos que se mencionaron en el artículo anterior. Solo mencionaré aquellos momentos que encontré por primera vez al escribir código para hacer un álbum de recortes en una librería.


En primer lugar, vale la pena mencionar la estructura. Ella no es muy cómoda. Por el contrario, por ejemplo, del sitio web de Read-Cities, las secciones del género con "filtros vacíos" solo dan 17 páginas. Por supuesto, todos los 8011 libros del género "Proseta extranjera contemporánea" no encajan en ellos.


Por lo tanto, no se me ocurrió nada mejor que recorrer los enlaces https://www.labirint.ru/books/ **** con un simple busto. Hablando francamente, el método no es el mejor (aunque solo sea porque la mayoría de los libros "antiguos" no tienen información, excepto el nombre y, por lo tanto, son prácticamente inútiles), por lo que si alguien ofrece una solución más elegante, me alegraré. Pero descubrí que debajo del orgulloso primer número en el sitio web del Laberinto hay un libro titulado "Cómo hacer alcohol ilegal" . Por desgracia, ya es imposible comprar este depósito de conocimiento.


Todas las direcciones durante la enumeración se pueden dividir en dos tipos:


  • Páginas que existen
  • Páginas que no existen

Las páginas existentes, a su vez, se pueden dividir en dos partes:


  • Páginas que contienen toda la información necesaria.
  • Páginas que no contienen toda la información necesaria.

Termino con una tabla de datos con siete columnas:


  1. ISBN - Número de libro ISBN
  2. PRECIO - precio del libro
  3. NOMBRE - título del libro
  4. AUTOR - autor del libro
  5. EDITORIAL - editorial
  6. AÑO - año de publicación
  7. PÁGINA - número de páginas

Todo está claro con las páginas con información completa, no requieren ningún cambio en comparación con el código de los sitios de películas.


En cuanto a las páginas en las que algunos datos no están disponibles, no es tan simple con ellos. Una búsqueda en la página devolverá solo aquellos valores que encuentre y la longitud de salida disminuirá en la cantidad de elementos que no encontrará. Esto romperá toda la estructura. Para evitar esto, se agregó una construcción if ... else a cada argumento, que evalúa la longitud del vector obtenido después de usar la función html_nodes y si es igual a cero, devuelve NA para evitar valores de polarización.


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

Pero como puede ver aquí, hay dos ifs y hasta dos más. Sólo los "internos" si ... son relevantes para la solución del problema descrito anteriormente. Exterior resuelve el problema con páginas inexistentes.


Páginas que simplemente no tienen más problemas. Si los valores se desplazan en páginas con datos faltantes, cuando la entrada read_html página inexistente, la función read_html error y el código dejará de ejecutarse. Porque de alguna manera no es posible detectar tales páginas de antemano, es necesario asegurarse de que el error no detenga todo el proceso.


La función de possibly del possibly paquete nos ayudará con esto. El significado de las funciones de possibly (además de possibly quietly y safely ) es reemplazar la salida impresa de los efectos secundarios (por ejemplo, errores) con un valor que nos convenga. possibly tiene una estructura possibly(.f, otherwise) y si se produce un error en el código, en lugar de detener su ejecución, utiliza el valor predeterminado (de lo contrario). En nuestro caso, se ve así:


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

n es una lista de direcciones de las páginas del sitio que recopilamos. En la salida, obtenemos una lista de longitud n, en la que los elementos de las páginas existentes estarán en la forma "normal" para realizar la función read_html , y los elementos de las páginas inexistentes consistirán en el vector de caracteres "NA". Tenga en cuenta que el valor predeterminado debe ser un vector de caracteres, porque en el futuro nos referiremos a él. Si escribimos solo NA , como en la parte del código PUBLISHER, esto no será posible. Para evitar confusiones, puede cambiar el valor de NA a cualquier otro.


Y ahora volvamos al código para obtener el nombre del editor. Externo si ... más se necesita para los mismos fines que interno, pero con respecto a las páginas inexistentes. Si la variable book_html es igual a "NA", entonces cada uno de los valores "raspados" también es igual a NA (aquí ya puede usar el NA "real", en lugar de un impostor simbólico). Entonces, al final, obtenemos una tabla de la siguiente forma:


ISBNPRECIONombreAUTOREDITORAñoPagina
46653057703221488Conjunto de arte de cuerda "Cute Puppy" (30 * 30 cm) (DH6021)NAGato jengibre2019NA
NANANANANANANA
9785171160814273Arkady Averchenko: historias divertidas para niñosAutor: Averchenko Arkady Timofeevich, Artista: Vlasova Anna YulievnaNiño2019288

Ahora de vuelta con la aceleración del proceso de raspado.


Computación paralela en R. Comparación de velocidad y dificultades al usar la función read_html

Por defecto, todos los cálculos en R se realizan en el mismo núcleo del procesador. Y mientras este desafortunado núcleo está sudando, "raspando" datos de miles de páginas para nosotros, el resto de nuestros camaradas se están "enfriando" realizando otras tareas. El uso de la computación paralela ayuda a atraer todos los núcleos del procesador al procesamiento / recepción de datos, lo que acelera el proceso.


No profundizaré en el diseño de la computación paralela en R, puede leer más sobre ellos, por ejemplo, aquí . La forma en que entendí el paralelismo en R es crear copias de R en grupos separados de acuerdo con el número de núcleos indicados que interactúan entre sí a través de sockets .


Te contaré sobre el error que cometí al usar la computación paralela. Inicialmente, mi plan era este: usando la computación paralela, obtengo una lista de 100 páginas "read" read_html , y luego en modo normal solo obtengo los datos que necesito. Al principio todo salió bien: obtuve una lista, pasé mucho menos tiempo en ella que en el modo normal R. Pero solo cuando intenté interactuar con esta lista, recibí un error:


 Error: external pointer is not valid 

Como resultado, me di cuenta de cuál era el problema, mirando ejemplos en Internet, y después de eso, de acuerdo con la ley de la maldad, encontré la explicación de Henrik Bengtsson en la viñeta para el paquete futuro . El hecho es que las funciones XML del paquete xml2 son objetos no exportables.
) Estos objetos están "vinculados" a esta sesión R y no se pueden transferir a otro proceso, lo que intenté hacer. Por lo tanto, la función iniciada en computación paralela debe contener un "ciclo completo" de operaciones: leer una página HTML, recibir y limpiar los datos necesarios.


Crear computación paralela en sí no toma mucho tiempo y líneas de código. Lo primero que necesita es descargar las bibliotecas. El repositorio de Github indica qué paquetes son necesarios para qué métodos. Aquí mostraré computación paralela usando la función parLapply del paquete parallel . Para hacer esto, simplemente ejecute doParallel (el parallel comenzará automáticamente en este caso). Si de repente no sabe u olvidó la cantidad de núcleos de su procesador, detecte cuántos detectCores


 # detectCores - ,     number_cl <- detectCores() 

A continuación, cree copias paralelas de R:


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

Ahora estamos escribiendo una función que hará todos los procedimientos que necesitamos. Noto que desde se crean nuevas sesiones. Los paquetes R cuyas funciones se utilizan en nuestra propia función deben escribirse en el cuerpo de la función. En spider_parallel.R, esto hace que el paquete stringr ejecute dos veces: primero para obtener las direcciones de página y luego borrar los datos.


Y luego el procedimiento casi no es diferente del uso de la función lapply habitual. En parLapply proporcionamos una lista de direcciones, nuestra propia función y, la única adición, una variable con los clústeres que creamos.


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

Eso es todo, ahora queda por comparar el tiempo dedicado.


Comparación de velocidad de cálculo en serie y en paralelo

Este será el punto más corto. La computación paralela fue 5 veces más rápida de lo habitual:


Velocidad de raspado sin usar computación paralela


el usuarioel sistemapasado
13,570,40112,84

Velocidad de raspado usando computación paralela


el usuarioel sistemapasado
0,140,0521/12

Que decir La computación paralela puede ahorrarle mucho tiempo sin crear dificultades para crear el código. Con un aumento en el número de núcleos, la velocidad aumentará casi en proporción a su número. Entonces, con algunos cambios, aceleramos el código 7 veces primero (deje de calcular read_html en cada paso), y luego otros 5 más, usando cálculos paralelos. Los scripts de araña sin computación paralela, que usan los paquetes parallel y foreach , están en el repositorio en Github.


Una pequeña descripción del paquete Rcrawler . Comparación de velocidad.

Hay varias otras formas de desechar páginas HTML en R, pero me centraré en el paquete Rcrawler . Su característica distintiva de otras herramientas en el lenguaje R es la capacidad de rastrear sitios. Puede configurar la función Rcrawler del mismo nombre para la dirección del Rcrawler y, metódicamente, página por página, omitirá todo el sitio. Rcrawler tiene muchos argumentos para configurar la búsqueda (por ejemplo, puede buscar por palabras clave, sectores del sitio (útil cuando el sitio consta de una gran cantidad de páginas), profundidad de búsqueda, ignorar los parámetros de URL que crean páginas duplicadas, y mucho más. las funciones ya se han establecido cálculos paralelos, que se especifican mediante los argumentos no_cores (el número de núcleos de procesador involucrados) y no_conn (el número de solicitudes paralelas).


Para nuestro caso, raspado de las direcciones especificadas, hay una función ContentScraper . No utiliza la computación paralela de manera predeterminada, por lo que deberá repetir todas las manipulaciones que describí anteriormente. Me gustó la función en sí misma: proporciona muchas opciones para configurar el raspado y se entiende bien a un nivel intuitivo. Además, aquí no puede usar if..else para páginas faltantes o valores faltantes, como La ejecución de la función no se detiene.


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

Pero con todas las cualidades positivas, la función ContentScraper tiene un ContentScraper muy serio: la velocidad del trabajo.


Contenido de Rcrawler Rcrawler ContentScraper rascado sin computación paralela


el usuarioel sistemapasado
47,470,29212,24

Rcrawler ContentScraper ContentScraper Rcrawler usando computación paralela


el usuarioel sistemapasado
0,010.0067,97

Por lo tanto, se debe usar Rcrawler si necesita omitir el sitio sin especificar primero las direcciones URL, así como con un pequeño número de páginas. En otros casos, la velocidad lenta superará todas las ventajas posibles de usar este paquete.


Agradecería cualquier comentario, sugerencia, queja
Enlace al repositorio de Github
Mi perfil circular

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


All Articles