Retour au blog
Guides
Mihai Maxim30 janvier 202314 min de lecture

Le web scraping avec Scrapy : la méthode simple

Le web scraping avec Scrapy : la méthode simple

Le web scraping avec Scrapy

Scrapy est une puissante bibliothèque Python permettant d'extraire des données depuis des sites web. Elle est rapide, efficace et facile à utiliser – croyez-moi, je l'ai testée. Que vous soyez data scientist, développeur ou simplement passionné par les données, Scrapy a quelque chose à vous offrir. Et surtout, c'est gratuit et open source.

Les projets Scrapy s'accompagnent d'une structure de fichiers qui vous aide à organiser votre code et vos données. Cela facilite la création et la maintenance des scrapers web, donc ça vaut vraiment le coup d'y penser si vous prévoyez de vous lancer sérieusement dans le web scraping. Le web scraping avec Scrapy, c'est comme avoir un assistant (virtuel, certes) à vos côtés lorsque vous vous lancez dans l'extraction de données.

Ce que nous allons créer

Quand on apprend une nouvelle compétence, c'est une chose de lire des articles à ce sujet, mais c'en est une autre de passer à la pratique. C'est pourquoi nous avons décidé de créer ensemble un scraper tout au long de ce guide. C'est le meilleur moyen de comprendre concrètement comment fonctionne le web scraping avec Scrapy.

Alors, que allons-nous créer exactement ? Nous allons créer un scraper qui extraira les définitions de mots du site web Urban Dictionary. C'est une cible amusante pour le scraping et cela contribuera à rendre le processus d'apprentissage plus agréable. Notre scraper sera simple : il renverra les définitions de divers mots trouvés sur le site web Urban Dictionary. Nous utiliserons les fonctionnalités intégrées de Scrapy pour sélectionner et extraire des données à partir de documents HTML afin de récupérer les définitions dont nous avons besoin.

Alors, c'est parti ! Dans la section suivante, nous passerons en revue les prérequis dont vous aurez besoin pour suivre ce tutoriel. À bientôt !

Prérequis

Avant de nous lancer dans la création de notre scraper, vous devrez effectuer quelques configurations. Dans cette section, nous verrons comment installer Scrapy et configurer un environnement virtuel pour notre projet. La documentation de Scrapy recommande d'installer Scrapy dans un environnement virtuel dédié. Cela vous permettra d'éviter tout conflit avec les paquets de votre système.

J'utilise Scrapy sur Ubuntu 22.04.1 WSL (Windows Subsystem for Linux), je vais donc configurer un environnement virtuel pour ma machine.

Je vous encourage à lire le chapitre « Comprendre la structure des dossiers » pour bien comprendre les outils avec lesquels nous travaillons. Jetez également un œil au chapitre « Scrapy Shell », cela facilitera grandement votre expérience de développement.

Configuration d'un environnement virtuel Python

Pour configurer un environnement virtuel Python sous Ubuntu, vous pouvez utiliser la commande mkvirtualenv. Assurez-vous d'abord que virtualenv et virtualenvwrapper sont bien installés :

$ sudo apt-get install virtualenv virtualenvwrapper

Ajoutez ces lignes à la fin de votre fichier .bashrc :

export WORKON_HOME=$HOME/.virtualenvs

export PROJECT_HOME=$HOME/Deve

export VIRTUALENVWRAPPER_PYTHON='/usr/bin/python3'

source /usr/local/bin/virtualenvwrapper.sh

J'ai utilisé Vim pour modifier le fichier, mais vous pouvez choisir l'éditeur de votre choix :

vim ~/.bashrc

// Use ctr + i to enter insert mode, use the down arrow to scroll to the bottom of the file.

// Paste the lines at the end of the file.

// Hit escape to exit insert mode, type wq and hit enter to save the changes and exit Vim.

Créez ensuite un nouvel environnement virtuel avec mkvirtualenv :

$ mkvirtualenv scrapy_env

Vous devriez maintenant voir (scrapy_env) s'afficher au début de la ligne de votre terminal.

Pour quitter l'environnement virtuel, tapez $ deactivate

Pour revenir à l'environnement virtuel scrapy_env, tapez $ workon scrapy_env

Installation de Scrapy

Vous pouvez installer Scrapy à l'aide du gestionnaire de paquets pip :

$ pip install scrapy

Cela installera la dernière version de Scrapy.

Vous pouvez créer un nouveau projet à l'aide de la commande scrapy startproject :

$ scrapy startproject myproject

