# מדריך API לסרטים ב-Python

> בנו כלי שורת פקודה קטן ב-Python שמקבל כותרת סרט, מחפש אותה, ואז שולף פרטים, שחקנים ודירוגים בקריאה נוספת אחת. צריך Python 3.10 ומעלה, את הספרייה requests ומפתח amodb. הכלי גם מנסה שוב ב-429 ושומר תשובות במטמון קבצים. זה לוקח כ-15 דקות ומסתיים בסקריפט שלם.

בונים את `movie.py`, כלי שורת פקודה. מעבירים לו כותרת, והוא מחפש, בוחר את ההתאמה הראשונה ומדפיס שנה, משך, ז'אנרים, שחקנים עיקריים, את דירוג IMDb וכתובת פוסטר. הוא דורש Python 3.10 ומעלה, את הספרייה `requests` ומפתח amodb. תכננו 15 דקות.

## שלב 1: קבלת מפתח API

amodb הוא בתשלום מראש וללא מסלול חינמי, ולכן בחרו קודם מסלול בעמוד [תמחור](https://amodb.dev/he/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 ואת המזהה של ההתאמה הטובה ביותר. תמיד העבירו פסק זמן, כי `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/he/search/).

## שלב 4: שליפת פרטים, שחקנים ודירוגים

תוצאות חיפוש קצרות. השתמשו במזהה עם `GET /3/movie/{movie_id}` והוסיפו `append_to_response=credits,ratings` כדי לקבל שחקנים ואת הציונים של IMDb, Kinopoisk, Metacritic, Rotten Tomatoes ו-TMDB באותה בקשה. כל ציון הוא אובייקט עם `value` ו-`votes`, או `null` כשהמקור לא ידוע, ולכן בדקו לפני שמדפיסים. פרטים בעמודים [סרטים](https://amodb.dev/he/movies/) ו[דירוגים](https://amodb.dev/he/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     | אין מזהה כזה          | מדפיס את `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/he/errors/), וכללי החלון בעמוד [מגבלות קצב](https://amodb.dev/he/rate-limits/).

## שלב 6: שמירת תגובות בדיסק

הקצה שומר פרטים 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/he/images-api/), ולא דורש מפתח.
- הוסיפו מסנן `year` לקריאת החיפוש, או עברו ל-[גילוי](https://amodb.dev/he/discover/) לרשימות מסוננות.
- הציגו מדד מצב רוח ליד הדירוג על ידי הוספת `moods` ל-`append_to_response`. לכל פריט ב-`moods.results` יש `name` ו-`avg` בסולם 0 עד 10.
- קראו את [המדריך למתחילים](https://amodb.dev/he/quickstart/) לאותו תהליך ב-curl, ואז נסו את [המדריך ב-JavaScript](https://amodb.dev/he/javascript-movie-api/) לגרסת אצווה.

## שאלות ותשובות

### איזו גרסת Python ואילו ספריות צריך?

Python 3.10 ומעלה והספרייה requests. כל השאר מגיע מהספרייה הסטנדרטית. המפתח עובר בכותרת Authorization Bearer, ולכן הוא אף פעם לא מופיע בכתובות, בהיסטוריית ה-shell או ביומני השרת.

### איך נשארים מתחת למגבלת הקצב?

שומרים תגובות במטמון וכובדים את Retry-After. כל מפתח מקבל 20, 40 או 120 בקשות לדקה בחלון נע של 60 שניות. הסקריפט מנסה שוב אחרי 429 פעמיים, ממתין את מספר השניות שבכותרת ומגביל את ההמתנה ל-30.

### אפשר להשתמש שוב בקוד שנכתב ללקוח TMDB v3?

כן, בקריאות קריאה. נתיבים, פרמטרים ו-JSON זהים ל-TMDB v3, ולכן מחליפים את כתובת הבסיס ל-https://api.amodb.dev/3 ושולחים מפתח amodb. שיטות כתיבה כמו דירוג סרט מחזירות 405 עם status_code 37.

### המפתח צריך להיות ב-api_key או בכותרת?

שתי הדרכים עובדות. הכותרת מרחיקה את המפתח מכתובות, מיומני פרוקסי ומשורות בקשה, ולכן המדריך משתמש ב-Authorization: Bearer. פרמטר השאילתה api_key נשאר זמין לכלים שלא יכולים להגדיר כותרות.

### לכמה זמן לשמור חיפוש ופרטים במטמון?

10 דקות לחיפוש ו-6 שעות לפרטים, אותם משכים כמו במטמון הקצה. מטמון מקומי קצר יותר רק מוסיף בקשות, כי עותק הקצה משותף בין מפתחות וכבר מחזיק את המשכים האלה.

## עמודים קשורים

- [API חיפוש](https://amodb.dev/he/search/): API חיפוש עם אותם נתיבים ו-JSON כמו TMDB v3. מצאו סרטים, סדרות, אנשים, אוספים, חברות ומילות מפתח לפי טקסט, עם סינון לפי שנה ועימוד של 20 תוצאות בעמוד.
- [API פרטי סרט](https://amodb.dev/he/movies/): API פרטי סרט עם אותם נתיבים ו-JSON כמו TMDB v3. קבלו סרט לפי מזהה, הוסיפו קרדיטים, דירוגים, מצבי רוח ופסקולים עם append_to_response, ב-14 שפות.
- [דירוגי סרטים וסדרות](https://amodb.dev/he/ratings/): API דירוגי סרטים וסדרות שמחזיר ציוני IMDb, Kinopoisk, Metacritic, Rotten Tomatoes ו-TMDB לכותר בקריאה אחת, עם value ו-votes ו-null לכל מקור שאינו ידוע.
- [מדריך למתחילים](https://amodb.dev/he/quickstart/): בצעו את הקריאה הראשונה ל-amodb: קבלו מפתח, בקשו פרטי סרט ב-curl, JavaScript, Python, PHP או Go, ואז טענו את הפוסטר מ-img.amodb.dev. פחות מ-2 דקות.
- [מגבלות קצב](https://amodb.dev/he/rate-limits/): amodb מגביל כל מפתח לפי בקשות לדקה בחלון נע של 60 שניות: 20, 40 או 120 בהתאם למסלול שלכם. קראו את כותרות מגבלת הקצב וטפלו ב-429 עם Retry-After.
- [שגיאות](https://amodb.dev/he/errors/): כל שגיאה של amodb עם סטטוס HTTP, status_code והודעה של TMDB, וגם מבנה השגיאה של OMDb. מה כל אחת מהן אומרת ומה הלקוח שלכם צריך לעשות כשהיא מופיעה.

עודכן: 2026-10-18. Source: https://amodb.dev/he/python-movie-api/
