Les images sont certains des fichiers les plus lourds et les plus utiles du web : photos de produits pour un catalogue, graphiques pour un jeu de données de recherche, ressources pour un pipeline d'apprentissage automatique. Quand vous en avez besoin de plus d'une poignée, cliquer sur "Enregistrer l'image sous" cesse d'être une option, et un court script Python fait le travail en quelques secondes plutôt qu'en un après-midi.

Ce guide présente six méthodes pratiques pour télécharger des images avec Python, d'un seul fichier avec requests à l'extraction de chaque image d'une page avec BeautifulSoup, en passant par le streaming de grands fichiers en morceaux, l'organisation de ce que vous sauvegardez, et l'accès à des sources protégées via la Crawlbase Crawling API. Chaque extrait est réel et s'exécute tel quel, vous pouvez donc copier chacun et l'adapter à vos propres cibles.

Ce que vous allez construire

À la fin, vous disposerez d'une petite boîte à outils de fonctions couvrant les cas courants, plus un court script qui les assemble.

  • Single download. Récupérez une image par URL et écrivez-la sur disque avec requests.
  • Standard-library download. Faites la même chose sans paquets tiers en utilisant urllib.request.
  • Page scrape. Trouvez chaque <img> sur une page avec BeautifulSoup et téléchargez chaque source.
  • Chunked streaming. Sauvegardez les grands fichiers en morceaux afin que l'utilisation de la mémoire reste constante.
  • Naming and folders. Dérivez des noms de fichiers propres et triez les téléchargements dans des répertoires.
  • Protected sources. Accédez aux images derrière un rendu ou une protection anti-bot via la Crawling API.

Pourquoi un simple téléchargement échoue sur certains sites

Télécharger une image est, dans sa forme la plus simple, un seul GET HTTP. Cela fonctionne parfaitement pour les fichiers statiques servis depuis une URL prévisible. Les problèmes commencent sur les sites réels. Certaines pages construisent leur grille d'images avec JavaScript après le chargement du HTML initial, donc une récupération brute renvoie un balisage sans balises d'image. D'autres se trouvent derrière des défenses anti-bot qui challengent ou bloquent les requêtes provenant d'IP de datacenter ou de tout ce qui ne ressemble pas à un vrai navigateur, et vous obtenez un 403 ou une page d'erreur HTML là où vous attendiez un JPEG.

Pour les fichiers simples, les cinq premières méthodes ci-dessous sont tout ce dont vous avez besoin. Pour les cas difficiles, la dernière méthode route la requête via une couche de rendu et un pool d'IP de confiance afin que le fichier revienne intact. Nous commençons par le chemin simple car la plupart des téléchargements n'ont jamais besoin de plus.

Prérequis

Vous n'avez pas besoin de grand-chose pour suivre ce guide.

Python 3.8 or later. Vérifiez votre version avec python --version. Si vous ne l'avez pas, installez-le depuis python.org.

Basic Python. Vous devez savoir exécuter un script et installer des paquets avec pip. Les fonctions, les boucles et l'instruction with suffisent.

A Crawlbase account (for the last method only). Les cinq premières méthodes n'utilisent que requests, urllib et BeautifulSoup. Pour la méthode des sources protégées, vous aurez besoin d'un compte Crawlbase gratuit et de son token API.

Configurer le projet

Créez un environnement virtuel afin que les dépendances du projet restent isolées, puis installez les deux bibliothèques tierces utilisées dans ce guide.

bash
python --version

python -m venv image_env
source image_env/bin/activate

pip install requests beautifulsoup4

Sous Windows, activez l'environnement avec image_env\Scripts\activate à la place de la ligne source. requests est le client HTTP qui récupère chaque fichier, et beautifulsoup4 analyse le HTML de la page afin que vous puissiez trouver les balises d'image. urllib, os et hashlib font partie de la bibliothèque standard et n'ont pas besoin d'être installés.

