Web Scraping al estilo de Java

Por definición, el raspado web se refiere al proceso de extraer una cantidad significativa de información de un sitio web mediante scripts o programas. Tales guiones o p...

Introducción

Por definición, el raspado web se refiere al proceso de extraer una cantidad significativa de información de un sitio web mediante scripts o programas. Dichos scripts o programas permiten extraer datos de un sitio web, almacenarlos y presentarlos tal como los diseñó el creador. Los datos recopilados también pueden ser parte de un proyecto más grande que utiliza los datos extraídos como entrada.

Anteriormente, para extraer datos de un sitio web, tenía que abrir manualmente el sitio web en un navegador y emplear la función de copiar y pegar, antigua pero dorada. Este método funciona, pero su principal inconveniente es que puede resultar agotador si la cantidad de sitios web es grande o hay una gran cantidad de información. Tampoco se puede automatizar.

Con web scraping, no solo puede automatizar el proceso, sino también escalarlo para manejar tantos sitios web como sus recursos informáticos lo permitan.

En esta publicación, exploraremos el web scraping usando el lenguaje Java. También espero que esté familiarizado con los conceptos básicos del lenguaje Java y tenga Java 8 instalado en su máquina.

¿Por qué Web Scraping? {#por qué webscraping}

El proceso de raspado web presenta varias ventajas que incluyen:

  • El tiempo requerido para extraer información de una fuente en particular se reduce significativamente en comparación con copiar y pegar manualmente los datos.
  • Los datos extraídos son más precisos y tienen un formato uniforme, lo que garantiza la coherencia.
  • Un web scraper se puede integrar en un sistema y alimentar datos directamente al sistema mejorando la automatización.
  • Algunos sitios web y organizaciones no proporcionan API que proporcionen la información en sus sitios web. Las API facilitan la extracción de datos, ya que son fáciles de consumir desde otras aplicaciones. En su ausencia, podemos utilizar web scraping para extraer información.

Las organizaciones utilizan ampliamente el web scraping en la vida real de las siguientes maneras:

  • Los motores de búsqueda como Google y DuckDuckGo implementan web scraping para indexar sitios web que finalmente aparecen en los resultados de búsqueda.
  • Los equipos de comunicación y marketing de algunas empresas utilizan scrapers para extraer información sobre sus organizaciones en Internet. Esto les ayuda a identificar su reputación en línea y trabajar para mejorarla.
  • Web scraping también se puede utilizar para mejorar el proceso de identificación y seguimiento de las últimas historias y tendencias en Internet.
  • Algunas organizaciones utilizan web scraping para estudios de mercado donde extraen información sobre sus productos y también sobre la competencia.

Estas son algunas de las formas en que se puede usar el web scraping y cómo puede afectar las operaciones de una organización.

Qué usar

Hay varias herramientas y bibliotecas implementadas en Java, así como API externas, que podemos usar para crear web scrapers. El siguiente es un resumen de algunos de los más populares:

  • JSoup: esta es una biblioteca simple de código abierto que proporciona una funcionalidad muy conveniente para extraer y manipular datos mediante el uso de DOM transversal o selectores CSS para encontrar datos. No es compatible con el análisis basado en XPath y es apto para principiantes. Se puede encontrar más información sobre el análisis de XPath aquí.

  • Unidad HTML - es un marco más poderoso que puede permitirle simular eventos del navegador, como hacer clic y enviar formularios al raspar y también tiene soporte para JavaScript. Esto mejora el proceso de automatización. También es compatible con el análisis basado en XPath, a diferencia de JSoup. También se puede utilizar para pruebas unitarias de aplicaciones web.

  • Excursión: esta es una biblioteca de extracción y automatización web que se puede usar para extraer datos de páginas HTML o cargas útiles de datos JSON mediante un navegador sin interfaz. Puede ejecutar y manejar solicitudes y respuestas HTTP individuales y también puede interactuar con API REST para extraer datos. Se ha actualizado recientemente para incluir compatibilidad con JavaScript.

Estas son solo algunas de las bibliotecas que puede usar para desechar sitios web usando el lenguaje Java. En esta publicación, trabajaremos con JSoup.

Implementación simple

Habiendo aprendido las ventajas, los casos de uso y algunas de las bibliotecas que podemos usar para lograr el raspado web con Java, implementemos un raspador simple usando la biblioteca JSoup. Vamos a desechar este sitio web simple que encontré - Código de clasificación que muestra proyectos de código abierto a los que puedes contribuir en Github y se pueden ordenar por idiomas .

Aunque hay API disponibles que brindan esta información, me parece un buen ejemplo para aprender o practicar web scraping.

Requisitos previos

Antes de continuar, asegúrese de tener instalado lo siguiente en su computadora:

Vamos a utilizar Experto para gestionar nuestro proyecto en cuanto a generación, empaquetado, gestión de dependencias, testing entre otras operaciones.

Verifique que Maven esté instalado ejecutando el siguiente comando:

1
$ mvn --version

La salida debe ser similar a:

1
2
3
4
5
Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-17T21:33:14+03:00)
Maven home: /usr/local/Cellar/Maven/3.5.4/libexec
Java version: 1.8.0_171, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre
Default locale: en_KE, platform encoding: UTF-8
OS name: "mac os x", version: "10.14.1", arch: "x86_64", family: "mac"

