用py 看看

This commit is contained in:
qist
2026-05-11 15:50:43 +08:00
parent 58523ef467
commit 3907dc932f
3 changed files with 295 additions and 2 deletions

293
py/ITalkBBTV.py Normal file
View File

@@ -0,0 +1,293 @@
# -*- coding: utf-8 -*-
# by @Qist
"""
ITalkBB TV - 海外华人影视
"""
import re
import json
import subprocess
import requests
from base.spider import Spider
class Spider(Spider):
def getName(self):
return 'ITalkBB TV'
def init(self, extend=""):
pass
def __init__(self):
self.host = 'https://www.italkbbtv.com'
self.header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0',
'Referer': 'https://www.italkbbtv.com/'
}
self.timeout = 20
self.class_names = '电视剧&电影&综艺&动画'.split('&')
self.class_urls = 'drama/62c670dc1dca2d424404499c&movie/62ac4ef36e0b5a13ed291544&variety/62ce7417c7daaa4a5d3fea14&cartoon/62ac4e6e4beefe53586478ca'.split('&')
self.key_map = {
'drama': 'dramaSeriesLists',
'movie': 'movieSeriesLists',
'variety': 'varietySeriesLists',
'cartoon': 'cartoonSeriesLists',
'shorts': 'shortsSeriesLists'
}
def get_nuxt_data(self, html):
"""从HTML中提取并解析__NUXT__数据"""
m = re.search(r'window\.__NUXT__=([\s\S]*?);</script>', html)
if not m:
return None
js_expr = m.group(1)
try:
result = subprocess.run(
['node', '-e', f'process.stdout.write(JSON.stringify({js_expr}))'],
capture_output=True, timeout=15
)
if result.returncode == 0 and result.stdout:
return json.loads(result.stdout)
except Exception as e:
print(f"get_nuxt_data error: {e}")
return None
def pick_pic(self, obj):
"""提取图片URL"""
if not obj:
return ''
images = obj.get('images', {}) or {}
poster = (images.get('poster') or [''])[0] if images.get('poster') else ''
landscape = (images.get('landscape') or [''])[0] if images.get('landscape') else ''
return poster or landscape or obj.get('imgUrl', '')
def join_stars(self, stars):
"""拼接演员/导演名"""
if not isinstance(stars, list):
return ''
return '/'.join([s.get('name', '') for s in stars if s.get('name')])
def make_vod_from_series(self, series, fallback_name=''):
"""从series对象构建vod字典"""
if not series:
return None
category_id = series.get('category_id', [])
if isinstance(category_id, list):
category_id = category_id[0] if category_id else ''
root_id = series.get('root_id', '')
series_id = series.get('id', '') or series.get('series_id', '')
route = 'shortsPlay' if root_id == '66b1d25cf2dde82c215f9b59' else 'play'
type_name = series.get('rootName', '') or series.get('categoryName', '')
remark = (series.get('latest_episode_name', '') or
series.get('latest_episode_shortname', '') or
('更新至' + str(series.get('episode_count', ''))) if series.get('episode_count') else '')
released_at = series.get('released_at')
vod_year = ''
if released_at:
try:
from datetime import datetime
vod_year = str(datetime.fromtimestamp(released_at).year)
except:
pass
stars = series.get('stars', {}) or {}
return {
'vod_id': route + '$' + series_id,
'vod_name': series.get('name', '') or fallback_name,
'vod_pic': self.pick_pic(series),
'vod_remarks': remark,
'vod_year': vod_year,
'type_name': type_name,
'vod_content': series.get('description', ''),
'vod_actor': self.join_stars(stars.get('actor', []) or []),
'vod_director': self.join_stars(stars.get('director', []) or [])
}
def make_vod_from_card(self, card):
"""从card对象构建vod字典搜索用"""
if not card:
return None
series = card.get('series') or card.get('target') or card
fallback = card.get('name', '') or card.get('title', '')
vod = self.make_vod_from_series(series, fallback)
if not vod:
return None
if not vod['vod_name']:
vod['vod_name'] = fallback
if not vod['vod_pic']:
vod['vod_pic'] = (self.pick_pic(card) or
((card.get('image', {}) or {}).get('poster', '')) or
((card.get('image', {}) or {}).get('landscape', '')))
if not vod['vod_remarks']:
vod['vod_remarks'] = card.get('description', '')
return vod
def unique_by_id(self, vod_list):
"""按vod_id去重"""
seen = {}
result = []
for v in vod_list:
vid = v.get('vod_id', '') if v else ''
if vid and vid not in seen:
seen[vid] = 1
result.append(v)
return result
def fetch(self, url):
"""发送GET请求"""
try:
resp = requests.get(url, headers=self.header, timeout=self.timeout)
resp.encoding = 'utf-8'
return resp.text
except Exception as e:
print(f"fetch error: {e}")
return ''
def homeContent(self, filter):
"""首页推荐"""
result = {'class': [], 'list': []}
for name, cid in zip(self.class_names, self.class_urls):
result['class'].append({'type_name': name, 'type_id': cid})
# 默认加载电视剧列表
url = f"{self.host}/drama/62c670dc1dca2d424404499c"
html = self.fetch(url)
nuxt = self.get_nuxt_data(html)
if nuxt:
try:
store = nuxt.get('state', {}).get('pageList', {}).get('dramaSeriesLists', {})
data = store.get('62c670dc1dca2d424404499c', {})
series_list = data.get('series', [])
vods = []
for s in series_list:
v = self.make_vod_from_series(s)
if v:
vods.append(v)
result['list'] = self.unique_by_id(vods)
except Exception as e:
print(f"homeContent parse error: {e}")
return result
def homeVideoContent(self):
"""首页视频内容"""
return {}
def categoryContent(self, tid, pg, filter, extend):
"""分类内容"""
result = {'list': [], 'page': int(pg), 'pagecount': 999, 'limit': 24, 'total': 999999}
parts = tid.split('/')
alias = parts[0]
cid = parts[1] if len(parts) > 1 else ''
url = f"{self.host}/{tid}"
if int(pg) > 1:
url += f"?page={pg}"
html = self.fetch(url)
nuxt = self.get_nuxt_data(html)
if nuxt:
try:
store = nuxt.get('state', {}).get('pageList', {}).get(self.key_map.get(alias, ''), {})
data = store.get(cid, {})
series_list = data.get('series', [])
vods = []
for s in series_list:
v = self.make_vod_from_series(s)
if v:
vods.append(v)
result['list'] = self.unique_by_id(vods)
except Exception as e:
print(f"categoryContent parse error: {e}")
return result
def detailContent(self, ids):
"""详情页"""
if not ids or not ids[0]:
return {'list': []}
vid = ids[0]
parts = vid.split('$')
route = parts[0] if len(parts) > 1 else 'play'
sid = parts[1] if len(parts) > 1 else vid
url = f"{self.host}/{route}/{sid}"
html = self.fetch(url)
nuxt = self.get_nuxt_data(html)
if not nuxt:
return {'list': []}
try:
play = nuxt.get('state', {}).get('play', {})
info = play.get('SeriesInfo', {})
eps = play.get('EpisodeList', [])
vod = self.make_vod_from_series(info) or {'vod_id': vid}
tabs = 'ITalkBB短剧' if route == 'shortsPlay' else 'ITalkBB'
play_urls = []
for ep in eps:
name = ep.get('shortname', '') or ep.get('name', '')
ep_id = ep.get('id', '')
play_urls.append(f"{name}${route}@{sid}@{ep_id}")
vod['vod_id'] = vid
vod['vod_name'] = info.get('name', '') or vod.get('vod_name', '')
vod['vod_pic'] = self.pick_pic(info) or vod.get('vod_pic', '')
vod['type_name'] = info.get('rootName', '') or info.get('categoryName', '') or vod.get('type_name', '')
vod['vod_content'] = info.get('description', '') or vod.get('vod_content', '')
stars = info.get('stars', {}) or {}
vod['vod_actor'] = self.join_stars(stars.get('actor', []) or [])
vod['vod_director'] = self.join_stars(stars.get('director', []) or [])
vod['vod_remarks'] = (info.get('latest_episode_name', '') or
info.get('latest_episode_shortname', '') or
vod.get('vod_remarks', ''))
vod['vod_play_from'] = tabs
vod['vod_play_url'] = '#'.join(play_urls)
return {'list': [vod]}
except Exception as e:
print(f"detailContent parse error: {e}")
return {'list': []}
def searchContent(self, key, quick, pg="1"):
"""搜索"""
url = f"{self.host}/?keyword={key}"
html = self.fetch(url)
nuxt = self.get_nuxt_data(html)
vod_list = []
if nuxt:
try:
data = nuxt.get('data', [{}])[0]
banners = data.get('bannerData', []) or []
groups = data.get('serverGroupDataList', []) or []
for card in banners:
v = self.make_vod_from_card(card)
if v:
vod_list.append(v)
for g in groups:
for card in (g.get('list', []) or []):
v = self.make_vod_from_card(card)
if v:
vod_list.append(v)
except Exception as e:
print(f"searchContent parse error: {e}")
# 客户端过滤
filtered = []
for v in vod_list:
if not v or not v.get('vod_name'):
continue
text = ' '.join([v.get('vod_name', ''), v.get('vod_remarks', ''),
v.get('vod_content', ''), v.get('type_name', '')])
if key in text:
filtered.append(v)
return {'list': self.unique_by_id(filtered)}
def playerContent(self, flag, id, vipFlags):
"""播放"""
parts = id.split('@')
route = parts[0] if len(parts) > 1 else 'play'
sid = parts[1] if len(parts) > 1 else ''
eid = parts[2] if len(parts) > 2 else ''
url = f"{self.host}/{route}/{sid}"
if eid:
url += f"?ep={eid}"
return {
'jx': 0,
'parse': 1,
'url': url,
'header': self.header
}
def localProxy(self, param):
return None

