Les bases et les applications de la syntaxe async/await en Python

La syntaxe async/await de Python joue un rôle crucial en permettant de traiter de manière concise les tâches asynchrones, en particulier dans les applications manipulant des tâches liées aux entrées/sorties (I/O) ou traitant de nombreuses requêtes. Cet article vous guidera à travers les concepts fondamentaux de cette syntaxe, ainsi que des exemples pratiques et des cas d’utilisation avancés. Apprenez les bases de la programmation asynchrone et approfondissez votre compréhension avec des exemples de code concrets.

Sommaire

Concepts fondamentaux de la syntaxe async/await


La syntaxe async/await de Python est un ensemble de mots-clés permettant d’implémenter la programmation asynchrone de manière concise. En les utilisant, il devient possible de traiter efficacement les opérations longues (comme les opérations I/O) et d’améliorer la réactivité du programme.

Qu’est-ce que la programmation asynchrone ?


La programmation asynchrone est une technique permettant à un programme d’exécuter d’autres tâches pendant qu’il attend la fin d’une tâche en cours. Contrairement à la programmation synchrone, où chaque tâche est exécutée une à une, la programmation asynchrone permet l’exécution « simultanée » de plusieurs tâches.

Rôle de async et await

  • async : Utilisé pour définir une fonction asynchrone. Cette fonction est appelée une coroutine et peut appeler d’autres traitements asynchrones via await.
  • await : Utilisé pour attendre le résultat d’un traitement asynchrone. Pendant ce temps, d’autres tâches peuvent être exécutées, ce qui améliore l’efficacité globale du programme.

Exemple de base d’utilisation


Voici un exemple simple de la syntaxe async/await :

import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(1)  # Attente de 1 seconde
    print("World")

# Exécution de la fonction asynchrone
asyncio.run(say_hello())

Ce code affiche « Hello », attend 1 seconde, puis affiche « World ». Pendant l’attente avec await, d’autres tâches asynchrones peuvent être exécutées.

Caractéristiques des coroutines

  • Les fonctions définies avec async ne peuvent pas être exécutées directement, elles doivent être appelées avec await ou avec asyncio.run().
  • Pour utiliser efficacement les traitements asynchrones, il est nécessaire de combiner correctement les coroutines et les tâches (explorées plus loin dans cet article).

Vue d’ensemble et rôle de la bibliothèque asyncio


La bibliothèque asyncio, incluse dans la bibliothèque standard de Python, fournit un ensemble d’outils pour gérer efficacement les tâches asynchrones. Elle permet de traiter les opérations I/O et de gérer plusieurs tâches en parallèle de manière simple.

Rôle de asyncio

  • Gestion de la boucle d’événements : Elle joue un rôle central dans la planification et l’exécution des tâches.
  • Gestion des coroutines et des tâches : Elle permet d’enregistrer les tâches asynchrones et de les exécuter efficacement.
  • Support des opérations asynchrones I/O : Elle permet d’exécuter des traitements impliquant des opérations I/O (comme les fichiers et la communication réseau) de manière asynchrone.

Qu’est-ce qu’une boucle d’événements ?


Une boucle d’événements est un moteur qui gère les tâches asynchrones dans un ordre précis. Dans asyncio, cette boucle gère les fonctions asynchrones et planifie efficacement leur exécution.

import asyncio

async def example_task():
    print("Task started")
    await asyncio.sleep(1)
    print("Task finished")

async def main():
    # Exécution de la tâche dans la boucle d'événements
    await example_task()

# Démarrage de la boucle d'événements et exécution de main()
asyncio.run(main())

Principales fonctions et classes asyncio

  • asyncio.run() : Démarre la boucle d’événements et exécute une fonction asynchrone.
  • asyncio.create_task() : Enregistre une coroutine en tant que tâche dans la boucle d’événements.
  • asyncio.sleep() : Attend de manière asynchrone pendant un certain temps.
  • asyncio.gather() : Exécute plusieurs tâches simultanément et récupère leurs résultats.
  • asyncio.Queue : Permet d’échanger des données de manière efficace entre des tâches asynchrones.

Exemple simple d’application


Voici un exemple où plusieurs tâches sont exécutées simultanément :

async def task1():
    print("Task 1 started")
    await asyncio.sleep(2)
    print("Task 1 finished")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(1)
    print("Task 2 finished")

