Quarkus + Picocli: Web scraper para extraer proyectos dependientes en GitHub
Introducción
Durante los pasados meses, mi equipo y yo hemos estado trabajando muy duro para lanzar Eclipse JKube. JKube es el sucesor del ya obsoleto Fabric8 Maven Plugin, y como tal, nuestro principal objetivo ahora mismo es migrar aquellos proyectos que dependent de éste a JKube.
GitHub proporciona muchas estadísticas y métricas, entre ellas la información acerca del grafo de dependencias del proyecto. Esta información es muy valiosa, ya que podemos averiguar qué proyectos (dentro de GitHub) dependen del nuestro. Para nuestro caso de uso de migración de usuarios, esta información es exactamente lo que necesitamos. Desafortunadamente, la API para desarrolladores de GitHub proporciona información sobre dependencias, pero no sobre dependientes.
En este post os mostraré cómo crear un web scraper empleando Picocli y Quarkus para crear un nativo binario que rastreará las dependencias de cualquier proyecto en GitHub.
Nota: Antes de utilizar esta herramienta es muy importante asegurarse que cumples con las normas sobre restricciones de GitHub para el uso de su API y Scraping.
Estructurando la aplicación
El primer paso es iniciar el proyecto, para ello podemos emplear la interfaz de code.quarkus.io para generar un proyecto en blanco. En este caso, solo vamos a necesitar Picocli como dependencia (experimental). Por otro lado, también añadiremos jsoup como dependencia a nuestro pom.xml.
El propósito de la aplicación es rastrear la web de GitHub que está protegida mediante SSL, por ello es necesario que habilitemos el URL protocol https
para el build de GraalVM. El plugin de Maven de Quarkus proporciona una forma muy sencilla de configurar esto mediante la inclusión de build arguments para que GraalVM los utilice.
1<!-- ... -->
2<build>
3 <plugins>
4 <plugin>
5 <groupId>io.quarkus</groupId>
6 <artifactId>quarkus-maven-plugin</artifactId>
7 <executions>
8 <execution>
9 <id>package</id>
10 <goals>
11 <goal>native-image</goal>
12 </goals>
13 <configuration>
14 <dockerBuild>${dockerBuild}</dockerBuild>
15 <additionalBuildArgs>
16 <additionalBuildArg>-H:EnableURLProtocols=https</additionalBuildArg>
17 <additionalBuildArg>-H:EnableURLProtocols=http</additionalBuildArg>
18 </additionalBuildArgs>
19 </configuration>
20 </execution>
21 </executions>
22 </plugin>
23 </plugins>
24</build>
25<!-- ... -->
Quarkus Command line application
A continuación, emplearemos Picocli para que nuestra aplicación sea amigable en la linea de comandos. Hay varias guías que nos ayudarán a conseguirlo, e incluso una sección completa del quarkus-cheat-sheet.
Application Main Class
Nuestra aplicación es muy sencilla y sólo require de un punto de entrada. Con el fin de hacer nuestro código más sencillo y legible, anotaremos nuestra main class con @CommandLine.Command
. Este es el código fuente resultante:
1@CommandLine.Command(name = "github-dependents")
2public class Application implements Runnable {
3
4 private final ScraperService scraperService;
5
6 @CommandLine.Parameters(index = "0", paramLabel = "URL", arity = "1",
7 description = "GitHub URL to the projects dependents list")
8 String dependentsUrl;
9
10 @Inject
11 public Application(ScraperService scraperService) {
12 this.scraperService = scraperService;
13 }
14
15 @Override
16 public void run() {
17 try {
18 new URL(dependentsUrl);
19 scraperService.scrape(dependentsUrl);
20 } catch(MalformedURLException ex) {
21 System.err.printf("URL %s is invalid, please provide a valid URL.%n", dependentsUrl);
22 CommandLine.usage(this, System.out);
23 } catch (Exception e) {
24 System.err.println(e.getMessage());
25 e.printStackTrace();
26 }
27 }
28
29}
Teniendo en cuenta que quiero que la aplicación se pueda emplear para cualquier proyecto de GitHub, es necesario un parámetro de entrada para que el usuario pueda especificar que página quiere analizar. Bajo esta premisa, anotaré el campo dependentsUrl
con @CommandLine.Parameters
. Quiero destacar como Picocli tiene un claro objetivo de permitir que las aplicaciones sean muy amigables para el usuario y proporciona una seria de parámetros para permitir generar comandos de ayuda.
También emplearemos la anotación @Inject
estándar de Java CDI proporcionada por Quarkus para inyectar la clase de servicio que se encargará de hacer el scraping.
Scraper Service
Esta clase contiene toda la lógica encargada de rastrear la página de dependientes en GitHub. La aplicación rastreará de forma recursiva las diferentes páginas devueltas por la consulta y devolverá una representación JSON de cada proyecto dependiente incluyendo la organización, el nombre, la URL, estrellas y forks de cada proyecto.
El servicio también se encarga de verificar que el input del usuario es correcto (URL válida, URL perteneciente a una página de dependientes, etc.). También incluye un algoritmo para reintentar y continuar con el rastreo en el caso en el que GitHub devuelva un HTTP status code 429 - Too Many Requests
.
Ejecutando la aplicación
JVM
Para arrancar la aplicación empleando un Java Virtual Machine (JVM), primero necesitamos compilarla y empaquetarla:
1mvn clean package
Una vez hemos empaquetado la aplicación ya podemos ejecutarla para rastrear los dependientes de un proyecto:
1java -jar target/github-dependents-scraper-uber.jar "https://github.com/eclipse/jkube/network/dependents?package_id=UGFja2FnZS0xMDY0ODYxMDkz"
Binario nativo
Para poder arrancar la aplicación empleando un nativo binario, primero tendremos que compilar, empaquetar y construir una imagen nativa de nuestra aplicación:
1mvn clean package -Pnative
Una vez el binario de nuestra aplicación esté listo, podemos ejecutarlo para rastrear los dependientes de un proyecto:
1./target/github-dependents-scraper-uber "https://github.com/eclipse/jkube/network/dependents?package_id=UGFja2FnZS0xMDY0ODYxMDkz"
El siguiente GIF muestra una demo de cómo utilizar la aplicación:
Conclusión
En este artículo os he mostrado como crear una aplicación de línea de comandos de manera muy sencilla empleando Quarkus y Picocli. Además hemos visto como crear un binario nativo de dicha aplicación sin ningún tipo de sufrimiento empleando las funcionalidades de Quarkus.
El código fuente completo de este post se encuentra en el repositorio de GitHub de github-dependents-scraper
.