Illustration représentant un circuit imprimé. À gauche, les logos de Python, Jupyter (pour les notebooks) et Selenium sont connectés par des lignes lumineuses (circuits) qui convergent vers la droite. À droite, un logo d'horloge avec des traits symbolisant l'accélération du temps, indiquant un gain de rapidité. Le tout sur un fond sombre de carte mère.

Guide Selenium Python : coder rapidement grâce aux notebooks

Ce guide complet vous montre comment accélérer le codage de vos scripts Selenium Python en utilisant Jupyter Notebooks.

Développer des jobs Selenium en Python peut rapidement devenir frustrant lorsque chaque modification du script impose un redémarrage complet du programme. Que ce soit pour des tests automatisés ou du web scraping, cette contrainte ralentit considérablement le cycle de développement et freine nos ambitions.

Heureusement, il existe une solution simple et efficace : utiliser les notebooks Python. Cette approche permet de garder le navigateur actif entre les exécutions, d’éviter les relances inutiles afin de prototyper plus rapidement. Dans cet article, nous allons voir pourquoi cette méthode change la donne, et comment l’appliquer concrètement avec un exemple pratique : extraire les nouveautés de jeux vidéo sur Steam.

Pourquoi le développement Selenium est lent ? Les problèmes courants

L’un des principaux inconvénients de Selenium réside dans la lenteur du cycle de développement. Chaque fois que nous modifions notre script, nous devons relancer entièrement le programme, ce qui implique :

  • Temps de démarrage du WebDriver : ouvrir un navigateur, charger les extensions et initialiser la session prend plusieurs secondes.
  • Re-lancement complet du script : même pour tester une petite modification, il faut exécuter le scénario depuis le début.
  • Impact sur la productivité : ces délais s’accumulent et ralentissent considérablement le développement de nouvelles fonctionnalités ou tests.

En pratique, cela signifie que la moindre erreur de nommage, de sélecteur ou de logique nous fait perdre énormément de temps. Résultat : moins d’efficacité, plus de frustration.

Solution : Jupyter Notebooks pour vos développement Selenium et Python

Pour contourner ces limitations, je me suis inspiré des pratiques des data scientists et des développeurs IA : utiliser des notebooks Python. Pourquoi cette approche est-elle si efficace ?

  • Travailler avec un contexte chaud : Les notebooks offrent un environnement interactif similaire à l’IDLE Python, permettant d’exécuter des commandes et de visualiser immédiatement les résultats. Contrairement à l’IDLE, ils conservent le code dans des cellules réutilisables, facilitant l’exécution répétée de blocs d’instructions spécifiques.
  • Garder le WebDriver actif entre les cellules : Contrairement à un script classique, un notebook permet d’exécuter notre code par blocs. Nous pouvons donc initialiser le driver Selenium une seule fois, puis exécuter tout ou partie des blocs suivants sans avoir à relancer le navigateur à la moindre modification.
  • Développement modulaire et itératif : Avec les notebooks, vous pouvez isoler les étapes clés (connexion, navigation, extraction des données) dans des cellules distinctes. Ces étapes peuvent être jouées une seule fois, ou rejouée afin de faciliter le debugging et le prototypage.

En résumé, en supprimant les relances inutiles, nous réduisons drastiquement le temps d’attente. Le cycle de développement est plus fluide, ce qui est idéal pour des tests automatisés ou du Web scraping multi-pages.

Tutorial complet : Web scraping des nouveautés sur Steam avec Selenium

L’objectif de ce cas pratique est d’illustrer l’intérêt des notebooks Python avec Selenium. Le notebook que nous allons créer permettra d’extraire les nouveautés de la plateforme Steam en développant de manière itérative.

Étape 1 : Initialiser le WebDriver

Dans mon exemple, je vais utiliser le driver Chrome :

from selenium import webdriver


# Create driver
driver = webdriver.Chrome()

Étape 2 : Accéder à la page des nouveautés

Ouverture de la page d’accueil avec Selenium

Nous commençons par naviguer vers la page d’accueil de Steam :

# Load Steam homepage
driver.get("https://store.steampowered.com/")

Réimporter les modules manquants

Pour naviguer vers la section des nouveautés, nous devons reproduire les clics sur le menu.

Pour sélectionner les éléments sur lesquels cliquer, nous avons besoin d’un sélecteur. Il faut donc ajouter l’import from selenium.webdriver.common.by import By.

Nous divisons alors la première cellule en deux blocs :

  • Un bloc d’import où nous ajoutons l’import de By
  • Un second bloc où nous initialisons le driver