Astuce 1 : Télécharger une seule image avec requests

Le cas le plus courant est une image à une URL donnée. Envoyez un GET, confirmez que la réponse est bien une image et écrivez les octets dans un fichier en mode binaire. Vérifier le code de statut et le type de contenu avant d'écrire vous évite de sauvegarder une page d'erreur HTML sous un nom .jpg.

python
import requests

url = "https://www.python.org/static/img/python-logo.png"
headers = {"User-Agent": "Mozilla/5.0 (image downloader)"}

response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200 and "image" in response.headers.get("Content-Type", ""):
    with open("python-logo.png", "wb") as f:
        f.write(response.content)
    print("Saved python-logo.png")
else:
    print(f"Skipped: {response.status_code} {response.headers.get('Content-Type')}")

Trois détails comptent ici. Le fichier est ouvert avec "wb" (écriture binaire) parce que les données d'image sont des octets, pas du texte, et les écrire comme du texte corromprait le fichier. La vérification du Content-Type confirme que le serveur a bien renvoyé une image plutôt qu'une page d'erreur qui se trouve à porter un statut 200. Le timeout empêche le script de se bloquer indéfiniment sur un serveur bloqué. Exécutez ceci et vous devriez voir Saved python-logo.png avec un vrai PNG sur disque à côté de votre script. C'est un téléchargement fonctionnel.

Crawlbase Crawling API

Le téléchargement unique ci-dessus fonctionne quand le fichier est servi depuis une URL simple. Dès que la page cache ses images derrière JavaScript ou bloque les requêtes de datacenter, ce GET renvoie une page d'erreur au lieu d'octets. La Crawling API rend la page dans un vrai navigateur et effectue une rotation des IP résidentielles côté serveur, puis renvoie la réponse finale, vous évitant ainsi de devoir gérer une flotte de navigateurs sans tête et un pool de proxies vous-même. Essayez-la sur l'offre gratuite avant de construire cette infrastructure vous-même.

Astuce 2 : Télécharger avec urllib de la bibliothèque standard

Si vous préférez ne pas ajouter de dépendance, la bibliothèque standard peut faire le même travail. urllib.request est livré avec Python, donc cette approche ne nécessite rien d'installé. Définir un User-Agent via un objet Request aide avec les serveurs qui rejettent l'agent urllib par défaut.

python
import urllib.request

url = "https://www.python.org/static/img/python-logo.png"
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (image downloader)"})

with urllib.request.urlopen(req, timeout=10) as resp, open("logo_urllib.png", "wb") as f:
    f.write(resp.read())

print("Saved logo_urllib.png")

L'instruction with ouvre à la fois la connexion et le fichier de sortie ensemble et les ferme proprement à la fin du bloc, même si une erreur est levée en cours d'écriture. Il existe aussi un raccourci en une ligne, urllib.request.urlretrieve(url, "logo.png"), pratique pour les scripts rapides mais qui ne vous donne aucun contrôle sur les en-têtes ou la gestion des erreurs, la forme explicite ci-dessus est donc la valeur par défaut plus sûre. requests et urllib écrivent les mêmes octets sur disque ; requests a simplement une API plus conviviale, c'est pourquoi la majeure partie du reste de ce guide l'utilise.

Astuce 3 : Télécharger toutes les images d'une page avec BeautifulSoup

Télécharger un fichier est facile. Le vrai travail consiste à extraire automatiquement chaque image d'une page. Le schéma est en deux étapes : récupérez le HTML de la page, puis analysez-le avec BeautifulSoup pour collecter le src de chaque balise <img>, et enfin parcourez ces URLs en réutilisant la logique de téléchargement unique du Conseil 1.

python
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

page_url = "https://en.wikipedia.org/wiki/Python_(programming_language)"
headers = {"User-Agent": "Mozilla/5.0 (image downloader)"}