async def main():
    # Exécution simultanée
    await asyncio.gather(task1(), task2())

asyncio.run(main())

Ce programme exécute les tâches 1 et 2 en parallèle, avec la tâche 2 terminant en premier.

Avantages de asyncio

  • Gestion efficace de nombreuses tâches.
  • Amélioration des performances pour les tâches liées aux I/O.
  • Planification flexible grâce à la boucle d’événements.

En comprenant asyncio, vous pourrez exploiter pleinement la puissance de la programmation asynchrone.

Différences et utilisation des coroutines et des tâches


Dans le contexte de la programmation asynchrone en Python, les coroutines et les tâches sont des concepts clés. En comprenant leurs caractéristiques et rôles, vous pourrez utiliser ces outils efficacement pour gérer vos traitements asynchrones.

Qu’est-ce qu’une coroutine ?


Une coroutine est une fonction spéciale définie comme une fonction asynchrone. Elle est définie avec async def et peut exécuter d’autres traitements asynchrones via await. Les coroutines peuvent être suspendues et reprises à l’extérieur de leur propre contexte.

Exemple : Définition et utilisation d’une coroutine

import asyncio

async def my_coroutine():
    print("Start coroutine")
    await asyncio.sleep(1)
    print("End coroutine")

# Exécution de la coroutine
asyncio.run(my_coroutine())

Qu’est-ce qu’une tâche ?


Une tâche est une coroutine qui a été enveloppée pour être exécutée dans la boucle d’événements. Elle est créée avec asyncio.create_task() et, une fois enregistrée dans la boucle, elle est exécutée de manière concurrente.

Exemple de création et d’exécution d’une tâche

import asyncio

async def my_coroutine(number):
    print(f"Coroutine {number} started")
    await asyncio.sleep(1)
    print(f"Coroutine {number} finished")

async def main():
    # Création de plusieurs tâches et exécution simultanée
    task1 = asyncio.create_task(my_coroutine(1))
    task2 = asyncio.create_task(my_coroutine(2))

    # Attente de la fin des tâches
    await task1
    await task2

asyncio.run(main())

Dans cet exemple, les tâches 1 et 2 commencent en même temps et leur traitement se fait en parallèle.

Différences entre coroutines et tâches

CaractéristiqueCoroutineTâche
Définitionasync defasyncio.create_task()
Exécutionawait ou asyncio.run()Planifiée et exécutée automatiquement par la boucle d’événements
Exécution simultanéeExécute un traitement asynchrone uniquePermet l’exécution simultanée de plusieurs traitements asynchrones

Comment choisir entre coroutine et tâche

  • Les coroutines sont utilisées pour des traitements asynchrones simples.
  • Les tâches sont utilisées pour exécuter plusieurs traitements asynchrones en parallèle.

Application : Traitement parallèle avec des tâches


Voici un exemple où plusieurs fonctions asynchrones sont exécutées simultanément grâce aux tâches :

import asyncio

async def fetch_data(url):
    print(f"Fetching data from {url}")
    await asyncio.sleep(2)  # Attente simulée pour réseau
    print(f"Finished fetching data from {url}")

async def main():
    urls = ["https://example.com", "https://example.org", "https://example.net"] 

    # Création de plusieurs tâches
    tasks = [asyncio.create_task(fetch_data(url)) for url in urls]

    # Attente de la fin de toutes les tâches
    await asyncio.gather(*tasks)

asyncio.run(main())

Ce programme génère et exécute plusieurs tâches en parallèle à l’aide d’une compréhension de liste.

Points importants et précautions

  • L’ordre d’exécution des tâches n’est pas garanti, elles ne conviennent donc pas aux traitements dépendants.
  • Les tâches ne peuvent être utilisées qu’au sein de la boucle d’événements.

Comprendre correctement les différences entre coroutines et tâches et les utiliser en fonction des besoins vous permettra de maximiser l’efficacité de vos programmes asynchrones.

Avantages et limites de la programmation asynchrone


La programmation asynchrone est particulièrement utile pour améliorer les performances des applications traitant de nombreuses opérations I/O, mais elle n’est pas universelle. Cette section explique les avantages et limites de cette technique pour vous aider à l’utiliser judicieusement.

