用 API 构建你自己的电影数据库

amodb 是独立的 API,使用 TMDB v3 协议。电影、人物、公司、系列合集、关键词和类型都有数字 ID 并相互指向,因此你可以用它填充 SQL 表并保持更新。用 discover 分页灌库,按时效刷新,再用 find 匹配你自己的标识符。amodb 没有变更动态,所以刷新就是对保留的行重新获取。

你会得到:

  • 六种相互关联、ID 稳定的实体类型。
  • 用于首次导入的 discover 分页:每页最多 20 条结果,每次查询 500 页。
  • discover 上的上映日期窗口,用来切分首次导入并列出新上映的影片。
  • 针对 imdb_id、tvdb_id、wikidata_id 和 kinopoisk_id 的外部 ID 查找。

可获取的数据

这是数据模型。每一行对应关系型副本中的一张表。

实体读取方式键关联到
电影GET /3/movie/{movie_id}id、imdb_idgenres[].id、production_companies[].id、belongs_to_collection.id
人物GET /3/person/{person_id}id、imdb_id通过 /3/movie/{movie_id}/credits 关联电影
公司GET /3/company/{company_id}id通过 discover 的 with_companies 关联电影。parent_company 始终为 null,description 为空
系列合集GET /3/collection/{collection_id}idparts[],即电影列表
关键词GET /3/keyword/{keyword_id}id通过 /3/keyword/{keyword_id}/movies 或 with_keywords 关联电影
类型GET /3/genre/movie/listid电影上的 genres[].id

多对多关系需要关联表:movie_genre、movie_company、movie_keyword 和 movie_credit。最后一张表请用演职员中的 credit_id 作为键。字段列表见电影、人物和系列合集。

三步开始

  1. 在定价页比较三种请求速率。速率越高,首次导入越短,其他都不变。
  2. 在账户后台创建密钥,并保存在环境变量中。
  3. 用一天的上映日期窗口调用 /3/discover/movie,再获取列表中的每个 ID。

请求示例

这个 PHP 脚本通过 discover 列出昨天和今天上映的电影,并把每条记录写入(upsert)SQLite 表。它在两次详情读取之间休眠 3 秒,以保持在每分钟 20 个请求以内;遇到 429 时按 Retry-After 重试;并删除 ID 现在返回 404 的行。

bashphp
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 返回的是列表行,而不是完整记录。这是第 1 页 20 条结果中的 2 条。其余字段请读取 /3/movie/{id}。

JSON
{
  "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
}

填充并刷新数据表

任务调用限制
首次导入带 sort_by 和日期窗口的 GET /3/discover/moviepage 取 1 到 500,每页最多 20 条结果,所以每次查询 10,000 条。要获取更多,请按 primary_release_date.gte 和 .lte 切分。
新上映primary_release_date.gte 设为上次运行时间的 GET /3/discover/movie,以及 GET /3/movie/upcoming缓存 30 分钟。
刷新行对超过 6 小时的行调用 GET /3/movie/{movie_id}没有变更动态。请优先刷新用户打开的作品,其余的刷新频率低一些。
关联你自己的 IDGET /3/find/{external_id}每次调用一个 ID,来源由 external_source 指定。
按需填充用户打开作品时调用 GET /3/movie/{movie_id}把响应连同时间戳一起保存。

分页窗口是主要的设计选择。当响应中的 total_pages 超过 500 时,说明窗口内的行数超过 10,000,请不断对半切分直到满足。近期的一年需要按月切分,一天的上映就有几百部作品。请保存已完成的切片,这样中断的运行可以继续。关联的匹配规则见按外部 ID 查找。

估算同步规模

每刷新一行就消耗一次详情读取。每分钟 20 个请求的套餐每小时可调用 1,200 次,40 个的是 2,400 次,120 个的是 7,200 次。一个 discover 页面用一次调用列出 20 个 ID。如果你的应用共用这个密钥,请在非高峰时段运行刷新,因为限制是按密钥而不是按脚本计算的。报告窗口内剩余额度的响应头见速率限制。

两层缓存

层内容TTL
边缘缓存详情和演职员6 小时
边缘缓存discover、热门趋势和热门列表30 分钟
边缘缓存搜索10 分钟
边缘缓存配置24 小时
边缘缓存图片30 天
你的数据表你保留的行直到你的时效规则要求重新获取,且不短于 6 小时

边缘缓存在各密钥之间共享,但速率限制先于它执行,所以缓存命中仍会占用你这一分钟中的一个请求。每 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% 的折扣。不会自动续费,没有免费额度,所有套餐读取的数据相同。