Python 电影 API 教程
构建一个小型 Python 命令行工具:接收片名并搜索,再用一次额外调用获取详情、演员和评分。你需要 Python 3.10 或更新版本、requests 库和 amodb 密钥。约 15 分钟,最后得到一个完整脚本。
你将构建 movie.py,一个命令行工具。你传入片名,它会搜索、选出第一个匹配项,并打印年份、时长、类型、主要演员、IMDb 评分和海报 URL。它需要 Python 3.10 或更新版本、requests 库和一个 amodb 密钥。预计用时 15 分钟。
步骤 1:获取 API 密钥
amodb 是预付的,没有免费额度,所以请先在定价页选择套餐。每分钟 20、40 和 120 个请求的套餐返回的数据相同。然后在账户后台创建密钥。密钥是 32 个小写十六进制字符。
把它导出为环境变量,这样它就不会出现在你的源代码中:
在 Windows PowerShell 中,同样的一行是 $env:AMODB_。
export AMODB_KEY="YOUR_API_KEY"python -m venv .venv
source .venv/bin/activate
pip install requests步骤 3:发送第一个请求
搜索接口是带有 query 参数的 GET /3/search/movie。这段代码打印 HTTP 状态和最佳匹配的 ID。务必传入超时,因为 requests 默认会一直等待。
响应采用 TMDB 的结构:按相关度排序的 results 数组,外加 page 和 total_results。所有筛选参数见搜索。
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"])步骤 4:获取详情、演员和评分
搜索结果很简短。用这个 ID 调用 GET / 并加上 append_,即可在同一个请求中获得演员以及 IMDb、Kinopoisk、Metacritic、Rotten Tomatoes 和 TMDB 的评分。每个评分是包含 value 和 votes 的对象,来源未知时为 null,所以打印之前请先判断。详情见电影和评分页。在最终脚本中,session 是已经带有 Authorization 请求头的 requests.Session。
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 被捕获并报告。 |
重试循环很短:
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))步骤 6:把响应缓存到磁盘
边缘缓存详情 6 小时、搜索 10 分钟。你自己的缓存能省去一次往返,也不消耗限额中的请求。最终脚本把每个响应存入以路径和参数的哈希命名的文件,并在文件比 TTL 更新时复用它。
步骤 7:完整程序
把它保存为 movie.py,然后运行 python movie.py "fight club"。
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()下一步
- 打印其他尺寸的海报。图片主机支持图片页列出的任何尺寸,且无需密钥。
- 在搜索调用中加入
year筛选,或改用发现获取筛选后的列表。 - 在
append_to_response中加入moods,即可在评分旁显示情绪指数。moods.results中的每一项都有name和范围为 0 到 10 的avg。 - 阅读快速开始了解 curl 版的同一流程,再试试用于批量处理的 JavaScript 教程。