# TP - Utiliser la navigation web automatique pour constituer un corpus

Alexander Delaporte

<figure><img src="image_1.png" /></figure>

## To cite this version:

Alexander Delaporte. TP - Utiliser la navigation web automatique pour constituer un corpus. Master. Université de Lille, France. 2025. hal-04906431

HAL Id: hal-04906431

https://hal.science/hal-04906431v1

Submitted on 22 Jan 2025

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_2.png" /></figure>
# Utiliser la navigation web automatique pour constituer un corpus

Dans cet exercice, vous allez constituer un corpus textuel à partir de données du web. Mais ces données se cachent derrière quelques obstacles : vous allez devoir mettre en place une navigation automatique avant de pouvoir y accéder !

## Avant de commencer

### Quelques mots à propos des notebooks Jupyter

Ce TP se présente sous forme de notebook Jupyter. C'est un format de document interactif qui se divise en cellules de différents types.

Dans cet exercice, vous allez rencontrer :

* Des cellules Markdown, qui contiennent du texte structuré au format markdown. Ces cellules correspondent aux consignes et explications.

* Des cellules Code, qui contiennent du code en Python que vous pouvez exécuter en exécutant la cellule correspondante.

Pour exécuter une cellule, vous pouvez utiliser différentes méthodes :

* Appuyer sur Shift + Enter pour exécuter la cellule courante et passer à la cellule suivante.

* Appuyer sur Ctrl + Enter pour exécuter la cellule courante sans passer à la cellule suivante.

* Cliquer sur le bouton ▶ pour exécuter la cellule courante et passer à la cellule suivante (équivalent à Shift + Enter).

> Le contenu du notebook est entièrement interactif et modifiable : selon vos besoins, vous pouvez modifier le contenu des cellules, ajouter de nouvelles cellules, réorganiser ou supprimer des cellules existantes. N'hésitez pas à faire des essais et ajouter vos annotations !

## Quelques mots à propos de Python

Nous n'avons ni l'espace ni le temps pour une introduction complète à Python, mais voici ce dont vous avez besoin pour réaliser l'exercice.

### Librairies et modules

Les modules sont des fichiers Python préexistants que vous pouvez importer dans votre propre programme avec l'instruction import. Les modules peuvent être regroupés en librairies. Certains modules font partie de la librairie standard de Python, c'est-à-dire qu'ils sont préinstallés et peuvent directement être importés dans tout programme Python. Pour les autres modules et librairies, il faudra les installer dans votre environnement avant de pouvoir les importer.

### Types de données

Parmi les nombreux types de données disponibles, vous allez manipuler trois types natifs de Python : str, list et dict.

### Chaînes de caractères (type str)

Le type str correspond aux chaînes de caractères (documentation). Pour déclarer une nouvelle chaîne de caractères, celle-ci doit être entourée de guillemets ("" ou '').

Par exemple :

In [ ]: petite_loutre = "La loutre cendrée est la plus petite des loutres."

In [ ]: grande_loutre = 'La loutre géante est la plus grande des loutres.'

Il est possible de concatenéner des chaînes de caractères avec l'opérateur +.

In [ ]: petite_loutre + grande_loutre
Comme dans d'autres langages de programmation, en Python une *fonction* est un bloc d'instructions qui peut être réutilisé à différents endroits du code. Une fonction peut prendre des paramètres ou non, et doit être déclarée avant de pouvoir être appelée dans le programme.

Une **méthode** est une fonction propre à une classe d'objets. Elle agit sur les instances issues de cette classe et doit être déclarée à l'intérieur de la classe.

## Appeler une fonction ou une méthode

La syntaxe n'est pas la même selon si ce qu'on veut utiliser est une fonction ou une méthode.

Appeler une fonction se fait avec la syntaxe `nom_fonction()` si la fonction ne prend pas de paramètres, et `nom_fonction(paramètres)` si elle en prend.

Par exemple, la fonction `len()` (documentation) qui renvoie la longueur de l'objet qu'on lui passe en paramètre :

In [ ]: len(loutres)

