Files
tvboxzt/py/奕搜.py
T
2026-04-20 16:05:34 +08:00

241 lines
9.3 KiB
Python
Raw 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
import re
import sys
from urllib.parse import quote, urljoin
from base.spider import Spider as BaseSpider
sys.path.append("..")
class Spider(BaseSpider):
def __init__(self):
self.name = "奕搜"
self.host = "https://ysso.cc"
self.headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/145.0.0.0 Safari/537.36"
),
"Referer": self.host + "/",
}
self.classes = [
{"type_id": "dy", "type_name": "电影"},
{"type_id": "dsj", "type_name": "电视剧"},
{"type_id": "zy", "type_name": "综艺"},
{"type_id": "dm", "type_name": "动漫"},
{"type_id": "jlp", "type_name": "纪录片"},
{"type_id": "dj", "type_name": "短剧"},
]
def init(self, extend=""):
return None
def getName(self):
return self.name
def homeContent(self, filter):
return {"class": self.classes}
def homeVideoContent(self):
return {"list": []}
def _build_url(self, path):
return urljoin(self.host + "/", str(path or "").strip())
def _extract_remark_from_title(self, title_text):
parts = re.findall(r"\[(.*?)\]", str(title_text or ""))
for part in parts:
text = part.strip()
if text.startswith("更"):
match = re.search(r"更\s*([0-9]+)", text)
return f"更新至{match.group(1)}集" if match else text
for part in parts:
text = part.strip()
if text.endswith("分"):
return f"评分:{text[:-1].strip()}"
for part in parts:
text = part.strip()
if re.match(r"^\d{4}$", text):
return f"首播:{text}"
return ""
def _clean_title_text(self, title):
return re.sub(r"\s*\[.*?\]", "", str(title or "")).strip()
def _clean_text(self, text):
return re.sub(r"\s+", " ", str(text or "").replace("\xa0", " ")).strip()
def _request_html(self, path_or_url):
target = path_or_url if str(path_or_url).startswith("http") else self._build_url(path_or_url)
response = self.fetch(target, headers=dict(self.headers), timeout=10, verify=False)
if response.status_code != 200:
return ""
return response.text or ""
def _parse_list_boxes(self, html):
root = self.html(html)
if root is None:
return []
items = []
seen = set()
for node in root.xpath("//*[contains(@class,'list-boxes')]"):
title_href = "".join(node.xpath(".//a[contains(@class,'text_title_p')][1]/@href")).strip()
fallback_href = "".join(node.xpath(".//*[contains(@class,'left_ly')]//a[1]/@href")).strip()
vod_id = title_href or fallback_href
title = self._clean_text("".join(node.xpath(".//a[contains(@class,'text_title_p')][1]//text()")))
if not vod_id or not title or vod_id in seen:
continue
seen.add(vod_id)
pic = self._build_url("".join(node.xpath(".//img[contains(@class,'image_left')][1]/@src")).strip())
remark = self._extract_remark_from_title(title) or self._clean_text(
"".join(node.xpath(".//*[contains(@class,'list-actions')]//span[1]//text()"))
)
items.append(
{
"vod_id": vod_id,
"vod_name": self._clean_title_text(title),
"vod_pic": pic,
"vod_remarks": remark,
}
)
return items
def categoryContent(self, tid, pg, filter, extend):
page = int(pg)
items = self._parse_list_boxes(self._request_html(self._build_url(f"/{tid}.html?page={page}")))
return {"page": page, "limit": len(items) or 20, "total": page * 30 + (len(items) or 20), "list": items}
def searchContent(self, key, quick, pg="1"):
page = int(pg)
keyword = self._clean_text(key)
if not keyword:
return {"page": page, "total": 0, "list": []}
url = self._build_url(f"/search.html?keyword={quote(keyword)}&page={page}")
items = self._parse_list_boxes(self._request_html(url))
return {"page": page, "limit": len(items) or 20, "total": len(items), "list": items}
def _identify_disk(self, value):
text = str(value or "").strip().lower()
if "pan.baidu.com" in text:
return "baidu"
if "pan.quark.cn" in text:
return "quark"
if "drive.uc.cn" in text:
return "uc"
if "alipan.com" in text or "aliyundrive.com" in text:
return "aliyun"
if "pan.xunlei.com" in text:
return "xunlei"
return ""
def _disk_priority(self, name):
order = {"baidu": 1, "quark": 2, "uc": 3, "aliyun": 4, "xunlei": 5}
return order.get(name, 999)
def _extract_share_links(self, root):
links = []
seen = set()
for href in root.xpath("//a[@target='_blank']/@href"):
url = str(href or "").strip()
if not url.startswith(("http://", "https://")) or url in seen:
continue
seen.add(url)
links.append(url)
return links
def _build_play_data(self, share_links):
unique_links = []
seen = set()
for link in share_links:
if link and link not in seen:
seen.add(link)
unique_links.append(link)
grouped = {}
for link in unique_links:
disk = self._identify_disk(link)
if not disk:
continue
grouped.setdefault(disk, []).append(f"{disk}${link}")
if grouped:
names = sorted(grouped, key=self._disk_priority)
return {
"vod_play_from": "$$$".join(names),
"vod_play_url": "$$$".join("#".join(grouped[name]) for name in names),
}
if unique_links:
return {
"vod_play_from": "奕搜",
"vod_play_url": "#".join(
f"{index + 1}$push://{link}" for index, link in enumerate(unique_links)
),
}
return {"vod_play_from": "奕搜", "vod_play_url": ""}
def detailContent(self, ids):
result = {"list": []}
for raw_id in ids:
vod_id = str(raw_id or "").strip()
if not vod_id:
continue
html = self._request_html(self._build_url(vod_id))
root = self.html(html)
if root is None:
continue
title = self._clean_text("".join(root.xpath("//h1[contains(@class,'articl_title')][1]//text()")))
director_items = []
actor_items = []
for span in root.xpath("//*[@id='info']/span"):
key = self._clean_text(
"".join(span.xpath(".//*[contains(@class,'pl')][1]//text()"))
).replace("", "").replace(":", "")
names = [self._clean_text(text) for text in span.xpath(".//*[contains(@class,'attrs')]//a/text()")]
names = [name for name in names if name]
if "导演" in key or "编剧" in key:
for name in names:
if name not in director_items:
director_items.append(name)
if "主演" in key or "演员" in key:
for name in names:
if name not in actor_items:
actor_items.append(name)
content = ""
for text in root.xpath("//p/text()"):
cleaned = self._clean_text(text)
if len(cleaned) > 20:
content = cleaned
break
remarks = self._extract_remark_from_title(title)
body_text = self._clean_text("".join(root.xpath("//body//text()")))
code_match = re.search(r"提取码[:]\s*([a-zA-Z0-9]{4})", body_text)
if code_match:
remarks = f"{remarks} 提取码: {code_match.group(1)}".strip()
play = self._build_play_data(self._extract_share_links(root))
result["list"].append(
{
"vod_id": vod_id,
"vod_name": self._clean_title_text(title),
"vod_pic": self._build_url(
"".join(
root.xpath(
"(//*[contains(@class,'tc-box') and contains(@class,'article-box')]//img)[1]/@src"
)
).strip()
),
"vod_remarks": remarks,
"vod_director": ", ".join(director_items),
"vod_actor": ", ".join(actor_items),
"vod_content": content,
"vod_play_from": play["vod_play_from"],
"vod_play_url": play["vod_play_url"],
}
)
return result
def playerContent(self, flag, id, vipFlags):
raw = str(id or "").strip()
if raw.startswith("push://"):
return {"parse": 0, "url": raw[7:]}
return {"parse": 0, "url": raw}