Zbuduj własną bazę filmów z API

amodb to niezależne API, które mówi protokołem TMDB v3. Filmy, osoby, firmy, kolekcje, słowa kluczowe i gatunki mają numeryczne ID i wskazują na siebie nawzajem, więc możesz zasilać z nich tabele SQL i utrzymywać je w aktualnym stanie. Zasil tabele paginacją discover, odświeżaj je według wieku i dopasowuj własne identyfikatory przez find. amodb nie ma kanału zmian, więc odświeżenie to zwykłe ponowne pobranie wierszy, które zachowujesz.

Co dostajesz:

  • Sześć powiązanych typów encji ze stałymi numerycznymi ID.
  • Paginację discover do pierwszego ładowania: do 20 wyników na stronę, 500 stron na zapytanie.
  • Okna dat premier w discover, którymi podzielisz pierwsze ładowanie i wypiszesz nowe premiery.
  • Wyszukiwanie po zewnętrznych ID imdb_id, tvdb_id, wikidata_id i kinopoisk_id.

Jakie dane dostajesz

To model danych. Każdy wiersz to jedna tabela w kopii relacyjnej.

EncjaOdczytKluczWskazuje na
FilmGET /3/movie/{movie_id}id, imdb_idgenres[].id, production_companies[].id, belongs_to_collection.id
OsobaGET /3/person/{person_id}id, imdb_idFilmy przez /3/movie/{movie_id}/credits
FirmaGET /3/company/{company_id}idFilmy przez with_companies w discover. parent_company jest zawsze null, a description jest puste
KolekcjaGET /3/collection/{collection_id}idparts[], lista filmów
Słowo kluczoweGET /3/keyword/{keyword_id}idFilmy przez /3/keyword/{keyword_id}/movies lub with_keywords
GatunekGET /3/genre/movie/listidgenres[].id w filmach

Powiązania wiele do wielu wymagają tabel łączących: movie_genre, movie_company, movie_keyword i movie_credit. Jako klucza ostatniej użyj credit_id ze strony obsada i ekipa. Listy pól są na stronach filmy, osoby i kolekcje.

Start w 3 krokach

  1. Porównaj trzy tempa żądań na stronie cennik. Wyższe tempo skraca pierwsze ładowanie, nic więcej się nie zmienia.
  2. Utwórz klucz w panelu konta i trzymaj go w zmiennej środowiskowej.
  3. Wywołaj /3/discover/movie dla jednodniowego okna dat premier, a potem pobierz każde ID z listy.

Przykładowe żądanie

Skrypt w PHP wypisuje filmy wydane wczoraj i dziś przez discover i zapisuje (upsert) każdy rekord do tabeli SQLite. Między odczytami szczegółów czeka 3 sekundy, by zmieścić się w 20 żądaniach na minutę, ponawia 429 po Retry-After i usuwa wiersze, których ID zwraca teraz 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 zwraca wiersze listy, nie pełne rekordy. To 2 z 20 wyników na stronie 1. Pozostałe pola pobierz z /3/movie/{id}.

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
}

Wypełnianie i odświeżanie tabel

ZadanieWywołanieLimity
Pierwsze ładowanieGET /3/discover/movie z sort_by i oknem datpage od 1 do 500, do 20 wyników na stronę, czyli 10 000 na zapytanie. Aby pójść dalej, dziel według primary_release_date.gte i .lte.
Nowe premieryGET /3/discover/movie z primary_release_date.gte ustawionym na ostatni przebieg oraz GET /3/movie/upcomingW cache przez 30 minut.
Odświeżanie wierszyGET /3/movie/{movie_id} dla wierszy starszych niż 6 godzinNie ma kanału zmian. Odświeżaj tytuły, które otwierają użytkownicy, a resztę rzadziej.
Łączenie własnych IDGET /3/find/{external_id}Jedno ID na wywołanie, źródło wybierane przez external_source.
Leniwe wypełnianieGET /3/movie/{movie_id}, gdy użytkownik otwiera tytułZapisz odpowiedź ze znacznikiem czasu.

Okno paginacji to główna decyzja projektowa. Gdy total_pages w odpowiedzi przekracza 500, okno zawiera ponad 10 000 wierszy, więc tnij je na pół, aż się zmieści. Ostatni rok wymaga miesięcy, a jeden dzień premier to kilkaset tytułów. Zapisuj ukończony wycinek, aby przerwany przebieg mógł wznowić pracę. Zasady dopasowania do łączenia opisuje strona wyszukiwanie po zewnętrznym ID.

Wymiarowanie synchronizacji

Każdy odświeżany wiersz kosztuje jeden odczyt szczegółów. Plan z 20 żądaniami na minutę daje 1 200 wywołań na godzinę, 40 daje 2 400, a 120 daje 7 200. Strona discover wypisuje 20 ID za jedno wywołanie. Uruchamiaj odświeżanie poza godzinami szczytu, jeśli aplikacja dzieli klucz, bo limit dotyczy klucza, a nie skryptu. Nagłówki podające, ile zostało w oknie, opisują limity żądań.

Cache w dwóch warstwach

WarstwaZawieraTTL
Cache na edgeSzczegóły i obsadę6 godzin
Cache na edgeListy discover, trending i popular30 minut
Cache na edgeWyszukiwanie10 minut
Cache na edgeKonfigurację24 godziny
Cache na edgeObrazy30 dni
Twoje tabeleWiersze, które zachowujeszDopóki reguła wieku nie każe pobrać ponownie, nigdy wcześniej niż po 6 godzinach

Cache na edge jest współdzielony między kluczami, ale limit żądań działa przed nim, więc trafienie w cache nadal zużywa jedno żądanie z twojej minuty. Ponowny odczyt wiersza częściej niż co 6 godzin zwraca tę samą kopię i marnuje limit. Decyzję o ponownym pobraniu zostaw regule wieku.

Jak wypada w porównaniu

MożliwośćWłasny scraping stronStatyczne pliki zbioru danychamodb
ŚwieżośćTak świeże jak ostatni crawlTak świeże jak ostatni opublikowany plikSzczegóły w cache 6 godzin, odświeżanie według wieku
Aktualizacje przyrostowePonowny crawl i porównaniePobranie nowego plikuDiscover z oknem dat premier, bez kanału zmian
ID między encjamiAdresy URL stronUstala wydawcaNumeryczne ID, imdb_id i /3/find
Co uruchamiaszCrawlery i magazyn danychMagazyn danych i zadanie importuMagazyn danych i skrypt synchronizacji
Limit żądańUstalają strony, które przeszukujeszNie dotyczy20, 40 lub 120 żądań na minutę, liczone na klucz
Języki na rekordCo oferuje dana stronaZależy od plików14, wybierane przez language

Cennik

Kosztowne jest pierwsze ładowanie i ogranicza je liczba żądań na minutę. Wybierz 20, 40 lub 120 na minutę na stronie cennik. Płacisz za 1, 3, 6, 12 lub 24 miesiące ze zniżką 0, 10, 20, 30 lub 40 procent. Nic nie odnawia się automatycznie, nie ma darmowego planu, a każdy plan czyta te same dane.