In [ ]: len("Les loutres de mer ont une poche où elles rangent leur caillou préféré.")

Les méthodes s'appliquant aux instances d'une classe d'objets donnée, la syntaxe pour appeler une méthode est `nom_instance.nom_méthode()` si la méthode ne prend pas de paramètres, et `nom_instance.nom_méthode(paramètres)` si elle en prend.

Par exemple, la classe `str` comporte une méthode `replace()` qui permet de remplacer une sous-chaîne de caractères. Elle prend en paramètres obligatoires la sous-chaîne à remplacer, et la suite de caractères à lui substituer :

In [ ]: loutre = "Loutre d'Europe" loutre.replace("d'Europe", "cendrée")

Une autre méthode de la classe `str` est `split()`, qui renvoie une liste (type `list`) représentant le texte segmenté suivant un séparateur donné.

Sans paramètre, l'espace est le séparateur par défaut :

In [ ]: texte = "Les loutres de mer font partie de la famille des mustélidés." texte.split()

Mais il est aussi possible de passer en paramètre un caractère ou une chaîne de caractères au choix, qui deviendra alors le séparateur :

In [ ]: texte.split("de")

La classe `list` a une méthode `append()` qui permet d'ajouter un élément à la fin de la liste. Elle prend en paramètre obligatoire l'élément à ajouter.

In [ ]: loutres.append("Moustillon")

In [ ]: loutres

... Et une méthode `pop()` qui supprime le dernier élément de la liste si on ne lui donne pas de paramètre :

In [ ]: loutres.pop()

(vous remarquerez qu'elle renvoie également la valeur de l'élément supprimé de la liste)

In [ ]: loutres

## Définir une fonction

La définition d'une fonction se fait avec l'instruction `def`. En Python, **les indentations structurent le code et sont obligatoires**, il est donc impératif d'indenter correctement les instructions qui font partie de la fonction.

In [ ]: def bienvenue(): print("Bonjour et bienvenue dans le fan-club des loutres cendrées !")

Maintenant que la fonction est définie, elle peut être appelée avec `bonvenue()`.

In [ ]: bienvenue()

Petite modification de la fonction `bonvenue()` en ajoutant un paramètre :

In [ ]: def bienvenue(nom):
Avec Pip :

```
cmd
pip install selenium
pip install browsermanager
```

Si ce n'est pas le cas (par exemple, si vous avez ouvert ce notebook via Binder) vous pouvez ignorer cette étape.

## Imports

Exécutez la cellule ci-dessous pour importer les librairies et modules dont vous aurez besoin.

```python
In [1]: # Selenium
from selenium import browser
from selenium import keys
from selenium import driver
from selenium import browserkeys
from selenium import browsermanager
from browsermanager import browser
from browsermanager import browsermanagerkeys
```

# Déclarer un navigateur virtuel pour accéder à la page de départ

## Se familiariser avec la navigation automatique

Nous allons d'abord faire un essai pour tester la navigation automatique sur une tâche simple.

Voici une fonction permettant de déclarer un nouvel objet `browser`. Issu de la librairie `selenium`. Celui-ci simule un navigateur web Firefox et va permettre de mettre en place la navigation automatique.

Cette fonction est prête à être utilisée, exécutez la cellule ci-dessous sans la modifier.

```python
In [1]: def set_driver():
    """Crée un browser Firefox avec option headless"""
    options = browser.FirefoxOptions()
    options.add_argument("--headless")
    new_driver = browser.Firefox(service=Service(GeckoDriverManager().install()), options(options))
    return new_driver
```

La fonction `set_driver()` est maintenant disponible pour créer un nouveau browser, c'est-à-dire un navigateur virtuel.

Stocker le résultat de `set_driver()` dans une variable va permettre de manipuler le browser et lui envoyer des commandes. Dans la cellule ci-dessous, déclarez une variable `driver` et associez-lui le résultat de la fonction `set_driver()`.

```python
In [1]: # Déclarez une variable driver pour stocker le résultat de set_driver()
```

