Skip to content

API 参考

本文档详细说明爬虫源需要实现的接口规范。

接口概览

爬虫源按需实现以下 5 个接口方法(并非全部必选)。例如推送类脚本只需实现 detailplay。运行器调用时传入 methodparamscontext,脚本内各 handler 的推荐签名(params, context)

方法说明params 主要字段返回值
home获取首页数据{}分类列表和推荐视频
category获取分类数据{categoryId, page, filters?}分页视频列表
detail获取视频详情{videoId}视频详情信息
search搜索视频{keyword, page?, quick?}搜索结果列表
play获取播放地址{playId, flag?}播放地址信息

所有接口的入参均由运行器传入两部分:params(本次调用的业务参数,见上表)和 context(请求上下文,见下文「请求上下文 context」)。调用端标识等从 context.from 获取,不再通过 params 传递。

请求上下文 context

params 外,运行器会传入 context(由 Runner 通过 stdin 注入),脚本应使用 handler 的第二个参数接收。context 各字段的详细说明见 爬虫开发介绍 - 请求上下文 context

typescript
// context 类型
interface RequestContext {
  baseURL?: string; // 当前请求的基础 URL(如 OmniBox 服务地址)
  headers?: Record<string, string>; // 客户端请求头(User-Agent、Cookie 等)
  sourceId?: string; // 当前爬虫源 ID
  from?: string; // 调用端:web(默认)| tvbox | uz | catvod | emby;未传时默认为 web
}
  • baseURL:可用于拼装绝对链接(如图片、播放地址)。
  • headers:客户端请求头,可按需透传到第三方 API。
  • sourceId:当前爬虫源 ID,调用 getSourceCategoryDataaddPlayHistory 等 API 时由 SDK 从 context 读取。
  • from:调用端标识,未传时默认为 web;可按端做差异化(例如网页端隐藏某线路、TV 端只返回直链等)。

home - 获取首页数据

获取首页的分类列表和推荐视频列表。

入参

  • params{}(无业务参数)
  • context:请求上下文,见 请求上下文 context。可从 context.fromcontext.baseURL 等按需使用。

返回值

typescript
{
  class: Array<{
    type_id: string;      // 分类ID
    type_name: string;   // 分类名称
  }>;
  list: Array<VodItem>;  // 推荐视频列表
  filters?: {            // 可选:筛选条件
    [categoryId: string]: Array<{
      key: string;       // 筛选器key
      name: string;      // 筛选器名称
      init: string;      // 默认值
      value: Array<{
        name: string;    // 选项名称
        value: string;   // 选项值
      }>;
    }>;
  };
  banner?: Array<BannerItem>;  // 可选:跑马灯Banner数据
}

示例

javascript
async function home(params, context) {
  // context 含 baseURL、headers、sourceId、from,可按需使用
  const from = context?.from; // 如 "web"(默认)| "tvbox" | "emby"
  return {
    class: [
      { type_id: "1", type_name: "电影" },
      { type_id: "2", type_name: "电视剧" },
    ],
    list: [
      {
        vod_id: "1",
        link: "https://movie.douban.com/subject/1",
        vod_name: "示例视频",
        vod_pic: "https://example.com/pic.jpg",
        type_id: "1",
        type_name: "电影",
        vod_remarks: "HD",
        vod_year: "2024",
        vod_douban_score: "8.5",
        vod_subtitle: "2024 / 中国大陆 / 剧情",
        search: 0, // 可选,UZ 专用:1=点击时执行搜索
      },
    ],
    banner: [
      {
        title: "热门电影",
        subtitle: "2024年度最佳",
        backgroundImage: "https://example.com/banner-bg.jpg",
        genre: "剧情",
        actors: "张三, 李四",
        description: "这是一部精彩的电影",
      },
    ],
  };
}

category - 获取分类数据

获取指定分类的分页视频列表。

入参

  • params:本次调用的业务参数。
字段类型必填说明
categoryIdstring分类 ID
pagenumber页码,默认 1
filtersobject筛选条件,如 type、area、year、sort 等

示例:{ "categoryId": "1", "page": 1, "filters": { "type": "电影", "area": "中国大陆", "year": "2024", "sort": "1" } }

