đ 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.Thread
pour lancertelecharger_page
en 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.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.
- Utilise
- Testez et comparez les performances.