# Руководство по Python movie API

> Создайте небольшой консольный инструмент на Python: он принимает название фильма, ищет его, затем одним дополнительным вызовом получает детали, актеров и рейтинги. Нужны Python 3.10 или новее, библиотека requests и ключ amodb. Это займет около 15 минут и закончится готовым скриптом.

Вы напишете `movie.py`, консольный инструмент. Вы передаете ему название, он ищет, берет первое совпадение и выводит год, длительность, жанры, главных актеров, рейтинг IMDb и URL постера. Нужны Python 3.10 или новее, библиотека `requests` и ключ amodb. Заложите 15 минут.

## Шаг 1: получите ключ API

amodb работает по предоплате, бесплатного тарифа нет, поэтому сначала выберите тариф на странице [цены](https://amodb.dev/ru/pricing/). Тарифы на 20, 40 и 120 запросов в минуту отдают одни и те же данные. Затем создайте ключ в личном кабинете. Ключ состоит из 32 шестнадцатеричных символов в нижнем регистре.

Экспортируйте его как переменную окружения, чтобы он не попал в исходный код:

```bash
export AMODB_KEY="YOUR_API_KEY"
```

В Windows PowerShell та же строка выглядит так: `$env:AMODB_KEY = "YOUR_API_KEY"`.

## Шаг 2: настройте проект

Создайте виртуальное окружение и установите `requests`:

```bash
python -m venv .venv
source .venv/bin/activate
pip install requests
```

В Windows вместо второй строки выполните `.venv\Scripts\activate`.

## Шаг 3: отправьте первый запрос

Эндпоинт поиска: `GET /3/search/movie` с параметром `query`. Этот фрагмент выводит HTTP-статус и id лучшего совпадения. Всегда передавайте таймаут, потому что `requests` по умолчанию ждет бесконечно.

```python
import os
import requests

res = requests.get(
    "https://api.amodb.dev/3/search/movie",
    params={"query": "fight club", "language": "en-US"},
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
print(res.status_code, res.json()["results"][0]["id"])
```

Ответ имеет формат TMDB: массив `results`, отсортированный по релевантности, а также `page` и `total_results`. Все фильтры описаны на странице [поиск](https://amodb.dev/ru/search/).

## Шаг 4: получите детали, актеров и рейтинги

Результаты поиска краткие. Используйте id в `GET /3/movie/{movie_id}` и добавьте `append_to_response=credits,ratings`, чтобы получить актерский состав и оценки IMDb, Kinopoisk, Metacritic, Rotten Tomatoes и TMDB в том же запросе. Каждая оценка представляет собой объект с `value` и `votes` или `null`, если источник неизвестен, поэтому проверяйте значение перед выводом. Подробности на страницах [фильмы](https://amodb.dev/ru/movies/) и [рейтинги](https://amodb.dev/ru/ratings/). В итоговом скрипте `session` является объектом `requests.Session`, который уже содержит заголовок Authorization.

```python
params = {"language": "en-US", "append_to_response": "credits,ratings"}
movie = session.get(f"{BASE}/movie/{movie_id}", params=params, timeout=10).json()
imdb = (movie.get("ratings") or {}).get("imdb")
```

## Шаг 5: обработайте ошибки и лимиты запросов

Проверяйте статус, прежде чем разбирать тело. Ошибки используют тело TMDB с `status_code` и `status_message`.

| Статус    | Значение                                   | Что делает скрипт                                                        |
| --------- | ------------------------------------------ | ------------------------------------------------------------------------ |
| 401       | Недействительный или приостановленный ключ | Останавливается и просит проверить `AMODB_KEY`. Повтор не поможет.       |
| 404       | Такого id нет                              | Выводит `status_message`.                                                |
| 429       | Превышен лимит запросов в минуту           | Ждет `Retry-After` секунд (максимум 30), затем повторяет до двух раз.    |
| 504       | Таймаут бэкенда                            | Выводится как ошибка. Запустите команду снова.                           |
| Сбой сети | Нет соединения или таймаут                 | Перехватывается как `requests.RequestException` и выводится в сообщении. |

Цикл повторов короткий:

```python
for attempt in range(3):
    res = session.get(url, params=params, timeout=10)
    if res.status_code != 429 or attempt == 2:
        break
    time.sleep(min(int(res.headers.get("Retry-After", "1")), 30))
```

Остальные коды перечислены на странице [ошибки](https://amodb.dev/ru/errors/), а правила окна на странице [лимиты запросов](https://amodb.dev/ru/rate-limits/).

## Шаг 6: кэшируйте ответы на диске

Edge-кэш хранит детали 6 часов, а поиск 10 минут. Ваш собственный кэш экономит обращение к серверу и не тратит запросы из вашего лимита. Итоговый скрипт сохраняет каждый ответ в файл, названный по хешу пути и параметров, и использует файл повторно, пока он моложе TTL.

## Шаг 7: полная программа

Сохраните это как `movie.py` и запустите `python movie.py "fight club"`.

```python
import hashlib
import json
import os
import sys
import time
from pathlib import Path

import requests

BASE = "https://api.amodb.dev/3"
POSTER = "https://img.amodb.dev/t/p/w342"
CACHE_DIR = Path.home() / ".cache" / "amodb-movie-cli"
SEARCH_TTL = 10 * 60
DETAILS_TTL = 6 * 60 * 60


class ApiError(Exception):
    pass


def error_message(res):
    try:
        return res.json()["status_message"]
    except (ValueError, KeyError):
        return res.reason


def get(session, path, params, ttl):
    CACHE_DIR.mkdir(parents=True, exist_ok=True)
    stamp = f"{path}?{json.dumps(params, sort_keys=True)}"
    cache_file = CACHE_DIR / (hashlib.sha256(stamp.encode()).hexdigest() + ".json")
    if cache_file.exists() and time.time() - cache_file.stat().st_mtime < ttl:
        return json.loads(cache_file.read_text(encoding="utf-8"))

    for attempt in range(3):
        try:
            res = session.get(BASE + path, params=params, timeout=10)
        except requests.RequestException as exc:
            raise ApiError(f"network error: {exc}") from exc
        if res.status_code != 429 or attempt == 2:
            break
        time.sleep(min(int(res.headers.get("Retry-After", "1")), 30))

    if res.status_code == 401:
        raise ApiError("401: the key is invalid or suspended. Check AMODB_KEY.")
    if res.status_code != 200:
        raise ApiError(f"{res.status_code}: {error_message(res)}")
    cache_file.write_text(res.text, encoding="utf-8")
    return res.json()


def search_movie(session, title):
    data = get(session, "/search/movie", {"query": title, "language": "en-US"}, SEARCH_TTL)
    if not data["results"]:
        raise ApiError(f"no movie found for {title!r}")
    return data["results"][0]["id"]


def movie_details(session, movie_id):
    params = {"language": "en-US", "append_to_response": "credits,ratings"}
    return get(session, f"/movie/{movie_id}", params, DETAILS_TTL)


def print_card(movie):
    year = (movie.get("release_date") or "")[:4] or "unknown year"
    genres = ", ".join(g["name"] for g in movie["genres"])
    cast = ", ".join(p["name"] for p in movie["credits"]["cast"][:3])
    imdb = (movie.get("ratings") or {}).get("imdb")
    print(f"{movie['title']} ({year}), {movie.get('runtime') or '?'} min")
    print(f"Genres: {genres or 'none listed'}")
    print(f"Cast: {cast or 'none listed'}")
    print(f"IMDb: {imdb['value']} ({imdb['votes']} votes)" if imdb else "IMDb: not known")
    if movie.get("poster_path"):
        print(f"Poster: {POSTER}{movie['poster_path']}")
    print(movie.get("overview") or "No overview in this language.")


def main():
    if len(sys.argv) < 2:
        sys.exit('Usage: python movie.py "fight club"')
    key = os.environ.get("AMODB_KEY")
    if not key:
        sys.exit("Set AMODB_KEY first.")
    session = requests.Session()
    session.headers["Authorization"] = f"Bearer {key}"
    try:
        movie_id = search_movie(session, " ".join(sys.argv[1:]))
        print_card(movie_details(session, movie_id))
    except ApiError as exc:
        sys.exit(f"error: {exc}")


if __name__ == "__main__":
    main()
```

## Что дальше

- Выведите постер другого размера. Хост изображений принимает любой из размеров, перечисленных на странице [API изображений](https://amodb.dev/ru/images-api/), и не требует ключа.
- Добавьте фильтр `year` в вызов поиска или перейдите на [discover](https://amodb.dev/ru/discover/) для отфильтрованных списков.
- Покажите шкалу настроения рядом с рейтингом, добавив `moods` в `append_to_response`. У каждого элемента `moods.results` есть `name` и `avg` по шкале от 0 до 10.
- Прочитайте [быстрый старт](https://amodb.dev/ru/quickstart/), где тот же сценарий показан в curl, а затем попробуйте [руководство по JavaScript](https://amodb.dev/ru/javascript-movie-api/) с пакетной версией.

## Вопросы и ответы

### Какая версия Python и какие библиотеки нужны?

Python 3.10 или новее и библиотека requests. Все остальное берется из стандартной библиотеки. Ключ передается в заголовке Authorization Bearer, поэтому он не попадает в URL, историю оболочки и серверные логи.

### Как не выйти за лимит запросов?

Кэшируйте ответы и учитывайте Retry-After. Каждый ключ получает 20, 40 или 120 запросов в минуту в скользящем окне в 60 секунд. Скрипт повторяет ответ 429 дважды, ждет число секунд из заголовка и ограничивает ожидание 30 секундами.

### Можно ли использовать код, написанный для клиента TMDB v3?

Да, для запросов на чтение. Пути, параметры и JSON совпадают с TMDB v3, поэтому достаточно заменить базовый URL на https://api.amodb.dev/3 и отправлять ключ amodb. Методы записи, например оценка фильма, возвращают 405 с status_code 37.

### Куда передавать ключ: в api_key или в заголовок?

Подходят оба способа. Заголовок не пускает ключ в URL, логи прокси и строки запросов, поэтому в этом руководстве используется Authorization: Bearer. Параметр запроса api_key остается для инструментов, которые не умеют задавать заголовки.

### Как долго кэшировать поиск и детали?

10 минут для поиска и 6 часов для деталей, то есть те же сроки, что у edge-кэша. Более короткий локальный кэш лишь добавляет запросы, потому что копия на edge общая для всех ключей и уже хранится в эти сроки.

## Связанные страницы

- [API поиска](https://amodb.dev/ru/search/): API поиска с путями и JSON как в TMDB v3. Находите фильмы, сериалы, людей, коллекции, компании и ключевые слова по тексту, с фильтром по году и пагинацией.
- [API деталей фильма](https://amodb.dev/ru/movies/): API деталей фильма с теми же путями и JSON, что в TMDB v3. Получите фильм по ID, добавьте актеров, рейтинги и настроения через append_to_response, на 14 языках.
- [Рейтинги фильмов и сериалов](https://amodb.dev/ru/ratings/): API рейтингов фильмов: оценки IMDb, Kinopoisk, Metacritic, Rotten Tomatoes и TMDB для тайтла одним запросом, с null для источника, о котором нет данных.
- [Быстрый старт](https://amodb.dev/ru/quickstart/): Первый вызов amodb: получите ключ, запросите детали фильма через curl, JavaScript, Python, PHP или Go и загрузите постер с img.amodb.dev. Меньше 2 минут.
- [Лимиты запросов](https://amodb.dev/ru/rate-limits/): amodb ограничивает ключ запросами в минуту в скользящем окне 60 секунд: 20, 40 или 120 по тарифу. Читайте заголовки лимита и обрабатывайте 429 с Retry-After.
- [Ошибки](https://amodb.dev/ru/errors/): Все ошибки amodb: HTTP-статус, status_code и сообщение TMDB, а также формат ошибки OMDb. Что означает каждая и что должен делать ваш клиент дальше.

Обновлено: 2026-10-18. Source: https://amodb.dev/ru/python-movie-api/