Si tout s'est bien passé, `driver` peut accéder à des pages web et en lire le contenu. C'est ce à quoi sert la méthode `get()`, qui prend en paramètre une chaîne de caractères correspondant à l'URL à interroger.

Dans la cellule suivante, utilisez `get()` pour accéder à ce site :

* Il s'agit d'une méthode de driver, la syntaxe à utiliser sera donc `driver.get(url)`.

* Selon votre préférence, vous pouvez ou non préalablement associer l'URL à une variable.

```python
In [1]: # Utilisez la méthode get() pour accéder à l'URL du lien "ce site"
```

En l'état, difficile de savoir si ça a fonctionné ou non. Essayons de récupérer des informations !

Vous pouvez ouvrir manuellement la page dans le navigateur de votre choix pour en inspecter le code source avec les outils de développement, ou consulter directement le code source de la page web sur Github.

Nous allons nous attaquer au nom de l'auteur du site (et du présent exercice). Lorsqu'on observe la page web, celui-ci se trouve sous la photo, dans l'encart présenté en colonne de gauche. Le voici dans le code source :

```html
<h4 class="card-title"><strong>Alexander Delaporte</strong></h4>
```

Pour cibler un élément particulier, il faut utiliser la méthode `find_element()` de `driver`. Celle-ci prend deux paramètres :

* Un objet de la classe `By` qui définit sur quelle caractéristique se baser pour identifier l'élément (`locator`, dans le jargon de `selenium`). Par exemple, si l'on essaie de récupérer un élément HTML sur la base de son identifiant, ce
sera By.ID.

* La valeur du locator de l'élément dans le code source.

Concrètement, l'élément que nous avons ici a 2 locators facilement repérables : son tag `h4` et sa classe `card-title`.

In [ ]:
```python
nom_element = driver.find_element(By TAG_NAME, "h4")
print(nom element.text)
```

In [ ]:
```python
nom element = driver.find_element(By. CLASS_NAME, "card-title")
print(nom element.text)
```

In [ ]:
```python
# Un autre locator pourrait être exploité, l'avez-vous trouvé ?
# Faites des essais en remplaçant les XXX dans l'extrait de code ci-dessous
nom element = driver.find_element(By.XXX, "XXX")
print(nom element.text)
```

Note : la syntaxe `.text` indique que `text` est un attribut de `nom_element`. Celui-ci correspond au contenu textuel de l'élément, lorsqu'il existe.

In [ ]: nom = nom_element.text

In [ ]: f"Le site alxdrdelaporte.github.io appartient à {nom}"

Mais qu'est-ce que l'option `--headless` ? Commentez la ligne `options.add_argument("
--headless")` dans la déclaration de la fonction `set_driver()` et regardez ce qu'il se passe, la différence devrait vous sauter aux yeux !
N'oubliez pas de commencer par réexécuter les cellules contenant la déclaration de `set_driver()` et celle de `driver` pour les actualiser.

## Planifier la navigation à automatiser

Mettons notre driver de côté pour le moment.

Avant de mettre en place la navigation automatique, le plus simple est de commencer par effectuer le parcours manuellement. Ouvrez ce joli site web dans votre navigateur et promenez-vous dessus jusqu'à trouver comment afficher un court texte sur les manchots empereurs, disponible en huit langues différentes. Ce sont ces huit versions du texte qui vont composer notre corpus textuel multilingue.

<figure><img src="image_3.png" /></figure>

Maintenant que vous avez une idée assez claire du parcours à suivre pour récupérer les données, vous pouvez vous pencher sur l'automatisation. Gardez quand même votre navigateur ouvert pour consulter le code source, vous en aurez besoin.

## Préparer le navigateur virtuel

... En fait, c'est déjà fait !

Vous pouvez reprendre le driver que vous avez utilisé précédemment. Il est généralement plus prudent d'activer l'option `--headless` pour ne pas interagir par erreur avec le navigateur virtuel, mais vous pouvez la désactiver pendant la réalisation de l'exercice pour avoir une meilleure visibilité sur votre travail.

Si vous voulez modifier votre choix d'option pour driver, pensez à actualiser `set_driver()` et `driver` en réexécutant les cellules correspondantes.

