Files
UHD_Media_Tool/docs/porting/moviepilot-discover.md
T

7.4 KiB
Raw Blame History

MoviePilot 推荐 / 探索对照(发现页)

上游:前端 MoviePilot-Frontend/src/pages/{recommend,discover,browse}.vue、views/discover/*、utils/recommendSources.ts; 后端 app/chain/{recommend,tmdb,douban,bangumi}.py、app/api/endpoints/{recommend,discover}.py、app/modules/douban/apiv2.py、app/modules/bangumi/bangumi.py、app/modules/themoviedb/tmdbapi.py(v3.0.10-1)。

MP 有什么

页面 结构 数据源 / 分类
推荐 /recommend 按分区横向滑动(MediaCardSlideView),每区「查看全部」进 /browse/<api>;顶部按「全部 / 电影 / 剧集 / 动漫 / 音乐 / 榜单」筛分区;分区可开关(存用户配置) TMDB 流行趋势(trending/all/week)、TMDB 热门电影 / 剧集(discover,剧集限 zh/en/ja/ko)、豆瓣正在热映、豆瓣热门电影 / 剧集 / 动画、豆瓣最新电影 / 剧集(`movie
探索 /discover 按来源分标签(TMDB / 豆瓣 / Bangumi / …),每个来源一组筛选芯片,下面是无限滚动网格(MediaCardListView,page++) TMDB:类型(电影 / 剧集)、排序、风格、原始语言、评分(+最少评分人数);豆瓣:类型、排序(综合 / 首映 / 近期热度 / 高分)、风格、地区、年代(拼成 tags);Bangumi:分类、排序(排名 / 日期)、年份
卡片 海报、评分、标题、年份;右上角「已入库 / 已订阅」标记;点开进媒体详情(豆瓣 / bgm 条目在详情页再识别 TMDB);卡片上可订阅 —

缓存:推荐链每个列表 @cached(ttl=24h, skip_empty=True);TMDB / 豆瓣 / bgm 底层请求另有 @cached(ttl=CONF.meta),失败(None)不缓存。

我们原来有什么

只有 bgm 每日放送、豆瓣实时热门电影 / 剧集三个标签,没有 TMDB 列表、榜单与筛选;卡片没有本地状态。

这次做了什么

页面选「综合 + 可拆分」:推荐(综合,按分区横排,每区「查看全部」带着同样的参数跳到探索)与探索(选来源后出筛选器,无限滚动),两者在同一个路由 /console/discover?view=recommend|explore,状态全部放在 URL 里,可分享、可回退。理由:MP 两页的分工(推荐看热点、探索做筛选)好用,但两页之间来回跳要重选条件;放一页、共享 URL 参数后「查看全部」就是带参数切到探索。

来源 列表 接口
TMDB 流行趋势(日 / 周 × 全部 / 电影 / 剧集)、热门、正在上映 / 正在播出、即将上映 / 今日播出、高分、按条件筛选(类型、风格、原始语言、出品地区、年份或年代、最低评分、排序)、关键词搜索 GET /discover/tmdb
豆瓣 榜单(实时热门、正在热映、热门、即将上映、Top250、国产 / 欧美 / 日 / 韩剧、动画、综艺、华语 / 全球口碑剧集榜)、标签(热门 / 最新 / 豆瓣高分 / 冷门佳片 / 华语 / 美剧…)、筛选(排序 + 风格 + 地区 + 年代,同 MP)、关键词搜索 GET /discover/douban
Bangumi 每日放送、热门趋势、排行(可选年份)、最新、关键词搜索 GET /discover/bangumi-calendar、GET /discover/bangumi

卡片:海报(懒加载)、评分、标题、年份 · 类型 · 风格;本地状态只显示非默认值(已订阅 / 媒体库已有 / 下载中)。点卡片:有本地作品进媒体库详情;TMDB 条目进 media/tmdb/:type/:id;豆瓣 / bgm 条目先按名称 + 年份匹配 TMDB(原有匹配逻辑),确定就进作品页,不确定列候选。卡片上「订阅」「搜索资源」同样先拿到 TMDB 作品再开订阅表单 / 选站点。

豆瓣 / bgm 条目确定匹配后记在 kv_cache discover.match(30 天),下次列表据此标本地状态,再点也不用重新匹配;不确定 / 失败不记。

取舍与偏离

  • HTTP 直连,不登录,不用浏览器:豆瓣(rexxar 带 Referer: https://m.douban.com/;桌面版 j/search_subjects、j/subject_suggest 带浏览器 UA)与 bgm(官方 API,只要 UA)2026-10-06 实测都能直接拿到。被风控(403、返回验证页而不是 JSON)时按失败处理、不缓存、界面报错可重试;以后真的频繁被拦,再考虑经内置浏览器代取(适配器的 fetch 是注入的,换成浏览器代理的 fetch 即可,本线不实现)。
  • bgm 热门趋势没有 API,抓 bgm.tv/anime/browser?sort=trends 的服务端渲染 HTML;HTML 是外部不可信字节,按 ADR-005 在沙箱任务 bangumi.trends 里解析(parseBangumiBrowser)。
  • 豆瓣搜索用 j/subject_suggest(联想,不分页,约 10 条);MP 用要签名的 frodo 接口,我们不用。
  • 豆瓣筛选用 rexxar movie|tv/recommend(MP 用 frodo 同名接口,参数相同);标签列表用桌面版 j/search_subjects。
  • TMDB 列表缓存:仍走 TMDB 请求缓存(tmdb.request,失败不缓存),但列表类路径(/trending/、/discover/、电影 / 剧集榜单)TTL 单独取 6 小时、空结果不超过它(TmdbConfig.cache.listTtlMs);我们详情缓存 30 天,列表不能跟着它。MP 为 24 小时(CONF.meta / recommend_ttl),这里取短一些,日榜不至于整天不变。
  • MP 的音乐、AniList、插件扩展来源不做;推荐分区的开关配置不做(分区不多,按「全部 / 电影 / 剧集 / 动画 / 榜单」筛即可)。
  • TMDB 风格、语言、地区字典写在 @umt/contracts(TMDB_MOVIE_GENRES 等),界面与服务端校验共用,不请求 /genre/list(同 MP)。

豆瓣 / bgm 请求缓存(对照 DoubanApi.__invoke / __post、BangumiApi 的 @cached)

上游 我们
每个请求 @cached(maxsize=CONF.douban, ttl=CONF.meta, skip_none=True),键 = 函数 + 参数 列表 / 搜索请求经 MetaRequestCache 端口 → 服务端 cachedCall(region douban.request / bangumi.request,上限 5000 条);键 = 方法 + 参数排序后的地址 + 请求体(metaRequestKey)
TTL 只有一个 CONF.meta 按类别取 storage.cacheTtl:搜索 metaSearchDays(缺省 1 天)、榜单 / 热门 / 每日放送 / 趋势页 metaListHours(缺省 6 小时)、空结果 metaEmptyHours(缺省 1 小时,不超过正常 TTL)
skip_none:请求失败返回 None 不缓存 网络失败、非 2xx(含 403)、返回的不是 JSON(风控 / 验证页)、趋势页不是浏览页,适配器都抛错,cachedCall 不写
条目详情同样走请求缓存 条目详情不走请求缓存:已有条目级缓存 douban.subject / bangumi.subject(30 天,存归一化后的标题 / 别名 / 年份,还收站点详情页与网页搜索得到的条目),再缓存一遍原始响应就是同一份数据存两遍;meta.subject_miss(6 小时)保留,是识别升级时防止反复打外站的负缓存,不属于请求缓存
限流:无(靠缓存) 每源进程内限流(豆瓣 2 并发、起始间隔 300 ms;bgm 3 并发、100 ms),只包真正发出的网络请求,缓存命中不占额度

设置字段只在 contracts/settings.ts 追加(metaSearchDays、metaListHours、metaEmptyHours),设置页界面由设置线决定是否展示。