返回值

typescript
{
  page: number; // 当前页码
  pagecount: number; // 总页数
  total: number; // 总记录数
  list: Array<VodItem>; // 视频列表
}

示例

javascript
async function category(params, context) {
  const categoryId = params.categoryId || "1";
  const page = params.page || 1;
  const filters = params.filters || {};
  // context.baseURL、context.from 等可按需使用

  return {
    page: page,
    pagecount: 10,
    total: 100,
    list: [
      {
        vod_id: `${categoryId}_${page}_1`,
        link: `https://movie.douban.com/subject/${categoryId}_${page}_1`,
        vod_name: "分类视频",
        vod_pic: "https://example.com/pic.jpg",
        type_id: categoryId,
        type_name: "电影",
        vod_remarks: "HD",
        vod_year: "2024",
        vod_douban_score: "8.5",
        search: 0, // 可选,UZ 专用:1=点击时执行搜索,0=否
      },
    ],
  };
}

detail - 获取视频详情

获取视频的详细信息,包括简介、播放地址等。

入参

  • params:本次调用的业务参数。
字段类型必填说明
videoIdstring视频 ID

示例:{ "videoId": "123" }

  • context:请求上下文,见 请求上下文 context调用端标识(如 web、tvbox、emby)从 context.from 获取,可按端做差异化(例如网页端隐藏某线路、TV 端只返回直链等)。

返回值

typescript
{
  list: Array<{
    vod_id: string;
    vod_name: string;
    vod_pic: string;
    vod_content?: string; // 视频简介
    vod_director?: string; // 导演
    vod_actor?: string; // 演员
    vod_area?: string; // 地区
    vod_year?: string; // 年份
    vod_remarks?: string; // 备注
    vod_douban_score?: string; // 豆瓣评分
    type_name?: string; // 分类名称
    vod_play_sources?: PlaySource[]; // 结构化播放源列表
  }>;
}

// 播放源结构
interface PlaySource {
  name: string; // 线路名称
  episodes: Episode[]; // 剧集列表
}

// 剧集信息
interface Episode {
  name: string; // 剧集名称
  playId: string; // 播放ID
  size?: number; // 视频大小(字节,可选)
  // TMDB剧集信息(可选,从刮削元数据中获取)
  episodeName?: string; // TMDB剧集名称
  episodeOverview?: string; // TMDB剧集简介
  episodeAirDate?: string; // TMDB剧集首播日期
  episodeStillPath?: string; // TMDB剧集剧照路径
  episodeVoteAverage?: number; // TMDB剧集评分
  episodeRuntime?: number; // TMDB剧集时长(分钟)
}

播放源格式说明

使用 vod_play_sources 数组,每个元素包含:

  • name: 线路名称
  • episodes: 剧集数组,每个剧集包含名称、播放ID、大小和 TMDB 信息(可选)

从刮削元数据获取TMDB信息

爬虫脚本可以通过 OmniBox.getScrapeMetadata() 获取刮削元数据,然后匹配 videoMappings 来填充剧集的TMDB信息:

javascript
const metadata = await OmniBox.getScrapeMetadata(resourceId);
const videoMappings = metadata.videoMappings || [];

// 在构建剧集时,匹配映射关系并填充TMDB信息
for (const file of videoFiles) {
  const formattedFileId = `${shareURL}|${file.fid}`;
  const mapping = videoMappings.find((m) => m.fileId === formattedFileId);

  const episode = {
    name: fileName,
    playId: formattedFileId,
    size: file.size,
  };

  // 如果找到映射关系,填充TMDB信息
  if (mapping) {
    episode.episodeName = mapping.episodeName;
    episode.episodeOverview = mapping.episodeOverview;
    episode.episodeAirDate = mapping.episodeAirDate;
    episode.episodeStillPath = mapping.episodeStillPath;
    episode.episodeVoteAverage = mapping.episodeVoteAverage;
    episode.episodeRuntime = mapping.episodeRuntime;
  }
}

示例

