Files
xbpq/圣魂.py
T
2026-07-23 08:08:41 +02:00

443 lines
17 KiB
Python

# -*- coding: utf-8 -*-
# 少货影视 - TVbox标准版
import re
import sys
import json
import urllib.parse
import urllib.request
import ssl
import http.cookiejar
sys.path.append('..')
from base.spider import Spider
ssl._create_default_https_context = ssl._create_unverified_context
class Spider(Spider):
def init(self, extend=""):
self.extend = extend
self.host = 'https://shdy3.com'
self.cookie_jar = http.cookiejar.CookieJar()
self.cookie_handler = urllib.request.HTTPCookieProcessor(self.cookie_jar)
self.opener = urllib.request.build_opener(self.cookie_handler)
self.fetch(self.host)
def getName(self):
return "少货影视"
def isVideoFormat(self, url):
return False
def manualVideoCheck(self):
return False
def destroy(self):
pass
headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 9; ALN-AL00 Build/PQ3B.190801.05281406; wv) AppleWebKit/537.36',
'accept-language': 'zh-CN,zh;q=0.9',
'Referer': 'https://shdy3.com/',
}
classes_config = [
("电影", "1"),
("电视剧", "2"),
("动漫", "4"),
]
def fetch(self, url):
try:
req = urllib.request.Request(url, headers=self.headers)
with self.opener.open(req, timeout=15) as response:
content = response.read()
try:
return content.decode('utf-8')
except:
return content.decode('gbk', errors='ignore')
except Exception as e:
print(f"请求失败: {e}")
return ''
def clean_html(self, text):
if not text:
return ''
text = re.sub(r'<[^>]+>', '', text)
text = re.sub(r'\s+', ' ', text)
return text.strip()
# ==================== 首页 ====================
def homeContent(self, filter):
result = {}
classes = []
for name, tid in self.classes_config:
classes.append({"type_name": name, "type_id": tid})
filters = {
"1": [{"key": "cateId", "name": "类型", "value": [
{"n": "全部", "v": "1"},
{"n": "喜剧", "v": "6"},
{"n": "爱情", "v": "7"},
{"n": "恐怖", "v": "8"},
{"n": "动作", "v": "9"},
{"n": "科幻", "v": "10"},
{"n": "战争", "v": "11"},
{"n": "犯罪", "v": "12"},
{"n": "动画", "v": "13"},
{"n": "奇幻", "v": "14"},
{"n": "剧情", "v": "15"},
{"n": "冒险", "v": "16"},
{"n": "悬疑", "v": "17"},
{"n": "惊悚", "v": "18"},
{"n": "其他", "v": "20"},
]}],
"2": [{"key": "cateId", "name": "类型", "value": [
{"n": "全部", "v": "2"},
{"n": "国产剧", "v": "20"},
{"n": "TVB", "v": "21"},
{"n": "韩剧", "v": "22"},
{"n": "美剧", "v": "23"},
{"n": "日剧", "v": "24"},
{"n": "英剧", "v": "25"},
{"n": "台剧", "v": "26"},
{"n": "其他", "v": "27"},
]}],
}
result['filters'] = filters
html = self.fetch(self.host)
vlist = []
if html:
items = re.findall(
r'<a[^>]*class="myui-vodlist__thumb[^"]*"[^>]*href="([^"]+)"[^>]*title="([^"]+)"[^>]*data-original="([^"]+)"[^>]*>.*?<span[^>]*class="pic-text[^"]*"[^>]*>(.*?)</span>',
html, re.DOTALL
)
for href, title, pic, remark in items[:12]:
if href and title:
if not href.startswith('http'):
href = self.host + href if href.startswith('/') else self.host + '/' + href
if pic and not pic.startswith('http'):
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
if pic and pic.startswith('//'):
pic = 'https:' + pic
vlist.append({
'vod_id': href,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': self.clean_html(remark)
})
result['class'] = classes
result['list'] = vlist
return result
def homeVideoContent(self):
return {}
# ==================== 分类 ====================
def categoryContent(self, tid, pg, filter, extend):
page = int(pg) if pg else 1
if extend and 'cateId' in extend:
cateId = extend.get('cateId')
else:
cateId = tid
if page <= 1:
url = f"{self.host}/list/{cateId}.html"
else:
url = f"{self.host}/list/{cateId}-{page}.html"
html = self.fetch(url)
if not html:
return {'list': [], 'page': page, 'pagecount': 1}
videos = []
items = re.findall(
r'<div class="v_img">.*?<a href="([^"]+)" title="([^"]+)">.*?<img[^>]*data-original="([^"]+)".*?<div class="v_note">(.*?)</div>',
html, re.DOTALL
)
for href, title, pic, remark in items:
if href and title:
if not href.startswith('http'):
href = self.host + href if href.startswith('/') else self.host + '/' + href
if pic and not pic.startswith('http'):
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
if pic and pic.startswith('//'):
pic = 'https:' + pic
videos.append({
'vod_id': href,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': self.clean_html(remark)
})
if not videos:
items2 = re.findall(
r'<a href="(/movie/[^"]+)" title="([^"]+)">.*?<img[^>]*data-original="([^"]+)"',
html, re.DOTALL
)
for href, title, pic in items2:
if href and title:
href = self.host + href if href.startswith('/') else self.host + '/' + href
if pic and not pic.startswith('http'):
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
if pic and pic.startswith('//'):
pic = 'https:' + pic
videos.append({
'vod_id': href,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': ''
})
pagecount = page
page_match = re.search(r'<span>\d+/(\d+)</span>', html)
if page_match:
pagecount = int(page_match.group(1))
else:
if re.search(r'<a[^>]*href="[^"]*-\d+\.html"[^>]*>下一页</a>', html):
pagecount = page + 1
else:
pagecount = page
return {
'list': videos,
'page': page,
'pagecount': pagecount,
'limit': len(videos),
'total': pagecount * len(videos) if videos else 999999
}
# ==================== 详情 ====================
def detailContent(self, ids):
vid = ids[0] if ids else ''
if not vid:
return {'list': []}
if not vid.startswith('http'):
vid = self.host + vid if vid.startswith('/') else self.host + '/' + vid
html = self.fetch(vid)
if not html:
return {'list': []}
info = {'vod_id': vid}
# ====== 标题 ======
title_match = re.search(r'<h1[^>]*class="v_title[^"]*"[^>]*>(.*?)</h1>', html, re.DOTALL)
info['vod_name'] = self.clean_html(title_match.group(1)) if title_match else '未知'
# ====== 海报 ======
pic_match = re.search(r'<img[^>]*class="lazyload"[^>]*data-original="([^"]+)"', html)
if pic_match:
pic = pic_match.group(1)
if pic and not pic.startswith('http'):
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
if pic and pic.startswith('//'):
pic = 'https:' + pic
info['vod_pic'] = pic
else:
info['vod_pic'] = ''
# ====== 简介 ======
desc_match = re.search(r'<p[^>]*class="p_txt[^"]*"[^>]*>(.*?)</p>', html, re.DOTALL)
info['vod_content'] = self.clean_html(desc_match.group(1)) if desc_match else ''
# ====== 导演、演员、年份、类型、地区 ======
info['vod_director'] = ''
info['vod_actor'] = ''
info['vod_year'] = ''
info['type_name'] = ''
info['vod_area'] = ''
info_p = re.search(r'<p>(.*?)</p>', html, re.DOTALL)
if info_p:
text = info_p.group(1)
# 去除<a>标签
text = re.sub(r'<a[^>]*>.*?</a>', '', text)
text = self.clean_html(text)
# 用 / 分割
parts = text.split('/')
for part in parts:
part = part.strip()
if not part:
continue
# 导演
if '导演:' in part:
info['vod_director'] = part.replace('导演:', '').strip()
# 主演
elif '主演:' in part:
info['vod_actor'] = part.replace('主演:', '').strip()
# 如果只有纯文本且没有冒号
elif ':' not in part:
if re.match(r'^\d{4}$', part):
info['vod_year'] = part
elif part in ['大陆', '美国', '日本', '韩国', '英国', '法国', '德国', '意大利', '西班牙', '加拿大', '澳大利亚', '台湾', '香港', '泰国', '印度', '其他']:
info['vod_area'] = part
else:
info['type_name'] = part
# 如果没提取到年份,从其他地方匹配
if not info['vod_year']:
year_match = re.search(r'(\d{4})</a>', html)
if year_match:
info['vod_year'] = year_match.group(1)
# ====== 播放列表 ======
play_from = []
play_url = []
from_match = re.search(r'<ul[^>]*class="from_list"[^>]*>(.*?)</ul>', html, re.DOTALL)
link_match = re.search(r'<ul[^>]*class="play_list"[^>]*id="play_link"[^>]*>(.*?)</ul>', html, re.DOTALL)
if from_match and link_match:
names = re.findall(r'<li[^>]*>(.*?)</li>', from_match.group(1), re.DOTALL)
names = [self.clean_html(n) for n in names if n.strip()]
link_blocks = re.findall(r'<li[^>]*>(.*?)</li>', link_match.group(1), re.DOTALL)
if names and link_blocks and len(names) == len(link_blocks):
for idx, block in enumerate(link_blocks):
eps = re.findall(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', block, re.DOTALL)
source_name = names[idx] if idx < len(names) else f"线路{idx+1}"
episodes = []
for ep_href, ep_name in eps:
ep_name = ep_name.strip()
if ep_name and ep_href:
if not ep_href.startswith('/'):
ep_href = '/' + ep_href
num = re.sub(r'[^0-9]', '', ep_name)
if num:
num = int(num)
else:
num = len(episodes) + 1
episodes.append((num, f'{ep_name}${ep_href}'))
if episodes:
episodes.sort(key=lambda x: x[0])
play_from.append(source_name)
play_url.append('#'.join([ep[1] for ep in episodes]))
if not play_from:
panels = re.findall(
r'<div[^>]*class="myui-panel_bd[^"]*"[^>]*>.*?<ul[^>]*class="myui-content__list[^"]*"[^>]*>(.*?)</ul>',
html, re.DOTALL
)
titles = re.findall(r'<h3[^>]*class="title"[^>]*>(.*?)</h3>', html)
if panels:
for idx, panel in enumerate(panels):
eps = re.findall(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', panel, re.DOTALL)
source_name = titles[idx] if idx < len(titles) else f"线路{idx+1}"
episodes = []
for ep_href, ep_name in eps:
ep_name = ep_name.strip()
if ep_name and ep_href:
if not ep_href.startswith('/'):
ep_href = '/' + ep_href
num = re.sub(r'[^0-9]', '', ep_name)
if num:
num = int(num)
else:
num = len(episodes) + 1
episodes.append((num, f'{ep_name}${ep_href}'))
if episodes:
episodes.sort(key=lambda x: x[0])
play_from.append(source_name)
play_url.append('#'.join([ep[1] for ep in episodes]))
if not play_from:
all_eps = re.findall(r'<a[^>]*href="(/play/[^"]+)"[^>]*>([^<]+)</a>', html, re.DOTALL)
if all_eps:
episodes = []
for ep_href, ep_name in all_eps:
ep_name = ep_name.strip()
if ep_name and ep_href:
if not ep_href.startswith('/'):
ep_href = '/' + ep_href
num = re.sub(r'[^0-9]', '', ep_name)
if num:
num = int(num)
else:
num = len(episodes) + 1
episodes.append((num, f'{ep_name}${ep_href}'))
if episodes:
episodes.sort(key=lambda x: x[0])
play_from.append("骚火影视")
play_url.append('#'.join([ep[1] for ep in episodes]))
info["vod_play_from"] = '$$$'.join(play_from) if play_from else "骚火影视"
info["vod_play_url"] = '$$$'.join(play_url) if play_url else ""
return {'list': [info]}
# ==================== 搜索 ====================
def searchContent(self, key, quick, pg="1"):
page = int(pg) if pg else 1
encoded_key = urllib.parse.quote(key, safe='')
url = f"{self.host}/s----------.html?wd={encoded_key}"
html = self.fetch(url)
if not html:
return {'list': [], 'page': page, 'pagecount': 0, 'limit': 90, 'total': 0}
videos = []
li_pattern = r'<li[^>]*class="clearfix"[^>]*>(.*?)</li>'
items = re.findall(li_pattern, html, re.DOTALL)
for item in items:
pic = ''
pic_match = re.search(r'data-original="([^"]+)"', item)
if pic_match:
pic = pic_match.group(1)
title_match = re.search(r'<a[^>]*href="([^"]+)"[^>]*title="([^"]+)"', item)
if not title_match:
continue
href = title_match.group(1)
title = title_match.group(2).strip()
remark = ''
remark_match = re.search(r'<span[^>]*class="pic-text[^"]*"[^>]*>(.*?)</span>', item)
if remark_match:
remark = self.clean_html(remark_match.group(1))
if href and title:
if not href.startswith('http'):
href = self.host + href if href.startswith('/') else self.host + '/' + href
if pic and not pic.startswith('http'):
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
if pic and pic.startswith('//'):
pic = 'https:' + pic
videos.append({
'vod_id': href,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': remark
})
return {'list': videos, 'page': page, 'pagecount': 999, 'limit': 90, 'total': 999999}
# ==================== 播放 ====================
def playerContent(self, flag, id, vipFlags):
"""
播放接口 - 完全模仿JS版本的play函数
"""
if id.startswith('http'):
final_url = id
else:
if id.startswith('/'):
final_url = self.host.rstrip('/') + id
else:
final_url = self.host.rstrip('/') + '/' + id
return {"parse": 1, "url": final_url, "jx": 0}
def localProxy(self, param):
return None