Cela initialisera un nouveau projet Scrapy appelé « myproject ». Il devrait contenir la structure de projet par défaut.

Comprendre la structure des dossiers

Voici la structure de projet par défaut :

myproject

├── myproject

│   ├── __init__.py

│   ├── items.py

│   ├── middlewares.py

│   ├── pipelines.py

│   ├── settings.py

│   └── spiders

│       └── __init__.py

└── scrapy.cfg

items.py

items.py est un modèle pour les données extraites. Ce modèle sera utilisé pour stocker les données que vous extrayez du site web.

Exemple :

import scrapy

class Product(scrapy.Item):

    name = scrapy.Field()

    price = scrapy.Field()

    description = scrapy.Field()

Ici, nous avons défini un élément appelé Product. Il peut être utilisé par un Spider (voir /spiders) pour stocker des informations sur le nom, le prix et la description d'un produit.

/spiders

/spiders est un dossier contenant des classes Spider. Dans Scrapy, les Spiders sont des classes qui définissent comment un site web doit être exploré.

Exemple :

import scrapy

from myproject.items import Product

class MySpider(scrapy.Spider):

    name = 'myspider'

    start_urls = ['<example_website_url>']

    

    def parse(self, response):

        # Extract the data for each product

        for product_div in response.css('div.product'):

            product = Product()

            product['name'] = product_div.css('h3.name::text').get()

            product['price'] = product_div.css('span.price::text').get()

            product['description'] = product_div.css('p.description::text').get()

            yield product

