# 光影台词 **Repository Path**: wangyidao/movies ## Basic Information - **Project Name**: 光影台词 - **Description**: 光影台词:收集与分享影视经典对白,打造开源台词数据库,助力内容创作与语言研究。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-02 - **Last Updated**: 2026-08-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 光影台词 · 经典电影台词馆 以 **豆瓣 Top 250** 为片单基准的纯前端(静态)经典电影台词网站,用 **Astro 5 + Tailwind v4** 构建。 所有数据在构建时由可重跑的 Python 脚本抓取并烘焙进站点——无后端服务器,**纯静态**。 ## 功能(13 项全覆盖) | 功能 | 实现 | | --- | --- | | 电影海报卡片列表 | 首页 `/` 海报墙(可按年份筛选 + 排序) | | 电影详情 | `/movie/[slug]` | | 创作人员列表 | `/people` 目录 + `/people/[slug]` 详情(由演职员自动派生) | | 台词 | 每部片详情 + 全局浏览 `/quotes` | | 搜索功能 | `/search` 客户端全文搜索(Fuse.js) | | 按上映时间搜索 | 首页年份区间筛选 + `/search` 年份区间 | | 豆瓣得分 | 详情页评分徽章 + 来源标注(豆瓣 / TMDB 兜底) | | 配乐 | 详情页「配乐」段(作曲 + 代表曲目) | | 时间轴 | `/timeline` 按上映年代排列 | | 影评 | 详情页「影评」段(TMDB / 豆瓣短评) | | 相关电影 | 详情页「相关电影」(TMDB /similar) | | 小说 | 详情页「原著小说」(Wikidata P144) | | 剧本 | 详情页「剧本 / 编剧」(Wikidata P58) | ## 目录结构 ``` movie-quotes/ ├── scripts/ # 数据抓取(Python 3.13)+ 编排器(Node) │ ├── orchestrate.mjs # npm run fetch 入口,按序 spawn 各脚本 │ ├── douban_top250.py # 豆瓣 Top250 排名/评分(反爬 + wmdb 兜底) │ ├── tmdb_common.py # TMDB 公共工具(DNS 绕过 / 限流重试 / slug) │ ├── tmdb_discover.py # 按年份批量发现片单(每年 ~50 部;需 key) │ ├── tmdb_enrich.py # TMDB 详情/海报/演职员/相似/影评(需 key;含 CN 网络绕过) │ ├── wikidata.py # SPARQL-free:原著(P144)/配乐(P86)/编剧(P58) │ ├── wikiquote.py # 台词(action=parse) │ ├── reviews_douban.py # 豆瓣短评(可选,best-effort) │ ├── assemble.py # 分层合并 → data/movies.json + public/search-index.json │ └── requirements.txt ├── data/ │ ├── seed/ # 人工种子(绝不覆盖) │ │ ├── top250.json # 豆瓣 Top250 基准清单(片单宇宙) │ │ ├── curated.json # 起始 ~40 部富集覆盖 │ │ └── curated_quotes.json # 每片精选台词(最高优先级) │ ├── raw/ # 各源原始输出(可重跑,gitignore) │ └── movies.json # 最终装配产物(Astro 构建消费) ├── src/ │ ├── data/{types.ts,movies.ts} # 类型 + 查询 helper(含人员/台词/时间轴派生) │ ├── lib/search.ts # Fuse.js 客户端搜索 + 年份筛选 │ ├── components/ # PosterCard / RatingBadge / QuoteList / PersonCard / Navbar / Footer │ ├── layouts/BaseLayout.astro │ └── pages/ # index / movie / people / timeline / quotes / search / 404 └── public/ # favicon + 生成的 search-index.json ``` ## 本地运行 ```bash cd movie-quotes npm install npm run fetch # 仅种子:无需 key,生成 data/movies.json npm run dev # 本地开发预览 npm run build && npm run preview ``` > 不填任何 key 也能跑通:种子已内置 ~90 部(含 ~40 部富集数据),首页即可出完整海报墙。 ## 数据获取脚本(保留以便持续更新) `npm run fetch` 按序运行:`douban_top250 → tmdb_discover → tmdb_enrich → wikidata → wikiquote → reviews_douban → assemble`。 任何一步失败(被限流/无 key)都不阻断其余步骤;最终 `assemble` 总能产出 `movies.json`。 ### 扩充片单:每年 ≥50 部(Top250 之外) `tmdb_discover.py` 用 TMDB `/discover/movie` **逐年枚举**,默认覆盖 **1939–2025 年、每年 50 部**(约 4350 部), 写入 `data/raw/tmdb_discover.json`,随后由 `tmdb_enrich.py` 补全海报/演职员,`assemble.py` 并入片单。 ```bash cp .env.example .env # 填入 TMDB_API_KEY npm run fetch ``` - **排序按 `vote_count` 降序**(而非 `popularity`):热度是实时值、每次跑都漂移,评分人数稳定,重跑结果**幂等**。 - **早期年份自动放宽**:1940s 前后 TMDB 收录有限,脚本会把评分人数门槛从 50 逐级降到 10、0;仍不足时打印告警并继续,不会中断。 - **断点续跑**:discover 每年落盘一次,enrich 每 50 部落盘索引,且已抓过详情的条目直接跳过。中断后重跑只补缺口。 - **不覆盖人工数据**:并入时用 `setdefault`,`top250.json` / `curated.json` 的既有条目优先级不变。 - **评分来源如实标注**:discover 条目不写入 `doubanRating`,其 TMDB 评分经 enrich 阶段标为 `ratingSource: "tmdb"`,不会伪装成豆瓣分。 ### 手动获取指定年份 不想跑全量时,用 `--years` 单独抓某几年。**命令行参数优先级高于 `.env` 环境变量。** ```bash # 单个年份 python scripts/tmdb_discover.py --years 1994 # 多个离散年份(逗号分隔,不要加空格) python scripts/tmdb_discover.py --years 1994,1997,2001 # 连续区间 python scripts/tmdb_discover.py --years 1990-1999 # 混合写法 + 自定义每年部数 python scripts/tmdb_discover.py --years 1994,1997,2010-2012 --per-year 80 ``` 也可用 npm script(`--` 后的参数会透传给脚本): ```bash npm run fetch:year -- --years 1994 npm run fetch:year -- --years 1990-1999 --per-year 30 ``` 抓完某年后,补全海报/演职员并重新组装: ```bash python scripts/tmdb_enrich.py # 或 npm run fetch:enrich python scripts/assemble.py # 或 npm run assemble ``` > `tmdb_enrich.py` 会自动跳过已抓过详情的条目,所以单独补一年不会重跑全库。 #### 全部参数 | 参数 | 简写 | 默认 | 说明 | | --- | --- | --- | --- | | `--years` | `-y` | — | 指定年份:`1994` / `1994,1997` / `1990-1999` / 混合 | | `--start-year` | — | `1939` | 起始年份(与 `--years` 同时给出时被忽略并告警) | | `--end-year` | — | `2025` | 结束年份(同上) | | `--per-year` | `-n` | `50` | 每年目标部数 | | `--min-votes` | — | `50` | 最低评分人数门槛,不足时自动放宽到 10、0 | | `--force` | `-f` | 关 | 忽略该年已有数据强制重抓 | | `--dry-run` | — | 关 | 只打印将抓取的年份与预计数量,不发请求、不写文件 | ```bash python scripts/tmdb_discover.py --help # 查看完整帮助 python scripts/tmdb_discover.py --years 1990-1999 --dry-run # 先确认范围 ``` #### 常见用法 ```bash # 先看看要抓多少,确认无误再真跑 python scripts/tmdb_discover.py --years 1980-1989 --dry-run # 某年数据不满意,强制重抓并提高配额 python scripts/tmdb_discover.py --years 2019 --force --per-year 100 # 冷门早期年份,直接放开评分人数门槛 python scripts/tmdb_discover.py --years 1939-1945 --min-votes 0 ``` > **增量与幂等**:默认跳过「已达标」的年份,中断后重跑只补缺口;需要覆盖已有数据时才加 `--force`。 > 结果统一累积在 `data/raw/tmdb_discover.json`,分多次抓不同年份不会互相覆盖。 ### 全量抓取的范围与配额 也可在 `.env` 中设默认值(命令行未传时生效): | 变量 | 默认 | 说明 | | --- | --- | --- | | `DISCOVER_START_YEAR` | `1939` | 起始年份 | | `DISCOVER_END_YEAR` | `2025` | 结束年份 | | `DISCOVER_PER_YEAR` | `50` | 每年目标部数 | | `DISCOVER_MIN_VOTES` | `50` | 最低评分人数门槛 | > **⚠️ 全量抓取的代价**:1939–2025 每年 50 部约 **4350 部**,TMDB 每部约 2 次请求 + 海报/背景图下载, > 受 0.2s 限速约束**耗时数小时**,`public/posters/` 体积可达 **1.5–3 GB**。 > 建议先小范围验证(如 `DISCOVER_START_YEAR=2020`)跑通再全量。若不想把图片入库, > 可只保留远程代理 URL——注释掉 `tmdb_enrich.py` 中 `fetch_image` 的下载分支即可。 - **想得到更全/更新/更深入的数据**:复制 `.env.example` → `.env`,填入 `TMDB_API_KEY`(免费注册 https://www.themoviedb.org/settings/api ), 可选填 `DOUBAN_COOKIE`(降低豆瓣被封概率),再跑一次 `npm run fetch`,即可: - 抓到完整豆瓣 Top 250 排名与评分; - 用 TMDB 补全全部影片的海报、演职员、相似电影、影评; - 用 Wikidata 补全原著小说 / 配乐 / 编剧; - 用 Wikiquote 增量补全台词。 - **增量安全**:`curated.json` / `curated_quotes.json` 中的人工内容永远不被脚本覆盖;脚本台词仅在不存在时追加(按 id 去重)。重跑不会丢失已富集数据。 ## 反爬与限流说明 - 豆瓣官方 API 已关闭,`douban_top250.py` 用随机 UA + Referer + Session Cookie + 随机延时 + 403/418 退避重试,失败回退 `api.wmdb.tv` 镜像。 - TMDB 限速 0.25s + 429 指数退避;无 key 时整步跳过。 - Wikiquote 解析按 `==Quotes==` 段提取,噪音较大,人工 curated 台词优先展示。 ## 中国大陆网络注意事项(重要) 本机若位于中国大陆网络,`api.themoviedb.org` 与 `image.tmdb.org` 会被 GFW 干扰,脚本已内置绕过,**无需手动处理**: 1. **`api.themoviedb.org`(API 主机)DNS 污染** → `tmdb_enrich.py` 通过 `doh.pub` 解析真实 CloudFront IP,并 `monkeypatch socket.getaddrinfo` 让请求走正确 IP(SNI/证书仍为原域名)。API 调用因此可用。 2. **`image.tmdb.org`(BunnyCDN 图片主机)被封锁** → 海报/背景图经图片代理 `https://wsrv.nl/?url=image.tmdb.org/t/p/...` 服务端代取后**下载到本地 `public/posters/`**,对国内访客友好。人物头像(演员/导演)则不本地化,直接以 `wsrv.nl` 代理 URL 输出,浏览器端加载。 3. 若你在**非中国大陆**网络运行,上述绕过仍然安全(doh.pub 也返回真实 IP),无需改动。 > 若 `wsrv.nl` 偶发不可用,脚本会把图片回退为远程代理 URL;可稍后重跑 `npm run fetch` 重新烘焙本地图。 ## 首页海报为何不显示 / 如何修复 根因:`data/movies.json` 里的 `posterUrl` 由 `assemble.py` 依据 **tmdbId** 拼出(`/posters/_poster.jpg`)。 而种子数据(豆瓣 Top250)本身**不含 tmdbId**,`data/raw/tmdb_index.json` 又只在跑过 `tmdb_enrich.py`(需 `TMDB_API_KEY`)后才生成。 一旦缺这层映射,`posterUrl` 全为 `null`,首页 `PosterCard` 只能显示文字占位、不出图。 `public/posters/` 里其实已下载了对应海报(文件名即 tmdbId),只是数据认不出它们。两种修复: **A. 立即出图(无需 key,离线)** `scripts/match_tmdb_ids.py` 用一份内置的高置信度 slug→tmdbId 字典 + 校验本地文件是否存在,生成 `data/seed/tmdb_id_map.json`; `assemble.py` 在 `tmdb_index.json` 缺失时会回退读取它。 ```bash python scripts/match_tmdb_ids.py # 生成 data/seed/tmdb_id_map.json(离线,匹配到本地有图的部分) npm run assemble # 重新生成 data/movies.json,对应影片出现 posterUrl ``` **B. 全部 90 部准确出图(需 key,推荐)** 填好 `.env` 的 `TMDB_API_KEY` 后,用 TMDB 搜索 API 自动匹配全部 slug→tmdbId(覆盖/补全内置字典): ```bash python scripts/match_tmdb_ids.py --online # 调 TMDB /search/movie 逐个匹配,产出完整 tmdb_id_map.json npm run assemble ``` > 更完整的做法是直接 `npm run fetch`:跑通 `tmdb_discover` + `tmdb_enrich` 后会重建 `data/raw/tmdb_index.json`, > assemble 用官方 id 关联本地海报,100% 准确(详见上文“扩充片单”)。 > 注意 `data/raw/` 被 `.gitignore` 忽略,映射脚本产出的 `data/seed/tmdb_id_map.json` 才会随仓库提交。 ## 奖项数据(从设立年至今) 奖项页(`/awards`)原本每个奖项只收录 2010–2025 年。`scripts/fetch_awards.py` 将其补全为 **6 个奖项各自从设立年到 2025 的逐年获奖名单**(奥斯卡 1929、威尼斯金狮 1932、戛纳金棕榈 1955、 柏林金熊 1951、金球最佳剧情片 1944、BAFTA 最佳影片 1949)。 数据来源(两种,按优先级): 1. **[默认/兜底]** `data/seed/awards_full.json` —— 随仓库提交的权威完整名单(人工维护)。 2. **[可选]** 维基百科列表页:`python scripts/fetch_awards.py --wiki` 会用英文维基 Action API 抓取并覆盖各奖项的 `winners`。 ```bash python scripts/fetch_awards.py # 用内置权威数据生成 data/seed/awards.json python scripts/fetch_awards.py --wiki # 尝试用维基百科实时数据覆盖(需可访问 en.wikipedia.org 的网络) python scripts/fetch_awards.py --dry-run # 仅校验年份范围,不写文件 ``` > ⚠️ **网络限制**:本项目运行环境(中国大陆)对维基百科中/英文站与 Wikidata 均为应用层封锁, > 因此 `--wiki` 在此环境实际抓不到,脚本会自动降级为 `awards_full.json`。 > 在可访问维基的网络下重跑 `--wiki` 即可用实时数据校准。 > 另:`awards_full.json` 中约 1970 年以前的个别早期条目为人工整理、建议联网后用 `--wiki` 复核。 ## 部署 `npm run build` 产出纯静态 `dist/`,可托管到任意静态空间(GitHub Pages / Gitee Pages / CloudStudio / Vercel)。 如需子路径部署:`SITE_URL=... SITE_BASE=/movie-quotes/ npm run build`。