Avantages de la programmation asynchrone

1. Accélération et efficacité

  • Utilisation optimale des ressources pendant les attentes I/O : Contrairement à la programmation synchrone où le programme est bloqué en attendant I/O, l’asynchrone permet d’exécuter d’autres tâches en parallèle.
  • Haute capacité de traitement : Idéal pour les serveurs traitant de nombreuses requêtes ou pour les clients effectuant simultanément des opérations réseau.

2. Meilleure réactivité

  • Amélioration de l’expérience utilisateur : L’exécution de tâches en arrière-plan sans bloquer l’interface utilisateur améliore la réactivité.
  • Réduction des temps d’attente : Grâce à l’I/O asynchrone, les autres tâches peuvent avancer simultanément, réduisant ainsi le temps d’attente global.

3. Flexibilité et évolutivité

  • Conception scalable : La programmation asynchrone consomme moins de ressources systèmes que les threads ou processus, rendant l’application plus scalable.
  • Multitâches efficace : L’asynchrone permet de basculer entre plusieurs tâches de manière optimale, ce qui permet au système de supporter une charge plus élevée.

Limites de la programmation asynchrone

1. Complexité du programme


La programmation asynchrone peut rendre le code moins intuitif, ce qui peut rendre le débogage et la maintenance plus difficiles. Les problèmes courants incluent :

  • Conditions de concurrence : Lorsque plusieurs tâches accèdent à la même ressource, il devient difficile d’assurer l’intégrité des données.
  • Enchevêtrement des callbacks : Dans les cas complexes, la dépendance entre les tâches peut rendre le code difficile à lire.

2. Inefficacité pour les tâches CPU-bound


La programmation asynchrone est optimisée principalement pour les tâches liées à l’I/O. Pour les tâches à forte intensité de calcul (CPU-bound), la présence du GIL (Global Interpreter Lock) peut limiter l’amélioration des performances.

3. Conception appropriée nécessaire


Pour que la programmation asynchrone fonctionne correctement, il est essentiel de bien concevoir le programme et de choisir les bonnes bibliothèques. Une mauvaise conception peut entraîner :

  • Deadlock : Les tâches qui attendent mutuellement leur fin peuvent se bloquer.
  • Incohérence de planification : Une planification inefficace peut entraîner des délais inattendus.

Points à prendre en compte pour utiliser la programmation asynchrone

1. Utilisation adaptée

  • Application aux traitements liés à l’I/O : Idéal pour les opérations de base de données, de communication réseau, et de manipulation de fichiers.
  • Utilisation de threads ou processus pour les tâches CPU-bound : Combinée à des techniques de traitement parallèle, l’asynchrone peut compléter ces tâches.

2. Utilisation d’outils et bibliothèques de qualité

  • asyncio : Outil de base pour gérer les tâches asynchrones dans la bibliothèque standard.
  • aiohttp : Bibliothèque spécialisée dans les communications HTTP asynchrones.
  • Quart ou FastAPI : Frameworks web compatibles avec la programmation asynchrone.

3. Débogage et surveillance

  • Utilisez des logs pour suivre le comportement des tâches et faciliter le débogage.
  • Activez le mode débogage de asyncio pour obtenir des informations détaillées sur les erreurs.

La programmation asynchrone peut considérablement améliorer les performances des applications si elle est bien conçue. Cependant, il est crucial de comprendre ses limites et de concevoir le programme de manière appropriée.

Écrire des fonctions asynchrones en pratique


Pour mettre en œuvre la programmation asynchrone en Python, vous devez définir des fonctions asynchrones en utilisant async et await. Dans cette section, nous apprendrons à créer des fonctions asynchrones et à comprendre le flux de travail de la programmation asynchrone.

Structure de base d’une fonction asynchrone


Une fonction asynchrone est définie avec async def. Pour appeler d’autres traitements asynchrones à l’intérieur de cette fonction, vous utilisez await.

Exemple de fonction asynchrone de base

import asyncio

async def greet():
    print("Hello,")
    await asyncio.sleep(1)  # Attente asynchrone de 1 seconde
    print("World!")

# Exécution de la fonction asynchrone
asyncio.run(greet())

Dans cet exemple, await asyncio.sleep(1) est l’endroit où le traitement asynchrone se produit. Grâce à la programmation asynchrone, d’autres tâches peuvent être exécutées pendant l’attente de 1 seconde.

