Собираем собственную базу фильмов через API
amodb является независимым API, который использует протокол TMDB v3. У фильмов, людей, компаний, коллекций, ключевых слов и жанров есть числовые id, и они ссылаются друг на друга, поэтому из него можно заполнять таблицы SQL и поддерживать их актуальными. Заполните таблицы постраничным discover, обновляйте их по возрасту и сопоставляйте свои идентификаторы через find. В amodb нет ленты изменений, поэтому обновление сводится к повторному запросу хранимых строк.
Что вы получаете:
- Шесть связанных типов сущностей со стабильными числовыми id.
- Постраничный discover для первой загрузки: до 20 результатов на странице, 500 страниц на запрос.
- Окна дат выхода в discover, чтобы делить первую загрузку и получать новые релизы.
- Поиск по внешним id для
imdb_id,tvdb_id,wikidata_idиkinopoisk_id.
Какие данные вы получаете
Это модель данных. Каждая строка соответствует одной таблице в реляционной копии.
| Сущность | Чем читать | Ключ | Связи |
|---|---|---|---|
| Фильм | GET / | id, imdb_id | genres[].id, production_, belongs_ |
| Человек | GET / | id, imdb_id | Фильмы через / |
| Компания | GET / | id | Фильмы через with_companies в discover. parent_company всегда null, а description пуст |
| Коллекция | GET / | id | parts[], список фильмов |
| Ключевое слово | GET / | id | Фильмы через / или with_keywords |
| Жанр | GET / | id | genres[].id у фильмов |
Связи многие ко многим требуют таблиц соединения: movie_genre, movie_company, movie_keyword и movie_credit. Используйте credit_id со страницы актеры и съемочная группа как ключ последней. Списки полей приведены на страницах фильмы, люди и коллекции.
Начало работы в 3 шага
- Сравните три значения запросов в минуту на странице цены. Больший лимит ускоряет первую загрузку, больше ничего не меняется.
- Создайте ключ в личном кабинете и храните его в переменной окружения.
- Вызовите
/3/discover/movieдля окна дат выхода в один день, затем запросите каждый id из списка.
Пример запроса
Скрипт на PHP получает через discover фильмы, вышедшие вчера и сегодня, и записывает каждую запись в таблицу SQLite. Между чтениями данных он ждет 3 секунды, чтобы не превысить 20 запросов в минуту, повторяет запрос после 429 через Retry-After и удаляет строки, id которых теперь возвращает 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 возвращает строки списка, а не полные записи. Здесь 2 из 20 результатов на странице 1. Остальные поля читайте через /3/movie/{id}.
{
"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
}Заполнение и обновление таблиц
| Задача | Вызов | Ограничения |
|---|---|---|
| Первая загрузка | GET /3/discover/movie с sort_by и окном дат | page от 1 до 500, до 20 результатов на странице, то есть 10 000 на запрос. Делите по primary_ и .lte, чтобы пойти дальше. |
| Новые релизы | GET /3/discover/movie с primary_, равным дате вашего последнего запуска, и GET /3/movie/upcoming | Кэш 30 минут. |
| Обновление строк | GET / для строк старше 6 часов | Ленты изменений нет. Обновляйте фильмы, которые открывают пользователи, а остальные реже. |
| Сопоставление своих id | GET / | Один id на вызов, источник выбирается через external_source. |
| Ленивое заполнение | GET /, когда пользователь открывает фильм | Сохраняйте ответ с меткой времени. |
Окно постраничной выборки: главное проектное решение. Если total_pages в ответе больше 500, в окне больше 10 000 строк, поэтому делите его пополам, пока оно не уместится. Недавнему году нужны месяцы, а один день релизов это несколько сотен фильмов. Сохраняйте завершенный срез, чтобы упавший запуск мог продолжиться. Правила сопоставления описаны на странице поиск по внешнему ID.
Оценка объема синхронизации
Каждая обновляемая строка стоит одно чтение данных. Тариф на 20 запросов в минуту дает 1 200 вызовов в час, 40 дает 2 400, а 120 дает 7 200. Страница discover перечисляет 20 id за один вызов. Запускайте обновление в непиковые часы, если приложение использует тот же ключ, потому что лимит действует на ключ, а не на скрипт. Заголовки, показывающие остаток в окне, описаны на странице лимиты запросов.
Кэширование в двух слоях
| Слой | Что хранит | TTL |
|---|---|---|
| Edge-кэш | Данные и состав участников | 6 часов |
| Edge-кэш | Списки discover, трендов и популярного | 30 минут |
| Edge-кэш | Поиск | 10 минут |
| Edge-кэш | Конфигурация | 24 часа |
| Edge-кэш | Изображения | 30 дней |
| Ваши таблицы | Строки, которые вы храните | Пока правило возраста не потребует повторного запроса, но не раньше чем через 6 часов |
Edge-кэш общий для всех ключей, но лимит запросов проверяется раньше него, поэтому попадание в кэш все равно тратит один запрос из вашей минуты. Повторное чтение строки чаще, чем раз в 6 часов, вернет ту же копию и впустую расходует квоту. Пусть правило возраста решает, когда запрашивать заново.
Сравнение
| Возможность | Парсинг страниц самостоятельно | Статические файлы датасетов | amodb |
|---|---|---|---|
| Свежесть | Настолько свежо, насколько ваш последний обход | Настолько свежо, насколько последний опубликованный файл | Данные хранятся в кэше 6 часов, повторный запрос по возрасту |
| Инкрементальные обновления | Повторный обход и сравнение | Скачивание нового файла | Discover с окном дат выхода, без ленты изменений |
| Id между сущностями | URL страниц | Определяются издателем | Числовые id, imdb_id и /3/find |
| Что вы запускаете | Обходчики и хранилище | Хранилище и задача импорта | Хранилище и скрипт синхронизации |
| Лимит запросов | Задается сайтами, которые вы обходите | Не применимо | 20, 40 или 120 запросов в минуту, считается на ключ |
| Языки записи | Что предлагает каждая страница | Зависит от файлов | 14, выбираются через language |
Цены
Первая загрузка самая затратная часть, и она ограничена вашим числом запросов в минуту. Выберите 20, 40 или 120 в минуту на странице цены. Оплатите 1, 3, 6, 12 или 24 месяца со скидкой 0, 10, 20, 30 или 40 процентов. Автоматического продления нет, бесплатного тарифа нет, а все тарифы читают одни и те же данные.