Costruire il tuo database di film da un'API

amodb è un'API indipendente che parla il protocollo TMDB v3. Film, persone, società, collezioni, parole chiave e generi hanno tutti ID numerici e si richiamano a vicenda, quindi puoi riempire delle tabelle SQL con essa e tenerle aggiornate. Inizializza le tabelle con la paginazione di discover, aggiornale in base all'età e collega i tuoi identificativi con find. amodb non ha un feed delle modifiche, quindi un aggiornamento è semplicemente una nuova lettura delle righe che tieni.

Cosa ottieni:

  • Sei tipi di entità collegate con ID numerici stabili.
  • La paginazione di discover per il primo caricamento: fino a 20 risultati per pagina, 500 pagine per query.
  • Finestre di date di uscita su discover, per suddividere il primo caricamento e per elencare le nuove uscite.
  • Ricerche per ID esterno per imdb_id, tvdb_id, wikidata_id e kinopoisk_id.

Quali dati ottieni

Questo è il modello dei dati. Ogni riga è una tabella in una copia relazionale.

EntitàSi legge conChiaveCollegamenti a
FilmGET /3/movie/{movie_id}id, imdb_idgenres[].id, production_companies[].id, belongs_to_collection.id
PersonaGET /3/person/{person_id}id, imdb_idI film tramite /3/movie/{movie_id}/credits
SocietàGET /3/company/{company_id}idI film tramite with_companies in discover. parent_company è sempre null e description è vuoto
CollezioneGET /3/collection/{collection_id}idparts[], una lista di film
Parola chiaveGET /3/keyword/{keyword_id}idI film tramite /3/keyword/{keyword_id}/movies o with_keywords
GenereGET /3/genre/movie/listidgenres[].id sui film

I collegamenti molti a molti richiedono tabelle di join: movie_genre, movie_company, movie_keyword e movie_credit. Usa credit_id da crediti come chiave dell'ultima. Gli elenchi dei campi sono nelle pagine film, persone e collezioni.

Parti in 3 passaggi

  1. Confronta le tre frequenze di richieste nella pagina dei prezzi. Una frequenza maggiore accorcia il primo caricamento, nient'altro cambia.
  2. Crea una chiave nella dashboard del tuo account e tienila in una variabile d'ambiente.
  3. Chiama /3/discover/movie per una finestra di un giorno di date di uscita, poi recupera ogni ID della lista.

Esempio di richiesta

Lo script PHP elenca i film usciti ieri e oggi tramite discover e inserisce o aggiorna ogni scheda in una tabella SQLite. Attende 3 secondi tra le letture dei dettagli per restare sotto le 20 richieste al minuto, ritenta un 429 dopo Retry-After, ed elimina le righe il cui ID ora restituisce 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 restituisce righe di lista, non schede complete. Questi sono 2 dei 20 risultati della pagina 1. Leggi /3/movie/{id} per gli altri campi.

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
}

Riempire e aggiornare le tabelle

CompitoChiamataLimiti
Primo caricamentoGET /3/discover/movie con sort_by e una finestra di datepage da 1 a 500, fino a 20 risultati per pagina, cioè 10.000 per query. Dividi con primary_release_date.gte e .lte per andare oltre.
Nuove usciteGET /3/discover/movie con primary_release_date.gte impostato sull'ultima esecuzione, e GET /3/movie/upcomingIn cache per 30 minuti.
Aggiornare le righeGET /3/movie/{movie_id} per le righe più vecchie di 6 oreNon c'è un feed delle modifiche. Aggiorna i titoli che gli utenti aprono, e gli altri meno spesso.
Collegare i tuoi IDGET /3/find/{external_id}Un ID per chiamata, fonte scelta con external_source.
Riempimento su richiestaGET /3/movie/{movie_id} quando un utente apre un titoloSalva la risposta con un timestamp.

La finestra di paginazione è la scelta progettuale principale. Quando total_pages in una risposta supera 500, la finestra contiene più di 10.000 righe, quindi dividila a metà finché non rientra. Un anno recente richiede dei mesi, e un giorno di uscite sono poche centinaia di titoli. Salva la fetta completata così un'esecuzione interrotta può riprendere. Le regole di corrispondenza per il join sono nella pagina ricerca per ID esterno.

Dimensionare la sincronizzazione

Ogni riga che aggiorni costa una lettura di dettagli. Un piano da 20 richieste al minuto dà 1.200 chiamate all'ora, 40 ne dà 2.400 e 120 ne dà 7.200. Una pagina di discover elenca 20 ID con una chiamata. Esegui l'aggiornamento nelle ore di minore traffico se la tua app condivide la chiave, perché il limite è per chiave e non per script. Vedi limiti di richieste per gli header che indicano quanto resta nella finestra.

Cache su due livelli

LivelloContieneTTL
Cache edgeDettagli e crediti6 ore
Cache edgeListe discover, tendenze e popolari30 minuti
Cache edgeRicerca10 minuti
Cache edgeConfigurazione24 ore
Cache edgeImmagini30 giorni
Le tue tabelleLe righe che tieniFinché la tua regola sull'età non impone di rileggere, mai prima di 6 ore

La cache edge è condivisa tra le chiavi, ma il limite di richieste viene prima, quindi una risposta dalla cache usa comunque una richiesta del tuo minuto. Rileggere una riga più spesso che ogni 6 ore restituisce la stessa copia e spreca quota. Lascia che sia una regola sull'età a decidere quando rileggere.

Confronto

FunzionalitàFare scraping delle pagine da soloFile di dataset staticiamodb
FreschezzaFresco quanto la tua ultima scansioneFresco quanto l'ultimo file pubblicatoDettagli in cache 6 ore, rilettura in base all'età
Aggiornamenti incrementaliRifare la scansione e confrontareScaricare un nuovo fileDiscover con finestra di date di uscita, nessun feed delle modifiche
ID tra entitàURL delle pagineDefiniti dall'editoreID numerici, imdb_id e /3/find
Cosa gestisciCrawler e storageStorage e un job di importazioneStorage e uno script di sincronizzazione
Limite di richiesteStabilito dai siti che scansioniNon applicabile20, 40 o 120 richieste al minuto, contate per chiave
Lingue per schedaQuelle offerte da ogni paginaDipende dai file14, scelte con language

Prezzi

Il primo caricamento è la parte costosa, ed è limitato dalle tue richieste al minuto. Scegli 20, 40 o 120 al minuto nella pagina dei prezzi. Paga 1, 3, 6, 12 o 24 mesi con uno sconto di 0, 10, 20, 30 o 40 per cento. Nulla si rinnova automaticamente, non esiste un piano gratuito, e ogni piano legge gli stessi dati.