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.

.py Python Mis à jour le Voir en Markdown

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_KEY = "YOUR_API_KEY".

Shell
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.

Shell
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.

Python
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 /3/movie/{movie_id} et ajoutez append_to_response=credits,ratings 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.

Python
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.

StatutSignificationCe que fait le script
401Clé invalide ou suspendueS'arrête et dit de vérifier AMODB_KEY. Réessayer ne peut pas aider.
404Cet identifiant n'existe pasAffiche le status_message.
429Au-dessus de vos requêtes par minuteAttend Retry-After secondes (plafond 30), puis réessaie jusqu'à deux fois.
504Délai dépassé côté backendAffiché comme une erreur. Relancez la commande.
Échec réseauPas 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.

Python
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".

Python
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 de moods.results a un name et un avg sur 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.