Extraction avancée de données web avec Scrapy

Dans ce tutoriel, nous allons créer un outil avancé d’extraction automatisée de données provenant de l’ensemble des pages d’un ou plusieurs sites.

Dans un précédent article, nous avons vu comment extraire simplement des données provenant d’une liste de pages web. Cela marche très bien pour une petite liste faite manuellement, mais devient très laborieux lorsqu’on veut scraper toutes les pages d’un ou plusieurs sites web.

Scrapy dispose heureusement d’un scraper bien particulier, appelé crawler, qui va nous aider dans cette tâche.

Notre premier crawler

Pour commencer, il faut créer un fichier basicrawler.py contenant le code suivant :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class Crawler(CrawlSpider):
    name = 'basicrawler'
    start_urls = ["https://www.byprog.com"]
    allowed_domains = ["byprog.com"]
    rules = (
        # Extract all inner domain links with state "follow"
        Rule(LinkExtractor(), callback='parse_items', follow=True, process_links='links_processor'),
    )

    def links_processor(self,links):
        """
        A hook into the links processing from an existing page, done in order to not follow "nofollow" links
        """
        ret_links = list()
        if links:
            for link in links:
                if not link.nofollow:
                    ret_links.append(link)
        return ret_links

    def parse_items(self, response):
        yield {
            "url": response.url,
            "title": response.css('title::text').extract_first(),
            "description": response.css("meta[name=description]::attr(content)").extract_first()
        }

Ce premier crawler permet d’extraire le titre, la description et l’url de chaque page du domaine byprog.com spécifié dans la variable allowed_domains, en démarrant par la page d’accueil du site https://www.byprog.com spécifiée dans la variable de liste start_urls. Un dictionnaire contenant ces informations est renvoyé pour chaque page.

La variable rules associée à la méthode links_processor() permet de ne récupérer que les liens en no-follow, respectant ainsi les règles d’exploration du site indiquées par le développeur.

Pour le lancer, et sauvegarder les données extraites dans un fichier CSV :

$ scrapy runspider basicrawler.py -o data.csv

Amélioration

Nous allons améliorer le crawler pour qu’il exploite un fichier contenant une liste de pages de démarrage à scraper (une par ligne) plutôt que de devoir modifier le fichier Python à chaque nouveau crawl :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from urllib.parse import urlparse

class Crawler(CrawlSpider):
    name = 'basicrawler'
    start_urls = []
    allowed_domains = []
    rules = (
        # Extract all inner domain links with state "follow"
        Rule(LinkExtractor(), callback='parse_items', follow=True, process_links='links_processor'),
    )

    def __init__(self, *args, **kwargs):
        super(CrawlSpider, self).__init__(*args, **kwargs)
        # read input file that contains domains to crawl
        with open(kwargs.get('file')) as f :
            self.start_urls = f.read().splitlines()
            self.allowed_domains = [urlparse(url).netloc for url in self.start_urls]
        self._compile_rules()

    def links_processor(self,links):
        """
        A hook into the links processing from an existing page, done in order to not follow "nofollow" links
        """
        ret_links = list()
        if links:
            for link in links:
                if not link.nofollow:
                    ret_links.append(link)
        return ret_links

    def parse_items(self, response):
        yield {
            "url": response.url,
            "title": response.css('title::text').extract_first(),
            "description": response.css("meta[name=description]::attr(content)").extract_first()
        }

Le domaine associé à chaque page de démarrage est extrait automatiquement grâce à la librairie urlparse.

Maintenant, pour appeler scrapy, il faut spécifier l’option -a file=nomfichier :

$ scrapy runspider basicrawler.py -a file=list_pages.txt -o data.csv

Le crawler créé dans ce tutoriel est disponible dans mon dépôt github.

Développeur Full-Stack en Freelance, je me donne pour mission d'aider les entreprises et les particuliers pour tous leurs projets et toutes leurs problématiques en informatique. N'hésitez pas à me contacter !