first commit
This commit is contained in:
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
14
.idea/m01_tp01_asyncio.iml
generated
Normal file
14
.idea/m01_tp01_asyncio.iml
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Poetry (m01_tp01_asyncio)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
||||
4
.idea/misc.xml
generated
Normal file
4
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Poetry (m01_tp01_asyncio)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/m01_tp01_asyncio.iml" filepath="$PROJECT_DIR$/.idea/m01_tp01_asyncio.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
153
README.md
Normal file
153
README.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# TP : Téléchargements synchrone et asynchrone (asyncio)
|
||||
|
||||
## Informations générales
|
||||
|
||||
**Cours** : Python Avancé > Programmation concurrente > Tâches IO-bound concurrentes avec asyncio \
|
||||
**Objectifs pédagogiques** :
|
||||
- Python avancé : programmation asynchrone
|
||||
- Outils modernes (poetry, PyCharm)
|
||||
- Bonnes pratiques de l'entreprise
|
||||
|
||||
---
|
||||
|
||||
## Prérequis
|
||||
|
||||
### Installation et configuration de l’environnement
|
||||
|
||||
- Faites `poetry install` après import du projet (fichier lock déja présent), pour s'assurer que toutes les dépendances sont installées.
|
||||
- Assurez-vous que l'interpréteur Python du projet pointe bien vers l'environnement virtuel créé par Poetry, ce qui devrait être le cas par défaut (sinon cliquer en bas à droite sur le nom de l'interpréteur puis `Add new interpreter`, ensuite `Add local interpreter...`, et sélectionner `poetry` avec Python `3.13`).
|
||||
- Attention, si vous êtes derrière un proxy, pour ce qui est de la version asynchrone en particulier, vous allez peut-être devoir passer le paramètre `proxy="http://host:port"` à la méthode `session.get()`.
|
||||
|
||||
### Connaissances préalables
|
||||
|
||||
- Connaissances de base en programmation
|
||||
|
||||
---
|
||||
|
||||
## Énoncé
|
||||
|
||||
### Contexte
|
||||
|
||||
Le téléchargement de plusieurs fichiers depuis Internet est une tâche (IO-bound) courante en développement.
|
||||
Cependant, la manière dont on s'y prend peut avoir un impact considérable sur la performance et la rapidité d'exécution d'un script.
|
||||
Cet exercice a pour but de comparer deux approches fondamentales : le téléchargement séquentiel (synchrone) et le téléchargement parallèle (asynchrone).
|
||||
|
||||
Les deux scripts Python permettent de télécharger la même liste d'images.
|
||||
Le premier le fera de manière traditionnelle, une image après l'autre.
|
||||
Le second utilisera les capacités asynchrones de Python pour effectuer les téléchargements en parallèle, afin d'observer le gain de performance.
|
||||
|
||||
---
|
||||
|
||||
### Partie 1 : téléchargement synchrone
|
||||
|
||||
Dans cette première partie, vous allez créer un script qui télécharge une liste d'images de manière séquentielle en utilisant la bibliothèque `requests`.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph "Processus synchrone bloc par bloc"
|
||||
direction TB
|
||||
A[Début] --> B{Télécharger Image 1};
|
||||
B --> C[Attente Réseau 1];
|
||||
C --> D{Sauvegarder Image 1};
|
||||
D --> E{Télécharger Image 2};
|
||||
E --> F[Attente Réseau 2];
|
||||
F --> G{Sauvegarder Image 2};
|
||||
G --> H[...];
|
||||
H --> I{Télécharger Image N};
|
||||
I --> J[Attente Réseau N];
|
||||
J --> K{Sauvegarder Image N};
|
||||
K --> L[Fin];
|
||||
end
|
||||
```
|
||||
|
||||
#### Objectif
|
||||
|
||||
À partir du squelette de code fourni, écrire un script Python nommé `sync_downloader.py` qui :
|
||||
1. Télécharge une liste d'images depuis des URLs.
|
||||
2. Sauvegarde ces images dans un répertoire local.
|
||||
3. Mesure et affiche le temps total d'exécution.
|
||||
|
||||
#### Cahier des charges
|
||||
|
||||
1. **Configuration de base** :
|
||||
* Utilisez la liste d'URLs et le chemin du répertoire de sortie fournis ci-dessous.
|
||||
* Le script doit créer le répertoire de sortie s'il n'existe pas. Utilisez le module `pathlib` pour une gestion propre des chemins.
|
||||
|
||||
2. **Logging** :
|
||||
* Mettez en place un système de `logging` simple pour afficher des informations pertinentes dans la console (début/fin du script, URL en cours de téléchargement, succès, erreurs).
|
||||
* Le format des logs doit inclure l'heure, le niveau du log et le message.
|
||||
|
||||
3. **Logique de téléchargement** :
|
||||
* Utilisez la bibliothèque `requests`. Pour optimiser les connexions réseau, effectuez toutes vos requêtes à travers une `requests.Session`.
|
||||
* Créez une fonction `download_and_save` qui prend en charge le téléchargement et la sauvegarde d'une seule image.
|
||||
* Parcourez la liste d'URLs et appelez cette fonction pour chaque URL.
|
||||
|
||||
4. **Gestion des erreurs** :
|
||||
* Votre script doit être robuste. Utilisez des blocs `try...except` pour gérer les erreurs potentielles comme les erreurs réseau (`requests.exceptions.RequestException`) ou les problèmes d'écriture sur le disque (`IOError`).
|
||||
* Loguez les erreurs de manière explicite sans pour autant arrêter le script (une image qui ne se télécharge pas ne doit pas empêcher les autres de se télécharger).
|
||||
|
||||
5. **Mesure de performance** :
|
||||
* Utilisez le module `time` (par exemple, `time.perf_counter()`) pour calculer la durée totale d'exécution du processus de téléchargement.
|
||||
* Affichez le résultat à la fin du script.
|
||||
|
||||
---
|
||||
|
||||
### Partie 2 : téléchargement asynchrone
|
||||
|
||||
Maintenant que vous avez une version fonctionnelle, mais relativement lente, vous allez la réécrire en utilisant `asyncio` pour paralléliser les téléchargements et réduire le temps d'attente.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Début] --> B{Lancement simultané des tâches};
|
||||
|
||||
subgraph "Exécution parallèle gérée par la boucle asyncio"
|
||||
T1[Tâche 1: Get URL 1] --> W1[Attente Réseau 1] --> S1[Sauvegarde 1];
|
||||
T2[Tâche 2: Get URL 2] --> W2[Attente Réseau 2] --> S2[Sauvegarde 2];
|
||||
T3[...] --> W3[...] --> S3[...];
|
||||
TN[Tâche N: Get URL N] --> WN[Attente Réseau N] --> SN[Sauvegarde N];
|
||||
end
|
||||
|
||||
B --> T1;
|
||||
B --> T2;
|
||||
B --> T3;
|
||||
B --> TN;
|
||||
|
||||
S1 --> G{Fin de toutes les tâches};
|
||||
S2 --> G;
|
||||
S3 --> G;
|
||||
SN --> G;
|
||||
|
||||
G --> H[Fin du script];
|
||||
```
|
||||
|
||||
#### Objectif
|
||||
|
||||
À partir du squelette de code fourni, compléter le script `async_downloader.py` qui accomplit la même tâche que le précédent, mais de manière asynchrone pour une performance maximale.
|
||||
|
||||
#### Cahier des charges
|
||||
|
||||
1. **Structure asynchrone** :
|
||||
* Transformez votre logique en utilisant `async` et `await`. La fonction principale (`main`) et la fonction de téléchargement (`download_and_save`) doivent devenir des coroutines (`async def`).
|
||||
* Utilisez `asyncio.run(main())` pour démarrer l'exécution.
|
||||
|
||||
2. **Logique de téléchargement asynchrone** :
|
||||
* Remplacez `requests.Session` par `aiohttp.ClientSession`.
|
||||
* Dans votre fonction `download_and_save`, utilisez `session.get()` pour la requête et `response.read()` pour obtenir le contenu binaire de l'image.
|
||||
* Pour l'écriture des fichiers, remplacez `open()` par `aiofiles.open()` pour ne pas bloquer la boucle événementielle.
|
||||
|
||||
3. **Exécution en parallèle** :
|
||||
* Dans votre fonction `main`, créez une liste de "tâches" (une pour chaque image à télécharger).
|
||||
* Utilisez `asyncio.gather()` pour lancer toutes les tâches en parallèle et attendre qu'elles soient toutes terminées.
|
||||
|
||||
4. **Gestion des erreurs et logging** :
|
||||
* Adaptez la gestion des erreurs pour les exceptions spécifiques à `aiohttp` (comme `aiohttp.ClientError`).
|
||||
* Conservez le système de `logging`. Notez que dans un contexte asynchrone, les messages de log des différentes tâches peuvent s'entremêler, ce qui est normal.
|
||||
|
||||
5. **Mesure de performance** :
|
||||
* Mesurez à nouveau le temps d'exécution avec `time.perf_counter()`.
|
||||
* Comparez ce temps avec celui obtenu dans la Partie 1. Qu'observez-vous ?
|
||||
* Si vous avez le temps, essayez de télécharger un plus grand nombre d'images, provenant éventuellement de serveurs HTTP différents, pour voir l'impact de l'asynchronisme.
|
||||
|
||||
#### Question de réflexion
|
||||
|
||||
Pourquoi l'approche asynchrone est-elle efficace pour ce type de tâche (opérations d'entrée/sortie réseau) par rapport à l'approche synchrone ?
|
||||
1014
poetry.lock
generated
Normal file
1014
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
pyproject.toml
Normal file
47
pyproject.toml
Normal file
@@ -0,0 +1,47 @@
|
||||
[project]
|
||||
name = "tp-asyncio"
|
||||
version = "0.1.0"
|
||||
description = "TP asyncio"
|
||||
authors = [
|
||||
{name = "Your Name",email = "you@example.com"}
|
||||
]
|
||||
readme = "README.md"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3.13",
|
||||
]
|
||||
keywords = ["multiprocessing", "concurrency"]
|
||||
|
||||
exclude = [
|
||||
{ path = "tests", format = "wheel" }
|
||||
]
|
||||
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.13"
|
||||
requests = "^2.32.4"
|
||||
aiohttp = "^3.12.14"
|
||||
aiofiles = "^24.1.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "^8.4.1"
|
||||
pytest-cov = "^6.2.1"
|
||||
coverage = { version="*", extras=["toml"]}
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
|
||||
[tool.pycln]
|
||||
all = true
|
||||
|
||||
[tool.isort]
|
||||
line_length = 120
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = true
|
||||
force_grid_wrap = 0
|
||||
use_parentheses = true
|
||||
ensure_newline_before_comments = true
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
0
src/tp_asyncio/__init__.py
Normal file
0
src/tp_asyncio/__init__.py
Normal file
126
src/tp_asyncio/async_downloader.py
Normal file
126
src/tp_asyncio/async_downloader.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""
|
||||
Script pour télécharger une liste d'images de manière asynchrone.
|
||||
|
||||
Ce script utilise `aiohttp` et `aiofiles` pour effectuer des téléchargements
|
||||
parallèles, ce qui est beaucoup plus rapide qu'une approche séquentielle.
|
||||
|
||||
Il inclut :
|
||||
- L'utilisation de sessions `aiohttp` pour optimiser les performances réseau.
|
||||
- Une gestion des erreurs robuste pour les opérations réseau et disque.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
import aiofiles
|
||||
import aiohttp
|
||||
from aiohttp import ClientError
|
||||
|
||||
# --- Configuration ---
|
||||
|
||||
# Une liste d'URLs d'images à télécharger
|
||||
IMG_URLS: List[str] = [
|
||||
"https://images.pexels.com/photos/842711/pexels-photo-842711.jpeg",
|
||||
"https://images.pexels.com/photos/3408744/pexels-photo-3408744.jpeg",
|
||||
"https://images.pexels.com/photos/3244513/pexels-photo-3244513.jpeg",
|
||||
"https://images.pexels.com/photos/210186/pexels-photo-210186.jpeg",
|
||||
"https://images.pexels.com/photos/1261728/pexels-photo-1261728.jpeg",
|
||||
"https://images.pexels.com/photos/414144/pexels-photo-414144.jpeg",
|
||||
"https://images.pexels.com/photos/110854/pexels-photo-110854.jpeg",
|
||||
"https://images.pexels.com/photos/546819/pexels-photo-546819.jpeg",
|
||||
"https://images.pexels.com/photos/1640777/pexels-photo-1640777.jpeg",
|
||||
"https://images.pexels.com/photos/885880/pexels-photo-885880.jpeg",
|
||||
"https://images.pexels.com/photos/5318967/pexels-photo-5318967.jpeg",
|
||||
"https://images.pexels.com/photos/3464632/pexels-photo-3464632.jpeg",
|
||||
"https://images.pexels.com/photos/2110951/pexels-photo-2110951.jpeg",
|
||||
"https://images.pexels.com/photos/774909/pexels-photo-774909.jpeg",
|
||||
"https://images.pexels.com/photos/168927/pexels-photo-168927.jpeg",
|
||||
]
|
||||
|
||||
# Le répertoire de sortie pour les images téléchargées
|
||||
OUTPUT_DIR: Path = Path("../../images")
|
||||
|
||||
# --- Fonctions ---
|
||||
|
||||
def setup_logging() -> None:
|
||||
"""
|
||||
Configure le système de logging pour le script.
|
||||
"""
|
||||
|
||||
# Sous un autre système d'exploitation que Windows, il est possible d'utiliser une blibliothèque de logging asynchrone telle que aiologger
|
||||
# Lorsqu'il existe notamment des Handler de type fichiers, cela évite de ralentir la boucle asyncio
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def download_and_save(
|
||||
session: aiohttp.ClientSession, url: str, output_dir: Path
|
||||
) -> None:
|
||||
"""
|
||||
Télécharge une image et la sauvegarde de manière asynchrone.
|
||||
|
||||
Paramètres:
|
||||
session (aiohttp.ClientSession): la session client `aiohttp` à utiliser.
|
||||
url (str): l'URL de l'image à télécharger.
|
||||
output_dir (Path): le répertoire où sauvegarder l'image.
|
||||
"""
|
||||
# TODO : votre code ici. Similaire à la version synchrone, mais avec `async`/`await`.
|
||||
# 1. Utilisez un bloc `try...except` pour gérer les erreurs (ClientError, asyncio.TimeoutError, IOError).
|
||||
#
|
||||
# 2. Dans le bloc `try` :
|
||||
# a. Extrayez le nom du fichier et construisez le chemin de sauvegarde (comme avant).
|
||||
# b. Logguez le début du téléchargement.
|
||||
# c. Utilisez `async with session.get(url) as response:` pour effectuer la requête.
|
||||
# d. Vérifiez le statut de la réponse avec `response.raise_for_status()`.
|
||||
# e. Lisez le contenu de la réponse de manière asynchrone : `data = await response.read()`.
|
||||
# f. Utilisez `async with aiofiles.open(path, "wb") as f:` pour ouvrir le fichier
|
||||
# sans bloquer la boucle événementielle.
|
||||
# g. Écrivez les données dans le fichier : `await f.write(data)`.
|
||||
# h. Logguez la confirmation de la sauvegarde.
|
||||
#
|
||||
# 3. Dans les blocs `except`, logguez les erreurs spécifiques.
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""
|
||||
Fonction principale asynchrone.
|
||||
|
||||
Orchestre la création du répertoire de sortie et le lancement
|
||||
des tâches de téléchargement en parallèle.
|
||||
"""
|
||||
setup_logging()
|
||||
logging.info("Début du script de téléchargement asynchrone.")
|
||||
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
logging.info(f"Le répertoire de sortie est : {OUTPUT_DIR}")
|
||||
|
||||
# TODO : votre code ici. C'est ici que la magie de l'asynchronisme opère.
|
||||
# 1. Créez une session client avec `async with aiohttp.ClientSession() as session:`.
|
||||
#
|
||||
# 2. À l'intérieur du `async with`, créez une liste de "tâches". Chaque tâche
|
||||
# est un appel à votre coroutine `download_and_save`.
|
||||
# Astuce : une compréhension de liste est parfaite pour ça.
|
||||
# Exemple : `tasks = [download_and_save(session, url, OUTPUT_DIR) for url in IMG_URLS]`
|
||||
#
|
||||
# 3. Utilisez `asyncio.gather()` pour lancer toutes les tâches en parallèle et
|
||||
# attendre qu'elles se terminent toutes.
|
||||
# La syntaxe est : `await asyncio.gather(*tasks)`
|
||||
# (L'étoile `*` dépaquette la liste de tâches en arguments pour `gather`).
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# `asyncio.run()` est la manière moderne de lancer une application asyncio
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
print("\nScript interrompu par l'utilisateur.")
|
||||
119
src/tp_asyncio/sync_downloader.py
Normal file
119
src/tp_asyncio/sync_downloader.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""
|
||||
Script pour télécharger une liste d'images de manière séquentielle.
|
||||
|
||||
Ce script inclut :
|
||||
- Un système de logging pour suivre les opérations.
|
||||
- Une gestion des erreurs robuste.
|
||||
- L'utilisation de sessions `requests` pour optimiser les performances réseau.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
# --- Configuration ---
|
||||
|
||||
# Une liste d'URLs d'images à télécharger.
|
||||
IMG_URLS: List[str] = [
|
||||
"https://images.pexels.com/photos/842711/pexels-photo-842711.jpeg",
|
||||
"https://images.pexels.com/photos/3408744/pexels-photo-3408744.jpeg",
|
||||
"https://images.pexels.com/photos/3244513/pexels-photo-3244513.jpeg",
|
||||
"https://images.pexels.com/photos/210186/pexels-photo-210186.jpeg",
|
||||
"https://images.pexels.com/photos/1261728/pexels-photo-1261728.jpeg",
|
||||
"https://images.pexels.com/photos/414144/pexels-photo-414144.jpeg",
|
||||
"https://images.pexels.com/photos/110854/pexels-photo-110854.jpeg",
|
||||
"https://images.pexels.com/photos/546819/pexels-photo-546819.jpeg",
|
||||
"https://images.pexels.com/photos/1640777/pexels-photo-1640777.jpeg",
|
||||
"https://images.pexels.com/photos/885880/pexels-photo-885880.jpeg",
|
||||
"https://images.pexels.com/photos/5318967/pexels-photo-5318967.jpeg",
|
||||
"https://images.pexels.com/photos/3464632/pexels-photo-3464632.jpeg",
|
||||
"https://images.pexels.com/photos/2110951/pexels-photo-2110951.jpeg",
|
||||
"https://images.pexels.com/photos/774909/pexels-photo-774909.jpeg",
|
||||
"https://images.pexels.com/photos/168927/pexels-photo-168927.jpeg",
|
||||
]
|
||||
|
||||
# Le répertoire de sortie pour les images téléchargées.
|
||||
OUTPUT_DIR: Path = Path("../../images")
|
||||
|
||||
# --- Fonctions ---
|
||||
|
||||
def setup_logging() -> None:
|
||||
"""
|
||||
Configure le système de logging pour le script.
|
||||
"""
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout),
|
||||
]
|
||||
)
|
||||
|
||||
def download_and_save(session: requests.Session, url: str, output_dir: Path) -> None:
|
||||
"""
|
||||
Télécharge une image depuis une URL et la sauvegarde dans le répertoire spécifié.
|
||||
|
||||
Paramètres:
|
||||
session (requests.Session): La session `requests` à utiliser pour la requête.
|
||||
url (str): L'URL de l'image à télécharger.
|
||||
output_dir (Path): Le répertoire où sauvegarder l'image.
|
||||
"""
|
||||
# TODO : votre code ici. Suivez ces étapes :
|
||||
# 1. Utilisez un bloc `try...except` pour gérer les erreurs potentielles
|
||||
# (RequestException, IOError).
|
||||
#
|
||||
# 2. Dans le bloc `try` :
|
||||
# a. Extrayez le nom du fichier à partir de l'URL.
|
||||
# Astuce : `url.split("/")[-1]` est une méthode simple.
|
||||
# b. Construisez le chemin de sauvegarde complet en utilisant `pathlib`.
|
||||
# Exemple : `output_dir / nom_du_fichier`
|
||||
# c. Logguez le début du téléchargement.
|
||||
# d. Effectuez une requête GET avec `session.get(url)`. Pensez à ajouter un timeout.
|
||||
# e. Vérifiez que la requête a réussi avec `response.raise_for_status()`.
|
||||
# f. Ouvrez le fichier de destination en mode écriture binaire ('wb')
|
||||
# en utilisant un `with open(...)`.
|
||||
# g. Écrivez le contenu de la réponse (`response.content`) dans le fichier.
|
||||
# h. Logguez la confirmation de la sauvegarde.
|
||||
#
|
||||
# 3. Dans les blocs `except`, logguez l'erreur de manière descriptive
|
||||
# (ex: `logging.error(...)`).
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Fonction principale du script.
|
||||
|
||||
Orchestre la création du répertoire de sortie et le téléchargement
|
||||
séquentiel des images.
|
||||
"""
|
||||
setup_logging()
|
||||
logging.info("Début du script de téléchargement.")
|
||||
|
||||
# Crée le répertoire de sortie s'il n'existe pas.
|
||||
# `parents=True` crée les répertoires parents si nécessaire.
|
||||
# `exist_ok=True` évite une erreur si le répertoire existe déjà.
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
logging.info(f"Le répertoire de sortie est : {OUTPUT_DIR}")
|
||||
|
||||
start_time = time.perf_counter()
|
||||
|
||||
# TODO : votre code ici. Suivez ces étapes :
|
||||
# 1. Utilisez un gestionnaire de contexte `with requests.Session() as session:`
|
||||
# pour créer une session qui sera utilisée pour toutes les requêtes.
|
||||
# Ceci optimise les connexions réseau.
|
||||
#
|
||||
# 2. À l'intérieur du `with`, faites une boucle `for` sur la liste `IMG_URLS`.
|
||||
#
|
||||
# 3. Dans la boucle, appelez la fonction `download_and_save()` pour chaque URL,
|
||||
# en lui passant la session, l'URL et le répertoire de sortie.
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
Reference in New Issue
Block a user