page = requests.get(page_url, headers=headers, timeout=10)
soup = BeautifulSoup(page.text, "html.parser")

image_urls = []
for img in soup.select("img"):
    src = img.get("src")
    if src:
        image_urls.append(urljoin(page_url, src))

print(f"Found {len(image_urls)} images")

Deux parties méritent qu'on s'y attarde. Le sélecteur img saisit chaque balise d'image sur la page, et lire img.get("src") au lieu de img["src"] renvoie None pour toute balise manquant l'attribut plutôt que de lever une erreur. L'autre élément clé est urljoin : les sources d'images sont souvent des chemins relatifs comme /images/photo.jpg, et joindre chacun avec l'URL de la page le transforme en adresse téléchargeable complète. Pour un tour plus approfondi de la sélection d'éléments de cette façon, consultez comment utiliser BeautifulSoup en Python.

Avec la liste des URLs absolues en main, la boucle de téléchargement réutilise le schéma d'écriture binaire du Conseil 1 et donne à chaque fichier son propre nom.

python
import os

os.makedirs("downloads", exist_ok=True)

for i, img_url in enumerate(image_urls):
    try:
        r = requests.get(img_url, headers=headers, timeout=10)
        if r.status_code == 200 and "image" in r.headers.get("Content-Type", ""):
            path = os.path.join("downloads", f"image_{i}.jpg")
            with open(path, "wb") as f:
                f.write(r.content)
    except requests.RequestException as e:
        print(f"Failed {img_url}: {e}")

Le try/except autour de chaque requête n'est pas optionnel une fois que vous téléchargez de nombreux fichiers. Sur des dizaines d'images, une finira par expirer, renvoyer une boucle de redirection ou disparaître, et attraper requests.RequestException permet à la boucle de sauter la mauvaise et de continuer au lieu de planter au trentième fichier. L'appel os.makedirs(..., exist_ok=True) crée le dossier de sortie une fois et ne fait rien s'il existe déjà.

Astuce 4 : Télécharger les gros fichiers en flux par blocs

Lire response.content charge le fichier entier en mémoire avant de l'écrire. C'est acceptable pour un logo, mais c'est gaspilleur pour une photo haute résolution ou une ressource de plusieurs mégaoctets, et cela peut épuiser la mémoire quand vous téléchargez de nombreux grands fichiers à la suite. Streamer la réponse et l'écrire en morceaux de taille fixe maintient l'utilisation de la mémoire constante quelle que soit la taille du fichier.

python
def download_stream(url, path, chunk_size=8192):
    with requests.get(url, headers=headers, stream=True, timeout=30) as r:
        r.raise_for_status()
        with open(path, "wb") as f:
            for chunk in r.iter_content(chunk_size=chunk_size):
                f.write(chunk)
    return path

download_stream(
    "https://upload.wikimedia.org/wikipedia/commons/c/c3/Python-logo-notext.svg",
    "downloads/large_logo.svg",
)

Passer stream=True indique à requests de ne pas télécharger le corps immédiatement ; à la place, iter_content le récupère en morceaux de chunk_size octets et écrit chaque morceau directement sur disque. La mémoire de pointe reste autour de la taille d'un morceau, huit kilo-octets ici, quelle que soit la taille du fichier. raise_for_status() transforme une réponse 4xx ou 5xx en exception afin que vous ne sauviez pas silencieusement un corps d'erreur, et le with extérieur sur la requête assure que la connexion est libérée une fois le fichier écrit.

Astuce 5 : Nommer et organiser vos fichiers

Tout sauvegarder sous image_0.jpg, image_1.jpg fonctionne, mais cela abandonne les noms de fichiers originaux et perd l'extension, ce qui compte si vous mélangez des PNG, des JPEG et des SVG. Un petit helper dérive un nom propre depuis l'URL et revient à un hash de contenu lorsque l'URL n'a pas de nom utilisable, ce qui garantit l'unicité et évite d'écraser deux fichiers qui partagent un nom par hasard.

