Logo Scrapy

Créer un jeu de données avec Scrapy

L’importance de l’acquisition de données pour le Data Scientist n’est plus à démontrer. Le web étant une source intarissable de données de toutes sortes, le web scraping ou web crawling s’est imposé comme une technique incontournable d’acquisition de données. Scrapy est un framework Python permettant de faciliter les tâches de scraping. Dans cet article nous verrons commant utiliser Scrapy pour créer un jeu de données de textes écrits en langage naturel. Nous commencerons par voir les bases de Scrapy puis nous créerons un jeu de données constitué de commentaires de films sur Allociné.

Qu’est-ce que Scrapy ?

Scrapy est un framework Python qui sert à faire du web scraping. Scrapy est adapté aux grands projets de web scraping. En effet, les projets Scrapy ont une structure assez clair qui facilite la maintenance et le passage à l’échelle. En plus, le framework offre une certaine rapidité due à l’asynchronisme des requêtes (Scrapy utilise Twisted). Bref! Je vous propose de jeter un œil à la structure de Scrapy.

Data flow de Scrapy

L’élément central du système est l’engine : tout passe par lui. Le flux de données lors du scraping est le suivant :

  1. Les Requêtes sont envoyées à l’engine par le spider ( = Code contenant l’agencement des Requêtes et fonctions de parsing).
  2. L’engine ordonnance les Requêtes dans le scheduler et lui demande (au scheduler) les prochaines Requêtes.
  3. Le scheduler donne les prochaines Requêtes à l’engine.
  4. L’engine envoie les prochaines Requêtes au downloader via son middleware.
  5. À la fin du téléchargement, le downloader envoie la Réponse à l’engine toujours en passant par le middleware.
  6. L’engine envoie la Réponse au spider pour traitement via le middleware du spider.
  7. Le spider traite la Réponse et retourne les items scrapés ainsi que les prochaines Requêtes à suivre à l’engine.
  8. L’engine envoie les items aux item pipelines puis passe les prochaines Requêtes à suivre au scheduler.
  9. Puis la première étape reprend jusqu’à ce qu’il n’y ait plus de requêtes dans le scheduler.

Ce flux est simple et décrit bien le rôle de chacun des blocs : spider, engine, scheduler, downloader, item pipelines et middleware. Dans la suite, nous nous intéresserons qu’aux spiders et aux item pipelines à travers notre projet démo.

Projet de démonstration

Description du projet

Le but de ce projet est de créer un jeu de données contenant des commentaires laissés par les cinéphiles sur Allociné. On veut, comme résultat, un fichier CSV ayant les colonnes : title, review, stars.

Title étant le titre du film, review le commentaire et stars le nombre d’étoiles (sur 5) laissé par le cinéphile.

Création du projet

Avant de créer le projet, il faut s’assurer d’avoir Scrapy installé. Utilisez la ligne de commande suivante pour l’installer :

pip install scrapy

Ou avec conda :

conda install -c conda-forge scrapy

Maintenant nous pouvons générer notre projet avec la ligne de commande suivante :

scrapy startproject datasets

« datasets » étant le nom du projet (vous pouvez l’appeler comme vous voulez).

Un dossier datasets est créé.

datasets
├── datasets
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       └── __init__.py
└── scrapy.cfg

La racine du projet contient le fichier scrapy.cfg qui est un fichier de configuration qui contient des variables telles que le nom du module qui contient les paramètres du projet et d’autres variables de déploiement.

La racine contient un autre dossier datasets qui contient le projet en lui même. Ce dossier qui est un package Python (d’où le __init__.py) contient le package spiders (qui pour l’heure est vide) ainsi que les modules : items, middlewares, pipelines et settings.

Le module middlewares contient les middlewares du projet. Les middlewares pour les spiders et pour le downloader sont créés par défaut. Mais il est possible d’en créer un nouveau.

Le module items définit les modèles des données que doivent respecter les items scrapés. Un item est un objet Python style « clé-valeur » qui représente un échantillon élémentaire du jeu de données. Si on considère notre jeu de données comme étant au format CSV, un item serait une ligne de ce CSV.

Le module pipelines contient les pipelines pour chaque item (modèle de données) défini dans le module items. Ces pipelines permettent de faire des traitements sur l’ensemble des items scrapés. Cela est pratique pour faire du nettoyage de données.

Le fichier settings.py contient des variables qui sont utilisées par l’engine et le reste du projet.

Création d’un Item

On commence par créer le modèle de données qu’on veut. On fait cela en créant une classe qui hérite de « scrapy.Item » et précisant les 3 champs qu’on souhaite avoir.

import scrapy

class ReviewsAllocineItem(scrapy.Item):
    
    title = scrapy.Field() # Le titre du film
    review = scrapy.Field() # Le commentaire
    stars = scrapy.Field() # La note donnée au film par l'auteur du commentaire