Coordination des fonctions asynchrones


Il est également possible de chaîner plusieurs fonctions asynchrones pour les faire coopérer.

Exemple de coordination de fonctions asynchrones

async def task1():
    print("Task 1 started")
    await asyncio.sleep(2)
    print("Task 1 finished")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(1)
    print("Task 2 finished")

async def main():
    # Exécution séquentielle des fonctions asynchrones
    await task1()
    await task2()

asyncio.run(main())

Dans cet exemple, la fonction main exécute les fonctions asynchrones task1 et task2 l’une après l’autre.

Fonctions asynchrones et exécution parallèle


Pour exécuter plusieurs fonctions asynchrones en parallèle, on utilise asyncio.create_task. Cela permet de faire avancer plusieurs traitements simultanément.

Exemple d’exécution parallèle

async def task1():
    print("Task 1 started")
    await asyncio.sleep(2)
    print("Task 1 finished")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(1)
    print("Task 2 finished")

async def main():
    # Création des tâches pour une exécution parallèle
    task1_coroutine = asyncio.create_task(task1())
    task2_coroutine = asyncio.create_task(task2())

    # Attente de la fin des deux tâches
    await task1_coroutine
    await task2_coroutine

asyncio.run(main())

Dans cet exemple, task1 et task2 s’exécutent en parallèle. task2 se termine après 1 seconde, tandis que task1 prend 2 secondes.

Exemple avancé : Compteur asynchrone simple


Voici un exemple utilisant des fonctions asynchrones pour effectuer plusieurs comptages en parallèle.

async def count(number):
    for i in range(1, 4):
        print(f"Counter {number}: {i}")
        await asyncio.sleep(1)  # Attente asynchrone de 1 seconde

async def main():
    # Exécution parallèle de plusieurs comptages
    await asyncio.gather(count(1), count(2), count(3))

asyncio.run(main())

Résultat d’exécution

Counter 1: 1
Counter 2: 1
Counter 3: 1
Counter 1: 2
Counter 2: 2
Counter 3: 2
Counter 1: 3
Counter 2: 3
Counter 3: 3

L’utilisation de la programmation asynchrone permet de voir que chaque compteur fonctionne indépendamment des autres.

Points clés et précautions

  • L’utilisation de la programmation asynchrone réduit le gaspillage des ressources système et permet une gestion efficace des tâches.
  • Utilisez judicieusement asyncio.gather et asyncio.create_task.
  • Lors de l’exécution des fonctions asynchrones, utilisez toujours asyncio.run ou une boucle d’événements.

En pratiquant les fonctions asynchrones dès le départ, vous renforcerez votre capacité à utiliser efficacement la programmation asynchrone.

Méthodes de mise en œuvre du traitement parallèle : utilisation de gather et wait


Dans le traitement asynchrone en Python, asyncio.gather et asyncio.wait sont utilisés pour exécuter efficacement plusieurs tâches en parallèle. Comprendre leurs caractéristiques et la manière de les utiliser permet de construire des programmes asynchrones plus flexibles.

Aperçu de asyncio.gather et exemple d’utilisation


asyncio.gather exécute plusieurs tâches asynchrones en même temps et attend que toutes les tâches soient terminées. Une fois les tâches terminées, il retourne les résultats sous forme de liste.

Exemple de base

import asyncio

async def task1():
    await asyncio.sleep(1)
    return "Tâche 1 terminée"

async def task2():
    await asyncio.sleep(2)
    return "Tâche 2 terminée"

async def main():
    results = await asyncio.gather(task1(), task2())
    print(results)

asyncio.run(main())

Résultat de l’exécution

['Tâche 1 terminée', 'Tâche 2 terminée']

Caractéristiques

  • Attend la fin des tâches exécutées en parallèle et retourne les résultats sous forme de liste.
  • Si une exception se produit, gather arrête toutes les tâches et transmet l’exception à l’appelant.

Aperçu de asyncio.wait et exemple d’utilisation


asyncio.wait exécute plusieurs tâches en parallèle et retourne un ensemble des tâches terminées et en attente.

Exemple de base

import asyncio

async def task1():
    await asyncio.sleep(1)
    print("Tâche 1 terminée")

