# Падручнік па Python і API фільмаў

> Створыце невялікую кансольную ўтыліту на Python: яна прымае назву фільма, шукае яго, а потым адным дадатковым выклікам атрымлівае дэталі, акцёраў і рэйтынгі. Патрэбныя Python 3.10 або новей, бібліятэка requests і ключ amodb. Гэта зойме каля 15 хвілін, у выніку атрымаецца поўны скрыпт.

Вы створыце `movie.py`, кансольную ўтыліту. Вы перадаяце ёй назву, яна шукае, бярэ першае супадзенне і друкуе год, працягласць, жанры, галоўных акцёраў, рэйтынг IMDb і URL постэра. Патрэбныя Python 3.10 або новей, бібліятэка `requests` і ключ amodb. Закладзіце 15 хвілін.

## Крок 1: Атрымайце ключ API

amodb працуе з аплатай наперад і без бясплатнага тарыфа, таму спачатку абярыце план на старонцы [цэны](https://amodb.dev/be/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/be/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/be/movies/) і [рэйтынгі](https://amodb.dev/be/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/be/errors/), а правілы акна на старонцы [ліміты запытаў](https://amodb.dev/be/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()
```

## Наступныя крокі

- Раздрукуйце постэр іншага памеру. Хост выяў прымае любы з памераў са старонкі [выявы](https://amodb.dev/be/images-api/) і не патрабуе ключа.
- Дадайце фільтр `year` у выклік пошуку або перайдзіце да [пошуку па фільтрах](https://amodb.dev/be/discover/) для спісаў з фільтрамі.
- Паказвайце індыкатар настрою побач з рэйтынгам, дадаўшы `moods` у `append_to_response`. Кожны элемент у `moods.results` мае `name` і `avg` па шкале ад 0 да 10.
- Прачытайце [хуткі старт](https://amodb.dev/be/quickstart/) пра той жа працэс у curl, потым паспрабуйце [падручнік па JavaScript](https://amodb.dev/be/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/be/search/): API пошуку з шляхамі і JSON як у TMDB v3. Фільмы, серыялы, персоны, калекцыі, кампаніі і ключавыя словы па тэксце, з фільтрамі па годзе і старонкамі.
- [API дэталяў фільма](https://amodb.dev/be/movies/): API дэталяў фільма з тымі ж шляхамі і JSON, што ў TMDB v3. Адзін фільм па id, а акцёры, рэйтынгі і настрой праз append_to_response, на 14 мовах.
- [Рэйтынгі фільмаў і серыялаў](https://amodb.dev/be/ratings/): API рэйтынгаў фільмаў вяртае ацэнкі IMDb, Kinopoisk, Metacritic, Rotten Tomatoes і TMDB для назвы адным выклікам, з null для крыніцы, якая невядомая.
- [Хуткі старт](https://amodb.dev/be/quickstart/): Першы выклік да amodb: атрымайце ключ, запытайце дэталі фільма ў curl, JavaScript, Python, PHP або Go і загрузіце постэр з img.amodb.dev. Менш за 2 хвіліны.
- [Ліміты запытаў](https://amodb.dev/be/rate-limits/): amodb абмяжоўвае ключ па запытах у хвіліну ў слізгальным акне 60 секунд: 20, 40 або 120 па плане. Чытайце загалоўкі лімітаў і апрацоўвайце 429 праз Retry-After.
- [Памылкі](https://amodb.dev/be/errors/): Усе памылкі amodb з HTTP-статусам, TMDB status_code і паведамленнем, а таксама форма памылкі OMDb. Што азначае кожная і што рабіць вашаму кліенту далей.

Абноўлена: 2026-10-18. Source: https://amodb.dev/be/python-movie-api/
