TVBox Python爬虫接口适配指南

在TVBox中,通过 chaquopy 实现了基于 python 爬虫的接口抽象。目前在一些更新比较频繁的类TVBox软件中均已支持,例如 Fongmi 影视、影视仓、EasyBox等。本文以 Fongmi 影视为参考,详细解读其中 Python 爬虫接口如何适配,各函数接口输入输出参数含义,使用场景等。

相关源代码见 https://github.com/FongMi/TV/tree/fongmi/chaquo/src/main/python/base/spider.py

以下内容由AI代码分析,人工验证测试后所总结。仅供参考。

init接口

1
2
def init(self, extend=""):
pass

init 接口主要负责初始化。

extend 入参为字符串格式,内容来自 json 配置文件中关联该py脚本的站点下的 ext 。在 json 配置文件中,你可以根据脚本需要完全自定义 ext 中的内容,例如传递网址、登录的cookie参数等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{

"key": "py_butailing",

"name": "不太灵",

"type": 3,

"api": "./api/butailing.py",

"searchable": 1,

"changeable": 1,

"quickSearch": 1,

"filterable": 1,

"playerType": 2,

"ext": {

"text":"ext-test",

"data":[1,2,3,4,5]

},

"style": {

"type": "rect",

"ratio":0.75

}

}

在实际运行时,最先被调用的就是 init 接口。因此你可以将一些通用的参数的初始化放在这个接口里,如 host 地址。

homeContent接口

1
2
def homeContent(self, filter):
pass

homeContent 接口主要负责处理站点分类、筛选以及首页视频列表。

在 TVBox 中,一个站点通常在顶部有多个可供用户选择的分类按钮,例如电影、电视、综艺等等。而在这些分类选中后,还可以提供筛选功能,例如电影可以选择动作片、恐怖片、科幻片等等。在这些分类还未选中时,首页还需要提供用于展示的视频列表信息。

filter 入参为布尔型变量,其内容来自 json 配置中的 filterable 参数,0 表示不可筛选,1 表示可筛选。

homeContent 返回的是一个 json,其格式参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
{

"class": [

{

"type_name": "电影",

"type_id": 20

},

{

"type_name": "电视剧",

"type_id": 21

}

],

"filters": {

"20": [

{

"key": "class",

"name": "类型",

"value": [

{

"n": "Netflix",

"v": "Netflix"

},

{

"n": "喜剧",

"v": "喜剧"

}

]

},

{

"key": "area",

"name": "地区",

"value": [

{

"n": "大陆",

"v": "大陆"

},

{

"n": "香港",

"v": "香港"

}

]

}

],

"21": [

{

"key": "class",

"name": "类型",

"value": [

{

"n": "Netflix",

"v": "Netflix"

},

{

"n": "古装",

"v": "古装"

}

]

},

{

"key": "area",

"name": "地区",

"value": [

{

"n": "大陆",

"v": "大陆"

},

{

"n": "韩国",

"v": "韩国"

}

]

}

]

},

"list": [

{

"vod_id": 69780,

"vod_name": "绝世战魂",

"vod_pic": "https://img.ffzy888.com/upload/vod/20221114-1/4f2a3910e3f0ad0f1878eb9932ff6a33.jpg",

"vod_remarks": "更新至127集"

},

{

"vod_id": 148464,

"vod_name": "地狱旅馆",

"vod_pic": "https://img.lzzyimg.com/upload/vod/20220909-1/6a60270e5b91796482f1557d814b4fa0.jpg",

"vod_remarks": "更新至04集"

}

]

}

json 内容一共包括三大块:class、filters、list。

class 为一数组,子元素由 type_name 和 type_id 构成,type_name 为分类名称,最终显示在顶部的分类选项就是它;type_id 是用于后续识别分类的值,一般可将其与站点网址中的分类路径相关联。

filters 是一个字典,子元素的键名与class中子元素的 type_id 对应,表示在该分类下所产生的筛选选项。子元素是一个列表,因为筛选时可以有多个筛选维度。对于其中某一个维度,其内容由 key、name 、value 构成。其中 key 用于后续识别当前筛选维度,name 用于显示,value 是一个列表,筛选时有多个选项,这些选项一般是互斥的(即只能选一个)。各个选项由 n 和 v 构成。n 表示名称,用于显示,v 为用于识别选项的值。

list 是一个列表,其子元素即为每个视频的信息,通常这里的信息是简略的,只要包括链接详情页的地址id,视频标题,视频封面,视频标签(如豆瓣评分)等。这里的 vod 形式实际上参考的是 苹果cms 。

TVBox界面示例

homeVideoContent接口

1
2
def homeVideoContent(self):
pass