View File

@@ -14,7 +14,7 @@
{"key":"闪雷","name":"闪雷┃MP4","type":3,"api":"./lib/drpy2.min.js","ext":"./js/678.js","header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key":"fyyy","name":"飞宇影院","type":3,"api":"csp_XBPQ","searchable":1,"quickSearch":1,"filterable":1,"changeable":1,"ext":{"分类url":"http://ntfeiyu.com/nt/{cateId}/area/{area}/by/{by}/class/{class}/lang/{lang}/page/{catePg}/year/{year}.html","分类":"电影$1#电视剧$2#综艺$3#动漫$4"},"header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key":"cupfox_in","name":"茶杯狐┃cupfox.in","type":3,"api":"./lib/drpy2.min.js","ext":"./js/茶杯狐.js","searchable":1,"quickSearch":1,"filterable":0,"changeable":1,"header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key":"italkbbtv","name":"ITalkBB TV","type":3,"api":"./lib/drpy2.min.js","ext":"./js/ITalkBBTV.js","searchable":1,"quickSearch":1,"filterable":0,"changeable":1,"header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key":"italkbbtv","name":"ITalkBB TV","type":3,"api":"./py/ITalkBBTV.py","searchable":1,"quickSearch":1,"filterable":0,"changeable":1},
{"key":"qiletv","name":"奇乐影视","type":3,"api":"csp_XBPQ","searchable":1,"quickSearch":1,"filterable":1,"changeable":1,"ext":"./json/奇乐.json","header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},

View File

@@ -15,7 +15,7 @@
{"key":"新闪雷","name":"新闪雷┃MP4","type":3,"api":"./lib/drpy2.min.js","ext":"./js/新闪雷.js","header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key":"fyyy","name":"飞宇影院","type":3,"api":"csp_XBPQ","searchable":1,"quickSearch":1,"filterable":1,"changeable":1,"ext":{"分类url":"http://ntfeiyu.com/nt/{cateId}/area/{area}/by/{by}/class/{class}/lang/{lang}/page/{catePg}/year/{year}.html","分类":"电影$1#电视剧$2#综艺$3#动漫$4"},"header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key":"cupfox_in","name":"茶杯狐┃cupfox.in","type":3,"api":"./lib/drpy2.min.js","ext":"./js/茶杯狐.js","searchable":1,"quickSearch":1,"filterable":0,"changeable":1,"header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key":"italkbbtv","name":"ITalkBB TV","type":3,"api":"./lib/drpy2.min.js","ext":"./js/ITalkBBTV.js","searchable":1,"quickSearch":1,"filterable":0,"changeable":1,"header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key":"italkbbtv","name":"ITalkBB TV","type":3,"api":"./py/ITalkBBTV.py","searchable":1,"quickSearch":1,"filterable":0,"changeable":1},
{"key":"qiletv","name":"奇乐影视","type":3,"api":"csp_XBPQ","searchable":1,"quickSearch":1,"filterable":1,"changeable":1,"ext":"./json/奇乐.json","header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key": "电影天堂","name": "影视 | 电影天堂","type": 1,"api": "http://caiji.dyttzyapi.com/api.php/provide/vod","searchable": 1,"quickSearch": 1,"filterable": 1,"changeable": 1,"playerType":2},