async def task2():
    await asyncio.sleep(2)
    print("Tâche 2 terminée")

async def main():
    tasks = [task1(), task2()]
    done, pending = await asyncio.wait(tasks)
    print(f"Tâches terminées : {len(done)}, Tâches en attente : {len(pending)}")

asyncio.run(main())

Résultat de l’exécution

Tâche 1 terminée
Tâche 2 terminée
Tâches terminées : 2, Tâches en attente : 0

Caractéristiques

  • Permet de vérifier l’état des tâches (terminées ou en attente).
  • Peut traiter les tâches non terminées même si certaines sont terminées prématurément.
  • Le paramètre return_when de asyncio.wait permet de contrôler la fin des tâches selon des conditions spécifiques.

Exemple d’option return_when

done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
  • FIRST_COMPLETED: Retourne dès que la première tâche est terminée.
  • FIRST_EXCEPTION: Retourne dès que la première exception se produit.
  • ALL_COMPLETED: Attend que toutes les tâches soient terminées (comportement par défaut).

Différences entre gather et wait

  • Lorsque vous souhaitez récupérer les résultats ensemble: utilisez asyncio.gather.
  • Lorsque vous souhaitez gérer individuellement l’état des tâches: utilisez asyncio.wait.
  • Lorsque vous souhaitez terminer prématurément ou gérer les exceptions: asyncio.wait est plus approprié.

Exemple pratique : appel parallèle d’API


Voici un exemple dans lequel plusieurs API sont appelées en parallèle et les réponses sont récupérées :

import asyncio

async def fetch_data(api_name, delay):
    print(f"Récupération de {api_name}...")
    await asyncio.sleep(delay)  # Attente simulée
    return f"Données de {api_name}"

async def main():
    apis = [("API_1", 2), ("API_2", 1), ("API_3", 3)]
    tasks = [fetch_data(api, delay) for api, delay in apis]

    # Traitement parallèle avec gather et collecte des résultats
    results = await asyncio.gather(*tasks)
    for result in results:
        print(result)

asyncio.run(main())

Résultat de l’exécution

Récupération de API_1...
Récupération de API_2...
Récupération de API_3...
Données de API_2
Données de API_1
Données de API_3

Remarques

  • Gestion des exceptions: Si des exceptions surviennent dans les tâches parallèles, elles doivent être capturées et traitées correctement. Utilisez try/except.
  • Annulation des tâches: Si une tâche devient inutile, utilisez task.cancel() pour l’annuler.
  • Attention aux deadlocks: Il est important de concevoir des tâches afin d’éviter les situations où elles se bloquent mutuellement.

En utilisant efficacement asyncio.gather et asyncio.wait, vous pouvez maximiser la flexibilité et l’efficacité des traitements asynchrones.

Exemple d’I/O asynchrone : opérations sur fichiers et réseaux


L’I/O asynchrone est une technique pour rendre plus efficaces les traitements qui impliquent des attentes, comme les opérations sur fichiers ou les communications réseau. En utilisant asyncio, il est possible d’implémenter facilement des opérations I/O asynchrones. Cette section présente des exemples concrets d’utilisation de l’I/O asynchrone.

Opérations asynchrones sur fichiers


Pour effectuer des opérations asynchrones sur des fichiers, nous utilisons la bibliothèque aiofiles, qui étend les opérations de fichiers de la bibliothèque standard pour les rendre asynchrones.

Exemple : lecture et écriture de fichiers de manière asynchrone

import aiofiles
import asyncio

async def read_file(filepath):
    async with aiofiles.open(filepath, mode='r') as file:
        contents = await file.read()
        print(f"Contenu de {filepath}:")
        print(contents)

async def write_file(filepath, data):
    async with aiofiles.open(filepath, mode='w') as file:
        await file.write(data)
        print(f"Données écrites dans {filepath}")

async def main():
    filepath = 'example.txt'
    await write_file(filepath, "Hello, Async File IO!")
    await read_file(filepath)

asyncio.run(main())

Points importants

  • En utilisant aiofiles.open, vous pouvez effectuer des opérations de fichiers de manière asynchrone.
  • Le mot-clé async with garantit que les fichiers sont gérés en toute sécurité.
  • Pendant les opérations sur fichiers, d’autres tâches peuvent continuer à s’exécuter.