homeVideoContent 主要负责处理首页的推荐视频列表,返回的是一个json,具体格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{

"list": [

{

"vod_id": 69780,

"vod_name": "绝世战魂",

"vod_pic": "https://img.ffzy888.com/upload/vod/20221114-1/4f2a3910e3f0ad0f1878eb9932ff6a33.jpg",

"vod_remarks": "更新至127集"

},

{

"vod_id": 148464,

"vod_name": "地狱旅馆",

"vod_pic": "https://img.lzzyimg.com/upload/vod/20220909-1/6a60270e5b91796482f1557d814b4fa0.jpg",

"vod_remarks": "更新至04集"

}

]

}

可以看到与 homeContent中的list类似,去掉了 class 和 filters。

categoryContent接口

1
2
def categoryContent(self, tid, pg, filter, extend):
pass

categoryContent 主要负责处理的是分类、筛选、翻页等动作发生时,页面中所需要刷新的视频列表信息。

tid 入参是 type_id 的缩写,格式为字符串,当分类按钮被选中时, tid的值也就更新为 type_id 的值。除了这个场景外,当存在其的非分类动作产生的跳转时,tid的值会更新为跳转时由vod传递过来的 vod_id 值。

pg 入参是 page 的缩写,格式为字符串,视频列表是按页加载的,对应网站中的某一页内容,当用户滚动视频列表到底部时,会触发加载新的一页,pg自增,再次调用该接口,从而获得新的视频内容。这就类似于懒加载。

filter 入参为布尔型变量,表示是否支持筛选,内容来自 json 配置中的 filterable 参数,0 表示不可筛选,1 表示可筛选。

extend 入参是一个字典,用户点击某个筛选按钮,则这个这个按钮的 key 会被记录,value 则来自value列表中所选中的选项对应的 v 值。

通过以上参数,合理解析由用户操作所产生的包括跳转地址、分页、分类筛选等信息,构造目标网页地址,然后再自行编写网页内容爬取函数,最后返回视频列表信息。

函数最终返回的结果为 json,格式如下:

{

"list": [

    {

        "vod_id": 69780,

        "vod_name": "绝世战魂",

        "vod_pic": "https://img.ffzy888.com/upload/vod/20221114-1/4f2a3910e3f0ad0f1878eb9932ff6a33.jpg",

        "vod_remarks": "更新至127集"

    },

    {

        "vod_id": 148464,

        "vod_name": "地狱旅馆",

        "vod_pic": "https://img.lzzyimg.com/upload/vod/20220909-1/6a60270e5b91796482f1557d814b4fa0.jpg",

        "vod_remarks": "更新至04集"

    }

],

"page": "1",

"pagecount": 9999,

"limit": 90,

"total": 999999

}

其中list为视频列表,剩下的是关于页码的信息,page 表示当前是第几页,pagecount 表示一共有多少页,limit 表示超时,total表示一共多少页。

这里值得一提的是,该接口实际上可以处理类似文件夹多层级打开的功能。视频信息完整的可供解析的参数包括:

{

"vod_id": "",

"vod_name": "",

"type_name": "",

"vod_pic": "",

"vod_remarks": "",

"vod_year": "",

"vod_area": "",

"vod_director": "",

"vod_actor": "",

"vod_content": "",

"vod_play_from": "",

"vod_play_url": "",

"vod_wallpaper": "",

"vod_tag": "",

"action": "",

"cate": "",

"style": "",

"land": "",

"circle": "",

"ratio": ""

}

其中 vod_tag 有两种选项:file 、folder,如果是folder则表示点击该视频封面后,会将vod_id 作为新的 tid 继续调用 本接口,从而形成多层点击,直到返回的视频 vod_tag 为 file,再点击时才调用 detailContent 。
所以在处理入参,构造待解析的网址时,需要处理好url合成的逻辑,因为这里可能存在很多场景共用这一个接口的问题。

另外视频的封面尺寸实际上可以在本接口中直接调整,通过style参数控制,具体参数包括:

//直式

{

“style”: {

"type": "rect"

}

}

//橫式

{

“style”: {

"type": "rect",

"ratio": 1.33

}

}

//正方

{

“style”: {

"type": "rect",

"ratio": 1

}

}

//正圓

{

“style”: {

"type": "oval"

}

}

//橢圓

{

“style”: {

"type": "oval",

"ratio": 1.1

}

}

detailContent接口

1
2
def detailContent(self, ids):
pass

detailContent 主要负责处理某个视频页网址的视频详细信息爬取。

ids 入参默认是一个列表,但是一般我们只取第一个元素作为视频页地址。

在这个接口中,需要获取视频的详细信息,最终接口返回 json,格式如下:

