W3docs

Python — Travailler avec les API (requests)

Apprenez à utiliser la bibliothèque requests de Python pour faire des appels GET et POST, envoyer des en-têtes, des paramètres et gérer les réponses JSON.

La bibliothèque requests est la méthode standard pour effectuer des appels HTTP depuis Python. Elle encapsule le module bas niveau urllib dans une API propre, de sorte que récupérer une page web ou appeler une API REST ne demande qu'une seule ligne au lieu de dix. Ce chapitre couvre tout ce dont vous avez besoin : installer requests, effectuer des appels GET et POST, envoyer des en-têtes et des paramètres de requête, travailler avec des réponses JSON, téléverser des fichiers, utiliser des sessions et gérer les erreurs de manière robuste.

Installation

requests ne fait pas partie de la bibliothèque standard, vous devez donc l'installer avec pip :

pip install requests

Si vous travaillez dans un environnement virtuel (recommandé), activez-le d'abord afin que le package soit limité à votre projet. Après l'installation, vérifiez que tout fonctionne :

import requests
print(requests.__version__)   # e.g. 2.32.3

Effectuer une requête GET

requests.get() envoie une requête HTTP GET et retourne un objet Response. Il s'agit de l'opération la plus courante — utilisée pour récupérer des données depuis des API, des pages web et des fichiers.

import requests

response = requests.get("https://jsonplaceholder.typicode.com/todos/1")

print(response.status_code)   # 200
print(response.url)           # https://jsonplaceholder.typicode.com/todos/1
print(response.text)          # raw response body as a string

Résultat attendu :

200
https://jsonplaceholder.typicode.com/todos/1
{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}

Lire la réponse en JSON

La plupart des API modernes retournent du JSON. Appelez .json() sur la réponse plutôt que d'analyser .text manuellement — cela appelle json.loads() pour vous et retourne un dict ou une liste Python.

import requests

response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
data = response.json()

print(data["title"])      # delectus aut autem
print(data["completed"])  # False

Consultez le chapitre Python JSON pour en savoir plus sur la correspondance entre les objets Python et les types JSON.

Envoyer des paramètres de requête

Les paramètres de requête sont les paires clé-valeur après le ? dans une URL, comme ?q=python&page=2. Passez-les sous forme de dict dans l'argument paramsrequests les encode dans l'URL et les ajoute automatiquement.

import requests

params = {
    "q": "python requests",
    "page": 1,
    "per_page": 5,
}

response = requests.get("https://httpbin.org/get", params=params)

# requests builds the full URL for you
print(response.url)
# https://httpbin.org/get?q=python+requests&page=1&per_page=5

Utilisez toujours params= plutôt que de construire l'URL manuellement — cela gère correctement les caractères spéciaux et l'encodage.

Envoyer des en-têtes de requête

Les en-têtes transportent des métadonnées : jetons d'authentification, préférences de type de contenu, clés API, etc. Passez-les sous forme de dict dans headers= :

import requests

headers = {
    "Accept": "application/json",
    "Authorization": "Bearer my-api-token",
    "User-Agent": "MyApp/1.0",
}

response = requests.get("https://httpbin.org/headers", headers=headers)
print(response.json())

En-têtes courants à envoyer :

En-têteRôle
AuthorizationJeton d'authentification (Bearer, Basic, etc.)
Content-TypeFormat du corps de la requête (ex. application/json)
AcceptFormat attendu en retour du serveur
User-AgentIdentifie votre client auprès du serveur
X-API-KeyClé API dans un en-tête personnalisé (varie selon le service)

Effectuer une requête POST

requests.post() envoie des données au serveur — utilisé pour créer des ressources, soumettre des formulaires ou appeler des actions.

Envoyer du JSON

Passez un dict Python à json=. La bibliothèque le sérialise et définit automatiquement l'en-tête Content-Type: application/json :

import requests

payload = {
    "title": "Buy groceries",
    "completed": False,
    "userId": 1,
}

response = requests.post(
    "https://jsonplaceholder.typicode.com/todos",
    json=payload,
)

print(response.status_code)   # 201 Created
print(response.json())

Résultat attendu :

201
{'title': 'Buy groceries', 'completed': False, 'userId': 1, 'id': 201}

Envoyer des données de formulaire

Certaines API ou formulaires HTML attendent des données au format application/x-www-form-urlencoded. Utilisez data= à la place de json= :

import requests