Opérations asynchrones sur le réseau


Pour les opérations réseau, la bibliothèque aiohttp permet de réaliser des requêtes HTTP de manière asynchrone.

Exemple : requêtes HTTP asynchrones

import aiohttp
import asyncio

async def fetch_url(session, url):
    async with session.get(url) as response:
        print(f"Récupération de {url}")
        content = await response.text()
        print(f"Contenu de {url}: {content[:100]}...")

async def main():
    urls = [
        "https://example.com",
        "https://example.org",
        "https://example.net"
    ]

    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        await asyncio.gather(*tasks)

asyncio.run(main())

Points importants

  • Utilisez aiohttp.ClientSession pour effectuer des communications HTTP asynchrones.
  • Le mot-clé async with est utilisé pour gérer la session et envoyer des requêtes de manière sûre.
  • Les requêtes multiples sont traitées en parallèle avec asyncio.gather, ce qui améliore l’efficacité.

Combinaison d’I/O asynchrone pour les fichiers et le réseau


En combinant les opérations asynchrones sur fichiers et réseau, vous pouvez réaliser une collecte et un stockage de données efficaces.

Exemple : sauvegarde des données téléchargées de manière asynchrone

import aiohttp
import aiofiles
import asyncio

async def fetch_and_save(session, url, filepath):
    async with session.get(url) as response:
        print(f"Récupération de {url}")
        content = await response.text()

        async with aiofiles.open(filepath, mode='w') as file:
            await file.write(content)
            print(f"Contenu de {url} sauvegardé dans {filepath}")

async def main():
    urls = [
        ("https://example.com", "example_com.txt"),
        ("https://example.org", "example_org.txt"),
        ("https://example.net", "example_net.txt")
    ]

    async with aiohttp.ClientSession() as session:
        tasks = [fetch_and_save(session, url, filepath) for url, filepath in urls]
        await asyncio.gather(*tasks)

asyncio.run(main())

Exemple de résultat d’exécution

  • Le contenu de https://example.com est sauvegardé dans le fichier example_com.txt.
  • De même, le contenu des autres URL est sauvegardé dans leurs fichiers respectifs.

Remarques lors de l’utilisation de l’I/O asynchrone

  1. Implémentation de la gestion des exceptions
    Préparez-vous à gérer les erreurs de réseau ou de sauvegarde de fichiers en utilisant une gestion d’exception appropriée.
   try:
       # Tâches asynchrones
   except Exception as e:
       print(f"Une erreur s'est produite : {e}")
  1. Gestion de la surcharge
    Exécuter trop de tâches asynchrones simultanément peut surcharger le système ou le serveur. Utilisez asyncio.Semaphore pour limiter le nombre de tâches en parallèle.
   semaphore = asyncio.Semaphore(5)  # Limite à 5 tâches parallèles

   async with semaphore:
       await some_async_task()
  1. Définition des délais d’attente
    Pour éviter de longues attentes dans le cas de tâches non répondues, définissez des délais d’attente.
   try:
       await asyncio.wait_for(some_async_task(), timeout=10)
   except asyncio.TimeoutError:
       print("Le délai d'attente a expiré")

Utiliser l’I/O asynchrone de manière appropriée permet d’améliorer considérablement l’efficacité et le débit des applications.

Exemple avancé : Construction d’un crawler web asynchrone


En utilisant le traitement asynchrone, il est possible de créer un crawler web rapide et efficace. Grâce à l’I/O asynchrone, vous pouvez récupérer plusieurs pages web en parallèle et maximiser la vitesse de crawling. Cette section explique un exemple de mise en œuvre d’un crawler web asynchrone en Python.

Structure de base d’un crawler web asynchrone


Un crawler web asynchrone repose sur trois éléments clés :

  1. Gestion des URL: Les URL à crawler sont gérées efficacement.
  2. Communication HTTP asynchrone: Les pages web sont récupérées avec la bibliothèque aiohttp.
  3. Sauvegarde des données: Les données récupérées sont sauvegardées via des opérations de fichiers asynchrones.

Exemple de code : Crawler Web Asynchrone


Voici un exemple d’implémentation d’un crawler web asynchrone :

import aiohttp
import aiofiles
import asyncio
from bs4 import BeautifulSoup