Dans la cellule ci-dessous, faites en sorte que driver accède à l'URL du site.

In [ ]: # Envoyez driver sur l'URL du lien "ce joli site web"

## Compléter et valider le formulaire pour accéder à la liste des textes disponibles

La page d'accueil du site comporte un formulaire, qu'il faut remplir pour accéder à la liste des textes. Le formulaire se compose de 3 éléments :

1. Un champ texte à compléter avec le texte demandé

2. Une checkbox à cocher

3. Un bouton sur lequel cliquer pour valider le tout

## Remplir le champ texte
In [ ]: # Vérifiez de quel bouton il s'agit

Quels paramètres avez-vous utilisé pour la méthode `findfind_elemen`t() , et quel bouton avez-vous récupéré ?

Obtenir la liste des boutons

Avec `find_elemen()` , d'une façon ou d'une autre vous avez probablement fini par récupérer le premier bouton... voire un autre bouton dont l'attribut `text` est vide si vous avez essayé de passer `By TAG_NAME`, "button" en paramètres (si c'est le cas, avez-vous compris de quel bouton il s'agit ?). Et les autres ? `find_elemen()` renvoie un élément unique, et comme le code source de ces boutons est très similaire, difficile d'en viser un en particulier.

Vous avez certainement compris que récupérer individuellement chaque bouton n'est pas adapté dans ce cas, d'autant plus qu'il va falloir ensuite leur appliquer un traitement identique. Nous allons plutôt utiliser la méthode `find_elements()` de `driver`, semblable à `find_elemen()` mais qui retourne une liste (type `list`) de l'ensemble des éléments du code source correspondant au critère passé en paramètre.

Complétez la cellule `ci-dessous` pour que `boutons` corresponde à la liste des boutons :

In [ ]: # Remplacez XXX pour que boutons corresponde à l'ensemble des boutons boutons = driver.find_elements(XXX)

Il y a huit boutons, la liste `bouton` devrait donc contenir 8 éléments. Dans la cellule `ci-dessous`, affichez le nombre d'éléments que comporte la liste `boutons` :

In [ ]: # Affichez le nombre d'éléments contenus dans la liste boutons

Avant de traiter l'ensemble des boutons, nous allons en choisir un avec lequel travailler dans un premier temps. Dans la cellule `ci-dessous`, associez le bouton de votre choix à la variable `bouton_test` et donnez l'instruction de cliquer dessus.

Pour la valeur de `bouton_test`, vous pouvez au choix :

* Reprendre la valeur de la variable `un_bouton` déclarée précédemment (sauf s'il s'agit du bouton dont l'attribut `text` n'a pas de valeur associée)

* Utiliser la méthode `find_elemen()`

* Ou, plus simple, sélectionner un item de la liste `boutons`

In [ ]: # Associez le bouton de votre choix à la variable bouton_test # Cliquez dessus

La fenêtre contenant le texte est apparue, mais à quel élément correspond-t-elle dans le code source ?

Ouvrez manuellement le site web dans un onglet de votre navigateur, puis naviguez jusqu'à cliquer sur le même bouton et afficher la fenêtre de texte. Observez le code source de la page à l'aide de l'inspecteur de code de votre navigateur (Ctrl + Shift + k sous Firefox, Ctrl + Shift + j sous Chrome ou Edge).

... La fenêtre n'y est pas ! Vous l'aviez peut-être déjà deviné en voyant son aspect graphique :

<figure><img src="image_4.png" /></figure>

Il s'agit d'une fenêtre pop-up, c'est-à-dire une fenêtre modale dont l'ouverture est déclenchée par un script JavaScript.

Comme `driver` ne change pas automatiquement de fenêtre, il est encore en train d'explorer la page qui se trouve maintenant en arrière-plan. Heureusement, il dispose d'un attribut `switch_to` qui permet de lui indiquer explicitement sur quoi se focaliser. La syntaxe à utiliser est `driver.switch_to.focus`, où la propriété `focus` est à choisir parmi une liste de possibilités, par exemple `driver.switch_to.active_elemen` pour cibler l'élément actif.

