爬蟲介接指南

從 Java、Python、JavaScript 範例開始,了解載入方式、方法呼叫與回傳資料。三種語言目前涵蓋 JAR、QuickJS、Chaquopy 與 Node.js 四種執行環境。

先讓 App 找到你的爬蟲

Site 最小配置

JSON / Sitesites[] 物件片段
{
  "key": "demo",
  "name": "Demo",
  "type": 3,
  "api": "./demo.js",
  "ext": {}
}
Java JARapi: "csp_Demo"

類別放在 com.github.catvod.spider.Demo,並繼承抽象 Spider;主配置必須提供 spider 或站點的 jar

Pythonapi: "./demo.py"

相對路徑以配置檔所在位置為基準;輸出 Spider 類別並繼承 base.spider.Spider

JavaScript · QuickJSapi: "./demo.js"

相對路徑以配置檔所在位置為基準;ES module 預設匯出物件或建立物件的函式。

可直接改寫的最小 Demo

三份範例示範「分類 → 列表 → 詳情 → 播放」資料流程。example.com 是結構示意,並不是可用來源。正式實作時須接上自己的資料取得、錯誤處理與搜尋。

回傳型別依語言區分Java 與 QuickJS 回傳 JSON 字串;Python 直接回傳 dict,由 Chaquopy bridge 序列化,不要再呼叫 json.dumps。直播方法回傳原始文字。
com/github/catvod/spider/Demo.java
package com.github.catvod.spider;

import android.content.Context;
import com.github.catvod.crawler.Spider;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.List;

public class Demo extends Spider {
    private String ext;

    @Override
    public void init(Context context, String extend) {
        this.ext = extend;
    }

    @Override
    public String homeContent(boolean filter) throws Exception {
        JSONArray classes = new JSONArray()
            .put(new JSONObject().put("type_id", "movie")
            .put("type_name", "電影"));
        return new JSONObject().put("class", classes).toString();
    }

    @Override
    public String categoryContent(String tid, String pg, boolean filter,
            HashMap<String, String> extend) throws Exception {
        JSONArray list = new JSONArray().put(card("demo-1", "範例項目"));
        return new JSONObject().put("list", list)
            .put("pagecount", 1).toString();
    }

    @Override
    public String detailContent(List<String> ids) throws Exception {
        JSONObject item = card(ids.get(0), "範例項目")
            .put("vod_play_from", "Demo")
            .put("vod_play_url", "第 1 集$demo://episode/1");
        return new JSONObject().put("list",
            new JSONArray().put(item)).toString();
    }

    @Override
    public String playerContent(String flag, String id,
            List<String> vipFlags) throws Exception {
        return new JSONObject().put("parse", 0)
            .put("url", resolve(id)).toString();
    }

    private JSONObject card(String id, String name) throws Exception {
        return new JSONObject().put("vod_id", id).put("vod_name", name)
            .put("vod_pic", "https://example.com/poster.jpg");
    }

    private String resolve(String id) {
        return id.replace("demo://", "https://example.com/");
    }
}

Node.js 使用獨立的載入契約

一般 ./demo.js 仍由 QuickJS 執行。Node.js 必須以 index.js.md5 作為配置入口,同目錄提供 index.jsindex.config.js index.config.js.md5

App 啟動 bundle 後,讀取 /config 回應的 videodata.video,把 video.sites 映射為 node: 站點路由。爬蟲以 HTTP JSON 處理接在各站點路由後的 /init/home/category/detail/search/play

不要只替換 api 前綴Node.js 不是 QuickJS 的 export default 物件模式;單獨把普通配置的 api 改成 node:,不會完成 Node bundle 初始化。

方法與呼叫時機

下表的方法名稱以 Java/Python 與 QuickJS 為主;Node.js 的 HTTP 端點請依上方獨立段落,不是把所有方法名稱直接加在網址後。

