feat: 「自动获取」改用豆瓣数据源,海报自动下载入库

此前的 OMDb 数据源必须用英文片名,国产片覆盖极差。改用豆瓣公开接口后
中文片名直接可搜,冷门片/老片/剧集也能命中。

fetch_movie.php(全量重写)
- 搜索走 movie.douban.com/j/subject_suggest;详情走移动端
  m.douban.com/rexxar/api/v2/movie/<id>(PC 详情页有反爬 302,故用移动端接口)
- 海报下载后写入 uploads/posters/,文件名沿用内容指纹规则
- 新增登录鉴权:未登录返回 401(此前该接口完全公开)
- 启用 TLS 证书校验(原为 CURLOPT_SSL_VERIFYPEER=false)
- 魔数兜底 + 12MB 上限:挡掉反爬返回的 HTML 页面与异常大响应
- old_poster 残留清理:重复点击时删掉上一张,且仅删「未被数据库引用」的文件
- 接口地址支持环境变量覆盖,便于用 mock 做端到端测试

lib/helpers.php
- 新增 isValidStoredKey():校验表单回传的存储键,防目录穿越与越权引用

add.php
- 前端改用新字段(豆瓣评分 / 地区 / 又名),并把 poster_key 放进隐藏域回传
- 提交时「手动上传的海报优先」,没有才采用自动获取已入库的海报

README.md
- 同步为豆瓣数据源说明,移除已过时的 OMDb API Key 与 SSL 校验两条安全提示

