Construire votre propre base de films depuis une API

amodb est une API indépendante qui parle le protocole TMDB v3. Les films, les personnes, les sociétés, les collections, les mots-clés et les genres ont tous des identifiants numériques et se renvoient les uns aux autres : vous pouvez donc remplir des tables SQL avec elle et les tenir à jour. Amorcez les tables avec la pagination de discover, actualisez-les selon l'âge, et reliez vos propres identifiants avec find. amodb n'a pas de flux de changements, donc une actualisation consiste simplement à relire les lignes que vous gardez.

Ce que vous obtenez :

  • Six types d'entités liées avec des identifiants numériques stables.
  • La pagination de discover pour le premier chargement : jusqu'à 20 résultats par page, 500 pages par requête.
  • Des fenêtres de dates de sortie sur discover, pour découper le premier chargement et lister les nouvelles sorties.
  • Des recherches par identifiant externe pour imdb_id, tvdb_id, wikidata_id et kinopoisk_id.

Les données obtenues

Voici le modèle de données. Chaque ligne correspond à une table dans une copie relationnelle.

EntitéLire avecCléLiens vers
FilmGET /3/movie/{movie_id}id, imdb_idgenres[].id, production_companies[].id, belongs_to_collection.id
PersonneGET /3/person/{person_id}id, imdb_idLes films via /3/movie/{movie_id}/credits
SociétéGET /3/company/{company_id}idLes films via with_companies dans discover. parent_company vaut toujours null et description est vide
CollectionGET /3/collection/{collection_id}idparts[], une liste de films
Mot-cléGET /3/keyword/{keyword_id}idLes films via /3/keyword/{keyword_id}/movies ou with_keywords
GenreGET /3/genre/movie/listidgenres[].id sur les films

Les liens plusieurs-à-plusieurs demandent des tables de jointure : movie_genre, movie_company, movie_keyword et movie_credit. Utilisez credit_id de crédits comme clé de la dernière. Les listes de champs sont sur films, personnes et collections.

Démarrer en 3 étapes

  1. Comparez les trois débits de requêtes sur les tarifs. Un débit plus élevé raccourcit le premier chargement, rien d'autre ne change.
  2. Créez une clé dans le tableau de bord de votre compte et gardez-la dans une variable d'environnement.
  3. Appelez /3/discover/movie pour une fenêtre d'un jour de dates de sortie, puis récupérez chaque identifiant de la liste.

Exemple de requête

Le script PHP liste les films sortis hier et aujourd'hui via discover et insère ou met à jour chaque fiche dans une table SQLite. Il attend 3 secondes entre les lectures de détails pour rester sous 20 requêtes par minute, réessaie un 429 après Retry-After, et supprime les lignes dont l'identifiant renvoie maintenant 404.

bashphp
curl -s "https://api.amodb.dev/3/discover/movie?primary_release_date.gte=2026-10-17&primary_release_date.lte=2026-10-18&sort_by=primary_release_date.asc&page=1" \
  -H "Authorization: Bearer $AMODB_KEY"
<?php
const BASE = 'https://api.amodb.dev/3';

function amodb(string $path, array $query = []): ?array
{
    $url = BASE . $path . '?' . http_build_query($query);
    for ($attempt = 0; $attempt < 3; $attempt++) {
        $retryAfter = 1;
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 10,
            CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('AMODB_KEY')],
            CURLOPT_HEADERFUNCTION => function ($handle, $line) use (&$retryAfter) {
                if (stripos($line, 'Retry-After:') === 0) {
                    $retryAfter = max(1, (int) trim(substr($line, 12)));
                }
                return strlen($line);
            },
        ]);
        $body = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($status === 429) {
            sleep($retryAfter);
            continue;
        }
        if ($status === 404) {
            return null;
        }
        if ($status !== 200) {
            throw new RuntimeException("amodb returned $status for $path");
        }
        return json_decode($body, true);
    }
    throw new RuntimeException("Still rate limited on $path");
}

$db = new PDO('sqlite:movies.sqlite');
$db->exec('CREATE TABLE IF NOT EXISTS movie (id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT, release_date TEXT, raw TEXT)');
$save = $db->prepare('INSERT OR REPLACE INTO movie (id, imdb_id, title, release_date, raw) VALUES (?, ?, ?, ?, ?)');
$drop = $db->prepare('DELETE FROM movie WHERE id = ?');

$from = date('Y-m-d', strtotime('-1 day'));
$to = date('Y-m-d');
$page = 1;
do {
    $list = amodb('/discover/movie', [
        'primary_release_date.gte' => $from,
        'primary_release_date.lte' => $to,
        'sort_by' => 'primary_release_date.asc',
        'page' => $page,
    ]);
    foreach ($list['results'] as $row) {
        $movie = amodb('/movie/' . $row['id'], ['language' => 'en-US']);
        if ($movie === null) {
            $drop->execute([$row['id']]);
            continue;
        }
        $save->execute([$movie['id'], $movie['imdb_id'] ?? null, $movie['title'], $movie['release_date'] ?? null, json_encode($movie)]);
        sleep(3);
    }
    $page++;
} while ($page <= min($list['total_pages'], 500));