Ainsi, seule l’exécution de la cellule d’import est nécessaire. Nous conservons l’état opérationnel du driver sans avoir à le réinitialiser.

Navigation vers la section des nouveautés

Les éléments du menu peuvent être sélectionnés à l’aide de différents types de sélecteurs : CSS, ID, XPATH, etc.

Pour identifier le bon sélecteur, nous devons inspecter la page et trouver la meilleure façon de cibler le menu « Nouveaux et à découvrir ». Dans notre cas, il suffit de cliquer sur le menu ayant pour ID noteworthy_tab :

# Click on "Nouveaux et à découvrir" menu
driver.find_element(By.ID, "noteworthy_tab").click()

Cette étape ouvre un sous-menu. J’ajoute donc une nouvelle cellule pour trouver et vérifier que le sélecteur du sous-menu est correct.

Après plusieurs tentatives, j’ai finalement trouvé un sélecteur relativement générique en deux étapes :

# Click on "Nouveautés" sub menu
driver.find_element(By.ID, "noteworthy_flyout") \
    .find_element(By.XPATH, ".//a[contains(@class, 'popup_menu_item') and contains(text(), 'Nouveautés')]") \
    .click()

Ici encore, j’ai pu retester en boucle la bonne méthode de sélection sans avoir à relancer mon programme depuis le début.

Étape 3 : Extraire les données

Nous allons maintenant extraire les données des nouveautés. Pour cela, nous devons identifier les éléments contenant les informations que nous souhaitons récupérer.

Ouvrir l’onglet « Nouveautés »

En analysant la structure de la page, nous constatons que la liste de jeux est déjà présente dans le DOM, mais elle est cachée par une règle CSS. Il est donc impossible d’extraire directement les informations avec Selenium (un élément masqué retourne du texte vide).

Pour y parvenir, nous devons cliquer sur l’onglet « Nouveautés » (à côté de « Sorties populaires ») afin de l’afficher.

# Click on "Nouveautés" tab
driver.find_element(By.ID, "tab_allnewreleases_content_trigger").click()

Extraire les informations d’un jeu par itération grâce au notebook

L’objectif de cette étape est d’extraire les informations utiles d’un jeu :

  • titre
  • prix
  • catégories
  • plateformes compatibles
  • URL de l’image de couverture

Dans une première cellule, je récupère la liste des éléments contenant les informations du jeu :

new_game_anchors = driver.find_elements(
    By.CSS_SELECTOR,
    "#tab_allnewreleases_content a.tab_item"
)

Puis, par itération, je tente d’extraire chaque information du premier élément de la liste :

el = new_game_anchors[0]

image_url = el.find_element(
    By.CSS_SELECTOR, "img.tab_item_cap_img"
).get_attribute("src")

title = el.find_element(
    By.CSS_SELECTOR, "div.tab_item_content .tab_item_name"
).text

price = el.find_element(
    By.CSS_SELECTOR, "div.discount_block .discount_final_price"
).text

tags = [
    tag.text.lstrip(", ")
    for tag in el.find_elements(
        By.CSS_SELECTOR, ".tab_item_top_tags .top_tag"
    )
]

platforms = [
    platform.get_attribute("class").split(" ")[-1]
    for platform in el.find_elements(
        By.CSS_SELECTOR, ".tab_item_details .platform_img"
    )
]

image_url, title, price, tags, platforms

Cette étape est fastidieuse, mais en utilisant l’approche itérative du notebook, j’ai pu aller beaucoup plus vite que si je devais relancer mon programme depuis le début à chaque nouvelle instruction.

Extraction complète des données

Maintenant que nous avons trouvé la méthode d’extraction des informations pour un premier jeu, nous allons modifier la cellule pour traiter l’ensemble des jeux. Cette modification nous permettra d’obtenir une liste complète des informations sous forme de dictionnaires.

new_games = []

for el in new_game_anchors:
    image_url = el.find_element(
        By.CSS_SELECTOR, "img.tab_item_cap_img"
    ).get_attribute("src")

    title = el.find_element(
        By.CSS_SELECTOR, "div.tab_item_content .tab_item_name"
    ).text

    price = el.find_element(
        By.CSS_SELECTOR, "div.discount_block .discount_final_price"
    ).text

    tags = [
        tag.text.lstrip(", ")
        for tag in el.find_elements(
            By.CSS_SELECTOR, ".tab_item_top_tags .top_tag"
        )
    ]

    platforms = [
        platform.get_attribute("class").split(" ")[-1]
        for platform in el.find_elements(
            By.CSS_SELECTOR, ".tab_item_details .platform_img"
        )
    ]

    new_games.append(
        {
            "image_url": image_url,
            "title": title,
            "price": price,
            "tags": tags,
            "platforms": platforms,
        },
    )