python
import os
import hashlib
from urllib.parse import urlparse

def filename_for(url, content, folder="downloads"):
    name = os.path.basename(urlparse(url).path)
    if not name or "." not in name:
        digest = hashlib.md5(content).hexdigest()[:12]
        name = f"{digest}.jpg"
    return os.path.join(folder, name)

# Example: turn a messy URL into a tidy path
r = requests.get(image_urls[0], headers=headers, timeout=10)
print(filename_for(image_urls[0], r.content))

urlparse(...).path supprime les chaînes de requête et les fragments de l'URL, et os.path.basename prend uniquement le segment final, donc .../photo.jpg?size=large devient photo.jpg. Lorsque l'URL n'a pas de vrai nom de fichier, le hash MD5 des octets du fichier donne un nom court, stable et résistant aux collisions. Ce même hash est aussi la façon la plus simple de repérer les doublons : deux images identiques produisent le même condensé, vous pouvez donc sauter un fichier déjà sauvegardé avant de l'écrire.

Sorting into subfolders

Si vous téléchargez depuis plusieurs pages ou catégories à la fois, passez un folder différent par source afin que les fichiers atterrissent dans des répertoires séparés. Combiné avec os.makedirs(folder, exist_ok=True), cela maintient une grande exécution organisée sur disque et facilite la recherche d'une image donnée plus tard sans avoir à parcourir un seul grand dossier.

Astuce 6 : Télécharger depuis des sources protégées avec la Crawling API

Les cinq méthodes ci-dessus couvrent toute image que vous pouvez atteindre avec une requête simple. Certains sites ne sont pas aussi coopératifs. La page peut rendre sa grille d'images avec JavaScript de sorte qu'une récupération ne renvoie aucune balise <img>, ou l'hôte d'images peut bloquer les IP de datacenter et servir un 403 au lieu du fichier. Vous pouvez résoudre les deux vous-même en exécutant un navigateur sans tête et en maintenant un pool de proxies résidentiels tournants, mais construire et maintenir cela en bonne santé représente la majorité de l'effort d'ingénierie et n'a rien à voir avec les images que vous souhaitez.

La Crawling API regroupe le rendu et la rotation des IP dans une seule requête. Vous installez le client officiel, puis routez la récupération de la page via lui ; le HTML renvoyé est entièrement rendu, donc l'analyse BeautifulSoup du Conseil 3 fonctionne sans modification.

bash
pip install crawlbase
python
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup
from urllib.parse import urljoin

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"})
page_url = "https://example.com/gallery"

result = api.get(page_url, {"ajax_wait": "true", "page_wait": 5000})
html = result["body"].decode("utf-8") if result["status_code"] == 200 else None

# Same parsing as Tip 3, now against rendered HTML
soup = BeautifulSoup(html, "html.parser")
image_urls = [urljoin(page_url, img["src"]) for img in soup.select("img[src]")]
print(f"Found {len(image_urls)} images on the rendered page")

Les deux options d'attente comptent sur une galerie rendue côté client. ajax_wait indique à l'API d'attendre que le contenu asynchrone finisse de charger, et page_wait maintient pendant un nombre fixe de millisecondes après le chargement afin que les images tardives apparaissent avant la capture. Utilisez le token JavaScript pour les sites qui construisent leur grille dans le navigateur ; pour les hôtes d'images statiques, le token normal est plus rapide. Une fois que image_urls est remplie, vous l'injectez directement dans le helper de streaming download_stream du Conseil 4. Si vous n'avez besoin que de la rotation des IP et non du rendu, par exemple quand le HTML de la page est correct mais que l'hôte d'images vous bloque, le Smart AI Proxy route le trafic requests ordinaire via le même pool de confiance avec un changement d'une ligne dans vos paramètres de proxy.

À quoi ressemble le résultat

