Tutoriel API de films en Python
Créez un petit outil Python en ligne de commande qui prend un titre de film, le cherche, puis récupère détails, distribution et notes en un appel de plus. Il vous faut Python 3.10 ou plus récent, la bibliothèque requests et une clé amodb. Cela prend environ 15 minutes et se termine par un script complet.
Vous allez construire movie.py, un outil en ligne de commande. Vous lui passez un titre, il cherche, retient la première correspondance et affiche l'année, la durée, les genres, la distribution principale, la note IMDb et une URL d'affiche. Il demande Python 3.10 ou plus récent, la bibliothèque requests et une clé amodb. Prévoyez 15 minutes.
Étape 1 : obtenir une clé d'API
amodb est prépayé et sans offre gratuite : choisissez donc d'abord une offre sur les tarifs. Les offres à 20, 40 et 120 requêtes par minute renvoient toutes les mêmes données. Créez ensuite une clé dans le tableau de bord de votre compte. Une clé fait 32 caractères hexadécimaux en minuscules.
Exportez-la dans une variable d'environnement pour qu'elle reste hors de votre code source :
Sous Windows PowerShell, la même ligne est $env:AMODB_.
export AMODB_KEY="YOUR_API_KEY"Étape 2 : mettre en place le projet
Créez un environnement virtuel et installez requests :
Sous Windows, lancez .venv\Scripts\activate à la place de la deuxième ligne.
python -m venv .venv
source .venv/bin/activate
pip install requestsÉtape 3 : envoyer la première requête
Le point de terminaison de recherche est GET /3/search/movie avec un paramètre query. Cet extrait affiche le statut HTTP et l'identifiant de la meilleure correspondance. Passez toujours un délai d'expiration, car requests attend indéfiniment par défaut.
La réponse a la forme TMDB : un tableau results trié par pertinence, plus page et total_results. Voir recherche pour tous les filtres.
import os
import requests
res = requests.get(
"https://api.amodb.dev/3/search/movie",
params={"query": "fight club", "language": "en-US"},
headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
timeout=10,
)
print(res.status_code, res.json()["results"][0]["id"])Étape 4 : récupérer les détails, la distribution et les notes
Les résultats de recherche sont courts. Utilisez l'identifiant avec GET / et ajoutez append_ pour obtenir la distribution et les notes IMDb, Kinopoisk, Metacritic, Rotten Tomatoes et TMDB dans la même requête. Chaque note est un objet avec value et votes, ou null quand la source n'est pas connue : testez donc avant d'afficher. Les détails sont sur les pages films et notes. Dans le script final, session est une requests.Session qui porte déjà l'en-tête Authorization.
params = {"language": "en-US", "append_to_response": "credits,ratings"}
movie = session.get(f"{BASE}/movie/{movie_id}", params=params, timeout=10).json()
imdb = (movie.get("ratings") or {}).get("imdb")Étape 5 : gérer les erreurs et les limites de débit
Vérifiez le statut avant d'analyser le corps. Les erreurs utilisent le corps TMDB avec status_code et status_message.
| Statut | Signification | Ce que fait le script |
|---|---|---|
| 401 | Clé invalide ou suspendue | S'arrête et dit de vérifier AMODB_KEY. Réessayer ne peut pas aider. |
| 404 | Cet identifiant n'existe pas | Affiche le status_message. |
| 429 | Au-dessus de vos requêtes par minute | Attend Retry-After secondes (plafond 30), puis réessaie jusqu'à deux fois. |
| 504 | Délai dépassé côté backend | Affiché comme une erreur. Relancez la commande. |
| Échec réseau | Pas de connexion ou délai dépassé | Intercepté comme requests.RequestException et signalé. |
La boucle de nouvelle tentative est courte :
D'autres codes sont listés sur erreurs, et les règles de la fenêtre sont sur limites de débit.
for attempt in range(3):
res = session.get(url, params=params, timeout=10)
if res.status_code != 429 or attempt == 2:
break
time.sleep(min(int(res.headers.get("Retry-After", "1")), 30))Étape 6 : mettre les réponses en cache sur disque
La périphérie garde les détails 6 heures et la recherche 10 minutes. Votre propre cache économise l'aller-retour et ne dépense aucune requête de votre limite. Le script final stocke chaque réponse dans un fichier nommé d'après un hachage du chemin et des paramètres, et réutilise le fichier tant qu'il est plus récent que la durée de vie.
Étape 7 : le programme complet
Enregistrez ceci sous movie.py et lancez python movie.py "fight club".
import hashlib
import json
import os
import sys
import time
from pathlib import Path
import requests
BASE = "https://api.amodb.dev/3"
POSTER = "https://img.amodb.dev/t/p/w342"
CACHE_DIR = Path.home() / ".cache" / "amodb-movie-cli"
SEARCH_TTL = 10 * 60
DETAILS_TTL = 6 * 60 * 60
class ApiError(Exception):
pass
def error_message(res):
try:
return res.json()["status_message"]
except (ValueError, KeyError):
return res.reason
def get(session, path, params, ttl):
CACHE_DIR.mkdir(parents=True, exist_ok=True)
stamp = f"{path}?{json.dumps(params, sort_keys=True)}"
cache_file = CACHE_DIR / (hashlib.sha256(stamp.encode()).hexdigest() + ".json")
if cache_file.exists() and time.time() - cache_file.stat().st_mtime < ttl:
return json.loads(cache_file.read_text(encoding="utf-8"))
for attempt in range(3):
try:
res = session.get(BASE + path, params=params, timeout=10)
except requests.RequestException as exc:
raise ApiError(f"network error: {exc}") from exc
if res.status_code != 429 or attempt == 2:
break
time.sleep(min(int(res.headers.get("Retry-After", "1")), 30))
if res.status_code == 401:
raise ApiError("401: the key is invalid or suspended. Check AMODB_KEY.")
if res.status_code != 200:
raise ApiError(f"{res.status_code}: {error_message(res)}")
cache_file.write_text(res.text, encoding="utf-8")
return res.json()
def search_movie(session, title):
data = get(session, "/search/movie", {"query": title, "language": "en-US"}, SEARCH_TTL)
if not data["results"]:
raise ApiError(f"no movie found for {title!r}")
return data["results"][0]["id"]
def movie_details(session, movie_id):
params = {"language": "en-US", "append_to_response": "credits,ratings"}
return get(session, f"/movie/{movie_id}", params, DETAILS_TTL)
def print_card(movie):
year = (movie.get("release_date") or "")[:4] or "unknown year"
genres = ", ".join(g["name"] for g in movie["genres"])
cast = ", ".join(p["name"] for p in movie["credits"]["cast"][:3])
imdb = (movie.get("ratings") or {}).get("imdb")
print(f"{movie['title']} ({year}), {movie.get('runtime') or '?'} min")
print(f"Genres: {genres or 'none listed'}")
print(f"Cast: {cast or 'none listed'}")
print(f"IMDb: {imdb['value']} ({imdb['votes']} votes)" if imdb else "IMDb: not known")
if movie.get("poster_path"):
print(f"Poster: {POSTER}{movie['poster_path']}")
print(movie.get("overview") or "No overview in this language.")
def main():
if len(sys.argv) < 2:
sys.exit('Usage: python movie.py "fight club"')
key = os.environ.get("AMODB_KEY")
if not key:
sys.exit("Set AMODB_KEY first.")
session = requests.Session()
session.headers["Authorization"] = f"Bearer {key}"
try:
movie_id = search_movie(session, " ".join(sys.argv[1:]))
print_card(movie_details(session, movie_id))
except ApiError as exc:
sys.exit(f"error: {exc}")
if __name__ == "__main__":
main()Pour aller plus loin
- Affichez une affiche dans une autre taille. L'hôte d'images accepte toutes les tailles listées sur images et n'exige aucune clé.
- Ajoutez un filtre
yearà l'appel de recherche, ou passez à découverte pour des listes filtrées. - Affichez un indicateur d'ambiance à côté de la note en ajoutant
moodsàappend_to_response. Chaque entrée demoods.resultsa unnameet unavgsur une échelle de 0 à 10. - Lisez le démarrage rapide pour le même parcours avec curl, puis essayez le tutoriel JavaScript pour une version par lots.