new_games

Dans cet exemple, je n’ai rencontré aucun problème et j’ai obtenu directement les informations complètes sur les jeux.

Néanmoins, certains sites sont beaucoup plus complexes. Certains éléments ne présentent pas toujours la même structure, soit à cause de données manquantes, soit à cause de widgets intégrés comme des publicités ou des composants « vous pourriez aussi aimer…​ », etc.

Cette généralisation peut devenir laborieuse. Bénéficier d’un contexte chaud grâce au notebook permet d’éviter les pertes de données et de réduire considérablement le temps de développement.

Optimisations supplémentaires : mode Headless et performance

Lorsque nous faisons des tests unitaires avec Selenium ou du Web scrapping, il est rare d’avoir besoin de récupérer l’ensemble des données de la page. Ce qui nous intéresse réellement, c’est d’accéder à la page une fois que JavaScript a été exécuté, et que le contenu ou la structure finale de la page est disponible.

Il est donc envisageable de réduire le chargement de certaines ressources pour accélérer le processus :

  • Désactiver le chargement des ressources,
  • Éviter de lancer une fenêtre graphique.

Réduire le chargement des ressources avec Selenium (Chrome & Firefox)

L’objectif est de minimiser le temps de chargement pour accélérer nos itérations pendant le prototypage. Pour cela, nous pouvons bloquer le chargement des images, CSS, JavaScript ou autres ressources non essentielles comme les polices ou les vidéos.

Par exemple, pour bloquer le chargement des images pour Chrome et le CSS, il est possible d’ajouter les instructions suivantes :

# Disable images
options = webdriver.ChromeOptions()

# ...
options.add_experimental_option(
    "prefs",
    {
        "profile.managed_default_content_settings.images": 2, # <1>
    }
)

# ...

# Create driver
driver = webdriver.Chrome(options=options)

# Disable CSS <2>
driver.execute_cdp_cmd("Network.setBlockedURLs", {"urls": ["*.css"]})
driver.execute_cdp_cmd("Network.enable", {})
1

Pour chrome, la désactivation du chargement des images passe par des options.

2

Pour désactiver le chargement des CSS, nous devons bloquer les requêtes qui téléchargent les fichiers CSS.

Idem pour Firefox, mais l’approche est légèrement différente :

# Initialize FirefoxOptions
options = webdriver.FirefoxOptions()

# Disable Images
options.set_preference("permissions.default.image", 2) # <1>
options.set_preference("permissions.default.stylesheet", 2) # <1>
1

2 signifie « bloquer ».

Si vous voulez en savoir plus sur la désactivation du chargement des autres ressources pour les drivers Chrome et Firefox, je vous invite à consulter l’article Selenium Guide: How to Block Images and Resources.

Activer le mode headless avec Selenium

Lorsque nos jobs de tests automatisés ou de Web scraping sont prêts à être exploités, il est préférable de lancer le navigateur en mode headless, c’est-à-dire sans interface graphique. Cela permet d’économiser des ressources et donc d’accélérer l’exécution du programme.

Si vous notez des temps d’exécution relativement longs durant la phase de développement, le mode headless peut également être une bonne solution pour accélérer votre prototypage. D’un côté, vous gardez votre vrai navigateur pour analyser le code source de la page et identifier vos sélecteurs, et de l’autre vous testez les instructions dans le navigateur headless.

Voici comment configurer le mode headless pour Chrome et Firefox :

# ...

# Headless mode for Chrome et Firefox
options.add_argument("--headless")

Pour vous donner un ordre d’idée, sur mon ordinateur, j’ai observé une réduction moyenne de de 40 % à 50 % sur le temps de lancement du driver et le temps d’exécution des instructions lorsque j’utilise le mode headless de Firefox. En revanche, avec Chrome, la réduction est beaucoup moins perceptible.

Conclusion

En utilisant les notebooks Python avec Selenium, nous gagnons en flexibilité et en rapidité. Cette approche nous permet de prototyper plus vite, de tester des parties de code sans tout relancer et d’optimiser nos cycles de développement.

En combinant cette méthode avec des astuces simples comme :

  • Réduire le chargement des ressources (images, CSS, polices),
  • Activer le mode headless pour économiser des ressources,

nous pouvons accélérer nos scripts Selenium de manière significative, que ce soit pour des tests automatisés ou du Web scraping.

Et vous, avez-vous déjà testé les notebooks avec Selenium ? avez-vous d’autres astuces pour accélérer vos développements ? N’hésitez pas à partager vos expériences en commentaires !

Laisser un commentaire