Scraping Web avec Scrapy et MongoDB
Scrapy est un framework de scraping Web Python robuste qui peut gérer les requêtes de manière asynchrone, suivre les liens et analyser le contenu du site. Pour stocker les données récupérées, vous pouvez utiliser MongoDB, une base de données NoSQL évolutive, qui stocke les données dans un format de type JSON. La combinaison de Scrapy avec MongoDB offre une solution puissante pour les projets de web scraping, tirant parti de l'efficacité de Scrapy et du stockage de données flexible de MongoDB.
Dans ce didacticiel, vous apprendrez à :
- Installer et configurer un projet Scrapy
- Créez un grattoir Web fonctionnel avec Scrapy
- Extraire des données de sites Web à l'aide de sélecteurs
- Stocker les données récupérées dans une base de données MongoDB
- Tester et déboguer votre scraper Web Scrapy
Si vous débutez dans le web scraping et que vous recherchez des outils flexibles et évolutifs, alors ce didacticiel est fait pour vous. Vous bénéficierez également de l’apprentissage de cette boîte à outils si vous avez déjà gratté des sites, mais que la complexité de votre projet est devenue trop grande avec Beautiful Soup and Requests.
Pour tirer le meilleur parti de ce didacticiel, vous devez posséder des connaissances de base en programmation Python, comprendre la programmation orientée objet, travailler confortablement avec des packages tiers et être familier avec HTML et CSS.
À la fin, vous saurez comment obtenir, analyser et stocker des données statiques à partir d’Internet, et vous connaîtrez plusieurs outils utiles qui vous permettront d’aller beaucoup plus loin.
Préparez l'échafaudage du grattoir
Vous commencerez par mettre en place les outils nécessaires et créer une structure de projet de base qui servira de base à vos tâches de scraping.
Tout en parcourant le didacticiel, vous construirez un projet de web scraping complet, en l'abordant comme un processus ETL (Extract, Transform, Load) :
- Extrayez les données du site Web en utilisant une araignée Scrapy comme robot d'exploration.
- Transformez ces données, par exemple en les nettoyant ou en les validant, à l'aide d'un pipeline d'éléments.
- Chargez les données transformées dans un système de stockage comme MongoDB avec un pipeline d'éléments.
Scrapy fournit un échafaudage pour tous ces processus, et vous exploiterez cet échafaudage pour apprendre le web scraping en suivant la structure robuste fournie par Scrapy et sur laquelle s'appuient de nombreux projets de web scraping à l'échelle de l'entreprise.
Remarque : Dans un projet de scraping Web Scrapy, un spider est une classe Python qui définit comment explorer un site Web spécifique ou un groupe de sites Web. Il contient la logique permettant d'effectuer des requêtes, d'analyser les réponses et d'extraire les données souhaitées.
Tout d’abord, vous installerez Scrapy et créerez un nouveau projet Scrapy, puis explorerez la structure du projet générée automatiquement pour vous assurer que vous êtes bien équipé pour procéder à la création d’un grattoir Web performant.
Installez le package Scrapy
Pour démarrer avec Scrapy, vous devez d'abord l'installer en utilisant pip
. Créez et activez un environnement virtuel pour séparer l'installation de votre installation Python globale. Ensuite, vous pouvez installer Scrapy :
(venv) $ python -m pip install scrapy
Une fois l'installation terminée, vous pouvez la vérifier en exécutant la commande scrapy
et en affichant le résultat :
(venv) $ scrapy
Scrapy 2.11.2 - no active project
Usage:
scrapy <command> [options] [args]
Available commands:
bench Run quick benchmark test
fetch Fetch a URL using the Scrapy downloader
genspider Generate new spider using pre-defined templates
runspider Run a self-contained spider (without creating a project)
settings Get settings values
shell Interactive scraping console
startproject Create new project
version Print Scrapy version
view Open URL in browser, as seen by Scrapy
[ more ] More commands available when run from project directory
Use "scrapy <command> -h" to see more info about a command
Le programme de ligne de commande (CLI) doit afficher le texte d'aide de Scrapy. Cela confirme que vous avez installé correctement le package. Vous allez ensuite exécuter la commande startproject
en surbrillance pour créer un projet.
Créer un projet Scrapy
Scrapy est construit autour de projets. En règle générale, vous créerez un nouveau projet pour chaque projet de web scraping sur lequel vous travaillez. Dans ce didacticiel, vous allez travailler sur le scraping d'un site Web appelé Books to Scrape, afin de pouvoir appeler votre projet books.
Comme vous l'avez peut-être déjà identifié dans le texte d'aide, le framework fournit une commande pour créer un nouveau projet :
(venv) $ scrapy startproject books
Cette commande ressemble à la commande que vous utilisez pour générer l'échafaudage d'un nouveau projet Django, et Scrapy et Django sont deux frameworks matures qui peuvent bien interagir l'un avec l'autre.
Dans Scrapy, la commande startproject
génère un nouveau projet Scrapy nommé books qui vous configure une structure de projet contenant plusieurs fichiers et répertoires générés automatiquement :
books/
│
├── books/
│ │
│ ├── spiders/
│ │ └── __init__.py
│ │
│ ├── __init__.py
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ └── settings.py
│
└── scrapy.cfg
Scrapy s'appuie sur cette structure et elle vous aidera à organiser efficacement votre projet Scrapy. Cliquez sur les différents fichiers et dossiers et réfléchissez à ce que chacun d'eux pourrait signifier dans le contexte d'un projet de web scraping :
books/
est le répertoire racine de votre projet Scrapy.books/spiders/
est un répertoire pour stocker vos définitions d'araignées.books/items.py
est un fichier permettant de définir la structure des données de vos éléments récupérés.books/middlewares.py
est un fichier pour gérer vos middlewares personnalisés.books/pipelines.py
est un fichier permettant de définir les pipelines de traitement des éléments.books/settings.py
est le fichier de paramètres pour configurer votre projet.
Dans ce didacticiel, vous toucherez la plupart des fichiers que vous pouvez voir répertoriés ci-dessus et leur ajouterez vos fonctionnalités personnalisées. Une fois l’échafaudage de votre projet configuré, vous allez maintenant jeter un œil au site Web source à partir duquel vous souhaitez extraire des informations.
Inspectez la source que vous souhaitez gratter
Vous n’irez pas loin dans vos ambitions de web scraping si vous ne connaissez pas la structure du site Web sur lequel vous souhaitez récupérer des informations. Par conséquent, l’inspection du site Web cible constitue une première étape essentielle de ce processus. Le moyen le plus rapide d’inspecter un site Web consiste à ouvrir votre navigateur, à accéder au site et à cliquer.
Regardez le site Web dans votre navigateur
Commencez par naviguer sur le site Web cible dans votre navigateur en tant qu'utilisateur. Identifiez les éléments spécifiques que vous souhaitez gratter. Par exemple, si vous récupérez les titres de livres, les prix et les URL d'une librairie, inspectez le code HTML pour localiser ces éléments. Vous pouvez le faire au mieux en utilisant les outils de développement intégrés à votre navigateur.
Remarque : Chaque navigateur propose des manières légèrement différentes d'ouvrir les outils de développement. Si vous n'avez jamais examiné les outils de développement de navigateurs auparavant, vous devrez peut-être effectuer une recherche sur le Web pour savoir comment les ouvrir dans votre cas spécifique. Vous pouvez également consulter un guide sur la façon d'inspecter un site à l'aide d'outils de développement.
Une fois que vous avez ouvert les outils de développement dans votre navigateur et identifié un élément de livre, vous verrez une interface qui ressemblera à celle présentée ci-dessous :
Une fois que vous aurez une bonne idée de la façon dont les informations sont organisées sur le site, vous souhaiterez rassembler les sélecteurs uniques pour chaque élément d'information que vous souhaitez récupérer. Vous souhaiterez extraire trois points de données de chaque livre :
- Titre
- Prix
- URL
Vous pouvez utiliser les outils de développement pour rassembler des expressions XPath ou des sélecteurs CSS pour chacun de ces éléments en cliquant avec le bouton droit sur l'élément et en sélectionnant Copier → Copier le sélecteur ou Copier → Copier XPath. . Par exemple, si vous copiez les sélecteurs CSS du premier livre du site, alors vous devriez obtenir les valeurs suivantes pour chacun des éléments :
- Title
#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > article > h3 > a
- Price
#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > article > div.product_price > p.price_color
- URL
#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > article > h3 > a
C’est beaucoup trop long pour être suivi ! Cependant, vous remarquerez peut-être qu’ils commencent tous de la même manière. C’est parce qu’il s’agit de chemins absolus qui commencent à l’élément racine du document HTML.
Remarque : L'utilisation de sélecteurs absolus peut constituer un moyen rapide d'identifier des éléments dans le document, mais ils sont fragiles et susceptibles de se briser en cas de modification de la structure du document. Pour un code plus robuste et maintenable, les sélecteurs relatifs sont généralement un meilleur choix.
Il semble que vous puissiez trouver toutes les informations qui vous intéressent dans une balise <article>
. Revenez à votre navigateur et identifiez cet élément <article>
:
<article class="product_pod">
<div class="image_container">
<a href="catalogue/a-light-in-the-attic_1000/index.html"><img
src="media/cache/2c/da/2cdad67c44b002e7ead0cc35693c0e8b.jpg"
alt="A Light in the Attic" class="thumbnail"></a>
</div>
<p class="star-rating Three">
<i class="icon-star"></i>
<i class="icon-star"></i>
<i class="icon-star"></i>
<i class="icon-star"></i>
<i class="icon-star"></i>
</p>
<h3><a href="catalogue/a-light-in-the-attic_1000/index.html"
title="A Light in the Attic">A Light in the ...</a></h3>
<div class="product_price">
<p class="price_color">£51.77</p>
<p class="instock availability">
<i class="icon-ok"></i>
In stock
</p>
<form>
<button type="submit" class="btn btn-primary btn-block"
data-loading-text="Adding...">Add to basket</button>
</form>
</div>
</article>
Les lignes en surbrillance confirment que vous pouvez trouver les trois informations pour chaque livre dans son élément <article>
– et cet élément a même un nom de classe, product_pod
!
Vous pouvez donc prévoir de cibler d'abord tous les éléments <article>
avec le nom de classe product_pod
. Ceux-ci représentent l’élément parent d’un seul livre, qui contient toutes les informations souhaitées. Vous pouvez ensuite parcourir tous ces éléments et extraire les informations pertinentes avec des sélecteurs beaucoup plus courts.
Mais existe-t-il un bon moyen de confirmer quels sélecteurs exécuteront vos enchères, avant d'écrire l'intégralité du script Spider ? Oui, il y en a ! Vous pouvez travailler avec le shell intégré de Scrapy pour faire exactement cela, et bien plus encore.
Prévisualisez les données avec Scrapy Shell
Avant d’écrire un spider à part entière, il est souvent utile de prévisualiser et de tester la manière dont vous extrayez les données d’une page Web. Le shell Scrapy est un outil interactif qui vous permet d'inspecter et d'extraire des données de pages Web en temps réel. Cela permet d'expérimenter les expressions XPath et les sélecteurs CSS pour garantir qu'ils ciblent correctement les éléments que vous recherchez.
Remarque : L'utilisation du shell vous permet d'afficher les données exactement comme si elles provenaient d'une requête HTTP. Lorsque vous inspectez la page rendue dans votre navigateur, vous affichez la représentation interne du navigateur du modèle d'objet de document (DOM). Pour le créer, votre navigateur peut avoir exécuté du code JavaScript et nettoyé le code HTML malformé, ce qui rend le code HTML résultant que vous pouvez inspecter à l'aide des outils de développement différent du code HTML d'origine.
Vous pouvez ouvrir le shell et le pointer vers le site que vous souhaitez récupérer en utilisant la commande shell
suivie de l'URL du site :
(venv) $ scrapy shell http://books.toscrape.com
Cette commande charge l'URL spécifiée et vous offre un environnement interactif pour explorer la structure HTML de la page. Vous pouvez le considérer comme un REPL Python interactif, mais avec accès aux objets Scrapy et au contenu HTML du site cible.
Lorsque vous exécutez cette commande, vous verrez des journaux suivis d'instructions d'utilisation :
...
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler <scrapy.crawler.Crawler object at 0x1070dcd40>
[s] item {}
[s] request <GET https://books.toscrape.com/>
[s] response <200 https://books.toscrape.com/>
[s] settings <scrapy.settings.Settings object at 0x10727ac90>
[s] spider <BookSpider 'book' at 0x107756990>
[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
>>>
Le shell fournit plusieurs objets pré-importés, response
étant le plus important pour inspecter les données que vous souhaitez extraire.
L'objet response
possède plusieurs attributs et méthodes utiles qui vous permettent d'inspecter la page de la même manière que vous le faisiez auparavant en utilisant les outils de développement de votre navigateur :
.url
contient l'URL de la page..status
vous montre le code d'état HTTP de la réponse..headers
affiche tous les en-têtes HTTP de la réponse..body
contient les octets bruts de la réponse..text
contient le texte décodé de la réponse sous forme de chaîne Unicode.
Par exemple, pour vérifier le code d'état HTTP de la réponse, vous pouvez exécuter la commande suivante :
>>> response.status
200
L'objet response
contient également le contenu de la page que vous avez chargée. Vous pouvez l'utiliser pour examiner le code HTML et trouver les éléments que vous souhaitez récupérer. Pour visualiser le contenu HTML de la page, vous pouvez utiliser .text
:
>>> response.text
'<!DOCTYPE html>\n<!--[if lt IE 7]>
...
Cependant, l’impression de l’intégralité du HTML est généralement fastidieuse, il est donc plus pratique d’utiliser des expressions XPath ou des sélecteurs CSS pour cibler des éléments spécifiques.
Scrapy utilise la bibliothèque Parsel pour gérer les expressions XPath et les sélecteurs CSS. Parsel fournit une classe Selector
que vous pouvez utiliser indépendamment de Scrapy. Ceci est utile si vous souhaitez expérimenter l'analyse HTML :
>>> from parsel import Selector
>>> html = "<html><body><h1>Hello, world!</h1></body></html>"
>>> selector = Selector(text=html)
>>> text = selector.xpath("//h1/text()").get()
>>> text
'Hello, world!'
Vous pouvez utiliser les mêmes sélecteurs Parsel dans Scrapy pour analyser l'objet response
.
Vous pouvez maintenant essayer les sélecteurs CSS que vous avez trouvés à l'aide de vos outils de développement pour confirmer s'ils peuvent cibler les informations souhaitées. Vous pouvez également utiliser des expressions XPath à la place, mais les sélecteurs CSS vous seront probablement plus familiers si vous avez exploré le développement Web.
Tout d'abord, vous souhaitez explorer le code HTML jusqu'aux éléments <article>
qui contiennent toutes les informations d'un livre, puis sélectionner chaque titre de livre en utilisant uniquement la partie pertinente du long sélecteur CSS que vous avez sélectionné. J'ai trouvé en copiant :
>>> all_book_elements = response.css("article.product_pod")
>>> for book in all_book_elements:
... print(book.css("h3 > a::attr(title)").get())
...
A Light in the Attic
Tipping the Velvet
Soumission
(...)
Vous avez commencé par cibler tous les éléments du conteneur de livres et attribué le résultat à all_book_elements
. Les appels à .css()
renvoient d'autres objets Selector
, vous pouvez donc continuer à approfondir en utilisant un autre appel à .css()
. Cette fois, vous définissez que vous souhaitez la valeur de l'attribut HTML title
de tous les éléments de lien contenus dans un élément <h3>
. En appelant .get()
sur le Selector
résultant, vous obtenez la première correspondance des résultats sous forme de chaîne.
Vous pouvez utiliser cette approche pour imprimer avec succès tous les titres de livres sur votre terminal.
Remarque : Pour ce site, il n'est pas réellement nécessaire d'explorer d'abord les éléments <article>
, car la page ne contient aucun autre <h3>
éléments qui contiennent des liens. Par conséquent, vous pouvez exécuter la même requête .css()
directement sur response
pour obtenir le même résultat.
Cependant, en général, vous ne pouvez pas compter sur quelque chose comme ça. Tout dépendra de la structure individuelle du site que vous grattez, il est donc souvent utile d'approfondir le HTML.
Vous avez confirmé que vous pouvez obtenir tous les titres de livres avec un sélecteur CSS raccourci. Maintenant, ciblez les URL et les prix de la même manière :
>>> for book in all_book_elements:
... print(book.css("h3 > a::attr(href)").get())
...
catalogue/a-light-in-the-attic_1000/index.html
catalogue/tipping-the-velvet_999/index.html
catalogue/soumission_998/index.html
(...)
>>> for book in all_book_elements:
... print(book.css(".price_color::text").get())
...
£51.77
£53.74
£50.10
(...)
Génial, vous pouvez adresser toutes les informations que vous souhaitez. Avant d'utiliser cette approche dans votre spider, considérez les concepts de la syntaxe CSS que vous avez utilisés pour cibler les bons éléments :
h3
anda
Cible les éléments de ce type d'élément HTML
>
Indique un élément enfant
.price_color
andarticle.product_pod
Indique un nom de classe et, éventuellement, spécifie sur quel élément le nom de classe doit être
::text
Cible le contenu textuel d'une balise HTML
::attr(href)
Cible la valeur d'un attribut HTML, dans ce cas l'URL dans un attribut
href
Vous pouvez utiliser la syntaxe CSS dans les sélecteurs pour cibler des informations spécifiques sur les sites Web, qu'il s'agisse du texte d'un élément ou même de la valeur d'un attribut HTML. À ce stade, vous avez identifié et confirmé des sélecteurs CSS plus courts qui peuvent vous donner accès à toutes les informations que vous recherchez :
- Book elements
article.product_pod
- URL
h3 > a::attr(href)
- Title
h3 > a::attr(titre)
- Price
.price_color::text
Cela a l'air beaucoup plus gérable ! Vous pouvez conserver ces sélecteurs car vous les utiliserez très bientôt lors de l'écriture de votre araignée.
Utiliser le shell Scrapy de cette manière vous aide à affiner vos sélecteurs et garantit que votre araignée extraira correctement les données souhaitées lorsque vous écrivez le code de l'araignée réel. Cette approche interactive permet de gagner du temps et de réduire les erreurs dans le processus de scraping.
Construisez votre Web Scraper avec Scrapy
Maintenant que vous comprenez bien comment inspecter les sites Web et que vous avez créé avec succès votre projet Scrapy, il est temps de créer votre araignée. Mais écrire votre araignée implique bien plus que simplement la pointer vers un site Web et la lâcher. Vous devrez tisser soigneusement la toile d’araignée pour vous assurer qu’elle collecte avec précision et efficacité les données dont vous avez besoin.
Dans cette section, vous aborderez des concepts essentiels tels que la création de classes d'éléments pour structurer vos données, l'application de sélecteurs pour identifier les éléments que vous souhaitez extraire et la gestion de la pagination pour parcourir des ensembles de données de plusieurs pages. À la fin de cette section, vous disposerez non seulement d'un grattoir Web fonctionnel, mais vous comprendrez également mieux comment personnaliser et optimiser vos araignées pour relever divers défis de grattage Web.
Collecter les données dans un Item
Un Item
est un conteneur qui définit la structure des données que vous souhaitez collecter. Il s'agit d'un mappage Python, ce qui signifie que vous pouvez attribuer des valeurs de la même manière qu'avec un dictionnaire Python.
Un Item
fournit également des fonctionnalités supplémentaires utiles pour le web scraping, telles que la validation et la sérialisation. Définir un Item
pour vos données permet d'organiser et de standardiser les données que vous récupérez, ce qui signifie moins d'efforts pour les traiter et les stocker ultérieurement.
Pour commencer à utiliser un élément, vous devez créer une classe qui hérite de scrapy.Item
et définir les champs que vous souhaitez supprimer en tant qu'instances de scrapy.Field
. Cette approche vous permet de spécifier la structure des données de manière claire et gérable.
Remarque : Si vous êtes familier avec le framework Web Django, vous reconnaîtrez peut-être cette façon de définir les éléments. Il utilise une syntaxe de définition de classe simplifiée semblable à la définition d'un modèle Django.
Scrapy est même livré avec un DjangoItem
dédié qui obtient sa définition de champs directement à partir d'un modèle Django.
La structure de projet par défaut que vous avez créée précédemment fournit un emplacement pour définir vos classes d'éléments dans un fichier appelé items.py
. Ouvrez le fichier et ajoutez du code à la classe BooksItem
générée avec des champs pour l'URL, le titre et le prix de chaque livre :
import scrapy
class BooksItem(scrapy.Item):
url = scrapy.Field()
title = scrapy.Field()
price = scrapy.Field()
Dans cet exemple, BooksItem
hérite de Item
et vous définissez .url
, .title
et .price
comme champs. La classe Field
est un espace réservé qui est juste un alias d'un dict
Python. Il s'intègre à Item
afin que vous puissiez l'utiliser pour définir les champs que Item
contiendra.
Maintenant que vous avez défini BooksItem
, vous pouvez l'utiliser dans votre araignée pour collecter des données. Vous utiliserez les sélecteurs que vous avez explorés à l’aide du shell pour aider votre araignée à trouver des URL, des titres de livres et des prix sur le site Web cible.
Écrivez une araignée
Scrapy
Il est enfin temps d’assembler les pièces que vous avez soigneusement construites jusqu’à présent. Le framework dispose d'une autre commande que vous pouvez utiliser pour créer facilement une araignée dans votre projet. Assurez-vous d'avoir navigué dans le dossier de votre projet appelé books/
, puis exécutez la commande pour générer votre premier spider :
(venv) $ cd books/
(venv) $ scrapy genspider book https://books.toscrape.com/
Created spider 'book' using template 'basic' in module:
books.spiders.book
En passant un nom d'araignée et l'URL cible à genspider
, Scrapy crée un nouveau fichier dans le répertoire spiders/
de votre projet. Ouvrez-le et jetez un oeil. Vous verrez une nouvelle classe qui hérite de scrapy.Spider
et porte le nom que vous avez entré, avec Spider
comme suffixe ; par exemple, BookSpider
dans votre cas. Il comprend également des échafaudages supplémentaires que vous allez maintenant modifier avec vos informations spécifiques.
Toute araignée de base a un nom et doit fournir deux informations supplémentaires :
- Par où commencer à gratter
- Comment analyser la réponse
Vous pouvez fournir ces informations en utilisant respectivement start_urls
et .parse()
. start_urls
est une liste de chaînes qui ne contient qu'une seule URL dans votre cas, mais pourrait en contenir davantage. Votre framework de scraping Web préféré a déjà renseigné cette valeur, ainsi que le nom de l'araignée, lorsque vous avez configuré l'échafaudage de l'araignée à l'aide de genspider
.
La méthode .parse()
d'un Spider
est une méthode de rappel que Scrapy appelle avec l'objet response
téléchargé pour chaque URL. Il doit contenir la logique permettant d'extraire les informations pertinentes de la réponse. Vous pouvez travailler avec les sélecteurs CSS que vous avez précédemment identifiés pour extraire les informations de la réponse, puis utiliser votre BooksItem
pour collecter les données :
import scrapy
from books.items import BooksItem
class BookSpider(scrapy.Spider):
name = "book"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com/"]
def parse(self, response):
for book in response.css("article.product_pod"):
item = BooksItem()
item["url"] = book.css("h3 > a::attr(href)").get()
item["title"] = book.css("h3 > a::attr(title)").get()
item["price"] = book.css(".price_color::text").get()
yield item
Vous avez créé une araignée Scrapy avec seulement quelques lignes de code ! Notez comment vous avez réutilisé le code que vous avez écrit dans le shell. Il vous suffisait de collecter les informations dans une instance BooksItem
, ce que vous faisiez en utilisant la syntaxe du dictionnaire. Enfin, vous avez utilisé yield
pour générer chaque élément.
Remarque : L'utilisation de yield
transforme .parse()
en un générateur qui produit ses résultats plutôt que de les renvoyer. Cela permet au framework de gérer plusieurs demandes et réponses simultanément.
Scrapy est construit sur le framework Twisted, qui est un moteur de réseau piloté par événements qui permet des requêtes asynchrones. Cela rend le processus de grattage plus efficace et évolutif.
Vous configurez .parse()
pour qu'il trouve d'abord tous les livres de la page actuelle, puis parcourt chaque livre. Pour chaque livre, il crée une instance BooksItem
et extrait l'URL, le titre et le prix à l'aide de sélecteurs CSS. Il attribue ces valeurs aux champs respectifs de votre BooksItem
, puis transmet l'instance d'élément au pipeline d'éléments.
Dans ce cas, Python n'appellera .parse()
qu'une seule fois, mais si vous travailliez sur un projet de scraping plus important impliquant plusieurs start_urls
, le framework l'appellerait automatiquement. pour chacun d'eux.
Maintenant que votre araignée est confortablement installée dans sa nouvelle toile, vous pouvez l’envoyer chercher des données sur Internet.
Extraire les données du site Web
Vous avez fini d’assembler votre araignée. Pour l'exécuter et voir les données récupérées, ouvrez votre terminal et confirmez que vous êtes toujours dans le répertoire de votre projet Scrapy. Vous pouvez démarrer le spider à partir de là en utilisant la commande crawl
:
(venv) $ scrapy crawl book
Scrapy commencera à explorer l'URL spécifiée. Il imprimera un tas d’informations de journalisation sur votre terminal. Nichées entre les journaux, vous devriez également voir les données extraites pour chaque livre sur la page de destination imprimées :
2024-08-28 10:26:48 [scrapy.utils.log] INFO: Scrapy 2.11.2 started (bot: books)
...
{'price': '£51.77',
'title': 'A Light in the Attic',
'url': 'catalogue/a-light-in-the-attic_1000/index.html'}
2024-08-28 10:26:50 [scrapy.core.scraper] DEBUG: Scraped from <200 https://books.toscrape.com/>
{'price': '£53.74',
'title': 'Tipping the Velvet',
'url': 'catalogue/tipping-the-velvet_999/index.html'}
2024-08-28 10:26:50 [scrapy.core.scraper] DEBUG: Scraped from <200 https://books.toscrape.com/>
...
2024-08-28 10:26:50 [scrapy.core.engine] INFO: Spider closed (finished)
Super! Vous avez réussi à récupérer et à extraire toutes les informations sur l'URL, le titre et les prix de la première page de la librairie factice ! Cependant, ce n’est qu’une page parmi tant d’autres.
Vous vous demandez peut-être si vous devrez ajouter chaque page à start_url
ou configurer une boucle for
pour parcourir toutes les pages disponibles. Ne vous inquiétez pas, il existe une méthode plus robuste et plus simple qui est déjà intégrée au framework !
Gérer la pagination et suivre les URL
De nombreux sites Web affichent des données sur plusieurs pages. Si votre araignée peut gérer la pagination et suivre les URL, elle peut alors naviguer sur plusieurs pages d'un site Web et extraire des données de chacune d'elles. Dans Scrapy, il vous suffira d'ajouter un peu de code à votre araignée pour qu'elle puisse gérer la pagination et explorer correctement votre site Web cible.
L'exploration du Web signifie parcourir systématiquement le Web pour indexer et collecter des informations. La fonction principale d'un robot d'exploration Web est de suivre les liens sur les pages Web pour découvrir du nouveau contenu et collecter des données. Dans le contexte du web scraping, un robot d'exploration peut parcourir différentes pages d'un site Web, extraire des informations pertinentes et les stocker pour une utilisation ultérieure.
Mais avant de pouvoir mettre à jour votre spider, vous devez comprendre comment le site Web gère la pagination. Ouvrez votre navigateur ou le shell Scrapy et inspectez le site Web pour trouver les contrôles de pagination.
Sur le site Books to Scrape, vous trouverez les liens de pagination en bas de page :
<li class="next"><a href="catalogue/page-2.html">next</a></li>
Le bouton suivant renvoie à la page de résultats suivante. Il est implémenté sous forme d'élément de liste (<li>
) avec une classe appelée "next"
qui contient un élément de lien (<a>
) avec l'URL de la page suivante. Lorsque vous cliquez dessus, vous verrez que le site charge la page suivante et affiche différents éléments du livre.
Avec ces connaissances à l'esprit, retournez à book.py
et modifiez votre BookSpider
existant pour gérer la pagination et récupérer les données de toutes les pages de résultats :
import scrapy
from books.items import BooksItem
class BookSpider(scrapy.Spider):
name = "book"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com/"]
def parse(self, response):
for book in response.css("article.product_pod"):
item = BooksItem()
item["url"] = book.css("h3 > a::attr(href)").get()
item["title"] = book.css("h3 > a::attr(title)").get()
item["price"] = book.css(".price_color::text").get()
yield item
next_page = response.css("li.next > a::attr(href)").get()
if next_page:
next_page_url = response.urljoin(next_page)
yield scrapy.Request(url=next_page_url, callback=self.parse)
Vous avez commencé par cibler l'attribut href
de l'élément concerné et en enregistrant sa valeur dans next_page
. Cependant, cette URL n'est pas une URL entièrement qualifiée, vous devrez donc la combiner avec l'URL de base avant que Scrapy puisse envoyer une nouvelle demande. Vous faites cela en utilisant .urljoin()
à l'intérieur du bloc conditionnel. Enfin, vous obtenez un objet scrapy.Request
, en lui transmettant l'URL que vous venez de concaténé et en utilisant la méthode .parse()
comme rappel.
Cette configuration permet au framework de faire une autre requête à la deuxième page de la librairie, toujours en utilisant la méthode .parse()
que vous avez écrite, poursuivant ainsi le processus de scraping. Le processus se poursuivra de manière récursive jusqu'à ce qu'il n'y ait plus de liens suivants, effaçant ainsi toutes les pages du site Web.
Votre araignée est maintenant configurée pour gérer la pagination. Vous pouvez l'exécuter à nouveau pour récupérer les données de toutes les pages :
(venv) $ scrapy crawl book
Scrapy commencera à la première page, extraira les données souhaitées, suivra les liens de pagination et continuera à extraire les données des pages suivantes jusqu'à ce qu'elles atteignent la dernière page. La sortie dans votre terminal ressemblera à celle d’avant, sauf que cela prendra plus de temps et vous donnera les informations sur tous les livres présents dans la librairie !
La gestion de la pagination et le suivi des URL sont des compétences essentielles pour créer des robots d'exploration Web efficaces. En incorporant une logique de pagination dans votre araignée, vous vous assurez que votre grattoir Web peut naviguer sur plusieurs pages et extraire des données complètes du site Web cible. Cette approche vous permet de créer des scrapers plus puissants et plus flexibles, capables de gérer un large éventail de sites Web.
Stockez les données récupérées dans MongoDB
Vous avez construit une araignée fonctionnelle qui explore toute la librairie, en suivant les liens de pagination pour extraire toutes les données dont vous avez besoin. Actuellement, Scrapy envoie ces données au flux d'erreurs standard (stderr) que vous voyez dans votre terminal.
Vous pouvez écrire du code pour l'enregistrer dans un fichier JSON, mais vous créez un scraper qui devrait être capable de gérer de grandes quantités de données et de continuer à fonctionner longtemps dans le futur.
Pour cette raison, c’est généralement une bonne idée de stocker les données que vous avez récupérées dans une base de données. Une base de données peut vous aider à organiser et à conserver les informations de manière sécurisée et pratique. MongoDB est un excellent choix pour gérer les données récupérées sur Internet en raison de sa flexibilité et de sa capacité à gérer des données dynamiques et semi-structurées. Dans cette section, vous apprendrez comment stocker les données récupérées dans une collection MongoDB.
Configurer une collection MongoDB sur votre ordinateur
Avant de pouvoir commencer à utiliser MongoDB, vous devez l'installer sur votre système. Le processus d'installation diffère en fonction de votre système d'exploitation, alors assurez-vous de suivre les instructions appropriées à votre système d'exploitation.
Remarque : Pour des instructions plus détaillées et des options de configuration supplémentaires, vous pouvez vous référer à l'introduction complète à MongoDB et Python.
Après avoir installé MongoDB avec succès, vous pouvez vérifier votre installation à l'aide de votre terminal :
$ mongod --version
db version v7.0.12
Build Info: {
"version": "7.0.12",
"gitVersion": "b6513ce0781db6818e24619e8a461eae90bc94fc",
"modules": [],
"allocator": "system",
"environment": {
"distarch": "aarch64",
"target_arch": "aarch64"
}
}
Le résultat exact que vous obtiendrez dépend de la version que vous avez installée, ainsi que de votre système d'exploitation. Mais cela ressemblera au résultat que vous pouvez voir ci-dessus. Si votre terminal vous affiche un message d’erreur, vous devrez alors revérifier votre installation et réessayer.
Si c'est la première fois que vous utilisez MongoDB, vous devrez peut-être effectuer quelques tâches de configuration pour le préparer au stockage des données.
Par défaut, MongoDB stocke les données dans /data/db
. Vous devrez peut-être créer ce répertoire et vous assurer qu'il dispose des autorisations appropriées. Par exemple:
$ sudo mkdir -p /data/db
$ sudo chown -R `id -u` /data/db
Si MongoDB n'est pas déjà en cours d'exécution, vous pouvez le démarrer avec la commande mongod
. Cela démarrera le serveur MongoDB et le liera au port par défaut 27017
.
Pour interagir avec votre instance MongoDB, vous pouvez utiliser le shell MongoDB en exécutant mongosh
dans votre terminal. Cela vous connectera au serveur MongoDB et vous permettra d'effectuer des opérations sur la base de données :
$ mongosh
Current Mongosh Log ID: 66868598a3dbed30a11bb1a2
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.2.10
Using MongoDB: 7.0.12
Using Mongosh: 2.2.10
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
test>
Dans le shell MongoDB, vous souhaitez créer une nouvelle base de données avec une nouvelle collection. Vous appellerez la base de données books_db
et la collection books
:
test> use books_db
switched to db books_db
books_db> db.createCollection("books")
{ ok: 1 }
books_db> show collections
books
books_db>
Avec votre nouvelle base de données et votre nouvelle collection en place, vous êtes prêt à vous replonger dans Scrapy pour connecter votre araignée à MongoDB. Vous allez le configurer de manière à ce que toutes les données récupérées se retrouvent dans votre nouvelle collection de livres
.
Connectez-vous à une base de données MongoDB depuis Scrapy
Vous utiliserez la bibliothèque tierce pymongo
pour vous connecter à votre base de données MongoDB depuis votre projet Scrapy. Tout d’abord, vous devrez installer pymongo
depuis PyPI :
(venv) $ python -m pip install pymongo
Une fois l'installation terminée, vous êtes prêt à ajouter des informations sur votre instance MongoDB à votre projet Scrapy.
Lorsque vous avez créé votre projet, vous avez également obtenu un fichier nommé settings.py
. C’est un endroit central qui vous permet de définir les paramètres de votre projet, et il contient déjà quelques informations. Il contient également de nombreuses notes sur les paramètres supplémentaires pour lesquels vous pouvez l'utiliser.
Un cas d'utilisation de settings.py
consiste à connecter le framework de web scraping à une base de données. Ouvrez settings.py
et ajoutez les détails de connexion MongoDB au bas du fichier :
# ...
MONGO_URI = "mongodb://localhost:27017"
MONGO_DATABASE = "books_db"
Vous chargerez plus tard ces variables dans votre pipeline et les utiliserez pour vous connecter à la base de données books_db
exécutée sur votre ordinateur local. Si vous vous connectez à une instance MongoDB hébergée, vous devrez alors adapter les informations en conséquence.
Traitez les données via un pipeline Scrapy
Les pipelines d'éléments de Scrapy sont une fonctionnalité puissante qui vous permet de traiter les données récupérées avant de les stocker ou de les traiter davantage. Les pipelines facilitent diverses tâches de post-traitement telles que le nettoyage, la validation et le stockage. Dans cette section, vous allez créer un pipeline d'éléments pour stocker les données récupérées dans votre nouvelle collection MongoDB, faisant écho au processus de chargement d'un workflow ETL.
Un pipeline d'éléments est une classe Python qui définit plusieurs méthodes pour traiter les éléments une fois que votre araignée les a récupérés sur Internet :
.open_spider()
est appelé lorsque l'araignée s'ouvre..close_spider()
est appelé lorsque l'araignée se ferme..process_item()
est appelé pour chaque composant du pipeline d'éléments. Il doit soit renvoyer un élément, soit déclencher une exceptionDropItem
..from_crawler()
crée un pipeline à partir d'unCrawler
afin de rendre les paramètres généraux du projet disponibles pour le pipeline.
Vous mettrez en œuvre toutes ces méthodes lors de la création de votre pipeline personnalisé pour insérer les éléments récupérés dans votre collection MongoDB. Bien que le chargement de données dans une base de données soit un cas d'utilisation possible d'un pipeline d'éléments, vous pouvez également les utiliser à d'autres fins, telles que le nettoyage des données HTML ou la validation des données récupérées.
Remarque : Du point de vue du processus ETL, un pipeline d'éléments peut servir à la fois aux opérations de transformation et de chargement.
Vous avez déjà installé MongoDB et pymongo
, créé une collection et ajouté les paramètres spécifiques à MongoDB au fichier settings.py
de votre projet. Ensuite, vous définirez une classe de pipeline dans books/pipelines.py
. Ce pipeline se connectera à MongoDB et insérera les éléments récupérés dans la collection :
import pymongo
from itemadapter import ItemAdapter
class MongoPipeline:
COLLECTION_NAME = "books"
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get("MONGO_URI"),
mongo_db=crawler.settings.get("MONGO_DATABASE"),
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db[self.COLLECTION_NAME].insert_one(ItemAdapter(item).asdict())
return item
Vous avez ajouté ce qui peut ressembler à un gros morceau de code. Cependant, la majeure partie est du code passe-partout. Voici ce que fait chaque pièce :
itemadapter
encapsule différents conteneurs de données pour les gérer de manière uniforme. Le package a été installé en tant que dépendance de Scrapy.COLLECTION_NAME
spécifie le nom de la collection MongoDB dans laquelle vous souhaitez stocker les éléments. Cela doit correspondre au nom de la collection que vous avez configurée précédemment..__init__()
initialise le pipeline avec l'URI MongoDB et le nom de la base de données. Vous pouvez accéder à ces informations car vous les récupérez à partir duCrawler
à l'aide de la méthode de classe.from_crawler()
..from_crawler()
est une méthode de classe qui vous donne accès à tous les composants principaux de Scrapy, tels que les paramètres. Dans ce cas, vous l'utilisez pour récupérer les paramètres MongoDB desettings.py
via le robot Scrapy..open_spider()
ouvre une connexion à MongoDB au démarrage du spider..close_spider()
ferme la connexion MongoDB lorsque le spider a terminé..process_item()
insère chaque élément récupéré dans la collection MongoDB. Cette méthode contient généralement les fonctionnalités de base d’un pipeline.
Avec votre MongoPipeline
en place, vous devrez toujours l'activer dans le contexte de votre projet afin que le framework l'utilise lors de la prochaine exécution. Pour activer le pipeline, vous devez l'ajouter aux paramètres de votre projet dans settings.py
.
Lorsque vous générez un échafaudage de projet Scrapy à l'aide de scrapy startproject
, votre fichier settings.py
inclut déjà une entrée pour ITEM_PIPELINES
. Comme la plupart des paramètres de ce fichier, il est commenté. Vous pouvez rechercher cette entrée et supprimer le commentaire Python, puis ajouter l'emplacement du nom qualifié de votre nouvel objet pipeline :
ITEM_PIPELINES = {
"books.pipelines.MongoPipeline": 300,
}
Vous pouvez ajouter des pipelines à votre projet sous forme d'entrées dans un dictionnaire, où le nom qualifié de votre classe de pipeline est la clé et un entier est la valeur. L'entier détermine l'ordre dans lequel Scrapy exécute les pipelines. Des nombres inférieurs signifient une priorité plus élevée.
Remarque : L'utilisation de 300
est une convention qui indique aux développeurs que ce nombre est flexible et qu'ils peuvent utiliser une large plage au-dessus et en dessous pour ordonner les priorités de vos pipelines.
En définissant un pipeline d'éléments, vous pouvez intégrer les opérations transform et load de manière transparente dans vos tâches de web scraping. Une fois votre connexion MongoDB configurée dans votre projet, il est temps d'exécuter le scraper une autre fois et d'évaluer s'il fonctionne comme prévu.
Évitez d'ajouter des entrées en double
Lorsque vous utilisez votre scraper pour la première fois, tout devrait fonctionner comme prévu. Scrapy remplit votre collection MongoDB avec les informations du site Web. Si vous vérifiez la longueur de votre collection après l'exécution initiale dans le shell Mongo, vous verrez qu'elle contient 1000
documents :
books_db> db.books.countDocuments()
1000
Cependant, si vous exécutez le scraper une autre fois, votre base de données de livres a doublé de longueur et contient désormais deux mille documents. Ce problème existe car par défaut, MongoDB crée un identifiant unique pour chaque document basé sur des horodatages, entre autres informations. Par conséquent, il considérera chaque élément récupéré comme un nouveau document et lui attribuera un nouvel identifiant, ce qui entraînera une duplication des données dans votre base de données.
C'est peut-être ce que vous souhaitez dans certains projets, mais ici, vous souhaitez conserver chaque élément de livre récupéré une seule fois, même si vous exécutez le grattoir plusieurs fois. Pour cela, vous devrez créer et spécifier un champ ID unique pour vos données que MongoDB pourra utiliser pour identifier chaque document.
Ouvrez à nouveau books/pipelines.py
et ajoutez la logique nécessaire pour vérifier les doublons en fonction d'un nouveau champ id
unique que vous dériverez du hachage de l'URL de la page individuelle :
import hashlib
import pymongo
from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem
class MongoPipeline:
COLLECTION_NAME = "books"
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get("MONGO_URI"),
mongo_db=crawler.settings.get("MONGO_DATABASE"),
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
item_id = self.compute_item_id(item)
if self.db[self.COLLECTION_NAME].find_one({"_id": item_id}):
raise DropItem(f"Duplicate item found: {item}")
else:
item["_id"] = item_id
self.db[self.COLLECTION_NAME].insert_one(ItemAdapter(item).asdict())
return item
def compute_item_id(self, item):
url = item["url"]
return hashlib.sha256(url.encode("utf-8")).hexdigest()
Pour éviter de collecter des éléments en double lors de plusieurs exécutions de Scraper, vous avez appliqué des mises à jour à .process_item()
et ajouté la nouvelle méthode d'assistance .compute_item_id()
:
Les lignes 1 et 4 contiennent deux nouvelles importations, une pour le module de bibliothèque standard
hashlib
que vous utilisez pour traiter les URL en identifiants uniques dans.compute_item_id()
. La deuxième importation apporteDropItem
descrapy.exceptions
, qui est un moyen rapide de supprimer les éléments en double et d'écrire dans les journaux de Scrapy.La ligne 28 appelle
.compute_item_id()
et attribue la sortie hachée àitem_id
.Les lignes 29 et 30 interrogent la collection MongoDB pour vérifier si un élément avec le même
_id
existe déjà. Si Python trouve un doublon, le code déclenche une exceptionDropItem
, qui indique au framework de supprimer cet élément et de ne pas le traiter davantage. S’il ne trouve pas de doublon, il passe aux étapes suivantes.Les lignes 31 à 34 constituent la condition
else
, où l'élément récupéré n'existe pas encore dans la base de données. Ce code est presque le code que vous aviez avant d'ajouter la logique de déduplication, avec cependant une différence importante. Avant d'insérer l'élément dans votre collection, vous ajoutez leitem_id
calculé comme valeur de l'attribut._id
à votreItem
. MongoDB utilise un champ nommé_id
comme clé primaire, s'il est présent.
Notez que comme avant, la méthode renvoie l'élément. Cela permet un traitement ultérieur par d'autres pipelines si vous deviez en créer.
Avant de pouvoir exécuter votre pipeline mis à jour, vous devrez prendre en compte le nouveau champ _id
sur un BooksItem
. Ouvrez books/items.py
et ajoutez-le à la définition de votre élément :
import scrapy
class BooksItem(scrapy.Item):
_id = scrapy.Field()
url = scrapy.Field()
title = scrapy.Field()
price = scrapy.Field()
Vous n'avez pas besoin de changer quoi que ce soit dans votre araignée, et vous remplissez maintenant chaque BooksItem
à partir de deux endroits différents dans votre code :
- Votre araignée ajoute des valeurs pour
.url
,.title
et.price
à unBooksItem
. - Votre pipeline d'articles ajoute la valeur pour
._id
, après l'avoir calculée à partir de.url
.
C'est un bel exemple de la puissance et de la flexibilité que vous offre l'utilisation des processus intégrés de Scrapy lors de la création de votre grattoir Web.
Comme brièvement mentionné, .compute_item_id()
utilise le hashlib
de Python pour hacher la valeur de l'URL. L’utilisation de l’URL hachée comme identifiant unique signifie que Python n’ajoutera à votre collection aucun livre que vous avez extrait deux fois de la même URL.
Ce que vous considérez comme un identifiant unique est une décision de conception à laquelle vous devrez réfléchir individuellement pour chaque projet. Dans ce cas, l’utilisation de l’URL hachée fonctionne bien car vous travaillez avec une librairie factice où chaque ressource de livre a une URL unique. Cependant, si les données de l'un de ces livres changeaient (tout en conservant la même URL), l'implémentation actuelle supprimerait cet élément.
Si la suppression de l'élément dans un tel cas n'est pas le comportement prévu, vous pouvez alors mettre à jour votre code en utilisant une opération upsert à la place. Dans MongoDB, vous pouvez effectuer des upserts en utilisant db.collection.updateOne()
avec upsert
défini sur true. pymongo
traduit cette opération en code Python :
# ...
def process_item(self, item, spider):
item_id = self.compute_item_id(item)
item_dict = ItemAdapter(item).asdict()
self.db[self.COLLECTION_NAME].update_one(
filter={"_id": item_id},
update={"$set": item_dict},
upsert=True
)
return item
# ...
Lorsque vous utilisez cette configuration, vous n'avez même pas besoin de mettre à jour votre BooksItem
car il contourne l'adaptateur d'élément et crée le _id
du document directement lors de l'interaction avec MongoDB.
Lorsque vous utilisez .update_one()
avec pymongo
, vous devez alors transmettre deux arguments principaux :
- Le filtre pour retrouver le document dans votre collection
- L'opération de mise à jour
Le troisième argument que vous fournissez, upsert=True
, garantit que MongoDB insérera l'élément s'il n'existe pas. Comme mentionné, vous n'êtes pas obligé d'ajouter explicitement la valeur de _id
à votre BooksItem
, car .update_one()
prend cette valeur du argument de filtre et l’utilise uniquement pour créer les documents de votre collection.
Vous devrez supprimer votre collection existante, qui contient toujours les identifiants basés sur l'horodatage, avant que ce code n'ait l'effet escompté :
books_db> db.books.drop()
true
books_db> db.books.countDocuments()
0
Vous pouvez maintenant continuer et exécuter votre scraper plusieurs fois, puis vérifier le nombre de documents que vous avez dans votre collection. Le nombre doit toujours rester à mille.
Gardez à l’esprit que l’approche que vous choisissez peut dépendre du projet sur lequel vous travaillez. Quoi qu'il en soit, vous avez maintenant une meilleure idée de la façon dont vous pouvez supprimer les éléments en double et mettre à jour les entrées existantes avec de nouvelles données.
Déboguez et testez votre Scrapy Web Scraper
Vous rencontrerez probablement des situations dans lesquelles votre grattoir ne fait pas ce que vous attendez de lui. Les projets de web scraping traitent toujours de plusieurs facteurs qui peuvent conduire à des résultats inattendus. Le débogage sera probablement une partie essentielle de votre processus de développement de scraper. Scrapy fournit plusieurs outils puissants pour vous aider à déboguer vos araignées et à garantir qu'ils fonctionnent comme prévu.
L'écriture de tests pour votre spider garantit que votre code continuera à fonctionner comme prévu. Les tests sont une partie cruciale de tout processus de développement logiciel, et le web scraping ne fait pas exception.
Dans cette section, vous expliquerez comment utiliser les outils de débogage et de test de Scrapy, y compris la fonction de journalisation, le shell Scrapy, une fonction de rappel d'erreur et les contrats Spider. Vous écrirez également des tests unitaires personnalisés pour confirmer que votre araignée fonctionne comme prévu.
Enregistrer les informations avec l'enregistreur
La journalisation est un outil fondamental pour comprendre ce que fait votre araignée à chaque étape. Par défaut, le framework enregistre divers événements, mais vous pouvez personnaliser la journalisation en fonction de vos besoins. Scrapy utilise la bibliothèque logging
de Python qui fait partie de la bibliothèque standard. Vous n’avez même pas besoin de l’importer, car les objets araignées ont déjà accès à un enregistreur.
Ouvrez votre fichier book.py
et ajoutez une journalisation pour suivre le moment où votre araignée navigue vers une nouvelle page :
# ...
def parse(self, response):
for book in response.css("article.product_pod"):
item = BooksItem()
item["url"] = book.css("h3 > a::attr(href)").get()
item["title"] = book.css("h3 > a::attr(title)").get()
item["price"] = book.css(".price_color::text").get()
yield item
next_page = response.css("li.next > a::attr(href)").get()
if next_page:
next_page_url = response.urljoin(next_page)
self.logger.info(
f"Navigating to next page with URL {next_page_url}."
)
yield scrapy.Request(url=next_page_url, callback=self.parse)
# ...
Vous avez ajouté du code qui accède au .logger
à partir de votre BookSpider
et enregistre les informations lorsque l'araignée navigue vers une nouvelle page avec le niveau de journalisation INFO.
Par défaut, le framework enregistre avec la gravité DEBUG, ce qui peut être utile pour le débogage, mais potentiellement écrasant lorsque vous le voyez à chaque exécution. Maintenant que vous avez ajouté un message de débogage de gravité supérieure, vous pouvez passer à ce niveau pour voir uniquement les messages INFO et de gravité supérieure imprimés sur votre console.
Vous pouvez configurer la connexion dans votre spider directement via des constantes dans settings.py
:
# ...
LOG_LEVEL = "INFO"
Après avoir ajouté cette ligne à settings.py
, exécutez votre scraper une autre fois. Vous ne verrez aucune information de débogage, mais uniquement des journaux d’une gravité INFO et supérieure.
Si vous exécutez l'araignée tout en remplissant une collection vide, vous n'obtiendrez que les informations selon lesquelles Scrapy a navigué vers une autre page.
Cependant, vous verrez un résultat différent si vous l'exécutez une deuxième fois, en fonction de la logique de déduplication que vous avez décidé de conserver dans MongoPipeline.process_item()
:
Si vous travaillez avec upserts et
.update_one()
de pymongo, vous ne verrez que les messages du journal lorsque Scrapy accède à une nouvelle page.Si vous travaillez toujours avec un champ
._id
explicite et unDropItem
, vous serez submergé de journaux d'avertissement. Scrapy écrit automatiquement un AVERTISSEMENT lorsqu'il détecte une exceptionDropItem
.
Consigner tous les avertissements est probablement une bonne idée, et vous souhaiterez peut-être conserver le fichier journal pour une inspection ultérieure. Vous pouvez définir une valeur pour LOG_FILE
dans les paramètres de Scrapy pour écrire dans un fichier au lieu de stderr, et adapter à nouveau le LOG_LEVEL
pour que Python n'écrive que les avertissements dans le fichier journal :
# ...
LOG_LEVEL = "WARNING"
LOG_FILE = "book_scraper.log"
Avec ces deux paramètres, vous avez personnalisé le niveau de journalisation et l'emplacement du fichier journal dans votre projet Scrapy. Définir LOG_LEVEL
sur "WARNING"
signifie que Python enregistrera uniquement les messages de gravité plus élevée. Le paramètre LOG_FILE
spécifie le fichier dans lequel Scrapy enregistrera les journaux.
Si vous exécutez maintenant votre spider, vous ne verrez aucune sortie dans votre console. Au lieu de cela, un nouveau fichier apparaît dans lequel Scrapy écrit des informations sur tous les éléments déposés, en supposant que vous travaillez avec le code qui utilise DropItem
:
2024-08-28 13:11:32 [scrapy.core.scraper] WARNING: Dropped: Duplicate item found: {'price': '£51.77',
'title': 'A Light in the Attic',
'url': 'catalogue/a-light-in-the-attic_1000/index.html'}
{'price': '£51.77',
'title': 'A Light in the Attic',
'url': 'catalogue/a-light-in-the-attic_1000/index.html'}
...
Si vous travaillez avec des upserts, vous devriez voir un fichier journal vide. C'est bon de savoir que rien ne s'est mal passé !
La journalisation peut vous aider à suivre le flux de votre araignée, à inspecter les valeurs des variables et à identifier les problèmes possibles. C'est souvent une bonne idée de définir le niveau de journalisation sur DEBUG et d'écrire les journaux dans un fichier, pour vous permettre de retracer ce que votre robot a fait au cas où il échouerait lors d'une exécution.
Gérer les erreurs avec errback
Scrapy vous permet de gérer les erreurs avec élégance à l'aide d'une fonction de rappel d'erreur. Si vous transmettez une méthode au paramètre errback
d'un objet Request
, alors Scrapy l'utilisera pour gérer les exceptions. Ceci est particulièrement utile lorsqu'il s'agit d'erreurs réseau ou de réponses inattendues du serveur, et vous pouvez l'utiliser pour enregistrer l'erreur sans mettre fin à votre programme.
Ouvrez book.py
et ajoutez une nouvelle méthode à votre araignée pour gérer les erreurs :
import scrapy
from books.items import BooksItem
class BookSpider(scrapy.Spider):
# ...
def log_error(self, failure):
self.logger.error(repr(failure))
Dans .log_error()
, vous interceptez l'exception et la enregistrez avec un niveau de gravité d'ERREUR. Cela peut vous aider à diagnostiquer les problèmes sans arrêter l’exécution de l’araignée.
Ensuite, vous devez intégrer votre nouvelle méthode dans le code. Pour détecter les erreurs qui se produisent lors des requêtes, vous devez spécifier le paramètre errback
en plus de callback
. De cette façon, vous pouvez vous assurer que Scrapy gère les erreurs en utilisant votre méthode .log_error()
:
# ...
def parse(self, response):
# ...
if next_page:
next_page_url = response.urljoin(next_page)
self.logger.info(
f"Navigating to next page with URL {next_page_url}."
)
yield scrapy.Request(
url=next_page_url,
callback=self.parse,
errback=self.log_error,
)
En transmettant le nom de votre méthode de rappel d'erreur à scrapy.Request
, vous vous assurez que toute demande de pages supplémentaires de la librairie qui renvoie une erreur sera enregistrée.
Si vous souhaitez également utiliser .log_error()
pour la requête initiale à Books to Scrape, vous devez alors refactoriser votre code pour utiliser .start_requests()
au lieu de vous fier uniquement à sur .start_urls
:
# ...
class BookSpider(scrapy.Spider):
name = "book"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com/"]
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(
url, callback=self.parse, errback=self.log_error
)
def parse(self, response):
# ...
En ajoutant .start_requests()
, vous bénéficiez d'une flexibilité supplémentaire sur la manière de gérer la requête initiale adressée à chaque URL dans start_urls
. Scrapy n'appelle cette méthode qu'une seule fois pour chaque URL de démarrage. Dans cet exemple, vous avez également ajouté votre nouvelle méthode .log_error()
à cette requête initiale en la transmettant à errback
. Si vous définissez votre propre gestion des erreurs avec errback
, vous pouvez alors dépanner et affiner efficacement les conditions d'erreur dans vos robots de scraping Web.
Après avoir exploré quelques outils de débogage proposés par Scrapy, vous allez maintenant voir comment vous pouvez écrire des tests pour vous assurer que votre scraper fonctionne comme prévu.
Signez des contrats Spider
Scrapy propose une fonctionnalité connue sous le nom de contrats, vous permettant d'intégrer des cas de test directement dans les docstrings de votre araignée. Semblable au doctest
de Python, cette fonctionnalité permet une validation rapide des méthodes Spider, garantissant qu'elles fonctionnent comme prévu sans nécessiter de configurations de test approfondies.
Les contrats Spider sont essentiellement des assertions que vous pouvez placer dans la documentation de vos méthodes Spider. Ils définissent le comportement attendu des méthodes :
@url
spécifie l'URL que Scrapy doit récupérer.@returns
indique le nombre attendu d'éléments ou de requêtes que l'araignée doit renvoyer.@scrapes
répertorie les champs que les éléments doivent contenir.
Ouvrez votre fichier book.py
et ajoutez un contrat à votre méthode .parse()
:
# ...
def parse(self, response):
"""
@url https://books.toscrape.com
@returns items 20 20
@returns request 1 50
@scrapes url title price
"""
for book in response.css("article.product_pod"):
# ...
Vous avez ajouté quatre contrats à la docstring de .parse()
:
@url https://books.toscrape.com
indique à Scrapy de récupérer l'URL spécifiée pour tester la méthode d'analyse. Ce contrat est requis pour l’exécution de tous les autres tests.@returns items 20 20
spécifie que la méthode doit renvoyer exactement vingt éléments. Le premier nombre correspond aux éléments minimum attendus et le second au maximum.@returns request 1 50
signifie que.parse()
doit générer au moins une et au plus cinquante requêtes. Cette partie est pertinente pour le code de pagination que vous avez configuré.@scrapes url title price
spécifie que chaque article retourné doit contenir les champsurl
,title
etprice
.
Avec seulement ces quatre lignes, vous avez mis en place des tests de base pour .parse()
qui peuvent vous aider à détecter très tôt les problèmes avec votre scraper.
Remarque : Il existe un autre contrat intégré, @cb_kwargugs
, que vous n'avez pas utilisé dans ce cas, mais qui peut être utile lorsque vous devez définir les arguments de mots clés pour la demande d’échantillon. Vous pouvez également rédiger des contrats personnalisés.
Pour exécuter les tests du contrat, vous pouvez utiliser la commande check
dans votre terminal :
(venv) $ scrapy check book
...
----------------------------------------------------------------------
Ran 3 contracts in 1.399s
OK
Lors de l'exécution de cette commande, Scrapy récupère l'URL spécifiée, exécute .parse()
et valide les résultats par rapport aux assertions de la docstring. Si l'une des assertions échoue, le framework affichera un message d'erreur indiquant ce qui n'a pas fonctionné.
Notez que le contrat @url
est une condition préalable au bon fonctionnement des autres contrats. Scrapy ne le répertorie pas comme condition de test dans la sortie, c'est pourquoi le message indique que Scrapy a exécuté trois contrats.
Si l'un des contrats échoue, Scrapy fournira des détails sur l'échec, tels que le nombre d'articles qui n'ont pas répondu aux attentes spécifiées. Allez-y et modifiez vos contrats pour voir à quoi ressemble un test échoué. Si vous êtes familier avec le module unittest
de Python, vous reconnaîtrez probablement le résultat. Sous le capot, les contrats utilisent unittest
pour organiser et exécuter les tests.
En bref, les contrats Spider présentent quelques avantages, tels que :
Validation rapide : les contrats offrent un moyen rapide de valider vos robots sans configurer de cadres de test étendus. Ceci est particulièrement utile lors du développement lorsque vous souhaitez vous assurer que vos méthodes spider fonctionnent correctement.
Documentation : les docstrings avec les contrats servent de documentation, indiquant clairement quel est le comportement attendu de vos méthodes Spider. Cela peut être bénéfique pour les membres de l'équipe ou lors de la révision du code après un certain temps.
Tests automatisés : les contrats peuvent être intégrés à votre pipeline de tests automatisés, garantissant ainsi que vos robots continuent de fonctionner correctement lorsque vous apportez des modifications à votre base de code.
En intégrant des contrats Spider dans votre processus de développement, vous pouvez améliorer la fiabilité et la maintenabilité de vos scrapers Web, en vous assurant qu'ils fonctionnent comme prévu dans diverses conditions.
Écrire des tests unitaires pour des tests détaillés
Les contrats Spider sont un excellent moyen de créer des tests rapides. Cependant, si vous travaillez avec un scraper complexe et que vous souhaitez vérifier ses fonctionnalités plus en détail, cela peut être une bonne idée d'écrire des tests unitaires pour tester des composants individuels.
Remarque : Une option populaire pour les tests unitaires en Python est la bibliothèque tierce pytest
. Cependant, vous continuerez à travailler avec la bibliothèque unittest
intégrée à Python. Scrapy l'utilise pour les contrats, et bon, il est déjà fourni avec Python !
Pour écrire des tests unitaires pour les méthodes BookSpider
, vous devez simuler l'environnement Scrapy. Cela implique des réponses et des demandes moqueuses pour garantir que vos méthodes Spider se comportent comme prévu.
Dans votre dossier de projet books/
de niveau supérieur, créez un dossier tests/
. Dans ce dossier, créez deux fichiers :
__init__.py
initialise le dossier en tant que package Python.test_book.py
contiendra les tests unitaires pour votre scraper.
Maintenant, prenez un moment pour réfléchir aux aspects de votre grattoir que vous aimeriez tester. Il peut être utile de reconsidérer ce que couvrent déjà vos contrats Spider, puis de construire là-dessus avec plus de spécificité.
Par exemple, bien qu'il existe des contrats qui vérifient le nombre d'éléments récupérés par page et les éléments extraits par Scrapy, vous ne disposez d'aucune vérification pour savoir s'il s'agit des bons éléments. Vos contrats ne savent rien du contenu des éléments qu’il extrait. Ce pourrait être une bonne idée d'écrire un test unitaire pour combler cette lacune.
De plus, vous souhaiterez peut-être confirmer que chaque appel à .parse()
identifie les bons éléments du code HTML, récupère tous les livres et crée une nouvelle requête pour l'URL de pagination :
import unittest
class BookSpiderTest(unittest.TestCase):
def test_parse_scrapes_all_items(self):
"""Test if the spider scrapes books and pagination links."""
pass
def test_parse_scrapes_correct_book_information(self):
"""Test if the spider scrapes the correct information for each book."""
pass
def test_parse_creates_pagination_request(self):
"""Test if the spider creates a pagination request correctly."""
pass
if __name__ == "__main__":
unittest.main()
Une fois vos intentions définies et une structure de classe de test de base en place, vous pouvez continuer et écrire votre code de test. Vos tests bénéficieront souvent d'une méthode .setUp()
qui crée la configuration nécessaire dont chaque test a besoin pour fonctionner correctement.
Pour ces tests unitaires, vous souhaitez vérifier un petit échantillon du code HTML de la page. Vous aurez également besoin d'accéder à votre araignée de livre et à certaines des classes que le framework utilise en interne pour gérer les demandes et les réponses. Comme vous aurez besoin de cette configuration pour chaque test, c'est un bon candidat pour passer à une méthode .setUp()
:
import unittest
from scrapy.http import HtmlResponse
from books.spiders.book import BookSpider
class BookSpiderTest(unittest.TestCase):
def setUp(self):
self.spider = BookSpider()
self.example_html = """
Insert the example HTML here
"""
self.response = HtmlResponse(
url="https://books.toscrape.com",
body=self.example_html,
encoding="utf-8"
)
# ...
La méthode .setUp()
initialise une instance de BookSpider
avant chaque test, crée un exemple de chaîne HTML, contenant deux entrées de livre et un lien vers la page suivante. Il simule ensuite une requête adressée au site en renvoyant un objet HtmlResponse
avec cet exemple HTML. Vous souhaitez que l'exemple HTML soit aussi proche que possible de la vraie page HTML, sans surcharger votre base de code.
Vous pouvez récupérer l'exemple HTML utilisé par ce didacticiel à partir des documents téléchargeables dans books/tests/sample.html
:
Cet extrait de la page de destination de Books to Scrape contient deux éléments de livre complets et le lien de pagination, mais est par ailleurs simplifié afin que la chaîne ne devienne pas inutilement longue. Ajoutez cet extrait de code HTML entre les guillemets triples-doubles pour l'attribuer à self.example_html
.
Remarque : Si vous souhaitez utiliser la page HTML complète, vous pouvez également le récupérer avec votre navigateur, requests
ou Scrapy et l'enregistrer en tant que variable de chaîne. Les résultats devraient être les mêmes.
Cependant, gardez à l’esprit que si vous récupérez le contenu à chaque exécution de votre script de test, cela entraîne une dégradation des performances et ne fonctionnera que si vous êtes connecté à Internet.
Une fois votre configuration en place, vous pouvez faire échouer votre premier test :
# ...
class BookSpiderTest(unittest.TestCase):
# ...
def test_parse_scrapes_all_items(self):
"""Test if the spider scrapes all books and pagination links."""
# There should be two book items and one pagination request
book_items = []
pagination_requests = []
self.assertEqual(len(book_items), 2)
self.assertEqual(len(pagination_requests), 1)
Si vous exécutez votre code de test à ce stade, vous obtiendrez un test échoué et deux tests réussis. Accédez à votre répertoire books/
de niveau supérieur, si vous n'y êtes pas déjà, puis exécutez la commande unittest
:
(venv) $ python -m unittest
.F.
======================================================================
FAIL: test_parse_scrapes_all_items (tests.test_book.BookSpiderTest.test_parse_scrapes_all_items)
Test if the spider scrapes all items including books and pagination links.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/path/to/books/tests/test_book.py", line 169, in test_parse_scrapes_all_items
self.assertEqual(len(book_items), 2)
AssertionError: 0 != 2
----------------------------------------------------------------------
Ran 3 tests in 0.011s
FAILED (failures=1)
Les tests vides réussissent car ils ne contiennent encore aucune assertion. Votre .test_parse_scrapes_all_items()
échoue car vous n'appelez pas encore la méthode .parse()
et ne gérez pas sa valeur de retour. Revenez dans le code et ajoutez les éléments manquants pour réussir votre test :
# ...
from scrapy.http import HtmlResponse, Request
from books.items import BooksItem
class BookSpiderTest(unittest.TestCase):
# ...
def test_parse_scrapes_all_items(self):
"""Test if the spider scrapes all books and pagination links."""
# Collect the items produced by the generator in a list
# so that it's possible to iterate over it more than once.
results = list(self.spider.parse(self.response))
# There should be two book items and one pagination request
book_items = [item for item in results if isinstance(item, BooksItem)]
pagination_requests = [
item for item in results if isinstance(item, Request)
]
self.assertEqual(len(book_items), 2)
self.assertEqual(len(pagination_requests), 1)
Vous commencez par ajouter une importation pour Request
depuis scrapy.http
. Vous utilisez cette classe pour comparer si la demande de pagination que le framework doit générer fait partie des résultats. Vous souhaiterez également confirmer que Scrapy rassemble les informations du livre dans un BooksItem
, vous les importez donc depuis books.items
.
Gardez à l'esprit que .parse()
est un générateur. Pour plus de commodité, vous rassemblez tous les éléments générés dans une liste. Ensuite, vous ajoutez deux compréhensions de liste qui rassemblent tous les objets dicts
et Request
dans les deux listes que vous avez créées précédemment lors de la configuration du plan de cette méthode de test. L'exemple HTML contient deux éléments de livre et une URL de pagination, c'est donc ce que vous vérifiez dans vos assertions.
Remarque : Si vous avez utilisé la page HTML complète pour votre test, vous devriez recevoir vingt éléments de livre et toujours une seule demande de pagination. Vous devrez adapter votre assertion en conséquence pour que le test réussisse.
Vous pouvez maintenant exécuter unittest
une autre fois, et les trois tests devraient réussir :
(venv) $ python -m unittest
...
----------------------------------------------------------------------
Ran 3 tests in 0.011s
Vous pouvez continuer à développer les deux autres méthodes de test en utilisant une approche similaire. Dans la section réductible ci-dessous, vous pouvez trouver un exemple d'implémentation :
Vous pouvez copier le code ci-dessous et le coller dans votre fichier test_book.py
:
import unittest
from scrapy.http import HtmlResponse, Request
from books.items import BooksItem
from books.spiders.book import BookSpider
class BookSpiderTest(unittest.TestCase):
def setUp(self):
self.spider = BookSpider()
self.example_html = """
Insert the example HTML here
"""
self.response = HtmlResponse(
url="https://books.toscrape.com",
body=self.example_html,
encoding="utf-8",
)
def test_parse_scrapes_all_items(self):
"""Test if the spider scrapes all books and pagination links."""
# Collect the items produced by the generator in a list
# so that it's possible to iterate over it more than once.
results = list(self.spider.parse(self.response))
# There should be two book items and one pagination request
book_items = [
item for item in results if isinstance(item, BooksItem)
]
pagination_requests = [
item for item in results if isinstance(item, Request)
]
self.assertEqual(len(book_items), 2)
self.assertEqual(len(pagination_requests), 1)
def test_parse_scrapes_correct_book_information(self):
"""Test if the spider scrapes the correct information for each book."""
results_generator = self.spider.parse(self.response)
# Book 1
book_1 = next(results_generator)
self.assertEqual(
book_1["url"], "catalogue/a-light-in-the-attic_1000/index.html"
)
self.assertEqual(book_1["title"], "A Light in the Attic")
self.assertEqual(book_1["price"], "£51.77")
# Book 2
book_2 = next(results_generator)
self.assertEqual(
book_2["url"], "catalogue/tipping-the-velvet_999/index.html"
)
self.assertEqual(book_2["title"], "Tipping the Velvet")
self.assertEqual(book_2["price"], "£53.74")
def test_parse_creates_pagination_request(self):
"""Test if the spider creates a pagination request correctly."""
results = list(self.spider.parse(self.response))
next_page_request = results[-1]
self.assertIsInstance(next_page_request, Request)
self.assertEqual(
next_page_request.url,
"https://books.toscrape.com/catalogue/page-2.html",
)
if __name__ == "__main__":
unittest.main()
Notez que vous devrez toujours mettre à jour la valeur de .example_html
avec l'exemple HTML que vous pouvez trouver dans books/tests/sample.html
dans les ressources téléchargeables :
Cette configuration garantit que l'exactitude des composants individuels de BookSpider
est testée, aidant ainsi à identifier et à résoudre les problèmes dès le début du processus de développement.
Gérer les défis courants du Web Scraping
Le web scraping peut être un outil puissant, mais il n’est souvent pas aussi simple qu’on pourrait l’espérer. Que vous ayez affaire à du contenu généré dynamiquement ou que vous ayez besoin de contourner les mécanismes anti-scraping, être prêt à surmonter ces obstacles est essentiel pour créer un grattoir Web robuste et fiable.
Dans cette section, vous explorerez certains des défis les plus courants que vous pourriez rencontrer. Vous découvrirez également quelques solutions pratiques pour les surmonter grâce à Scrapy et son vaste écosystème.
Equipé de ces outils et de ces bonnes pratiques, vous serez mieux préparé à créer des scrapers résilients capables d'extraire des données précieuses également à partir de sites Web plus difficiles.
Réessayer les demandes ayant échoué
Le web scraping implique souvent l’envoi de nombreuses requêtes aux serveurs web. Certaines de ces requêtes peuvent entraîner des erreurs de connexion dues à des problèmes de réseau, à des temps d'arrêt du serveur ou à des blocages temporaires du site cible. Ces erreurs peuvent perturber le processus de scraping et conduire à une collecte de données incomplète. La mise en œuvre d'une logique de nouvelle tentative robuste dans votre projet permet de garantir que les problèmes temporaires n'entraînent pas l'échec de votre araignée.
Scrapy fournit une prise en charge intégrée pour réessayer les demandes qui rencontrent certains types d'erreurs. Ce RetryMiddleware
est activé par défaut, donc dans de nombreux cas, vous n'aurez rien à faire, et Scrapy réessayera automatiquement les demandes ayant échoué les plus courantes pour vous. Le middleware réessaye les requêtes ayant échoué un nombre de fois spécifié avant d'abandonner, augmentant ainsi les chances de réussite de l'extraction des données.
Si vous avez besoin de personnaliser le comportement de RetryMiddleware
, vous pouvez le faire facilement directement dans votre fichier settings.py
. Parmi les options de configuration disponibles, il y en a trois que vous souhaiterez peut-être ajuster pour votre projet :
RETRY_ENABLED
: active ou désactive le middleware de nouvelle tentative. Il est activé par défaut.RETRY_TIMES
: spécifie le nombre maximum de tentatives pour une requête ayant échoué. Par défaut, le framework réessaye deux fois une requête ayant échoué.RETRY_HTTP_CODES
: définit les codes de réponse HTTP qui doivent déclencher une nouvelle tentative.
Comme mentionné, Scrapy réessaye vos requêtes par défaut, mais si vous devez configurer le middleware de nouvelle tentative pour votre projet Scrapy en dehors de la valeur par défaut, vous pouvez modifier ces valeurs dans le fichier settings.py
:
# ...
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 429]
Dans cet exemple, vous avez modifié le nombre de tentatives par requête à 3
et adapté les codes HTTP par défaut afin que Scrapy ne réessaye les requêtes que lorsqu'il reçoit HTTP 500
(erreur interne du serveur). et les codes d'erreur 429
(trop de requêtes). Cependant, s'en tenir aux paramètres par défaut implémentés par Scrapy est généralement une bonne idée pour la plupart des projets Scraper.
En bref, Scrapy gère par défaut la logique de nouvelle tentative via son RetryMiddleware
intégré, que vous pouvez également personnaliser. Grâce à cela, vous pouvez obtenir une extraction de données plus fiable et réduire l’impact des problèmes transitoires.
Gérer le contenu dynamique
Le scraping de contenu dynamique, tel que les pages rendues par JavaScript, présente un ensemble unique de défis. Les techniques traditionnelles de web scraping qui reposent sur du HTML statique ne fonctionnent pas lorsque vous traitez du contenu généré dynamiquement. Il existe quelques approches que vous pouvez adopter pour toujours accéder aux données qui vous intéressent :
Requêtes de reproduction : votre navigateur récupère souvent les données dont vous avez besoin via des appels d'API en arrière-plan. En inspectant les requêtes réseau effectuées par le navigateur à l'aide des outils de développement de votre navigateur, vous pouvez identifier ces points de terminaison d'API et faire directement des requêtes similaires à partir de votre scraper. Cela évite le besoin de rendu JavaScript et est plus efficace.
Utilisez Splash pour pré-afficher JavaScript : Splash est un navigateur sans tête conçu spécifiquement pour le web scraping. Il vous permet de restituer du JavaScript et de capturer le HTML résultant. Scrapy dispose d'un middleware dédié pour Splash, appelé scrapy-splash, qui vous permet de l'intégrer de manière transparente dans votre projet.
Automatiser un navigateur : des outils tels que Selenium et Playwright fournissent une automatisation complète du navigateur, y compris l'exécution de JavaScript. Selenium est bien établi et largement utilisé, tandis que Playwright propose des alternatives plus récentes, plus rapides et plus fiables avec une prise en charge intégrée de plusieurs navigateurs. Scrapy peut être intégré à Playwright à l'aide du package
scrapy-playwright
.
Bien que le scraping du contenu des pages rendues en JavaScript augmente certainement la difficulté de votre projet de scraping Web, Scrapy vous propose des intégrations utiles qui vous aideront à faire le travail.
Gérer les mécanismes anti-grattage
De nombreux sites Web emploient des mesures anti-grattage pour protéger leurs données et garantir une utilisation équitable de leurs ressources. Vous pouvez contourner certaines de ces mesures avec différents degrés d'effort.
Scrapy vous permet de personnaliser certaines des approches les plus couramment utilisées directement dans le fichier settings.py
d'un projet.
Les sites Web bloquent souvent les demandes des clients sans en-têtes d'agent utilisateur ou par défaut. La définition d'un agent utilisateur personnalisé dans les paramètres de votre projet peut aider à imiter un vrai navigateur :
# ...
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
Vous pouvez également utiliser un middleware pour alterner les agents utilisateurs et les proxys, ce qui peut aider à distribuer les requêtes et réduire les risques de blocage :
# ...
DOWNLOADER_MIDDLEWARES = {
"scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None,
"scrapy_user_agents.middlewares.RandomUserAgentMiddleware": 400,
}
# ...
Enfin, vous pouvez introduire des délais entre les requêtes, ce qui peut également contribuer à éviter le déclenchement de mesures anti-bot :
# ...
DOWNLOAD_DELAY = 2
Si un site construit de nombreuses défenses contre les web scrapers, vous voudrez peut-être vous demander si c'est vraiment une bonne idée de le gratter. Pour tout site que vous envisagez de supprimer, vous devez toujours vérifier et respecter son fichier robots.txt
. Ce fichier spécifie les autorisations du site pour les robots d'exploration Web. Scrapy dispose également d'un paramètre pour cela, qui est activé par défaut :
# ...
ROBOTSTXT_OBEY = True
# ...
En appliquant ces techniques et configurations, vous pouvez relever les défis courants du web scraping, en garantissant que vos scrapers sont robustes, efficaces et respectueux des sites Web avec lesquels vous interagissez.
Conclusion
En suivant ce didacticiel, vous avez utilisé avec succès Scrapy et MongoDB pour un projet complet de web scraping qui suit le processus ETL. Beau travail !
Vous avez découvert un large éventail de fonctionnalités de Scrapy, y compris les capacités de test fournies et comment vous pouvez les étendre. Vous avez également appris comment gérer les défis courants du web scraping, tels que la gestion des tentatives, le traitement du contenu dynamique et le contournement des mécanismes anti-scraping.
Dans ce didacticiel, vous avez appris à :
- Installer et configurer un projet Scrapy
- Créez un grattoir Web fonctionnel avec Scrapy
- Extraire des données de sites Web à l'aide de sélecteurs
- Stocker les données récupérées dans une base de données MongoDB
- Testez et déboguez votre scraper Web Scrapy
Grâce à ces compétences, vous êtes bien équipé pour aborder une variété de projets de web scraping, en tirant parti de l'efficacité de Scrapy et de la flexibilité de MongoDB. Que vous collectiez des données à des fins de recherche, construisiez une application basée sur les données ou exploriez simplement le Web, les connaissances et les outils que vous avez acquis grâce à ce didacticiel vous seront très utiles. Continuez à gratter et restez respectueux !