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_idekinopoisk_id.
Quais dados você recebe
Este é o modelo de dados. Cada linha é uma tabela em uma cópia relacional.
| Entidade | Leia com | Chave | Liga a |
|---|---|---|---|
| Filme | GET / | id, imdb_id | genres[].id, production_, belongs_ |
| Pessoa | GET / | id, imdb_id | Filmes por / |
| Produtora | GET / | id | Filmes por with_companies no discover. parent_company é sempre null e description fica vazio |
| Coleção | GET / | id | parts[], uma lista de filmes |
| Palavra-chave | GET / | id | Filmes por / ou with_keywords |
| Gênero | GET / | id | genres[].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
- Compare as três taxas de requisição em preços. Uma taxa maior encurta a primeira carga, e nada mais muda.
- Crie uma chave no painel da sua conta e guarde-a em uma variável de ambiente.
- Chame
/3/discover/moviepara 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.
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.
{
"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
| Tarefa | Chamada | Limites |
|---|---|---|
| Primeira carga | GET /3/discover/movie com sort_by e uma janela de datas | page de 1 a 500, até 20 resultados por página, ou seja, 10,000 por consulta. Fatie por primary_ e .lte para ir além. |
| Lançamentos novos | GET /3/discover/movie com primary_ igual à data da sua última execução, e GET /3/movie/upcoming | Em cache por 30 minutos. |
| Atualizar linhas | GET / para linhas com mais de 6 horas | Não há feed de alterações. Atualize os títulos que os usuários abrem e o restante com menos frequência. |
| Relacionar seus ids | GET / | Um id por chamada, com a fonte escolhida em external_source. |
| Preenchimento sob demanda | GET / quando um usuário abre um título | Guarde 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
| Camada | Guarda | TTL |
|---|---|---|
| Cache de borda | Detalhes e créditos | 6 horas |
| Cache de borda | Listas de discover, em alta e populares | 30 minutos |
| Cache de borda | Busca | 10 minutos |
| Cache de borda | Configuração | 24 horas |
| Cache de borda | Imagens | 30 dias |
| Suas tabelas | Linhas que você mantém | Até 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
| Recurso | Fazer scraping das páginas | Arquivos de dataset estáticos | amodb |
|---|---|---|---|
| Atualidade | Tão atual quanto seu último rastreamento | Tão atual quanto o último arquivo publicado | Detalhes em cache por 6 horas, nova busca por idade |
| Atualizações incrementais | Rastrear de novo e comparar | Baixar um arquivo novo | Discover com janela de datas de lançamento, sem feed de alterações |
| Ids entre entidades | URLs das páginas | Definidos pelo publicador | Ids numéricos, imdb_id e /3/find |
| O que você mantém | Crawlers e armazenamento | Armazenamento e um job de importação | Armazenamento e um script de sincronização |
| Limite de requisições | Definido pelos sites que você rastreia | Não se aplica | 20, 40 ou 120 requisições por minuto, contadas por chave |
| Idiomas por registro | O que cada página oferecer | Depende dos arquivos | 14, 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.