đ Web scraping concurrent
đŻ Objectif pĂ©dagogiqueâ
- Comprendre pourquoi le threading est utile pour les I/O réseau.
- Ăcrire un tĂ©lĂ©chargeur de pages web sĂ©quentiel puis concurrent.
- Mesurer et comparer les performances.
đ° DĂ©marrage rapide â TĂ©lĂ©charger une page avec httpbinâ
Avant d'aborder le threading, voici un mini-exemple trĂšs simple qui tĂ©lĂ©charge une page de test depuis https://httpbin.org. Ce service est conçu pour expĂ©rimenter les requĂȘtes HTTP et renvoyer ce que vous lui envoyez.
Exemple minimal:
import time
import urllib.request
import urllib.parse
import urllib.error
def telecharger_page(url, params=None, headers=None, timeout=10):
   """Télécharge une page et retourne des métriques de base.
   Args:
      url (str): URL à télécharger
      params (dict|None): paramĂštres de requĂȘte (?a=b)
      headers (dict|None): en-tĂȘtes HTTP
      timeout (int|float): délai max en secondes
   Returns:
      dict: informations sur la réponse ou l'erreur
   """
   debut = time.time()
   try:
      # Construire l'URL avec les paramĂštres
      if params:
         query_string = urllib.parse.urlencode(params)
         url = f"{url}?{query_string}"
      
      # CrĂ©er la requĂȘte avec User-Agent par dĂ©faut
      req = urllib.request.Request(url)
      
      # Ajouter un User-Agent par défaut pour éviter les blocages
      req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')
      
      # Ajouter les en-tĂȘtes personnalisĂ©s
      if headers:
         for key, value in headers.items():
            req.add_header(key, value)
      
      # Effectuer la requĂȘte
      with urllib.request.urlopen(req, timeout=timeout) as resp:
         content = resp.read()
         duree = time.time() - debut
         return {
            "url": url,
            "url_finale": resp.url,
            "status": resp.status,
            "taille": len(content),
            "duree": duree,
            "content_type": resp.headers.get("Content-Type", ""),
            "succes": True,
         }
   except urllib.error.HTTPError as e:
      duree = time.time() - debut
      return {"url": url, "erreur": "HTTPError", "details": f"Code {e.code}: {e.reason}",
              "duree": duree, "succes": False}
   except urllib.error.URLError as e:
      duree = time.time() - debut
      return {"url": url, "erreur": "URLError", "details": str(e.reason),
              "duree": duree, "succes": False}
   except Exception as e:
      duree = time.time() - debut
      return {"url": url, "erreur": "Exception", "details": str(e), "duree": duree, "succes": False}
# 1) Appel simple: endpoint /get avec paramÚtres (renvoie les params envoyés)
params = {"q": "python threading", "lang": "fr", "page": 1}
resultat = telecharger_page("https://httpbin.org/get", params=params)
print(resultat)
# 2) Endpoint qui simule un délai réseau
resultat_delay = telecharger_page("https://httpbin.org/delay/1")
print(resultat_delay)
# 3) Endpoint qui renvoie du HTML simple
html_res = telecharger_page("https://httpbin.org/html")
print(html_res)
Adresses utiles pour vos tests:
- https://httpbin.org/get â renvoie les query params envoyĂ©s
- https://httpbin.org/delay/1 â simule 1 seconde d'attente (essayez 2, 3âŠ)
- https://httpbin.org/status/200 â retourne un code HTTP prĂ©cis (200, 404, 500âŠ)
- https://httpbin.org/json â renvoie un JSON
- https://httpbin.org/html â renvoie une page HTML
đ ĂnoncĂ©â
Vous allez construire pas à pas un petit scraper web, d'abord séquentiel puis avec threads.
Partie 1 â TĂ©lĂ©chargement sĂ©quentiel (baseline)â
- Créez un fichier scraping_baseline.py.
- Ăcrivez scraping_sequentiel(urls)qui:- Boucle sur les URLs et appelle telecharger_page.
- Stocke les résultats dans une liste et mesure la durée totale.
- Affiche le nombre de succÚs/échecs et le temps total.
 
- Boucle sur les URLs et appelle 
- Testez avec une boucle sur plusieurs URLs de httpbin.org.
- Mesurez le temps total.
Attendu: fonctionnement correct mais temps total â somme des dĂ©lais.
Partie 2 â Version avec threadsâ
- Créez une fonction scraping_avec_threads(urls)qui:- Utilise threading.Threadpour lancertelecharger_pageen parallÚle, un thread par URL.
- Partage une structure resultats(liste ou dict).
- Mesure et affiche le temps total.
 
- Utilise 
- Rejouez le mĂȘme jeu d'URLs et comparez les temps avec la baseline.
Attendu : fonctionnement correct et temps total âȘ somme des dĂ©lais.
Il est souvent utile de limiter le nombre de threads actifs pour ne pas surcharger le systĂšme ou le serveur distant. Par exemple, si vous avez 100 URLs Ă tĂ©lĂ©charger, vous ne voulez peut-ĂȘtre pas lancer 100 threads en mĂȘme temps. Dans la partie 2, nous laissons cette optimisation de cĂŽtĂ© pour la simplicitĂ©.
Partie 3 â Avec ThreadPoolExecutorâ
- Créez une fonction scraping_avec_threadpool(urls, max_workers=5)qui:- Utilise concurrent.futures.ThreadPoolExecutorpour gérer un pool de threads.
- Soumet les tùches de téléchargement et collecte les résultats.
- Mesure et affiche le temps total.
 
- Utilise 
- Testez et comparez les performances.