Scraper, ça veut dire, en bon français, « extraire le contenu d’une page web ou d’un site », sachant qu’on ne désire pas tout collecter, mais seulement le contenu intéressant. Et qu’on s’intéresse plutôt à un site qu’à une seule page (sinon autant le faire à la main).
Pourquoi scraper un contenu ? Pour l’analyser, en extraire des informations précises, fréquemment mises à jour, ou collecter une grande masse d’information textuelle pour faire des analyses sur le contenu. Avec ma passion pour le traitement de la langue, c’est évidemment dans cette optique que je le fais. La proximité des élections va faire émerger beaucoup de discours politiques, les sujets vont changer, de grands thèmes vont apparaître puis disparaître. C’est du miel pour les passionnés de l’analyse de texte (dont je suis). Mais pour analyser, il faut d’abord collecter du contenu, donc « scraper ».
J’ai découvert récemment l’excellent site vie-publique.fr, qui recense les discours et communications gouvernementales : plus de 140.000 discours qui remontent pour certains à 1974 (allocution présidentielle). Il est régulièrement alimenté plusieurs fois par jour avec les interviews des membres du gouvernement, les discours officiels, etc. Malheureusement, tout ça n’est pas en open-data (n’en demandons pas trop), donc il s’agit de pages HTML, sobrement formattées, qu’il va falloir collecter et décortiquer.
Utilisation de scrapy
scrapy est un outil open-source en python permettant de « descendre » le contenu d’un site. Il intègre toutes les parties standard d’un scraper/crawler (crawler, parce qu’il va falloir aller de page en page). Il gère donc :
- les requêtes HTTP,
- le lancement de plusieurs « threads » en parallèle,
- l’extraction de contenu à partir de directives CSS ou XPath
- les « pipelines » de traitement pour analyser le contenu
- les exports standard des données (en XML, JSON, … et tous formats qu’on voudra bien programmer)
Il ne reste qu’à écrire les fonctions spécifiques au(x) site(s) visé(s) et aux traitement que l’on veut effectuer sur les données. Scrapy est de plus doté d’un « shell » permettant de tester les différentes fonctions sur une page donnée et d’outils de mise en place d’un environnement. La documentation comporte un tutoriel plutôt bien fait pour démarrer. J’en reprends rapidement les grandes étapes.
Initier le projet
Après avoir installé scrapy (classiquement, via un pip install scrapy), la commande
scrapy start project politexts
va créer une arborescence avec l’ensemble des fichiers nécessaires à scrapy (initialisés avec des valeurs par défaut). En fait, l’essentiel de ce qui reste à faire, c’est d’écrire le « corps » du scraper, c’est-à-dire le code python qui va analyser les pages. Il devra se trouver dans le répertoire politexts/politexts/spiders
. Appelons-le vie-publique.py
.
Ecrire un spider
Scrapy regorge d’un grand nombre d’utilitaires, en particulier le LinkExtractor
. Il va collecter tous les liens qui se trouvent sur une page ou dans une zone donnée. Ca tombe bien, c’est justement ce que l’on veut faire : collecter tous les liens des discours présents sur une page pour aller les lire un par un. On délimite la zone, comme un peu partout avec Scrapy, avec une expression CSS ou XPath (voire un mélange des deux) et on lance le LinkExtractor dessus, charge à lui de collecter tous les liens qui s’y trouvent.
L’autre lien important qu’il faudra aller chercher, c’est la pagination, autrement dit un lien vers la prochaine page. Les pages sont bien formatées, le lien est explicitement marqué avec un rel=next
, facile à trouver donc. Ca nous donne un premier morceau de script comme suit (dans le répertoire spiders
):
import re
import scrapy
from scrapy import Request
from scrapy.linkextractors import LinkExtractor
class ViePubliqueSpider(scrapy.Spider):
name = 'vie_publique'
allowed_domains = ['vie-publique.fr']
start_urls = ['https://www.vie-publique.fr/discours']
link_extractor = LinkExtractor(restrict_css='div .teaserSimple--content')
def parse(self, response):
# Sur une page, on va chercher les discours référencés
for link in self.link_extractor.extract_links(response):
yield Request(link.url, callback=self.parse_speech)
# crawl de la page suivante
next_page = response.css('a[rel="next"]::attr(href)').get()
if next_page:
next_page = response.urljoin(next_page)
yield Request(next_page, callback=self.parse)
Notre « spider » est donc configuré pour s’appeler vie_publique
, son action est restreinte au domaine vie-publique.fr
et va commencer par collecter l’url https://www.vie-publique.fr/discours
. C’est le minimum pour déclarer un spider. A cela on. ajoute, pour les besoins du. projet, un LinkExtractor
qui sera limité aux éléments div
de classe teaserSimple--content
.
Comment trouver ces styles ? Comment décrire ces sélecteurs ? Le premier outil est le mode « développeur » de votre browser : il permet d’afficher le source HTML de la page et d’inspecter la hiérarchie. des éléments. Les styles permettent d’écrire les sélecteurs CSS, la hiérarchie les chemins XPath. Ces outils existent pour les développeurs Web sur Google, Firefox, Safari etc. Puis pour tester ces sélecteurs, scrapy propose un fort utile shell
qui permet d’avoir accès à un interpréteur Python en contexte d’une page, avec les objets Scrapy déjà instanciés.
Revenons au script: la méthode par défaut appelée sur les pages est parse()
, c’est celle que nous avons implémentée et qui sera rappelée pour chacune des « pages suivantes ». Pour les liens dans la page, nous avons indiqué explicitement que la méthode à appeler (« callback ») devrait être parse_speech()
qui va donc extraire le contenu d’un discours précis. Il y a un discours par page, bien formaté, il n’y a qu’à aller se servir. Voici le corps de la méthode:
def parse_speech(self, response):
yield {
"url": response.url,
"title": response.css("h1::text").get(),
"raw_text": response.css(".field--name-field-texte-integral").get(),
"date": response.xpath("//time/@datetime").get(),
"keywords": [tag.strip() for tag in response.css(".btn-tag::text").getall()],
"desc": response.css(".discour--desc > h2::text").get(),
"persons": {
"roles": response.css("ul.line-intervenant").css("li::text").getall(),
"names": response.css("ul.line-intervenant").css("li").css("a::text").getall()
}
Cette méthode produit un dictionnaire python qui sera sérialisé par Scrapy. Elle identifie les différentes zones de la page et construit l’objet en collectant titre, date, mots-clés, noms des intervenants.
Lancer la collecte
Pour finaliser la projet, il faut positionner quelques valeurs dans le fichier settings.py
, a minima, une valeur pour USER_AGENT
, qui signera votre forfait et un DOWNLOAD_DELAY
d’au moins 1. C’est le délai d’attente entre deux requêtes, afin de ne pas surcharger le serveur. Notre script vie-publique.py
assemblé avec les deux portions. ci-dessus, il ne reste qu’à le lancer:
scrapy crawl vie_publique -o vie-publique.json:jsonlines
L’option -o
donne le fichier de sortie, ici vie-publique.json
. Le :jsonlines
est un sélecteur de format de sortie qui. permet de ne pas réellement produire un fichier json mais une série de lignes JSON, chacune correspondant à un enregistrement. Cela permettra d’itérer sur le fichier produit, ligne après ligne afin d’utiliser son contenu, par exemple pour le mettre dans une base de données ou l’indexer dans un moteur de recherche. Il ne nous reste qu’à vous armer de patience le temps de la collecte et à vous l’analyse de la parole officielle depuis des décennies !
Un comptage
Un rapide comptage sur un premier crawl permet de se rendre compte de l’activité du site, plutôt importante, de 5 à 10 par jour pour le dernier quinquennat, avec une moyenne un peu plus élevée sur avant 2017. Faut-il y voir la différence de relations qu’entretiennent le président actuel et le précédent avec la presse ? Quoi qu’il en soit, la trêve de l’été est visible dans les comptages, avec une baisse de publications régulière au milieu de chaque année.

Utiliser les données
Le travail n’est pas terminé pour ce site, il reste en particulier à analyser les textes des interviews: les intervenants ne sont pas explicitement identifiés, autrement que par le texte, ce qui fait qu’on ne peut pas analyser le texte de façon fiable: impossible de déterminer ce qui est question de ce qui est la réponse. Le texte, dans ce cas, ressemble à cela:
<p>YVES CALVI<br>
Jean CASTEX, bonjour.</p>
<p>JEAN CASTEX<br>
Bonjour.</p>
<p>YVES CALVI<br>
Bienvenu, Monsieur le Premier ministre.</p>
<p>JEAN CASTEX<br>
Merci.</p>
C’est un problème typique d’analyse de contenu: il va falloir décortiquer en détail la structure du texte pour savoir si le « Bonjour » est d’Alba Ventura ou de Jean Castex, tout cela à partir d’informations visuellement explicites pour un humain mais bien moins pour une machine. Quelques regex devraient faire l’affaire, si le format a le bon goût d’être constant sur toute la période…
Une fois l’analyse faite, un format existe pour représenter ce type de choses, c’est la TEI (Text Encoding Initiative), format XML de représentation de textes. La TEI est un format modulaire, avec entre autres un module performance texts qui distingue bien les tours de parole et qui parle, de façon explicite (il semble avoir été fait initialement pour transcrire les pièces de théâtre). Notre corpus en TEI sera parfaitement exploitable pour analyser qui a dit quoi sur quel sujet (et quand).
Diffuser les données ?
Mon idée initiale était de proposer un encodage XML explicite de ce superbe corpus (au format TEI, donc) pour permettre à tous les acteurs du NLP d’en faire des analyses. Malheureusement, c’est impossible car ces contenus ne sont pas en licence libre. En particulier, les interviews sont propriété des média qui les ont diffusés. Pour les discours, on pourrait espérer une diffusion plus ouverte, mais ce n’est pas le cas. Il vous faudra donc répéter la collecte chez vous, si vous voulez tirer parti de cette mine d’informations.
Quant à moi, je vais me limiter (…) à faire des analyses sur la parole gouvernementale et à les publier si je trouve des choses intéressantes (j’y compte bien). Et si vous avez des idées d’analyse, n’hésitez pas à me les soumettre.
A suivre, donc…
2 commentaires sur “Scraper les discours politiques”