{

"list": [

    {

        "vod_name": "爱的小麻烦",

        "vod_pic": "https://api.codetabs.com/v1/proxy/?quest=https://img.cfwebname.top/i/2025/08/15/689e94507a6a5.png",

        "vod_year": "2020",

        "vod_director": "罗伯托·菲斯科",

        "vod_actor": "阿方索·杜萨勒,瑞吉娜·布兰登,Francesca Mercadante",

        "type_name": "喜剧",

        "vod_area": "墨西哥",

        "vod_content": "  他全心爱上的女子竟然讨厌小孩!这下如何是好?9 岁的女儿提议假装成他的妹妹,应该不会有什麽问题吧?",

        "vod_play_from": "WEB-1080P$$$其他",

        "vod_play_url": "磁力$magnet:?xt=urn:btih:69A166E5CFACB8C24B2280D96395F15556737024#磁力$magnet:?xt=urn:btih:74EE1CA8F44788A68895C3CDA07C9F3A68D3B66A#磁力$magnet:?xt=urn:btih:A866D12DC828C7C0DD9AB31A348186F2E4025ED0$$$磁力$magnet:?xt=urn:btih:42F4AF63FE801EEC9F9941BE13F1A743DC0A379A"

    }

]

}

视频资源

这里要重点解释的是 vod_play_from 和 vod_play_url 这两个是绑定在一起的,其内容语法来自 苹果cms 。在苹果cms中,通过下面这种语法表示一个视频资源:

视频名称$视频链接

当有多个视频资源时(通常在电视剧资源时表示视频集数),通过#串联。不同线路通过 $$$ 串联。上述vod_play_from 表示路线,vod_play_url 表示该路线下对应的资源。这里的视频链接可以是直接播放的直链,也可以是某个可解析出视频直链的地址,这样在 playerContent 接口中需要写解析方法。
富文本

在 FongMi 影视中,允许在 vod_director、vod_actor、vod_content等内容中插入富文本链接,软件中带有富文本链接的内容会变黄色,带有下划线。富文本格式如下:

content = f'[a=cr:{{"id":"{href}","name":"{name}"}}/]{name}[/a]'

其中href为跳转的链接,类似 vod_id 功能,name用于显示。

当点击该富文本链接时,会将href作为跳转tid,再次调用 categoryContent 。

playerContent接口

1
2
def playerContent(self, flag, id, vipFlags):
pass

playerContent 主要负责处理播放视频时视频的资源地址解析。

id 入参即为detailContent 中传入的某个视频链接地址。

flag 入参即为detailContent 中传入的某个视频链接对应的vod_play_from参数。

vipFlags 入参目前尚未看到有开源的接口源有用到的。

接口返回的结果为json,格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{

"jx":1,

"parse": 0,

"url": "https://tyyszywvod5.com/videos/202507/03/68669eb47ea4833d7f00048e/ae77de/index.m3u8",

"header": {

"User-Agent": "okhttp/3.14.9"

}

}

其中 jx 表示用第几个解析接口,parse表示是否需要解析。url为需要播放的地址。header 可有可无。

searchContent接口

1
2
def searContent(self, key, quick, pg="1"):
pass

searchContent 主要处理站点内容搜索返回视频列表。

key 入参即为搜索框输入的文本内容。

quick 为布尔型变量,表示是否启用快速搜索。

pg 表示页码,可能存在多页搜索结果。

该接口与前面的接口类似,只是从搜索页的网址中获取视频列表,返回结果也为json,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{

"list": [

{

"vod_name": "爱的小麻烦",

"vod_pic": "https://api.codetabs.com/v1/proxy/?quest=https://img.cfwebname.top/i/2025/08/15/689e94507a6a5.png",

"vod_year": "2020",

"vod_director": "罗伯托·菲斯科",

"vod_actor": "阿方索·杜萨勒,瑞吉娜·布兰登,Francesca Mercadante",

"type_name": "喜剧",

"vod_area": "墨西哥",

"vod_content": "  他全心爱上的女子竟然讨厌小孩!这下如何是好?9 岁的女儿提议假装成他的妹妹,应该不会有什麽问题吧?",

"vod_play_from": "WEB-1080P$$$其他",

"vod_play_url": "磁力$magnet:?xt=urn:btih:69A166E5CFACB8C24B2280D96395F15556737024#磁力$magnet:?xt=urn:btih:74EE1CA8F44788A68895C3CDA07C9F3A68D3B66A#磁力$magnet:?xt=urn:btih:A866D12DC828C7C0DD9AB31A348186F2E4025ED0$$$磁力$magnet:?xt=urn:btih:42F4AF63FE801EEC9F9941BE13F1A743DC0A379A"

}

],

"page":"1"

}

其他

其他还有很多接口,用于辅助的,例如日志打印的log,结合Fongmi的调试功能可以打印一些关键信息用于脚本开发调试。

再比如缓存相关的,getCache、setCache、delCache。获取网页内容的fetch等等。

以上辅助接口功能单一比较好理解,在此不再赘述。
参考资料

https://gitee.com/PizazzXS/another-d/tree/master/movie/py

https://www.cnblogs.com/gshang/p/19085715/tvbox_py_api