Files
xvip/py/西瓜影视.py
2026-08-15 10:30:50 +02:00

337 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
# by @PyramidStore AutoGen
import re
import sys
sys.path.append('..')
import json
from urllib.parse import quote
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.nav_host = 'https://www.xiguadh.com'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
self.host = self._get_host()
def _get_host(self):
"""获取视频站点 URL,失败时从导航页获取"""
default_host = 'https://www.bzzdyy.com'
try:
r = self.fetch(default_host, headers=self.headers, timeout=10, verify=False)
if r.status_code == 200:
return default_host
except Exception:
pass
try:
r = self.fetch(self.nav_host, headers=self.headers, timeout=15, verify=False)
html = r.text
urls = re.findall(r'url:\s*["\']([^"\']+)["\']', html)
for url in urls:
if url.startswith('http') and 'xiguadh' not in url:
return url.rstrip('/')
except Exception:
pass
return default_host
def getName(self):
return '西瓜影院'
def isVideoFormat(self, url):
return False
def manualVideoCheck(self):
return True
def homeContent(self, filter):
try:
r = self.fetch(self.host, headers=self.headers, timeout=15, verify=False)
html = r.text
# 提取主要分类
nav_match = re.search(r'<ul class="stui-header__menu">(.*?)</ul>', html, re.DOTALL)
if nav_match:
nav_html = nav_match.group(1)
categories = re.findall(r'<li[^>]*><a href="/index.php/vod/type/id/(\d+)\.html">([^<]+)</a></li>', nav_html)
else:
categories = []
seen = set()
classes = []
for tid, name in categories:
if tid not in seen:
seen.add(tid)
classes.append({'type_name': name, 'type_id': tid})
if not classes:
raise Exception('No categories found')
# 提取首页推荐视频
videos = self._parse_vodlist(html)
except Exception:
classes = [
{'type_name': '电影', 'type_id': '20'},
{'type_name': '连续剧', 'type_id': '37'},
{'type_name': '动漫', 'type_id': '43'},
{'type_name': '综艺', 'type_id': '45'},
{'type_name': 'B站', 'type_id': '47'},
{'type_name': '人人专区', 'type_id': '60'},
]
videos = []
return {"class": classes, "list": videos}
def _parse_vodlist(self, html):
"""解析视频列表"""
items = re.findall(
r'<a class="stui-vodlist__thumb[^"]*"\s+href="([^"]+)"\s+title="([^"]*)"[^>]*data-original="([^"]*)"',
html
)
videos = []
for href, title, pic in items:
vod_id = re.search(r'/id/(\d+)\.html', href)
if vod_id:
vod_id = vod_id.group(1)
else:
continue
remark_match = re.search(r'<span class="pic-text text-right"><b>([^<]+)</b></span>',
html[html.find(href):html.find(href)+500] if href in html else '')
remark = remark_match.group(1) if remark_match else ''
if pic.startswith('/'):
pic = self.host + pic
videos.append({
'vod_id': vod_id,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': remark,
})
return videos
def homeVideoContent(self):
return ''
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg)
url = f'{self.host}/index.php/vod/type/id/{tid}/page/{pg}.html'
try:
r = self.fetch(url, headers=self.headers, timeout=15, verify=False)
html = r.text
items = re.findall(
r'<a class="stui-vodlist__thumb[^"]*"\s+href="([^"]+)"\s+title="([^"]*)"[^>]*data-original="([^"]*)"',
html
)
videos = []
for href, title, pic in items:
vod_id = re.search(r'/id/(\d+)\.html', href)
if vod_id:
vod_id = vod_id.group(1)
else:
continue
remark_match = re.search(r'<span class="pic-text text-right"><b>([^<]+)</b></span>',
html[html.find(href):html.find(href)+500] if href in html else '')
remark = remark_match.group(1) if remark_match else ''
if pic.startswith('/'):
pic = self.host + pic
videos.append({
'vod_id': vod_id,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': remark,
})
return {
"list": videos,
"page": pg,
"pagecount": 9999,
"limit": 90,
"total": len(videos),
}
except Exception as e:
return {"list": [], "page": pg, "pagecount": 1, "limit": 90, "total": 0}
def detailContent(self, ids):
try:
vod_id = ids[0] if isinstance(ids, list) else ids
url = f'{self.host}/index.php/vod/detail/id/{vod_id}.html'
r = self.fetch(url, headers=self.headers, timeout=15, verify=False)
html = r.text
title_match = re.search(r'<h1 class="title">([^<]+)</h1>', html)
title = title_match.group(1).strip() if title_match else ''
self._vod_name = title
pic_match = re.search(r'<img class="lazyload" data-original="([^"]*)"', html)
pic = pic_match.group(1) if pic_match else ''
if pic.startswith('/'):
pic = self.host + pic
info_match = re.search(r'类型:([^/]+)\s*/\s*地区:([^/]+)\s*/\s*年份:(\d+)', html)
type_name = info_match.group(1).strip() if info_match else ''
area = info_match.group(2).strip() if info_match else ''
year = info_match.group(3) if info_match else ''
remark_match = re.search(r'状态:<span[^>]*>([^<]+)</span>', html)
remark = remark_match.group(1).strip() if remark_match else ''
director_match = re.search(r'导演:(.*?)</p>', html, re.DOTALL)
director = ''
if director_match:
director = re.sub(r'<[^>]+>', '', director_match.group(1)).strip()
actor_match = re.search(r'主演:([^<]+)', html)
actor = actor_match.group(1).strip() if actor_match else ''
desc_match = re.search(r'<span class="detail-content"[^>]*>([^<]+)</span>', html)
desc = desc_match.group(1).strip() if desc_match else ''
play_from = []
play_url = []
source_tabs = re.findall(r'<li><a href="#playlist\d+"[^>]*>([^<]+)</a></li>', html)
for idx, source_name in enumerate(source_tabs):
source_id = idx + 1
episodes_match = re.search(
f'<div id="playlist{source_id}" class="tab-pane[^"]*"[^>]*>.*?<ul class="stui-content__playlist[^"]*"[^>]*>(.*?)</ul>',
html, re.DOTALL
)
if episodes_match:
episodes = re.findall(r'<a href="([^"]+)">([^<]+)</a>', episodes_match.group(1))
episode_list = []
for ep_url, ep_name in episodes:
episode_list.append(f'{ep_name}${self.host}{ep_url}')
play_from.append(source_name)
play_url.append('#'.join(episode_list))
vod_play_from = '$$$'.join(play_from) if play_from else '默认'
vod_play_url = '$$$'.join(play_url) if play_url else ''
vod = {
'vod_id': vod_id,
'vod_name': title,
'vod_pic': pic,
'vod_year': year,
'vod_area': area,
'vod_remarks': remark,
'vod_director': director,
'vod_actor': actor,
'vod_content': desc,
'vod_play_from': vod_play_from,
'vod_play_url': vod_play_url,
}
return {"list": [vod]}
except Exception as e:
return {"list": []}
def searchContent(self, key, quick, pg="1"):
pg = int(pg)
url = f'{self.host}/index.php/vod/search/wd/{quote(key)}.html'
try:
r = self.fetch(url, headers=self.headers, timeout=15, verify=False)
html = r.text
items = re.findall(
r'<a class="stui-vodlist__thumb[^"]*"\s+href="([^"]+)"\s+title="([^"]*)"[^>]*data-original="([^"]*)"',
html
)
videos = []
for href, title, pic in items:
vod_id = re.search(r'/id/(\d+)\.html', href)
if vod_id:
vod_id = vod_id.group(1)
else:
continue
remark_match = re.search(r'<span class="pic-text text-right"><b>([^<]+)</b></span>',
html[html.find(href):html.find(href)+500] if href in html else '')
remark = remark_match.group(1) if remark_match else ''
if pic.startswith('/'):
pic = self.host + pic
videos.append({
'vod_id': vod_id,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': remark,
})
return {
"list": videos,
"page": pg,
"pagecount": 9999,
"limit": 90,
"total": len(videos),
}
except Exception as e:
return {"list": [], "page": pg, "pagecount": 1, "limit": 90, "total": 0}
def _clean_vod_name(self, name):
import re
if not name:
return ''
cleaned = re.sub(r'第\s*\d+\s*[集話话章部期]', '', name)
cleaned = re.sub(r'EP\s*\d+', '', cleaned, flags=re.IGNORECASE)
cleaned = re.sub(r'全\d+集', '', cleaned)
cleaned = re.sub(r'更新至\d+集', '', cleaned)
cleaned = re.sub(r'\d+集全', '', cleaned)
cleaned = re.sub(r'[(].*?[)]', '', cleaned)
cleaned = re.sub(r'\s*-\s*.*$', '', cleaned)
cleaned = re.sub(r'\s+', ' ', cleaned)
cleaned = re.sub(r'^[\s\-_,.,。、]+|[\s\-_,.,。、]+$', '', cleaned)
return cleaned.strip()
def _build_danmaku_url(self, vod_name, vod_index=''):
import re
idx = 0
if vod_index:
s = str(vod_index).strip()
m = re.search(r'第\s*(\d+)\s*[集話话章部期]', s)
if m:
idx = int(m.group(1))
else:
m = re.search(r'(\d+)', s)
if m:
idx = int(m.group(1))
cleaned_name = self._clean_vod_name(vod_name)
params = []
if cleaned_name:
params.append(f'vodName={quote(cleaned_name)}')
params.append(f'vodIndex={idx}')
query = '&'.join(params)
return f'http://127.0.0.1:9978/proxy?do=appdanmu&{query}'
def playerContent(self, flag, id, vipFlags):
try:
ep_name = ''
vod_index = ''
if '$' in id:
parts = id.split('$', 1)
ep_name = parts[0]
url = parts[1] if len(parts) > 1 else ''
else:
url = id if id.startswith('http') else f'{self.host}{id}'
# 从 URL 中提取集数 (nid 参数)
nid_match = re.search(r'nid/(\d+)\.html', url)
if nid_match:
vod_index = nid_match.group(1)
danmaku_url = self._build_danmaku_url(self._vod_name, vod_index)
r = self.fetch(url, headers=self.headers, timeout=15, verify=False)
html = r.text
iframe_match = re.search(r'<iframe[^>]+src="([^"]+)"', html)
if iframe_match:
iframe_url = iframe_match.group(1)
if not iframe_url.startswith('http'):
iframe_url = self.host + iframe_url
return {
"parse": 1,
"url": iframe_url,
"header": self.headers,
"danmaku": danmaku_url
}
src_match = re.search(r'(https?://[^"\'<>\s]+\.m3u8[^"\'<>\s]*)', html)
if src_match:
return {
"parse": 0,
"url": src_match.group(1),
"header": self.headers,
"danmaku": danmaku_url
}
return {
"parse": 1,
"url": url,
"header": self.headers,
"danmaku": danmaku_url
}
except Exception as e:
danmaku_url = self._build_danmaku_url(self._vod_name, '')
return {"parse": 1, "url": id, "header": {}, "danmaku": danmaku_url}
def localProxy(self, param):
return [200, {}, ""]
def destroy(self):
pass