Files
sg/py/红果果.py
2026-08-14 11:25:10 +02:00

256 lines
9.5 KiB
Python

from base.spider import Spider
import requests
import re
import json
import urllib.parse
host = "https://www.hongguoguo.tv"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36',
'Referer': host
}
timeout = 10
# TVBox 兼容的 header 字符串格式
def _header_str():
return f"User-Agent={headers['User-Agent']}&Referer={headers['Referer']}"
class Spider(Spider):
def getName(self):
return "红果果短剧"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def homeContent(self, filter):
# 福利短剧(fuliduanju)需要会员权限,已移除
classes = [
{"type_id": "jingxuanduanju", "type_name": "精选短剧"}
]
return {"class": classes}
def homeVideoContent(self):
try:
r = requests.get(host, headers=headers, timeout=timeout)
return {'list': self._parse_list(r.text)}
except Exception as e:
print(f"[homeVideoContent] error: {e}")
return {'list': []}
def categoryContent(self, cid, pg, filter, ext):
page = int(pg) if pg else 1
if page == 1:
url = f"{host}/vod/show/id/{cid}.html"
else:
url = f"{host}/vod/show/id/{cid}/page/{page}.html"
try:
r = requests.get(url, headers=headers, timeout=timeout)
videos = self._parse_list(r.text)
return {
'list': videos,
'page': pg,
'pagecount': 9999,
'limit': 24,
'total': 999999
}
except Exception as e:
print(f"[categoryContent] error: {e}")
return {'list': []}
def detailContent(self, ids):
did = str(ids[0])
try:
play_page = f"{host}/vod/play/id/{did}/sid/1/nid/1.html"
r = requests.get(play_page, headers=headers, timeout=timeout)
text = r.text
# 权限检查
if '没有权限' in text or '升级会员' in text:
print(f"[detailContent] ID={did} 需要会员权限")
return {'list': []}
# 提取 player_aaaa(支持多种闭合标签)
player = None
for pattern in [
r'var player_aaaa=(\{.*?\})</script>',
r'var player_aaaa=(\{.*?\});',
r'player_aaaa=(\{.*?\})</script>',
]:
m = re.search(pattern, text, re.DOTALL)
if m:
try:
player = json.loads(m.group(1))
break
except json.JSONDecodeError:
continue
if not player:
print(f"[detailContent] ID={did} 未找到 player_aaaa")
return {'list': []}
vod_data = player.get('vod_data', {})
play_url = player.get('url', '').replace('\\/', '/')
play_from = player.get('from', 'm3u8')
if not play_url:
print(f"[detailContent] ID={did} 无播放地址")
return {'list': []}
# 提取封面
pic = ''
pic_m = re.search(r'"thumbnailUrl":"([^"]+)"', text)
if pic_m:
pic = pic_m.group(1).replace('\\/', '/')
# 提取简介
content = ''
desc_m = re.search(r'<p[^>]*class="[^"]*desc[^"]*"[^>]*>(.*?)</p>', text, re.DOTALL)
if desc_m:
content = re.sub(r'<[^>]+>', '', desc_m.group(1)).strip()
# 提取集数按钮
ep_pattern = r'href="/vod/play/id/' + did + r'/sid/(\d+)/nid/(\d+)\.html"[^>]*>([^<]*)</a>'
episodes = re.findall(ep_pattern, text)
play_from_list = []
play_url_list = []
if not episodes:
# 单集短剧
play_from_list.append(play_from)
vod_name = vod_data.get('vod_name', '第1集')
play_url_list.append(f"{vod_name}${play_url}")
else:
# 按线路分组
lines = {}
for sid, nid, name in episodes:
sid = int(sid)
if sid not in lines:
lines[sid] = []
lines[sid].append((int(nid), name.strip()))
for sid in sorted(lines.keys()):
line_eps = sorted(lines[sid], key=lambda x: x[0])
urls = []
line_name = None
for idx, (nid, name) in enumerate(line_eps):
if sid == 1 and nid == 1:
# 当前页面(第一集)
line_name = play_from
urls.append(f"{name}${play_url}")
else:
# 请求其他集页面
ep_url = f"{host}/vod/play/id/{did}/sid/{sid}/nid/{nid}.html"
try:
rr = requests.get(ep_url, headers=headers, timeout=8)
if '没有权限' in rr.text:
continue
for pattern in [
r'var player_aaaa=(\{.*?\})</script>',
r'var player_aaaa=(\{.*?\});',
r'player_aaaa=(\{.*?\})</script>',
]:
mm = re.search(pattern, rr.text, re.DOTALL)
if mm:
try:
ep_player = json.loads(mm.group(1))
ep_url_real = ep_player.get('url', '').replace('\\/', '/')
if ep_url_real:
urls.append(f"{name}${ep_url_real}")
if line_name is None:
line_name = ep_player.get('from', f'线路{sid}')
break
except json.JSONDecodeError:
continue
except Exception as e:
print(f"[detailContent] 获取集 {sid}/{nid} 失败: {e}")
continue
if urls and line_name:
play_from_list.append(line_name)
play_url_list.append("#".join(urls))
# 如果多线路处理失败,回退到单集
if not play_from_list:
play_from_list.append(play_from)
vod_name = vod_data.get('vod_name', '第1集')
play_url_list.append(f"{vod_name}${play_url}")
VOD = {
"vod_id": did,
"vod_name": vod_data.get('vod_name', ''),
"vod_pic": pic,
"vod_actor": vod_data.get('vod_actor', ''),
"vod_director": vod_data.get('vod_director', ''),
"type_name": vod_data.get('vod_class', ''),
"vod_remarks": "短剧",
"vod_content": content,
"vod_play_from": "$$$".join(play_from_list),
"vod_play_url": "$$$".join(play_url_list)
}
return {'list': [VOD]}
except Exception as e:
print(f"[detailContent] 获取详情失败: {e}")
return {'list': []}
def playerContent(self, flag, id, vipFlags):
print(f"[playerContent] flag={flag}, url={id[:80]}...")
return {
"parse": 0,
"playUrl": '',
"url": id,
"header": _header_str()
}
def searchContent(self, key, quick, pg=1):
page = int(pg) if pg else 1
keyword = urllib.parse.quote(key)
if page == 1:
url = f"{host}/vod/search.html?wd={keyword}"
else:
url = f"{host}/vod/search.html?wd={keyword}&page={page}"
try:
r = requests.get(url, headers=headers, timeout=timeout)
videos = self._parse_list(r.text)
return {
'list': videos,
'page': page,
'pagecount': 9999,
'limit': 24,
'total': 999999
}
except Exception as e:
print(f"[searchContent] error: {e}")
return {'list': []}
def _parse_list(self, text):
videos = []
pattern = (
r'<a[^>]*href="/vod/play/id/(\d+)/sid/1/nid/1\.html"[^>]*>'
r'.*?<div class="Image">.*?'
r'<div class="lazy" data-original="([^"]+)"[^>]*>.*?</div>'
r'.*?<span class="f">([^<]*)</span>'
r'.*?<span class="b">([^<]*)</span>'
r'.*?</div>.*?'
r'<h2 class="tim-title">([^<]*)</h2>'
r'.*?</a>'
)
matches = re.findall(pattern, text, re.DOTALL)
for m in matches:
vid, pic, score, status, name = m
videos.append({
"vod_id": vid,
"vod_name": name.strip(),
"vod_pic": pic.strip(),
"vod_remarks": f"{score.strip()} {status.strip()}",
"vod_content": ""
})
return videos