Discover renvoie des lignes de liste, pas des fiches complètes. Voici 2 des 20 résultats de la page 1. Lisez /3/movie/{id} pour le reste des champs.

JSON
{
  "page": 1,
  "results": [
    {
      "adult": false,
      "backdrop_path": "/siepLRGVjM8yLKppApkD3uSh8Y0.jpg",
      "genre_ids": [
        35
      ],
      "id": 1800227,
      "original_language": "en",
      "original_title": "Sly's Pack",
      "overview": "Nesmith, Gary, & Tony are smoking weed on the roof of a parking garage, but a...",
      "popularity": 0.0071,
      "poster_path": "/3HpIWZUI7jmpKZkfZNdchS2WsvN.jpg",
      "release_date": "2026-10-17",
      "title": "Sly's Pack",
      "video": false,
      "vote_average": 0,
      "vote_count": 0
    },
    {
      "adult": false,
      "backdrop_path": null,
      "genre_ids": [
        99
      ],
      "id": 1800168,
      "original_language": "en",
      "original_title": "The Good Life",
      "overview": "The Good LIfe is based on the best-selling book by Drs. Robert Waldinger and...",
      "popularity": 0.0143,
      "poster_path": null,
      "release_date": "2026-10-17",
      "title": "The Good Life",
      "video": false,
      "vote_average": 0,
      "vote_count": 0
    }
  ],
  "total_pages": 15,
  "total_results": 299
}

Remplir et actualiser les tables

TâcheAppelLimites
Premier chargementGET /3/discover/movie avec sort_by et une fenêtre de datespage de 1 à 500, jusqu'à 20 résultats par page, soit 10 000 par requête. Découpez par primary_release_date.gte et .lte pour aller au-delà.
Nouvelles sortiesGET /3/discover/movie avec primary_release_date.gte réglé sur votre dernière exécution, et GET /3/movie/upcomingMis en cache 30 minutes.
Actualiser les lignesGET /3/movie/{movie_id} pour les lignes de plus de 6 heuresIl n'y a pas de flux de changements. Actualisez les titres que les utilisateurs ouvrent, et le reste moins souvent.
Relier vos identifiantsGET /3/find/{external_id}Un identifiant par appel, source choisie avec external_source.
Remplissage à la demandeGET /3/movie/{movie_id} quand un utilisateur ouvre un titreStockez la réponse avec un horodatage.

La fenêtre de pagination est le principal choix de conception. Quand total_pages dans une réponse dépasse 500, la fenêtre contient plus de 10 000 lignes : coupez-la en deux jusqu'à ce qu'elle tienne. Une année récente demande des mois, et un jour de sorties représente quelques centaines de titres. Stockez la tranche terminée pour qu'une exécution interrompue puisse reprendre. Les règles de correspondance pour la jointure sont sur recherche par identifiant externe.

Dimensionner la synchronisation

Chaque ligne que vous actualisez coûte une lecture de détails. Une offre à 20 requêtes par minute donne 1 200 appels par heure, 40 donne 2 400 et 120 donne 7 200. Une page de discover liste 20 identifiants pour un appel. Lancez l'actualisation en heures creuses si votre application partage la clé, car la limite est par clé et non par script. Voir limites de débit pour les en-têtes qui indiquent ce qu'il reste dans la fenêtre.

Une mise en cache en deux couches

CoucheContientDurée de vie
Cache de périphérieDétails et crédits6 heures
Cache de périphérieListes discover, tendances et populaires30 minutes
Cache de périphérieRecherche10 minutes
Cache de périphérieConfiguration24 heures
Cache de périphérieImages30 jours
Vos tablesLes lignes que vous gardezJusqu'à ce que votre règle d'âge impose de relire, jamais avant 6 heures

Le cache de périphérie est partagé entre les clés, mais la limite de débit passe avant lui : une réponse servie depuis le cache consomme donc quand même une requête de votre minute. Relire une ligne plus souvent que toutes les 6 heures renvoie la même copie et gaspille du quota. Laissez une règle d'âge décider quand relire.

Comparaison

CapacitéExtraire les pages soi-mêmeFichiers de jeux de données statiquesamodb
FraîcheurAussi frais que votre dernier parcoursAussi frais que le dernier fichier publiéDétails en cache 6 heures, relecture selon l'âge
Mises à jour incrémentalesParcourir de nouveau et comparerTélécharger un nouveau fichierDiscover avec une fenêtre de dates de sortie, pas de flux de changements
Identifiants entre entitésURL de pagesDéfinis par l'éditeurIdentifiants numériques, imdb_id et /3/find
Ce que vous exploitezDes robots d'exploration et du stockageDu stockage et une tâche d'importDu stockage et un script de synchronisation
Limite de débitFixée par les sites parcourusSans objet20, 40 ou 120 requêtes par minute, comptées par clé
Langues par ficheCe que propose chaque pageSelon les fichiers14, choisies avec language

Tarifs

Le premier chargement est la partie coûteuse, et il est borné par vos requêtes par minute. Choisissez 20, 40 ou 120 par minute sur les tarifs. Payez 1, 3, 6, 12 ou 24 mois avec 0, 10, 20, 30 ou 40 pour cent de réduction. Rien ne se renouvelle automatiquement, il n'y a pas d'offre gratuite, et chaque offre lit les mêmes données.