entrepreneur-interet-general / OpenScraper

An open source webapp for scraping: towards a public service for webscraping
http://www.cis-openscraper.com/
MIT License
92 stars 22 forks source link

Modifier masterspider.py risque de casser des spiders existantes #61

Open DavidBruant opened 5 years ago

DavidBruant commented 5 years ago

Aujourd'hui, une spider est constituée de :

Quand on modifie la configuration ou masterspider.py (ou une dépendance de masterspider.py), on prend le risque de casser l'araignée en question

A priori, une fois que la configuration d'une spider est faite, il n'y a pas de raison de la modifier sauf si le site visé a changé

Par contre, masterspider.py est changé à chaque fois qu'un nouveau cas de site web est trouvé ou à chaque fois qu'un bug lié à ce fichier est trouvé. Quand un tel changement a lieu, il n'est actuellement pas possible facilement de savoir si l'on a cassé une spider existante. On peut seulement l'espérer. Actuellement, la manière la plus efficace de vérifier que l'on n'a rien cassé est de tester à la main toutes les configurations de spider existantes avec le nouveau code. La détection d'un bug suite à cette vérification peut amener à un nouveau changement sur masterspider.py...

masterspider.py a changé et va encore changer et encore dans le futur, de manières que l'on n'a pas encore prévu

JulienParis commented 5 years ago

Les modifications sur masterspider.py sont effectivement à faire avec précaution toutefois il y a quelques pare-feux en place dans la structure même de cette fonction pour éviter de provoquer des dégâts.

architecture générale du fichier masterspider.py

masterspider.py contient plusieurs objects, les principaux étant la fonction run_generic_spider et une classe GenericSpider.

Se baser sur une instanciation générique (GenericSpider) de la classe elle-même générique Spiderde Scrappy permet à la fois de bénéficier des fonctions de scrappy 'out the box' (parsing, requests, itérations, configuration des robots, etc...) et de sa logique de 'pipelines' pour bien différencier ce qui est de l'ordre du crawling et ce qui est de l'ordre de la sauvegarde dans la BDD, les pipelines.

les 'pare-feux' actuels pour éviter toute catastrophe lors de modifications de masterspider.py

Pour éviter qu'un changement dans masterspider.py rende une configuration pré-existante incompatible et caduque les précautions que j'ai prises jusqu'à maintenant sont les suivantes :

1. la structure générale cohérente avec la logique de Scrappy

Les nouvelles fonctions sont écrites à part : soit en 'dehors' de la classe GenericSpider (comme flattenSpiderConfig, clean_xpath_for_reactive, etc...), soit à l'intérieur de la classe (comme get_next_page). Les fonctions 'en dehors' pourront plus tard être mises dans un autre fichier (on cleanerait 150 lignes de codes)

Une fois l'url_start chargée on peut à volonté et en fonction de la configuration du spider :

2. le contenu de la fonction "interne" parse

La fonction "interne" de la classe GenericSpider jouant un rôle centrale est la fonction parse, dont a besoin l'instance de Spider, c'est dans cette fonction que jouent toutes les conditions... Pour bien séparer les besoins parse possède trois types de comportements bien distingués qui s'activent en fonction de la configuration du scraper :

  1. parser une API (if self.spider_config_flat["parse_api"] == True) ;
  2. parser un site non réactif (if self.spider_config_flat["parse_reactive"] == False)
  3. parser un site réactif (else... )

Les principaux fonctionnements de GenericSpider sont basés sur des cliquets (true|false), et ce "triage" initial (parser une API | un site non réactif | un site réactif) est assez central : en effet ces 3 possibilités de base actionnent chacune des outils spécifiques : pures requêtes et parsing de JSON dans le 1er cas, bibliothèque de requête/parsing de scrappy (urllib) dans le 2eme cas, Selenium dans le 3eme... Les cas particuliers de sites (réactifs ou pas, infinite scroll ou pas, pages détaillées ou pas, API ou pas... ) sont au final autant de cliquets à activer ou pas à l'intérieur de la fonction parse ou ailleurs dans GenericSpider. De fait ces conditions sont bien isolées les unes des autres et on choisit de les activer dans la configuration du spider côté client.

3. les commentaires

J'ai été peu avare sur les commentaires et les logs de debug dans le fichier masterspider.py, certains logs étant commentés dans le but de pouvoir re-débugger plus simplement en phase de développement. (logs et commentaires expliquant aussi que ce fichier compte autant de lignes de code)

4. le modèle de données des spiders

Les spiders ont un modèle de données relativement stable, bien séparés dans chaque document et à l'image de ce qu'on voir sur l'interface graphique figurent :

Les valeurs par défaut de tous les spiders de la BDD sont initialisées et vérifiées à chaque fois qu'on relance l'appli, au niveau de la fonction de run main.py Donc si on veut ajouter de nouvelles conditions paramétrables par l'utilisateur via l'interface, et que ces nouvelles conditions n'existent pas auparavant pour d'autres spiders de la BDD on peut injecter des valeurs par défaut qu'on peut choisir d'activer ou pas ensuite dans parse de GenericSpider


Conclusion temporaire

Sans que cela ne réduise à zéro les risques ces précautions m'ont servi à plusieurs reprises à éviter de mélanger ce qui était de l'ordre de la configuration de spider (très flexible), des variables à disposition pour jouer avec la classe Spider de Scrappy, et enfin ce qui est de l'ordre du plus délicat, cad le contenu de la fonction parse de GenericSpider

On peut pour le moment changer les configurations de spider à sa guise (voire même utiliser la partie "notes" de la config pour garder en mémoire des éléments de config). Le problème n'est pas tant de "casser des spiders" (OpenScraper est même fait pour que ce soit simple de casser, refaire), mais bien lorsqu'il s'agit d'ajouter de nouvelles fonctionnalités (type infinite scroll qui n'est activable que lorsque qu'on utilise Selenium) et que ces nouvelles fonctionnalités demandent d'ajouter de nouvelles variables de configuration "dans le dur"

Un travail de factorisation et d'externalisation des fonctions critiques est certainement nécessaire afin d'épurer GenericSpider, toutefois le but de cette classe (nodale dans OpenScraper) est bien de pouvoir s'adapter à la configuration de spider proposée par le client, et de gérer les conditions|erreurs|variables sans faire bugger l'appli dans son ensemble.

JulienParis commented 5 years ago

modif de masterspider.py (+main.py, +settings_corefields.py) pour gérer le nouveau cas d'infinite scroll sans casser la logique générale de masterspider ni le bon fonctionnement des spiders précédents : https://github.com/entrepreneur-interet-general/OpenScraper/commit/fe582d0dfe9c8f7c8f1c9091365cb36f8e18e060