方法何時呼叫責任主要回傳
init建立實例時;Node 重啟可再次呼叫讀取 ext、建立連線或快取
homeContent / home進入首頁分類與選填 filtersResult.class
homeVideoContent / homeVod首頁分類完成首頁推薦卡片Result.list
categoryContent / category分類、換頁、變更篩選該頁卡片與總頁數list + pagecount
detailContent / detail點擊卡片完整資訊與播放分組list[0]
searchContent / search搜尋搜尋結果,可支援頁碼list + pagecount
playerContent / play選擇集數最終 URL 與播放參數Result.url
liveContent / live載入直播配置TXT、M3U 或 Group JSON原始文字
proxy / localProxy本地代理收到請求依執行環境提供狀態、類型、內容與標頭陣列 / HTTP 回應
action自訂操作可由 Result 解析的結果JSON 字串 / Python dict
destroy重新載入或清除釋放執行緒、連線、引擎資源

Result 通用回傳格式

探索類

JSON / Resulthome · category
{
  "class": [
    { "type_id": "movie", "type_name": "電影" }
  ],
  "filters": {
    "movie": [
      {
        "key": "area",
        "name": "地區",
        "init": "",
        "value": [
          { "n": "全部", "v": "" },
          { "n": "台灣", "v": "tw" }
        ]
      }
    ]
  },
  "list": [
    {
      "vod_id": "demo-1",
      "vod_name": "範例項目",
      "vod_pic": "https://example.com/poster.jpg",
      "vod_remarks": "示意"
    }
  ],
  "pagecount": 1,
  "msg": "選填提示"
}

homeContent 使用 class / filters;列表、搜尋、詳情使用 list。filters 第一層鍵必須等於分類的 type_id 值,本例是 movie。

播放類

JSON / ResultplayerContent
{
  "url": "https://example.com/video/demo-1.m3u8",
  "parse": 0,
  "header": {
    "User-Agent": "ExampleClient/1.0",
    "Referer": "https://example.com/"
  },
  "format": "application/x-mpegURL",
  "subs": [
    {
      "url": "https://example.com/subs/demo-1.vtt",
      "name": "繁體中文",
      "lang": "zh-TW",
      "format": "text/vtt",
      "flag": 0
    }
  ],
  "danmaku": [
    { "url": "https://example.com/danmaku/demo-1.xml", "name": "示意彈幕" }
  ],
  "artwork": "https://example.com/poster.jpg",
  "desc": "示意播放資訊",
  "position": 120000
}

url 是核心欄位;parse=0 表示此欄位未要求解析,但 jx=1 或配置旗標比對仍可能觸發解析。

完整 Result 欄位

欄位類型用途預設
classClass[]首頁分類清單。[]
listVod[]首頁、分類與搜尋卡片;詳情取 list[0]。[]
filtersobject以分類 ID 為鍵,值為 Filter 陣列或單一 Filter。{}
urlstring | string[] | Url播放 URL 或多畫質清單,三種寫法見下方範例。
headerobject | string播放請求標頭;也接受 JSON 物件字串。整份為空時回退 Site.header,不逐鍵合併。{}
msgstring顯示提示文字;只有 code 為 0 時顯示。空字串
codeinteger訊息控制值,不是 HTTP 狀態碼;非 0 時隱藏 msg。0
danmakuDanmaku[] | string彈幕清單、單一 URL 或 JSON 陣列字串;仍受 App 彈幕載入設定控制。[]
subsSub[]外掛字幕清單。[]
playUrlstring解析前綴;json: 後接 JSON 解析端點,parse: 後接具名解析器。空字串
artworkstring更新播放頁顯示的圖片。空字串
jxFromstring擴展 JSON 解析結果回報的解析器名稱,用於成功提示;不是播放分組旗標。空字串
flagstring播放分組與解析比對旗標;未填時 App 補入呼叫 playerContent 時的 flag。由 App 補入
descstring清理後更新播放頁描述。空字串
formatstring媒體 MIME type 提示,請填完整類型,例如 application/x-mpegURL。
clickstringWebView 載入後執行的 JavaScript;Site.click 非空時優先。空字串
keystringApp 內部回填的 Site key;爬蟲不必提供,提供也會被覆寫。由 App 補入
positioninteger播放起點,單位毫秒;120000 為 2 分鐘。未填時不覆蓋既有播放進度。
pagecountinteger總頁數;0 或未填表示未知,仍可繼續載入更多。0
parseinteger1 要求解析;0 仍可能因 jx=1 或配置旗標比對進入解析。0
jxinteger1 要求進入解析,即使 parse=0。0
drmDrm播放 DRM 設定,與 Channel.drm 使用相同物件,見配置字典。