javascript
async function detail(params, context) {
  const videoId = params.videoId;
  const from = context?.from || "web"; // 调用端:web | tvbox | uz | catvod | emby

  // 获取刮削元数据(可选)
  let metadata = null;
  try {
    metadata = await OmniBox.getScrapeMetadata(videoId);
  } catch (error) {
    OmniBox.log("warn", `获取元数据失败: ${error.message}`);
  }

  const videoMappings = metadata?.videoMappings || [];

  // 构建结构化播放源
  const playSources = [
    {
      name: "线路1",
      episodes: [
        {
          name: "第1集",
          playId: "ep1_id",
          size: 1073741824, // 1GB in bytes
          // 如果匹配到TMDB映射,填充TMDB信息
          episodeName: "离乡",
          episodeOverview: "少年王林,家境贫困...",
          episodeAirDate: "2023-09-25",
          episodeStillPath: "/gYVT7ybhpQswGrrGU3WBXKnwW5Q.jpg",
          episodeVoteAverage: 9.7,
          episodeRuntime: 25,
        },
        {
          name: "第2集",
          playId: "ep2_id",
          size: 1073741824,
        },
      ],
    },
    {
      name: "线路2",
      episodes: [
        {
          name: "第1集",
          playId: "ep1_alt_id",
          size: 1073741824,
        },
      ],
    },
  ];

  return {
    list: [
      {
        vod_id: videoId,
        link: `https://movie.douban.com/subject/${videoId}`,
        vod_name: "示例视频详情",
        vod_pic: "https://example.com/pic.jpg",
        vod_content: "视频简介内容",
        vod_year: "2024",
        vod_remarks: "HD",
        vod_douban_score: "8.5",
        type_name: "电影",
        vod_play_sources: playSources,
      },
    ],
  };
}

search - 搜索视频

根据关键词搜索视频。

入参

  • params:本次调用的业务参数。
字段类型必填说明
keywordstring搜索关键词
pagenumber页码,默认 1
quickboolean是否为快速搜索(可选)

示例:{ "keyword": "关键词", "page": 1, "quick": false }

返回值

typescript
{
  page: number; // 当前页码
  pagecount: number; // 总页数
  total: number; // 总记录数
  list: Array<VodItem>; // 搜索结果列表
}

示例

javascript
async function search(params, context) {
  const keyword = params.keyword || "";
  const page = params.page || 1;
  // context.from、context.sourceId 等可按需使用

  if (!keyword) {
    return {
      page: 1,
      pagecount: 0,
      total: 0,
      list: [],
    };
  }

  return {
    page: page,
    pagecount: 5,
    total: 50,
    list: [
      {
        vod_id: `search_${keyword}_1`,
        link: `https://movie.douban.com/subject/search_${keyword}_1`,
        vod_name: `搜索结果: ${keyword}`,
        vod_pic: "https://example.com/pic.jpg",
        type_id: "1",
        type_name: "电影",
        vod_remarks: "HD",
        vod_year: "2024",
        vod_douban_score: "8.5",
        search: 1, // 可选,UZ 专用:1=点击该条时执行搜索
      },
    ],
  };
}

play - 获取播放地址

获取视频的播放地址。可同时返回弹幕列表danmaku)和解析标识parse)。

入参

  • params:本次调用的业务参数。
字段类型必填说明
playIdstring播放地址 ID(如剧集 playId)
flagstring播放源标识,默认 "play"

示例:{ "playId": "ep1_123", "flag": "play" }

  • context:请求上下文,见 请求上下文 context调用端标识context.from 获取,可按端做差异化(例如网页端过滤某线路、TV 端只返回直链等)。

返回值

推荐格式:urls 数组(含弹幕与 parse)

typescript
{
  urls: Array<{ name: string; url: string }>;  // 播放地址列表,每项为画质/线路名与地址
  flag: string;                                 // 播放源标识
  header?: Record<string, string>;              // HTTP 请求头(可选)
  danmaku?: Array<{ name: string; url: string }>; // 弹幕列表(可选),见下文
  parse?: number;                               // 0=直链不需解析,1=需客户端嗅探解析(仅 ok影视 app 有效)
}

弹幕 danmaku(可选)

返回弹幕时,客户端可加载对应弹幕文件并叠加到播放器。结构为数组,每项包含:

字段类型说明
namestring弹幕名称(如剧名+集数,用于展示)
urlstring弹幕文件或接口 URL(如 XML 弹幕接口)