Création d’un Spider

Le point d’entrée du projet est le spider. Nous allons créer un nouveau fichier dans le dossier spiders que l’on va appeler « reviews_allocine.py« . Évidemment, vous êtes libre de l’appeler comme vous voulez.

Dans ce fichier, on va créer le spider à proprement dit qui n’est rien d’autre qu’une classe héritant de la classe Spider de scrapy.

from scrapy import Request, Spider

class SpiderReviewsAllocine(Spider):
    # Nom du spider
    name = "reviews_allocine"
    # URL de la page à scraper
    url = "https://www.allocine.fr/film/fichefilm-10568/critiques/spectateurs/"

    def start_requests(self):
        yield Request(url=url, callback=self.parse_films)

    def parse_films(self, response):
        print("Called back")
        pass

On donne un nom au spider pour pouvoir le lancer de la façon suivante :

scrapy crawl reviews_allocine

La fonction start_requests du spider est le point d’entrée du spider. La seule ligne de cette fonction veut dire : émettre une requête (GET par défaut) sur l’URL url et appeler la fonction self.parse_films avec la réponse de la requête comme paramètre. La réponse de la requête est une instance de la classe Response et contient le code source de la page dont l’URL est url. La prochaine étape est donc de parser le code source (HTML) dans la fonction parse_films.

Parsing du HTML

Pour parser le code de la page, il nous faut l’inspecter. Pour ça il faut ouvrir la page en question dans un navigateur, faire clique droit puis cliquer sur inspecter. Cela doit ressembler à ça :

Inspection de la page pour scraping

Le menu d’inspection permet de naviguer à travers les éléments HTML et de faire la correspondance avec leurs affichages. Notre but ici sera de trouver les bons sélecteurs pour sélectionner les blocs de commentaires puis le texte du commentaire et le nombre d’étoiles pour chaque bloc. En cherchant un peu on voit que les blocs de commentaire sont dans des balises div :

<div>class="hred review-card cf" id="review_4644768">
...
</div>

On va donc sélectionner, avec le sélecteur css de Scrapy, tous les div de la page qui ont comme classe « review-card ».

review_blocks = response.css("div.review-card")

Ensuite, il faut trouver l’élément HTML qui contient le texte du commentaire et le nombre d’étoiles et les récupérer pour chaque review_blocks.

review_blocks = response.css("div.review-card")
for review_card in review_blocks:
    review = review_card.css("div.content-txt::text").extract_first()
    stars = review_card.css("span.stareval-note::text").extract_first()
    # Pour avoir la note (nombre d'étoiles) en float
    stars = float(stars.replace(",", "."))

On oublie pas le titre du film qui lui est le même pour tous les commentaires de la page.

title = response.css("a.titlebar-link::text").extract_first()

Finalisation du Spider

La dernière étape pour avoir de la donnée est de retourner des items. Cette étape est essentielle. Et elle consiste juste à créer une instance de ReviewsAllocineItem à chaque commentaire, à renseigner chacun de ses champs avec les valeurs scrapées puis les « yield ». Notre fichier spider final ressemble maintenant à ça :

from scrapy import Request, Spider
from ..items import ReviewsAllocineItem

class SpiderReviewsAllocine(Spider):
    # Nom du spider
    name = "reviews_allocine"
    # URL de la page à scraper
    url = "https://www.allocine.fr/film/fichefilm-10568/critiques/spectateurs/"

    def start_requests(self):
        yield Request(url=url, callback=self.parse_films)

    def parse_films(self, response):
        title = response.css("a.titlebar-link::text").extract_first()
        review_blocks = response.css("div.review-card")
        for review_card in review_blocks:
            review = review_card.css("div.content-txt::text").extract_first()
            stars = review_card.css("span.stareval-note::text").extract_first()
            
            # Pour avoir la note (nombre d'étoiles) en float
            stars = float(stars.replace(",", "."))

            item = ReviewsAllocineItem()

            item['title'] = title
            item['stars'] = stars
            item['review'] = review

            yield item

Pour lancer notre spider et avoir les données scrapées dans un fichier CSV, on fait la commande suivante :

scrapy crawl reviews_allocine -o reviews_allocine.csv

Et voilà!

Conclusion

Scrapy est un framework Python qui facilite énormément les tâches de web scraping. Dans cet article, nous avons vu le principe global de fonctionnement de Scrapy. Puis nous avons vu un exemple de web crawling avec le framework qui nous a permis de créer un jeu de données. Le but de cet article était de faire une introduction au framework Scrapy. Pour une version plus complète du projet de scraping des commentaires d’Allociné, cliquez ici.

Scrapy est un outil robuste et assez facile à prendre en main. Si vous faites souvent de l’extraction de données sur le web, cet outil peut sûrement vous simplifier la vie.

N’hésitez pas à laisser des commentaires.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.