Configuración

Con Maven configurado correctamente, generemos nuestro proyecto ejecutando el siguiente comando:

1
2
$ mvn archetype:generate -DgroupId=com.codetriage.scraper -DartifactId=codetriagescraper -DarchetypeArtifactId=Maven-archetype-quickstart -DarchetypeVersion=1.1 -DinteractiveMode=false
$ cd codetriagescraper

Esto generará el proyecto que contendrá nuestro raspador.

En la carpeta generada, hay un archivo llamado pom.xml que contiene detalles sobre nuestro proyecto y también las dependencias. Aquí es donde agregaremos la dependencia de JSoup y una configuración de complemento para permitir que Maven incluya las dependencias del proyecto en el archivo jar producido. También nos permitirá ejecutar el archivo jar usando el comando java -jar.

Elimine la sección dependencias en pom.xml y reemplácela con este fragmento, que actualiza las dependencias y las configuraciones del complemento:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  <dependencies>
      <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
      </dependency>
     <!-- our scraping library -->
      <dependency>
          <groupId>org.jsoup</groupId>
          <artifactId>jsoup</artifactId>
          <version>1.11.3</version>
      </dependency>
  </dependencies>

  <build>
      <plugins>
          <!--
          This plugin configuration will enable Maven to include the project dependencies
          in the produced jar file.
          It also enables us to run the jar file using `java -jar command`
          -->
          <plugin>
              <groupId>org.apache.Maven.plugins</groupId>
              <artifactId>Maven-shade-plugin</artifactId>
              <version>3.2.0</version>
              <executions>
                  <execution>
                      <phase>package</phase>
                      <goals>
                          <goal>shade</goal>
                      </goals>
                      <configuration>
                          <transformers>
                              <transformer
                                      implementation="org.apache.Maven.plugins.shade.resource.ManifestResourceTransformer">
                                  <mainClass>com.codetriage.scraper.App</mainClass>
                              </transformer>
                          </transformers>
                      </configuration>
                  </execution>
              </executions>
          </plugin>
      </plugins>
  </build>

Verifiquemos nuestro trabajo hasta ahora ejecutando los siguientes comandos para compilar y ejecutar nuestro proyecto:

1
2
$ mvn package
$ java -jar target/codetriagescraper-1.0-SNAPSHOT.jar

El resultado debería ser Hello World! impreso en la consola. Estamos listos para comenzar a construir nuestro raspador.

Implementación

Antes de implementar nuestro raspador, debemos perfilar el sitio web que vamos a desechar para ubicar los datos que pretendemos desechar.

Para lograr esto, debemos abrir el sitio web Código de clasificación y seleccionar Java Language en un navegador e inspeccionar el código HTML usando las herramientas Dev.

En Chrome, haz clic derecho en la página y selecciona "Inspeccionar" para abrir las herramientas de desarrollo.

El resultado debería verse así:

Página de inicio de CodeTriage{.img-responsive}

Como puede ver, podemos atravesar el HTML e identificar en qué parte del DOM se encuentra la lista de repositorios.

Desde el HTML, podemos ver que los repositorios están contenidos en una lista desordenada cuya clase es repo-list. En su interior están los elementos de la lista que contienen la información del repositorio que requerimos, como se puede ver en la siguiente captura de pantalla:

Lista de repositorios inspeccionados{.img-responsive}

Cada repositorio está contenido en una entrada de elemento de lista cuyo atributo clase es repo-item y la clase incluye una etiqueta de anclaje que alberga la información que necesitamos. Dentro de la etiqueta de anclaje, tenemos una sección de encabezado que contiene el nombre del repositorio y la cantidad de problemas. A esto le sigue una sección de párrafo que contiene la descripción y el nombre completo del repositorio. Esta es la información que necesitamos.

Ahora construyamos nuestro raspador para capturar esta información. Abra el archivo App.java que debería verse un poco así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.codetriage.scraper;

import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;


public class App {

  public static void main(String[] args) {
    System.out.println( "Hello World!" );
  }
}

En la parte superior del archivo, importamos IOException y algunas clases de JSoup que nos ayudarán a analizar los datos.