验证
- 本地 mock 豆瓣端到端:33/33 通过
- 既有存储回归套件:196/196 通过
- A/B 归一化比对:除 add.php 按预期变化外,其余 45 个响应完全一致
- 线上实测:流浪地球 / 哪吒之魔童闹海 均抓取成功,海报落盘为合法 JPEG
This commit is contained in:
2026-09-21 23:05:40 +08:00
parent 294c34bbc2
commit ddf2eeafaa
4 changed files with 416 additions and 100 deletions
+42 -20
View File
@@ -30,7 +30,7 @@
- **两种图片访问方式**:**直连存储**(浏览器直接访问对象存储)与**服务端代理**(经本站 `media.php` 鉴权后由服务器读取回吐,**桶可保持私有**)。两者的取舍与配置见下文《图片存储与对象存储》。
### 信息辅助
- **OMDb 自动获取**:在添加页输入片名后点击「📡 自动获取」,自动填充年份、类型、片长、导演、主演、IMDb 评分与剧情简介,并预览海报(海报仅供参考,仍需本地上传)。
- **豆瓣自动获取**:在添加页输入片名(中文即可,英文片名也能命中对应条目)后点击「📡 自动获取」,自动填充年份、地区、类型、片长、导演、主演、豆瓣评分、又名与剧情简介,**并把海报下载入库**——提交时若未手动上传图片,就直接采用自动获取的海报。
- **CSV 批量导入**:一次性导入历史数据,自动识别 UTF-8 / GBK / GB2312 编码,自动创建不存在的影院,影院名去重,全程事务保护(出错自动回滚),并输出逐行错误报告。
### 检索与统计
@@ -69,7 +69,7 @@
| 数据库 | SQLite(`db/movies.db`,首次运行自动建表) |
| 前端 | 原生 HTML / CSS / JavaScript,无构建步骤 |
| 第三方库 | [Cropper.js 1.5.13](https://github.com/fengyuanchen/cropperjs)(图片裁剪)、[Chart.js 4.4.0](https://www.chartjs.org/)(图表),均由 CDN 引入 |
| 外部 API | [OMDb API](http://www.omdbapi.com/)(电影信息查询,需申请 API Key) |
| 外部 API | [豆瓣](https://movie.douban.com/)(公开网页接口,**无需申请 Key**,用于电影信息与海报查询) |
---
@@ -112,7 +112,7 @@ MovieLog_Server/
├── settings.php # 系统设置页:存储后端选择、图片访问方式、连接测试、迁移
├── storage_api.php # 存储维护接口(stat / migrate / test),返回 JSON
├── media.php # 服务端图片代理:鉴权后由服务器读取对象并同源回吐
├── fetch_movie.php # OMDb 代理接口,返回 JSON
├── fetch_movie.php # 豆瓣抓取接口(搜索 + 详情 + 海报入库),返回 JSON
├── test_crop.php # 开发调试页:验证 Base64 图片落盘是否正常
├── css/style.css # 全局样式
├── js/main.js # 公共脚本(含各页面共用的表单联动函数、破图兜底)
@@ -179,7 +179,11 @@ MovieLog_Server/
chmod -R 755 db uploads
```
3. **配置 OMDb API Key**(可选,仅影响「自动获取」功能):编辑 `fetch_movie.php`,将 `OMDB_API_KEY` 换成你在 [omdbapi.com](http://www.omdbapi.com/apikey.aspx) 申请的免费 Key。
3. **确认「自动获取」可用(可选)**:该功能由服务端直接请求豆瓣的公开网页接口实现,**无需申请任何 API Key**。只需保证:
- PHP 已启用 `curl` 扩展(本项目的图片下载、对象存储也都依赖它);
- 服务器能出网访问 `movie.douban.com`、`m.douban.com` 与 `img*.doubanio.com`。
若服务器网络受限导致请求失败,「自动获取」会返回明确提示,但**不影响其它功能**——照常手动填写信息、上传海报即可。
4. **访问站点**:浏览器打开 `login.php`。
@@ -277,27 +281,45 @@ CSV **共 9 列,顺序固定**:
### 接口:`fetch_movie.php`
供添加页前端调用,服务端代请求 OMDb 避免浏览器跨域。
供添加页前端调用,服务端代请求豆瓣(既避免浏览器跨域,也复用服务器的出网能力)。
- **请求**:`GET fetch_movie.php?q=电影名称`
- **请求**:`GET fetch_movie.php?q=片名[&old_poster=上一次抓取的海报键]`
- `q`:片名,中文或英文均可(英文名会命中对应的中文条目)
- `old_poster`:可选。用于清理「上一次抓取、但本次未采用」的海报文件,前端在重复点击时自动回传
- **需要登录**:未登录返回 `HTTP 401` + `{"error": "…"}`
- **响应**:`Content-Type: application/json`
- **成功**:
```json
{
"title": "Forrest Gump",
"year": "1994",
"rated": "PG-13",
"released": "06 Jul 1994",
"runtime": "142 min",
"genre": "Drama, Romance",
"director": "Robert Zemeckis",
"actors": "Tom Hanks, Robin Wright, Gary Sinise",
"plot": "...",
"poster": "https://...",
"imdbRating": "8.8"
"ok": true,
"source": "douban",
"douban_id": "34780991",
"douban_url": "https://movie.douban.com/subject/34780991/",
"title": "哪吒之魔童闹海",
"original_title": "",
"year": "2025",
"genre": "剧情 / 喜剧 / 动画",
"runtime": "144分钟",
"director": "饺子",
"actors": "吕艳婷 / 囧森瑟夫 / 瀚墨",
"plot": "……剧情简介……",
"rating": "8.4",
"country": "中国大陆",
"alias": "哪吒2 / 哪吒2之魔童闹海 / Ne Zha 2",
"poster": "/uploads/posters/xxxxxxxxxxxx_xxxxxx_poster.jpg",
"poster_key": "uploads/posters/xxxxxxxxxxxx_xxxxxx_poster.jpg"
}
```
- **失败**:`{"error": "错误说明"}`(如「未找到相关电影,请尝试更准确的关键词」)
- **字段说明**:
- `poster` 是给 `<img>` 预览的地址。海报已由本接口**下载入库**,所以它是本站同源地址(或存储直链),不是豆瓣的外链。
- `poster_key` 是提交表单时应写入 `movies.poster_path` 的存储键。前端把它放进隐藏域 `fetched_poster_key` 回传;
提交时**手动上传的海报优先**,没有才采用该键。键的目录与文件名、以及文件是否真实存在,都由服务端校验。
- `rating` 为豆瓣评分(保留一位小数)。无评分、或该片暂无海报时,对应字段为空串。
- **失败**:`{"error": "错误说明"}`(如「豆瓣没有找到「xxx」,换个更准确的片名试试」)
> **关于数据来源与合规**:本功能使用的是豆瓣的**公开网页接口**(非官方开放 API,也不需要 API Key),
> 按需抓取少量条目用于个人观影记录,并已加入登录鉴权。请自行评估使用场景与豆瓣的服务条款;
> 若需商用或大规模采集,请改用官方或商业数据源。
---
@@ -539,10 +561,10 @@ curl -sk -H "Host: 你的域名" -H "Cookie: PHPSESSID=xxxx" -o /dev/null \
部署到公网前请务必处理以下事项:
1. **修改默认口令**:默认账号 `admin / 123456` 必须在首次登录后立即更换。
2. **更换 OMDb API Key**:`fetch_movie.php` 中当前硬编码的 Key 为开发期临时使用,请替换为你自己的 Key;若仓库需要公开,建议将该值改为从环境变量读取。
2. **「自动获取」会写入存储**:`fetch_movie.php` 已要求登录后才能调用,且只请求固定的豆瓣域名、只把文件写入 `uploads/posters/`;表单回传的 `fetched_poster_key` 与 `old_poster` 都要通过「目录前缀 + 文件名 + 数据库引用」三重校验才会被采用或删除。若你的部署不需要该功能,可直接删除 `fetch_movie.php` 并移除 `add.php` 里的按钮。
3. **删除或保护 `test_crop.php`**:该文件是开发调试页,**没有登录校验**,仅用于验证 Base64 图片能否落盘,生产环境应直接删除。
4. **关闭调试输出**:`config.php` 顶部当前为 `error_reporting(E_ALL)` + `display_errors=1`,上线前请改为关闭或仅写日志,避免泄露路径与数据库信息。
5. **启用 HTTPS 并校正证书校验**:`fetch_movie.php` 中 `CURLOPT_SSL_VERIFYPEER` 被设为 `false`,生产环境应改为 `true`。
5. **保证 CA 证书可用**:图片下载与「自动获取」都依赖 `curl`,且 `fetch_movie.php` 已启用证书校验(`CURLOPT_SSL_VERIFYPEER = true`)。若服务器缺少 CA 证书束(`curl.cainfo` 为空,常见于最小化安装),请求会失败,请安装 `ca-certificates` 包。
6. **保护数据目录**:`db/`(含 `movies.db`)与 `uploads/` 应禁止直接目录列举;SQLite 文件更应通过 Web 服务器规则**拒绝任何 HTTP 访问**。Nginx 示例:
```nginx
location ~ ^/(db|uploads/.*\.(db|sqlite)) { deny all; }