url 的多畫質寫法

單一 URL 見上方播放範例。陣列必須以「名稱、URL」成對排列,不是單純的 URL 清單;也可改用 values 物件。頂層 position 是毫秒,Url.position 則是從 0 起算的畫質索引,實際選取仍會由 App 調整。

名稱與 URL 成對陣列

JSON / ResultplayerContent
{
  "parse": 0,
  "url": [
    "主畫質", "https://example.com/video/main.m3u8",
    "低畫質", "https://example.com/video/low.m3u8"
  ]
}

Url 物件

JSON / ResultplayerContent
{
  "parse": 0,
  "url": {
    "values": [
      { "n": "主畫質", "v": "https://example.com/video/main.m3u8" },
      { "n": "低畫質", "v": "https://example.com/video/low.m3u8" }
    ],
    "position": 0
  }
}

drm 的子欄位請參考共用物件字典;詳情的播放分組見下方集數格式。

常用資料物件

Vod

vod_id
傳給 detailContent 的唯一值。
vod_name
顯示名稱。
vod_pic / vod_remarks
縮圖與角標。
type_name
分類文字。
vod_year / vod_area
年份與地區。
vod_director / vod_actor
人員資訊。
vod_content
詳細描述。
vod_play_from / vod_play_url
播放分組與集數,分隔方式見下節。
vod_tag / action
folder 表示資料夾;action 為自訂操作。
cate
提供此物件時也視為資料夾,可含 land、circle、ratio。
land / circle / ratio / style
卡片外觀覆蓋。

Class

type_id / id
傳給 categoryContent 的分類 ID。
type_name / name
分類顯示名稱。
type_flag
1 代表資料夾分類。
land / circle / ratio
該分類預設卡片樣式。
filters
也可在分類物件內提供 Filter 陣列。

Filter

key / name
傳入鍵與顯示名稱。
init
預設選項值。
value
選項陣列,例如 [{"n":"全部","v":""}];n 是顯示名稱,v 是傳入值。

Sub

url
字幕檔位置。
name / lang
顯示名稱與語言代碼。
format
字幕 MIME type,可省略並由 App 推定。
flag
0 交由 App 依字幕數量與偏好語言選擇;非零使用 Media3 選擇旗標:1 預設、2 強制、4 自動選擇,可位元組合。

Danmaku

url
彈幕資料位置。
name
選填顯示名稱。

播放分組與集數字串

$$$分隔播放分組
#分隔同組集數
$分隔集數名稱與 id
JSON / Vod fieldslist[] 中的 Vod 欄位片段
{
  "vod_play_from": "主線路$$$備用線路",
  "vod_play_url": "第 01 集$demo://ep/1#第 02 集$demo://ep/2$$$第 01 集$backup://ep/1"
}

使用者點擊某集後,$ 右側的 value 會成為 playerContent(flag, id, …)id

Proxy 回傳:依執行環境區分

JSON / Proxy 陣列proxy / localProxy
[
  200,
  "text/plain; charset=utf-8",
  "demo response",
  { "Cache-Control": "no-cache" }
]

上例是 QuickJS / Python 一般陣列模式:狀態碼、Content-Type、內容、選填標頭;第 5 格 1 表示 Base64 內容。Python 方法名稱是 localProxy,第三格也可直接回傳 bytes。

Java 的一般 Object[] 回傳格式中,第三格必須是 InputStream,第四格標頭選填,不能直接放字串或 bytes;也可只回傳一格 NanoHTTPD.Response。QuickJS 的 from=catvod 另有 JSON response 模式;Node Proxy 則直接轉接 HTTP 回應。

代理請求帶 siteKey 時會交給對應 Spider;JAR 模式沒有 siteKey 時,則找 com.github.catvod.spider.Proxy.proxy(Map) 靜態入口。