From 7e2cf41405725e3cb0cdc5643779cc57e4a7743e Mon Sep 17 00:00:00 2001
From: qist <87984115@qq.com>
Date: Mon, 11 May 2026 17:14:27 +0800
Subject: [PATCH] fix
---
py/ITalkBBTV.py | 400 ++++++++++++++++++------------------------------
1 file changed, 147 insertions(+), 253 deletions(-)
diff --git a/py/ITalkBBTV.py b/py/ITalkBBTV.py
index f325dd16..0f180257 100644
--- a/py/ITalkBBTV.py
+++ b/py/ITalkBBTV.py
@@ -9,12 +9,6 @@ import requests
from datetime import datetime
from base.spider import Spider
-try:
- import js2py
- HAS_JS2PY = True
-except ImportError:
- HAS_JS2PY = False
-
class Spider(Spider):
def getName(self):
@@ -44,137 +38,76 @@ class Spider(Spider):
self.class_names = '电视剧&直播频道&短剧&综艺&电影&动画'.split('&')
self.class_urls = 'drama/62c670dc1dca2d424404499c&live/62ac4e2e4beefe535864769d&shorts/66b1d25cf2dde82c215f9b59&variety/62ce7417c7daaa4a5d3fea14&movie/62ac4ef36e0b5a13ed291544&cartoon/62ac4e6e4beefe53586478ca'.split('&')
- self.key_map = {
- 'drama': 'dramaSeriesLists',
- 'movie': 'movieSeriesLists',
- 'variety': 'varietySeriesLists',
- 'cartoon': 'cartoonSeriesLists',
- 'shorts': 'shortsSeriesLists'
- }
-
- def get_nuxt_data(self, html):
+ def parse_list_page(self, html):
+ """从列表页HTML提取vod列表"""
if not html:
- return None
+ return []
+ cards = re.findall(r']*href="(/(?:play|shortsPlay)/[a-f0-9]+)"[^>]*>(.*?)', html, re.DOTALL)
+ vods = []
+ seen = set()
+ for href, content in cards:
+ sid = href.split('/')[-1]
+ if sid in seen:
+ continue
+ seen.add(sid)
+ # 名称: span class="info-title" 或 img alt中的《》
+ name = ''
+ name_match = re.search(r'info-title[^>]*>([^<]+)', content)
+ if name_match:
+ name = name_match.group(1).strip()
+ if not name:
+ alt_match = re.search(r'alt="[^"]*[《]([^》]+)[》]', content)
+ if alt_match:
+ name = alt_match.group(1).strip()
+ # 图片
+ img_match = re.search(r'
]*src="([^"]+)"', content)
+ pic = img_match.group(1) if img_match else ''
+ # 备注 (集数)
+ remarks = ''
+ ep_match = re.search(r'(全\d+集|更新至\d+集)', content)
+ if ep_match:
+ remarks = ep_match.group(1)
+ if name:
+ vods.append({
+ 'vod_id': 'play$' + sid,
+ 'vod_name': name,
+ 'vod_pic': pic,
+ 'vod_remarks': remarks
+ })
+ return vods
+
+ def parse_live_page(self, html):
+ """从直播页HTML提取频道列表"""
+ if not html:
+ return []
m = re.search(r'window\.__NUXT__=([\s\S]*?);', html)
if not m:
- return None
- js_expr = m.group(1)
- try:
- result = js2py.eval_js(js_expr)
- return result.to_dict()
- except Exception as e:
- print(f"get_nuxt_data error: {e}")
- return None
-
- def pick_pic(self, obj):
- if not obj:
- return ''
- images = obj.get('images', {}) or {}
- poster_list = images.get('poster', []) or []
- landscape_list = images.get('landscape', []) or []
- poster = poster_list[0] if poster_list else ''
- landscape = landscape_list[0] if landscape_list 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=''):
- if not series:
- return None
- 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:
- 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_channel(self, ch):
- if not ch:
- return None
- return {
- 'vod_id': 'live@' + (ch.get('id', '') or ''),
- 'vod_name': ch.get('name', ''),
- 'vod_pic': self.pick_pic(ch),
- 'vod_remarks': ch.get('categoryName', '') or ch.get('rootName', ''),
- 'vod_year': '',
- 'type_name': ch.get('categoryName', ''),
- 'vod_content': '',
- 'vod_actor': '',
- 'vod_director': ''
- }
-
- def make_vod_from_card(self, card):
- 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):
- 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
+ return []
+ js = m.group(1)
+ # 提取直播频道: id:"xxx" ... name:"CCTV-1"
+ channels = re.findall(r'\{[^{}]*id:"([a-f0-9]+)"[^{}]*name:"([^"]+)"', js)
+ vods = []
+ seen = set()
+ for ch_id, name in channels:
+ if ch_id in seen:
+ continue
+ seen.add(ch_id)
+ if any(kw in name for kw in ['CCTV', '卫视', '频道', '新闻', '综合', '影视', '体育', '少儿', '科教', '农业']):
+ vods.append({
+ 'vod_id': 'live@' + ch_id,
+ 'vod_name': name,
+ 'vod_pic': '',
+ 'vod_remarks': '直播'
+ })
+ return vods
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}")
+ html = self.fetch(self.host + '/drama/62c670dc1dca2d424404499c')
+ result['list'] = self.parse_list_page(html)
return result
def homeVideoContent(self):
@@ -184,49 +117,18 @@ class Spider(Spider):
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 ''
- # 直播频道特殊处理
- if alias == 'live':
- return self._live_category(tid, pg)
-
- url = f"{self.host}/{tid}"
+ url = self.host + '/' + tid
if int(pg) > 1:
- url += f"?page={pg}"
+ url += '?page=' + str(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 _live_category(self, tid, pg):
- result = {'list': [], 'page': int(pg), 'pagecount': 1, 'limit': 100, 'total': 0}
- url = f"{self.host}/{tid}"
- html = self.fetch(url)
- nuxt = self.get_nuxt_data(html)
- if nuxt:
- try:
- channels = nuxt.get('state', {}).get('pageList', {}).get('liveChannelsList', []) or []
- vods = []
- for ch in channels:
- v = self.make_vod_from_channel(ch)
- if v:
- vods.append(v)
- result['list'] = vods
- result['total'] = len(vods)
- except Exception as e:
- print(f"_live_category parse error: {e}")
+ if alias == 'live':
+ result['list'] = self.parse_live_page(html)
+ result['total'] = len(result['list'])
+ result['pagecount'] = 1
+ else:
+ result['list'] = self.parse_list_page(html)
return result
def detailContent(self, ids):
@@ -236,111 +138,103 @@ class Spider(Spider):
# 直播频道
if vid.startswith('live@'):
- ch_id = vid.replace('live@', '')
- return self._live_detail(ch_id)
+ return {'list': [{'vod_id': vid, 'vod_name': vid.replace('live@', ''),
+ 'vod_play_from': 'ITalkBB直播', 'vod_play_url': '直播$' + vid.replace('live@', '')}]}
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}"
+ url = 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}")
+ if not html:
return {'list': []}
- def _live_detail(self, ch_id):
- # 直播频道直接返回播放地址
- return {
- 'list': [{
- 'vod_id': 'live@' + ch_id,
- 'vod_name': ch_id,
- 'vod_play_from': 'ITalkBB直播',
- 'vod_play_url': f'直播${ch_id}'
- }]
+ # 从详情页提取信息
+ m = re.search(r'window\.__NUXT__=([\s\S]*?);', html)
+ if not m:
+ return {'list': []}
+ js = m.group(1)
+
+ # 提取系列名称
+ name = ''
+ name_match = re.search(r'SeriesInfo:\{[^}]*name:"([^"]+)"', js)
+ if name_match:
+ name = name_match.group(1)
+
+ # 提取描述
+ desc = ''
+ desc_match = re.search(r'SeriesInfo:\{[^}]*description:"([^"]*)"', js)
+ if desc_match:
+ desc = desc_match.group(1)
+
+ # 提取图片
+ pic = ''
+ pic_match = re.search(r'SeriesInfo:\{[^}]*poster:\["([^"]+)"', js)
+ if pic_match:
+ pic = pic_match.group(1).replace('\\u002F', '/')
+
+ # 提取演员
+ actor = ''
+ actor_matches = re.findall(r'actor:\[\{[^}]*name:"([^"]+)"', js)
+ if actor_matches:
+ actor = '/'.join(actor_matches[:5])
+
+ # 提取导演
+ director = ''
+ dir_match = re.findall(r'director:\[\{[^}]*name:"([^"]+)"', js)
+ if dir_match:
+ director = '/'.join(dir_match)
+
+ # 提取剧集列表
+ eps = re.findall(r'\{[^{}]*id:"([a-f0-9]+)"[^{}]*name:"([^"]*)"[^{}]*shortname:"([^"]*)"', js)
+ if not eps:
+ eps = re.findall(r'\{[^{}]*shortname:"([^"]*)"[^{}]*id:"([a-f0-9]+)"', js)
+ eps = [(eid, sn, sn) for sn, eid in eps]
+ if not eps:
+ # 更宽松的匹配
+ eps = re.findall(r'id:"([a-f0-9]{24})"[^}]*name:"([^"]*)"', js)
+
+ tabs = 'ITalkBB短剧' if route == 'shortsPlay' else 'ITalkBB'
+ play_urls = []
+ for ep_id, ep_name in eps[:50]:
+ display = ep_name or ep_id[-4:]
+ play_urls.append(display + '$' + route + '@' + sid + '@' + ep_id)
+
+ vod = {
+ 'vod_id': vid,
+ 'vod_name': name,
+ 'vod_pic': pic,
+ 'vod_remarks': '',
+ 'vod_year': '',
+ 'type_name': '',
+ 'vod_content': desc,
+ 'vod_actor': actor,
+ 'vod_director': director,
+ 'vod_play_from': tabs,
+ 'vod_play_url': '#'.join(play_urls)
}
+ return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
- url = f"{self.host}/?keyword={key}"
+ url = 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)}
+ vods = self.parse_list_page(html)
+ # 客户端过滤
+ filtered = [v for v in vods if key in (v.get('vod_name', '') + v.get('vod_remarks', ''))]
+ return {'list': filtered}
def playerContent(self, flag, id, vipFlags):
- # 直播频道
if id.startswith('live@'):
- ch_id = id.replace('live@', '')
- return {
- 'parse': 0,
- 'url': f'{self.host}/live/{ch_id}',
- 'header': self.header,
- 'playUrl': ''
- }
+ return {'parse': 0, 'url': self.host + '/live/' + id.replace('live@', ''),
+ 'header': self.header, 'playUrl': ''}
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}"
+ url = self.host + '/' + route + '/' + sid
if eid:
- url += f"?ep={eid}"
- return {
- 'parse': 1,
- 'url': url,
- 'header': self.header,
- 'playUrl': ''
- }
+ url += '?ep=' + eid
+ return {'parse': 1, 'url': url, 'header': self.header, 'playUrl': ''}
def fetch(self, url):
try: