Sync all projects
This commit is contained in:
@@ -879,6 +879,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/四虎视频.py"
|
||||
},
|
||||
{
|
||||
"key": "lyt",
|
||||
"name": "🐬撸一天.py|🔞[成人]",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/撸一天.py"
|
||||
},
|
||||
{
|
||||
"key": "scpd",
|
||||
"name": "🐬香肠派对.py|🔞[成人]",
|
||||
|
||||
@@ -467,6 +467,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/四虎视频.py"
|
||||
},
|
||||
{
|
||||
"key": "lyt",
|
||||
"name": "🐬撸一天.py|🔞[成人]",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/撸一天.py"
|
||||
},
|
||||
{
|
||||
"key": "scpd",
|
||||
"name": "🐬香肠派对.py|🔞[成人]",
|
||||
|
||||
@@ -807,6 +807,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/四虎视频.py"
|
||||
},
|
||||
{
|
||||
"key": "lyt",
|
||||
"name": "🐬撸一天.py|🔞",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/撸一天.py"
|
||||
},
|
||||
{
|
||||
"key": "scpd",
|
||||
"name": "🐬香肠派对.py|🔞",
|
||||
|
||||
@@ -51,6 +51,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/枫叶影院.py"
|
||||
},
|
||||
{
|
||||
"key": "dytt",
|
||||
"name": "🐬电影天堂.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/电影天堂.py"
|
||||
},
|
||||
{
|
||||
"key": "kkvm",
|
||||
"name": "🐬可可影视.py",
|
||||
@@ -484,6 +490,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/四虎视频.py"
|
||||
},
|
||||
{
|
||||
"key": "lyt",
|
||||
"name": "🐬撸一天.py|🔞",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/撸一天.py"
|
||||
},
|
||||
{
|
||||
"key": "scpd",
|
||||
"name": "🐬香肠派对.py|🔞",
|
||||
|
||||
+461
@@ -0,0 +1,461 @@
|
||||
# coding=utf-8
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
import requests
|
||||
import base64
|
||||
from bs4 import BeautifulSoup
|
||||
from urllib.parse import unquote, urljoin
|
||||
|
||||
try:
|
||||
from base.spider import Spider as BaseSpider
|
||||
except ImportError:
|
||||
class BaseSpider():
|
||||
def fetch(self, url, headers=None, timeout=10):
|
||||
try:
|
||||
res = requests.get(url, headers=headers, timeout=timeout, allow_redirects=True)
|
||||
res.encoding = 'utf-8'
|
||||
return res
|
||||
except Exception as e:
|
||||
print(f"fetch error: {e}")
|
||||
return None
|
||||
|
||||
class Spider(BaseSpider):
|
||||
def getName(self):
|
||||
return "撸一天"
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = "https://luyitian.com"
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
'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,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
'Connection': 'keep-alive'
|
||||
})
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {"list": []}
|
||||
|
||||
def localProxy(self, params):
|
||||
return [200, "video/MP2T", ""]
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return False
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def fetch(self, url, headers=None, timeout=5):
|
||||
try:
|
||||
req_headers = headers or self.session.headers
|
||||
res = self.session.get(url, headers=req_headers, timeout=timeout, allow_redirects=True)
|
||||
res.encoding = 'utf-8'
|
||||
return res
|
||||
except Exception as e:
|
||||
print(f"fetch error: {e}")
|
||||
return None
|
||||
|
||||
def _get_topic_filters(self):
|
||||
"""
|
||||
从 /topic/ 页面提取所有专题入口,生成子分类列表。
|
||||
适配真实 URL:/topicdetail-7/
|
||||
"""
|
||||
url = f"{self.host}/topic/"
|
||||
res = self.fetch(url, timeout=5)
|
||||
if not res:
|
||||
return []
|
||||
soup = BeautifulSoup(res.text, 'html.parser')
|
||||
|
||||
# 专门匹配 /topicdetail- 链接
|
||||
topic_links = soup.select('a[href*="/topicdetail-"]')
|
||||
if not topic_links:
|
||||
# 兜底:可能写成 /topicdetail/ 格式
|
||||
topic_links = soup.select('a[href*="/topicdetail"]')
|
||||
|
||||
filters = []
|
||||
seen = set()
|
||||
for a in topic_links:
|
||||
href = a.get('href', '')
|
||||
# 提取数字 ID(例如 /topicdetail-7/)
|
||||
match = re.search(r'/topicdetail-(\d+)', href)
|
||||
if not match:
|
||||
match = re.search(r'/topicdetail/(\d+)', href)
|
||||
if not match:
|
||||
continue
|
||||
tid = match.group(1)
|
||||
name = a.get_text(strip=True) or a.get('title', '') or f"专题{tid}"
|
||||
if len(name) < 2:
|
||||
continue
|
||||
if tid not in seen:
|
||||
seen.add(tid)
|
||||
filters.append({"n": name, "v": tid})
|
||||
return filters
|
||||
|
||||
def homeContent(self, filter):
|
||||
classes = [
|
||||
{"type_name": "最近更新", "type_id": "new"},
|
||||
{"type_name": "热门影片", "type_id": "hot"},
|
||||
{"type_name": "影片专题", "type_id": "topic"},
|
||||
{"type_name": "中文字幕", "type_id": "28"},
|
||||
{"type_name": "国产", "type_id": "20"},
|
||||
{"type_name": "日本有码", "type_id": "21"},
|
||||
{"type_name": "日本无码", "type_id": "22"},
|
||||
{"type_name": "欧美", "type_id": "23"},
|
||||
{"type_name": "动漫", "type_id": "24"},
|
||||
{"type_name": "伦理", "type_id": "25"},
|
||||
{"type_name": "韩国", "type_id": "36"},
|
||||
{"type_name": "另类", "type_id": "41"}
|
||||
]
|
||||
|
||||
filters = {
|
||||
"28": [{"key": "tid", "name": "子分类", "value": [
|
||||
{"n": "全部", "v": "28"},
|
||||
{"n": "日本中字", "v": "51"}
|
||||
]}],
|
||||
"20": [{"key": "tid", "name": "子分类", "value": [
|
||||
{"n": "全部", "v": "20"},
|
||||
{"n": "国产精品", "v": "26"},
|
||||
{"n": "国产剧情", "v": "27"},
|
||||
{"n": "国产自拍", "v": "29"},
|
||||
{"n": "国产主播", "v": "35"},
|
||||
{"n": "国模私拍", "v": "85"},
|
||||
{"n": "网红明星", "v": "91"},
|
||||
{"n": "国产SM", "v": "105"},
|
||||
{"n": "台湾辣妹", "v": "107"},
|
||||
{"n": "香港正妹", "v": "108"}
|
||||
]}],
|
||||
"21": [{"key": "tid", "name": "子分类", "value": [
|
||||
{"n": "全部", "v": "21"},
|
||||
{"n": "人妻", "v": "31"},
|
||||
{"n": "素人", "v": "44"},
|
||||
{"n": "口爆颜射", "v": "46"},
|
||||
{"n": "萝莉少女", "v": "47"},
|
||||
{"n": "美乳巨乳", "v": "48"},
|
||||
{"n": "制服诱惑", "v": "52"},
|
||||
{"n": "调教", "v": "57"},
|
||||
{"n": "出轨", "v": "58"},
|
||||
{"n": "有码精品", "v": "101"}
|
||||
]}],
|
||||
"22": [{"key": "tid", "name": "子分类", "value": [
|
||||
{"n": "全部", "v": "22"},
|
||||
{"n": "无码精品", "v": "102"}
|
||||
]}],
|
||||
"23": [{"key": "tid", "name": "子分类", "value": [
|
||||
{"n": "全部", "v": "23"},
|
||||
{"n": "欧美精品", "v": "104"}
|
||||
]}],
|
||||
"24": [{"key": "tid", "name": "子分类", "value": [
|
||||
{"n": "全部", "v": "24"},
|
||||
{"n": "动漫精品", "v": "103"}
|
||||
]}],
|
||||
"25": [{"key": "tid", "name": "子分类", "value": [
|
||||
{"n": "全部", "v": "25"},
|
||||
{"n": "综合三级", "v": "39"}
|
||||
]}],
|
||||
"36": [{"key": "tid", "name": "子分类", "value": [
|
||||
{"n": "全部", "v": "36"},
|
||||
{"n": "韩国主播", "v": "37"}
|
||||
]}],
|
||||
"41": [{"key": "tid", "name": "子分类", "value": [
|
||||
{"n": "全部", "v": "41"},
|
||||
{"n": "Cosplay", "v": "106"}
|
||||
]}]
|
||||
}
|
||||
|
||||
# 动态注入专题子分类(例如:2018必看、2019必看...)
|
||||
topic_values = self._get_topic_filters()
|
||||
if topic_values:
|
||||
filters["topic"] = [{
|
||||
"key": "tid",
|
||||
"name": "专题",
|
||||
"value": topic_values
|
||||
}]
|
||||
|
||||
return {'class': classes, 'filters': filters}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
pg = int(pg)
|
||||
result = {"list": [], "page": pg, "pagecount": 999, "limit": 20, "total": 9999}
|
||||
|
||||
real_tid = extend.get('tid', tid)
|
||||
soup = None
|
||||
|
||||
# ---------- 影片专题下的具体专题(/topicdetail-7/) ----------
|
||||
if real_tid.isdigit() and tid == "topic":
|
||||
urls_to_try = [
|
||||
f"{self.host}/topicdetail-{real_tid}/",
|
||||
f"{self.host}/topicdetail-{real_tid}.html",
|
||||
f"{self.host}/topicdetail/{real_tid}/",
|
||||
f"{self.host}/topicdetail/{real_tid}.html",
|
||||
f"{self.host}/topicdetail-{real_tid}-{pg}/",
|
||||
f"{self.host}/topicdetail/{real_tid}-{pg}/"
|
||||
]
|
||||
for url in urls_to_try:
|
||||
res = self.fetch(url, headers={'Referer': self.host})
|
||||
if res and res.status_code == 200 and ('video-img-box' in res.text or 'vodlist' in res.text or 'vodplay' in res.text):
|
||||
soup = BeautifulSoup(res.text, 'html.parser')
|
||||
break
|
||||
if not soup:
|
||||
return result
|
||||
|
||||
# ---------- new / hot / topic 总入口 ----------
|
||||
elif real_tid in ["new", "hot", "topic"]:
|
||||
if real_tid == "topic":
|
||||
return result
|
||||
if pg > 1:
|
||||
url = f"{self.host}/label/{real_tid}/page/{pg}/"
|
||||
else:
|
||||
url = f"{self.host}/label/{real_tid}/"
|
||||
res = self.fetch(url, headers={'Referer': self.host})
|
||||
if not res:
|
||||
url = f"{self.host}/label/{real_tid}/"
|
||||
res = self.fetch(url, headers={'Referer': self.host})
|
||||
if not res:
|
||||
return result
|
||||
soup = BeautifulSoup(res.text, 'html.parser')
|
||||
|
||||
# ---------- 普通分类 ----------
|
||||
else:
|
||||
urls_to_try = [
|
||||
f"{self.host}/vodtype/{real_tid}-{pg}.html",
|
||||
f"{self.host}/vodtype/{real_tid}-{pg}/",
|
||||
f"{self.host}/type/{real_tid}-{pg}.html",
|
||||
f"{self.host}/type/{real_tid}-{pg}/",
|
||||
f"{self.host}/vodtype/{real_tid}/",
|
||||
f"{self.host}/vodtype/{real_tid}.html"
|
||||
]
|
||||
res = None
|
||||
for url in urls_to_try:
|
||||
res = self.fetch(url, headers={'Referer': self.host})
|
||||
if res and res.status_code == 200:
|
||||
if 'video-img-box' in res.text or 'vodlist' in res.text or 'item' in res.text:
|
||||
break
|
||||
res = None
|
||||
if not res:
|
||||
return result
|
||||
soup = BeautifulSoup(res.text, 'html.parser')
|
||||
|
||||
# ---------- 统一解析视频列表 ----------
|
||||
vod_list = []
|
||||
items = soup.select('.video-img-box') or soup.select('.video-film-list .video-item') or soup.select('.vodlist_item') or soup.select('.item')
|
||||
|
||||
for item in items:
|
||||
a = item.select_one('a')
|
||||
if not a:
|
||||
continue
|
||||
href = a.get('href', '')
|
||||
vid_match = re.search(r'/vodplay/(\d+)', href) or \
|
||||
re.search(r'/voddetail/(\d+)', href) or \
|
||||
re.search(r'/vod/(\d+)', href) or \
|
||||
re.search(r'/play/(\d+)', href)
|
||||
vid = vid_match.group(1) if vid_match else href
|
||||
|
||||
name = ""
|
||||
img = item.select_one('img')
|
||||
if img and img.get('alt'):
|
||||
name = img['alt']
|
||||
if not name and a.get('title'):
|
||||
name = a['title']
|
||||
if not name:
|
||||
title_elem = item.select_one('.title a') or item.select_one('.detail .title a')
|
||||
if title_elem:
|
||||
name = title_elem.get_text(strip=True)
|
||||
if not name:
|
||||
name = a.get_text(strip=True)
|
||||
if not name:
|
||||
name = "未知标题"
|
||||
|
||||
pic = ""
|
||||
if img:
|
||||
pic = img.get('data-src') or img.get('src', '')
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = urljoin(self.host, pic)
|
||||
|
||||
remark = ""
|
||||
remark_elem = item.select_one('.sub-title') or item.select_one('.remarks') or item.select_one('.video-remarks')
|
||||
if remark_elem:
|
||||
remark = remark_elem.get_text(strip=True)
|
||||
if len(remark) > 20:
|
||||
remark = remark[:20]
|
||||
else:
|
||||
text = item.get_text(strip=True)
|
||||
parts = [p.strip() for p in text.split('\n') if p.strip()]
|
||||
if parts:
|
||||
remark = parts[-1][:20]
|
||||
|
||||
vod_list.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": name.strip(),
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
})
|
||||
|
||||
result['list'] = vod_list
|
||||
|
||||
# ---------- 分页信息 ----------
|
||||
page_elem = soup.select_one('.pagination a:last-child') or soup.select_one('.page a:last-child')
|
||||
if page_elem and page_elem.get('href'):
|
||||
try:
|
||||
nums = re.findall(r'(\d+)', page_elem['href'])
|
||||
if nums:
|
||||
result['pagecount'] = max(int(nums[-1]), 1)
|
||||
except:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
vid = ids[0]
|
||||
url = f"{self.host}/vodplay/{vid}-1-1/"
|
||||
res = self.fetch(url, headers={'Referer': self.host})
|
||||
if not res:
|
||||
return {"list": []}
|
||||
|
||||
soup = BeautifulSoup(res.text, 'html.parser')
|
||||
raw_title = soup.title.text.split('|')[0].replace('在线播放在线观看','').replace('《','').replace('》','').strip()
|
||||
|
||||
vod = {
|
||||
"vod_id": vid,
|
||||
"vod_name": raw_title,
|
||||
"vod_type": "视频",
|
||||
"vod_content": "资源来自于网络",
|
||||
"vod_play_from": "Luyitian",
|
||||
"vod_play_url": f"播放${vid}-1-1"
|
||||
}
|
||||
return {"list": [vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg=1):
|
||||
url = f"{self.host}/vodsearch/{key}----------{pg}---/"
|
||||
res = self.fetch(url, headers={'Referer': self.host})
|
||||
if not res:
|
||||
return {"list": []}
|
||||
|
||||
soup = BeautifulSoup(res.text, 'html.parser')
|
||||
vod_list = []
|
||||
items = soup.select('.video-img-box') or soup.select('.video-film-list .video-item')
|
||||
|
||||
for item in items:
|
||||
a = item.select_one('a')
|
||||
if not a:
|
||||
continue
|
||||
href = a.get('href', '')
|
||||
vid_match = re.search(r'/vodplay/(\d+)', href) or re.search(r'/voddetail/(\d+)', href)
|
||||
vid = vid_match.group(1) if vid_match else href
|
||||
|
||||
name = ""
|
||||
img = item.select_one('img')
|
||||
if img and img.get('alt'):
|
||||
name = img['alt']
|
||||
if not name and a.get('title'):
|
||||
name = a['title']
|
||||
if not name:
|
||||
title_elem = item.select_one('.title a')
|
||||
if title_elem:
|
||||
name = title_elem.get_text(strip=True)
|
||||
if not name:
|
||||
name = a.get_text(strip=True)
|
||||
if not name:
|
||||
name = "搜索结果"
|
||||
|
||||
pic = ""
|
||||
if img:
|
||||
pic = img.get('data-src') or img.get('src', '')
|
||||
|
||||
vod_list.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": name.strip(),
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": ""
|
||||
})
|
||||
return {"list": vod_list}
|
||||
|
||||
def _js_decode(self, js_str):
|
||||
b64_match = re.search(r'atob\s*\(\s*["\']([^"\']+)["\']\s*\)', js_str)
|
||||
if b64_match:
|
||||
try:
|
||||
decoded = base64.b64decode(b64_match.group(1)).decode('utf-8')
|
||||
return decoded
|
||||
except:
|
||||
pass
|
||||
unescape_match = re.search(r'unescape\s*\(\s*["\']([^"\']+)["\']\s*\)', js_str)
|
||||
if unescape_match:
|
||||
try:
|
||||
decoded = unquote(unescape_match.group(1))
|
||||
return decoded
|
||||
except:
|
||||
pass
|
||||
url_match = re.search(r'(https?://[^\s"\']+\.m3u8[^\s"\']*)', js_str, re.I)
|
||||
if url_match:
|
||||
return url_match.group(1)
|
||||
return None
|
||||
|
||||
def _sniff_xhr(self, html, page_url):
|
||||
patterns = [
|
||||
r'fetch\s*\(\s*["\']([^"\']+\.m3u8[^"\']*)["\']',
|
||||
r'XMLHttpRequest.*?\.open\s*\(\s*["\']GET["\']\s*,\s*["\']([^"\']+\.m3u8[^"\']*)["\']',
|
||||
r'\.get\s*\(\s*["\']([^"\']+\.m3u8[^"\']*)["\']',
|
||||
r'url\s*:\s*["\']([^"\']+\.m3u8[^"\']*)["\']',
|
||||
r'src\s*=\s*["\']([^"\']+\.m3u8[^"\']*)["\']',
|
||||
]
|
||||
for pat in patterns:
|
||||
match = re.search(pat, html, re.I)
|
||||
if match:
|
||||
url = match.group(1)
|
||||
if not url.startswith('http'):
|
||||
url = urljoin(page_url, url)
|
||||
return url
|
||||
|
||||
scripts = re.findall(r'<script[^>]*>(.*?)</script>', html, re.I | re.S)
|
||||
for script_content in scripts:
|
||||
if script_content.strip():
|
||||
found = self._js_decode(script_content)
|
||||
if found and '.m3u8' in found:
|
||||
return found
|
||||
return None
|
||||
|
||||
def playerContent(self, flag, id, vipFlags=None):
|
||||
play_url = f"{self.host}/vodplay/{id}/"
|
||||
res = self.fetch(play_url, headers={'Referer': self.host}, timeout=5)
|
||||
if not res:
|
||||
return {"parse": 1, "url": play_url}
|
||||
|
||||
html = res.text
|
||||
m3u8_url = None
|
||||
|
||||
match = re.search(r'var\s+player_aaaa\s*=\s*(\{.*?\});', html, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
json_str = match.group(1).strip()
|
||||
if json_str.endswith(','):
|
||||
json_str = json_str[:-1]
|
||||
config = json.loads(json_str)
|
||||
m3u8_url = config.get('url', '')
|
||||
except:
|
||||
pass
|
||||
|
||||
if not m3u8_url:
|
||||
m3u8_url = self._js_decode(html)
|
||||
|
||||
if not m3u8_url:
|
||||
m3u8_url = self._sniff_xhr(html, play_url)
|
||||
|
||||
if not m3u8_url:
|
||||
return {"parse": 1, "url": play_url}
|
||||
|
||||
m3u8_url = unquote(m3u8_url)
|
||||
if m3u8_url.startswith('//'):
|
||||
m3u8_url = 'https:' + m3u8_url
|
||||
elif not m3u8_url.startswith('http'):
|
||||
m3u8_url = urljoin(self.host, m3u8_url)
|
||||
|
||||
return {
|
||||
"parse": 0,
|
||||
"playUrl": "",
|
||||
"url": m3u8_url,
|
||||
"header": {
|
||||
"User-Agent": self.session.headers['User-Agent'],
|
||||
"Referer": play_url,
|
||||
"Origin": self.host
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import xml.etree.ElementTree as ET
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import binascii
|
||||
import requests
|
||||
import random
|
||||
import base64
|
||||
import html
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://web.tt4747.com"
|
||||
headerx1 = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
|
||||
headerx = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
|
||||
}
|
||||
class Spider(Spider):
|
||||
global xurl
|
||||
global headerx
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
leixing = {"key": "类型","name": "类型",
|
||||
"value": [{"n": "全部", "v": ""},{"n": "Netflix", "v": "Netflix"},{"n": "剧情", "v": "剧情"},{"n": "喜剧", "v": "喜剧"},{"n": "动作", "v": "动作"},
|
||||
{"n": "爱情", "v": "爱情"},{"n": "恐怖", "v": "恐怖"},{"n": "惊悚", "v": "惊悚"},{"n": "犯罪", "v": "犯罪"},{"n": "科幻", "v": "科幻"},
|
||||
{"n": "悬疑", "v": "悬疑"},{"n": "奇幻", "v": "奇幻"},{"n": "冒险", "v": "冒险"},{"n": "战争", "v": "战争"},{"n": "历史", "v": "历史"},
|
||||
{"n": "古装", "v": "古装"},{"n": "家庭", "v": "家庭"},{"n": "传记", "v": "传记"},{"n": "武侠", "v": "武侠"},{"n": "同性", "v": "同性"},
|
||||
{"n": "歌舞", "v": "歌舞"},{"n": "短片", "v": "短片"},{"n": "动画", "v": "动画"},{"n": "儿童", "v": "儿童"},{"n": "职场", "v": "职场"}]}
|
||||
zy_leixing = {"key": "类型","name": "类型",
|
||||
"value": [{"n": "全部", "v": ""},{"n": "纪录", "v": "纪录"},{"n": "真人秀", "v": "真人秀"},{"n": "记录", "v": "记录"},{"n": "脱口秀", "v": "脱口秀"},
|
||||
{"n": "剧情", "v": "剧情"},{"n": "历史", "v": "历史"},{"n": "喜剧", "v": "喜剧"},{"n": "传记", "v": "传记"},{"n": "相声", "v": "相声"},
|
||||
{"n": "节目", "v": "节目"},{"n": "歌舞", "v": "歌舞"},{"n": "冒险", "v": "冒险"},{"n": "运动", "v": "运动"},{"n": "Season", "v": "Season"},
|
||||
{"n": "犯罪", "v": "犯罪"},{"n": "短片", "v": "短片"},{"n": "搞笑", "v": "搞笑"},{"n": "晚会", "v": "晚会"}]}
|
||||
dm_leixing = {"key": "类型","name": "类型",
|
||||
"value": [{"n": "全部", "v": ""},{"n": "Netflix", "v": "Netflix"},{"n": "动态漫画", "v": "动态漫画"},{"n": "剧情", "v": "剧情"},{"n": "动画", "v": "动画"},
|
||||
{"n": "喜剧", "v": "喜剧"},{"n": "冒险", "v": "冒险"},{"n": "动作", "v": "动作"},{"n": "奇幻", "v": "奇幻"},{"n": "科幻", "v": "科幻"},
|
||||
{"n": "儿童", "v": "儿童"},{"n": "搞笑", "v": "搞笑"},{"n": "爱情", "v": "爱情"},{"n": "家庭", "v": "家庭"},{"n": "短片", "v": "短片"},
|
||||
{"n": "热血", "v": "热血"},{"n": "益智", "v": "益智"},{"n": "悬疑", "v": "悬疑"},{"n": "经典", "v": "经典"},{"n": "校园", "v": "校园"},
|
||||
{"n": "Anime", "v": "Anime"},{"n": "运动", "v": "运动"},{"n": "亲子", "v": "亲子"},{"n": "青春", "v": "青春"},{"n": "恋爱", "v": "恋爱"},
|
||||
{"n": "武侠", "v": "武侠"},{"n": "惊悚", "v": "惊悚"}]}
|
||||
diqu = {"key": "地区","name": "地区",
|
||||
"value": [{"n": "全部", "v": ""},{"n": "大陆", "v": "大陆"},{"n": "香港", "v": "香港"},{"n": "台湾", "v": "台湾"},{"n": "美国", "v": "美国"},
|
||||
{"n": "日本", "v": "日本"},{"n": "韩国", "v": "韩国"},{"n": "英国", "v": "英国"},{"n": "法国", "v": "法国"},{"n": "德国", "v": "德国"},
|
||||
{"n": "印度", "v": "印度"},{"n": "泰国", "v": "泰国"},{"n": "丹麦", "v": "丹麦"},{"n": "瑞典", "v": "瑞典"},{"n": "巴西", "v": "巴西"},
|
||||
{"n": "加拿大", "v": "加拿大"},{"n": "俄罗斯", "v": "俄罗斯"},{"n": "意大利", "v": "意大利"},{"n": "比利时", "v": "比利时"},{"n": "爱尔兰", "v": "爱尔兰"},
|
||||
{"n": "西班牙", "v": "西班牙"},{"n": "澳大利亚", "v": "澳大利亚"},{"n": "其他", "v": "其他"}]}
|
||||
yuyuan = {"key": "语言","name": "语言",
|
||||
"value": [{"n": "全部", "v": ""},{"n": "国语", "v": "国语"},{"n": "粤语", "v": "粤语"},{"n": "英语", "v": "英语"},{"n": "日语", "v": "日语"},
|
||||
{"n": "韩语", "v": "韩语"},{"n": "法语", "v": "法语"},{"n": "其他", "v": "其他"}]}
|
||||
nianfen = {"key": "年份","name": "年份",
|
||||
"value": [{"n": "全部", "v": ""},{"n": "2025", "v": "2025"},{"n": "2024", "v": "2024"},{"n": "2023", "v": "2023"},{"n": "2022", "v": "2022"},
|
||||
{"n": "2021", "v": "2021"},{"n": "2020", "v": "2020"},{"n": "2019", "v": "2019"},{"n": "2018", "v": "2018"},{"n": "2017", "v": "2017"},
|
||||
{"n": "2016", "v": "2016"},{"n": "2015", "v": "2015"},{"n": "2014", "v": "2014"},{"n": "2013", "v": "2013"},{"n": "2012", "v": "2012"},
|
||||
{"n": "2011", "v": "2011"},{"n": "2010", "v": "2010"}]}
|
||||
paixu = {"key": "排序","name": "排序",
|
||||
"value": [{"n": "全部", "v": ""},{"n": "按时间", "v": "time"},{"n": "按人气", "v": "hits"},{"n": "按评分", "v": "score"}]}
|
||||
result = {"class": [{"type_id": "1", "type_name": "电影"},
|
||||
{"type_id": "2", "type_name": "电视剧"},
|
||||
{"type_id": "3", "type_name": "综艺"},
|
||||
{"type_id": "4", "type_name": "动漫"},
|
||||
{"type_id": "5", "type_name": "短剧"},
|
||||
{"type_id": "6", "type_name": "伦理"}],
|
||||
|
||||
"list": [],
|
||||
"filters": {"1": [leixing,diqu,yuyuan,nianfen,paixu],
|
||||
"2": [leixing,diqu,yuyuan,nianfen,paixu],
|
||||
"3": [zy_leixing,diqu,yuyuan,nianfen,paixu],
|
||||
"4": [dm_leixing,diqu,yuyuan,nianfen,paixu],
|
||||
"5": [paixu],
|
||||
"6": [paixu]}}
|
||||
|
||||
return result
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def fetch_image_response_info(self, url):
|
||||
try:
|
||||
response = requests.get(url, timeout=10, allow_redirects=True)
|
||||
return response.text
|
||||
except Exception as e:
|
||||
return ''
|
||||
|
||||
# def fetch_image_response_info(self, url):
|
||||
# try:
|
||||
# response = requests.get(url, timeout=10, allow_redirects=True)
|
||||
# content_type = response.headers.get('Content-Type', '')
|
||||
#
|
||||
# if 'text' in content_type or 'json' in content_type:
|
||||
# return {
|
||||
# 'status': 'text',
|
||||
# 'content': response.text
|
||||
# }
|
||||
# elif 'image' in content_type:
|
||||
# # img_base64 = base64.b64encode(response.content).decode('utf-8')
|
||||
# # print(img_base64)
|
||||
# return {
|
||||
# 'status': 'image',
|
||||
# 'content': '',
|
||||
# 'size': len(response.content)
|
||||
# }
|
||||
# else:
|
||||
# return {
|
||||
# 'status': 'unknown',
|
||||
# 'content': str(response.content)
|
||||
# }
|
||||
#
|
||||
# except Exception as e:
|
||||
# return {
|
||||
# 'status': 'error',
|
||||
# 'error': str(e)
|
||||
# }
|
||||
|
||||
def get_video_covers(self, videos, max_workers=8):
|
||||
vod_ids = []
|
||||
for video in videos:
|
||||
vod_id = video['vod_id']
|
||||
if '/voddetail/' in vod_id:
|
||||
vod_id = vod_id.split('/voddetail/')[1].strip('/')
|
||||
vod_ids.append(vod_id)
|
||||
|
||||
cover_map = {}
|
||||
lock = threading.Lock()
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
|
||||
def fetch_cover(vid):
|
||||
nonlocal success_count, fail_count
|
||||
url = f"https://web.tt4747.com/voddetail/{vid}/"
|
||||
time.sleep(random.uniform(0.3, 1.0))
|
||||
try:
|
||||
headers = {
|
||||
'User-Agent': random.choice([
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0'
|
||||
]),
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': 'https://web.tt4747.com/'
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
pic_div = soup.find('div', class_='detail-pic')
|
||||
if pic_div:
|
||||
img = pic_div.find('img')
|
||||
if img:
|
||||
pic_urls = img.get('data-original')
|
||||
pic_urls = 'https://pics.xhsdns.cn/vod/252/252122.jpg'
|
||||
pic_info = self.fetch_image_response_info(pic_urls)
|
||||
pic_url = f'ddata:image/png;base64,{pic_info}'
|
||||
if pic_url:
|
||||
with lock:
|
||||
success_count += 1
|
||||
cover_map[vid] = pic_url
|
||||
return vid, pic_url
|
||||
pattern = r'data-original="([^"]+\.jpg)"'
|
||||
match = re.search(pattern, response.text)
|
||||
if match:
|
||||
pic_url = match.group(1)
|
||||
with lock:
|
||||
success_count += 1
|
||||
cover_map[vid] = pic_url
|
||||
return vid, pic_url
|
||||
|
||||
with lock:
|
||||
fail_count += 1
|
||||
return vid, None
|
||||
else:
|
||||
with lock:
|
||||
fail_count += 1
|
||||
return vid, None
|
||||
except Exception as e:
|
||||
with lock:
|
||||
fail_count += 1
|
||||
return vid, None
|
||||
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
futures = {executor.submit(fetch_cover, vid): vid for vid in vod_ids}
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
future.result(timeout=30)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
for video in videos:
|
||||
vod_id = video['vod_id']
|
||||
if '/voddetail/' in vod_id:
|
||||
vid = vod_id.split('/voddetail/')[1].strip('/')
|
||||
else:
|
||||
vid = vod_id
|
||||
video['vod_pic'] = cover_map.get(vid, '')
|
||||
return videos
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
result = {}
|
||||
videos = []
|
||||
if pg:
|
||||
page = int(pg)
|
||||
else:
|
||||
page = 1
|
||||
LX = ext.get('类型', '')
|
||||
DQ = ext.get('地区', '')
|
||||
YY = ext.get('语言', '')
|
||||
NF = ext.get('年份', '')
|
||||
PX = ext.get('排序', '')
|
||||
if cid == '5' or cid == '6':
|
||||
url = f'{xurl}/rss/index.xml?mid=1&tid={cid}&page={str(page)}&limit=24&class=&year=&area=&lang=&by={PX}'
|
||||
else:
|
||||
url = f'{xurl}/rss/index.xml?mid=1&tid={cid}&page={str(page)}&limit=24&class={LX}&year={NF}&area={DQ}&lang={YY}&by={PX}'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(res)
|
||||
items = root.findall('.//item')
|
||||
for item in items:
|
||||
title_elem = item.find('title')
|
||||
if title_elem is not None and title_elem.text:
|
||||
title_text = title_elem.text
|
||||
if ' ' in title_text:
|
||||
first_space = title_text.find(' ')
|
||||
last_space = title_text.rfind(' ')
|
||||
if first_space == last_space:
|
||||
vod_name = title_text[:first_space]
|
||||
vod_remarks = title_text[last_space + 1:]
|
||||
else:
|
||||
vod_name = title_text[:first_space]
|
||||
vod_remarks = title_text[last_space + 1:]
|
||||
else:
|
||||
vod_name = title_text
|
||||
vod_remarks = ""
|
||||
else:
|
||||
vod_name = ""
|
||||
vod_remarks = ""
|
||||
link_elem = item.find('link')
|
||||
vod_ids = link_elem.text if link_elem is not None else ""
|
||||
vod_id = vod_ids.replace('http://web.dy1996.com/', 'https://web.tt4747.com/')
|
||||
pubdate_elem = item.find('pubDate')
|
||||
if pubdate_elem is not None and pubdate_elem.text:
|
||||
pubdate_text = pubdate_elem.text
|
||||
if ' ' in pubdate_text:
|
||||
vod_year = pubdate_text.split(' ')[0]
|
||||
else:
|
||||
vod_year = pubdate_text
|
||||
else:
|
||||
vod_year = ""
|
||||
|
||||
video = {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_name,
|
||||
"vod_year": vod_year,
|
||||
"vod_pic": '',
|
||||
"vod_remarks": vod_remarks
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
except ET.ParseError as e:
|
||||
pass
|
||||
except Exception as e:
|
||||
pass
|
||||
if videos:
|
||||
try:
|
||||
videos = self.get_video_covers(videos, max_workers=8)
|
||||
except Exception as e:
|
||||
''
|
||||
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
result = {}
|
||||
videos = []
|
||||
xianlu = '咖啡直播'
|
||||
if did.startswith('@@@'):
|
||||
fenge = did.split("@@@")
|
||||
url = f"{fenge[1]}"
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
data = json.loads(res)['data']['replays']
|
||||
ids = []
|
||||
for item in data:
|
||||
bf_name = item['title']
|
||||
bf_url = item['video_url']
|
||||
ids.append(f"{bf_name}${bf_url}")
|
||||
bofang = '#'.join(ids)
|
||||
else:
|
||||
bofang = did
|
||||
videos.append({
|
||||
"vod_play_from": xianlu,
|
||||
"vod_play_url": bofang
|
||||
})
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
fenge = id.split("http")
|
||||
id = f"http{fenge[1]}"
|
||||
url = id
|
||||
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = url
|
||||
result["header"] = headerx
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
spider_instance = Spider()
|
||||
|
||||
# res=spider_instance.homeContent('filter') # 分类🚨
|
||||
|
||||
# res = spider_instance.homeVideoContent() # 首页🚨
|
||||
|
||||
res=spider_instance.categoryContent('2', 1, 'filter', {}) # 分页🚨
|
||||
|
||||
# res = spider_instance.detailContent(['@@@https://kafeizhibo.cc/api/v1/match/21895/recordings']) # 详情页🚨
|
||||
|
||||
# res = spider_instance.playerContent('1', '直播21$01https://live.666666.zip/live/4528263.m3u8', 'vipFlags') # 播放页🚨
|
||||
|
||||
# res = spider_instance.searchContentPage('我', 'quick', '1') # 搜索页🚨
|
||||
|
||||
print(res)
|
||||
Reference in New Issue
Block a user