Crea tu propia base de datos de películas desde una API
amodb es una API independiente que habla el protocolo TMDB v3. Películas, personas, productoras, colecciones, palabras clave y géneros tienen ids numéricos y se referencian entre sí, así que puedes llenar tablas SQL con ellos y mantenerlas al día. Carga las tablas con la paginación de discover, actualízalas por antigüedad y cruza tus propios identificadores con find. amodb no tiene un feed de cambios, por lo que actualizar es simplemente volver a pedir las filas que conservas.
Qué obtienes:
- Seis tipos de entidad enlazados con ids numéricos estables.
- Paginación de discover para la primera carga: hasta 20 resultados por página, 500 páginas por consulta.
- Ventanas de fechas de estreno en discover, para dividir la primera carga y para listar los estrenos nuevos.
- Búsquedas por ID externo con
imdb_id,tvdb_id,wikidata_idykinopoisk_id.
Qué datos obtienes
Este es el modelo de datos. Cada fila es una tabla en una copia relacional.
| Entidad | Se lee con | Clave | Enlaza con |
|---|---|---|---|
| Película | GET / | id, imdb_id | genres[].id, production_, belongs_ |
| Persona | GET / | id, imdb_id | Películas mediante / |
| Productora | GET / | id | Películas mediante with_companies en discover. parent_company siempre es null y description está vacío |
| Colección | GET / | id | parts[], una lista de películas |
| Palabra clave | GET / | id | Películas mediante / o with_keywords |
| Género | GET / | id | genres[].id en las películas |
Las relaciones de muchos a muchos necesitan tablas de unión: movie_genre, movie_company, movie_keyword y movie_credit. Usa credit_id de créditos como clave de la última. Las listas de campos están en películas, personas y colecciones.
Empieza en 3 pasos
- Compara las tres velocidades de solicitudes en precios. Una velocidad mayor acorta la primera carga y no cambia nada más.
- Crea una clave en el panel de tu cuenta y guárdala en una variable de entorno.
- Llama a
/3/discover/moviecon una ventana de un día de fechas de estreno y luego obtén cada id de la lista.
Ejemplo de solicitud
El script en PHP lista las películas estrenadas ayer y hoy con discover y hace un upsert de cada registro en una tabla SQLite. Espera 3 segundos entre lecturas de detalle para mantenerse por debajo de 20 solicitudes por minuto, reintenta un 429 después de Retry-After y borra las filas cuyo id ahora devuelve 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));Discover devuelve filas de lista, no registros completos. Estos son 2 de los 20 resultados de la página 1. Lee /3/movie/{id} para obtener el resto de los 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
}Llena y actualiza las tablas
| Tarea | Llamada | Límites |
|---|---|---|
| Primera carga | GET /3/discover/movie con sort_by y una ventana de fechas | page de 1 a 500, hasta 20 resultados por página, es decir 10,000 por consulta. Divide por primary_ y .lte para ir más allá. |
| Estrenos nuevos | GET /3/discover/movie con primary_ fijado en tu última ejecución, y GET /3/movie/upcoming | En caché durante 30 minutos. |
| Actualizar filas | GET / para filas con más de 6 horas | No hay feed de cambios. Actualiza los títulos que abren los usuarios y el resto con menos frecuencia. |
| Unir tus propios ids | GET / | Un id por llamada, con la fuente elegida mediante external_source. |
| Carga bajo demanda | GET / cuando un usuario abre un título | Guarda la respuesta con una marca de tiempo. |
La ventana de paginación es la decisión de diseño principal. Cuando total_pages en una respuesta supera 500, la ventana contiene más de 10,000 filas, así que pártela por la mitad hasta que quepa. Un año reciente necesita meses, y un solo día de estrenos son unos cientos de títulos. Guarda el tramo que terminaste para que una ejecución interrumpida pueda reanudarse. Las reglas de coincidencia para la unión están en buscar por ID externo.
Dimensiona la sincronización
Cada fila que actualizas cuesta una lectura de detalle. Un plan de 20 solicitudes por minuto da 1,200 llamadas por hora, uno de 40 da 2,400 y uno de 120 da 7,200. Una página de discover lista 20 ids con una sola llamada. Ejecuta la actualización en horas de poca carga si tu aplicación comparte la clave, porque el límite es por clave, no por script. Consulta límites de solicitudes para ver los encabezados que indican lo que queda en la ventana.
Caché en dos capas
| Capa | Contiene | TTL |
|---|---|---|
| Caché perimetral | Detalles y créditos | 6 horas |
| Caché perimetral | Listas de discover, tendencias y populares | 30 minutos |
| Caché perimetral | Búsqueda | 10 minutos |
| Caché perimetral | Configuración | 24 horas |
| Caché perimetral | Imágenes | 30 días |
| Tus tablas | Las filas que conservas | Hasta que tu regla de antigüedad indique volver a pedirlas, nunca antes de 6 horas |
La caché perimetral se comparte entre claves, pero el límite de solicitudes se aplica antes que ella, así que un acierto de caché sigue gastando una solicitud de tu minuto. Releer una fila con más frecuencia que cada 6 horas devuelve la misma copia y desperdicia cuota. Deja que una regla de antigüedad decida cuándo volver a pedirla.
Cómo se compara
| Capacidad | Hacer scraping de las páginas tú mismo | Archivos de datos estáticos | amodb |
|---|---|---|---|
| Actualidad | Tan reciente como tu último rastreo | Tan reciente como el último archivo publicado | Detalles en caché 6 horas, vuelve a pedirlos por antigüedad |
| Actualizaciones incrementales | Rastrear de nuevo y comparar | Descargar un archivo nuevo | Discover con una ventana de fechas de estreno, sin feed de cambios |
| Ids entre entidades | URL de las páginas | Los define el editor | Ids numéricos, imdb_id y /3/find |
| Qué ejecutas | Rastreadores y almacenamiento | Almacenamiento y un trabajo de importación | Almacenamiento y un script de sincronización |
| Límite de solicitudes | Lo fijan los sitios que rastreas | No aplica | 20, 40 o 120 solicitudes por minuto, contadas por clave |
| Idiomas por registro | Los que ofrezca cada página | Depende de los archivos | 14, elegidos con language |
Precios
La primera carga es la parte costosa, y la limita tu número de solicitudes por minuto. Elige 20, 40 o 120 por minuto en precios. Paga por 1, 3, 6, 12 o 24 meses con un descuento del 0, 10, 20, 30 o 40 por ciento. Nada se renueva automáticamente, no hay nivel gratuito y todos los planes leen los mismos datos.