Crie seu próprio banco de dados de filmes a partir de uma API

O amodb é uma API independente que fala o protocolo TMDB v3. Filmes, pessoas, produtoras, coleções, palavras-chave e gêneros têm ids numéricos e apontam uns para os outros, então você pode preencher tabelas SQL com eles e mantê-las atualizadas. Faça a carga inicial das tabelas com a paginação do discover, atualize-as por idade e relacione seus próprios identificadores com find. O amodb não tem feed de alterações, então atualizar é só buscar de novo as linhas que você mantém.

O que você recebe:

  • Seis tipos de entidades ligadas, com ids numéricos estáveis.
  • Paginação do discover para a primeira carga: até 20 resultados por página, 500 páginas por consulta.
  • Janelas de datas de lançamento no discover, para fatiar a primeira carga e listar lançamentos novos.
  • Buscas por id externo para imdb_id, tvdb_id, wikidata_id e kinopoisk_id.

Quais dados você recebe

Este é o modelo de dados. Cada linha é uma tabela em uma cópia relacional.

EntidadeLeia comChaveLiga a
FilmeGET /3/movie/{movie_id}id, imdb_idgenres[].id, production_companies[].id, belongs_to_collection.id
PessoaGET /3/person/{person_id}id, imdb_idFilmes por /3/movie/{movie_id}/credits
ProdutoraGET /3/company/{company_id}idFilmes por with_companies no discover. parent_company é sempre null e description fica vazio
ColeçãoGET /3/collection/{collection_id}idparts[], uma lista de filmes
Palavra-chaveGET /3/keyword/{keyword_id}idFilmes por /3/keyword/{keyword_id}/movies ou with_keywords
GêneroGET /3/genre/movie/listidgenres[].id nos filmes

Relações muitos para muitos precisam de tabelas de junção: movie_genre, movie_company, movie_keyword e movie_credit. Use credit_id de créditos como chave da última. As listas de campos estão em filmes, pessoas e coleções.

Comece em 3 passos

  1. Compare as três taxas de requisição em preços. Uma taxa maior encurta a primeira carga, e nada mais muda.
  2. Crie uma chave no painel da sua conta e guarde-a em uma variável de ambiente.
  3. Chame /3/discover/movie para uma janela de um dia de datas de lançamento e depois busque cada id da lista.

Exemplo de requisição

O script PHP lista os filmes lançados ontem e hoje pelo discover e grava cada registro em uma tabela SQLite com upsert. Ele espera 3 segundos entre as leituras de detalhes para ficar abaixo de 20 requisições por minuto, repete um 429 depois de Retry-After e apaga as linhas cujo id agora retorna 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));

O discover retorna linhas de lista, não registros completos. Estes são 2 dos 20 resultados da página 1. Leia /3/movie/{id} para o restante dos campos.

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
}

Preencha e atualize as tabelas

TarefaChamadaLimites
Primeira cargaGET /3/discover/movie com sort_by e uma janela de dataspage de 1 a 500, até 20 resultados por página, ou seja, 10,000 por consulta. Fatie por primary_release_date.gte e .lte para ir além.
Lançamentos novosGET /3/discover/movie com primary_release_date.gte igual à data da sua última execução, e GET /3/movie/upcomingEm cache por 30 minutos.
Atualizar linhasGET /3/movie/{movie_id} para linhas com mais de 6 horasNão há feed de alterações. Atualize os títulos que os usuários abrem e o restante com menos frequência.
Relacionar seus idsGET /3/find/{external_id}Um id por chamada, com a fonte escolhida em external_source.
Preenchimento sob demandaGET /3/movie/{movie_id} quando um usuário abre um títuloGuarde a resposta com um carimbo de data e hora.

A janela de paginação é a principal decisão de projeto. Quando total_pages em uma resposta passa de 500, a janela tem mais de 10,000 linhas, então divida-a ao meio até caber. Um ano recente precisa ser cortado em meses, e um dia de lançamentos tem algumas centenas de títulos. Guarde a fatia que você terminou para que uma execução interrompida possa continuar. As regras de correspondência para a junção estão em buscar por id externo.

Dimensionando a sincronização

Cada linha que você atualiza custa uma leitura de detalhes. Um plano de 20 requisições por minuto dá 1,200 chamadas por hora, o de 40 dá 2,400 e o de 120 dá 7,200. Uma página do discover lista 20 ids com uma chamada. Rode a atualização fora do horário de pico se seu app compartilha a chave, porque o limite vale por chave, não por script. Veja limites de requisições para os headers que informam o que resta na janela.

Cache em duas camadas

CamadaGuardaTTL
Cache de bordaDetalhes e créditos6 horas
Cache de bordaListas de discover, em alta e populares30 minutos
Cache de bordaBusca10 minutos
Cache de bordaConfiguração24 horas
Cache de bordaImagens30 dias
Suas tabelasLinhas que você mantémAté sua regra de idade mandar buscar de novo, nunca antes de 6 horas

O cache de borda é compartilhado entre chaves, mas o limite de requisições age antes dele, então um acerto de cache ainda usa uma requisição do seu minuto. Reler uma linha com mais frequência que a cada 6 horas retorna a mesma cópia e desperdiça cota. Deixe uma regra de idade decidir quando buscar de novo.

Como se compara

RecursoFazer scraping das páginasArquivos de dataset estáticosamodb
AtualidadeTão atual quanto seu último rastreamentoTão atual quanto o último arquivo publicadoDetalhes em cache por 6 horas, nova busca por idade
Atualizações incrementaisRastrear de novo e compararBaixar um arquivo novoDiscover com janela de datas de lançamento, sem feed de alterações
Ids entre entidadesURLs das páginasDefinidos pelo publicadorIds numéricos, imdb_id e /3/find
O que você mantémCrawlers e armazenamentoArmazenamento e um job de importaçãoArmazenamento e um script de sincronização
Limite de requisiçõesDefinido pelos sites que você rastreiaNão se aplica20, 40 ou 120 requisições por minuto, contadas por chave
Idiomas por registroO que cada página oferecerDepende dos arquivos14, escolhidos com language

Preços

A primeira carga é a parte cara, e ela é limitada pelas suas requisições por minuto. Escolha 20, 40 ou 120 por minuto em preços. Pague por 1, 3, 6, 12 ou 24 meses, com 0, 10, 20, 30 ou 40 por cento de desconto. Nada renova automaticamente, não há plano gratuito e todo plano lê os mesmos dados.