示例:根据当前集数匹配第三方弹幕 API,返回一条或多条弹幕源。

javascript
// 示例:返回一条弹幕
playResponse.danmaku = [{ name: "某番剧 - 第1集", url: "https://danmu.example.com/api/v2/comment/12345?format=xml" }];

参考实现见 backend/static/templates/js/site_spider.js 中的 playmatchDanmu

parse 字段(可选)

含义
0播放地址为直链(如 .m3u8.mp4),无需解析
1需要客户端嗅探解析(如从解析页/跳转页中提取真实播放地址)

说明parse === 1 时的“客户端嗅探解析”仅对 ok影视 app 有效,其他客户端(网页、TVBox、UZ 等)会忽略该字段,按直链处理。

其他兼容格式

  • 格式 1:单地址 url: string,可带 headerdanmakuparse
  • 格式 2url: string[](名称与地址交替)。
  • 格式 3url: { values: Array<{name, url}>, position: number }

示例

javascript
async function play(params, context) {
  const playId = params.playId;
  const flag = params.flag || "play";
  const from = context?.from || "web"; // 调用端:web | tvbox | uz | catvod | emby

  // 推荐:使用 urls 数组格式,可同时返回弹幕与 parse
  let urls = [
    { name: "4K", url: `https://example.com/video/${playId}_4k.m3u8` },
    { name: "1080P", url: `https://example.com/video/${playId}_1080p.m3u8` },
  ];
  if (from === "web") {
    urls = urls.filter((item) => item.name !== "RAW");
  }

  const playResponse = {
    urls,
    flag,
    header: { "User-Agent": "Mozilla/5.0", Referer: "https://example.com/" },
    parse: /\.(m3u8|mp4)$/.test(playId) ? 0 : 1, // 直链为 0,否则为 1(仅 ok影视 app 会做嗅探解析)
  };

  // 可选:附加弹幕(若脚本有弹幕匹配逻辑)
  const danmakuList = await matchDanmu(videoName, episodeNum); // 自定义函数
  if (danmakuList.length > 0) {
    playResponse.danmaku = danmakuList; // [{ name: "剧名 - 第1集", url: "https://..." }, ...]
  }

  return playResponse;
}

仅返回单一直链时也可使用简化格式:

javascript
return {
  url: `https://example.com/video/${playId}.m3u8`,
  flag: flag,
  header: { Referer: "https://example.com/" },
  parse: 0, // 直链,无需解析
  danmaku: [{ name: "弹幕", url: "https://danmu.example.com/api/comment/123?format=xml" }],
};

数据模型

VodItem - 视频项

用于 home、category、detail、search 等接口返回的列表项。除下列字段外,detail 返回的单条还可包含 vod_contentvod_directorvod_actorvod_play_sources 等,见 detail 返回值

typescript
{
  vod_id: string;           // 视频ID(必填)
  link: string;             // 豆瓣详情链接(必填),格式:https://movie.douban.com/subject/{vod_id}
  vod_name: string;         // 视频名称(必填)
  vod_pic: string;          // 封面图片URL(必填)
  type_id: string;          // 分类ID(必填)
  type_name: string;       // 分类名称(必填)
  vod_remarks?: string;    // 备注(可选)
  vod_year?: string;        // 年份(可选)
  vod_douban_score?: string; // 豆瓣评分(可选)
  vod_subtitle?: string;    // 副标题(可选)
  vod_tag?: string;         // 特殊标记(可选)。当为 "folder" 时表示该条目是“目录/文件夹”,客户端点击会进入该目录(以 vod_id 作为下一层 categoryId)而不是跳转播放页
  search?: number;          // UZ 播放器专用:点击该影片时是否执行搜索,1=是,0=否(可选)
}

BannerItem - Banner项(首页轮播)

用于网页端首页显示的跑马灯轮播图

typescript
{
  title: string;            // 标题(必填)
  subtitle?: string;        // 副标题(可选)
  backgroundImage: string;  // 背景图 URL(必填)
  genre?: string;           // 类型/标签(可选)
  actors?: string;          // 演员信息(可选)
  description?: string;     // 描述(可选)
}

下一步