Construiește-ți propria bază de date de filme dintr-un API

amodb este un API independent care vorbește protocolul TMDB v3. Filmele, persoanele, companiile, colecțiile, cuvintele-cheie și genurile au toate ID-uri numerice și trimit unele la altele, așa că poți umple tabele SQL din el și le poți ține la zi. Încarcă tabelele cu paginarea discover, reîmprospătează-le după vechime și potrivește-ți propriii identificatori cu find. amodb nu are flux de modificări, așa că o reîmprospătare este pur și simplu o nouă preluare a rândurilor pe care le păstrezi.

Ce primești:

  • Șase tipuri de entități legate, cu ID-uri numerice stabile.
  • Paginare discover pentru prima încărcare: până la 20 de rezultate pe pagină, 500 de pagini pe interogare.
  • Ferestre de date de lansare în discover, ca să tai prima încărcare în felii și să listezi lansările noi.
  • Căutări după ID extern pentru imdb_id, tvdb_id, wikidata_id și kinopoisk_id.

Ce date primești

Acesta este modelul de date. Fiecare rând este un tabel într-o copie relațională.

EntitateSe citește cuCheieSe leagă de
FilmGET /3/movie/{movie_id}id, imdb_idgenres[].id, production_companies[].id, belongs_to_collection.id
PersoanăGET /3/person/{person_id}id, imdb_idFilme prin /3/movie/{movie_id}/credits
CompanieGET /3/company/{company_id}idFilme prin with_companies în discover. parent_company este mereu null, iar description este gol
ColecțieGET /3/collection/{collection_id}idparts[], o listă de filme
Cuvânt-cheieGET /3/keyword/{keyword_id}idFilme prin /3/keyword/{keyword_id}/movies sau with_keywords
GenGET /3/genre/movie/listidgenres[].id la filme

Legăturile mulți la mulți cer tabele de joncțiune: movie_genre, movie_company, movie_keyword și movie_credit. Folosește credit_id din credite drept cheie pentru ultimul. Listele de câmpuri sunt la filme, persoane și colecții.

Începe în 3 pași

  1. Compară cele trei ritmuri de cereri la prețuri. Un ritm mai mare scurtează prima încărcare, nimic altceva nu se schimbă.
  2. Creează o cheie în panoul contului tău și păstrează-o într-o variabilă de mediu.
  3. Apelează /3/discover/movie pentru o fereastră de o zi de date de lansare, apoi preia fiecare ID din listă.

Exemplu de cerere

Scriptul PHP listează filmele lansate ieri și azi prin discover și face upsert pentru fiecare înregistrare într-un tabel SQLite. Doarme 3 secunde între citirile de detalii ca să rămână sub 20 de cereri pe minut, reîncearcă un 429 după Retry-After și șterge rândurile al căror ID întoarce acum 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 întoarce rânduri de listă, nu înregistrări complete. Acestea sunt 2 dintre cele 20 de rezultate de pe pagina 1. Citește /3/movie/{id} pentru restul câmpurilor.

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
}

Umple și reîmprospătează tabelele

SarcinăApelLimite
Prima încărcareGET /3/discover/movie cu sort_by și o fereastră de datepage de la 1 la 500, până la 20 de rezultate pe pagină, deci 10,000 pe interogare. Împarte pe primary_release_date.gte și .lte ca să mergi mai departe.
Lansări noiGET /3/discover/movie cu primary_release_date.gte setat la ultima ta rulare, și GET /3/movie/upcomingȚinute în cache 30 de minute.
Reîmprospătare rânduriGET /3/movie/{movie_id} pentru rândurile mai vechi de 6 oreNu există flux de modificări. Reîmprospătează titlurile pe care le deschid utilizatorii, iar restul mai rar.
Legarea propriilor ID-uriGET /3/find/{external_id}Un ID pe apel, sursa se alege cu external_source.
Umplere la cerereGET /3/movie/{movie_id} când un utilizator deschide un titluStochează răspunsul cu un marcaj de timp.

Fereastra de paginare este principala alegere de proiectare. Când total_pages dintr-un răspuns este peste 500, fereastra conține mai mult de 10,000 de rânduri, așa că împarte-o la jumătate până încape. Pentru un an recent îți trebuie luni, iar o singură zi de lansări înseamnă câteva sute de titluri. Stochează felia pe care ai terminat-o, ca o rulare întreruptă să poată continua. Regulile de potrivire pentru legare sunt la căutare după ID extern.

Dimensionarea sincronizării

Fiecare rând pe care îl reîmprospătezi costă o citire de detalii. Un plan de 20 de cereri pe minut dă 1,200 de apeluri pe oră, cel de 40 dă 2,400, iar cel de 120 dă 7,200. O pagină discover listează 20 de ID-uri pentru un singur apel. Rulează reîmprospătarea în afara orelor de vârf dacă aplicația ta folosește aceeași cheie, pentru că limita este pe cheie, nu pe script. Vezi limite de cereri pentru antetele care arată ce a mai rămas din fereastră.

Cache pe două straturi

StratConțineTTL
Cache la margineDetalii și credite6 ore
Cache la margineListe discover, în tendințe și populare30 de minute
Cache la margineCăutare10 minute
Cache la margineConfigurație24 de ore
Cache la margineImagini30 de zile
Tabelele taleRândurile pe care le păstreziPână când regula ta de vechime spune să le preiei din nou, niciodată mai devreme de 6 ore

Cache-ul la margine este partajat între chei, dar limita de cereri rulează înaintea lui, așa că o lovitură de cache consumă tot o cerere din minutul tău. Recitirea unui rând mai des de 6 ore întoarce aceeași copie și irosește cota. Lasă o regulă de vechime să decidă când preiei din nou.

Cum se compară

CapacitateExtragi singur paginileFișiere de set de date staticeamodb
ProspețimeCât de proaspăt e ultimul tău crawlCât de proaspăt e ultimul fișier publicatDetalii în cache 6 ore, preluare după vechime
Actualizări incrementaleReiei crawl-ul și compariDescarci un fișier nouDiscover cu o fereastră de date de lansare, fără flux de modificări
ID-uri între entitățiURL-uri de paginiDefinite de editorID-uri numerice, imdb_id și /3/find
Ce ruleziCrawlere și stocareStocare și un job de importStocare și un script de sincronizare
Limită de cereriStabilită de site-urile pe care le parcurgiNu se aplică20, 40 sau 120 de cereri pe minut, numărate pe cheie
Limbi pe înregistrareCe oferă fiecare paginăDepinde de fișiere14, alese cu language

Prețuri

Prima încărcare este partea scumpă și este limitată de numărul tău de cereri pe minut. Alege 20, 40 sau 120 pe minut la prețuri. Plătești pentru 1, 3, 6, 12 sau 24 de luni, cu reduceri de 0, 10, 20, 30 sau 40 la sută. Nimic nu se reînnoiește automat, nu există nivel gratuit, iar fiecare plan citește aceleași date.