En vous aidant de la documentation, complétez la cellule `ci-dessous` pour forcer `driver` à explorer la fenêtre modale.

In [ ]: # Remplacez XXX pour que driver se focalise sur la fenêtre modale fenetre_modale = driver.switch_to.XXX # Faites un print du contenu textuel de la fenêtre modale pour vérifier que ça a fonctionné

Si le texte a été récupéré correctement, vous pouvez refermer le pop-up en utilisant sa méthode `accept()` (action équivalente à cliquer sur son bouton OK).

In [ ]: # Refermez le pop-up

Maintenant que nous avons un traitement qui fonctionne pour un bouton, il est temps de l'appliquer à l'ensemble des boutons à l'aide d'une boucle `for` !

Dans la cellule `ci-dessous`, commencez par écrire une boucle qui affiche le texte de chaque bouton (c'est-à-dire les
noms des langues présents sur les boutons, pas le texte contenu dans le pop-up).

In [ ]: # Affichez le texte de chaque bouton à l'aide d'une boucle for

Ça fonctionne ? Il ne reste plus qu'à tout rassembler. Conservez la boucle, mais remplacez l'instruction `print()` par le traitement que vous aviez appliqué à `bouton_test` pour afficher le texte de la fenêtre modale.

## Indice

**Facultatif :** Vous devrez perfectionner cette boucle à plusieurs reprises par la suite. Vous pouvez tout à fait simplement copier-coller le code d'une cellule à l'autre, mais si vous préférez vous pouvez également créer une ou plusieurs fonctions.

In [ ]: # En vous basant sur la boucle précédente, appliquez votre traitement de bouton_test à l'ensemble des boutons

Pas mal ! Cependant, extraire le texte ne constitue pas un corpus : en l'état, on ne pourrait pas effectuer d'analyse exploitable de ces contenus qui mélangent données et métadonnées, dans la plupart des cas exprimées dans des langues différentes.

Regardez la sortie produite par la cellule précédente et/ou l'aspect du texte directement dans son pop-up sur le site. Voyez-vous un séparateur qui vous permettrait de séparer les différents éléments composant le contenu textuel ? Le texte extrait de la fenêtre modale étant de type `str`, vous pouvez utiliser la méthode `split()` vue précédemment pour le segmenter.

In [ ]: # Copiez-collez le code de la cellule précédente # Modifiez-le pour segmenter le texte avant de l'afficher

Vous devriez avoir, pour chaque version du texte, une liste dans laquelle chaque item correspond soit à une métadonnée, soit au texte à proprement parler. Modifiez votre boucle pour y associer les données et métadonnées à des variables, puis utilisez un `print()` pour vérifier que vos variables sont associées à la valeur attendue.

## Indice

In [ ]: # Copiez-collez le code de la cellule précédente # Modifiez-le pour associer les (méta)données à des variables

En fait, il y a une autre métadonnée intéressante à récupérer : l'indication de la langue à laquelle correspond chaque texte. Elle ne fait pas partie du contenu du pop-up, mais vous pouvez la récupérer dans le bouton. Dans la cellule ci-dessous, reprenez votre code et ajoutez-y la déclaration de la variable `langue`, dont la valeur est la langue dans laquelle est rédigée le texte correspondant à chaque bouton.

Vous savez déjà récupérer l'indication telle qu'elle apparaît sur le bouton, mais il y a aussi un moyen d'obtenir le nom de la langue traduit en français. Pour l'extraire, la méthode `get Dom attribute()` des objets `WebElement` vous sera utile : pour rappel, elle prend en paramètre le nom de l'attribut d'un `WebElement` et en retourne la valeur.

In [ ]: # Copiez-collez le code de la cellule précédente # Modifiez-le pour ajouter la variable langue

Dernier détail, les indications de l'URL source et de la licence telles qu'elles se présentent dans le pop-up comportent du texte superflu pour notre corpus.