form_data = {
    "username": "alice",
    "password": "secret",
}

response = requests.post("https://httpbin.org/post", data=form_data)
print(response.status_code)

Envoyer des fichiers (téléversement multipart)

Pour téléverser un fichier, ouvrez-le en mode binaire et passez-le via files= :

import requests

with open("report.pdf", "rb") as f:
    response = requests.post(
        "https://httpbin.org/post",
        files={"file": f},
    )

print(response.status_code)

requests encode le téléversement en multipart/form-data, ce qu'attendent la plupart des points de terminaison de téléversement de fichiers.

Autres méthodes HTTP

Les API REST utilisent différents verbes HTTP pour différentes opérations. requests fournit une fonction par méthode :

import requests

base = "https://jsonplaceholder.typicode.com/todos/1"

# Update a resource (replace entirely)
response = requests.put(base, json={"title": "Updated", "completed": True, "userId": 1})
print(response.status_code)   # 200

# Partial update
response = requests.patch(base, json={"completed": True})
print(response.status_code)   # 200

# Delete a resource
response = requests.delete(base)
print(response.status_code)   # 200

Gestion des erreurs

Vérifier les codes de statut

Le code de statut HTTP vous indique si la requête a réussi. Les groupes les plus importants :

PlageSignification
2xxSuccès (200 OK, 201 Created, 204 No Content)
3xxRedirection (gérée automatiquement par requests)
4xxErreur client (400 Bad Request, 401 Unauthorized, 404 Not Found)
5xxErreur serveur (500 Internal Server Error, 503 Service Unavailable)

raise_for_status()

Appeler .raise_for_status() sur une réponse lève automatiquement une exception HTTPError si le code de statut est 4xx ou 5xx. C'est la manière la plus propre d'échouer rapidement sur les mauvaises réponses :

import requests

response = requests.get("https://jsonplaceholder.typicode.com/todos/99999")

try:
    response.raise_for_status()
    data = response.json()
    print(data)
except requests.exceptions.HTTPError as err:
    print(f"HTTP error: {err}")

Sans raise_for_status(), une réponse 404 ressemble à un succès — votre code lit un corps d'erreur et le traite silencieusement.

Gérer les erreurs réseau