Para construir nuestro raspador, modificaremos nuestra función principal para manejar las tareas de raspado. Entonces, comencemos imprimiendo el título de la página web en la terminal usando el siguiente código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  public static void main(String[] args) {
     try {
       // Here we create a document object and use JSoup to fetch the website
       Document doc = Jsoup.connect("https://www.codetriage.com/?language=Java").get();

       // With the document fetched, we use JSoup's title() method to fetch the title
       System.out.printf("Title: %s\n", doc.title());

     // In case of any IO errors, we want the messages written to the console
     } catch (IOException e) {
       e.printStackTrace();
     }
  }

Guarde el archivo y ejecute el siguiente comando para probar lo que hemos escrito hasta ahora:

1
$ mvn package && java -jar target/codetriagescraper-1.0-SNAPSHOT.jar

La salida debe ser la siguiente:

Título del documento raspado{.img-responsive}

Nuestro raspador está tomando forma y ahora podemos extraer más datos del sitio web.

Identificamos que todos los repositorios que necesitamos tienen un nombre de clase de repo-item, lo usaremos junto con la función getElementsByClass() de JSoup, para obtener todos los repositorios en la página.

Para cada elemento del repositorio, el nombre del repositorio está contenido en un elemento de encabezado que tiene el nombre de clase repo-item-title, el número de problemas está contenido en un intervalo cuya clase es repo-item-issues. La descripción del repositorio está contenida en un elemento de párrafo cuya clase es repo-item-description, y el nombre completo que podemos usar para generar el enlace de Github se incluye en un lapso con la clase repo-item-full- nombre.

Usaremos la misma función getElementsByClass() para extraer la información anterior, pero el alcance estará dentro de un solo elemento del repositorio. Esa es mucha información de una vez, así que describiré cada paso en los comentarios de la siguiente parte de nuestro programa. Volvemos a nuestro método principal y lo extendemos de la siguiente manera:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 public static void main(String[] args) {
    try {
      // Here we create a document object and use JSoup to fetch the website
      Document doc = Jsoup.connect("https://www.codetriage.com/?language=Java").get();

      // With the document fetched, we use JSoup's title() method to fetch the title
      System.out.printf("Title: %s\n", doc.title());

      // Get the list of repositories
      Elements repositories = doc.getElementsByClass("repo-item");

      /**
       * For each repository, extract the following information:
       * 1. Title
       * 2. Number of issues
       * 3. Description
       * 4. Full name on github
       */
      for (Element repository : repositories) {
        // Extract the title
        String repositoryTitle = repository.getElementsByClass("repo-item-title").text();

        // Extract the number of issues on the repository
        String repositoryIssues = repository.getElementsByClass("repo-item-issues").text();

        // Extract the description of the repository
        String repositoryDescription = repository.getElementsByClass("repo-item-description").text();

        // Get the full name of the repository
        String repositoryGithubName = repository.getElementsByClass("repo-item-full-name").text();

        // The reposiory full name contains brackets that we remove first before generating the valid Github link.
        String repositoryGithubLink = "https://github.com/" + repositoryGithubName.replaceAll("[()]", "");

        // Format and print the information to the console
        System.out.println(repositoryTitle + " - " + repositoryIssues);
        System.out.println("\t" + repositoryDescription);
        System.out.println("\t" + repositoryGithubLink);
        System.out.println("\n");
      }

    // In case of any IO errors, we want the messages written to the console
    } catch (IOException e) {
      e.printStackTrace();
    }
}

Ahora compilemos y ejecutemos nuestro raspador mejorado con el mismo comando:

1
$ mvn package && java -jar target/codetriagescraper-1.0-SNAPSHOT.jar

La salida del programa debería verse así:

Salida de raspado final{.img-responsive}

¡Sí! Nuestro raspador funciona según la captura de pantalla anterior. Hemos logrado escribir un programa simple que extraerá información de CodeTriage para nosotros y la imprimirá en nuestra terminal.

Por supuesto, este no es el lugar de descanso final para esta información, puede almacenarla en una base de datos y mostrarla en una aplicación u otro sitio web o incluso servirla en una API para que se muestre en una extensión de Chrome. Las oportunidades son muchas y depende de usted decidir qué quiere hacer con los datos.

Conclusión

En esta publicación, aprendimos sobre el web scraping usando el lenguaje Java y construimos un scraper funcional usando la biblioteca JSoup simple pero poderosa.

Entonces, ahora que tenemos el raspador y los datos, ¿qué sigue? Hay más en el web scraping de lo que hemos cubierto. Por ejemplo: llenado de formularios, simulación de eventos de usuario como hacer clic, y existen más bibliotecas que pueden ayudarlo a lograr esto. La práctica es tan importante como útil, así que cree más scrapers que cubran nuevos terrenos de complejidad con cada uno nuevo e incluso con diferentes bibliotecas para ampliar su conocimiento. También puede integrar scrapers en sus proyectos existentes o nuevos.

El código fuente del raspador está disponible en Github como referencia. encia.