async def fetch_page(session, url):
    try:
        async with session.get(url) as response:
            if response.status == 200:
                html = await response.text()
                print(f"Récupéré {url}")
                return html
            else:
                print(f"Échec de récupération de {url}: {response.status}")
                return None
    except Exception as e:
        print(f"Erreur de récupération de {url}: {e}")
        return None

async def parse_and_save(html, url, filepath):
    if html:
        soup = BeautifulSoup(html, 'html.parser')
        title = soup.title.string if soup.title else "Pas de titre"
        async with aiofiles.open(filepath, mode='a') as file:
            await file.write(f"URL: {url}\nTitre: {title}\n\n")
        print(f"Données sauvegardées pour {url}")

async def crawl(urls, output_file):
    async with aiohttp.ClientSession() as session:
        tasks = []
        for url in urls:
            tasks.append(process_url(session, url, output_file))
        await asyncio.gather(*tasks)

async def process_url(session, url, output_file):
    html = await fetch_page(session, url)
    await parse_and_save(html, url, output_file)

async def main():
    urls = [
        "https://example.com",
        "https://example.org",
        "https://example.net"
    ]
    output_file = "crawl_results.txt"

    # Initialisation : vider le fichier de résultats
    async with aiofiles.open(output_file, mode='w') as file:
        await file.write("")

    await crawl(urls, output_file)

asyncio.run(main())

Explication du fonctionnement du code

  1. Fonction fetch_page
    Effectue une requête HTTP asynchrone pour récupérer le HTML d’une page web. Elle vérifie le code de statut et gère les erreurs.
  2. Fonction parse_and_save
    Analyse le HTML avec BeautifulSoup pour en extraire le titre de la page et sauvegarde ces informations de manière asynchrone dans un fichier.
  3. Fonction crawl
    Prend une liste d’URL et traite chaque URL en parallèle. Les tâches sont exécutées avec asyncio.gather.
  4. Fonction process_url
    Effectue le processus complet pour une URL donnée, en combinant fetch_page et parse_and_save.

Exemple de résultat d’exécution


Les données suivantes seront sauvegardées dans le fichier crawl_results.txt :

URL: https://example.com
Titre: Example Domain

URL: https://example.org
Titre: Example Domain

URL: https://example.net
Titre: Example Domain

Améliorations des performances

  • Limitation des tâches parallèles
    Lorsque vous crawlez un grand nombre d’URL, limitez le nombre de tâches parallèles pour éviter de surcharger le serveur.
  semaphore = asyncio.Semaphore(10)

  async def limited_process_url(semaphore, session, url, output_file):
      async with semaphore:
          await process_url(session, url, output_file)
  • Ajout de la fonctionnalité de réessai
    En cas d’échec d’une requête, ajoutez un mécanisme de réessai pour améliorer la fiabilité.

Points importants

  1. Vérification de la légalité
    Avant d’utiliser un crawler, assurez-vous de respecter le fichier robots.txt et les termes d’utilisation du site cible.
  2. Gestion des erreurs
    Traitez correctement les erreurs de réseau et d’analyse HTML pour éviter que le crawler ne s’arrête en cas d’erreur.
  3. Paramétrage des délais d’attente
    Définissez des délais d’attente pour éviter de patienter indéfiniment lors des requêtes.
   async with session.get(url, timeout=10) as response:

Un crawler web asynchrone bien conçu et contrôlé permet de collecter des données de manière efficace et évolutive.

Résumé


Dans cet article, nous avons expliqué en détail le traitement asynchrone en Python en utilisant la syntaxe async/await, depuis les bases jusqu’aux applications avancées. Comprendre le traitement asynchrone permet d’optimiser les tâches liées à I/O et d’améliorer les performances des applications.

En particulier, nous avons abordé les bases de la bibliothèque asyncio, l’utilisation de gather et wait pour le traitement parallèle, des exemples d’I/O asynchrone, et la construction d’un crawler web asynchrone pour illustrer des applications pratiques.

Le traitement asynchrone, lorsqu’il est bien conçu, peut permettre de construire des systèmes efficaces et évolutifs, mais il est essentiel de prêter attention à la gestion des exceptions et à la légalité. Nous espérons que cet article vous aidera à mieux comprendre et à appliquer le traitement asynchrone dans vos projets.

Sommaire