Comment explorer une page web avec Scrapy et Python 3
Introduction
Le web scraping, souvent appelé crawling web ou spidering web, est l'acte de parcourir programmatique une collection de pages web et d'extraire des données, et c'est un outil puissant pour travailler avec des données sur le web.
Avec un web scraper, vous pouvez extraire des données sur un ensemble de produits, obtenir un grand corpus de texte ou de données quantitatives avec lesquelles jouer, récupérer des données d'un site sans API officielle, ou simplement satisfaire votre propre curiosité.
Dans ce tutoriel, vous apprendrez les fondamentaux du processus de scraping et d’exploration alors que vous explorerez un ensemble de données ludique. Nous allons utiliser Quotes to Scrape, une base de données de citations hébergée sur un site conçu pour tester des robots d'exploration web. À la fin de ce tutoriel, vous aurez un scraper web Python entièrement fonctionnel qui parcourt une série de pages contenant des citations et les affiche sur votre écran.
Le dessinateur sera facilement extensible, afin que vous puissiez le modifier et l'utiliser comme base pour vos propres projets de collecte de données sur le web.
Conditions préalables
Pour compléter ce tutoriel, vous aurez besoin d'un environnement de développement local pour Python 3. Vous pouvez suivre le guide Comment installer et configurer un environnement de programmation local pour Python 3 afin de configurer tout ce dont vous avez besoin.
Étape 1 — Créer un scraper de base
Le scraping est un processus en deux étapes :
- Trouver et télécharger systématiquement des pages web.
- Extraire des informations des pages téléchargées.
Ces deux étapes peuvent être mises en œuvre de plusieurs manières dans de nombreuses langues.
Vous pouvez créer un scraper à partir de zéro en utilisant des modules ou des bibliothèques fournies par votre langage de programmation, mais ensuite vous devrez faire face à certains maux de tête potentiels à mesure que votre scraper devient plus complexe. Par exemple, vous devrez gérer la concurrence afin de pouvoir explorer plus d'une page à la fois. Vous voudrez probablement découvrir comment transformer vos données extraites en différents formats comme CSV, XML ou JSON. Et vous devrez parfois faire face à des sites qui nécessitent des paramètres et des modèles d'accès spécifiques.
Vous aurez plus de chances de réussite si vous construisez votre scrappeur sur une bibliothèque existante qui gère ces problèmes pour vous. Pour ce tutoriel, nous allons utiliser Python et Scrapy pour construire notre scrappeur.
Scrapy est l'une des bibliothèques de scraping Python les plus populaires et puissantes ; elle adopte une approche « batteries comprises » en matière de scraping, ce qui signifie qu'elle gère beaucoup des fonctionnalités courantes dont tous les scrapers ont besoin, de sorte que les développeurs n'ont pas à réinventer la roue à chaque fois.
Scrapy, comme la plupart des paquets Python, est sur PyPI (également connu sous le nom de pip). PyPI, l'Index des Paquets Python, est un dépôt géré par la communauté de tous les logiciels Python publiés.
Si vous avez une installation de Python comme celle décrite dans les prérequis de ce tutoriel, vous avez déjà pip installé sur votre machine, vous pouvez donc installer Scrapy avec la commande suivante :
- pip install scrapy
Si vous rencontrez des problèmes avec l'installation, ou si vous souhaitez installer Scrapy sans utiliser pip, consultez la documentation officielle d'installation.
Avec Scrapy installé, créez un nouveau dossier pour notre projet. Vous pouvez le faire dans le terminal en exécutant :
- mkdir quote-scraper
Maintenant, accédez au nouveau répertoire que vous venez de créer :
- cd quote-scraper
Ensuite, créez un nouveau fichier Python pour notre scraper appelé scraper.py. Nous mettrons tout notre code dans ce fichier pour ce tutoriel. Vous pouvez créer ce fichier en utilisant le logiciel de votre choix.
Commencez le projet en créant un scraper très basique qui utilise Scrapy comme fondation. Pour cela, vous devrez créer une classe Python qui sous-classe scrapy.Spider, une classe de spider de base fournie par Scrapy. Cette classe aura deux attributs obligatoires :
nom— juste un nom pour l'araignée.start_urls— une liste d'URL à partir de laquelle vous commencez à explorer. Nous commencerons avec une URL.
Ouvrez le fichier scrapy.py dans votre éditeur de texte et ajoutez ce code pour créer l'araignée de base :
import scrapy
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['https://quotes.toscrape.com']
Décomposons cela ligne par ligne :
Tout d'abord, nous importons scrapy afin de pouvoir utiliser les classes que le paquet fournit.
Ensuite, nous prenons la classe Spider fournie par Scrapy et en faisons une sous-classe appelée BrickSetSpider. Pensez à une sous-classe comme à une forme plus spécialisée de sa classe parente. La classe Spider possède des méthodes et des comportements qui définissent comment suivre les URL et extraire des données des pages qu'elle trouve, mais elle ne sait pas où chercher ni quelles données rechercher. En créant une sous-classe, nous pouvons lui donner ces informations.
Enfin, nous nommons la classe quote-spider et donnons à notre scraper une seule URL à partir de laquelle commencer : https://quotes.toscrape.com. Si vous ouvrez cette URL dans votre navigateur, cela vous mènera à une page de résultats de recherche, affichant la première des nombreuses pages de citations célèbres.
Maintenant, testez le scrapper. En général, les fichiers Python sont exécutés avec une commande comme python chemin/vers/fichier.py. Cependant, Scrapy dispose de sa propre interface en ligne de commande pour simplifier le processus de démarrage d'un scrapper. Démarrez votre scrapper avec la commande suivante :
- scrapy runspider scraper.py
La commande affichera quelque chose comme ceci :
2022-12-02 10:30:08 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor
2022-12-02 10:30:08 [scrapy.extensions.telnet] INFO: Telnet Password: b4d94e3a8d22ede1
2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
...
'scrapy.extensions.logstats.LogStats']
2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
...
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
...
'scrapy.spidermiddlewares.depth.DepthMiddleware']
2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2022-12-02 10:30:08 [scrapy.core.engine] INFO: Spider opened
2022-12-02 10:30:08 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2022-12-02 10:30:08 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2022-12-02 10:49:32 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://quotes.toscrape.com> (referer: None)
2022-12-02 10:30:08 [scrapy.core.engine] INFO: Closing spider (finished)
2022-12-02 10:30:08 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 226,
...
'start_time': datetime.datetime(2022, 12, 2, 18, 30, 8, 492403)}
2022-12-02 10:30:08 [scrapy.core.engine] INFO: Spider closed (finished)
C'est beaucoup de contenu, alors décomposons-le.
- Le scraper s'est initialisé et a chargé des composants et des extensions supplémentaires dont il avait besoin pour lire les données à partir des URL.
- Il a utilisé l'URL que nous avons fournie dans la liste
start_urlset a récupéré le HTML, tout comme le ferait votre navigateur web. - Il a passé ce HTML à la méthode
parse, qui ne fait rien par défaut. Puisque nous n'avons jamais écrit notre propre méthodeparse, l'araignée se termine simplement sans effectuer de travail.
Maintenant, tirons quelques données de la page.
Étape 2 — Extraction des données d'une page
Nous avons créé un programme très basique qui télécharge une page, mais il ne fait pas encore de scraping ou d'exploration. Donnons-lui des données à extraire.
Si vous regardez la page que nous voulons extraire, vous verrez qu'elle a la structure suivante :
- Il y a un en-tête qui est présent sur chaque page.
- Il y a un lien pour se connecter.
- Ensuite, il y a les citations elles-mêmes, affichées dans ce qui ressemble à un tableau ou à une liste ordonnée. Chaque citation a un format similaire.
Lorsque vous écrivez un scraper, vous devez examiner la source du fichier HTML et vous familiariser avec la structure. Voici donc le code, avec les balises non pertinentes pour notre objectif supprimées pour plus de lisibilité :
quotes.toscrape.com<body>
...
<div class="quote" itemscope itemtype="http://schema.org/CreativeWork">
<span class="text" itemprop="text">“I have not failed. I've just found 10,000 ways that won't work.”</span>
<span>by <small class="author" itemprop="author">Thomas A. Edison
<a href="/author/Thomas-A-Edison">(about)</a>
</span>
<div class="tags">
Tags:
<meta class="keywords" itemprop="keywords" content="edison,failure,inspirational,paraphrased" / >
<a class="tag" href="/tag/edison/page/1/">edison</a>
<a class="tag" href="/tag/failure/page/1/">failure</a>
<a class="tag" href="/tag/inspirational/page/1/">inspirational</a>
<a class="tag" href="/tag/paraphrased/page/1/">paraphrased</a>
</div>
</div>
...
</body>
Le scraping de cette page est un processus en deux étapes :
- Tout d'abord, récupérez chaque citation en recherchant les parties de la page qui contiennent les données que nous voulons.
- Ensuite, pour chaque citation, extraire les données que nous voulons en tirant les données des balises HTML.
scrapy extrait des données en fonction des sélécteurs que vous fournissez. Les sélecteurs sont des motifs que nous pouvons utiliser pour trouver un ou plusieurs éléments sur une page afin que nous puissions ensuite travailler avec les données à l'intérieur de l'élément. scrapy prend en charge les sélecteurs CSS ou les sélecteurs XPath.
Nous allons utiliser des sélecteurs CSS pour l'instant, car le CSS est parfaitement adapté pour trouver tous les ensembles sur la page. Si vous regardez le HTML, vous verrez que chaque citation est spécifiée avec la classe quote. Comme nous recherchons une classe, nous utiliserons .quote pour notre sélecteur CSS. La partie . du sélecteur recherche l'attribut class sur les éléments. Tout ce que nous avons à faire est de créer une nouvelle méthode dans notre classe nommée parse et de passer ce sélecteur dans l'objet response, comme ceci :
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['https://quotes.toscrape.com']
def parse(self, response):
QUOTE_SELECTOR = '.quote'
TEXT_SELECTOR = '.text::text'
AUTHOR_SELECTOR = '.author::text'
for quote in response.css(QUOTE_SELECTOR):
pass
Ce code récupère tous les ensembles sur la page et les parcourt pour extraire les données. Maintenant, extrayons les données de ces citations afin que nous puissions les afficher.
Un autre regard sur la source de la page que nous analysons nous dit que le texte de chaque citation est stocké dans un span avec la classe text et l'auteur de la citation dans une balise <small> avec la classe author :
quotes.toscrape.com ...
<span class="text" itemprop="text">“I have not failed. I've just found 10,000 ways that won't work.”</span>
<span>by <small class="author" itemprop="author">Thomas A. Edison
...
L'objet quote que nous parcourons a sa propre méthode css, donc nous pouvons passer un sélecteur pour localiser des éléments enfants. Modifiez votre code comme suit pour localiser le nom de l'ensemble et l'afficher :
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['https://quotes.toscrape.com']
def parse(self, response):
QUOTE_SELECTOR = '.quote'
TEXT_SELECTOR = '.text::text'
AUTHOR_SELECTOR = '.author::text'
for quote in response.css(QUOTE_SELECTOR):
yield {
'text': quote.css(TEXT_SELECTOR).extract_first(),
'author': quote.css(AUTHOR_SELECTOR).extract_first(),
}
Remarque : La virgule finale après extract_first() n'est pas une faute de frappe. En Python, une virgule finale dans les objets dict est une syntaxe valide, et c'est une bonne façon de laisser de la place pour ajouter plus d'éléments, ce que nous ferons ici plus tard.
Vous remarquerez deux choses qui se passent dans ce code :
- Nous ajoutons
::textà nos sélecteurs pour la citation et l'auteur. C'est un pseudo-sélecteur CSS qui récupère le texte à l'intérieur de la balise plutôt que la balise elle-même. - Nous appelons
extract_first()sur l'objet retourné parquote.css(TEXT_SELECTOR)car nous voulons juste le premier élément qui correspond au sélecteur. Cela nous donne une chaîne, plutôt qu'une liste d'éléments.
Enregistrez le fichier et exécutez à nouveau le scrapper :
- scrapy runspider scraper.py
Cette fois, la sortie contiendra les citations et leurs auteurs :
...
2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”', 'author': 'Albert Einstein'}
2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”', 'author': 'Jane Austen'}
2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", 'author': 'Marilyn Monroe'}
...
Continuons à développer cela en ajoutant de nouveaux sélecteurs pour les liens vers des pages sur l'auteur et des étiquettes pour la citation. En examinant le HTML de chaque citation, nous trouvons :
- Le lien vers la page à propos de l'auteur est stocké dans un lien immédiatement après son nom.
- Les balises sont stockées sous forme d'une collection de balises
a, chacune classéetag, stockées dans un élémentdivavec la classetags.
Alors, modifions le scraper pour obtenir cette nouvelle information :
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['https://quotes.toscrape.com']
def parse(self, response):
QUOTE_SELECTOR = '.quote'
TEXT_SELECTOR = '.text::text'
AUTHOR_SELECTOR = '.author::text'
ABOUT_SELECTOR = '.author + a::attr("href")'
TAGS_SELECTOR = '.tags > .tag::text'
for quote in response.css(QUOTE_SELECTOR):
yield {
'text': quote.css(TEXT_SELECTOR).extract_first(),
'author': quote.css(AUTHOR_SELECTOR).extract_first(),
'about': 'https://quotes.toscrape.com' +
quote.css(ABOUT_SELECTOR).extract_first(),
'tags': quote.css(TAGS_SELECTOR).extract(),
}
Enregistrez vos modifications et exécutez à nouveau le scrapper :
- scrapy runspider scraper.py
Maintenant, la sortie contiendra les nouvelles données :
2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”', 'author': 'Albert Einstein', 'about': 'https://quotes.toscrape.com/author/Albert-Einstein', 'tags': ['inspirational', 'life', 'live', 'miracle', 'miracles']}
2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”', 'author': 'Jane Austen', 'about': 'https://quotes.toscrape.com/author/Jane-Austen', 'tags': ['aliteracy', 'books', 'classic', 'humor']}
2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", 'author': 'Marilyn Monroe', 'about': 'https://quotes.toscrape.com/author/Marilyn-Monroe', 'tags': ['be-yourself', 'inspirational']}
Transformons maintenant ce scraper en une araignée qui suit les liens.
Étape 3 — Explorer plusieurs pages
Vous avez réussi à extraire des données de cette page initiale, mais nous ne progressons pas au-delà pour voir le reste des résultats. Tout l'intérêt d'un spider est de détecter et de parcourir les liens vers d'autres pages et d'extraire des données de ces pages également.
Vous remarquerez que le haut et le bas de chaque page ont un petit caret droit (>) qui renvoie à la page suivante de résultats. Voici le code HTML pour cela :
quotes.toscrape.com...
<nav>
<ul class="pager">
<li class="next">
<a href="/page/2/">Next <span aria-hidden="true">→</span></a>
</li>
</ul>
</nav>
...
Dans la source, vous trouverez une balise li avec la classe next, et à l'intérieur de cette balise, il y a une balise a avec un lien vers la page suivante. Tout ce que nous avons à faire est de dire au scraper de suivre ce lien s'il existe.
Modifiez votre code comme suit :
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['https://quotes.toscrape.com']
def parse(self, response):
QUOTE_SELECTOR = '.quote'
TEXT_SELECTOR = '.text::text'
AUTHOR_SELECTOR = '.author::text'
ABOUT_SELECTOR = '.author + a::attr("href")'
TAGS_SELECTOR = '.tags > .tag::text'
NEXT_SELECTOR = '.next a::attr("href")'
for quote in response.css(QUOTE_SELECTOR):
yield {
'text': quote.css(TEXT_SELECTOR).extract_first(),
'author': quote.css(AUTHOR_SELECTOR).extract_first(),
'about': 'https://quotes.toscrape.com' +
quote.css(ABOUT_SELECTOR).extract_first(),
'tags': quote.css(TAGS_SELECTOR).extract(),
}
next_page = response.css(NEXT_SELECTOR).extract_first()
if next_page:
yield scrapy.Request(response.urljoin(next_page))
Tout d'abord, nous définissons un sélecteur pour le lien “page suivante”, extrayons la première correspondance et vérifions si elle existe. L'scrapy.Request est un nouvel objet de requête que Scrapy sait qu'il doit récupérer et analyser le suivant.
Cela signifie qu'une fois que nous passons à la page suivante, nous chercherons un lien vers la page suivante là-bas, et sur cette page, nous chercherons un lien vers la page suivante, et ainsi de suite, jusqu'à ce que nous ne trouvions plus de lien vers la page suivante. C'est le principe clé du web scraping : trouver et suivre des liens. Dans cet exemple, c'est très linéaire ; une page a un lien vers la page suivante jusqu'à ce que nous atteignions la dernière page. Mais vous pourriez suivre des liens vers des tags, ou d'autres résultats de recherche, ou toute autre URL que vous souhaitez.
Maintenant, si vous enregistrez votre code et exécutez à nouveau l'araignée, vous verrez qu'elle ne s'arrête pas simplement une fois qu'elle a parcouru la première page de jeux. Elle continue à parcourir toutes les 100 citations sur les 10 pages. Dans l'ensemble, ce n'est pas une énorme quantité de données, mais maintenant vous connaissez le processus par lequel vous trouvez automatiquement de nouvelles pages à extraire.
Voici notre code complet pour ce tutoriel :
import scrapy
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['https://quotes.toscrape.com']
def parse(self, response):
QUOTE_SELECTOR = '.quote'
TEXT_SELECTOR = '.text::text'
AUTHOR_SELECTOR = '.author::text'
ABOUT_SELECTOR = '.author + a::attr("href")'
TAGS_SELECTOR = '.tags > .tag::text'
NEXT_SELECTOR = '.next a::attr("href")'
for quote in response.css(QUOTE_SELECTOR):
yield {
'text': quote.css(TEXT_SELECTOR).extract_first(),
'author': quote.css(AUTHOR_SELECTOR).extract_first(),
'about': 'https://quotes.toscrape.com' +
quote.css(ABOUT_SELECTOR).extract_first(),
'tags': quote.css(TAGS_SELECTOR).extract(),
}
next_page = response.css(NEXT_SELECTOR).extract_first()
if next_page:
yield scrapy.Request(
response.urljoin(next_page),
)
Conclusion
Dans ce tutoriel, vous avez construit une araignée entièrement fonctionnelle qui extrait des données des pages web en moins de trente lignes de code. C'est un excellent début, mais il y a beaucoup de choses amusantes que vous pouvez faire avec cette araignée. Cela devrait suffire à vous faire réfléchir et expérimenter. Si vous avez besoin de plus d'informations sur Scrapy, consultez la documentation officielle de Scrapy. Pour plus d'informations sur le travail avec des données provenant du web, consultez notre tutoriel sur « Comment extraire des pages web avec Beautiful Soup et Python 3 ».