Après un scraping de page, un petit manifeste de ce que vous avez sauvegardé est utile pour l'audit et pour sauter les doublons lors de la prochaine exécution. Écrire un enregistrement par fichier vous donne un journal structuré à côté des images elles-mêmes.

json
[
  {
    "source_url": "https://example.com/img/photo-01.jpg",
    "saved_as": "downloads/photo-01.jpg",
    "content_type": "image/jpeg",
    "bytes": 184213
  },
  {
    "source_url": "https://example.com/img/diagram.svg",
    "saved_as": "downloads/diagram.svg",
    "content_type": "image/svg+xml",
    "bytes": 9042
  }
]

Chaque enregistrement relie l'URL originale au fichier local, note le type de contenu afin que vous connaissiez le vrai format, et enregistre la taille en octets. Conserver le source_url signifie qu'une exécution ultérieure peut vérifier ce que vous avez déjà avant de le télécharger à nouveau.

Passer à l'échelle sur de nombreuses images

Pour quelques dizaines de fichiers, la boucle séquentielle du Conseil 3 convient. Quand vous en tirez des milliers, télécharger un à la fois est le goulot d'étranglement, car la majeure partie du temps est passée à attendre sur le réseau plutôt que sur votre CPU. Un pool de threads permet à plusieurs téléchargements de s'exécuter simultanément tout en restant beaucoup plus simple que du code asynchrone complet.

python
from concurrent.futures import ThreadPoolExecutor

def save_one(url):
    try:
        r = requests.get(url, headers=headers, timeout=15)
        if r.status_code == 200 and "image" in r.headers.get("Content-Type", ""):
            path = filename_for(url, r.content)
            with open(path, "wb") as f:
                f.write(r.content)
            return path
    except requests.RequestException:
        return None

with ThreadPoolExecutor(max_workers=8) as pool:
    saved = list(pool.map(save_one, image_urls))

print(f"Saved {len([p for p in saved if p])} of {len(image_urls)} images")

Huit workers est un bon point de départ ; le pousser trop haut risque de surcharger l'hôte, ce qui est à la fois impoli et un moyen rapide de se faire limiter en débit. Chaque appel à save_one est autonome et absorbe ses propres erreurs afin qu'un échec ne saborde jamais le lot. Si vous téléchargez à ce volume depuis une source protégée, routez save_one via la Crawling API ou le Smart AI Proxy afin que les IP tournantes absorbent la charge au lieu de votre adresse unique. Pour plus d'informations sur le maintien des grandes exécutions en vie, consultez comment scraper des sites sans être bloqué.

Télécharger des images de façon responsable

Les images ne sont pas des données flottantes librement ; elles sont une création, et presque toutes les images que vous trouvez en ligne sont protégées par le droit d'auteur de celui qui les a créées. Pouvoir télécharger un fichier n'est pas la même chose qu'être autorisé à l'utiliser. Avant de pointer un script sur un site, lisez ses conditions d'utilisation et vérifiez son robots.txt, maintenez votre volume de requêtes assez bas pour ne pas surcharger le serveur, et ne téléchargez que ce que vous avez réellement le droit d'utiliser. Les photos derrière une connexion, les images personnelles et les images sous licence non acquise sont hors limites.

Le même soin s'applique à ce que vous faites ensuite. Ne redistribuez pas les images de quelqu'un d'autre comme les vôtres, et n'injectez pas de médias protégés par le droit d'auteur dans un modèle ou un jeu de données sans la permission du titulaire des droits. Lorsqu'un site offre une API officielle ou une voie de licence pour ses médias, utilisez-la : c'est la façon la plus propre d'obtenir des images sur lesquelles vous pouvez vous appuyer légalement, et elle vient généralement avec des conditions qui précisent exactement ce que vous pouvez en faire.

Récapitulatif