Dans la cellule suivante, reprenez à nouveau votre code et modifiez la déclaration des variables correspondant à l'URL source et à la licence pour que leurs valeurs respectives correspondent uniquement à l'URL (par exemple, https://fr.wikipedia.org/wiki/Manchot_empereur à la place de Source : https://fr.wikipedia.org/wiki/Manchot_empereur) et uniquement à la licence (par exemple, CC BY-SA 4.0 à la place de Licence = Sous licence CC BY-SA 4.0). Une méthode de la classe `str` vue plus tôt vous permettra d'effectuer facilement cette modification.

In [ ]: # Copiez-collez le code de la cellule précédente # Modifiez-le pour nettoyer le texte de l'URL source et de la licence

Le plus dur est fait : nous avons toutes les données nécessaires pour constituer notre corpus. Seulement, elles ne sont pour l'instant stockées nulle part, ce qui n'est pas pratique et va grandement compliquer les choses pour leurs appliquer d'éventuels traitements par la suite.

Dans la cellule ci-dessous, une liste vide `donnees_corpus` est déjà déclarée. Sans modifier la déclaration de la liste, utilisez à nouveau votre boucle `for` afin d'extraire les données, en ma modifiant de cette façon :

* Remplacez l'instruction `print()` par la déclaration d'un dictionnaire `donnees_texte` contenant les (méta)données extraites.

* Ajoutez `donnees_texte` à la liste `donnees_corpus`.

In [ ]: donnees_corpus = []
```python
# Copiez-collez le code de la cellule précédente
# Modifiez-le pour stocker les données dans un dictionnaire donnees_texte, puis ajouter le dictionnaire à la li
```

Vérifiez le résultat obtenu en appelant la variable `donnees_corpus` :

```python
In [1]: # Appelez donnees_corpus pour vérifier son contenu
# (Si la lecture est trop inconfortable, vous pouvez aussi utiliser les 2 cellules suivantes sans les modifier
```

Voici une fonction qui vous permettra d'exporter les données dans un fichier texte. Vous pouvez l'utiliser, sans la modifier, pour visualiser plus confortablement le contenu de `donnees_corpus`.

```python
In [1]: def dicos2fichier(liste_dicos, fichier):
    with open(fichier, "w", encoding="utf-8") as cible:
        for dico in liste_dicos:
            for cle, valeur in dico.items():
                cible.write(f"{cle} = {valeur}\n")
            cible.write("\n-----------\n\n")
```

```python
In [1]: # Décommentez la ligne ci-dessous pour exporter le contenu de donnees_corpus dans le fichier donnees_corpus.txt
# dicos2fichier(donnees_corpus, "donnees_corpus.txt")
```

Bravo, vous avez mis en place une navigation web automatique pour constituer les données d'un corpus textuel !

## Aller plus loin

Vous avez terminé mais vous voulez encore vous amuser avec Python pour la constitution de corpus linguistique ?

Voici quelques suggestions :

* Essayez d'écrire des fonctions, si vous ne l'avez pas déjà fait. Vous pouvez répartir les traitements entre plusieurs fonctions ; pour information, il est possible d'appeler une fonction à l'intérieur d'une autre fonction.

* Ajoutez une étape de validation de l'URL source, par exemple en vérifiant que le titre principal de la page web et celui du texte extrait du pop-up sont identiques.

* Ici, les données ont été extraites et nettoyées mais pas vraiment structurées. Il ne manque pas grand chose pour en faire un corpus réellement exploitable, au format XML. Si vous souhaitez essayer, je vous conseille de partir de la liste `donnees_corpus` que vous avez déjà constituée, et de l'intégrer à une structure XML en vous aidant des documents suivants :

* TP Construire un corpus XML à partir du web (en particulier son corrigé).

* Article Produire un corpus web format XML en 15 lignes de code sur Tekipaki.

* Article Déclaration XML automatique sur Tekipaki.

* Tout ce que vous voulez ! Si vous avez une autre idée, vous pouvez essayer de la mettre en place.

Utiliser la navigation web automatique pour constituer un corpus par Alexander Delaporte est mis à disposition selon les termes de la licence Creative Commons CC BY-SA 4.0.

<figure><img src="image_5.png" /></figure>