Lors d'un  exercice j'ai dû mettre en place un web scraper. Je me suis intéressé à plusieurs outils, et méthodes de scraping. Pour ceux qui ne connaissent pas cette pratique il s'agit d'extraire de l'information d'un document. Cela m'a fait réaliser un wrapper d'un scraper Node, puis un projet  permettant d'exécuter du bash sur le résultat d'un scraper de manière récurrente.

Le Web scraping

Je ne vais pas rentrer dans le détail, cela se fait en parcourant le code  d'un document, et en ciblant ce que nous souhaitons récupérer.

Trois choses sont compliquées dans ce procédé  :

  • Cibler la data : on va essayer de rendre cela le plus simple possible en passant par des langages déclaratifs
  • Rester générique : Nous définissons un chemin pour cibler la data. Ce dernier doit être précis tout en demeurant générique. La data que vous allez scanner est amenée à évoluer. Vous devez pouvoir espérer que votre chemin ne soit pas modifié au cours de ces évolutions, ou qu'il soit facile à éditer.
  • Rester humain : Imaginons que vous alliez récupérer de l'information publique sur du Web, vous ne pouvez pas spammer le fournisseur. Vous devez respecter le service en face, timing, etc ... comme pour une API.

Les approches

Il existe deux approches de Web scraping :

  • charger des pages HTML via le protocole http et aller rechercher notre info, le plus rapide.
  • De plus en plus de site sont générés via du Javascript,  il faut donc une autre approche, avec Puppeteer par exemple, afin de simuler une navigation via un browser.

Ferret permet de gérer les deux.

Les outils du moment

Parmi mes recherches, un de mes favoris est Ferret, simple d'utilisation, en GO ...

LET google = DOCUMENT("https://www.google.com/", true)

INPUT(google, 'input[name="q"]', "ferret", 25)
CLICK(google, 'input[name="btnK"]')

WAIT_NAVIGATION(google)

FOR result IN ELEMENTS(google, '.g')
    // filter out extra elements like videos and 'People also ask'
    FILTER TRIM(result.attributes.class) == 'g'
    RETURN {
        title: INNER_TEXT(result, 'h3'),
        description: INNER_TEXT(result, '.st'),
        url: INNER_TEXT(result, 'cite')
    }

Cependant il ne permet pas encore l'utilisation de proxy : Scrapoxy ... (une alternative au proxy serait les fonctions Lambda d'aws).

Ma solution

Pour ma part je devais réaliser la solution en Node. J'ai testé différents scrapers et me suis arrêté sur Osmosis. Facile à utiliser, mais la configuration n'étant pas à mon gout, j'ai réalisé rapidement un wrapper de configuration. Le but était de générer mon scraping à partir d'un JSON. Le projet est disponible en Open Source pour les intéressés, OsmosisWrapper. Ce n'est qu'un exercice et non un projet finalisé.

A step more

Par la suite je me suis demandé comment je pourrais utiliser ce projet facilement. L'idée suivante m'est venue :

  • 1/ je veux scraper n'importe quoi, et le configurer via un simple JSON
  • 2/ je veux récupérer ma data au format JSON dans un Array via une tache CRON (un array car la pluspart du temps nous allons scraper une page contenant un ensemble d'items)
  • 3/ je veux pouvoir exécuter quelque chose sur les items de cet Array, ou choisir de les stocker

Suivant le scraping ciblé, je peux configurer mon wrapper via un JSON, cette étape était complétée. Cependant, suivant ce scraping, je veux aussi pouvoir stocker la data, ou l'utiliser. Ma conclusion a été que le plus simple serait de permettre d'exécuter des commandes bash sur cette data.

J'ai donc repris le précédent projet osmosisWrapper, et créé un projet WebScrapingToShell, qui permet de :

  • paramétrer X configurations de scraper
  • Exécuter X web scraping via des taches cron
  • Exécuter du bash sur la data récupérée (mongo, youtube-dl ...)
  • Choisir que cette exécution soit historisée ou non, et se fasse sur tous les items récupérés ou non

Aujourd'hui il me permet par de récupérer chaque semaine un podcast gratuit sur youtube, afin de le regarder hors ligne :), j'ai laissé un example.

Le projet reste en état de travail, il a été pensé pour moi, et non pour le grand publique. Si toutefois cela vous intéresse, n'hésitez pas à participer, les README.md sont normalement ok.