Le « spider » parcourt les start_urls, extrait le nom, le prix et la description de tous les produits trouvés sur les pages (à l'aide de sélecteurs CSS) et stocke les données dans l'élément Product (voir items.py). Il « rend » ensuite ces éléments, ce qui amène Scrapy à les transmettre au composant suivant dans le pipeline (voir pipelines.py).

Yield est un mot-clé en Python qui permet à une fonction de renvoyer une valeur sans se terminer. Au lieu de cela, elle produit la valeur et suspend l'exécution de la fonction jusqu'à ce que la valeur suivante soit demandée.

Par exemple :

def count_up_to(max):

    count = 1

    while count <= max:

        yield count

        count += 1

for number in count_up_to(5):

    print(number)

// returns 1 2 3 4 5 (each on a new line)  

pipelines.py

Les pipelines sont chargés de traiter les éléments (voir items.py et /spiders) extraits par les spiders. Vous pouvez les utiliser pour nettoyer le code HTML, valider les données, les exporter vers un format personnalisé ou les enregistrer dans une base de données.

Exemple :

import pymongo

class MongoPipeline(object):

    def __init__(self):

        self.conn = pymongo.MongoClient('localhost', 27017)

        self.db = self.conn['mydatabase']

        self.product_collection = self.db['products']

        self.other_collection = self.db['other']

        

    def process_item(self, item, spider):

        if spider.name == 'product_spider':

            //insert item in the product_collection

        elif spider.name == 'other_spider':

            //insert item in the other_collection

        return item

Nous avons créé un pipeline nommé MongoPipeline. Il se connecte à deux collections MongoDB (product_collection et other_collection). Le pipeline reçoit des éléments (voir items.py) provenant des spiders (voir /spiders) et les traite dans la fonction process_item. Dans cet exemple, la fonction process_item ajoute les éléments à leurs collections respectives.

settings.py

settings.py stocke divers paramètres qui contrôlent le comportement du projet Scrapy, tels que les pipelines, les middlewares et les extensions à utiliser, ainsi que les paramètres relatifs à la manière dont le projet doit gérer les requêtes et les réponses.

Par exemple, vous pouvez l'utiliser pour définir l'ordre d'exécution des pipelines (voir pipelines.py) :

ITEM_PIPELINES = {

    'myproject.pipelines.MongoPipeline': 300,

    'myproject.pipelines.JsonPipeline': 302,

}

// MongoPipeline will execute before JsonPipeline, because it has a lower order number(300)

Ou définir un conteneur d'exportation :

FEEDS = {

    'items': {'uri': 'file:///tmp/items.json', 'format': 'json'},

}

// this will save the scraped data to a items.json

scrappy.cfg

scrapy.cfg est le fichier de configuration contenant les paramètres principaux du projet.

[settings] 

default = [name of the project].settings  

[deploy] 

#url = http://localhost:6800/ 

project = [name of the project]

middlewares.py

Il existe deux types de middlewares dans Scrapy : les middlewares de téléchargement et les middlewares de spider.

Les middlewares de téléchargement sont des composants qui peuvent être utilisés pour modifier les requêtes et les réponses, gérer les erreurs et implémenter une logique de téléchargement personnalisée. Ils se situent entre le spider et le téléchargeur Scrapy.

Les middlewares de spider sont des composants pouvant être utilisés pour implémenter une logique de traitement personnalisée. Ils se situent entre le moteur et le spider.

Le Shell Scrapy

Avant de nous lancer dans l'aventure passionnante de la mise en œuvre de notre scraper Urban Dictionary, nous devons d'abord nous familiariser avec le Shell Scrapy. Cette console interactive nous permet de tester notre logique de scraping et de voir les résultats en temps réel. C'est comme un bac à sable virtuel où nous pouvons nous amuser et affiner notre approche avant de lâcher notre Spider sur le Web. Croyez-moi, cela vous fera gagner beaucoup de temps et vous évitera bien des maux de tête à long terme. Alors, amusons-nous un peu et découvrons le Scrapy Shell.

Ouvrir le shell

Pour ouvrir le Scrapy Shell, vous devez d'abord vous rendre dans le répertoire de votre projet Scrapy dans votre terminal. Ensuite, il vous suffit d'exécuter la commande suivante :

scrapy shell

Cela ouvrira le Shell Scrapy et vous verrez apparaître une invite où vous pourrez saisir et exécuter des commandes Scrapy. Vous pouvez également passer une URL en argument à la commande du shell pour extraire directement une page web, comme ceci :

scrapy shell <url>

Par exemple :

scrapy shell https://www.urbandictionary.com/define.php?term=YOLO

Renverra (dans l'objet response) le code HTML de la page web contenant les définitions du mot YOLO (dans l'Urban Dictionary).

Sinon, une fois dans le shell, vous pouvez utiliser la commande fetch pour récupérer une page web.

fetch('https://www.urbandictionary.com/define.php?term=YOLO')

Vous pouvez également démarrer le shell avec le paramètre nolog pour qu'il n'affiche pas les journaux :

scrapy shell --nolog

Utilisation du shell

Dans cet exemple, j'ai récupéré l'URL « https://www.urbandictionary.com/define.php?term=YOLO » et enregistré le code HTML dans le fichier test_output.html.

(scrapy_env) mihai@DESKTOP-0RN92KH:~/myproject$ scrapy shell --nolog

[s] Available Scrapy objects:

[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)

[s]   crawler    <scrapy.crawler.Crawler object at 0x7f1eef80f6a0>

[s]   item       {}

[s]   settings   <scrapy.settings.Settings object at 0x7f1eef80f4c0>

[s] Useful shortcuts:

[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)

[s]   fetch(req)                  Fetch a scrapy.Request and update local objects

[s]   shelp()           Shell help (print this help)

[s]   view(response)    View response in a browser

>>> response // response is empty

>>> fetch('https://www.urbandictionary.com/define.php?term=YOLO')

>>> response

<200 https://www.urbandictionary.com/define.php?term=Yolo>

>>> with open('test_output.html', 'w') as f:

...      f.write(response.text)

...

118260

Examinons maintenant test_output.html et identifions les sélecteurs dont nous aurions besoin pour extraire les données pour notre scraper Urban Dictionary.

Browser developer tools showing HTML for a dictionary entry with meaning, example text, and contributor metadata

Nous pouvons observer que :

  • Chaque conteneur de définition de mot possède la classe « definition ».
  • La signification du mot se trouve à l'intérieur de la balise div portant la classe « meaning ».
  • Les exemples pour le mot se trouvent à l'intérieur de la balise div portant la classe « example ».
  • Les informations concernant l'auteur et la date de publication se trouvent dans la balise div portant la classe « contributor ».

Testons maintenant quelques sélecteurs dans le shell Scrapy :

Pour obtenir des références à tous les conteneurs de définition, nous pouvons utiliser des sélecteurs CSS ou XPath :

Vous pouvez en savoir plus sur les sélecteurs XPath ici : https://www.webscrapingapi.com/the-ultimate-xpath-cheat-sheet

definitions = response.css('div.definition')
definitions = response.xpath('//div[contains(@class,"definition")]')

Nous devons extraire la signification, l'exemple et les informations relatives à l'article de chaque conteneur de définition. Testons quelques sélecteurs avec le premier conteneur :

>>> first_def = definitions[0]

>>> meaning = first_def.css('div.meaning').xpath(".//text()").extract()

>>> meaning

['Yolo ', 'means', ', '', 'You Only Live Once', ''.']

>>> meaning = "".join(meaning)

>>> meaning

'Yolo means, 'You Only Live Once'.'

>>> example = first_def.css('div.example').xpath(".//text()").extract()

>>> example = "".join(example)

>>> example

'"Put your seatbelt on." Jessica said.\r"HAH, YOLO!" Replies Anna.\r(They then proceed to have a car crash. Long story short...Wear a seatbelt.)'

>>> post_data = first_def.css('div.contributor').xpath(".//text()").extract()

>>> post_data

['by ', 'Soy ugly', ' April 24, 2019']

En utilisant le shell Scrapy, nous avons pu trouver rapidement un sélecteur général qui répond à nos besoins.

definition.css('div.<meaning|example|contributor>').xpath(".//text()").extract() 

// returns an array with all the text found inside the <meaning|example|contributor>

ex: ['Yolo ', 'means', ', '', 'You Only Live Once', ''.']

Pour en savoir plus sur les sélecteurs Scrapy, consultez la documentation. https://docs.scrapy.org/en/latest/topics/selectors.html

Implémentation du scraper Urban Dictionary

Bravo ! Maintenant que vous maîtrisez l'utilisation du shell Scrapy et que vous comprenez le fonctionnement interne d'un projet Scrapy, il est temps de se plonger dans la mise en œuvre de notre scraper Urban Dictionary. À ce stade, vous devriez vous sentir en confiance et prêt à vous attaquer à la tâche d'extraire toutes ces définitions de mots hilarantes (et parfois discutables) du Web. Alors sans plus attendre, commençons à construire notre scraper !

Définition d&#x27;un Item

Tout d'abord, nous allons implémenter un Item : (voir items.py)

class UrbanDictionaryItem(scrapy.Item):

    meaning = scrapy.Field()

    author = scrapy.Field()

    date = scrapy.Field()

    example = scrapy.Field()

Cette structure contiendra les données extraites par le Spider.

Définition d&#x27;un Spider

Voici comment nous allons définir notre Spider (voir /spiders) :

import scrapy

from ..items import UrbanDictionaryItem

class UrbanDictionarySpider(scrapy.Spider):

name = 'urban_dictionary'

start_urls = ['https://www.urbandictionary.com/define.php?term=Yolo']

def parse(self, response):

définitions = response.css('div.definition')

for definition in definitions:

item = UrbanDictionaryItem()

item['meaning'] = definition.css('div.meaning').xpath(".//text()").extract()

item['example'] = definition.css('div.example').xpath(".//text()").extract()

auteur = définition.css('div.contributor').xpath(".//text()").extract()

item['date'] = author[2]

item['author'] = author[1]

yield item

Pour exécuter le spider urban_dictionary, utilisez la commande suivante :

scrapy crawl urban_dictionary

// the results should appear in the console (most probably at the top of the logs)

Création d&#x27;un pipeline

À ce stade, les données ne sont pas nettoyées.

{'author': 'Soy ugly',

 'date': ' April 24, 2019',

 'example': ['“Put your ',

             'seatbelt',

             ' on.” Jessica said.\n',

             '“HAH, YOLO!” Replies Anna.\n',

             '(They then proceed to have a ',

             'car crash',

             '. ',

             'Long story short',

             '...Wear a seatbelt.)'],

 'meaning': ['Yolo ', 'means', ', ‘', 'You Only Live Once', '’.']}

Nous voulons modifier les champs « example » et « meaning » pour qu'ils contiennent des chaînes de caractères, et non des tableaux. Pour ce faire, nous allons écrire un pipeline (voir pipelines.py) qui transformera les tableaux en chaînes de caractères en concaténant les mots.

class SanitizePipeline:

    def process_item(self, item, spider):

        # Sanitize the 'meaning' field

        item['meaning'] = "".join(item['meaning'])

        # Sanitize the 'example' field

        item['example'] = "".join(item['example'])

       

        # Sanitize the 'date' field

        item['date'] = item['date'].strip() 

        return item

 //ex: ['Yolo ', 'means', ', ‘', 'You Only Live Once', '’.'] turns to 

       'Yolo means, ‘You Only Live Once’.'

Activation du pipeline

Une fois le pipeline défini, nous devons l'activer. Si nous ne le faisons pas, nos objets UrbanDictionaryItem ne seront pas nettoyés. Pour ce faire, ajoutez-le à votre fichier settings.py (voir settings.py) :

 ITEM_PIPELINES = {

     'myproject.pipelines.SanitizePipeline': 1,

 }

Pendant que vous y êtes, vous pouvez également spécifier un fichier de sortie dans lequel les données extraites seront placées.

 FEEDS = {

     'items': {'uri': 'file:///tmp/items.json', 'format': 'json'},

 }

  // this will put the scraped data in an items.json file.

Facultatif : implémentation d&#x27;un middleware proxy

Le web scraping peut s'avérer fastidieux. L'un des principaux problèmes que nous rencontrons souvent est que de nombreux sites web nécessitent le rendu JavaScript pour afficher pleinement leur contenu. Cela peut nous poser d'énormes problèmes en tant que scrapers, car nos outils ne sont souvent pas capables d'exécuter du JavaScript comme le ferait un navigateur web classique. Cela peut entraîner l'extraction de données incomplètes, ou pire, l'interdiction de notre adresse IP par le site web pour avoir effectué trop de requêtes en peu de temps.

Notre solution à ce problème est WebScrapingApi. Grâce à notre service, il vous suffit d'envoyer des requêtes à l'API et celle-ci se chargera de tout le travail fastidieux à votre place. Elle exécutera le JavaScript, fera tourner les proxys et gérera même les CAPTCHA, vous garantissant ainsi de pouvoir scraper facilement même les sites web les plus récalcitrants.

Un middleware proxy transmettra chaque requête de récupération effectuée par Scrapy au serveur proxy. Le serveur proxy effectuera alors la requête à notre place, puis renverra le résultat.

Tout d'abord, nous allons définir une classe ProxyMiddleware dans le fichier middlewares.py.

import base64

class ProxyMiddleware:

    def process_request(self, request, spider):

        # Set the proxy for the request

        request.meta['proxy'] = "http://proxy.webscrapingapi.com:80"

        request.meta['verify'] = False

        # Set the proxy authentication for the request

        proxy_user_pass =  "webscrapingapi.proxy_type=residential.render_js=1:<API_KEY>"

        encoded_user_pass = base64.b64encode(proxy_user_pass.encode()).decode()

        request.headers['Proxy-Authorization'] = f'Basic {encoded_user_pass}'

Dans cet exemple, nous avons utilisé le serveur proxy WebScrapingApi.

webscrapingapi.proxy_type=residential.render_js=1 correspond au nom d'utilisateur d'authentification du proxy, <API_KEY> au mot de passe.

Vous pouvez obtenir une clé API_KEY gratuite en créant un nouveau compte sur https://www.webscrapingapi.com/

Une fois le ProxyMiddleware défini, il suffit de l'activer en tant que DOWNLOAD_MIDDLEWARE dans le fichier settings.py.

DOWNLOADER_MIDDLEWARES = {

    'myproject.middlewares.ProxyMiddleware': 1,

}

Et voilà, c'est tout. Comme vous pouvez le constater, le web scraping avec Scrapy peut considérablement simplifier notre travail.

Conclusion

Nous avons réussi ! Nous avons créé un scraper capable d’extraire des définitions d’Urban Dictionary et nous avons beaucoup appris sur Scrapy en cours de route. De la création d’éléments personnalisés à l’utilisation de middlewares et de pipelines, nous avons démontré à quel point le web scraping avec Scrapy peut être puissant et polyvalent. Et le meilleur dans tout ça ? Il reste encore tant à découvrir.

Félicitations pour avoir suivi ce parcours jusqu'au bout avec moi. Vous devriez désormais vous sentir en confiance pour vous attaquer à n'importe quel projet de web scraping qui se présentera à vous. N'oubliez pas : le web scraping n'a pas besoin d'être intimidant ou insurmontable. Avec les bons outils et les bonnes connaissances, cela peut être une expérience amusante et enrichissante. Si vous avez besoin d'un coup de main, n'hésitez pas à nous contacter ici, à https://www.webscrapingapi.com/. Nous sommes experts en web scraping et serons ravis de vous aider de toutes les manières possibles.

À propos de l'auteur
Mihai Maxim, Développeur Full Stack @ WebScrapingAPI
Mihai MaximDéveloppeur Full Stack

Mihai Maxim est développeur Full Stack chez WebScrapingAPI ; il participe à l'ensemble du produit et contribue à la création d'outils et de fonctionnalités fiables pour la plateforme.

Commencez à créer

Prêt à faire évoluer votre système de collecte de données ?

Rejoignez plus de 2 000 entreprises qui utilisent WebScrapingAPI pour extraire des données Web à l'échelle de l'entreprise, sans aucun coût d'infrastructure.