Points clés

  • Binary mode is non-negotiable. Ouvrez toujours le fichier de sortie avec "wb" et vérifiez que la réponse est bien une image avant d'écrire, afin de ne jamais sauvegarder une page d'erreur comme JPEG.
  • Find then fetch for whole pages. Analysez le HTML avec BeautifulSoup pour collecter chaque source <img>, résolvez les chemins relatifs avec urljoin, puis téléchargez chaque URL dans une boucle protégée.
  • Stream large files in chunks. Utilisez stream=True et iter_content afin que la mémoire de pointe reste constante quelle que soit la taille du fichier.
  • Name and dedupe deliberately. Dérivez des noms de fichiers propres depuis l'URL, revenez à un hash de contenu, et utilisez ce hash pour sauter les doublons.
  • Reach protected sources through the API. Quand une page rend les images en JavaScript ou bloque votre IP, la Crawling API renvoie le HTML rendu afin que votre analyseur et votre code de téléchargement existants continuent de fonctionner.

Foire aux questions

Quelle est la façon la plus simple de télécharger une image en Python ?

Envoyez un GET avec requests, puis écrivez response.content dans un fichier ouvert en mode binaire ("wb"). C'est trois lignes pour un seul fichier. Vérifiez d'abord le code de statut et l'en-tête Content-Type afin de ne sauvegarder que de vraies données d'image et non une page d'erreur HTML qui s'est trouvé à renvoyer un statut 200.

Dois-je utiliser requests ou urllib ?

Les deux téléchargent les mêmes octets. requests a une API plus propre, une gestion des en-têtes plus facile et le streaming intégré, c'est pourquoi la plupart des codes l'utilisent. Optez pour urllib.request quand vous voulez zéro dépendance tierce, car il est livré avec Python. La logique de téléchargement est par ailleurs identique.

Comment télécharger toutes les images d'une page web ?

Récupérez le HTML de la page, analysez-le avec BeautifulSoup et collectez le src de chaque balise <img>. Résolvez les chemins relatifs en URLs absolues avec urljoin, puis parcourez la liste et téléchargez chaque fichier avec le schéma d'image unique. Enveloppez chaque téléchargement dans try/except afin qu'une mauvaise URL n'arrête pas tout le lot, comme montré dans le Conseil 3.

Pourquoi mon fichier téléchargé s'ouvre-t-il comme une image corrompue ?

Généralement l'une de deux raisons. Soit le fichier a été ouvert en mode texte au lieu de binaire, ce qui corrompt les octets, assurez-vous donc d'utiliser "wb". Soit le serveur a renvoyé une page d'erreur HTML plutôt que l'image, c'est pourquoi vérifier le code de statut et le Content-Type avant d'écrire compte. Sur les pages lourdes en JavaScript, l'image peut ne pas exister du tout dans le HTML initial, auquel cas vous avez besoin d'une étape de rendu.

Comment télécharger des images sans être bloqué ?

Envoyez un User-Agent réaliste, rythmez vos requêtes avec un court délai et évitez de marteler un hôte avec de nombreux threads simultanés. À volume plus élevé, vous avez également besoin d'IPs qui semblent être de vrais visiteurs, ce qu'une seule machine ne peut pas fournir. Router via des IP résidentielles tournantes, via la Crawling API ou le Smart AI Proxy, est ce qui empêche les grandes exécutions d'images de déclencher des limites de débit.

Comment éviter de télécharger deux fois la même image ?

Calculez un hash des octets de chaque fichier avec hashlib et conservez les condensés déjà vus dans un ensemble. Avant d'écrire un nouveau fichier, vérifiez si son hash est déjà dans l'ensemble ; si c'est le cas, sautez-le. Le même condensé constitue également un nom de fichier fiable et résistant aux collisions lorsque l'URL n'a pas de nom utilisable propre.

Commencer à construire

Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.

Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.

En libre-service · Sans appel commercial requis · Volumes de crawl entreprise disponibles