Les échecs au niveau réseau (échec de résolution DNS, connexion refusée, délai d'expiration) lèvent requests.exceptions.ConnectionError ou requests.exceptions.Timeout. Attrapez les deux avec la classe de base requests.exceptions.RequestException :

import requests

try:
    response = requests.get("https://api.example.com/data", timeout=5)
    response.raise_for_status()
    data = response.json()
except requests.exceptions.Timeout:
    print("The request timed out — server took too long to respond.")
except requests.exceptions.ConnectionError:
    print("Could not connect — check your network or the URL.")
except requests.exceptions.HTTPError as err:
    print(f"HTTP error {response.status_code}: {err}")
except requests.exceptions.RequestException as err:
    print(f"Unexpected error: {err}")

Ce modèle couvre toute la hiérarchie des exceptions : délai d'expiration, connexion, erreur HTTP et la classe de base fourre-tout. Consultez Python try/except pour approfondir la gestion des exceptions en Python.

Toujours définir un délai d'expiration

Par défaut, requests attend indéfiniment si le serveur ne répond jamais. Passez toujours timeout= pour éviter les programmes bloqués :

# timeout=(connect_timeout, read_timeout) in seconds
response = requests.get("https://api.example.com/data", timeout=(3, 10))

La forme en tuple définit séparément le délai de connexion et le délai de lecture. Un délai de lecture de 10 secondes signifie « attendre jusqu'à 10 secondes entre les octets après l'établissement de la connexion ».

Utiliser les sessions

Un objet requests.Session maintient des paramètres — en-têtes, cookies, authentification — sur plusieurs requêtes vers le même hôte. Il réutilise également la connexion TCP sous-jacente (mise en pool des connexions), ce qui est plus rapide que créer une nouvelle connexion à chaque appel.

import requests

with requests.Session() as session:
    # Set headers once — every request in this session will include them
    session.headers.update({
        "Authorization": "Bearer my-api-token",
        "Accept": "application/json",
    })

    # All requests reuse the connection and headers
    r1 = session.get("https://api.example.com/users")
    r2 = session.get("https://api.example.com/posts")
    r3 = session.post("https://api.example.com/todos", json={"title": "New"})

    print(r1.status_code, r2.status_code, r3.status_code)

Utilisez une session chaque fois que vous effectuez plus d'une requête vers le même serveur.

Inspecter la réponse

L'objet Response expose tout ce que le serveur a renvoyé :

import requests

response = requests.get("https://httpbin.org/get")

print(response.status_code)      # 200
print(response.reason)           # OK
print(response.headers)          # dict of response headers
print(response.headers["Content-Type"])  # application/json
print(response.encoding)         # utf-8
print(response.elapsed)          # how long the request took
print(response.url)              # final URL (after redirects)

Pour le contenu binaire (images, PDF), utilisez response.content (retourne des bytes) à la place de response.text :

import requests

response = requests.get("https://httpbin.org/image/png")
with open("image.png", "wb") as f:
    f.write(response.content)

Authentification

HTTP Basic Auth

Passez un tuple (username, password) à auth= :

import requests

response = requests.get(
    "https://httpbin.org/basic-auth/alice/secret",
    auth=("alice", "secret"),
)
print(response.status_code)   # 200

Jeton Bearer (clés API)

La plupart des API modernes utilisent un jeton Bearer dans l'en-tête Authorization :

import requests

headers = {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..."}
response = requests.get("https://api.example.com/me", headers=headers)

Ne codez jamais en dur les jetons dans les fichiers source. Chargez-les depuis des variables d'environnement ou un gestionnaire de secrets :

import os
import requests

token = os.environ["API_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
response = requests.get("https://api.example.com/me", headers=headers)

Exemple concret — API GitHub

Cet exemple illustre un modèle complet : session, authentification Bearer, pagination, gestion des erreurs et analyse JSON :

import os
import requests

GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
BASE_URL = "https://api.github.com"

with requests.Session() as session:
    session.headers.update({
        "Authorization": f"Bearer {GITHUB_TOKEN}",
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28",
    })

    try:
        # Fetch the first page of public repos for a user
        response = session.get(
            f"{BASE_URL}/users/torvalds/repos",
            params={"per_page": 5, "sort": "updated"},
            timeout=10,
        )
        response.raise_for_status()
        repos = response.json()

        for repo in repos:
            print(f"{repo['name']:40s}{repo['stargazers_count']}")

    except requests.exceptions.HTTPError as err:
        print(f"GitHub API error: {err}")
    except requests.exceptions.RequestException as err:
        print(f"Network error: {err}")

Ce modèle — session avec en-têtes partagés, raise_for_status(), try/except délimité — est l'approche prête pour la production pour tout client API que vous écrivez.

Requêtes concurrentes avec asyncio

Pour les programmes qui appellent de nombreux points de terminaison simultanément, la bibliothèque synchrone requests bloque à chaque appel. Passez à aiohttp (l'équivalent asynchrone) et combinez-le avec le module asyncio de Python :

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.json()

async def main():
    urls = [
        "https://jsonplaceholder.typicode.com/todos/1",
        "https://jsonplaceholder.typicode.com/todos/2",
        "https://jsonplaceholder.typicode.com/todos/3",
    ]
    async with aiohttp.ClientSession() as session:
        results = await asyncio.gather(*[fetch(session, u) for u in urls])
    for r in results:
        print(r["title"])

asyncio.run(main())

Consultez le chapitre Python asyncio pour comprendre le fonctionnement de async/await avant d'adopter ce modèle.

Référence rapide

TâcheCode
Requête GETrequests.get(url)
GET avec paramètresrequests.get(url, params={"key": "val"})
GET avec en-têtesrequests.get(url, headers={"Authorization": "Bearer token"})
POST corps JSONrequests.post(url, json={"key": "val"})
POST données de formulairerequests.post(url, data={"key": "val"})
Téléverser un fichierrequests.post(url, files={"file": open("f.pdf", "rb")})
PUT / PATCH / DELETErequests.put/patch/delete(url, json=...)
Vérifier le statutresponse.status_code
Lever une exception sur 4xx/5xxresponse.raise_for_status()
Analyser le corps JSONresponse.json()
Corps texte brutresponse.text
Corps en octets brutsresponse.content
Définir un délai d'expirationrequests.get(url, timeout=5)
Réutiliser la connexionwith requests.Session() as s: ...

Chapitres associés

Pratique

Pratique
Which argument sends a Python dict as a JSON body in a POST request?
Which argument sends a Python dict as a JSON body in a POST request?
Was this page helpful?