Aller au contenu principal

🌐 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:

📜 É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)​

  1. Créez un fichier scraping_baseline.py.
  2. É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.
  3. Testez avec une boucle sur plusieurs URLs de httpbin.org.
  4. Mesurez le temps total.

Attendu: fonctionnement correct mais temps total ≈ somme des dĂ©lais.

Partie 2 — Version avec threads​

  1. Créez une fonction scraping_avec_threads(urls) qui:
    • Utilise threading.Thread pour lancer telecharger_page en parallĂšle, un thread par URL.
    • Partage une structure resultats (liste ou dict).
    • Mesure et affiche le temps total.
  2. Rejouez le mĂȘme jeu d'URLs et comparez les temps avec la baseline.

Attendu : fonctionnement correct et temps total â‰Ș somme des dĂ©lais.

remarque

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​

  1. Créez une fonction scraping_avec_threadpool(urls, max_workers=5) qui:
    • Utilise concurrent.futures.ThreadPoolExecutor pour 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.
  2. Testez et comparez les performances.