This commit is contained in:
2026-03-24 18:40:17 +08:00
parent a53ca2fa61
commit 82656f8f2a
637 changed files with 3306118 additions and 0 deletions

265
PY1/103.py Normal file
View File

@@ -0,0 +1,265 @@
# coding=utf-8
import re
import sys
import urllib.parse
import json
from pyquery import PyQuery as pq
import requests
sys.path.append('..')
from base.spider import Spider as BaseSpider
class Spider(BaseSpider):
def __init__(self):
super().__init__()
self.base_url = "http://oxax.tv"
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'
),
'Referer': self.base_url,
'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',
})
# 完整的频道列表从实际页面提取的40个频道
self.all_channels = [
{"title": "ОХ-АХ HD", "href": "/oh-ah.html"},
{"title": "CineMan XXX HD", "href": "/sl-hot1.html"},
{"title": "CineMan XXX2 HD", "href": "/sl-hot2.html"},
{"title": "Brazzers TV Europe", "href": "/brazzers-tv-europe.html"},
{"title": "Brazzers TV", "href": "/brazzers-tv.html"},
{"title": "Red Lips", "href": "/red-lips.html"},
{"title": "KinoXXX", "href": "/kino-xxx.html"},
{"title": "XY Max HD", "href": "/xy-max-hd.html"},
{"title": "XY Plus HD", "href": "/xy-plus-hd.html"},
{"title": "XY Mix HD", "href": "/xy-mix-hd.html"},
{"title": "Barely legal", "href": "/barely-legal.html"},
{"title": "Playboy TV", "href": "/playboy-tv.html"},
{"title": "Vivid Red HD", "href": "/vivid-red.html"},
{"title": "Exxxotica HD", "href": "/hot-pleasure.html"},
{"title": "Babes TV", "href": "/babes-tv.html"},
{"title": "Русская ночь", "href": "/russkaya-noch.html"},
{"title": "Pink O TV", "href": "/pink-o.html"},
{"title": "Erox HD", "href": "/erox-hd.html"},
{"title": "Eroxxx HD", "href": "/eroxxx-hd.html"},
{"title": "Hustler HD", "href": "/hustler-hd.html"},
{"title": "Private TV", "href": "/private-tv.html"},
{"title": "Redlight HD", "href": "/redlight-hd.html"},
{"title": "Penthouse Gold HD", "href": "/penthouse-gold.html"},
{"title": "Penthouse Quickies", "href": "/penthouse-2.html"},
{"title": "O-la-la", "href": "/o-la-la.html"},
{"title": "Blue Hustler", "href": "/blue-hustler.html"},
{"title": "Шалун", "href": "/shalun.html"},
{"title": "Dorcel TV", "href": "/dorcel-tv.html"},
{"title": "Extasy HD", "href": "/extasyhd.html"},
{"title": "XXL", "href": "/xxl.html"},
{"title": "FAP TV 2", "href": "/fap-tv-2.html"},
{"title": "FAP TV 3", "href": "/fap-tv-3.html"},
{"title": "FAP TV 4", "href": "/fap-tv-4.html"},
{"title": "FAP TV Parody", "href": "/fap-tv-parody.html"},
{"title": "FAP TV Compilation", "href": "/fap-tv-compilation.html"},
{"title": "FAP TV Anal", "href": "/fap-tv-anal.html"},
{"title": "FAP TV Teens", "href": "/fap-tv-teens.html"},
{"title": "FAP TV Lesbian", "href": "/fap-tv-lesbian.html"},
{"title": "FAP TV BBW", "href": "/fap-tv-bbw.html"},
{"title": "FAP TV Trans", "href": "/fap-tv-trans.html"},
]
# ========= 工具方法 =========
def _abs_url(self, base, url):
"""转换为绝对URL"""
if not url:
return ''
if url.startswith('http'):
return url
if url.startswith('//'):
return 'http:' + url
if url.startswith('/'):
return self.base_url + url
return base.rsplit('/', 1)[0] + '/' + url
def _get_channel_image(self, channel_name):
"""根据频道名称生成图片URL使用占位图"""
# 为每个频道生成唯一的颜色
color_map = {
'brazzers': 'FFD700', 'playboy': 'FF69B4', 'hustler': 'DC143C',
'penthouse': '9370DB', 'vivid': 'FF1493', 'private': '8B008B',
'dorcel': 'FF6347', 'cineman': '4169E1', 'fap': 'FF4500',
'xy': 'DA70D6', 'erox': 'FF00FF', 'kino': '8A2BE2',
}
color = '1E90FF' # 默认蓝色
name_lower = channel_name.lower()
for key, col in color_map.items():
if key in name_lower:
color = col
break
text = urllib.parse.quote(channel_name[:20])
return f"https://via.placeholder.com/400x225/{color}/FFFFFF?text={text}"
# ========= Spider接口实现 =========
def getName(self):
return "OXAX直播"
def init(self, extend):
pass
def homeContent(self, filter):
"""返回分类列表"""
return {
'class': [
{'type_name': '全部频道', 'type_id': 'all'},
{'type_name': 'HD频道', 'type_id': 'hd'},
{'type_name': 'FAP系列', 'type_id': 'fap'},
]
}
def homeVideoContent(self):
"""首页推荐 - 显示所有频道"""
videos = []
for ch in self.all_channels:
videos.append({
'vod_id': ch['href'],
'vod_name': ch['title'],
'vod_pic': self._get_channel_image(ch['title']),
'vod_remarks': '直播',
})
return {'list': videos}
def categoryContent(self, tid, pg, filter, extend):
"""分类内容 - 支持分页和过滤"""
pg = int(pg)
items_per_page = 30
# 根据分类ID过滤频道
if tid == 'hd':
channels = [ch for ch in self.all_channels if 'HD' in ch['title'].upper()]
elif tid == 'fap':
channels = [ch for ch in self.all_channels if 'FAP' in ch['title'].upper()]
else: # all
channels = self.all_channels
# 分页
start = (pg - 1) * items_per_page
end = start + items_per_page
page_channels = channels[start:end]
# 构建视频列表
videos = []
for ch in page_channels:
videos.append({
'vod_id': ch['href'],
'vod_name': ch['title'],
'vod_pic': self._get_channel_image(ch['title']),
'vod_remarks': '直播',
})
return {
'list': videos,
'page': pg,
'pagecount': max(1, (len(channels) + items_per_page - 1) // items_per_page),
'limit': items_per_page,
'total': len(channels),
}
def detailContent(self, array):
"""详情页 - 直接返回页面URL不做m3u8提取"""
if not array or not array[0]:
return {'list': []}
relative_path = array[0]
detail_url = self._abs_url(self.base_url, relative_path)
# 提取标题(从相对路径推断)
title = relative_path.replace('.html', '').replace('/', '').replace('-', ' ').title()
# 从 all_channels 查找真实标题
for ch in self.all_channels:
if ch['href'] == relative_path:
title = ch['title']
break
vod = {
'vod_id': relative_path,
'vod_name': title,
'vod_pic': self._get_channel_image(title),
'vod_remarks': '直播',
'vod_content': '成人电视直播频道',
'vod_play_from': 'OXAX',
'vod_play_url': f'{title}${detail_url}',
}
return {'list': [vod]}
def searchContent(self, key, quick, page='1'):
"""搜索功能"""
if not key:
return {'list': []}
key_lower = key.lower()
results = []
# 在本地频道列表中搜索
for ch in self.all_channels:
if key_lower in ch['title'].lower():
results.append({
'vod_id': ch['href'],
'vod_name': ch['title'],
'vod_pic': self._get_channel_image(ch['title']),
'vod_remarks': '直播',
})
return {'list': results}
def playerContent(self, flag, id, vipFlags):
"""
播放器内容解析 - 关键修改点
直接返回 video:// 协议的URL让播放器自行解析页面
"""
result = {
"parse": 0,
"playUrl": "",
"url": "",
"header": {
"User-Agent": self.session.headers.get('User-Agent'),
"Referer": self.base_url
}
}
if not id:
return result
try:
# id格式: "标题$URL"
url = id
if '$' in url:
url = url.split('$')[1]
result["url"] = f"video://{url}"
except Exception as e:
print(f"[ERROR] 播放器解析失败: {e}")
return result
def isVideoFormat(self, url):
"""判断是否为视频格式 - video:// 协议不是直接的视频格式"""
return False
def manualVideoCheck(self):
"""不需要手动视频检查"""
return False
def localProxy(self, param):
"""不使用本地代理"""
return {}

319
PY1/18.py Normal file
View File

@@ -0,0 +1,319 @@
# coding=utf-8
#!/usr/bin/python
import sys
sys.path.append('..')
from base.spider import Spider
import json
import urllib.parse
import re
class Spider(Spider):
def getName(self):
return "快递🔞"
def init(self, extend=""):
self.host = "https://www.xjjkdfw.sbs"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 11; M2007J3SC Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045713 Mobile 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',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive',
'Referer': self.host
}
self.log(f"快递🔞爬虫初始化完成,主站: {self.host}")
def isVideoFormat(self, url):
return False
def manualVideoCheck(self):
return True
def homeContent(self, filter):
"""获取首页内容和分类"""
result = {}
classes = self._getCategories()
result['class'] = classes
try:
rsp = self.fetch(self.host, headers=self.headers)
html = rsp.text
videos = self._getVideos(html)
result['list'] = videos
except Exception as e:
self.log(f"首页获取出错: {str(e)}")
result['list'] = []
return result
def homeVideoContent(self):
"""首页视频内容(可留空)"""
return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
"""分类内容"""
try:
pg_int = int(pg)
if pg_int == 1:
url = f"{self.host}/vodtype/{tid}.html"
else:
url = f"{self.host}/vodtype/{tid}/page/{pg_int}.html"
self.log(f"访问分类URL: {url}")
rsp = self.fetch(url, headers=self.headers)
html = rsp.text
videos = self._getVideos(html)
pagecount = 999
page_links = re.findall(r'<a href="/vodtype/{}/page/(\d+)\.html"'.format(tid), html)
if page_links:
pagecount = max([int(p) for p in page_links if p.isdigit()])
if not videos:
self.log(f"警告: 分类ID {tid}, 页码 {pg} 未找到任何视频。URL: {url}")
return {
'list': videos,
'page': pg_int,
'pagecount': pagecount,
'limit': 20,
'total': 999999
}
except Exception as e:
self.log(f"分类内容获取出错 (tid={tid}, pg={pg}): {str(e)}")
return {'list': []}
def searchContent(self, key, quick, pg="1"):
"""搜索功能(使用官方 AJAX 接口)"""
try:
search_url = f"{self.host}/index.php/ajax/suggest?mid=1&wd={urllib.parse.quote(key)}"
self.log(f"搜索URL: {search_url}")
rsp = self.fetch(search_url, headers=self.headers)
data = json.loads(rsp.text)
videos = []
for item in data:
video = {
'vod_id': item.get('id', ''),
'vod_name': item.get('name', ''),
'vod_pic': item.get('pic', ''),
'vod_remarks': item.get('actor', '')
}
videos.append(video)
return {'list': videos}
except Exception as e:
self.log(f"搜索出错: {str(e)}")
return {'list': []}
def detailContent(self, ids):
"""详情页面"""
try:
vid = ids[0]
detail_url = f"{self.host}/voddetail/{vid}.html"
self.log(f"详情URL: {detail_url}")
rsp = self.fetch(detail_url, headers=self.headers)
html = rsp.text
video_info = self._getDetail(html, vid)
return {'list': [video_info]} if video_info else {'list': []}
except Exception as e:
self.log(f"详情获取出错 (vid: {ids[0]}): {str(e)}")
return {'list': []}
def playerContent(self, flag, id, vipFlags):
"""播放链接解析"""
try:
play_page_url = f"{self.host}/vodplay/{id}.html"
self.log(f"播放页面URL: {play_page_url}")
rsp = self.fetch(play_page_url, headers=self.headers)
if rsp.status_code != 200:
self.log(f"播放页请求失败,状态码: {rsp.status_code}")
return {'parse': 1, 'playUrl': '', 'url': play_page_url}
html = rsp.text
# 1. 优先解析 JS 中的 player_aaaa 变量
player_pattern = r'var player_aaaa=({.*?});'
player_match = re.search(player_pattern, html, re.DOTALL)
if player_match:
try:
player_data = json.loads(player_match.group(1).replace("'", '"'))
video_url = player_data.get('url', '').strip()
if video_url:
if video_url.startswith('//'):
video_url = 'https:' + video_url
elif video_url.startswith('/') and not video_url.startswith('http'):
video_url = self.host.rstrip('/') + video_url
self.log(f"✅ 找到视频直链: {video_url}")
return {
'parse': 0,
'playUrl': '',
'url': video_url,
'header': json.dumps(self.headers)
}
except Exception as e:
self.log(f"解析player_aaaa失败: {str(e)}")
# 2. 解析 iframe 播放器
iframe_match = re.search(r'<iframe[^>]*src=["\']([^"\']+)["\']', html)
if iframe_match:
iframe_url = iframe_match.group(1).strip()
if iframe_url.startswith('//'):
iframe_url = 'https:' + iframe_url
elif iframe_url.startswith('/') and not iframe_url.startswith('http'):
iframe_url = self.host.rstrip('/') + iframe_url
self.log(f"📹 找到iframe播放源: {iframe_url}")
return {'parse': 1, 'playUrl': '', 'url': iframe_url}
# 3. 最后手段:返回播放页本身,让播放器自己嗅探
self.log(f"⚠️ 未找到播放源,返回原始播放页")
return {'parse': 1, 'playUrl': '', 'url': play_page_url}
except Exception as e:
self.log(f"播放链接获取出错 (id: {id}): {str(e)}")
return {'parse': 1, 'playUrl': '', 'url': f"{self.host}/vodplay/{id}.html"}
# ========== 辅助方法 ==========
def _getCategories(self):
"""从首页提取分类"""
try:
rsp = self.fetch(self.host, headers=self.headers)
html = rsp.text
categories = []
pattern = r'<a href="/vodtype/(\d+)\.html"[^>]*>([^<]+)</a>'
matches = re.findall(pattern, html)
seen = set()
for tid, name in matches:
if name.strip() and tid not in seen:
seen.add(tid)
categories.append({'type_id': tid, 'type_name': name.strip()})
return categories
except Exception as e:
self.log(f"获取分类出错: {str(e)}")
return []
def _getVideos(self, html):
"""从HTML中提取视频列表"""
videos = []
# 匹配结构:
# <a class="thumbnail" href="/vodplay/123-1-1.html">
# <img data-original="https://xxx.jpg" ...>
# </a>
# <a href="/voddetail/123.html">标题</a>
# <p class="vodtitle">分类 - <span class="title">日期</span></p>
pattern = r'<a\s+class="thumbnail"[^>]*href="(/vodplay/(\d+)-\d+-\d+\.html)"[^>]*>.*?data-original="([^"]+)".*?</a>.*?<a\s+href="/voddetail/\d+\.html"[^>]*>([^<]+)</a>.*?<p\s+class="vodtitle">([^<]+?)\s*-\s*<span\s+class="title">([^<]+)</span>'
matches = re.findall(pattern, html, re.DOTALL | re.IGNORECASE)
for full_play_link, vid, pic, title, category, date in matches:
if not pic.startswith('http'):
pic = self.host + pic if pic.startswith('/') else 'https:' + pic if pic.startswith('//') else pic
video = {
'vod_id': vid,
'vod_name': title.strip(),
'vod_pic': pic,
'vod_remarks': f"{category.strip()} | {date.strip()}"
}
videos.append(video)
return videos
def _getDetail(self, html, vid):
"""获取详情信息"""
try:
# 标题
title = self.regStr(r'<h2\s+class="title">([^<]+)</h2>', html)
# 封面
pic = self.regStr(r'data-original="([^"]+)"', html)
if pic and not pic.startswith('http'):
pic = self.host + pic if pic.startswith('/') else 'https:' + pic if pic.startswith('//') else pic
# 简介
desc = self.regStr(r'<div\s+class="content">([\s\S]*?)</div>', html)
if desc:
desc = desc.strip().replace('<br>', '\n').replace('</br>', '')
else:
desc = title
# 演员 (从标题中提取)
actor = ""
actor_match = re.search(r'([\u4e00-\u9fa5]{2,4})[-\s]+[A-Z0-9-]+', title)
if actor_match:
actor = actor_match.group(1).strip()
# 导演信息,网站未提供,留空
director = ""
# 播放源
play_from = []
play_url_list = []
playlist_matches = re.findall(r'<ul\s+class="playlist">([\s\S]*?)</ul>', html)
if playlist_matches:
for i, pl_html in enumerate(playlist_matches):
source_name = f"线路{i+1}"
episodes = []
ep_matches = re.findall(r'<a\s+href="(/vodplay/(\d+-\d+-\d+)\.html)"[^>]*>([^<]+)</a>', pl_html)
for full_url, ep_id, ep_name in ep_matches:
episodes.append(f"{ep_name.strip()}${ep_id}")
if episodes:
play_from.append(source_name)
play_url_list.append('#'.join(episodes))
# 如果没有播放列表,则创建一个默认的
if not play_url_list:
play_from = ["默认源"]
play_url_list = [f"第1集${vid}-1-1"]
# 其他字段
type_name = self.regStr(r'<a\s+href="/vodtype/\d+\.html"[^>]*>([^<]+)</a>', html)
return {
'vod_id': vid,
'vod_name': title,
'vod_pic': pic,
'type_name': type_name.strip() if type_name else "未知",
'vod_year': "2025",
'vod_area': "网络",
'vod_remarks': "高清",
'vod_actor': actor,
'vod_director': director,
'vod_content': desc,
'vod_play_from': '$$$'.join(play_from),
'vod_play_url': '$$$'.join(play_url_list)
}
except Exception as e:
self.log(f"获取详情失败 (vid={vid}): {str(e)}")
return {
'vod_id': vid,
'vod_name': "加载失败",
'vod_pic': "",
'type_name': "",
'vod_year': "",
'vod_area': "",
'vod_remarks': "",
'vod_actor': "",
'vod_director': "",
'vod_content': "详情加载失败",
'vod_play_from': "默认源",
'vod_play_url': f"第1集${vid}-1-1"
}
def regStr(self, pattern, string):
"""正则提取第一个匹配组"""
try:
match = re.search(pattern, string)
return match.group(1) if match else ""
except:
return ""

290
PY1/1AV研究所.py Normal file
View File

@@ -0,0 +1,290 @@
# -*- coding: utf-8 -*-
import re
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
requests.packages.urllib3.disable_warnings()
import base64
from urllib.parse import urljoin, unquote
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "AV研究所"
def init(self, extend=""):
super().init(extend)
self.site_url = "https://xn--cdn0308-1yjs01cc-rf0zn60cta5031amw9i.yanjiusuo0038.top"
self.headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Mobile Safari/537.36",
"Referer": self.site_url,
"Accept-Language": "zh-CN,zh;q=0.9"
}
self.sess = requests.Session()
retry = Retry(total=3, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
self.sess.mount("https://", HTTPAdapter(max_retries=retry))
self.sess.mount("http://", HTTPAdapter(max_retries=retry))
self.page_size = 20
self.total = 9999
def fetch(self, url, timeout=10):
try:
return self.sess.get(url, headers=self.headers, timeout=timeout, verify=False)
except Exception:
return None
def _abs(self, u):
if not u:
return ""
if u.startswith("//"):
return "https:" + u
if u.startswith(("http://", "https://")):
return u
return self.site_url + (u if u.startswith("/") else "/" + u)
def _clean(self, s):
if not s:
return ""
s = re.sub(r"<[^>]+>", "", s, flags=re.S)
return re.sub(r"\s+", " ", s).strip()
def homeContent(self, filter):
# 可按你的站点改成动态抓取;这里先给固定分类
cate_list = [
{"type_name": "最新", "type_id": "latest-insert"},
{"type_name": "最近发布", "type_id": "recent-release"},
{"type_name": "评分榜", "type_id": "top-rating"},
{"type_name": "收藏榜", "type_id": "top-favorites"},
]
return {"class": cate_list}
def _parse_video_list(self, html):
video_list = []
# 匹配 <dl> ... <dt><a href=...><img data-src=...><i>日期</i> ... <h3>标题</h3>
dls = re.findall(r"<dl>([\s\S]*?)</dl>", html, flags=re.S)
for item in dls:
m_href = re.search(r'<dt>\s*<a[^>]+href=["\']([^"\']+)["\']', item, flags=re.S)
m_pic = re.search(r'<img[^>]+(?:data-src|src)=["\']([^"\']+)["\']', item, flags=re.S)
m_date = re.search(r"<i>([^<]+)</i>", item, flags=re.S)
m_name = re.search(r"<h3>([\s\S]*?)</h3>", item, flags=re.S)
if not (m_href and m_pic and m_name):
continue
video_list.append({
"vod_id": self._abs(m_href.group(1).strip()),
"vod_name": self._clean(m_name.group(1)),
"vod_pic": self._abs(m_pic.group(1).strip()),
"vod_remarks": self._clean(m_date.group(1)) if m_date else "",
"style": {"type": "rect", "ratio": 1.33}
})
return video_list
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg) if str(pg).isdigit() else 1
# 兼容两种分页:
# /list/AvDB/latest-insert.html
# /list/AvDB/latest-insert/2.html
if pg == 1:
list_url = f"{self.site_url}/list/AvDB/{tid}.html"
else:
list_url = f"{self.site_url}/list/AvDB/{tid}/{pg}.html"
res = self.fetch(list_url)
video_list = self._parse_video_list(res.text) if (res and res.ok) else []
pagecount = pg + 1 if len(video_list) else pg
return {
"list": video_list,
"page": pg,
"pagecount": pagecount,
"limit": self.page_size,
"total": self.total
}
def searchContent(self, key, quick, pg=1):
pg = int(pg) if str(pg).isdigit() else 1
# 页面结构里的搜索参数是 GET: /?m=search&u=AvDB&p=1&k=关键词
search_url = f"{self.site_url}/?m=search&u=AvDB&p={pg}&k={requests.utils.quote(key)}"
res = self.fetch(search_url)
video_list = self._parse_video_list(res.text) if (res and res.ok) else []
pagecount = pg + 1 if len(video_list) else pg
return {
"list": video_list,
"page": pg,
"pagecount": pagecount,
"limit": self.page_size,
"total": len(video_list) if len(video_list) < self.total else self.total
}
def detailContent(self, ids):
vod_id = ids[0] if ids else ""
if not vod_id:
return {"list": [{"vod_name": "视频ID为空"}]}
res = self.fetch(vod_id)
if not (res and res.ok):
return {"list": [{"vod_id": vod_id, "vod_name": "视频详情解析失败"}]}
html = res.text
# 标题
m_name = re.search(r"<h1>([\s\S]*?)</h1>", html, re.S)
vod_name = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", "", m_name.group(1))).strip() if m_name else "未知名称"
# 封面(详情页通常没有主封面,兜底从猜你喜欢首图拿)
m_pic = re.search(r'<img[^>]+(?:data-src|src)=["\']([^"\']+)["\']', html, re.I)
vod_pic = ""
if m_pic:
p = m_pic.group(1).strip()
if p.startswith("//"):
vod_pic = "https:" + p
elif p.startswith(("http://", "https://")):
vod_pic = p
else:
vod_pic = self.site_url + (p if p.startswith("/") else "/" + p)
# 关键:提取 iframe 播放页地址
m_iframe = re.search(
r'<iframe[^>]*class=["\']player-iframe["\'][^>]*src=["\']([^"\']+)["\']',
html, re.I | re.S
)
play_entry = ""
if m_iframe:
iframe_src = m_iframe.group(1).strip()
if iframe_src.startswith("//"):
iframe_src = "https:" + iframe_src
elif not iframe_src.startswith(("http://", "https://")):
iframe_src = self.site_url + (iframe_src if iframe_src.startswith("/") else "/" + iframe_src)
# !!! 必须是 名称$地址
play_entry = f"在线播放${iframe_src}"
else:
# 兜底:给详情页,让 playerContent 再二次提 iframe
play_entry = f"在线播放${vod_id}"
detail_info = {
"vod_id": vod_id,
"vod_name": vod_name,
"vod_pic": vod_pic,
"vod_remarks": "",
"type_name": "",
"vod_play_from": "主线路",
"vod_play_url": play_entry
}
return {"list": [detail_info]}
def playerContent(self, flag, id, vipFlags):
import re
import json
from urllib.parse import urljoin, urlparse, parse_qsl, urlencode, urlunparse, unquote
def force_m_stream(u):
"""强制线路为 M原版src=missav移除 variant"""
pr = urlparse(u)
q = dict(parse_qsl(pr.query, keep_blank_values=True))
q["res"] = "720P"
q["src"] = "missav"
q.pop("variant", None)
new_query = urlencode(q, doseq=True)
return urlunparse((pr.scheme, pr.netloc, pr.path, pr.params, new_query, pr.fragment))
def abs_url(u, base):
if u.startswith("//"):
return "https:" + u
if u.startswith(("http://", "https://")):
return u
return urljoin(base, u)
play_url = id.split("$", 1)[1] if "$" in id else id
if not play_url:
return {"parse": 0, "url": "", "header": self.headers}
play_url = abs_url(play_url, self.site_url)
# 直链直接返回
if re.search(r"\.(m3u8|mp4|flv)(\?|$)", play_url, re.I):
return {"parse": 0, "url": play_url, "header": self.headers}
# 先请求当前地址(可能是详情页,也可能是 iframe 页)
try:
h = dict(self.headers)
h["Referer"] = self.site_url
r = self.sess.get(play_url, headers=h, timeout=10, verify=False)
if not (r and r.ok):
return {"parse": 1, "url": play_url, "header": self.headers}
html = r.text
except Exception:
return {"parse": 1, "url": play_url, "header": self.headers}
# 若是详情页,先提取 iframe
m_iframe = re.search(
r'<iframe[^>]*class=["\']player-iframe["\'][^>]*src=["\']([^"\']+)["\']',
html, re.I | re.S
)
if m_iframe:
iframe_url = abs_url(m_iframe.group(1).strip(), play_url)
else:
iframe_url = play_url
# 强制 M原版
iframe_url = force_m_stream(iframe_url)
# 请求 iframe 页面
try:
h2 = dict(self.headers)
h2["Referer"] = play_url
r2 = self.sess.get(iframe_url, headers=h2, timeout=10, verify=False)
if not (r2 and r2.ok):
return {"parse": 1, "url": iframe_url, "header": self.headers}
ihtml = r2.text
except Exception:
return {"parse": 1, "url": iframe_url, "header": self.headers}
final_url = ""
# 主解析var playurls = [...]
m = re.search(r"var\s+playurls\s*=\s*(\[[\s\S]*?\])\s*;", ihtml, re.I)
if m:
try:
arr = json.loads(m.group(1))
if isinstance(arr, list) and arr:
# playurls 通常已是当前线路的结果,取 auto 或第一个
target = None
for it in arr:
if isinstance(it, dict) and str(it.get("name", "")).lower() == "auto" and it.get("url"):
target = it
break
if target is None:
target = arr[0]
if isinstance(target, dict):
u = str(target.get("url", "")).strip().replace("\\/", "/")
u = unquote(u)
final_url = abs_url(u, iframe_url)
except Exception:
pass
# 兜底正则
if not final_url:
mm = re.search(r'https?://[^\s"\'<>]+?\.(?:m3u8|mp4|flv)(?:\?[^\s"\'<>]*)?', ihtml, re.I)
if mm:
final_url = mm.group(0).strip()
if final_url:
return {
"parse": 0,
"url": final_url,
"header": {
"User-Agent": self.headers.get("User-Agent", ""),
"Referer": iframe_url
}
}
return {"parse": 1, "url": iframe_url, "header": self.headers}

335
PY1/4K.py Normal file
View File

@@ -0,0 +1,335 @@
import requests
from bs4 import BeautifulSoup
import re
from base.spider import Spider
import sys
import json
import base64
import urllib.parse
from Crypto.Cipher import ARC4
from Crypto.Util.Padding import unpad
import binascii
sys.path.append('..')
xurl = "https://www.fullhd.xxx/zh/"
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'
}
pm = ''
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 extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
if pl == 3:
plx = []
while True:
start_index = text.find(start_str)
if start_index == -1:
break
end_index = text.find(end_str, start_index + len(start_str))
if end_index == -1:
break
middle_text = text[start_index + len(start_str):end_index]
plx.append(middle_text)
text = text.replace(start_str + middle_text + end_str, '')
if len(plx) > 0:
purl = ''
for i in range(len(plx)):
matches = re.findall(start_index1, plx[i])
output = ""
for match in matches:
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
if match3:
number = match3.group(1)
else:
number = 0
if 'http' not in match[0]:
output += f"#{'📽️' + match[1]}${number}{xurl}{match[0]}"
else:
output += f"#{'📽️' + match[1]}${number}{match[0]}"
output = output[1:]
purl = purl + output + "$$$"
purl = purl[:-3]
return purl
else:
return ""
else:
start_index = text.find(start_str)
if start_index == -1:
return ""
end_index = text.find(end_str, start_index + len(start_str))
if end_index == -1:
return ""
if pl == 0:
middle_text = text[start_index + len(start_str):end_index]
return middle_text.replace("\\", "")
if pl == 1:
middle_text = text[start_index + len(start_str):end_index]
matches = re.findall(start_index1, middle_text)
if matches:
jg = ' '.join(matches)
return jg
if pl == 2:
middle_text = text[start_index + len(start_str):end_index]
matches = re.findall(start_index1, middle_text)
if matches:
new_list = [f'{item}' for item in matches]
jg = '$$$'.join(new_list)
return jg
def homeContent(self, filter):
result = {}
result = {"class": [{"type_id": "latest-updates", "type_name": "最新视频🌠"},
{"type_id": "top-rated", "type_name": "最佳视频🌠"},
{"type_id": "most-popular", "type_name": "热门影片🌠"}],
}
return result
def homeVideoContent(self):
videos = []
try:
detail = requests.get(url=xurl, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
# Get videos from different sections
sections = {
"latest-updates": "最新视频",
"top-rated": "最佳视频",
"most-popular": "热门影片"
}
for section_id, section_name in sections.items():
section = doc.find('div', id=f"list_videos_videos_watched_right_now_items")
if not section:
continue
vods = section.find_all('div', class_="item")
for vod in vods:
names = vod.find_all('a')
name = names[0]['title'] if names and 'title' in names[0].attrs else section_name
ids = vod.find_all('a')
id = ids[0]['href'] if ids else ""
pics = vod.find('img', class_="lazyload")
pic = pics['data-src'] if pics and 'data-src' in pics.attrs else ""
if pic and 'http' not in pic:
pic = xurl + pic
remarks = vod.find('span', class_="duration")
remark = remarks.text.strip() if remarks else ""
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
result = {'list': videos}
return result
except Exception as e:
print(f"Error in homeVideoContent: {str(e)}")
return {'list': []}
def categoryContent(self, cid, pg, filter, ext):
result = {}
videos = []
try:
if pg and int(pg) > 1:
url = f'{xurl}/{cid}/{pg}/'
else:
url = f'{xurl}/{cid}/'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
section = doc.find('div', class_="list-videos")
if section:
vods = section.find_all('div', class_="item")
for vod in vods:
names = vod.find_all('a')
name = names[0]['title'] if names and 'title' in names[0].attrs else ""
ids = vod.find_all('a')
id = ids[0]['href'] if ids else ""
pics = vod.find('img', class_="lazyload")
pic = pics['data-src'] if pics and 'data-src' in pics.attrs else ""
if pic and 'http' not in pic:
pic = xurl + pic
remarks = vod.find('span', class_="duration")
remark = remarks.text.strip() if remarks else ""
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
except Exception as e:
print(f"Error in categoryContent: {str(e)}")
result = {
'list': videos,
'page': pg,
'pagecount': 9999,
'limit': 90,
'total': 999999
}
return result
def detailContent(self, ids):
global pm
did = ids[0]
result = {}
videos = []
playurl = ''
if 'http' not in did:
did = xurl + did
res1 = requests.get(url=did, headers=headerx)
res1.encoding = "utf-8"
res = res1.text
content = '👉' + self.extract_middle_text(res,'<h1>','</h1>', 0)
yanuan = self.extract_middle_text(res, '<span>Pornstars:</span>','</div>',1, 'href=".*?">(.*?)</a>')
bofang = did
videos.append({
"vod_id": did,
"vod_actor": yanuan,
"vod_director": '',
"vod_content": content,
"vod_play_from": '💗4K💗',
"vod_play_url": bofang
})
result['list'] = videos
return result
def playerContent(self, flag, id, vipFlags):
parts = id.split("http")
xiutan = 0
if xiutan == 0:
if len(parts) > 1:
before_https, after_https = parts[0], 'http' + parts[1]
res = requests.get(url=after_https, headers=headerx)
res = res.text
url2 = self.extract_middle_text(res, '<video', '</video>', 0).replace('\\', '')
soup = BeautifulSoup(url2, 'html.parser')
first_source = soup.find('source')
src_value = first_source.get('src')
response = requests.head(src_value, allow_redirects=False)
if response.status_code == 302:
redirect_url = response.headers['Location']
response = requests.head(redirect_url, allow_redirects=False)
if response.status_code == 302:
redirect_url = response.headers['Location']
result = {}
result["parse"] = xiutan
result["playUrl"] = ''
result["url"] = redirect_url
result["header"] = headerx
return result
def searchContentPage(self, key, quick, page):
result = {}
videos = []
if not page:
page = '1'
if page == '1':
url = f'{xurl}/search/{key}/'
else:
url = f'{xurl}/search/{key}/{str(page)}/'
try:
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
section = doc.find('div', class_="list-videos")
if section:
vods = section.find_all('div', class_="item")
for vod in vods:
names = vod.find_all('a')
name = names[0]['title'] if names and 'title' in names[0].attrs else ""
ids = vod.find_all('a')
id = ids[0]['href'] if ids else ""
pics = vod.find('img', class_="lazyload")
pic = pics['data-src'] if pics and 'data-src' in pics.attrs else ""
if pic and 'http' not in pic:
pic = xurl + pic
remarks = vod.find('span', class_="duration")
remark = remarks.text.strip() if remarks else ""
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
except Exception as e:
print(f"Error in searchContentPage: {str(e)}")
result = {
'list': videos,
'page': page,
'pagecount': 9999,
'limit': 90,
'total': 999999
}
return result
def searchContent(self, key, quick):
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

330
PY1/51吸瓜.py Normal file
View File

@@ -0,0 +1,330 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import random
import re
import sys
import threading
import time
import requests
from base64 import b64decode, b64encode
from urllib.parse import urlparse
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from pyquery import PyQuery as pq
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend="{}"):
config=json.loads(extend)
self.domin=config['site']
self.proxies = config.get('proxy',{}) or {}
self.plp = config.get('plp', '')
self.headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="134", "Google Chrome";v="134"',
'Accept-Language': 'zh-CN,zh;q=0.9'
}
self.host=self.host_late(self.gethosts())
self.headers.update({'Origin': self.host, 'Referer': f"{self.host}/"})
self.getcnh()
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeContent(self, filter):
data=pq(requests.get(self.host, headers=self.headers,proxies=self.proxies).content)
result = {}
classes = []
for k in list(data('.navbar-nav.mr-auto').children('li').items())[1:-3]:
if k('ul'):
for j in k('ul li').items():
classes.append({
'type_name': j('a').text(),
'type_id': j('a').attr('href').strip(),
})
else:
classes.append({
'type_name': k('a').text(),
'type_id': k('a').attr('href').strip(),
})
result['class'] = classes
result['list'] = self.getlist(data('#index article a'))
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
if '@folder' in tid:
id=tid.replace('@folder','')
videos=self.getfod(id)
else:
data=pq(requests.get(f"{self.host}{tid}{pg}", headers=self.headers,proxies=self.proxies).content)
videos=self.getlist(data('#archive article a'),tid)
result = {}
result['list'] = videos
result['page'] = pg
result['pagecount'] = 1 if '@folder' in tid else 99999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
url=ids[0] if ids[0].startswith("http") else f"{self.host}{ids[0]}"
data=pq(requests.get(url, headers=self.headers,proxies=self.proxies).content)
vod = {'vod_play_from': '51吸瓜'}
try:
clist = []
if data('.tags .keywords a'):
for k in data('.tags .keywords a').items():
title = k.text()
href = k.attr('href')
clist.append('[a=cr:' + json.dumps({'id': href, 'name': title}) + '/]' + title + '[/a]')
vod['vod_content'] = '点击展开↓↓↓\n'+' '.join(clist)
except:
vod['vod_content'] = data('.post-title').text()
try:
plist=[]
if data('.dplayer'):
for c, k in enumerate(data('.dplayer').items(), start=1):
config = json.loads(k.attr('data-config'))
plist.append(f"视频{c}${config['video']['url']}")
vod['vod_play_url']='#'.join(plist)
except:
vod['vod_play_url']=f"请停止活塞运动,可能没有视频${url}"
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
data=pq(requests.get(f"{self.host}/search/{key}/{pg}", headers=self.headers,proxies=self.proxies).content)
return {'list':self.getlist(data('#archive article a')),'page':pg}
def playerContent(self, flag, id, vipFlags):
p=0 if re.search(r'\.(m3u8|mp4|flv|ts|mkv|mov|avi|webm)', id) else 1
return {'parse': p, 'url': f"{self.plp}{id}", 'header': self.headers}
def localProxy(self, param):
try:
url=self.d64(param['url'])
match = re.search(r"loadBannerDirect\('([^']*)'", url)
if match:
url=match.group(1)
res = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=10)
return [200, res.headers.get('Content-Type'), self.aesimg(res.content)]
except Exception as e:
self.log(f"图片代理错误: {str(e)}")
return [500, 'text/html', '']
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
self.log(f"Base64编码错误: {str(e)}")
return ""
def d64(self, encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
self.log(f"Base64解码错误: {str(e)}")
return ""
def gethosts(self):
url=self.domin
curl=self.getCache('host_51cn')
if curl:
try:
data=pq(requests.get(curl, headers=self.headers, proxies=self.proxies).content)('a').attr('href')
if data:
parsed_url = urlparse(data)
url = parsed_url.scheme + "://" + parsed_url.netloc
except:
pass
try:
html = pq(requests.get(url, headers=self.headers, proxies=self.proxies).content)
html_pattern = r"Base64\.decode\('([^']+)'\)"
html_match = re.search(html_pattern, html('script').eq(-1).text(), re.DOTALL)
if not html_match:raise Exception("未找到html")
html = pq(b64decode(html_match.group(1)).decode())('script').eq(-4).text()
return self.hstr(html)
except Exception as e:
self.log(f"获取: {str(e)}")
return ""
def getcnh(self):
try:
data=pq(requests.get(f"{self.host}/homeway.html", headers=self.headers,proxies=self.proxies).content)
url=data('.post-content[itemprop="articleBody"] blockquote p').eq(0)('a').attr('href')
parsed_url = urlparse(url)
host = parsed_url.scheme + "://" + parsed_url.netloc
self.setCache('host_51cn',host)
except Exception as e:
self.log(f"获取: {str(e)}")
def hstr(self, html):
pattern = r"(backupLine\s*=\s*\[\])\s+(words\s*=)"
replacement = r"\1, \2"
html = re.sub(pattern, replacement, html)
data = f"""
var Vx = {{
range: function(start, end) {{
const result = [];
for (let i = start; i < end; i++) {{
result.push(i);
}}
return result;
}},
map: function(array, callback) {{
const result = [];
for (let i = 0; i < array.length; i++) {{
result.push(callback(array[i], i, array));
}}
return result;
}}
}};
Array.prototype.random = function() {{
return this[Math.floor(Math.random() * this.length)];
}};
var location = {{
protocol: "https:"
}};
function executeAndGetResults() {{
var allLines = lineAry.concat(backupLine);
var resultStr = JSON.stringify(allLines);
return resultStr;
}};
{html}
executeAndGetResults();
"""
return self.p_qjs(data)
def p_qjs(self, js_code):
try:
from com.whl.quickjs.wrapper import QuickJSContext
ctx = QuickJSContext.create()
result_json = ctx.evaluate(js_code)
ctx.destroy()
return json.loads(result_json)
except Exception as e:
self.log(f"执行失败: {e}")
return []
def get_domains(self):
html = pq(requests.get(self.domin, headers=self.headers,proxies=self.proxies).content)
html_pattern = r"Base64\.decode\('([^']+)'\)"
html_match = re.search(html_pattern, html('script').eq(-1).text(), re.DOTALL)
if not html_match:
raise Exception("未找到html")
html = b64decode(html_match.group(1)).decode()
words_pattern = r"words\s*=\s*'([^']+)'"
words_match = re.search(words_pattern, html, re.DOTALL)
if not words_match:
raise Exception("未找到words")
words = words_match.group(1).split(',')
main_pattern = r"lineAry\s*=.*?words\.random\(\)\s*\+\s*'\.([^']+)'"
domain_match = re.search(main_pattern, html, re.DOTALL)
if not domain_match:
raise Exception("未找到主域名")
domain_suffix = domain_match.group(1)
domains = []
for _ in range(3):
random_word = random.choice(words)
domain = f"https://{random_word}.{domain_suffix}"
domains.append(domain)
return domains
def getfod(self, id):
url = f"{self.host}{id}"
data = pq(requests.get(url, headers=self.headers, proxies=self.proxies).content)
vdata=data('.post-content[itemprop="articleBody"]')
r=['.txt-apps','.line','blockquote','.tags','.content-tabs']
for i in r:vdata.remove(i)
p=vdata('p')
videos=[]
for i,x in enumerate(vdata('h2').items()):
c=i*2
videos.append({
'vod_id': p.eq(c)('a').attr('href'),
'vod_name': p.eq(c).text(),
'vod_pic': f"{self.getProxyUrl()}&url={self.e64(p.eq(c+1)('img').attr('data-xkrkllgl'))}",
'vod_remarks':x.text()
})
return videos
def host_late(self, url_list):
if isinstance(url_list, str):
urls = [u.strip() for u in url_list.split(',')]
else:
urls = url_list
if len(urls) <= 1:
return urls[0] if urls else ''
results = {}
threads = []
def test_host(url):
try:
start_time = time.time()
response = requests.head(url,headers=self.headers,proxies=self.proxies,timeout=1.0, allow_redirects=False)
delay = (time.time() - start_time) * 1000
results[url] = delay
except Exception as e:
results[url] = float('inf')
for url in urls:
t = threading.Thread(target=test_host, args=(url,))
threads.append(t)
t.start()
for t in threads:
t.join()
return min(results.items(), key=lambda x: x[1])[0]
def getlist(self,data,tid=''):
videos = []
l='/mrdg' in tid
for k in data.items():
a=k.attr('href')
b=k('h2').text()
c=k('span[itemprop="datePublished"]').text()
if a and b and c:
videos.append({
'vod_id': f"{a}{'@folder' if l else ''}",
'vod_name': b.replace('\n', ' '),
'vod_pic': f"{self.getProxyUrl()}&url={self.e64(k('script').text())}",
'vod_remarks': c,
'vod_tag':'folder' if l else '',
'style': {"type": "rect", "ratio": 1.33}
})
return videos
def aesimg(self, word):
key = b'f5d965df75336270'
iv = b'97b60394abc2fbe1'
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = unpad(cipher.decrypt(word), AES.block_size)
return decrypted

404
PY1/51吸瓜动态版.py Normal file
View File

@@ -0,0 +1,404 @@
# -*- coding: utf-8 -*-
# 🌈 Love
import json
import random
import re
import sys
import threading
import time
from base64 import b64decode, b64encode
from urllib.parse import urlparse
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from pyquery import PyQuery as pq
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
try:self.proxies = json.loads(extend)
except:self.proxies = {}
self.headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
}
# Use working dynamic URLs directly
self.host = self.get_working_host()
self.headers.update({'Origin': self.host, 'Referer': f"{self.host}/"})
self.log(f"使用站点: {self.host}")
print(f"使用站点: {self.host}")
pass
def getName(self):
return "🌈 51吸瓜"
def isVideoFormat(self, url):
# Treat direct media formats as playable without parsing
return any(ext in (url or '') for ext in ['.m3u8', '.mp4', '.ts'])
def manualVideoCheck(self):
return False
def destroy(self):
pass
def homeContent(self, filter):
try:
response = requests.get(self.host, headers=self.headers, proxies=self.proxies, timeout=15)
if response.status_code != 200:
return {'class': [], 'list': []}
data = self.getpq(response.text)
result = {}
classes = []
# Try to get categories from different possible locations
category_selectors = [
'.category-list ul li',
'.nav-menu li',
'.menu li',
'nav ul li'
]
for selector in category_selectors:
for k in data(selector).items():
link = k('a')
href = (link.attr('href') or '').strip()
name = (link.text() or '').strip()
# Skip placeholder or invalid entries
if not href or href == '#' or not name:
continue
classes.append({
'type_name': name,
'type_id': href
})
if classes:
break
# If no categories found, create some default ones
if not classes:
classes = [
{'type_name': '首页', 'type_id': '/'},
{'type_name': '最新', 'type_id': '/latest/'},
{'type_name': '热门', 'type_id': '/hot/'}
]
result['class'] = classes
result['list'] = self.getlist(data('#index article a'))
return result
except Exception as e:
print(f"homeContent error: {e}")
return {'class': [], 'list': []}
def homeVideoContent(self):
try:
response = requests.get(self.host, headers=self.headers, proxies=self.proxies, timeout=15)
if response.status_code != 200:
return {'list': []}
data = self.getpq(response.text)
return {'list': self.getlist(data('#index article a, #archive article a'))}
except Exception as e:
print(f"homeVideoContent error: {e}")
return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
try:
if '@folder' in tid:
id = tid.replace('@folder', '')
videos = self.getfod(id)
else:
# Build URL properly
if tid.startswith('/'):
if pg and pg != '1':
url = f"{self.host}{tid}page/{pg}/"
else:
url = f"{self.host}{tid}"
else:
url = f"{self.host}/{tid}"
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
if response.status_code != 200:
return {'list': [], 'page': pg, 'pagecount': 1, 'limit': 90, 'total': 0}
data = self.getpq(response.text)
videos = self.getlist(data('#archive article a, #index article a'), tid)
result = {}
result['list'] = videos
result['page'] = pg
result['pagecount'] = 1 if '@folder' in tid else 99999
result['limit'] = 90
result['total'] = 999999
return result
except Exception as e:
print(f"categoryContent error: {e}")
return {'list': [], 'page': pg, 'pagecount': 1, 'limit': 90, 'total': 0}
def detailContent(self, ids):
try:
url = f"{self.host}{ids[0]}" if not ids[0].startswith('http') else ids[0]
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
if response.status_code != 200:
return {'list': [{'vod_play_from': '51吸瓜', 'vod_play_url': f'页面加载失败${url}'}]}
data = self.getpq(response.text)
vod = {'vod_play_from': '51吸瓜'}
# Get content/description
try:
clist = []
if data('.tags .keywords a'):
for k in data('.tags .keywords a').items():
title = k.text()
href = k.attr('href')
if title and href:
clist.append('[a=cr:' + json.dumps({'id': href, 'name': title}) + '/]' + title + '[/a]')
vod['vod_content'] = ' '.join(clist) if clist else data('.post-title').text()
except:
vod['vod_content'] = data('.post-title').text() or '51吸瓜视频'
# Get video URLs (build episode list when multiple players exist)
try:
plist = []
used_names = set()
if data('.dplayer'):
for c, k in enumerate(data('.dplayer').items(), start=1):
config_attr = k.attr('data-config')
if config_attr:
try:
config = json.loads(config_attr)
video_url = config.get('video', {}).get('url', '')
# Determine a readable episode name from nearby headings if present
ep_name = ''
try:
parent = k.parents().eq(0)
# search up to a few ancestors for a heading text
for _ in range(3):
if not parent: break
heading = parent.find('h2, h3, h4').eq(0).text() or ''
heading = heading.strip()
if heading:
ep_name = heading
break
parent = parent.parents().eq(0)
except Exception:
ep_name = ''
base_name = ep_name if ep_name else f"视频{c}"
name = base_name
count = 2
# Ensure the name is unique
while name in used_names:
name = f"{base_name} {count}"
count += 1
used_names.add(name)
if video_url:
self.log(f"解析到视频: {name} -> {video_url}")
print(f"解析到视频: {name} -> {video_url}")
plist.append(f"{name}${video_url}")
except:
continue
if plist:
self.log(f"拼装播放列表,共{len(plist)}")
print(f"拼装播放列表,共{len(plist)}")
vod['vod_play_url'] = '#'.join(plist)
else:
vod['vod_play_url'] = f"未找到视频源${url}"
except Exception as e:
vod['vod_play_url'] = f"视频解析失败${url}"
return {'list': [vod]}
except Exception as e:
print(f"detailContent error: {e}")
return {'list': [{'vod_play_from': '51吸瓜', 'vod_play_url': f'详情页加载失败${ids[0] if ids else ""}'}]}
def searchContent(self, key, quick, pg="1"):
try:
url = f"{self.host}/search/{key}/{pg}" if pg != "1" else f"{self.host}/search/{key}/"
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
if response.status_code != 200:
return {'list': [], 'page': pg}
data = self.getpq(response.text)
videos = self.getlist(data('#archive article a, #index article a'))
return {'list': videos, 'page': pg}
except Exception as e:
print(f"searchContent error: {e}")
return {'list': [], 'page': pg}
def playerContent(self, flag, id, vipFlags):
url = id
p = 1
if self.isVideoFormat(url):
# m3u8/mp4 direct play; when using proxy setting, wrap to proxy for m3u8
if '.m3u8' in url:
url = self.proxy(url)
p = 0
self.log(f"播放请求: parse={p}, url={url}")
print(f"播放请求: parse={p}, url={url}")
return {'parse': p, 'url': url, 'header': self.headers}
def localProxy(self, param):
if param.get('type') == 'img':
res=requests.get(param['url'], headers=self.headers, proxies=self.proxies, timeout=10)
return [200,res.headers.get('Content-Type'),self.aesimg(res.content)]
elif param.get('type') == 'm3u8':return self.m3Proxy(param['url'])
else:return self.tsProxy(param['url'])
def proxy(self, data, type='m3u8'):
if data and len(self.proxies):return f"{self.getProxyUrl()}&url={self.e64(data)}&type={type}"
else:return data
def m3Proxy(self, url):
url=self.d64(url)
ydata = requests.get(url, headers=self.headers, proxies=self.proxies, allow_redirects=False)
data = ydata.content.decode('utf-8')
if ydata.headers.get('Location'):
url = ydata.headers['Location']
data = requests.get(url, headers=self.headers, proxies=self.proxies).content.decode('utf-8')
lines = data.strip().split('\n')
last_r = url[:url.rfind('/')]
parsed_url = urlparse(url)
durl = parsed_url.scheme + "://" + parsed_url.netloc
iskey=True
for index, string in enumerate(lines):
if iskey and 'URI' in string:
pattern = r'URI="([^"]*)"'
match = re.search(pattern, string)
if match:
lines[index] = re.sub(pattern, f'URI="{self.proxy(match.group(1), "mkey")}"', string)
iskey=False
continue
if '#EXT' not in string:
if 'http' not in string:
domain = last_r if string.count('/') < 2 else durl
string = domain + ('' if string.startswith('/') else '/') + string
lines[index] = self.proxy(string, string.split('.')[-1].split('?')[0])
data = '\n'.join(lines)
return [200, "application/vnd.apple.mpegur", data]
def tsProxy(self, url):
url = self.d64(url)
data = requests.get(url, headers=self.headers, proxies=self.proxies, stream=True)
return [200, data.headers['Content-Type'], data.content]
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64编码错误: {str(e)}")
return ""
def d64(self, encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64解码错误: {str(e)}")
return ""
def get_working_host(self):
"""Get working host from known dynamic URLs"""
# Known working URLs from the dynamic gateway
dynamic_urls = [
'https://actor.zncielnl.cc/',
'https://ability.vgwtswi.xyz',
'https://am.vgwtswi.xyz'
]
# Test each URL to find a working one
for url in dynamic_urls:
try:
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=10)
if response.status_code == 200:
# Verify it has the expected content structure
data = self.getpq(response.text)
articles = data('#index article a')
if len(articles) > 0:
self.log(f"选用可用站点: {url}")
print(f"选用可用站点: {url}")
return url
except Exception as e:
continue
# Fallback to first URL if none work (better than crashing)
self.log(f"未检测到可用站点,回退: {dynamic_urls[0]}")
print(f"未检测到可用站点,回退: {dynamic_urls[0]}")
return dynamic_urls[0]
def getlist(self, data, tid=''):
videos = []
l = '/mrdg' in tid
for k in data.items():
a = k.attr('href')
b = k('h2').text()
# Some pages might not include datePublished; use a fallback
c = k('span[itemprop="datePublished"]').text() or k('.post-meta, .entry-meta, time').text()
if a and b:
videos.append({
'vod_id': f"{a}{'@folder' if l else ''}",
'vod_name': b.replace('\n', ' '),
'vod_pic': self.getimg(k('script').text()),
'vod_remarks': c or '',
'vod_tag': 'folder' if l else '',
'style': {"type": "rect", "ratio": 1.33}
})
return videos
def getfod(self, id):
url = f"{self.host}{id}"
data = self.getpq(requests.get(url, headers=self.headers, proxies=self.proxies).text)
vdata=data('.post-content[itemprop="articleBody"]')
r=['.txt-apps','.line','blockquote','.tags','.content-tabs']
for i in r:vdata.remove(i)
p=vdata('p')
videos=[]
for i,x in enumerate(vdata('h2').items()):
c=i*2
videos.append({
'vod_id': p.eq(c)('a').attr('href'),
'vod_name': p.eq(c).text(),
'vod_pic': f"{self.getProxyUrl()}&url={p.eq(c+1)('img').attr('data-xkrkllgl')}&type=img",
'vod_remarks':x.text()
})
return videos
def getimg(self, text):
match = re.search(r"loadBannerDirect\('([^']+)'", text)
if match:
url = match.group(1)
return f"{self.getProxyUrl()}&url={url}&type=img"
else:
return ''
def aesimg(self, word):
key = b'f5d965df75336270'
iv = b'97b60394abc2fbe1'
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = unpad(cipher.decrypt(word), AES.block_size)
return decrypted
def getpq(self, data):
try:
return pq(data)
except Exception as e:
print(f"{str(e)}")
return pq(data.encode('utf-8'))

318
PY1/58视频.py Normal file
View File

@@ -0,0 +1,318 @@
from Crypto.Cipher import AES
from base.spider import Spider
import re,sys,json,base64,requests
from Crypto.Util.Padding import unpad
from urllib.parse import quote, unquote, urljoin
sys.path.append('..')
class Spider(Spider):
headers = {
'User-Agent': "Mozilla/5.0 (Linux; Android 12; SM-S9080 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36 uni-app Html5Plus/1.0 (Immersed/0.6666667)",
'Connection': "Keep-Alive",
'Accept-Encoding': "gzip",
'Version': "1.3.26",
'Token': ""
}
play_headers = {'User-Agent': 'io.dcloud.application.DCloudApplication/1.3.26 (Linux;Android 12)'}
host, datakey, dataiv, deviceid, home_class, block_id,bn = '', '', '', '', '',[],b'\xe6\x83\x85\xe8\x89\xb2'
def init(self, extend=""):
try:
config = json.loads(extend)
except (json.JSONDecodeError, TypeError):
config = {}
self.host = config.get("host", "https://58api.zggggs.com")
self.datakey = config.get("datakey", "58928cae68092afc")
self.dataiv = config.get("dataiv", "e9d732a1edcdcc0a")
self.deviceid = config.get("deviceid", "d60ddbcd469741f68e2755dca38f5171")
payload = {
'UserId': "0",
'device_id': self.host
}
response = self.post(f'{self.host}/addons/appto/app.php/tindex/home_config2', data=payload, headers=self.headers).json()
data = self.decrypt(response['data'])
data2 = json.loads(data)
block_id = []
for i in data2['viphome']:
block_id.append(i['id'])
self.block_id = block_id
self.home_class = data2['home']
def homeContent(self, filter):
home_class = self.home_class
classes = []
for i in home_class:
if i['id'] == 0:
continue
classes.append({'type_id':i['id'],'type_name':i['title']})
return {'class': classes}
def homeVideoContent(self):
payload = {
'UserId': "0",
'device_id': self.deviceid,
'Id': "0",
'Type': "1",
'Page': "1",
'Limit': "10"
}
response = self.post(f'{self.host}/addons/appto/app.php/tindex/home_vod_list2', data=payload, headers=self.headers).json()
data = self.decrypt(response['data'])
data2 = json.loads(data)
vods = []
for i in data2['sections']:
vods.extend(i['vods'])
vods.extend(data2['vods'])
videos = []
for i in vods:
if i['type_id'] in self.block_id or i['group_id'] != 0 or self.bn.decode('utf-8') in i['vod_class']:
continue
vod_pic = i.get('vod_pic')
if vod_pic.startswith('mac://'):
vod_pic = vod_pic.replace('mac://', 'https://', 1)
videos.append({
'vod_id': i.get('vod_id'),
'vod_name': i.get('vod_name'),
'vod_class': i.get('vod_class'),
'vod_pic': vod_pic,
'vod_remarks': i.get('vod_remarks'),
'vod_score': i.get('vod_score')
})
return {'list': videos}
def detailContent(self, ids):
payload = {
'UserId': "0",
'device_id': self.deviceid,
'id': ids
}
data = self.post(f"{self.host}/addons/appto/app.php/tindex/page_player", data=payload, headers=self.headers).json()
data2 = self.decrypt(data['data'])
data3 = json.loads(data2)
if data3['type_id'] in self.block_id:
return {'list': []}
if not data3['group_id'] == 0:
return {'list': []}
videos = []
videos.append({
'vod_id': data3.get('vod_id'),
'vod_name': data3.get('vod_name'),
'vod_content': data3.get('vod_blurb'),
'vod_remarks': data3.get('vod_serial'),
'vod_year': data3.get('vod_year'),
'vod_area': data3.get('vod_area'),
'vod_play_from': '58视频',
'vod_play_url': data3['vod_play_url']
})
return {'list': videos}
def searchContent(self, key, quick, pg="1"):
url = f"{self.host}/addons/appto/app.php/tindex/search_film"
videos = []
type_list = {'film','short'}
for search_type in type_list:
payload = {
'UserId': "0",
'device_id': self.deviceid,
'Search': key,
'type': search_type,
'Page': pg,
'Limit': "10"
}
response = self.post(url, data=payload, headers=self.headers).json()
data = self.decrypt(response['data'])
vods =json.loads(data)['vods']
for i in vods['list']:
if i['type_id'] in self.block_id or self.bn.decode('utf-8') in i['vod_class'] or b'\xe4\xbc\x9a\xe5\x91\x98'.decode('utf-8') in i['vod_type_name']:
continue
vod_pic = i['vod_pic']
if vod_pic.startswith('mac://'):
vod_pic = vod_pic.replace('mac://', 'https://', 1)
video = {
"vod_id": i['vod_id'],
"vod_name": i['vod_name'],
"vod_class": i['vod_class'],
"vod_pic": vod_pic,
"vod_remarks": i['vod_remarks']
}
videos.append(video)
return {'list': videos, 'page': pg, 'limit': vods['limit'], 'total': vods['total']}
def categoryContent(self, tid, pg, filter, extend):
payload = {
'UserId': "0",
'device_id': self.host,
'Id': tid,
'Type': "1",
'Page': pg,
'Limit': "10"
}
response = self.post(f'{self.host}/addons/appto/app.php/tindex/home_vod_list2', data=payload,headers=self.headers).json()
data = self.decrypt(response['data'])
data2 = json.loads(data)
videos = []
for i in data2['vods']:
if 'payload' in i or 'banner' in i['vod_class']:
continue
vod_pic = i.get('vod_pic')
if vod_pic.startswith('mac://'):
vod_pic = vod_pic.replace('mac://', 'https://', 1)
videos.append({
'vod_id': i.get('vod_id'),
'vod_name': i.get('vod_name'),
'vod_class': i.get('vod_class'),
'vod_pic': vod_pic,
'vod_score':i.get('vod_score'),
'vod_remarks': i.get('vod_remarks'),
'vod_score': i.get('vod_score')
})
return {'list': videos}
def playerContent(self, flag, id, vipFlags):
if '.m3u8' in id:
try:
proxyurl = f'{self.getProxyUrl(True)}&type=58sp'
except Exception:
proxyurl = 'http://127.0.0.1:9978/proxy?do=py&type=58sp'
url = f"{proxyurl}&url={quote(id,safe='')}"
return {'jx': 0, 'playUrl': '', 'parse': 0, 'url': url,'header': self.play_headers}
def proxy58sp(self, params):
url = unquote(params['url'])
data = self.modify_m3u8(url)
return [200, "application/vnd.apple.mpegurl", data]
def decrypt(self,ciphertext):
try:
ciphertext = base64.b64decode(ciphertext)
key_bytes = self.datakey.encode('utf-8')
iv_bytes = self.dataiv.encode('utf-8')
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size)
return decrypted_data.decode('utf-8')
except Exception as e:
return None
def modify_m3u8(self, url, retries=3, timeout=10):
current_url = url
while True:
try:
for attempt in range(retries):
try:
response = requests.get(current_url, timeout=timeout,headers=self.play_headers)
response.raise_for_status()
content = response.text
break
except (requests.RequestException, ValueError) as e:
if attempt == retries - 1:
raise Exception(f"请求失败: {str(e)}")
print(f"请求尝试 {attempt + 1}/{retries} 失败,正在重试...")
base_url = current_url.rsplit('/', 1)[0] + '/'
lines = content.strip().split('\n')
is_master_playlist = any(line.startswith('#EXT-X-STREAM-INF:') for line in lines)
if is_master_playlist:
highest_bandwidth = 0
best_playlist_url = None
bandwidth_regex = re.compile(r'BANDWIDTH=(\d+)')
for i, line in enumerate(lines):
if line.startswith('#EXT-X-STREAM-INF:'):
match = bandwidth_regex.search(line)
if match:
bandwidth = int(match.group(1))
if i + 1 < len(lines) and not lines[i + 1].startswith('#'):
playlist_url = lines[i + 1].strip()
if not playlist_url.startswith(('http:', 'https:')):
playlist_url = urljoin(base_url, playlist_url)
if bandwidth > highest_bandwidth:
highest_bandwidth = bandwidth
best_playlist_url = playlist_url
if best_playlist_url:
print(f"选择最高清晰度流: {highest_bandwidth}bps")
current_url = best_playlist_url
continue
else:
raise Exception("未找到有效的子播放列表")
key_regex = re.compile(r'#EXT-X-KEY:(.*)URI="([^"]+)"(.*)')
segment_regex = re.compile(r'#EXTINF:([\d.]+),')
m3u8_output = []
first_segment_index = -1
segment_durations = []
segment_indices = []
for i, line in enumerate(lines):
if line.startswith('#EXTINF:'):
match = segment_regex.search(line)
if match:
duration = float(match.group(1))
segment_durations.append(duration)
segment_indices.append(i)
modified_remove_start_indices = []
if len(segment_durations) >= 2:
second_duration_str = "{0:.3f}".format(segment_durations[1])
if second_duration_str.endswith('67'):
print(f"第2个分片({second_duration_str})符合规则将删除前2个分片")
modified_remove_start_indices = segment_indices[:2]
elif len(segment_durations) >= 3:
third_duration_str = "{0:.3f}".format(segment_durations[2])
if third_duration_str.endswith('67'):
print(f"第3个分片({third_duration_str})符合规则将删除前3个分片")
modified_remove_start_indices = segment_indices[:3]
lines_to_remove = set()
for seg_idx in modified_remove_start_indices:
lines_to_remove.add(seg_idx)
if seg_idx + 1 < len(lines):
lines_to_remove.add(seg_idx + 1)
for i, line in enumerate(lines):
line = line.strip()
if not line:
continue
if line.startswith('#EXT-X-KEY:'):
match = key_regex.search(line)
if match:
prefix = match.group(1)
key_url = match.group(2)
suffix = match.group(3)
if not key_url.startswith(('http:', 'https:')):
key_url = urljoin(base_url, key_url)
updated_key_line = f'#EXT-X-KEY:{prefix}URI="{key_url}"{suffix}'
m3u8_output.append(updated_key_line)
print(f"补全加密KEY URL: {key_url}")
continue
if i in lines_to_remove:
print(f"移除行: {line}")
continue
if not line.startswith('#') and i > first_segment_index:
segment_url = line
if not segment_url.startswith(('http:', 'https:')):
segment_url = urljoin(base_url, segment_url)
m3u8_output.append(segment_url)
else:
m3u8_output.append(line)
if first_segment_index == -1 and line.startswith('#EXTINF:'):
first_segment_index = i
if not any(not line.startswith('#') for line in m3u8_output):
raise Exception("未找到TS片段")
if not m3u8_output or not m3u8_output[0].startswith('#EXTM3U'):
m3u8_output.insert(0, '#EXTM3U')
return '\n'.join(m3u8_output)
except Exception as e:
return f"#EXTM3U\n#EXT-X-ERROR:{str(e)}"
def localProxy(self, params):
if params['type'] == "58sp":
return self.proxy58sp(params)
return None
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass

110
PY1/91.py Normal file
View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import sys
from Crypto.Cipher import AES
from pyquery import PyQuery as pq
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend='{}'):
pass
def destroy(self):
pass
host='https://91-short.com'
headers = {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="141", "Google Chrome";v="141"',
'sec-ch-ua-platform': '"Windows"',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.55 Safari/537.36',
}
cache={}
def getvs(self,data):
videos = []
for i in data.items():
a = i("a")
videos.append({
'vod_id': a.attr('href'),
'vod_name': a.attr('title'),
'vod_pic': self.getProxyUrl()+"&url="+i("img").attr("data-cover"),
'vod_remark': i(".module-item-caption").text() or i(".module-item-ru").text(),
})
return videos
def homeContent(self, filter):
resp=self.fetch(self.host,headers=self.headers)
tab1=pq(resp.content)("#tablist > a")
resp = self.fetch(f"{self.host}/film/home_recommend_list", headers=self.headers)
tab2 = pq(resp.content)("#tablist > a")
classes = []
for k in (tab1+tab2).items():
href=k.attr('href')
if not href or "http" in href:
continue
classes.append({
'type_name': k.text(),
'type_id': href,
})
return {'class':classes}
def categoryContent(self, tid, pg, filter, extend):
if pg=="1":
resp=self.fetch(self.host+tid,headers=self.headers)
qu=".module-items > .module-item > .module-item-cover"
doc=pq(resp.content)
stext=doc('main').next('script').html()
self.cache[tid]=stext.strip().split('\n',1)[0].strip().split('=',1)[-1].replace('"','').strip()
else:
resp=self.fetch(self.host+self.cache[tid],headers=self.headers)
qu = ".module-item > .module-item-cover"
doc=pq(resp.content.decode())
self.cache[tid]=doc("script").eq(-1).text()
result = {}
result['list'] = self.getvs(doc(qu))
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
resp=self.fetch(self.host+ids[0],headers=self.headers)
doc=pq(resp.content)
stext=doc('.player-wrapper > script').eq(-1).html().strip()
try:
url=stext.split('\n')[-1].split('=')[-1].replace('"','').strip()
p=0
except Exception as e:
url=self.host+ids[0]
p=1
vod = {
'vod_director': '沐辰',
'vod_play_from': '91——short',
'vod_play_url': f'{doc(".module-item-in").text() or doc("h2.module-title").text()}${url}@@{p}'
}
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
resp=self.fetch(f'{self.host}/search',headers=self.headers,params={'wd':key})
qu = ".module-items > .module-item > .module-item-cover"
data = pq(resp.content)(qu)
return {'list':self.getvs(data),'page':pg}
def playerContent(self, flag, id, vipFlags):
url,p=id.split('@@')
return {'parse': int(p), 'url': url}
def localProxy(self, param):
res=self.fetch(param['url'])
key = b'Jui7X#cdleN^3eZb'
cipher = AES.new(key, AES.MODE_ECB)
decrypted = cipher.decrypt(res.content)
return [200,res.headers.get('Content-Type'),decrypted]

236
PY1/911.py Normal file
View File

@@ -0,0 +1,236 @@
# coding=utf-8
# !/python
import sys
import requests
from bs4 import BeautifulSoup
import re
from urllib.parse import urljoin
from base.spider import Spider
import time
sys.path.append('..')
# 全局配置
xurl = "https://911blw.com"
backup_urls = ["https://hlj.fun", "https://911bl16.com"]
headerx = {
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1",
"Referer": "https://911blw.com",
"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"
}
IMAGE_FILTER = ["/usr/themes/ads-close.png", "close", "icon", "logo"]
class Spider(Spider):
def getName(self):
return "911爆料网"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def fetch_page(self, url, use_backup=False):
global xurl
original_url = url
if use_backup:
for backup in backup_urls:
test_url = url.replace(xurl, backup)
try:
time.sleep(1)
res = requests.get(test_url, headers=headerx, timeout=10)
res.raise_for_status()
res.encoding = "utf-8"
text = res.text
if len(text) > 1000:
print(f"[DEBUG] 使用备用 {backup}: {test_url}")
return text
except:
continue
print(f"[ERROR] 所有备用失败,回退原 URL")
try:
time.sleep(1)
res = requests.get(original_url, headers=headerx, timeout=10)
res.raise_for_status()
res.encoding = "utf-8"
text = res.text
doc = BeautifulSoup(text, "html.parser")
title = doc.title.string if doc.title else "无标题"
print(f"[DEBUG] 页面 {original_url}: 长度={len(text)}, 标题={title}")
if len(text) < 1000:
print(f"[DEBUG] 内容过短,尝试备用域名")
return self.fetch_page(original_url, use_backup=True)
return text
except Exception as e:
print(f"[ERROR] 请求失败 {original_url}: {e}")
return None
def extract_content(self, html, url):
videos = []
if not html:
return videos
doc = BeautifulSoup(html, "html.parser")
containers = doc.select("ul.row li, div.article-item, article, .post-item, div[class*='item']")
print(f"[DEBUG] 找到 {len(containers)} 个容器")
for i, vod in enumerate(containers[:20], 1):
try:
# 标题
title_elem = vod.select_one("h2.headline, .headline, a[title]")
name = title_elem.get("title") or title_elem.get_text(strip=True) if title_elem else ""
if not name:
name_match = re.search(r'headline">(.+?)<', str(vod))
name = name_match.group(1).strip() if name_match else ""
# 链接
link_elem = vod.select_one("a")
id = urljoin(xurl, link_elem["href"]) if link_elem else ""
# 备注
remarks_elem = vod.select_one("span.small, time, .date")
remarks = remarks_elem.get_text(strip=True) if remarks_elem else ""
if not remarks:
remarks_match = re.search(r'datePublished[^>]*>(.+?)<', str(vod))
remarks = remarks_match.group(1).strip() if remarks_match else ""
# 图片 - 扩展属性
img = vod.select_one("img")
pic = None
if img:
# 检查多种图片属性
for attr in ["data-lazy-src", "data-original", "data-src", "src"]:
pic = img.get(attr)
if pic:
break
# 检查背景图片
if not pic:
bg_div = vod.select_one("div[style*='background-image']")
if bg_div and "background-image" in bg_div.get("style", ""):
bg_match = re.search(r'url\([\'"]?(.+?)[\'"]?\)', bg_div["style"])
pic = bg_match.group(1) if bg_match else None
if pic:
pic = urljoin(xurl, pic)
alt = img.get("alt", "").lower() if img else ""
if any(f in pic.lower() or f in alt for f in IMAGE_FILTER):
pic = None
print(f"[DEBUG] 项 {i} 图片: {pic}, 属性={img.attrs if img else '无img'}")
# 简介
desc_match = re.search(r'og:description" content="(.+?)"', html)
description = desc_match.group(1) if desc_match else ""
if name and id:
video = {
"vod_id": id,
"vod_name": name[:100],
"vod_pic": pic,
"vod_remarks": remarks,
"vod_content": description
}
videos.append(video)
print(f"[DEBUG] 项 {i}: 标题={name[:50]}..., 链接={id}, 图片={pic}")
except Exception as e:
print(f"[DEBUG] 项 {i} 错误: {e}")
continue
print(f"[DEBUG] 提取 {len(videos)} 个项")
return videos
def homeVideoContent(self):
url = f"{xurl}/category/jrgb/1/"
html = self.fetch_page(url)
videos = self.extract_content(html, url)
return {'list': videos}
def homeContent(self, filter):
result = {'class': []}
categories = [
{"type_id": "/category/jrgb/", "type_name": "最新爆料"},
{"type_id": "/category/rmgb/", "type_name": "精选大瓜"},
{"type_id": "/category/blqw/", "type_name": "猎奇吃瓜"},
{"type_id": "/category/rlph/", "type_name": "TOP5大瓜"},
{"type_id": "/category/ssdbl/", "type_name": "社会热点"},
{"type_id": "/category/hjsq/", "type_name": "海角社区"},
{"type_id": "/category/mrds/", "type_name": "每日大赛"},
{"type_id": "/category/xyss/", "type_name": "校园吃瓜"},
{"type_id": "/category/mxhl/", "type_name": "明星吃瓜"},
{"type_id": "/category/whbl/", "type_name": "网红爆料"},
{"type_id": "/category/bgzq/", "type_name": "反差爆料"},
{"type_id": "/category/fljq/", "type_name": "网黄福利"},
{"type_id": "/category/crfys/", "type_name": "午夜剧场"},
{"type_id": "/category/thjx/", "type_name": "探花经典"},
{"type_id": "/category/dmhv/", "type_name": "禁漫天堂"},
{"type_id": "/category/slec/", "type_name": "吃瓜精选"},
{"type_id": "/category/zksr/", "type_name": "重口调教"},
{"type_id": "/category/crlz/", "type_name": "精选连载"}
]
result['class'] = categories
return result
def categoryContent(self, cid, pg, filter, ext):
url = f"{xurl}{cid}{pg}/" if pg != "1" else f"{xurl}{cid}"
html = self.fetch_page(url)
videos = self.extract_content(html, url)
return {
'list': videos,
'page': pg,
'pagecount': 9999,
'limit': 90,
'total': 999999
}
def detailContent(self, ids):
videos = []
did = ids[0]
html = self.fetch_page(did)
if html:
source_match = re.search(r'"url":"(.*?)"', html)
purl = source_match.group(1).replace("\\", "") if source_match else ""
videos.append({
"vod_id": did,
"vod_play_from": "爆料",
"vod_play_url": purl,
"vod_content": re.search(r'og:description" content="(.+?)"', html).group(1) if re.search(r'og:description" content="(.+?)"', html) else ""
})
return {'list': videos}
def playerContent(self, flag, id, vipFlags):
return {"parse": 0, "playUrl": "", "url": id, "header": headerx}
def searchContent(self, key, quick):
return self.searchContentPage(key, quick, "1")
def searchContentPage(self, key, quick, page):
url = f"{xurl}/search/{key}/{page}/"
html = self.fetch_page(url)
videos = self.extract_content(html, url)
return {'list': videos, 'page': page, 'pagecount': 9999, 'limit': 90, 'total': 999999}
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 = Spider()
# 测试首页推荐
result = spider.homeVideoContent()
print(f"测试首页推荐: {len(result['list'])} 个项")
for item in result['list'][:3]:
print(item)
# 测试分类
for cate in ["jrgb", "rmgb", "blqw"]:
result = spider.categoryContent(f"/category/{cate}/", "1", False, {})
print(f"测试分类 {cate}: {len(result['list'])} 个项")
for item in result['list'][:2]:
print(item)

84
PY1/91RB1.py Normal file
View File

@@ -0,0 +1,84 @@
#author Kyle
import re, sys, time, urllib.parse
sys.path.append('..')
from base.spider import Spider as BaseSpider
class Spider(BaseSpider):
def __init__(self):
super().__init__(); self.base = 'https://www.91rb.com'; self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Referer': self.base + '/'}
def getName(self): return '91热爆'
def init(self, extend=""): self.extend = extend or ''; return {'class': 'movie'}
def isVideoFormat(self, url): return bool(re.search(r'\.(m3u8|mp4)(\?|$)', url))
def manualVideoCheck(self): return False
def destroy(self): pass
def homeContent(self, filter): return {'class': [{'type_name': '最新上传', 'type_id': 'latest-updates'}, {'type_name': '热门视频', 'type_id': 'most-popular'}, {'type_name': '收藏最多', 'type_id': 'most-favourited'}, {'type_name': '日本AV', 'type_id': 'tags/av2/'}, {'type_name': 'jav', 'type_id': 'tags/jav/'}, {'type_name': '韩国', 'type_id': 'tags/20c3f16d021b069ce3af1da50b15bd83/'}]}
def homeVideoContent(self):
try: return self._listPage(self._buildListUrl('latest-updates', '1'))
except Exception as e: self.log(f'homeVideoContent error: {e}'); return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
try: return self._listPage(self._buildListUrl(tid, pg), page=pg)
except Exception as e: self.log(f'categoryContent error: {e}'); return {'list': [], 'page': pg, 'pagecount': 1, 'limit': 48, 'total': 0}
def detailContent(self, ids):
vid = self._ensure_id(ids[0]); detail_url = f"{self.base}/videos/{vid}/"; name = f'视频 {vid}'; pic = ''
try:
r = self.fetch(detail_url, headers=self.headers, timeout=10, allow_redirects=True)
if r and hasattr(r, 'text'):
doc = self.html(r.text)
if doc:
name = ''.join(doc.xpath('//h1//text()')).strip() or name
ogs = doc.xpath('//meta[@property="og:image"]/@content'); tws = doc.xpath('//meta[@name="twitter:image"]/@content')
pic = ogs[0].strip() if ogs else (tws[0].strip() if tws else '')
if pic: pic = self._abs_url(pic)
except Exception as e: self.log(f'detailContent fetch error: {e}')
if not pic: pic = self._cover_fallback(vid)
vod = {'vod_id': str(vid), 'vod_name': name, 'vod_pic': pic, 'type_name': '', 'vod_year': '', 'vod_remarks': '', 'vod_content': '', 'vod_play_from': '91RB', 'vod_play_url': f'正片${vid}'}
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
key_enc = urllib.parse.quote(key); url = f"{self.base}/search/{key_enc}/"
if pg != '1': url = url.rstrip('/') + f'/{pg}/'
try: return self._listPage(url, page=pg)
except Exception as e: self.log(f'searchContent error: {e}'); return {'list': [], 'page': pg, 'pagecount': 1, 'total': 0}
def playerContent(self, flag, id, vipFlags):
vid = self._ensure_id(id); group = int(vid) - (int(vid) % 1000)
m3u8 = f"https://91rbnet.gslb-al.com/hls/contents/videos/{group}/{vid}/{vid}.mp4/index.m3u8"
try:
r = self.fetch(m3u8, headers=self.headers, timeout=5, allow_redirects=True, verify=True, stream=True)
if r.status_code >= 400: self.log(f'm3u8 head status={r.status_code}, fallback to direct anyway')
except Exception as e: self.log(f'playerContent HEAD error: {e}')
return {'parse': 0, 'playUrl': '', 'url': m3u8, 'header': self.headers}
def localProxy(self, param): return None
def _buildListUrl(self, tid, pg):
path = tid.strip('/') or 'latest-updates'; page_suffix = f"/{pg}/" if str(pg) != '1' else '/'
if path.startswith('categories') or path in ['latest-updates', 'most-popular', 'most-favourited']: return f"{self.base}/{path}{page_suffix}"
return f"{self.base}/{path}{page_suffix}"
def _abs_url(self, url):
if not url: return url
u = url.strip()
return 'https:' + u if u.startswith('//') else (self.base + u if u.startswith('/') else u)
def _parse_srcset_first(self, srcset):
if not srcset: return ''
return srcset.split(',')[0].strip().split(' ')[0]
def _cover_fallback(self, vid):
try: iv = int(vid); group = iv - (iv % 1000); return f'https://rimg.iomycdn.com/videos_screenshots/{group}/{iv}/preview.jpg'
except Exception: return ''
def _listPage(self, url, page='1'):
doc = self.html(self.fetch(url, headers=self.headers, timeout=10).text)
if doc is None: return {'list': [], 'page': page, 'pagecount': 1, 'total': 0}
nodes, videos, seen = doc.xpath('//main//a[contains(@href, "/videos/")]'), [], set()
for a in nodes:
href = a.get('href') or ''; m = re.search(r'/videos/(\d+)/', href)
if not m or '/login' in href: continue
vid = m.group(1);
if vid in seen: continue
seen.add(vid); title = ''; img = a.xpath('.//img')
if img:
im = img[0]; title = (im.get('alt') or '').strip()
pic = (im.get('src') or im.get('data-src') or im.get('data-original') or '').strip()
if not pic: pic = self._parse_srcset_first(im.get('data-srcset') or im.get('srcset') or '')
pic = self._abs_url(pic)
else: title = (a.text or '').strip(); pic = ''
title = title or f'视频 {vid}'
if not pic or pic.startswith('data:'): pic = self._cover_fallback(vid)
videos.append({'vod_id': vid, 'vod_name': title, 'vod_pic': pic, 'vod_remarks': ''})
return {'list': videos, 'page': str(page), 'pagecount': 9999, 'limit': 48, 'total': 0}
def _ensure_id(self, s):
m = re.search(r'(\d+)', str(s)); return m.group(1) if m else str(s)

186
PY1/91rb.py Normal file
View File

@@ -0,0 +1,186 @@
#author Kyle
import re, sys, time, urllib.parse
sys.path.append('..')
from base.spider import Spider as BaseSpider
class Spider(BaseSpider):
def __init__(self):
super().__init__();
self.base = 'https://www.91rb.com';
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': self.base + '/'
}
self.proxy_base = 'https://vpsdn.leuse.top/proxy?single=true&url='
def getName(self):
return '91热爆'
def init(self, extend=""):
self.extend = extend or '';
return {'class': 'movie'}
def isVideoFormat(self, url):
return bool(re.search(r'\.(m3u8|mp4)(\?|$)', url))
def manualVideoCheck(self):
return False
def destroy(self):
pass
def homeContent(self, filter):
return {
'class': [
{'type_name': '最新上传', 'type_id': 'latest-updates'},
{'type_name': '热门视频', 'type_id': 'most-popular'},
{'type_name': '收藏最多', 'type_id': 'most-favourited'},
{'type_name': '日本AV', 'type_id': 'tags/av2/'},
{'type_name': 'jav', 'type_id': 'tags/jav/'},
{'type_name': '韩国', 'type_id': 'tags/20c3f16d021b069ce3af1da50b15bd83/'}
]
}
def homeVideoContent(self):
try:
return self._listPage(self._buildListUrl('latest-updates', '1'))
except Exception as e:
self.log(f'homeVideoContent error: {e}');
return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
try:
return self._listPage(self._buildListUrl(tid, pg), page=pg)
except Exception as e:
self.log(f'categoryContent error: {e}');
return {'list': [], 'page': pg, 'pagecount': 1, 'limit': 48, 'total': 0}
def detailContent(self, ids):
vid = self._ensure_id(ids[0]);
detail_url = f"{self.base}/videos/{vid}/";
name = f'视频 {vid}';
pic = ''
try:
r = self._fetch_with_proxy(detail_url, headers=self.headers, timeout=10, allow_redirects=True)
if r and hasattr(r, 'text'):
doc = self.html(r.text)
if doc:
name = ''.join(doc.xpath('//h1//text()')).strip() or name
ogs = doc.xpath('//meta[@property="og:image"]/@content');
tws = doc.xpath('//meta[@name="twitter:image"]/@content')
pic = ogs[0].strip() if ogs else (tws[0].strip() if tws else '')
if pic:
pic = self._abs_url(pic)
except Exception as e:
self.log(f'detailContent fetch error: {e}')
if not pic:
pic = self._cover_fallback(vid)
vod = {
'vod_id': str(vid),
'vod_name': name,
'vod_pic': pic,
'type_name': '',
'vod_year': '',
'vod_remarks': '',
'vod_content': '',
'vod_play_from': '91RB',
'vod_play_url': f'正片${vid}'
}
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
key_enc = urllib.parse.quote(key);
url = f"{self.base}/search/{key_enc}/"
if pg != '1':
url = url.rstrip('/') + f'/{pg}/'
try:
return self._listPage(url, page=pg)
except Exception as e:
self.log(f'searchContent error: {e}');
return {'list': [], 'page': pg, 'pagecount': 1, 'total': 0}
def playerContent(self, flag, id, vipFlags):
vid = self._ensure_id(id);
group = int(vid) - (int(vid) % 1000)
m3u8 = f"https://91rbnet.gslb-al.com/hls/contents/videos/{group}/{vid}/{vid}.mp4/index.m3u8"
try:
# 对于视频流,也使用代理
r = self._fetch_with_proxy(m3u8, headers=self.headers, timeout=5, allow_redirects=True, verify=True, stream=True)
if r.status_code >= 400:
self.log(f'm3u8 head status={r.status_code}, fallback to direct anyway')
except Exception as e:
self.log(f'playerContent HEAD error: {e}')
return {'parse': 0, 'playUrl': '', 'url': m3u8, 'header': self.headers}
def localProxy(self, param):
return None
def _buildListUrl(self, tid, pg):
path = tid.strip('/') or 'latest-updates';
page_suffix = f"/{pg}/" if str(pg) != '1' else '/'
if path.startswith('categories') or path in ['latest-updates', 'most-popular', 'most-favourited']:
return f"{self.base}/{path}{page_suffix}"
return f"{self.base}/{path}{page_suffix}"
def _abs_url(self, url):
if not url:
return url
u = url.strip()
return 'https:' + u if u.startswith('//') else (self.base + u if u.startswith('/') else u)
def _parse_srcset_first(self, srcset):
if not srcset:
return ''
return srcset.split(',')[0].strip().split(' ')[0]
def _cover_fallback(self, vid):
try:
iv = int(vid);
group = iv - (iv % 1000);
return f'https://rimg.iomycdn.com/videos_screenshots/{group}/{iv}/preview.jpg'
except Exception:
return ''
def _listPage(self, url, page='1'):
doc = self.html(self._fetch_with_proxy(url, headers=self.headers, timeout=10).text)
if doc is None:
return {'list': [], 'page': page, 'pagecount': 1, 'total': 0}
nodes, videos, seen = doc.xpath('//main//a[contains(@href, "/videos/")]'), [], set()
for a in nodes:
href = a.get('href') or '';
m = re.search(r'/videos/(\d+)/', href)
if not m or '/login' in href:
continue
vid = m.group(1);
if vid in seen:
continue
seen.add(vid);
title = '';
img = a.xpath('.//img')
if img:
im = img[0];
title = (im.get('alt') or '').strip()
pic = (im.get('src') or im.get('data-src') or im.get('data-original') or '').strip()
if not pic:
pic = self._parse_srcset_first(im.get('data-srcset') or im.get('srcset') or '')
pic = self._abs_url(pic)
else:
title = (a.text or '').strip();
pic = ''
title = title or f'视频 {vid}'
if not pic or pic.startswith('data:'):
pic = self._cover_fallback(vid)
videos.append({'vod_id': vid, 'vod_name': title, 'vod_pic': pic, 'vod_remarks': ''})
return {'list': videos, 'page': str(page), 'pagecount': 9999, 'limit': 48, 'total': 0}
def _ensure_id(self, s):
m = re.search(r'(\d+)', str(s));
return m.group(1) if m else str(s)
def _fetch_with_proxy(self, url, headers=None, timeout=10, **kwargs):
"""通过代理服务器获取内容"""
proxy_url = f'{self.proxy_base}{urllib.parse.quote(url)}'
if headers is None:
headers = self.headers
return super().fetch(proxy_url, headers=headers, timeout=timeout, **kwargs)

313
PY1/AppMuou.py Normal file
View File

@@ -0,0 +1,313 @@
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
"""
示例
{
"key": "key",
"name": "name",
"type": 3,
"api": "./AppMuou.py",
"ext": {
"host": "https://muouapp.oss-cn-hangzhou.domain.com/xxx/xxx.txt", 应用域名(支持txt或域名)
"name": "xxx", 应用名称
"version": "4.2.0" 应用版本号
}
}
"""
from Crypto.Cipher import AES
from base.spider import Spider
from Crypto.Util.Padding import unpad
import re,sys,time,json,base64,hashlib,urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sys.path.append('..')
class Spider(Spider):
host,name,version,data_key,data_iv,cms_host,jx_api,playerinfo,= '', '', '', '', '', '', '',[]
headers = {
'User-Agent': "Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 MUOUAPP/10.8.4506.400",
'Accept-Encoding': "gzip",
'brand-model': "xiaomi",
'app-device': "nodata",
'app-time': "",
'sys-version': "12",
'device': "831395239bddf2e6",
'os': "Android",
'app-version': version
}
def init(self, extend=""):
try:
config = json.loads(extend)
except (json.JSONDecodeError, TypeError):
config = {}
name = config.get("name", "muou")
self.headers['app-version'] = config.get("version", "4.2.0")
self.host = config['host']
if not re.match(r'^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(\/)?$', self.host):
self.host = self.fetch(self.host, headers=self.headers, timeout=10, verify=False).text.rstrip('/')
timestamp = int(time.time())
self.headers['app-time'] = str(timestamp)
inner_sha1 = hashlib.sha1(f"{timestamp}{name}".encode('utf-8')).hexdigest()
outer_sha1 = hashlib.sha1(f"{timestamp}{inner_sha1}muouapp".encode('utf-8')).hexdigest()
payload = { 't': timestamp, 'n': inner_sha1, 'm': outer_sha1 }
try:
response = self.post(f'{self.host}/app_info.php', data=payload)
if response.status_code != 200:
return '-2 数据获取失败'
dat = response.text
except:
return '-2 数据获取失败'
try:
dat2 = json.loads(dat)
except:
return '-2 数据获取失败'
data = dat2.get('data', '')
a = dat2.get('a', '')
e = dat2.get('e', '')
s = dat2.get('s', '')
if not a or not e or not s:
return '-3 参数获取失败'
data2 = self.t(data, s, e)
key = hashlib.md5(a.encode('utf-8')).hexdigest()[:16]
iv = hashlib.md5(outer_sha1.encode('utf-8')).hexdigest()[:16]
result = self.decrypt(data2, key, iv)
if not result:
return '-4 解密失败'
try:
dat3 = json.loads(result)
except:
return '-5 解密失败'
key2 = dat3['key']
iv2 = dat3['iv']
self.data_key = hashlib.md5(key2.encode('utf-8')).hexdigest()[:16]
self.data_iv = hashlib.md5(iv2.encode('utf-8')).hexdigest()[:16]
self.cms_host = dat3['HBqq']
jx_api = dat3.get('HBrjjg','')
if jx_api.startswith('http'):
self.jx_api = jx_api
return None
def homeContent(self, filter):
if not self.cms_host:
return {'list': []}
self.headers['app-time'] = str(int(time.time()))
try:
response = self.fetch(f'{self.cms_host}/api.php/v1.vod/types', headers=self.headers).text
except Exception as e:
return {"class": [], "filters": {}}
try:
data = json.loads(response) or {}
except json.JSONDecodeError:
try:
data = json.loads(self.decrypt(response))
except (json.JSONDecodeError, TypeError):
return {"class": [], "filters": {}}
filter_keys = {"class", "area", "lang", "year", "letter", "by", "sort"}
filters = {}
classes = []
typelist = data.get('data', {}).get('typelist', [])
for item in typelist:
type_id = str(item["type_id"])
classes.append({"type_name": item["type_name"], "type_id": type_id})
extend = item.get("type_extend", {})
type_filters = []
for key, value_str in extend.items():
if key not in filter_keys:
continue
stripped = value_str.strip()
if not stripped:
continue
values = [v.strip() for v in stripped.split(",") if v.strip()]
if not values:
continue
type_filters.append({
"key": key,
"name": key,
"value": [{"n": v, "v": v} for v in values]
})
if type_filters:
filters[type_id] = type_filters
return {"class": classes, "filters": filters}
def homeVideoContent(self):
if not self.cms_host:
return {'list': []}
self.headers['app-time'] = str(int(time.time()))
print(f'{self.cms_host}/api.php/v1.vod/HomeIndex?page=&limit=6')
response = self.fetch(f'{self.cms_host}/api.php/v1.vod/HomeIndex?page=&limit=6', headers=self.headers).text
try:
data = json.loads(response)
except (json.JSONDecodeError, TypeError):
data_ = self.decrypt(response)
data = json.loads(data_)
videos = []
for i in data['data']:
if i.get('vod_list'):
vod_list = i['vod_list']
for j in vod_list:
pic = j.get('vod_pic')
if pic:
if not pic.startswith('http'):
j['vod_pic'] = self.cms_host + pic
videos.extend(vod_list)
return {'list': videos}
def detailContent(self, ids):
self.headers['app-time'] = str(int(time.time()))
response = self.fetch(f'{self.cms_host}/api.php/v1.vod/detail?vod_id={ids[0]}', headers=self.headers).text
try:
data = json.loads(response)
except (json.JSONDecodeError, TypeError):
data_ = self.decrypt(response)
data = json.loads(data_)
data = data['data']
if data == '':
return {'list': []}
vod_play_url = ''
show = ''
for i,j in data['vod_play_list'].items():
show += j['player_info']['show'] + '$$$'
urls = j.get('urls',{})
play_url = ''
if isinstance(urls, dict):
for i2,j2 in urls.items():
play_url += f"{j2['name']}${j2['from']}@{j2['url']}#"
play_url = play_url.rstrip('#')
vod_play_url += play_url + '$$$'
data['vod_play_from'] = show.rstrip('$$$')
data['vod_play_url'] = vod_play_url.rstrip('$$$')
data['vod_play_note'] = '$$$'
data.pop('vod_play_list')
data.pop('type')
return {'list': [data]}
def searchContent(self, key, quick, pg="1"):
if not self.cms_host:
return {'list': []}
self.headers['app-time'] = str(int(time.time()))
response = self.fetch(f'{self.cms_host}/api.php/v1.vod?wd={key}&limit=18&page={pg}', headers=self.headers).text
try:
data = json.loads(response)
except (json.JSONDecodeError, TypeError):
data_ = self.decrypt(response)
data = json.loads(data_)
videos = data['data']['list']
for item in data['data']['list']:
item.pop('type', None)
return {'list': videos, 'page': pg}
def categoryContent(self, tid, pg, filter, extend):
if not self.cms_host:
return {'list': []}
self.headers['app-time'] = str(int(time.time()))
response = self.fetch(
f"{self.cms_host}/api.php/v1.vod?type={tid}&class={extend.get('class', '')}&area={extend.get('area', '')}&year={extend.get('year', '')}&by=time&page={pg}&limit=18",
headers=self.headers).text
try:
data = json.loads(response)
except (json.JSONDecodeError, TypeError):
data_ = self.decrypt(response)
data = json.loads(data_)
videos = data['data']['list']
for item in data['data']['list']:
pic = item.get('vod_pic', '')
if pic:
if not pic.startswith('http'):
item['vod_pic'] = self.cms_host + pic
item.pop('type', None)
print(videos)
return {'list': videos, 'page': pg}
def playerContent(self, flag, id, vipFlags):
play_from, raw_url = id.split('@')
jx,url,playurl, = 1,raw_url,''
try:
if not self.playerinfo:
res = self.fetch(f'{self.host}/api.php?action=playerinfo',headers=self.headers).text
data = self.decrypt(res)
playerinfo =json.loads(data).get('data',{}).get('playerinfo',[])
if len(playerinfo) > 1:
self.playerinfo = playerinfo
if self.playerinfo:
for i in self.playerinfo:
play_jx = i.get('playerjiekou','')
if i.get('playername') == play_from and play_jx.startswith('http'):
response = self.fetch(f'{play_jx}{raw_url}&playerkey={play_from}',headers=self.headers,verify=False).text
try:
data = json.loads(response)
except (json.JSONDecodeError, TypeError):
data_ = self.decrypt(response)
data = json.loads(data_)
if str(data.get('code','')) == '403':
playurl = ''
else:
playurl = data['url']
jx = 0
except Exception:
playurl = ''
if playurl.startswith('http'):
url = playurl
else:
if re.search(r'^https?[^\s]*\.(m3u8|mp4|flv)', raw_url, re.I):
url = raw_url
jx = 0
else:
try:
response = self.fetch(self.jx_api + raw_url,headers=self.headers,verify=False).text
try:
data = json.loads(response)
except (json.JSONDecodeError, TypeError):
data_ = self.decrypt(response)
data = json.loads(data_)
playurl = data.get('url','')
if playurl.startswith('http'):
jx,url = 0,playurl
else:
jx,url = 1,raw_url
except Exception as e:
jx,url = 1,raw_url
if url.startswith('NBY-'):
jx,url = 0,''
return {'jx': jx, 'parse': 0, 'url': url,'header': {'User-Agent': 'Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 MUOUAPP/10.8.4506.400'}}
def decrypt(self,data, key='', iv=''):
if not(key or iv):
key = self.data_key
iv = self.data_iv
key_bytes = key.encode('utf-8')
iv_bytes = iv.encode('utf-8')
encrypted_data = base64.b64decode(data)
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
decrypted_padded = cipher.decrypt(encrypted_data)
decrypted = unpad(decrypted_padded, AES.block_size)
return decrypted.decode('utf-8')
def t(self, s, v, v1):
if s is not None and s != '':
n = len(s)
if v < 0 or v1 < 0:
raise ValueError("参数不能为负数")
if v + v1 <= n:
return s[v:n - v1]
else:
return ''
return s
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def localProxy(self, param):
pass

168
PY1/Appfox.py Normal file
View File

@@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
import re,sys,json,urllib3
from base.spider import Spider
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sys.path.append('..')
class Spider(Spider):
headers,host,froms,detail,custom_first,parses,custom_parses = {
'User-Agent': "Mozilla/5.0 (Linux; Android 12; SM-S9080 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36",
'Connection': "Keep-Alive",
'Accept-Encoding': "gzip",
'Accept-Language': "zh-CN,zh;q=0.8",
'Cache-Control': "no-cache"
},'','','','',{},{}
def init(self, extend=''):
ext = extend.strip()
if ext.startswith('http'):
host = ext
else:
arr = json.loads(ext)
host = arr['host']
self.froms = arr.get('from', '')
self.custom_parses = arr.get('parse', {})
self.custom_first = arr.get('custom_first',0)
if not re.match(r'^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(\/)?$', host):
host = self.fetch(host, headers=self.headers, verify=False).json()['apiDomain']
self.host = host.rstrip('/')
def homeContent(self, filter):
if not self.host: return None
response = self.fetch(f'{self.host}/api.php/Appfox/init', headers=self.headers, verify=False).json()
classes = []
for i in response['data']['type_list']:
classes.append({'type_id': i['type_id'],'type_name': i['type_name']})
return {'class': classes}
def homeVideoContent(self):
if not self.host: return None
response = self.fetch(f'{self.host}/api.php/Appfox/index', headers=self.headers, verify=False).json()
data = response['data']
videos = []
for i in data:
for j in i.get('banner', []):
videos.append(j)
for k in i.get('categories', []):
for l in k.get('videos',[]):
videos.append(l)
return {'list': videos}
def categoryContent(self, tid, pg, filter, extend):
if not self.host: return None
response = self.fetch(f"{self.host}/api.php/Appfox/vodList?type_id={tid}&class=全部&area=全部&lang=全部&year=全部&sort=最新&page={pg}", headers=self.headers, verify=False).json()
videos = []
for i in response['data']['recommend_list']:
videos.append(i)
return {'list': videos}
def searchContent(self, key, quick, pg='1'):
if not self.host: return None
path = f"{self.host}/api.php/Appfox/vod?ac=detail&wd={key}"
if self.froms: path += '&from=' + self.froms
response = self.fetch(path, headers=self.headers, verify=False, timeout=7).json()
self.detail = response['list']
return response
def detailContent(self, ids):
video = next((i.copy() for i in self.detail if str(i['vod_id']) == str(ids[0])), None)
if not video:
detail_response = self.fetch(f"{self.host}/api.php/Appfox/vod?ac=detail&ids={ids[0]}",headers=self.headers,verify=False).json()
video = detail_response.get('list')[0]
if not video: return {'list': []}
play_from = video['vod_play_from'].split('$$$')
play_urls = video['vod_play_url'].split('$$$')
try:
config_response = self.fetch(f"{self.host}/api.php/Appfox/config",headers=self.headers,verify=False).json()
player_list = config_response.get('data', {}).get('playerList', [])
jiexi_data_list = config_response.get('data', {}).get('jiexiDataList', [])
except Exception:
return {'list': [video]}
player_map = {player['playerCode']: player for player in player_list}
processed_play_urls = []
for idx, play_code in enumerate(play_from):
if play_code in player_map:
player_info = player_map[play_code]
if player_info['playerCode'] != player_info['playerName']:
play_from[idx] = f"{player_info['playerName']}\u2005({play_code})"
if idx < len(play_urls):
urls = play_urls[idx].split('#')
processed_urls = []
for url in urls:
parts = url.split('$')
if len(parts) >= 2:
parts[1] = f"{play_code}@{parts[1]}"
processed_urls.append('$'.join(parts))
else:
processed_urls.append(url)
processed_play_urls.append('#'.join(processed_urls))
video['vod_play_from'] = '$$$'.join(play_from)
video['vod_play_url'] = '$$$'.join(processed_play_urls)
self.parses = {p['playerCode']: p['url'] for p in jiexi_data_list if p.get('url', '').startswith('http')}
return {'list': [video]}
def playerContent(self, flag, id, vipflags):
play_from, raw_url = id.split('@', 1)
jx, parse, parsed = 0, 0, 0
url = raw_url
parses_main = []
if self.custom_first == 1:
parses_main.append(self.custom_parses)
parses_main.append(self.parses)
else:
parses_main.append(self.parses)
parses_main.append(self.custom_parses)
print(parses_main)
for parses2 in parses_main:
if not parsed and not re.match(r'https?://.*\.(m3u8|mp4|flv|mkv)', url):
for key, parsers in parses2.items():
if play_from not in key:
continue
if isinstance(parsers, list):
for parser in parsers:
if parser.startswith('parse:'):
url, jx, parse = parser.split('parse:')[1] + raw_url, 0, 1
break
try:
response = self.fetch(f"{parser}{raw_url}", headers=self.headers, verify=False).json()
if response.get('url', '').startswith('http'):
url, parsed = response['url'], 1
break
except Exception:
continue
else:
if parsers.startswith('parse:'):
url, jx, parse = parsers.split('parse:')[1] + raw_url, 0, 1
break
try:
response = self.fetch(f"{parsers}{raw_url}", headers=self.headers, verify=False).json()
if response.get('url', '').startswith('http'):
url, parsed = response['url'], 1
break
except Exception:
continue
if parsed or parse:
break
if parsed or parse:
break
if not (re.match(r'https?:\/\/.*\.(m3u8|mp4|flv|mkv)', url) or parsed == 1):
jx = 1
return {'jx': jx, 'parse': parse, 'url': url, 'header': {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}}
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def localProxy(self, param):
pass

187
PY1/ApptoV5无加密.py Normal file
View File

@@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
# 本资源来源于互联网公开渠道,仅可用于个人学习及爬虫技术交流。
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
"""
{
"key": "xxx",
"name": "xxx",
"type": 3,
"api": "./ApptoV5无加密.py",
"ext": "http://domain.com"
}
"""
import re,sys,uuid
from base.spider import Spider
sys.path.append('..')
class Spider(Spider):
host,config,local_uuid,parsing_config = '','','',[]
headers = {
'User-Agent': "Dart/2.19 (dart:io)",
'Accept-Encoding': "gzip",
'appto-local-uuid': local_uuid
}
def init(self, extend=''):
try:
host = extend.strip()
if not host.startswith('http'):
return {}
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?/?$', host):
host_=self.fetch(host).json()
self.host = host_['domain']
else:
self.host = host
self.local_uuid = str(uuid.uuid4())
response = self.fetch(f'{self.host}/apptov5/v1/config/get?p=android&__platform=android', headers=self.headers).json()
config = response['data']
self.config = config
parsing_conf = config['get_parsing']['lists']
parsing_config = {}
for i in parsing_conf:
if len(i['config']) != 0:
label = []
for j in i['config']:
if j['type'] == 'json':
label.append(j['label'])
parsing_config.update({i['key']:label})
self.parsing_config = parsing_config
return None
except Exception as e:
print(f'初始化异常:{e}')
return {}
def detailContent(self, ids):
response = self.fetch(f"{self.host}/apptov5/v1/vod/getVod?id={ids[0]}",headers=self.headers).json()
data3 = response['data']
videos = []
vod_play_url = ''
vod_play_from = ''
for i in data3['vod_play_list']:
play_url = ''
for j in i['urls']:
play_url += f"{j['name']}${i['player_info']['from']}@{j['url']}#"
vod_play_from += i['player_info']['show'] + '$$$'
vod_play_url += play_url.rstrip('#') + '$$$'
vod_play_url = vod_play_url.rstrip('$$$')
vod_play_from = vod_play_from.rstrip('$$$')
videos.append({
'vod_id': data3.get('vod_id'),
'vod_name': data3.get('vod_name'),
'vod_content': data3.get('vod_content'),
'vod_remarks': data3.get('vod_remarks'),
'vod_director': data3.get('vod_director'),
'vod_actor': data3.get('vod_actor'),
'vod_year': data3.get('vod_year'),
'vod_area': data3.get('vod_area'),
'vod_play_from': vod_play_from,
'vod_play_url': vod_play_url
})
return {'list': videos}
def searchContent(self, key, quick, pg='1'):
url = f"{self.host}/apptov5/v1/search/lists?wd={key}&page={pg}&type=&__platform=android"
response = self.fetch(url, headers=self.headers).json()
data = response['data']['data']
for i in data:
if i.get('vod_pic').startswith('mac://'):
i['vod_pic'] = i['vod_pic'].replace('mac://', 'http://', 1)
return {'list': data, 'page': pg, 'total': response['data']['total']}
def playerContent(self, flag, id, vipflags):
default_ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
parsing_config = self.parsing_config
parts = id.split('@')
if len(parts) != 2:
return {'parse': 0, 'url': id, 'header': {'User-Agent': default_ua}}
playfrom, rawurl = parts
label_list = parsing_config.get(playfrom)
if not label_list:
return {'parse': 0, 'url': rawurl, 'header': {'User-Agent': default_ua}}
result = {'parse': 1, 'url': rawurl, 'header': {'User-Agent': default_ua}}
for label in label_list:
payload = {
'play_url': rawurl,
'label': label,
'key': playfrom
}
try:
response = self.post(
f"{self.host}/apptov5/v1/parsing/proxy?__platform=android",
data=payload,
headers=self.headers
).json()
except Exception as e:
print(f"请求异常: {e}")
continue
if not isinstance(response, dict):
continue
if response.get('code') == 422:
continue
data = response.get('data')
if not isinstance(data, dict):
continue
url = data.get('url')
if not url:
continue
ua = data.get('UA') or data.get('UserAgent') or default_ua
result = {
'parse': 0,
'url': url,
'header': {'User-Agent': ua}
}
break
return result
def homeContent(self, filter):
config = self.config
if not config:
return {}
home_cate = config['get_home_cate']
classes = []
for i in home_cate:
if isinstance(i.get('extend', []),dict):
classes.append({'type_id': i['cate'], 'type_name': i['title']})
return {'class': classes}
def homeVideoContent(self):
response = self.fetch(f'{self.host}/apptov5/v1/home/data?id=1&mold=1&__platform=android',headers=self.headers).json()
data = response['data']
vod_list = []
for i in data['sections']:
for j in i['items']:
vod_pic = j.get('vod_pic')
if vod_pic.startswith('mac://'):
vod_pic = vod_pic.replace('mac://', 'http://', 1)
vod_list.append({
"vod_id": j.get('vod_id'),
"vod_name": j.get('vod_name'),
"vod_pic": vod_pic,
"vod_remarks": j.get('vod_remarks')
})
return {'list': vod_list}
def categoryContent(self, tid, pg, filter, extend):
response = self.fetch(f"{self.host}/apptov5/v1/vod/lists?area={extend.get('area','')}&lang={extend.get('lang','')}&year={extend.get('year','')}&order={extend.get('sort','time')}&type_id={tid}&type_name=&page={pg}&pageSize=21&__platform=android", headers=self.headers).json()
data = response['data']
data2 = data['data']
for i in data['data']:
if i.get('vod_pic','').startswith('mac://'):
i['vod_pic'] = i['vod_pic'].replace('mac://', 'http://', 1)
return {'list': data2, 'page': pg, 'total': data['total']}
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def localProxy(self, param):
pass

47
PY1/Chaturbate.py Normal file
View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
import re, json, requests
from base.spider import Spider
class Spider(Spider):
def getName(self): return "Chaturbate 直播"
def init(self, extend=""):
self.base, self.headers = "https://chaturbate.com", {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
return self
def homeContent(self, filter):
return {"class": [{"type_id": "f", "type_name": "女性"}, {"type_id": "m", "type_name": "男性"}, {"type_id": "c", "type_name": "情侣"}]}
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg) if pg else 0
gender = {"f": "f", "m": "m", "c": "c"}.get(tid, "")
url = f"{self.base}/api/ts/roomlist/room-list/?enable_recommendations=false&genders={gender}&limit=90&offset={pg*90}"
data = self.fetch(url, headers=self.headers).json()
total = data.get("total_count", 0)
videos = [
{"vod_id": r["username"], "vod_pic": r.get("img", ""), "vod_remarks": "",
"vod_name": f'{r["username"]}{" (" + str(r["display_age"]) + ")" if r.get("display_age") else ""}'}
for r in data.get("rooms", [])
]
return {"list": videos, "page": pg + 1, "pagecount": (total + 89) // 90, "limit": 90, "total": total}
def detailContent(self, ids):
room_slug = ids[0]
return {"list": [{"vod_id": room_slug, "vod_name": f"Chaturbate - {room_slug}", "vod_pic": "",
"vod_play_from": "Chaturbate", "vod_play_url": f"直播源${room_slug}"}]}
def playerContent(self, flag, id, vipFlags):
url, data, headers = "https://chaturbate.com/get_edge_hls_url_ajax/", {"room_slug": id}, {"X-Requested-With": "XMLHttpRequest"}
try:
play_url = requests.post(url, data=data, headers=headers).json().get("url", id)
except (requests.RequestException, json.JSONDecodeError):
play_url = id
return {"parse": 0, "playUrl": "", "url": play_url, "header": headers}
def searchContent(self, key, quick, pg="0"):
pg = int(pg) if pg else 0
url_api = f"{self.base}/api/ts/roomlist/room-list/?enable_recommendations=false&limit=90&offset={pg*90}&query={key}"
data = self.fetch(url_api, headers=self.headers).json()
videos = [
{"vod_id": r["username"], "vod_pic": r.get("img", ""), "vod_remarks": "",
"vod_name": f'{r["username"]}{" (" + str(r["display_age"]) + ")" if r.get("display_age") else ""}'}
for r in data.get("rooms", [])
]
return {"list": videos}
def isVideoFormat(self, url): return ".m3u8" in url
def manualVideoCheck(self): return True

165
PY1/DSYS.py Normal file
View File

@@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import time
import uuid
from base64 import b64decode, b64encode
import json
import sys
from urllib.parse import urlparse, urlunparse
from Crypto.Cipher import AES
from Crypto.Hash import MD5
from Crypto.Util.Padding import unpad, pad
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host = "https://api.230110.xyz"
phost = "https://cdn.230110.xyz"
headers = {
'origin': host,
'referer': f'{host}/',
'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.8 Mobile/15E148 Safari/604.1',
}
def homeContent(self, filter):
data='9XSPkyFMrOOG34JSg//ZosMof45cyBo9hwZMZ5rvI6Yz/ZZlXWIf8/644OzwW+FNIOdJ61R/Lxjy1tqN+ZzokxtiVzb8LjYAkh6GFudwAUXFt9yS1ZjAxC3tDKrQsJQLk3nym0s00DBBzLBntRBDFz7nbba+OOBuQOZpL3CESGL42l4opdoViQLhO/dIizY1kIOk2NxxpDC9Z751gPl1ctHWuLWhuLG/QWgNWi/iHScjKrMHJKcC9GQHst/4Q3dgZ03eQIIVB6jvoV1XXoBCz6fjM/jM3BXpzSttT4Stglwy93gWuNWuZiKypHK2Q0lO10oM0ceRW2a0fPGId+rNYMRO3cR/C0ZueD4cmTAVOuxVr9ZZSP8/nhD0bHyAPONXtchIDJb0O/kdFHk2KTJfQ5q4fHOyzezczc4iQDV/R0S8cGZKM14MF+wytA/iljfj43H0UYqq5pM+MCUGRTdYEtuxCp0+A+DiOhNZwY/Km/TgBoGZQWGbpljJ2LAVnWhxX+ickLH7zuR/FeIwP/R8zOuR+8C8UlT9eHTqtvfNzaGdFxt316atHy8TNjRO7J5a177mqsHs3ziG0toDDzLDCbhRUjFgVA3ktahhXiWaaCo/ZGSJAA8TDO5DYqnJ0JDaX0ILPj8QB5zxrHYmRE8PboIr3RBAjz1sREbaHfjrUjoh29ePhlolLV00EvgoxP5knaqt5Ws/sq5IG57qKCAPgqXzblPLHToJGBtukKhLp8jbGJrkb6PVn4/jysks0NGE'
return {'class':self.aes(data,False)}
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
data = {"q": "", "filter": [f"type_id = {tid}"], "offset": (int(pg)-1) * 24, "limit": 24, "sort": ["video_time:desc"],"lang": "zh-cn", "route": "/videos/search"}
result = {}
if 'skey_' in tid:return self.searchContent(tid.split('_')[-1], True, pg)
result['list'] = self.getl(self.getdata(data))
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
data={"limit":1,"filter":[f"video_id = {ids[0]}"],"lang":"zh-cn","route":"/videos/search"}
res = self.getdata(data)[0]
purl=urlunparse(urlparse(self.phost)._replace(path=urlparse(res.get('video_url')).path))
vod = {
'vod_play_from': 'dsysav',
'vod_play_url': f"{res.get('video_duration')}${purl}"
}
if res.get('video_tag'):
clist = []
tags=res['video_tag'].split(',')
for k in tags:
clist.append('[a=cr:' + json.dumps({'id': f'skey_{k}', 'name': k}) + '/]' + k + '[/a]')
vod['vod_content'] = ' '.join(clist)
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
data={"q":key,"filter":[],"offset":(int(pg)-1) * 24,"limit":24,"sort":["video_time:desc"],"lang":"zh-cn","route":"/videos/search"}
return {'list':self.getl(self.getdata(data)),'page':pg}
def playerContent(self, flag, id, vipFlags):
if id.endswith('.mpd'):
id=f"{self.getProxyUrl()}&url={self.e64(id)}&type=mpd"
return {'parse': 0, 'url': id, 'header':self.headers}
def localProxy(self, param):
if param.get('type') and param['type']=='mpd':
url = self.d64(param.get('url'))
ids=url.split('/')
id=f"{ids[-3]}/{ids[-2]}/"
xpu = f"{self.getProxyUrl()}&path=".replace('&', '&amp;')
data = self.fetch(url, headers=self.headers).text
data = data.replace('initialization="', f'initialization="{xpu}{id}').replace('media="',f'media="{xpu}{id}')
return [200,'application/octet-stream',data]
else:
hsign=self.md5(f"AjPuom638LmWfWyeM5YueKuJ9PuWLdRn/mpd/{param.get('path')}1767196800")
bytes_data = bytes.fromhex(hsign)
sign = b64encode(bytes_data).decode('utf-8').replace('=','').replace('+','-').replace('/','_')
url=f"{self.phost}/mpd/{param.get('path')}?sign={sign}&expire=1767196800"
return [302,'text/plain',None,{'Location':url}]
def liveContent(self, url):
pass
def aes(self, text, operation=True):
key = b'OPQT123412FRANME'
iv = b'MRDCQP12QPM13412'
cipher = AES.new(key, AES.MODE_CBC, iv)
if operation:
ct_bytes = cipher.encrypt(pad(json.dumps(text).encode("utf-8"), AES.block_size))
ct = b64encode(ct_bytes).decode("utf-8")
return ct
else:
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
return json.loads(pt.decode("utf-8"))
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64编码错误: {str(e)}")
return ""
def d64(self,encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64解码错误: {str(e)}")
return ""
def md5(self, text):
h = MD5.new()
h.update(text.encode('utf-8'))
return h.hexdigest()
def getl(self,data):
videos = []
for i in data:
img = i.get('video_cover')
if img and 'http' in img:img = urlunparse(urlparse(self.phost)._replace(path=urlparse(img).path))
videos.append({
'vod_id': i.get('video_id'),
'vod_name': i.get('video_title'),
'vod_pic': img,
'vod_remarks': i.get('video_duration'),
'style': {"type": "rect", "ratio": 1.33}
})
return videos
def getdata(self,data):
uid = str(uuid.uuid4())
t = int(time.time())
json_data = {
'sign': self.md5(f"{self.e64(json.dumps(data))}{uid}{t}AjPuom638LmWfWyeM5YueKuJ9PuWLdRn"),
'nonce': uid,
'timestamp': t,
'data': self.aes(data),
}
res = self.post(f"{self.host}/v1", json=json_data, headers=self.headers).json()
res = self.aes(res['data'], False)
return res

599
PY1/E佬通用视频 (3).py Normal file
View File

@@ -0,0 +1,599 @@
import json
import re
import sys
import hashlib
import time
from base64 import b64decode, b64encode
from urllib.parse import urlparse, urljoin, urlencode, quote
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from pyquery import PyQuery as pq
sys.path.append('..')
from base.spider import Spider as BaseSpider
# 图片缓存,避免重复解密
img_cache = {}
class Spider(BaseSpider):
def init(self, extend=""):
"""初始化,支持 extend='{"host":"https://example.com", "proxies":{...}}'"""
try:
cfg = json.loads(extend) if isinstance(extend, str) else extend or {}
self.proxies = cfg.get('proxies', {})
self.host = (cfg.get('host', '') or '').strip()
if not self.host:
self.host = self.get_working_host()
except:
self.proxies = {}
self.host = self.get_working_host()
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,image/avif,image/webp,image/apng,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1'
}
self.headers.update({'Origin': self.host, 'Referer': f"{self.host}/"})
print(f"[Spider] 使用站点: {self.host}")
def getName(self):
return "🌈 通用视频解析器|Pro增强版"
def isVideoFormat(self, url):
return any(ext in (url or '').lower() for ext in ['.m3u8', '.mp4', '.ts', '.flv', '.mkv', '.avi', '.webm'])
def manualVideoCheck(self):
return False
def destroy(self):
global img_cache
img_cache.clear()
def get_working_host(self):
"""尝试多个host找到可用的"""
dynamic_urls = [
'https://wanwuu.com/'
]
for url in dynamic_urls:
try:
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=3)
if response.status_code == 200:
print(f"[Spider] 检测到可用host: {url}")
return url.rstrip('/')
except Exception as e:
continue
return dynamic_urls[0].rstrip('/') if dynamic_urls else 'https://jszyapi.com'
def homeContent(self, filter):
"""首页:动态分类 + 视频列表 + filters"""
try:
response = requests.get(self.host, headers=self.headers, proxies=self.proxies, timeout=10)
if response.status_code != 200:
return {'class': [], 'filters': {}, 'list': []}
response.encoding = response.apparent_encoding
data = self.getpq(response.text)
classes = []
# 多选择器:导航链接
nav_items = data('nav a, .menu a, .nav a, #header a, .header a, ul.navbar-nav a, .category-list a, .scroll-content a, .module-menu a, .module-tab-item')
seen_hrefs = set()
bad_words = ['登录', '注册', '搜索', '首页', 'Home', 'Login', 'Search', '联系', '关于', '留言', 'RSS', '推特', 'TG', 'Q群', '合作', '公告', 'APP', '下载']
for k in nav_items.items():
href = (k.attr('href') or '').strip()
name = k.text().strip()
if not href or href in ['#', '/', ''] or 'javascript' in href:
continue
if not name or len(name) < 2 or len(name) > 12:
continue
if any(bw in name for bw in bad_words):
continue
if href in seen_hrefs:
continue
# 规范化href
if not href.startswith('http'):
href = urljoin(self.host, href)
classes.append({'type_name': name, 'type_id': href})
seen_hrefs.add(href)
if len(classes) >= 25:
break
if not classes:
classes = [
{'type_name': '最新', 'type_id': '/latest/'},
{'type_name': '热门', 'type_id': '/hot/'},
{'type_name': '推荐', 'type_id': '/recommend/'}
]
# 视频列表
videos = self.getlist(data, '#content article, #main article, .posts article, .container .row article, article, .video-list .video-item, .module-poster-item, .avdata-outer, .search-result, .card')
# filters
filters = {
'class': [{'n': '全部', 'v': ''}, {'n': '高清', 'v': 'HD'}, {'n': '4K', 'v': '4K'}],
'area': [{'n': '全部', 'v': ''}, {'n': '日本', 'v': 'jp'}, {'n': '欧美', 'v': 'us'}, {'n': '国产', 'v': 'cn'}],
'year': [{'n': '全部', 'v': ''}, {'n': '2024', 'v': '2024'}, {'n': '2023', 'v': '2023'}, {'n': '2022', 'v': '2022'}],
'lang': [{'n': '全部', 'v': ''}, {'n': '中文', 'v': 'zh'}, {'n': '日语', 'v': 'jp'}, {'n': '英文', 'v': 'en'}]
}
return {'class': classes, 'filters': filters, 'list': videos}
except Exception as e:
print(f"[homeContent] Error: {e}")
return {'class': [], 'filters': {}, 'list': []}
def homeVideoContent(self):
"""首页视频内容 (复用homeContent)"""
res = self.homeContent(None)
return {'list': res.get('list', [])}
def categoryContent(self, tid, pg, filter, extend):
"""分类内容"""
try:
if '@folder' in tid:
v = self.getfod(tid.replace('@folder', ''))
return {'list': v, 'page': 1, 'pagecount': 1, 'limit': 90, 'total': len(v)}
pg = int(pg) if pg else 1
url = tid if tid.startswith('http') else f"{self.host}{tid if tid.startswith('/') else '/' + tid}"
url = url.rstrip('/')
# 构造分页URL
real_url = f"{url}/" if pg == 1 else f"{url}/{pg}/"
if '?page=' in url or '?pg=' in url:
real_url = url.replace('{pg}', str(pg))
# extend参数添加filter
if isinstance(extend, dict):
params = []
for key in ['class', 'area', 'year', 'lang', 'letter', 'by']:
if extend.get(key):
params.append(f"{key}={quote(str(extend[key]))}")
if params:
sep = '&' if '?' in real_url else '?'
real_url = real_url + sep + '&'.join(params)
print(f"[categoryContent] 请求URL: {real_url}")
response = requests.get(real_url, headers=self.headers, proxies=self.proxies, timeout=10)
if response.status_code != 200:
return {'list': [], 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 0}
response.encoding = response.apparent_encoding
data = self.getpq(response.text)
# 【关键修复】分类页面用更全面的选择器
videos = self.getlist(
data,
'.module-item, .module-poster-item, .video-item, article, .card, li.vodlist_item, .stui-vodlist__box, a.module-item-pic, .myui-vodlist__box',
tid
)
print(f"[categoryContent] 提取到 {len(videos)} 个视频")
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
except Exception as e:
print(f"[categoryContent] Error: {e}")
import traceback
traceback.print_exc()
return {'list': [], 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 0}
def detailContent(self, ids):
"""详情页:提取视频源(增强版)"""
try:
url = ids[0] if ids[0].startswith('http') else f"{self.host}{ids[0]}"
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=10)
response.encoding = response.apparent_encoding
html_text = response.text
data = self.getpq(html_text)
plist = []
unique_urls = set()
def add_play_url(name, u):
if not u or u in unique_urls:
return
if not u.startswith('http'):
u = urljoin(self.host, u)
unique_urls.add(u)
plist.append(f"{name}${u}")
# 1. Script 中的 m3u8/mp4 (优先级最高)
scripts = data('script')
for s in scripts.items():
txt = s.text()
if 'url' in txt and ('.m3u8' in txt or '.mp4' in txt):
urls = re.findall(r'["\']+(http[^"\']+\.(?:m3u8|mp4)[^\'"]*)["\']', txt)
for u in urls:
add_play_url("脚本源", u)
break
# 2. DPlayer 配置
if data('.dplayer'):
for c, k in enumerate(data('.dplayer').items(), start=1):
config_attr = k.attr('data-config')
if config_attr:
try:
config = json.loads(config_attr)
video_url = config.get('video', {}).get('url', '')
if video_url:
add_play_url(f"DPlayer{c}", video_url)
except:
pass
# 3. Video 标签 (HTML5)
for v in data('video').items():
src = v.attr('src')
if src:
add_play_url("HTML5视频", src)
for src_tag in v('source').items():
add_play_url("HTML5源", src_tag.attr('src'))
# 4. Iframe 嗅探
for iframe in data('iframe').items():
src = iframe.attr('src') or iframe.attr('data-src')
if src and any(x in src for x in ['.m3u8', '.mp4', 'upload', 'cloud', 'player', 'embed']):
if not any(x in src for x in ['google', 'facebook', 'disqus']):
add_play_url("Iframe源", src)
# 5. 通用变量/JSON 正则 (核心增强)
common_patterns = [
r'var\s+main\s*=\s*["\']([^"\']+)["\']',
r'url\s*:\s*["\']([^"\']+\.(?:m3u8|mp4))["\']',
r'vurl\s*=\s*["\']([^"\']+)["\']',
r'vid\s*:\s*["\']([^"\']+\.(?:m3u8|mp4))["\']',
r'"url"\s*:\s*"([^"]+)"',
r'video_url\s*=\s*[\'"]([^\'"]+)[\'"]',
r'var\s+videoUrl\s*=\s*["\']([^"\']+)["\']',
r'playurl\s*=\s*["\']([^"\']+)["\']',
r'"playUrl"\s*:\s*"([^"]+)"',
r'src="([^"]*\.(?:m3u8|mp4)[^"]*)"',
r'data-src="([^"]*\.(?:m3u8|mp4)[^"]*)"',
r'mp4Url\s*=\s*["\']([^"\']+)["\']',
r'm3u8Url\s*=\s*["\']([^"\']+)["\']',
]
for pat in common_patterns:
matches = re.finditer(pat, html_text, re.IGNORECASE)
for match in matches:
u = match.group(1)
if any(ext in u for ext in ['.m3u8', '.mp4', '.flv', '.m4v']):
add_play_url("正则源", u)
# 6. Script JSON embed (ThePorn/porn87风格)
try:
json_matches = re.findall(r'<script[^>]*type="text/javascript"[^>]*>(.*?)</script>', html_text, re.DOTALL)
for json_str in json_matches:
try:
obj = json.loads(json_str)
if isinstance(obj, dict):
for k, v in obj.items():
if isinstance(v, str) and ('.m3u8' in v or '.mp4' in v):
add_play_url(f"JSON-{k}", v)
except:
pass
except:
pass
# 7. 兜底:文本链接
if not plist:
content_area = data('.post-content, article, .content, .video-info, .module-info-introduction')
for i, link in enumerate(content_area('a').items(), start=1):
link_text = link.text().strip()
link_href = link.attr('href')
if link_href and any(kw in link_text for kw in ['点击观看', '观看', '播放', '视频', '第一弹', '线路', 'Play', '播放器']):
ep_name = link_text.replace('点击观看:', '').replace('点击观看', '').strip()
if not ep_name:
ep_name = f"线路{i}"
add_play_url(ep_name, link_href)
play_url = '#'.join(plist) if plist else f"无视频源,请尝试网页播放${url}"
# 标题
vod_title = data('h1').text().strip()
if not vod_title:
vod_title = data('.post-title, .module-info-heading, .video-title').text().strip()
if not vod_title:
vod_title = data('title').text().split('|')[0].strip()
# 描述
vod_content = data('.post-content, article, .module-info-introduction-content, .video-desc').text().strip()
return {'list': [{'vod_play_from': '通用解析', 'vod_play_url': play_url, 'vod_content': vod_content or vod_title}]}
except Exception as e:
print(f"[detailContent] Error: {e}")
import traceback
traceback.print_exc()
return {'list': [{'vod_play_from': '通用解析', 'vod_play_url': '获取失败'}]}
def searchContent(self, key, quick, pg="1"):
"""搜索"""
try:
pg = int(pg) if pg else 1
url = f"{self.host}/?s={quote(key)}"
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=10)
response.encoding = response.apparent_encoding
data = self.getpq(response.text)
videos = self.getlist(data, 'article, .search-result, .post, .video-item, .module-poster-item, .avdata-outer, .card, .module-item')
return {'list': videos, 'page': pg, 'pagecount': 9999}
except Exception as e:
print(f"[searchContent] Error: {e}")
return {'list': [], 'page': pg, 'pagecount': 9999}
def playerContent(self, flag, id, vipFlags):
"""播放器"""
if 'html' in id or 'php' in id or 'embed' in id or 'player' in id:
parse = 1 # 需要解析
elif self.isVideoFormat(id):
parse = 0 # 直接播放
else:
parse = 1
url = self.proxy(id) if '.m3u8' in id else id
return {'parse': parse, 'url': url, 'header': self.headers}
def localProxy(self, param):
"""本地代理处理m3u8/ts/图片解密"""
try:
type_ = param.get('type')
url = param.get('url')
if type_ == 'cache':
key = param.get('key')
if content := img_cache.get(key):
return [200, 'image/jpeg', content]
return [404, 'text/plain', b'Expired']
elif type_ == 'img':
real_url = self.d64(url) if not url.startswith('http') else url
res = requests.get(real_url, headers=self.headers, proxies=self.proxies, timeout=10)
content = self.aesimg(res.content)
return [200, 'image/jpeg', content]
elif type_ == 'm3u8':
return self.m3Proxy(url)
else: # ts
return self.tsProxy(url)
except Exception as e:
print(f"[localProxy] Error: {e}")
return [404, 'text/plain', b'']
def proxy(self, data, type='m3u8'):
"""生成代理URL"""
if data and self.proxies:
return f"{self.getProxyUrl()}&url={self.e64(data)}&type={type}"
return data
def m3Proxy(self, url):
"""m3u8代理"""
try:
url = self.d64(url)
res = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=10)
res.encoding = res.apparent_encoding
data = res.text
base = res.url.rsplit('/', 1)[0]
lines = []
for line in data.split('\n'):
if '#EXT' not in line and line.strip():
if not line.startswith('http'):
if line.startswith('/'):
host_base = '/'.join(res.url.split('/')[:3])
line = f"{host_base}{line}"
else:
line = f"{base}/{line}"
lines.append(self.proxy(line, 'ts'))
else:
lines.append(line)
return [200, "application/vnd.apple.mpegurl", '\n'.join(lines)]
except Exception as e:
print(f"[m3Proxy] Error: {e}")
return [404, 'text/plain', b'']
def tsProxy(self, url):
"""ts代理"""
try:
content = requests.get(self.d64(url), headers=self.headers, proxies=self.proxies, timeout=10).content
return [200, 'video/mp2t', content]
except:
return [404, 'text/plain', b'']
def e64(self, text):
"""base64编码"""
return b64encode(str(text).encode()).decode()
def d64(self, text):
"""base64解码"""
return b64decode(str(text).encode()).decode()
def aesimg(self, data):
"""AES解密图片"""
if len(data) < 16:
return data
# 多密钥尝试 (从成品提取)
keys = [
(b'f5d965df75336270', b'97b60394abc2fbe1'),
(b'75336270f5d965df', b'abc2fbe197b60394'),
]
for k, v in keys:
try:
dec = unpad(AES.new(k, AES.MODE_CBC, v).decrypt(data), 16)
if dec.startswith(b'\xff\xd8') or dec.startswith(b'\x89PNG') or dec.startswith(b'GIF8'):
return dec
except:
pass
try:
dec = unpad(AES.new(k, AES.MODE_ECB).decrypt(data), 16)
if dec.startswith(b'\xff\xd8'):
return dec
except:
pass
return data
def getlist(self, data_pq, selector, tid=''):
"""解析视频列表"""
videos = []
is_folder = '/mrdg' in (tid or '')
items = data_pq(selector)
if len(items) == 0:
items = data_pq('a:has(img)')
print(f"[getlist] 找到 {len(items)} 个候选项")
seen_ids = set()
ad_keywords = ['娱乐', '棋牌', '澳门', '葡京', '太阳城', '彩票', 'AV', '直播', '充值', '下载', '回家']
for k in items.items():
# 【关键修复】更灵活的链接提取
if k.is_('a'):
a = k
container = k.parent()
else:
# 优先查找带href的a标签
a = k('a[href]').eq(0)
if not a or not a.attr('href'):
a = k('a').eq(0)
container = k
href = a.attr('href')
if not href:
continue
if any(x in href for x in ['/category/', '/tag/', '/feed/', '/page/', '/author/', 'gitlub']):
continue
if href in ['/', '#']:
continue
# 【优化】标题提取
title = container.find('h2, h3, h4, .title, .video-title, .module-poster-item-title, .module-item-title').text()
if not title:
title = a.attr('title') or a.attr('data-title')
if not title:
title = a.find('img').attr('alt')
if not title:
title = container.find('.video-name, .vodlist_title').text()
if not title:
title = a.text()
if not title or len(title.strip()) < 2:
continue
if any(ad in title for ad in ad_keywords):
continue
card_html = k.outer_html() if hasattr(k, 'outer_html') else str(k)
script_text = k('script').text()
img = self.getimg(script_text, k, card_html)
if not img:
continue
if '.gif' in img.lower():
continue
if href in seen_ids:
continue
if not href.startswith('http'):
href = urljoin(self.host, href)
seen_ids.add(href)
remark = container.find('time, .date, .meta, .views, .video-duration, .module-item-note, .pic-text').text() or ''
videos.append({
'vod_id': f"{href}{'@folder' if is_folder else ''}",
'vod_name': title.strip(),
'vod_pic': img,
'vod_remarks': remark,
'vod_tag': 'folder' if is_folder else '',
'style': {"type": "rect", "ratio": 1.33}
})
return videos
def getimg(self, text, elem=None, html_content=None):
"""提取图片URL"""
# 1. var img_url (吃瓜网特色)
if m := re.search(r'var\s+img_url\s*=\s*[\'"]([^\'"]+)[\'"]', text or ''):
return self._proc_url(m.group(1))
# 2. loadBannerDirect
if m := re.search(r"loadBannerDirect\('([^']+)'", text or ''):
return self._proc_url(m.group(1))
if html_content is None and elem is not None:
html_content = elem.outer_html() if hasattr(elem, 'outer_html') else str(elem)
if not html_content:
return ''
html_content = html_content.replace('&quot;', '"').replace('&apos;', "'").replace('&amp;', '&')
# 3. data-src / data-original
if m := re.search(r'data-src\s*=\s*["\']([^"\']+)["\']', html_content, re.I):
return self._proc_url(m.group(1))
if m := re.search(r'data-original\s*=\s*["\']([^"\']+)["\']', html_content, re.I):
return self._proc_url(m.group(1))
# 4. http链接
if m := re.search(r'(https?://[^"\'\s)]+\.(?:jpg|png|jpeg|webp|gif))', html_content, re.I):
return self._proc_url(m.group(1))
# 5. url()
if 'url(' in html_content:
m = re.search(r'url\s*\(\s*[\'"]?([^\"\'\)]+)[\'"]?\s*\)', html_content, re.I)
if m:
return self._proc_url(m.group(1))
# 6. src属性
if m := re.search(r'src\s*=\s*["\']([^"\']+\.(?:jpg|png|jpeg|webp|gif))["\']', html_content, re.I):
return self._proc_url(m.group(1))
return ''
def _proc_url(self, url):
"""处理URL转义、代理、AES"""
if not url:
return ''
url = url.strip('\'" ')
if url.startswith('data:'):
try:
_, b64_str = url.split(',', 1)
raw = b64decode(b64_str)
if not (raw.startswith(b'\xff\xd8') or raw.startswith(b'\x89PNG') or raw.startswith(b'GIF8')):
raw = self.aesimg(raw)
key = hashlib.md5(raw).hexdigest()
img_cache[key] = raw
return f"{self.getProxyUrl()}&type=cache&key={key}"
except:
return ""
if not url.startswith('http'):
url = urljoin(self.host, url)
# 所有图片走代理解密
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=img"
def getfod(self, id):
"""文件夹处理"""
return []
def getpq(self, data):
"""获取pq对象"""
try:
return pq(data)
except:
return pq(data.encode('utf-8'))

298
PY1/E佬通用视频.py Normal file
View File

@@ -0,0 +1,298 @@
import re
import sys
import urllib.parse
from pyquery import PyQuery as pq
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "美人吹箫"
def init(self, extend):
pass
def homeContent(self, filter):
result = {}
classes = []
try:
rsp = self.fetch("https://www.mrcx2.yachts/cn/home/web/")
if rsp and rsp.text:
doc = pq(rsp.text)
items = doc('.stui-header__menu li a')
for item in items.items():
name = item.text()
href = item.attr('href')
if name and href and name != "首页":
match = re.search(r'/type/id/(\d+)\.html', href)
if match:
classes.append({
'type_name': name,
'type_id': match.group(1)
})
if not classes:
items = doc('.dropdown.type li a')
for item in items.items():
name = item.text()
href = item.attr('href')
if name and href and name != "首页":
match = re.search(r'/type/id/(\d+)\.html', href)
if match:
classes.append({
'type_name': name,
'type_id': match.group(1)
})
seen = set()
unique_classes = []
for cls in classes:
if cls['type_id'] not in seen:
seen.add(cls['type_id'])
unique_classes.append(cls)
classes = unique_classes
except Exception as e:
print(f"homeContent error: {e}")
result['class'] = classes
return result
def homeVideoContent(self):
result = {}
try:
videos = []
url = "https://www.mrcx2.yachts/cn/home/web/"
rsp = self.fetch(url)
if rsp and rsp.text:
doc = pq(rsp.text)
items = doc('.stui-vodlist li.index')
for item in items.items():
a = item.find('.stui-vodlist__thumb')
href = a.attr('href')
title = a.attr('title') or item.find('.title a').attr('title')
img = a.attr('data-original') or a.attr('style')
if img and 'background-image:' in img:
match = re.search(r'url\(["\']?(.*?)["\']?\)', img)
if match:
img = match.group(1)
if not title or not href:
continue
play_count = item.find('.text').text() or '播放0次'
score = item.find('.score').text() or '0.0 分'
videos.append({
'vod_id': href,
'vod_name': title,
'vod_pic': img,
'vod_remarks': f"{score} | {play_count}"
})
result['list'] = videos
except Exception as e:
print(f"homeVideoContent error: {e}")
return result
def categoryContent(self, tid, pg, filter, extend):
result = {}
videos = []
try:
url = f"https://www.mrcx2.yachts/cn/home/web/index.php/vod/type/id/{tid}/page/{pg}.html"
rsp = self.fetch(url)
if rsp and rsp.text:
doc = pq(rsp.text)
items = doc('.stui-vodlist li')
for item in items.items():
a = item.find('.stui-vodlist__thumb')
href = a.attr('href')
title = a.attr('title') or item.find('.title a').attr('title')
img = a.attr('data-original') or a.attr('style')
if img and 'background-image:' in img:
match = re.search(r'url\(["\']?(.*?)["\']?\)', img)
if match:
img = match.group(1)
if not title or not href:
continue
play_count = item.find('.text').text() or '播放0次'
score = item.find('.score').text() or '0.0 分'
videos.append({
'vod_id': href,
'vod_name': title,
'vod_pic': img,
'vod_remarks': f"{score} | {play_count}"
})
except Exception as e:
print(f"categoryContent error: {e}")
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, array):
result = {}
if not array or not array[0]:
return result
try:
aid = array[0]
if aid.startswith('/'):
play_url = "https://www.mrcx2.yachts" + aid
else:
play_url = "https://www.mrcx2.yachts/cn/home/web/" + aid
rsp = self.fetch(play_url)
if not rsp or not rsp.text:
return result
html = rsp.text
doc = pq(html)
vod = {
'vod_id': aid,
'vod_name': doc('title').text() or '',
'vod_pic': '',
'vod_remarks': '',
'vod_content': '',
'vod_play_from': 'E佬通用视频',
'vod_play_url': ''
}
img = doc('.stui-vodlist__thumb').attr('data-original') or doc('.stui-vodlist__thumb').attr('style')
if img and 'background-image:' in img:
match = re.search(r'url\(["\']?(.*?)["\']?\)', img)
if match:
vod['vod_pic'] = match.group(1)
if not vod['vod_pic']:
img = doc('img').filter(lambda i, this: pq(this).attr('src') and 'cover' in pq(this).attr('src').lower()).attr('src')
if img:
vod['vod_pic'] = img
description = doc('.stui-vodlist__detail .text').text()
if description:
vod['vod_remarks'] = description
content = doc('.content').text() or doc('.detail').text() or doc('.stui-content__detail').text()
if content:
vod['vod_content'] = content
vod['vod_play_url'] = f'正片${play_url}'
result['list'] = [vod]
except Exception as e:
print(f"detailContent error: {e}")
return result
def searchContent(self, key, quick, page='1'):
result = {}
videos = []
try:
if not key:
return result
url = f"https://www.mrcx2.yachts/cn/home/web/index.php/vod/search/wd/{urllib.parse.quote(key)}/page/{page}.html"
rsp = self.fetch(url)
if rsp and rsp.text:
doc = pq(rsp.text)
items = doc('.stui-vodlist li')
for item in items.items():
a = item.find('.stui-vodlist__thumb')
href = a.attr('href')
title = a.attr('title') or item.find('.title a').attr('title')
img = a.attr('data-original') or a.attr('style')
if img and 'background-image:' in img:
match = re.search(r'url\(["\']?(.*?)["\']?\)', img)
if match:
img = match.group(1)
if not title or not href:
continue
play_count = item.find('.text').text() or '播放0次'
score = item.find('.score').text() or '0.0 分'
videos.append({
'vod_id': href,
'vod_name': title,
'vod_pic': img,
'vod_remarks': f"{score} | {play_count}"
})
except Exception as e:
print(f"searchContent error: {e}")
result['list'] = videos
return result
def playerContent(self, flag, id, vipFlags):
result = {}
try:
if not id:
return result
if id.startswith('http'):
play_url = id
else:
if id.startswith('/'):
play_url = "https://www.mrcx2.yachts" + id
else:
play_url = "https://www.mrcx2.yachts/cn/home/web/" + id
result["parse"] = 1
result["playUrl"] = ''
result["url"] = play_url
result["header"] = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Referer': 'https://www.mrcx2.yachts/'
}
except Exception as e:
print(f"playerContent error: {e}")
return result
def isVideoFormat(self, url):
return False
def manualVideoCheck(self):
return False
def localProxy(self, param):
return {}

102
PY1/FeiApp.py Normal file
View File

@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
import re,sys,urllib3
from base.spider import Spider
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sys.path.append('..')
class Spider(Spider):
headers,host = {
'User-Agent': "okhttp/4.9.3",
'Accept-Encoding': "gzip"
},''
def init(self, extend=''):
host = extend.strip()
if host.startswith('http'):
self.host = host.rstrip('/')
def homeContent(self, filter):
response = self.fetch(f'{self.host}/api.php?type=getsort', headers=self.headers,verify=False).json()
classes = []
for i in response['list']:
classes.append({'type_id':i['type_id'],'type_name':i['type_name']})
return {'class': classes}
def homeVideoContent(self):
response = self.fetch(f'{self.host}/api.php?type=getHome', headers=self.headers,verify=False).json()
videos = []
for i, j in response.items():
if not isinstance(j, dict):
continue
lis = j.get('list')
if isinstance(lis, list):
videos.extend(lis)
return {'list': videos}
def categoryContent(self, tid, pg, filter, extend):
response = self.fetch(f"{self.host}/api.php?type=getvod&type_id={tid}&page={pg}&tag={extend.get('class','全部')}&year={extend.get('year','全部')}", headers=self.headers,verify=False).json()
return {'list': response['list'], 'page': pg}
def searchContent(self, key, quick, pg="1"):
response = self.fetch(f'{self.host}/api.php?type=getsearch&text={key}', headers=self.headers,verify=False).json()
for i in response['list']:
if not i.get('vod_content'):
i['vod_content'] = i['vod_blurb']
return {'list': response['list'], 'page': pg}
def detailContent(self, ids):
response = self.fetch(f'{self.host}/api.php?type=getVodinfo&id={ids[0]}', headers=self.headers,verify=False).json()
show = ''
vod_play_url = ''
for i in response['vod_player']['list']:
show += i.get('from','') + '$$$'
play_url = i.get('url','')
vod_play_url += '#'.join(f"{item}@{ids[0]}" for item in play_url.split('#')) + '$$$'
videos = [{
'vod_name': response.get('vod_name'),
'vod_pic': response.get('vod_pic'),
'vod_id': response.get('vod_id'),
'vod_class': response.get('vod_class'),
'vod_actor': response.get('vod_actor'),
'vod_blurb': response.get('vod_blurb'),
'vod_content': response.get('vod_blurb'),
'vod_remarks': response.get('vod_remarks'),
'vod_lang': response.get('vod_lang'),
'vod_play_from': show.rstrip('$$$'),
'vod_play_url': vod_play_url.rstrip('$$$')
}]
return {'list': videos}
def playerContent(self, flag, id, vipflags):
jx, ua = 0, 'Dalvik/2.1.0 (Linux; U; Android 14; Xiaomi 15 Build/SQ3A.220705.004)'
ua2 = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
vodurl, vodid = id.split('@')
if re.match(r'https?:\/\/.*\.(m3u8|mp4|flv)', vodurl):
url = vodurl
else:
try:
response = self.fetch(f'{self.host}/api.php?type=jx&vodurl={vodurl}&vodid={vodid}', headers=self.headers,verify=False).json()
url = response['url']
if not url.startswith('http') or url == vodurl:
jx, url, ua = 1, vodurl, ua2
except Exception:
jx, url, ua = 1, vodurl, ua2
return {'jx': jx, 'parse': 0, 'url': url,'header': {'User-Agent': ua}}
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def localProxy(self, param):
pass

251
PY1/JAV目录.py Normal file
View File

@@ -0,0 +1,251 @@
# -*- coding: utf-8 -*-
import re
import sys
from pyquery import PyQuery as pq
from base64 import b64decode, b64encode
from requests import Session
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.headers['referer'] = f'{self.host}/'
self.session = Session()
self.session.headers.update(self.headers)
def getName(self):
return "JAV目录大全"
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host = "https://javmenu.com"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-full-version': '"133.0.6943.98"',
'sec-ch-ua-arch': '"x86"',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua-platform-version': '"19.0.0"',
'sec-ch-ua-model': '""',
'sec-ch-ua-full-version-list': '"Not(A:Brand";v="99.0.0.0", "Google Chrome";v="133.0.6943.98", "Chromium";v="133.0.6943.98"',
'dnt': '1',
'upgrade-insecure-requests': '1',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'priority': 'u=0, i'
}
# -------------------- 业务接口 --------------------
def homeContent(self, filter):
cateManual = {
"FC2在线": "/zh/fc2/online",
"成人动画": "/zh/hanime/online",
"国产在线": "/zh/chinese/online",
"有码在线": "/zh/censored/online",
"无码在线": "/zh/uncensored/online",
"欧美在线": "/zh/western/online"
}
classes = [{'type_name': k, 'type_id': v} for k, v in cateManual.items()]
return {'class': classes}
def homeVideoContent(self):
data = self.getpq("/zh")
return {'list': self.getlist(data(".video-list-item"))}
def categoryContent(self, tid, pg, filter, extend):
url = f"{self.host}{tid}" if pg == '1' else f"{self.host}{tid}?page={pg}"
data = self.getpq(url)
return {
'list': self.getlist(data(".video-list-item")),
'page': pg,
'pagecount': 9999,
'limit': 90,
'total': 999999
}
def detailContent(self, ids):
vod_id = ids[0]
if not vod_id.startswith('http'):
url = f"{self.host}{vod_id}"
else:
url = vod_id
vod_id = vod_id.replace(self.host, '')
data = self.getpq(url)
vod = {
'vod_id': vod_id,
'vod_name': data('h1').text() or data('title').text().split(' - ')[0],
'vod_pic': self.getCover(data),
'vod_content': data('.card-text').text() or '',
'vod_director': '',
'vod_actor': self.getActors(data),
'vod_area': '日本',
'vod_year': self.getYear(data('.text-muted').text()),
'vod_remarks': self.getRemarks(data),
'vod_play_from': 'JAV在线',
'vod_play_url': self.getPlaylist(data, url)
}
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
url = f"{self.host}/zh/search?wd={key}&page={pg}"
data = self.getpq(url)
return {'list': self.getlist(data(".video-list-item"))}
def playerContent(self, flag, id, vipFlags):
return {'parse': 0, 'url': self.d64(id), 'header': self.headers}
# -------------------- 私有工具 --------------------
def getlist(self, data):
vlist = []
for item in data.items():
link = item('a').attr('href')
if not link or '/zh/' not in link:
continue
link = link.replace(self.host, '') if link.startswith(self.host) else link
name = item('.card-title').text() or item('img').attr('alt') or ''
if not name:
continue
vlist.append({
'vod_id': link,
'vod_name': name.split(' - ')[0].strip(),
'vod_pic': self.getListPicture(item),
'vod_remarks': (item('.text-muted').text() or '').strip(),
'style': {'ratio': 1.5, 'type': 'rect'}
})
return vlist
# ******** 修复版本支持LazyLoad和正确过滤 ********
def getListPicture(self, item):
"""
获取列表中的图片
支持LazyLoad延迟加载机制
过滤水印、占位符和无预览图
"""
# 获取所有img标签
imgs = item('img')
for img in imgs.items():
# 优先级先从data-src获取LazyLoad属性再从src获取
pic = img.attr('data-src') or img.attr('src')
# 过滤条件:排除水印、占位符、加载图片
if pic and not any(keyword in pic for keyword in ['button_logo', 'no_preview', 'loading.gif', 'loading.png']):
return pic
return ''
def getCover(self, data):
"""
获取详情页的图片
支持LazyLoad延迟加载机制
过滤水印、占位符和无预览图
"""
# 获取所有img标签
imgs = data('img')
for img in imgs.items():
# 优先级先从data-src获取LazyLoad属性再从src获取
pic = img.attr('data-src') or img.attr('src')
# 过滤条件:排除水印、占位符、加载图片
if pic and not any(keyword in pic for keyword in ['button_logo', 'no_preview', 'loading.gif', 'loading.png', 'website_building']):
return pic
return ''
# **********************************
def getActors(self, data):
"""获取演员信息"""
actors = []
h1_text = data('h1').text()
if h1_text:
actors.extend(h1_text.strip().split()[1:])
actor_links = data('a[href*="/actor/"]')
for actor_link in actor_links.items():
actor_text = actor_link.text()
if actor_text and actor_text not in actors:
actors.append(actor_text)
return ','.join(actors) if actors else '未知'
def getYear(self, date_str):
"""从日期字符串中提取年份"""
m = re.search(r'(\d{4})-\d{2}-\d{2}', date_str or '')
return m.group(1) if m else ''
def getRemarks(self, data):
"""获取备注信息(标签)"""
tags = [tag.text() for tag in data('.badge').items() if tag.text()]
return ' '.join(set(tags)) if tags else ''
def getPlaylist(self, data, url):
"""
获取播放列表
从source、video标签和脚本中提取m3u8链接
"""
play_urls, seen = [], set()
# 从source标签获取
for src in data('source').items():
u = src.attr('src')
if u and u not in seen:
play_urls.append(f"{len(play_urls)+1}${self.e64(u)}")
seen.add(u)
# 从video标签获取
for u in data('video').items():
u = u.attr('src')
if u and u not in seen:
play_urls.append(f"线路{len(play_urls)+1}${self.e64(u)}")
seen.add(u)
# 从脚本中提取m3u8链接
for m in re.findall(r'https?://[^\s"\'<>]+\.m3u8[^\s"\'<>]*', data('script').text()):
if m not in seen:
play_urls.append(f"线路{len(play_urls)+1}${self.e64(m)}")
seen.add(m)
# 如果没有找到播放链接使用页面URL
if not play_urls:
play_urls.append(f"在线播放${self.e64(url)}")
return '#'.join(play_urls)
def getpq(self, path=''):
"""获取页面内容并返回PyQuery对象"""
url = path if path.startswith('http') else f'{self.host}{path}'
try:
rsp = self.session.get(url, timeout=20)
rsp.encoding = 'utf-8'
return pq(rsp.text)
except Exception as e:
print(f"getpq error: {e}")
return pq('')
def e64(self, text):
"""Base64编码"""
try:
return b64encode(text.encode('utf-8')).decode('utf-8')
except Exception:
return ''
def d64(self, encoded_text):
"""Base64解码"""
try:
return b64decode(encoded_text.encode('utf-8')).decode('utf-8')
except Exception:
return ''

301
PY1/Phb.py Normal file
View File

@@ -0,0 +1,301 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import re
import sys
from base64 import b64decode, b64encode
from urllib.parse import urlparse
import requests
from pyquery import PyQuery as pq
from requests import Session
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
'''
内置代理配置真心jar为例
{
"key": "Phb",
"name": "Phb",
"type": 3,
"searchable": 1,
"quickSearch": 1,
"filterable": 1,
"api": "./py/Phb.py",
"ext": {
"http": "http://127.0.0.1:1072",
"https": "http://127.0.0.1:1072"
}
},
http(s)代理都是http
'''
try:self.proxies = json.loads(extend)
except:self.proxies = {}
self.headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5410.0 Safari/537.36',
'pragma': 'no-cache',
'cache-control': 'no-cache',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
'dnt': '1',
'sec-ch-ua-mobile': '?0',
'sec-fetch-site': 'cross-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'priority': 'u=1, i',
}
self.host = self.gethost()
self.headers.update({'referer': f'{self.host}/', 'origin': self.host})
self.session = Session()
self.session.proxies.update(self.proxies)
self.session.headers.update(self.headers)
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeContent(self, filter):
result = {}
cateManual = {
"视频": "/video",
"片单": "/playlists",
"频道": "/channels",
"分类": "/categories",
"明星": "/pornstars"
}
classes = []
filters = {}
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
data = self.getpq('/recommended')
vhtml = data("#recommendedListings .pcVideoListItem .phimage")
return {'list': self.getlist(vhtml)}
def categoryContent(self, tid, pg, filter, extend):
vdata = []
result = {}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
if tid == '/video' or '_this_video' in tid:
pagestr = f'&' if '?' in tid else f'?'
tid = tid.split('_this_video')[0]
data = self.getpq(f'{tid}{pagestr}page={pg}')
vdata = self.getlist(data('#videoCategory .pcVideoListItem'))
elif tid == '/playlists':
data = self.getpq(f'{tid}?page={pg}')
vhtml = data('#playListSection li')
vdata = []
for i in vhtml.items():
vdata.append({
'vod_id': 'playlists_click_' + i('.thumbnail-info-wrapper .display-block a').attr('href'),
'vod_name': i('.thumbnail-info-wrapper .display-block a').attr('title'),
'vod_pic': self.proxy(i('.largeThumb').attr('src')),
'vod_tag': 'folder',
'vod_remarks': i('.playlist-videos .number').text(),
'style': {"type": "rect", "ratio": 1.33}
})
elif tid == '/channels':
data = self.getpq(f'{tid}?o=rk&page={pg}')
vhtml = data('#filterChannelsSection li .description')
vdata = []
for i in vhtml.items():
vdata.append({
'vod_id': 'director_click_' + i('.avatar a').attr('href'),
'vod_name': i('.avatar img').attr('alt'),
'vod_pic': self.proxy(i('.avatar img').attr('src')),
'vod_tag': 'folder',
'vod_remarks': i('.descriptionContainer ul li').eq(-1).text(),
'style': {"type": "rect", "ratio": 1.33}
})
elif tid == '/categories' and pg == '1':
result['pagecount'] = 1
data = self.getpq(f'{tid}')
vhtml = data('.categoriesListSection li .relativeWrapper')
vdata = []
for i in vhtml.items():
vdata.append({
'vod_id': i('a').attr('href') + '_this_video',
'vod_name': i('a').attr('alt'),
'vod_pic': self.proxy(i('a img').attr('src')),
'vod_tag': 'folder',
'style': {"type": "rect", "ratio": 1.33}
})
elif tid == '/pornstars':
data = self.getpq(f'{tid}?o=t&page={pg}')
vhtml = data('#popularPornstars .performerCard .wrap')
vdata = []
for i in vhtml.items():
vdata.append({
'vod_id': 'pornstars_click_' + i('a').attr('href'),
'vod_name': i('.performerCardName').text(),
'vod_pic': self.proxy(i('a img').attr('src')),
'vod_tag': 'folder',
'vod_year': i('.performerVideosViewsCount span').eq(0).text(),
'vod_remarks': i('.performerVideosViewsCount span').eq(-1).text(),
'style': {"type": "rect", "ratio": 1.33}
})
elif 'playlists_click' in tid:
tid = tid.split('click_')[-1]
if pg == '1':
hdata = self.getpq(tid)
self.token = hdata('#searchInput').attr('data-token')
vdata = self.getlist(hdata('#videoPlaylist .pcVideoListItem .phimage'))
else:
tid = tid.split('playlist/')[-1]
data = self.getpq(f'/playlist/viewChunked?id={tid}&token={self.token}&page={pg}')
vdata = self.getlist(data('.pcVideoListItem .phimage'))
elif 'director_click' in tid:
tid = tid.split('click_')[-1]
data = self.getpq(f'{tid}/videos?page={pg}')
vdata = self.getlist(data('#showAllChanelVideos .pcVideoListItem .phimage'))
elif 'pornstars_click' in tid:
tid = tid.split('click_')[-1]
data = self.getpq(f'{tid}/videos?page={pg}')
vdata = self.getlist(data('#mostRecentVideosSection .pcVideoListItem .phimage'))
result['list'] = vdata
return result
def detailContent(self, ids):
url = f"{self.host}{ids[0]}"
data = self.getpq(ids[0])
vn = data('meta[property="og:title"]').attr('content')
dtext = data('.userInfo .usernameWrap a')
pdtitle = '[a=cr:' + json.dumps(
{'id': 'director_click_' + dtext.attr('href'), 'name': dtext.text()}) + '/]' + dtext.text() + '[/a]'
vod = {
'vod_name': vn,
'vod_director': pdtitle,
'vod_remarks': (data('.userInfo').text() + ' / ' + data('.ratingInfo').text()).replace('\n', ' / '),
'vod_play_from': 'Pornhub',
'vod_play_url': ''
}
js_content = data("#player script").eq(0).text()
plist = [f"{vn}${self.e64(f'{1}@@@@{url}')}"]
try:
pattern = r'"mediaDefinitions":\s*(\[.*?\]),\s*"isVertical"'
match = re.search(pattern, js_content, re.DOTALL)
if match:
json_str = match.group(1)
udata = json.loads(json_str)
plist = [
f"{media['height']}${self.e64(f'{0}@@@@{url}')}"
for media in udata[:-1]
if (url := media.get('videoUrl'))
]
except Exception as e:
print(f"提取mediaDefinitions失败: {str(e)}")
vod['vod_play_url'] = '#'.join(plist)
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
data = self.getpq(f'/video/search?search={key}&page={pg}')
return {'list': self.getlist(data('#videoSearchResult .pcVideoListItem .phimage'))}
def playerContent(self, flag, id, vipFlags):
ids = self.d64(id).split('@@@@')
if '.m3u8' in ids[1]: ids[1] = self.proxy(ids[1], 'm3u8')
return {'parse': int(ids[0]), 'url': ids[1], 'header': self.headers}
def localProxy(self, param):
url = self.d64(param.get('url'))
if param.get('type') == 'm3u8':
return self.m3Proxy(url)
else:
return self.tsProxy(url)
def m3Proxy(self, url):
ydata = requests.get(url, headers=self.headers, proxies=self.proxies, allow_redirects=False)
data = ydata.content.decode('utf-8')
if ydata.headers.get('Location'):
url = ydata.headers['Location']
data = requests.get(url, headers=self.headers, proxies=self.proxies).content.decode('utf-8')
lines = data.strip().split('\n')
last_r = url[:url.rfind('/')]
parsed_url = urlparse(url)
durl = parsed_url.scheme + "://" + parsed_url.netloc
for index, string in enumerate(lines):
if '#EXT' not in string:
if 'http' not in string:
domain = last_r if string.count('/') < 2 else durl
string = domain + ('' if string.startswith('/') else '/') + string
lines[index] = self.proxy(string, string.split('.')[-1].split('?')[0])
data = '\n'.join(lines)
return [200, "application/vnd.apple.mpegur", data]
def tsProxy(self, url):
data = requests.get(url, headers=self.headers, proxies=self.proxies, stream=True)
return [200, data.headers['Content-Type'], data.content]
def gethost(self):
try:
response = requests.get('https://www.pornhub.com', headers=self.headers, proxies=self.proxies,
allow_redirects=False)
return response.headers['Location'][:-1]
except Exception as e:
print(f"获取主页失败: {str(e)}")
return "https://www.pornhub.com"
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64编码错误: {str(e)}")
return ""
def d64(self, encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64解码错误: {str(e)}")
return ""
def getlist(self, data):
vlist = []
for i in data.items():
vlist.append({
'vod_id': i('a').attr('href'),
'vod_name': i('a').attr('title'),
'vod_pic': self.proxy(i('img').attr('src')),
'vod_remarks': i('.bgShadeEffect').text() or i('.duration').text(),
'style': {'ratio': 1.33, 'type': 'rect'}
})
return vlist
def getpq(self, path):
try:
response = self.session.get(f'{self.host}{path}').text
return pq(response.encode('utf-8'))
except Exception as e:
print(f"请求失败: , {str(e)}")
return None
def proxy(self, data, type='img'):
if data and len(self.proxies):return f"{self.getProxyUrl()}&url={self.e64(data)}&type={type}"
else:return data

301
PY1/Pornhub.py Normal file
View File

@@ -0,0 +1,301 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import re
import sys
from base64 import b64decode, b64encode
from urllib.parse import urlparse
import requests
from pyquery import PyQuery as pq
from requests import Session
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
'''
内置代理配置真心jar为例
{
"key": "Phb",
"name": "Phb",
"type": 3,
"searchable": 1,
"quickSearch": 1,
"filterable": 1,
"api": "./py/Phb.py",
"ext": {
"http": "http://127.0.0.1:1072",
"https": "http://127.0.0.1:1072"
}
},
http(s)代理都是http
'''
try:self.proxies = json.loads(extend)
except:self.proxies = {}
self.headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5410.0 Safari/537.36',
'pragma': 'no-cache',
'cache-control': 'no-cache',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
'dnt': '1',
'sec-ch-ua-mobile': '?0',
'sec-fetch-site': 'cross-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'priority': 'u=1, i',
}
self.host = self.gethost()
self.headers.update({'referer': f'{self.host}/', 'origin': self.host})
self.session = Session()
self.session.proxies.update(self.proxies)
self.session.headers.update(self.headers)
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeContent(self, filter):
result = {}
cateManual = {
"视频": "/video",
"片单": "/playlists",
"频道": "/channels",
"分类": "/categories",
"明星": "/pornstars"
}
classes = []
filters = {}
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
data = self.getpq('/recommended')
vhtml = data("#recommendedListings .pcVideoListItem .phimage")
return {'list': self.getlist(vhtml)}
def categoryContent(self, tid, pg, filter, extend):
vdata = []
result = {}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
if tid == '/video' or '_this_video' in tid:
pagestr = f'&' if '?' in tid else f'?'
tid = tid.split('_this_video')[0]
data = self.getpq(f'{tid}{pagestr}page={pg}')
vdata = self.getlist(data('#videoCategory .pcVideoListItem'))
elif tid == '/playlists':
data = self.getpq(f'{tid}?page={pg}')
vhtml = data('#playListSection li')
vdata = []
for i in vhtml.items():
vdata.append({
'vod_id': 'playlists_click_' + i('.thumbnail-info-wrapper .display-block a').attr('href'),
'vod_name': i('.thumbnail-info-wrapper .display-block a').attr('title'),
'vod_pic': self.proxy(i('.largeThumb').attr('src')),
'vod_tag': 'folder',
'vod_remarks': i('.playlist-videos .number').text(),
'style': {"type": "rect", "ratio": 1.33}
})
elif tid == '/channels':
data = self.getpq(f'{tid}?o=rk&page={pg}')
vhtml = data('#filterChannelsSection li .description')
vdata = []
for i in vhtml.items():
vdata.append({
'vod_id': 'director_click_' + i('.avatar a').attr('href'),
'vod_name': i('.avatar img').attr('alt'),
'vod_pic': self.proxy(i('.avatar img').attr('src')),
'vod_tag': 'folder',
'vod_remarks': i('.descriptionContainer ul li').eq(-1).text(),
'style': {"type": "rect", "ratio": 1.33}
})
elif tid == '/categories' and pg == '1':
result['pagecount'] = 1
data = self.getpq(f'{tid}')
vhtml = data('.categoriesListSection li .relativeWrapper')
vdata = []
for i in vhtml.items():
vdata.append({
'vod_id': i('a').attr('href') + '_this_video',
'vod_name': i('a').attr('alt'),
'vod_pic': self.proxy(i('a img').attr('src')),
'vod_tag': 'folder',
'style': {"type": "rect", "ratio": 1.33}
})
elif tid == '/pornstars':
data = self.getpq(f'{tid}?o=t&page={pg}')
vhtml = data('#popularPornstars .performerCard .wrap')
vdata = []
for i in vhtml.items():
vdata.append({
'vod_id': 'pornstars_click_' + i('a').attr('href'),
'vod_name': i('.performerCardName').text(),
'vod_pic': self.proxy(i('a img').attr('src')),
'vod_tag': 'folder',
'vod_year': i('.performerVideosViewsCount span').eq(0).text(),
'vod_remarks': i('.performerVideosViewsCount span').eq(-1).text(),
'style': {"type": "rect", "ratio": 1.33}
})
elif 'playlists_click' in tid:
tid = tid.split('click_')[-1]
if pg == '1':
hdata = self.getpq(tid)
self.token = hdata('#searchInput').attr('data-token')
vdata = self.getlist(hdata('#videoPlaylist .pcVideoListItem .phimage'))
else:
tid = tid.split('playlist/')[-1]
data = self.getpq(f'/playlist/viewChunked?id={tid}&token={self.token}&page={pg}')
vdata = self.getlist(data('.pcVideoListItem .phimage'))
elif 'director_click' in tid:
tid = tid.split('click_')[-1]
data = self.getpq(f'{tid}/videos?page={pg}')
vdata = self.getlist(data('#showAllChanelVideos .pcVideoListItem .phimage'))
elif 'pornstars_click' in tid:
tid = tid.split('click_')[-1]
data = self.getpq(f'{tid}/videos?page={pg}')
vdata = self.getlist(data('#mostRecentVideosSection .pcVideoListItem .phimage'))
result['list'] = vdata
return result
def detailContent(self, ids):
url = f"{self.host}{ids[0]}"
data = self.getpq(ids[0])
vn = data('meta[property="og:title"]').attr('content')
dtext = data('.userInfo .usernameWrap a')
pdtitle = '[a=cr:' + json.dumps(
{'id': 'director_click_' + dtext.attr('href'), 'name': dtext.text()}) + '/]' + dtext.text() + '[/a]'
vod = {
'vod_name': vn,
'vod_director': pdtitle,
'vod_remarks': (data('.userInfo').text() + ' / ' + data('.ratingInfo').text()).replace('\n', ' / '),
'vod_play_from': '老僧酿酒',
'vod_play_url': ''
}
js_content = data("#player script").eq(0).text()
plist = [f"{vn}${self.e64(f'{1}@@@@{url}')}"]
try:
pattern = r'"mediaDefinitions":\s*(\[.*?\]),\s*"isVertical"'
match = re.search(pattern, js_content, re.DOTALL)
if match:
json_str = match.group(1)
udata = json.loads(json_str)
plist = [
f"{media['height']}${self.e64(f'{0}@@@@{url}')}"
for media in udata[:-1]
if (url := media.get('videoUrl'))
]
except Exception as e:
print(f"提取mediaDefinitions失败: {str(e)}")
vod['vod_play_url'] = '#'.join(plist)
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
data = self.getpq(f'/video/search?search={key}&page={pg}')
return {'list': self.getlist(data('#videoSearchResult .pcVideoListItem .phimage'))}
def playerContent(self, flag, id, vipFlags):
ids = self.d64(id).split('@@@@')
if '.m3u8' in ids[1]: ids[1] = self.proxy(ids[1], 'm3u8')
return {'parse': int(ids[0]), 'url': ids[1], 'header': self.headers}
def localProxy(self, param):
url = self.d64(param.get('url'))
if param.get('type') == 'm3u8':
return self.m3Proxy(url)
else:
return self.tsProxy(url)
def m3Proxy(self, url):
ydata = requests.get(url, headers=self.headers, proxies=self.proxies, allow_redirects=False)
data = ydata.content.decode('utf-8')
if ydata.headers.get('Location'):
url = ydata.headers['Location']
data = requests.get(url, headers=self.headers, proxies=self.proxies).content.decode('utf-8')
lines = data.strip().split('\n')
last_r = url[:url.rfind('/')]
parsed_url = urlparse(url)
durl = parsed_url.scheme + "://" + parsed_url.netloc
for index, string in enumerate(lines):
if '#EXT' not in string:
if 'http' not in string:
domain = last_r if string.count('/') < 2 else durl
string = domain + ('' if string.startswith('/') else '/') + string
lines[index] = self.proxy(string, string.split('.')[-1].split('?')[0])
data = '\n'.join(lines)
return [200, "application/vnd.apple.mpegur", data]
def tsProxy(self, url):
data = requests.get(url, headers=self.headers, proxies=self.proxies, stream=True)
return [200, data.headers['Content-Type'], data.content]
def gethost(self):
try:
response = requests.get('https://www.pornhub.com', headers=self.headers, proxies=self.proxies,
allow_redirects=False)
return response.headers['Location'][:-1]
except Exception as e:
print(f"获取主页失败: {str(e)}")
return "https://www.pornhub.com"
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64编码错误: {str(e)}")
return ""
def d64(self, encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64解码错误: {str(e)}")
return ""
def getlist(self, data):
vlist = []
for i in data.items():
vlist.append({
'vod_id': i('a').attr('href'),
'vod_name': i('a').attr('title'),
'vod_pic': self.proxy(i('img').attr('src')),
'vod_remarks': i('.bgShadeEffect').text() or i('.duration').text(),
'style': {'ratio': 1.33, 'type': 'rect'}
})
return vlist
def getpq(self, path):
try:
response = self.session.get(f'{self.host}{path}').text
return pq(response.encode('utf-8'))
except Exception as e:
print(f"请求失败: , {str(e)}")
return None
def proxy(self, data, type='img'):
if data and len(self.proxies):return f"{self.getProxyUrl()}&url={self.e64(data)}&type={type}"
else:return data

196
PY1/ThePorn.py Normal file
View File

@@ -0,0 +1,196 @@
# -*- coding: utf-8 -*-
import re
import sys
from pyquery import PyQuery as pq
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "ThePorn"
def init(self, extend=""):
pass
def homeContent(self, filter):
result = {}
try:
rsp = self.fetch("https://theporn.cc/categories")
root = pq(rsp.text)
classes = []
main_categories = {
"影片": "categories/all", "日本AV": "jav", "欧美": "eu", "VR": "vr",
"最佳影片": "video/best", "日本无码": "jav/uncensored"
}
for name, cid in main_categories.items():
classes.append({'type_name': name, 'type_id': cid})
for item in root('.categories_list .categorie_item').items():
a_tag = item.find('a')
if a_tag:
name = a_tag.text().strip()
href = a_tag.attr('href')
if href and name:
classes.append({
'type_name': name,
'type_id': f"categories/{href.split('/')[-1]}"
})
seen = set()
result['class'] = [cls for cls in classes if not (key := (cls['type_name'], cls['type_id'])) in seen and not seen.add(key)]
except Exception as e:
cateManual = {
"影片": "categories/all", "日本AV": "jav", "欧美": "eu", "VR": "vr",
"最佳影片": "video/best", "日本无码": "jav/uncensored"
}
result['class'] = [{'type_name': k, 'type_id': v} for k, v in cateManual.items()]
return result
def homeVideoContent(self):
rsp = self.fetch("https://theporn.cc/")
return {'list': [self.parseVideoItem(item) for item in pq(rsp.text)('.video-list .avdata-outer').items()]}
def parseVideoItem(self, item):
try:
a_tag = item.find('a.av-link')
href = a_tag.attr('href')
img_tag = item.find('img.cover-img')
cover = img_tag.attr('data-src') or img_tag.attr('src')
return {
"vod_id": href.split('/')[-1],
"vod_name": item.find('.av_data_title').text(),
"vod_pic": cover if cover.startswith('http') else "https://theporn.cc" + cover,
"vod_remarks": item.find('.duration').text()
} if href else None
except:
return None
def categoryContent(self, tid, pg, filter, extend):
# 修复翻页逻辑
if int(pg) == 1:
url = f"https://theporn.cc/{tid}"
else:
url = f"https://theporn.cc/{tid}/{pg}"
rsp = self.fetch(url)
root = pq(rsp.text)
videos = []
for selector in ['.video-list .avdata-outer', '.avdata-outer', '.video-list .card']:
video_list = root(selector)
if video_list.length > 0:
videos = [self.parseVideoItem(item) for item in video_list.items()]
break
# 尝试获取总页数
pagecount = 9999
try:
pagination = root('.pagination')
if pagination.length > 0:
page_links = pagination.find('a')
if page_links.length > 0:
last_page = 1
for link in page_links.items():
href = link.attr('href')
if href:
# 从URL中提取页码
match = re.search(r'/(\d+)$', href)
if match:
page_num = int(match.group(1))
last_page = max(last_page, page_num)
pagecount = last_page
except:
pass
return {
'list': videos,
'page': int(pg),
'pagecount': pagecount,
'limit': 90,
'total': 999999
}
def detailContent(self, array):
tid = array[0]
rsp = self.fetch(f"https://theporn.cc/video/{tid}")
root = pq(rsp.text)
title = root('title').text().replace(' - ThePorn', '') or root('.av-big-title .inner-title').text()
cover = root('.cover-img').attr('src')
if not cover:
match = re.search(r'background-image:\s*url\("([^"]+)"\)', root('.vjs-poster').attr('style') or '')
cover = match.group(1) if match else ''
return {'list': [{
'vod_id': tid,
'vod_name': title,
'vod_pic': cover,
'vod_content': title,
'vod_actor': '未知演员',
'vod_director': '未知导演',
'vod_area': '未知地区',
'vod_year': '2024',
'vod_remarks': root('.duration').text() or '未知时长',
'vod_play_from': 'ThePorn',
'vod_play_url': f'第1集${tid}'
}]}
def searchContent(self, key, quick, page='1'):
# 搜索页面的翻页逻辑
if int(page) == 1:
url = f"https://theporn.cc/search/{key}"
else:
url = f"https://theporn.cc/search/{key}/{page}"
rsp = self.fetch(url)
root = pq(rsp.text)
for selector in ['.search-results .video-item', '.video-list .avdata-outer', '.avdata-outer']:
video_list = root(selector)
if video_list.length > 0:
return {'list': [self.parseVideoItem(item) for item in video_list.items()]}
return {'list': []}
def playerContent(self, flag, id, vipFlags):
rsp = self.fetch(f"https://theporn.cc/video/{id}")
root = pq(rsp.text)
hash_id = ""
for script in root('script').items():
script_text = script.text()
if 'hash_id' in script_text:
match = re.search(r'"hash_id":\s*"([a-f0-9]+)"', script_text)
if match:
hash_id = match.group(1)
break
if hash_id:
src = f"https://b2.bttss.cc/videos/{hash_id}/g.m3u8?h=3121efe8979c635"
else:
video_tag = root('video#video-player_html5_api')
src = video_tag.attr('src') if video_tag else ""
return {
"parse": 0,
"playUrl": "",
"url": src,
"header": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Referer": "https://theporn.cc/",
"Origin": "https://theporn.cc"
}
}
def isVideoFormat(self, url):
return any(fmt in url.lower() for fmt in ['.m3u8', '.mp4', '.avi', '.mkv', '.flv', '.webm']) if url else False
def manualVideoCheck(self):
pass
def localProxy(self, param):
return [200, "video/MP2T", {}, ""]

270
PY1/Xhm.py Normal file
View File

@@ -0,0 +1,270 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import sys
from base64 import b64decode, b64encode
from pyquery import PyQuery as pq
from requests import Session
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = self.gethost()
self.headers['referer'] = f'{self.host}/'
self.session = Session()
self.session.headers.update(self.headers)
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-full-version': '"133.0.6943.98"',
'sec-ch-ua-arch': '"x86"',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua-platform-version': '"19.0.0"',
'sec-ch-ua-model': '""',
'sec-ch-ua-full-version-list': '"Not(A:Brand";v="99.0.0.0", "Google Chrome";v="133.0.6943.98", "Chromium";v="133.0.6943.98"',
'dnt': '1',
'upgrade-insecure-requests': '1',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'priority': 'u=0, i'
}
def homeContent(self, filter):
result = {}
cateManual = {
"4K": "/4k",
"国产": "two_click_/categories/chinese",
"最新": "/newest",
"最佳": "/best",
"频道": "/channels",
"类别": "/categories",
"明星": "/pornstars"
}
classes = []
filters = {}
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
if k !='4K':filters[cateManual[k]]=[{'key':'type','name':'类型','value':[{'n':'4K','v':'/4k'}]}]
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
data = self.getpq()
return {'list': self.getlist(data(".thumb-list--sidebar .thumb-list__item"))}
def categoryContent(self, tid, pg, filter, extend):
vdata = []
result = {}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
if tid in ['/4k', '/newest', '/best'] or 'two_click_' in tid:
if 'two_click_' in tid: tid = tid.split('click_')[-1]
data = self.getpq(f'{tid}{extend.get("type","")}/{pg}')
vdata = self.getlist(data(".thumb-list--sidebar .thumb-list__item"))
elif tid == '/channels':
data = self.getpq(f'{tid}/{pg}')
jsdata = self.getjsdata(data)
for i in jsdata['channels']:
vdata.append({
'vod_id': f"two_click_" + i.get('channelURL'),
'vod_name': i.get('channelName'),
'vod_pic': i.get('siteLogoURL'),
'vod_year': f'videos:{i.get("videoCount")}',
'vod_tag': 'folder',
'vod_remarks': f'subscribers:{i["subscriptionModel"].get("subscribers")}',
'style': {'ratio': 1.33, 'type': 'rect'}
})
elif tid == '/categories':
result['pagecount'] = pg
data = self.getpq(tid)
self.cdata = self.getjsdata(data)
for i in self.cdata['layoutPage']['store']['popular']['assignable']:
vdata.append({
'vod_id': "one_click_" + i.get('id'),
'vod_name': i.get('name'),
'vod_pic': '',
'vod_tag': 'folder',
'style': {'ratio': 1.33, 'type': 'rect'}
})
elif tid == '/pornstars':
data = self.getpq(f'{tid}/{pg}')
pdata = self.getjsdata(data)
for i in pdata['pagesPornstarsComponent']['pornstarListProps']['pornstars']:
vdata.append({
'vod_id': f"two_click_" + i.get('pageURL'),
'vod_name': i.get('name'),
'vod_pic': i.get('imageThumbUrl'),
'vod_remarks': i.get('translatedCountryName'),
'vod_tag': 'folder',
'style': {'ratio': 1.33, 'type': 'rect'}
})
elif 'one_click' in tid:
result['pagecount'] = pg
tid = tid.split('click_')[-1]
for i in self.cdata['layoutPage']['store']['popular']['assignable']:
if i.get('id') == tid:
for j in i['items']:
vdata.append({
'vod_id': f"two_click_" + j.get('url'),
'vod_name': j.get('name'),
'vod_pic': j.get('thumb'),
'vod_tag': 'folder',
'style': {'ratio': 1.33, 'type': 'rect'}
})
result['list'] = vdata
return result
def detailContent(self, ids):
data = self.getpq(ids[0])
djs = self.getjsdata(data)
vn = data('meta[property="og:title"]').attr('content')
dtext = data('#video-tags-list-container')
href = dtext('a').attr('href')
title = dtext('span[class*="body-bold-"]').eq(0).text()
pdtitle = ''
if href:
pdtitle = '[a=cr:' + json.dumps({'id': 'two_click_' + href, 'name': title}) + '/]' + title + '[/a]'
vod = {
'vod_name': vn,
'vod_director': pdtitle,
'vod_remarks': data('.rb-new__info').text(),
'vod_play_from': 'Xhamster',
'vod_play_url': ''
}
try:
plist = []
d = djs['xplayerSettings']['sources']
f = d.get('standard')
def custom_sort_key(url):
quality = url.split('$')[0]
number = ''.join(filter(str.isdigit, quality))
number = int(number) if number else 0
return -number, quality
if f:
for key, value in f.items():
if isinstance(value, list):
for info in value:
id = self.e64(f'{0}@@@@{info.get("url") or info.get("fallback")}')
plist.append(f"{info.get('label') or info.get('quality')}${id}")
plist.sort(key=custom_sort_key)
if d.get('hls'):
for format_type, info in d['hls'].items():
if url := info.get('url'):
encoded = self.e64(f'{0}@@@@{url}')
plist.append(f"{format_type}${encoded}")
except Exception as e:
plist = [f"{vn}${self.e64(f'{1}@@@@{ids[0]}')}"]
print(f"获取视频信息失败: {str(e)}")
vod['vod_play_url'] = '#'.join(plist)
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
data = self.getpq(f'/search/{key}?page={pg}')
return {'list': self.getlist(data(".thumb-list--sidebar .thumb-list__item")), 'page': pg}
def playerContent(self, flag, id, vipFlags):
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5410.0 Safari/537.36',
'pragma': 'no-cache',
'cache-control': 'no-cache',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
'dnt': '1',
'sec-ch-ua-mobile': '?0',
'origin': self.host,
'sec-fetch-site': 'cross-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'referer': f'{self.host}/',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'priority': 'u=1, i',
}
ids = self.d64(id).split('@@@@')
return {'parse': int(ids[0]), 'url': ids[1], 'header': headers}
def localProxy(self, param):
pass
def gethost(self):
try:
response = self.fetch('https://xhamster.com', headers=self.headers, allow_redirects=False)
return response.headers['Location']
except Exception as e:
print(f"获取主页失败: {str(e)}")
return "https://zn.xhamster.com"
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64编码错误: {str(e)}")
return ""
def d64(self, encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64解码错误: {str(e)}")
return ""
def getlist(self, data):
vlist = []
for i in data.items():
vlist.append({
'vod_id': i('.role-pop').attr('href'),
'vod_name': i('.video-thumb-info a').text(),
'vod_pic': i('.role-pop img').attr('src'),
'vod_year': i('.video-thumb-info .video-thumb-views').text().split(' ')[0],
'vod_remarks': i('.role-pop div[data-role="video-duration"]').text(),
'style': {'ratio': 1.33, 'type': 'rect'}
})
return vlist
def getpq(self, path=''):
h = '' if path.startswith('http') else self.host
response = self.session.get(f'{h}{path}').text
try:
return pq(response)
except Exception as e:
print(f"{str(e)}")
return pq(response.encode('utf-8'))
def getjsdata(self, data):
vhtml = data("script[id='initials-script']").text()
jst = json.loads(vhtml.split('initials=')[-1][:-1])
return jst

276
PY1/Xvd.py Normal file
View File

@@ -0,0 +1,276 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import re
import sys
from urllib.parse import urlparse
import requests
from pyquery import PyQuery as pq
from base64 import b64decode, b64encode
from requests import Session
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
try:self.proxies = json.loads(extend)
except:self.proxies = {}
self.session = Session()
self.session.proxies.update(self.proxies)
self.session.headers.update(self.headers)
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host = "https://www.xvideos.com"
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5410.0 Safari/537.36",
"pragma": "no-cache",
"cache-control": "no-cache",
"sec-ch-ua-platform": "\"Windows\"",
"sec-ch-ua": "\"Not(A:Brand\";v=\"99\", \"Google Chrome\";v=\"133\", \"Chromium\";v=\"133\"",
"dnt": "1",
"origin":host,
'referer':f'{host}/',
"sec-ch-ua-mobile": "?0",
"sec-fetch-site": "cross-site",
"sec-fetch-mode": "cors",
"sec-fetch-dest": "empty",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
"priority": "u=1, i"
}
def homeContent(self, filter):
result = {}
cateManual = {
"最新": "/new",
"最佳": "/best",
"频道": "/channels-index",
"标签": "/tags",
"明星": "/pornstars-index"
}
classes = []
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
result['class'] = classes
return result
def homeVideoContent(self):
data = self.getpq()
return {'list':self.getlist(data(".mozaique .frame-block"))}
def categoryContent(self, tid, pg, filter, extend):
vdata = []
result = {}
page = f"/{int(pg) - 1}" if pg != '1' else ''
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
if tid=='/new' or 'tags_click' in tid:
if 'tags_click' in tid:tid=tid.split('click_')[-1]
data=self.getpq(f'{tid}/{pg}')
vdata=self.getlist(data(".mozaique .frame-block"))
elif tid=='/best':
if pg=='1':
self.path=self.session.get(f'{self.host}{tid}',allow_redirects=False).headers['Location']
data=self.getpq(f'{self.path}{page}')
vdata=self.getlist(data(".mozaique .frame-block"))
elif tid=='/channels-index' or tid=='/pornstars-index':
data = self.getpq(f'{tid}{page}')
vhtml=data(".mozaique .thumb-block")
for i in vhtml.items():
a = i('.thumb-inside .thumb a')
match = re.search(r'src="([^"]+)"', a('script').text())
img=''
if match:
img = match.group(1).strip()
vdata.append({
'vod_id': f"channels_click_{'/channels'if tid=='/channels-index' else ''}"+a.attr('href'),
'vod_name': a('.profile-name').text() or i('.profile-name').text().replace('\xa0','/'),
'vod_pic': self.proxy(img),
'vod_tag': 'folder',
'vod_remarks': i('.thumb-under .profile-counts').text(),
'style': {'ratio': 1.33, 'type': 'rect'}
})
elif tid=='/tags':
result['pagecount'] = pg
vhtml = self.getpq(tid)
vhtml = vhtml('.tags-list')
for d in vhtml.items():
for i in d('li a').items():
vdata.append({
'vod_id': "tags_click_"+i.attr('href'),
'vod_name': i.attr('title') or i('b').text(),
'vod_pic': '',
'vod_tag': 'folder',
'vod_remarks': i('.navbadge').text(),
'style': {'ratio': 1.33, 'type': 'rect'}
})
elif 'channels_click' in tid:
tid=tid.split('click_')[-1]
vhtml=self.session.post(f'{self.host}{tid}/videos/best/{int(pg)-1}').json()
for i in vhtml['videos']:
vdata.append({
'vod_id': i.get('u'),
'vod_name': i.get('tf'),
'vod_pic': self.proxy(i.get('il')),
'vod_year': i.get('n'),
'vod_remarks': i.get('d'),
'style': {'ratio': 1.33, 'type': 'rect'}
})
result['list'] = vdata
return result
def detailContent(self, ids):
url = f"{self.host}{ids[0]}"
data = self.getpq(ids[0])
vn=data('meta[property="og:title"]').attr('content')
dtext=data('.main-uploader a')
href=dtext.attr('href')
pdtitle=''
if href and href.count('/') < 2:
href=f'/channels{href}'
pdtitle = '[a=cr:' + json.dumps({'id': 'channels_click_'+href, 'name': dtext('.name').text()}) + '/]' + dtext('.name').text() + '[/a]'
vod = {
'vod_name': vn,
'vod_director':pdtitle,
'vod_remarks': data('.page-title').text().replace(vn,''),
'vod_play_from': 'Xvideos',
'vod_play_url': ''
}
js_content = data("#video-player-bg script")
jstr=''
for script in js_content.items():
content = script.text()
if 'setVideoUrlLow' in content and 'html5player' in content:
jstr = content
break
plist = [f"{vn}${self.e64(f'{1}@@@@{url}')}"]
def extract_video_urls(js_content):
try:
low = re.search(r'setVideoUrlLow\([\'"]([^\'"]+)[\'"]\)', js_content)
high = re.search(r'setVideoUrlHigh\([\'"]([^\'"]+)[\'"]\)', js_content)
hls = re.search(r'setVideoHLS\([\'"]([^\'"]+)[\'"]\)', js_content)
return {
'hls': hls.group(1) if hls else None,
'high': high.group(1) if high else None,
'low': low.group(1) if low else None
}
except Exception as e:
print(f"提取视频URL失败: {str(e)}")
return {}
if jstr:
try:
urls = extract_video_urls(jstr)
plist = [
f"{quality}${self.e64(f'{0}@@@@{url}')}"
for quality, url in urls.items()
if url
]
except Exception as e:
print(f"提取url失败: {str(e)}")
vod['vod_play_url'] = '#'.join(plist)
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
data=self.getpq(f'/?k={key}&p={int(pg)-1}')
return {'list':self.getlist(data(".mozaique .frame-block")),'page':pg}
def playerContent(self, flag, id, vipFlags):
ids=self.d64(id).split('@@@@')
if '.m3u8' in ids[1]: ids[1] = self.proxy(ids[1], 'm3u8')
return {'parse': int(ids[0]), 'url': ids[1], 'header': self.headers}
def localProxy(self, param):
url=self.d64(param['url'])
if param.get('type') == 'm3u8':
return self.m3Proxy(url)
else:
return self.tsProxy(url)
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64编码错误: {str(e)}")
return ""
def d64(self,encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64解码错误: {str(e)}")
return ""
def getlist(self, data):
vlist=[]
for i in data.items():
a=i('.thumb-inside .thumb a')
b=i('.thumb-under .title a')
vlist.append({
'vod_id': a.attr('href'),
'vod_name': b('a').attr('title'),
'vod_pic': self.proxy(a('img').attr('data-src')),
'vod_year': a('.video-hd-mark').text(),
'vod_remarks': b('.duration').text(),
'style': {'ratio': 1.33, 'type': 'rect'}
})
return vlist
def getpq(self, path=''):
response = self.session.get(f'{self.host}{path}').text
try:
return pq(response)
except Exception as e:
print(f"{str(e)}")
return pq(response.encode('utf-8'))
def m3Proxy(self, url):
ydata = requests.get(url, headers=self.headers, proxies=self.proxies, allow_redirects=False)
data = ydata.content.decode('utf-8')
if ydata.headers.get('Location'):
url = ydata.headers['Location']
data = requests.get(url, headers=self.headers, proxies=self.proxies).content.decode('utf-8')
lines = data.strip().split('\n')
last_r = url[:url.rfind('/')]
parsed_url = urlparse(url)
durl = parsed_url.scheme + "://" + parsed_url.netloc
for index, string in enumerate(lines):
if '#EXT' not in string:
if 'http' not in string:
domain=last_r if string.count('/') < 2 else durl
string = domain + ('' if string.startswith('/') else '/') + string
lines[index] = self.proxy(string, string.split('.')[-1].split('?')[0])
data = '\n'.join(lines)
return [200, "application/vnd.apple.mpegur", data]
def tsProxy(self, url):
data = requests.get(url, headers=self.headers, proxies=self.proxies, stream=True)
return [200, data.headers['Content-Type'], data.content]
def proxy(self, data, type='img'):
if data and len(self.proxies):return f"{self.getProxyUrl()}&url={self.e64(data)}&type={type}"
else:return data

260
PY1/Xvideos.py Normal file
View File

@@ -0,0 +1,260 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import re
import sys
from pyquery import PyQuery as pq
from base64 import b64decode, b64encode
from requests import Session
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.headers['referer']=f'{self.host}/'
self.session = Session()
self.session.headers.update(self.headers)
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host = "https://www.xvideos.com"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-full-version': '"133.0.6943.98"',
'sec-ch-ua-arch': '"x86"',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua-platform-version': '"19.0.0"',
'sec-ch-ua-model': '""',
'sec-ch-ua-full-version-list': '"Not(A:Brand";v="99.0.0.0", "Google Chrome";v="133.0.6943.98", "Chromium";v="133.0.6943.98"',
'dnt': '1',
'upgrade-insecure-requests': '1',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'priority': 'u=0, i'
}
def homeContent(self, filter):
result = {}
cateManual = {
"最新": "/new",
"最佳": "/best",
"频道": "/channels-index",
"标签": "/tags",
"明星": "/pornstars-index"
}
classes = []
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
result['class'] = classes
return result
def homeVideoContent(self):
data = self.getpq()
return {'list':self.getlist(data(".mozaique .frame-block"))}
def categoryContent(self, tid, pg, filter, extend):
vdata = []
result = {}
page = f"/{int(pg) - 1}" if pg != '1' else ''
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
if tid=='/new' or 'tags_click' in tid:
if 'tags_click' in tid:tid=tid.split('click_')[-1]
data=self.getpq(f'{tid}/{pg}')
vdata=self.getlist(data(".mozaique .frame-block"))
elif tid=='/best':
if pg=='1':
self.path=self.session.get(f'{self.host}{tid}',headers=self.headers,allow_redirects=False).headers['Location']
data=self.getpq(f'{self.path}{page}')
vdata=self.getlist(data(".mozaique .frame-block"))
elif tid=='/channels-index' or tid=='/pornstars-index':
data = self.getpq(f'{tid}{page}')
vhtml=data(".mozaique .thumb-block")
for i in vhtml.items():
a = i('.thumb-inside .thumb a')
match = re.search(r'src="([^"]+)"', a('script').text())
img=''
if match:
img = match.group(1).strip()
vdata.append({
'vod_id': f"channels_click_{'/channels'if tid=='/channels-index' else ''}"+a.attr('href'),
'vod_name': a('.profile-name').text() or i('.profile-name').text().replace('\xa0','/'),
'vod_pic': img,
'vod_tag': 'folder',
'vod_remarks': i('.thumb-under .profile-counts').text(),
'style': {'ratio': 1.33, 'type': 'rect'}
})
elif tid=='/tags':
result['pagecount'] = pg
vhtml = self.getpq(tid)
vhtml = vhtml('.tags-list')
for d in vhtml.items():
for i in d('li a').items():
vdata.append({
'vod_id': "tags_click_"+i.attr('href'),
'vod_name': i.attr('title') or i('b').text(),
'vod_pic': '',
'vod_tag': 'folder',
'vod_remarks': i('.navbadge').text(),
'style': {'ratio': 1.33, 'type': 'rect'}
})
elif 'channels_click' in tid:
tid=tid.split('click_')[-1]
headers=self.session.headers.copy()
headers.update({'Accept': 'application/json, text/javascript, */*; q=0.01'})
vhtml=self.post(f'{self.host}{tid}/videos/best/{int(pg)-1}',headers=headers).json()
for i in vhtml['videos']:
vdata.append({
'vod_id': i.get('u'),
'vod_name': i.get('tf'),
'vod_pic': i.get('il'),
'vod_year': i.get('n'),
'vod_remarks': i.get('d'),
'style': {'ratio': 1.33, 'type': 'rect'}
})
result['list'] = vdata
return result
def detailContent(self, ids):
url = f"{self.host}{ids[0]}"
data = self.getpq(ids[0])
vn=data('meta[property="og:title"]').attr('content')
dtext=data('.main-uploader a')
href=dtext.attr('href')
pdtitle=''
if href and href.count('/') < 2:
href=f'/channels{href}'
pdtitle = '[a=cr:' + json.dumps({'id': 'channels_click_'+href, 'name': dtext('.name').text()}) + '/]' + dtext('.name').text() + '[/a]'
vod = {
'vod_name': vn,
'vod_director':pdtitle,
'vod_remarks': data('.page-title').text().replace(vn,''),
'vod_play_from': '老僧酿酒',
'vod_play_url': ''
}
js_content = data("#video-player-bg script")
jstr=''
for script in js_content.items():
content = script.text()
if 'setVideoUrlLow' in content and 'html5player' in content:
jstr = content
break
plist = [f"{vn}${self.e64(f'{1}@@@@{url}')}"]
def extract_video_urls(js_content):
try:
low = re.search(r'setVideoUrlLow\([\'"]([^\'"]+)[\'"]\)', js_content)
high = re.search(r'setVideoUrlHigh\([\'"]([^\'"]+)[\'"]\)', js_content)
hls = re.search(r'setVideoHLS\([\'"]([^\'"]+)[\'"]\)', js_content)
return {
'hls': hls.group(1) if hls else None,
'high': high.group(1) if high else None,
'low': low.group(1) if low else None
}
except Exception as e:
print(f"提取视频URL失败: {str(e)}")
return {}
if jstr:
try:
urls = extract_video_urls(jstr)
plist = [
f"{quality}${self.e64(f'{0}@@@@{url}')}"
for quality, url in urls.items()
if url
]
except Exception as e:
print(f"提取url失败: {str(e)}")
vod['vod_play_url'] = '#'.join(plist)
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
data=self.getpq(f'/?k={key}&p={int(pg)-1}')
return {'list':self.getlist(data(".mozaique .frame-block")),'page':pg}
def playerContent(self, flag, id, vipFlags):
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5410.0 Safari/537.36',
'pragma': 'no-cache',
'cache-control': 'no-cache',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
'dnt': '1',
'sec-ch-ua-mobile': '?0',
'origin': self.host,
'sec-fetch-site': 'cross-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'referer': f'{self.host}/',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'priority': 'u=1, i',
}
ids=self.d64(id).split('@@@@')
return {'parse': int(ids[0]), 'url': ids[1], 'header': headers}
def localProxy(self, param):
pass
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64编码错误: {str(e)}")
return ""
def d64(self,encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64解码错误: {str(e)}")
return ""
def getlist(self, data):
vlist=[]
for i in data.items():
a=i('.thumb-inside .thumb a')
b=i('.thumb-under .title a')
vlist.append({
'vod_id': a.attr('href'),
'vod_name': b('a').attr('title'),
'vod_pic': a('img').attr('data-src'),
'vod_year': a('.video-hd-mark').text(),
'vod_remarks': b('.duration').text(),
'style': {'ratio': 1.33, 'type': 'rect'}
})
return vlist
def getpq(self, path=''):
response = self.session.get(f'{self.host}{path}').text
try:
return pq(response)
except Exception as e:
print(f"{str(e)}")
return pq(response.encode('utf-8'))

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

392
PY1/avjoy.py Normal file
View File

@@ -0,0 +1,392 @@
# -*- coding: utf-8 -*-
import json
import re
import requests
from pyquery import PyQuery as pq
import sys
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
host = 'https://cn.avjoy.me'
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/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',
'referer': 'https://cn.avjoy.me/',
'origin': 'https://cn.avjoy.me',
}
def init(self, extend=''):
self.proxies = json.loads(extend).get('proxy', {}) if extend else {}
self.session = requests.Session()
self.session.headers.update(self.headers)
def getName(self):
return "hohoj"
def fetch(self, url, params=None):
try:
resp = self.session.get(url, headers=self.session.headers, params=params,
proxies=self.proxies, timeout=10, allow_redirects=True)
return resp.text
except:
return ''
def fetch_resp(self, url, params=None, extra_headers=None, stream=False):
try:
hdrs = self.session.headers.copy()
if extra_headers:
hdrs.update(extra_headers)
return self.session.get(url, headers=hdrs, params=params,
proxies=self.proxies, timeout=10,
allow_redirects=True, stream=stream)
except Exception:
return None
def homeContent(self, filter):
html = self.fetch(self.host)
return {
'class': [
{'type_name': '最新上传视频', 'type_id': 'videos'},
{'type_name': '视频', 'type_id': 'videos'},
{'type_name': '类别', 'type_id': 'categories'},
{'type_name': '标签', 'type_id': 'tags'}
],
'filters': self.get_filters(),
'list': self.parse_videos_from_list_html(pq(html))
}
def get_filters(self):
return {}
def categoryContent(self, tid, pg, filter, extend):
norm = tid.lstrip('/') if not tid.startswith('http') else tid
if '?' in norm and not norm.startswith('http'):
norm = norm.split('?', 1)[0]
url = f"{self.host}/{norm}" if not norm.startswith('http') else norm
params = (extend or {}).copy()
try:
if int(pg) > 1:
params['page'] = pg
except:
pass
params.pop('o', None)
html = self.fetch(url, params)
doc = pq(html)
m_cur = re.search(r"current_url\s*=\s*\"([^\"]+)\"", html)
if m_cur:
base_path = m_cur.group(1)
if base_path.startswith('/videos/') or base_path.startswith('/search/videos/'):
url = f"{self.host}{base_path}"
html = self.fetch(url, params)
doc = pq(html)
def uniq_append(items, entry):
key = (entry.get('vod_id'), entry.get('vod_name'))
if key and key not in {(i.get('vod_id'), i.get('vod_name')) for i in items}:
items.append(entry)
if tid == 'categories':
items = []
for card in doc('div.content-left .row.content-row > div').items():
a = card.find('a').eq(0)
href = (a.attr('href') or '').strip()
name = card.find('.category-title .title-truncate').text().strip()
pic = (card.find('.thumb-overlay img').attr('src') or '').strip()
if href and name and href.startswith('/videos/'):
cat_id = href.lstrip('/')
if pic and pic.startswith('/'):
pic = f"{self.host}{pic}"
uniq_append(items, {
'vod_id': cat_id,
'vod_name': name,
'vod_pic': pic,
'vod_tag': 'folder',
'style': {"type": "rect", "ratio": 1.1}
})
for a in doc('.dropdown-menu.multi-column-dropdown a').items():
href = (a.attr('href') or '').strip()
name = a.text().strip()
if href.startswith('/videos/') and name:
uniq_append(items, {
'vod_id': href.lstrip('/'),
'vod_name': name,
'vod_pic': '',
'vod_tag': 'folder',
'style': {"type": "rect", "ratio": 1.1}
})
return {
'list': items,
'page': '1',
'pagecount': 1,
'limit': 90,
'total': len(items)
}
if tid == 'tags':
items = []
for a in doc('.popular-tag a').items():
name = a.text().strip()
href = (a.attr('href') or '').strip()
if href.startswith('/search/videos/') and name:
uniq_append(items, {
'vod_id': href.lstrip('/'),
'vod_name': name,
'vod_tag': 'folder',
'style': {"type": "rect", "ratio": 1.0}
})
for a in doc('.trending-searches a').items():
name = a.text().strip()
href = (a.attr('href') or '').strip()
if href.startswith('/search/videos/') and name:
uniq_append(items, {
'vod_id': href.lstrip('/'),
'vod_name': name,
'vod_tag': 'folder',
'style': {"type": "rect", "ratio": 1.0}
})
return {
'list': items,
'page': '1',
'pagecount': 1,
'limit': 90,
'total': len(items)
}
videos = self.parse_videos_from_list_html(doc)
if not videos:
fallback = []
for a in doc('a[href^="/video/"]').items():
href = a.attr('href')
title = a.text().strip()
img = a.parents().find('img').eq(0).attr('src')
if href and title:
uniq_append(fallback, {
'vod_id': href,
'vod_name': title,
'vod_pic': img,
'style': {"type": "rect", "ratio": 1.5}
})
videos = fallback
pagecount = 1
try:
pagecount = doc('.pagination a').length or 1
except:
pagecount = 1
return {
'list': videos,
'page': pg,
'pagecount': pagecount,
'limit': 90,
'total': 999999
}
def detailContent(self, ids):
vid = ids[0]
url = f"{self.host}{vid}" if vid.startswith('/') else f"{self.host}/{vid}"
html = self.fetch(url)
data = pq(html)
title = data('h1').text() or data('title').text() or ''
title = re.sub(r'\s*HoHoJ.*$', '', title)
title = re.sub(r'\s*\|.*$', '', title)
title = title.strip()
poster = data('video#video').attr('poster') or data('meta[property="og:image"]').attr('content')
vod_year = data('.info span').eq(-1).text()
m_vid = re.search(r"video_id\s*=\s*\"(\d+)\"", html)
video_id = m_vid.group(1) if m_vid else ''
if not video_id:
m_url_id = re.search(r"/video/(\d+)", url) or re.search(r"/video/(\d+)", html)
video_id = m_url_id.group(1) if m_url_id else ''
m_vkey = re.search(r"/embed/([a-zA-Z0-9]+)", html)
vkey = m_vkey.group(1) if m_vkey else ''
play_id = video_id or vkey
vod = {
'vod_id': vid,
'vod_name': title,
'vod_play_from': '撸出血',
'vod_play_url': f"{title}${play_id or ''}",
'vod_pic': poster,
'vod_year': vod_year,
}
tags = []
for tag in data('a.tag').items():
name = tag.text().strip()
href = tag.attr('href')
if name and href:
tags.append(f'[a=cr:{json.dumps({"id": href, "name": name})}/]{name}[/a]')
if tags:
vod['vod_content'] = ' '.join(tags)
director_name = data('a[href^="/user/"]').text().strip()
if director_name:
try:
from urllib.parse import quote
director_href = f"/search/videos/{quote(director_name)}"
except:
director_href = f"/search/videos/{director_name}"
director_link = f"[a=cr:{json.dumps({'id': director_href, 'name': director_name})}/]{director_name}[/a]"
vod['vod_content'] = (vod.get('vod_content', '') + ('\n' if vod.get('vod_content') else '') + '导演:' + director_link)
intro = (data('section.video-description').text() or '').strip()
if not intro:
intro = (data('meta[name="description"]').attr('content') or '').strip()
if intro:
vod['vod_content'] = (vod.get('vod_content', '') + ('\n' if vod.get('vod_content') else '') + '影片介绍:' + intro)
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
params = {}
try:
if int(pg) > 1:
params['page'] = pg
except:
pass
url = f"{self.host}/search/videos/{requests.utils.quote(key)}"
html = self.fetch(url, params)
if not html:
html = self.fetch(f"{self.host}/search", {'text': key, **params})
return {'list': self.parse_videos_from_list_html(pq(html)), 'page': pg}
def playerContent(self, flag, id, vipFlags):
def pick_best_source(html_text):
sources = []
for m in re.finditer(r"<source[^>]+src=\"([^\"]+)\"[^>]*>", html_text):
frag = html_text[m.start():m.end()]
src = m.group(1)
res_m = re.search(r"res=\'?(\d+)\'?", frag)
label_m = re.search(r"label=\'([^\']+)\'", frag)
res = int(res_m.group(1)) if res_m else 0
label = label_m.group(1) if label_m else ''
sources.append((res, label, src))
if sources:
sources.sort(reverse=True)
return sources[0][2]
mv = re.search(r"<video[^>]+src=\"([^\"]+)\"", html_text)
if mv:
return mv.group(1)
mv2 = re.search(r"var\s+videoSrc\s*=\s*[\"']([^\"']+)[\"']", html_text)
if mv2:
return mv2.group(1)
doc = pq(html_text)
return doc('video source').attr('src') or doc('video').attr('src') or ''
raw = str(id).strip()
if re.match(r'^https?://', raw) and self.isVideoFormat(raw):
return {
'parse': 0,
'url': raw,
'header': {
'user-agent': self.headers['user-agent'],
'referer': self.host,
'origin': self.host,
}
}
m = re.search(r"/video/(\d+)", raw) or re.search(r"id=(\d+)", raw)
if m:
raw = m.group(1)
is_numeric = re.match(r"^\d+$", raw) is not None
video_url = ''
referer_used = ''
if is_numeric:
for path in [f"{self.host}/video/{raw}", f"{self.host}/video/{raw}/"]:
self.session.headers['referer'] = path
play_html = self.fetch(path)
video_url = pick_best_source(play_html)
if video_url:
referer_used = path
break
m_dl = re.search(r"href=\"(/download\\.php\\?id=\\d+[^\"]*label=1080p)\"", play_html)
if not m_dl:
m_dl = re.search(r"href=\"(/download\\.php\\?id=\\d+[^\"]*)\"", play_html)
if m_dl:
dl_url = f"{self.host}{m_dl.group(1)}"
resp = self.fetch_resp(dl_url, extra_headers={'referer': path}, stream=True)
if resp and resp.ok:
resp.close()
video_url = resp.url
referer_used = path
break
if not video_url:
embed_url = f"{self.host}/embed/{raw}" if not is_numeric else f"{self.host}/embed?id={raw}"
self.session.headers['referer'] = embed_url
html = self.fetch(embed_url)
v2 = pick_best_source(html)
if v2:
video_url = v2
referer_used = embed_url
return {
'parse': 0,
'url': video_url or '',
'header': {
'user-agent': self.headers['user-agent'],
'referer': referer_used or self.host,
'origin': self.host,
}
}
def parse_videos_from_list_html(self, doc: pq):
videos = []
for item in doc('.row.content-row > div').items():
link = item.find('a').eq(0).attr('href')
img = item.find('.thumb-overlay img').eq(0).attr('src')
info = item.find('.content-info').eq(0)
title = info.find('.content-title').text().strip()
duration = (item.find('.video-duration, .thumb-overlay .duration, .content-duration, .duration').eq(0).text() or '').strip()
overlay_text = (item.find('.thumb-overlay').text() or '').strip()
hd_flag = bool(item.find('.hd, .icon-hd, .hd-icon, .badge-hd, .label-hd').length) or ('HD' in overlay_text)
if not link or not title:
continue
parts = []
if hd_flag:
parts.append('HD')
if duration:
parts.append(duration)
remarks = ''.join(parts)
videos.append({
'vod_id': link,
'vod_name': re.sub(r'\s*\|.*$', '', re.sub(r'\s*HoHoJ.*$', '', title)).strip(),
'vod_pic': img,
'vod_remarks': remarks or '',
'vod_tag': '',
'style': {"type": "rect", "ratio": 1.5}
})
if not videos:
for info in doc('.content-info').items():
a = info('a').eq(0)
link = a.attr('href')
title = info('.content-title').text().strip()
if not link or not title:
continue
img = info.prev('a').find('img').attr('src') or info.prevAll('a').eq(0).find('img').attr('src')
duration = (info.parents().find('.video-duration, .thumb-overlay .duration, .content-duration, .duration').eq(0).text() or '').strip()
overlay_text = (info.parents().find('.thumb-overlay').text() or '').strip()
hd_flag = bool(info.parents().find('.hd, .icon-hd, .hd-icon, .badge-hd, .label-hd').length) or ('HD' in overlay_text)
parts = []
if hd_flag:
parts.append('HD')
if duration:
parts.append(duration)
remarks = ''.join(parts)
videos.append({
'vod_id': link,
'vod_name': re.sub(r'\s*\|.*$', '', re.sub(r'\s*HoHoJ.*$', '', title)).strip(),
'vod_pic': img,
'vod_remarks': remarks or '',
'vod_tag': '',
'style': {"type": "rect", "ratio": 1.5}
})
return videos
def isVideoFormat(self, url):
return bool(url) and (url.lower().endswith('.mp4') or url.lower().endswith('.m3u8'))
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeVideoContent(self):
pass
def localProxy(self, param):
pass
def liveContent(self, url):
pass

179
PY1/badnews (1).py Normal file
View File

@@ -0,0 +1,179 @@
# -*- coding: utf-8 -*-
import re
from base.spider import Spider
class Spider(Spider):
def __init__(self):
self.name = 'Bad.news'
self.host = 'https://bad.news'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',
'Referer': self.host + '/',
'Origin': self.host,
'Accept-Language': 'zh-CN,zh;q=0.9'
}
def getName(self):
return self.name
def init(self, extend=""):
pass
# =========================
# 首页分类
# =========================
def homeContent(self, filter):
return {
'class': [
{'type_id': '', 'type_name': '新出品'},
{'type_id': '/dm', 'type_name': 'H动漫'},
{'type_id': '/av/release', 'type_name': '日本AV'},
{'type_id': '/tag/long-porn', 'type_name': '长视频'}
]
}
def homeVideoContent(self):
return self.categoryContent('', '1', False, {})
# =========================
# 列表解析
# =========================
def parse_list(self, html):
videos = []
# 定义黑名单关键词
black_list = ['热点', '招聘', '20k', '工作制', '双休', '远程', '月薪']
# 1. 解析瀑布流 (p1)
p1 = re.findall(
r'href="([^"]+)"[^>]*title="([^"]+)"[^>]*(?:data-echo-background|poster)="([^"]+)"',
html, re.S
)
for path, title, pic in p1:
# 过滤逻辑:检查标题是否包含黑名单中的任何词
if any(word in title for word in black_list):
continue
if path.startswith('/'):
videos.append({
'vod_id': path,
'vod_name': title.strip(),
'vod_pic': pic.split('?')[0],
'vod_remarks': ''
})
# 2. 解析 table 信息流 (p2)
p2 = re.findall(r'<table.*?>(.*?)</table>', html, re.S)
for block in p2:
# 先提取标题进行预校验
title_m = re.search(r'<h3.*?>(.*?)</h3>', block, re.S)
raw_title = re.sub('<[^>]+>', '', title_m.group(1)).strip() if title_m else ''
# 如果标题为空或者是黑名单广告,直接跳过
if not raw_title or any(word in raw_title for word in black_list):
continue
link = re.search(r'href="([^"]+)"', block)
if not link:
continue
path = link.group(1)
if not path.startswith('/') or any(v['vod_id'] == path for v in videos):
continue
pic_m = re.search(r'poster="([^"]+)"', block)
videos.append({
'vod_id': path,
'vod_name': raw_title,
'vod_pic': pic_m.group(1).split('?')[0] if pic_m else '',
'vod_remarks': ''
})
return videos
# =========================
# 分类
# =========================
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg)
url = f'{self.host}{tid}/page-{pg}' if tid else (self.host if pg == 1 else f'{self.host}/page-{pg}')
res = self.fetch(url, headers=self.headers)
return {'list': self.parse_list(res.text), 'page': pg, 'pagecount': 999}
# =========================
# 详情页HTML + DM 分流)
# =========================
def detailContent(self, ids):
path = ids[0]
url = self.host + path
html = self.fetch(url, headers=self.headers).text
title_m = re.search(r'<title>(.*?)</title>', html)
title = title_m.group(1).split('-')[0].strip() if title_m else 'Bad.news'
# ===== DMH动漫=========
if path.startswith('/dm'):
iframe = re.search(r'<iframe[^>]+src="([^"]+)"', html)
play_url = iframe.group(1) if iframe else url
if play_url.startswith('/'):
play_url = self.host + play_url
return {'list': [{
'vod_id': play_url,
'vod_name': title,
'vod_play_from': 'DM-Web',
'vod_play_url': f'播放${play_url}'
}]}
# ===== 普通 HTML 视频 =====
m = re.search(r'<video[^>]+data-source="([^"]+)"', html)
if m:
return {'list': [{
'vod_id': path,
'vod_name': title,
'vod_play_from': 'HTML',
'vod_play_url': f'播放${m.group(1)}'
}]}
return {'list': []}
# =========================
# 播放器
# =========================
def playerContent(self, flag, id, vipFlags):
headers = {
'User-Agent': self.headers['User-Agent'],
'Referer': self.host + '/',
'Origin': self.host,
'Range': 'bytes=0-'
}
# DM 用 WebView 嗅探
if flag == 'DM-Web':
return {
'parse': 1,
'sniff': 1,
'url': id,
'header': headers,
'sniff_include': ['.mp4', '.m3u8'],
'sniff_exclude': [
'.html', '.js', '.css',
'.jpg', '.png', '.gif',
'google', 'facebook',
'doubleclick', 'analytics',
'ads', 'tracker'
]
}
# HTML 直连
return {'parse': 0, 'url': id}
# =========================
# 搜索
# =========================
def searchContent(self, key, quick, pg="1"):
url = f'{self.host}/search/q-{key}'
res = self.fetch(url, headers=self.headers)
return {'list': self.parse_list(res.text)}

271
PY1/boyfriend.show.py Normal file
View File

@@ -0,0 +1,271 @@
# coding=utf-8
# !/usr/bin/python
import sys, re, os
import base64
import hashlib
import requests
from typing import Tuple
try:
from base.spider import Spider
except ModuleNotFoundError:
# Ensure project root (..\..\) is on sys.path when executed directly
CURRENT_FILE = os.path.abspath(__file__)
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(CURRENT_FILE)))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
from base.spider import Spider
from datetime import datetime, timedelta
from urllib.parse import quote, unquote
from urllib3.util.retry import Retry
sys.path.append('..')
class Spider(Spider):
def init(self, extend="{}"): # noqa: N802 (match framework signature)
origin = 'https://zh.boyfriend.show'
self.host = origin
self.headers = {
'Origin': origin,
'Referer': f"{origin}/",
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0'
}
self.stripchat_key = self.decode_key_compact()
# 缓存字典
self._hash_cache = {}
self.create_session_with_retry()
def getName(self):
return 'Boyfriend Show'
def isVideoFormat(self, url):
return bool(re.search(r'(m3u8|mp4)(?:\?|$)', url))
def manualVideoCheck(self):
return False
def destroy(self):
try:
self.session.close()
except Exception:
pass
def homeContent(self, filter):
CLASSES = [
{'type_name': '女主播g', 'type_id': 'girls'},
{'type_name': '情侣c', 'type_id': 'couples'},
{'type_name': '男主播m', 'type_id': 'men'},
{'type_name': '跨性别t', 'type_id': 'trans'}
]
VALUE = (
{'n': '中国', 'v': 'tagLanguageChinese'},
{'n': '亚洲', 'v': 'ethnicityAsian'},
{'n': '白人', 'v': 'ethnicityWhite'},
{'n': '拉丁', 'v': 'ethnicityLatino'},
{'n': '混血', 'v': 'ethnicityMultiracial'},
{'n': '印度', 'v': 'ethnicityIndian'},
{'n': '阿拉伯', 'v': 'ethnicityMiddleEastern'},
{'n': '黑人', 'v': 'ethnicityEbony'}
)
VALUE_MEN = (
{'n': '情侣', 'v': 'sexGayCouples'},
{'n': '直男', 'v': 'orientationStraight'}
)
TIDS = ('girls', 'couples', 'men', 'trans')
filters = {
tid: [{'key': 'tag', 'value': VALUE_MEN + VALUE if tid == 'men' else VALUE}]
for tid in TIDS
}
return {
'class': CLASSES,
'filters': filters
}
def homeVideoContent(self):
return []
def categoryContent(self, tid, pg, filter, extend):
limit = 60
offset = limit * (int(pg) - 1)
url = f"{self.host}/api/front/models?improveTs=false&removeShows=false&limit={limit}&offset={offset}&primaryTag={tid}&sortBy=stripRanking&rcmGrp=A&rbCnGr=true&prxCnGr=false&nic=false"
if 'tag' in extend:
url += "&filterGroupTags=%5B%5B%22" + extend['tag'] + "%22%5D%5D"
rsp = self.fetch(url).json()
videos = [
{
"vod_id": str(vod['username']).strip(),
"vod_name": f"{self.country_code_to_flag(str(vod['country']).strip())}{str(vod['username']).strip()}",
# 使用与站点一致的org域名
"vod_pic": f"https://img.doppiocdn.org/thumbs/{vod['snapshotTimestamp']}/{vod['id']}",
"vod_remarks": "" if vod.get('status') == "public" else "🎫"
}
for vod in rsp.get('models', [])
]
total = int(rsp.get('filteredCount', 0))
return {
"list": videos,
"page": pg,
"pagecount": (total + limit - 1) // limit,
"limit": limit,
"total": total
}
def detailContent(self, array):
username = array[0]
rsp = self.fetch(f"{self.host}/api/front/v2/models/username/{username}/cam").json()
info = rsp['cam']
user = rsp['user']['user']
id = str(user['id'])
country = str(user['country']).strip()
isLive = "" if user['isLive'] else " 已下播"
flag = self.country_code_to_flag(country)
remark, startAt = '', ''
if show := info.get('show'):
startAt = show.get('createdAt')
elif show := info.get('groupShowAnnouncement'):
startAt = show.get('startAt')
if startAt:
BJtime = (datetime.strptime(startAt, "%Y-%m-%dT%H:%M:%SZ") + timedelta(hours=8)).strftime("%m月%d%H:%M")
remark = f"🎫 始于 {BJtime}"
vod = {
"vod_id": id,
"vod_name": str(info['topic']).strip(),
"vod_pic": str(user['avatarUrl']),
"vod_director": f"{flag}{username}{isLive}",
"vod_remarks": remark,
'vod_play_from': 'Boyfriend Show',
'vod_play_url': f"{id}${id}"
}
return {'list': [vod]}
def process_key(self, key: str) -> Tuple[str, str]:
tags = {'G': 'girls', 'C': 'couples', 'M': 'men', 'T': 'trans'}
parts = key.split(maxsplit=1) # 仅分割第一个空格
if len(parts) > 1 and (tag := tags.get(parts[0].upper())):
return tag, parts[1].strip()
return 'men', key.strip()
def searchContent(self, key, quick, pg="1"):
result = {}
if int(pg) > 1:
return result
tag, key = self.process_key(key)
url = f"{self.host}/api/front/v4/models/search/group/username?query={key}&limit=900&primaryTag={tag}"
rsp = self.fetch(url).json()
result['list'] = [
{
"vod_id": str(user['username']).strip(),
"vod_name": f"{self.country_code_to_flag(str(user['country']).strip())}{user['username']}",
"vod_pic": f"https://img.doppiocdn.org/thumbs/{user['snapshotTimestamp']}/{user['id']}",
"vod_remarks": "" if user['status'] == "public" else "🎫"
}
for user in rsp.get('models', [])
if user['isLive']
]
return result
def playerContent(self, flag, id, vipFlags):
# 主索引m3u8
url = f"https://edge-hls.doppiocdn.net/hls/{id}/master/{id}_auto.m3u8?playlistType=lowLatency"
rsp = self.fetch(url)
lines = rsp.text.strip().split('\n')
psch, pkey = '', ''
urls = []
for i, line in enumerate(lines):
if line.startswith('#EXT-X-MOUFLON:'):
if parts := line.split(':'):
if len(parts) >= 4:
psch, pkey = parts[2], parts[3]
if '#EXT-X-STREAM-INF' in line:
name_start = line.find('NAME="') + 6
name_end = line.find('"', name_start)
qn = line[name_start:name_end]
# URL在下一行
url_base = lines[i + 1]
# 组合最终的URL并加上psch和pkey参数
full_url = f"{url_base}&psch={psch}&pkey={pkey}"
proxy_url = f"{self.getProxyUrl()}&url={quote(full_url)}"
# 将画质和URL添加到列表中
urls.extend([qn, proxy_url])
return {
"url": urls,
"parse": '0',
"contentType": '',
"header": self.headers
}
def localProxy(self, param):
url = unquote(param['url'])
data = self.fetch(url)
if data.status_code == 403:
data = self.fetch(re.sub(r'\d+p\d*\.m3u8', '160p_blurred.m3u8', url))
if data.status_code != 200:
return [404, "text/plain", ""]
data = data.text
if "#EXT-X-MOUFLON:FILE" in data:
data = self.process_m3u8_content_v2(data)
return [200, "application/vnd.apple.mpegur", data]
def process_m3u8_content_v2(self, m3u8_content):
lines = m3u8_content.strip().split('\n')
for i, line in enumerate(lines):
if (line.startswith('#EXT-X-MOUFLON:FILE:') and 'media.mp4' in lines[i + 1]):
encrypted_data = line.split(':', 2)[2].strip()
try:
decrypted_data = self.decrypt(encrypted_data, self.stripchat_key)
except Exception:
decrypted_data = self.decrypt(encrypted_data, "Zokee2OhPh9kugh4")
lines[i + 1] = lines[i + 1].replace('media.mp4', decrypted_data)
return '\n'.join(lines)
def country_code_to_flag(self, country_code):
if len(country_code) != 2 or not country_code.isalpha():
return country_code
flag_emoji = ''.join([chr(ord(c.upper()) - ord('A') + 0x1F1E6) for c in country_code])
return flag_emoji
def decode_key_compact(self):
base64_str = "NTEgNzUgNjUgNjEgNmUgMzQgNjMgNjEgNjkgMzkgNjIgNmYgNGEgNjEgMzUgNjE="
decoded = base64.b64decode(base64_str).decode('utf-8')
key_bytes = bytes(int(hex_str, 16) for hex_str in decoded.split(" "))
return key_bytes.decode('utf-8')
def compute_hash(self, key: str) -> bytes:
"""计算并缓存SHA-256哈希"""
if key not in self._hash_cache:
sha256 = hashlib.sha256()
sha256.update(key.encode('utf-8'))
self._hash_cache[key] = sha256.digest()
return self._hash_cache[key]
def decrypt(self, encrypted_b64: str, key: str) -> str:
# 修复Base64填充
padding = len(encrypted_b64) % 4
if padding:
encrypted_b64 += '=' * (4 - padding)
# 计算哈希并解密
hash_bytes = self.compute_hash(key)
encrypted_data = base64.b64decode(encrypted_b64)
# 异或解密
decrypted_bytes = bytearray()
for i, cipher_byte in enumerate(encrypted_data):
key_byte = hash_bytes[i % len(hash_bytes)]
decrypted_bytes.append(cipher_byte ^ key_byte)
return decrypted_bytes.decode('utf-8')
def create_session_with_retry(self):
self.session = requests.Session()
retry_strategy = Retry(
total=3,
backoff_factor=0.3,
status_forcelist=[429, 500, 502, 503, 504]
)
adapter = requests.adapters.HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
def fetch(self, url):
return self.session.get(url, headers=self.headers, timeout=10)

118
PY1/cam4.py Normal file
View File

@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
import json
import time
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "Cam4直播"
def init(self, extend=""):
self.base = "https://zh.cam4.com"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
return self
def homeContent(self, filter):
classes = [
{"type_id": "female", "type_name": "女性"},
{"type_id": "couples", "type_name": "情侣"},
]
return {"class": classes}
def categoryContent(self, tid, pg, filter, extend):
if not pg:
pg = 1
params = f"?directoryJson=true&online=true&url=true&page={pg}"
if tid == "female":
params += "&gender=female"
elif tid == "male":
params += "&gender=male"
elif tid == "couples":
params += "&broadcastType=male_female_group"
elif tid == "shemale":
params += "&gender=shemale"
url = f"{self.base}/directoryCams{params}"
rsp = self.fetch(url, headers=self.headers)
data = rsp.text
try:
jRoot = json.loads(data)
except:
return {"list": []}
videos = []
for u in jRoot.get("users", []):
title = f"{u.get('username')} ({u.get('countryCode', '')})"
if "age" in u:
title += f" - {u['age']}"
if "resolution" in u:
res = u["resolution"].split(":")[-1]
title += f" [HD:{res}]"
video = {
"vod_id": u.get("hlsPreviewUrl"),
"vod_name": title,
"vod_pic": u.get("snapshotImageLink", ""),
"vod_remarks": u.get("statusMessage", ""),
}
videos.append(video)
result = {
"list": videos,
"page": int(pg),
"pagecount": 9999,
"limit": 90,
"total": len(videos)
}
return result
def detailContent(self, ids):
id = ids[0]
vod = {
"vod_id": id,
"vod_name": "Cam4直播",
"vod_pic": "",
"vod_play_from": "Cam4",
"vod_play_url": f"直播源${id}",
}
return {"list": [vod]}
def playerContent(self, flag, id, vipFlags):
play_url = id
return {
"parse": 0,
"playUrl": "",
"url": play_url,
"header": self.headers
}
def searchContent(self, key, quick, pg="1"):
url = f"{self.base}/directoryCams?directoryJson=true&online=true&url=true&showTag={key}&page={pg}"
rsp = self.fetch(url, headers=self.headers)
data = rsp.text
try:
jRoot = json.loads(data)
except:
return {"list": []}
videos = []
for u in jRoot.get("users", []):
title = f"{u.get('username')} ({u.get('countryCode', '')})"
video = {
"vod_id": u.get("hlsPreviewUrl"),
"vod_name": title,
"vod_pic": u.get("snapshotImageLink", ""),
"vod_remarks": u.get("statusMessage", ""),
}
videos.append(video)
return {"list": videos}
def isVideoFormat(self, url):
return ".m3u8" in url
def manualVideoCheck(self):
return True

255
PY1/epo.py Normal file
View File

@@ -0,0 +1,255 @@
#author Kyle
import sys
import time
import json
import re
import urllib.parse
from base64 import b64decode, b64encode
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = "https://www.eporner.com"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Referer': self.host + '/',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
}
self.timeout = 10
self.retries = 2
self.cookies = {"EPRNS": "1"}
def getName(self):
return "EP涩"
def isVideoFormat(self, url):
return True
def manualVideoCheck(self):
return False
def destroy(self):
pass
def homeContent(self, filter):
result = {}
result['class'] = [
{"type_id": "/cat/all/", "type_name": "最新"},
{"type_id": "/best-videos/", "type_name": "最佳视频"},
{"type_id": "/top-rated/", "type_name": "最高评分"},
{"type_id": "/cat/4k-porn/", "type_name": "4K"},
]
return result
def homeVideoContent(self):
result = {}
videos = self._api_search(query="all", page=1, order="latest", gay="0", per_page=20)
result['list'] = videos
return result
def categoryContent(self, tid, pg, filter, extend):
result = {}
params = self._map_tid_to_api(tid)
videos = self._api_search(page=int(pg), **params)
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = len(videos) or 20
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
result = {}
videos = self._api_search(query=key or "all", page=int(pg), order="latest", gay="0")
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = len(videos) or 20
result['total'] = 999999
return result
def detailContent(self, ids):
result = {}
url = ids[0]
if not url.startswith('http'):
url = self.host + url
r = self.fetch(url, headers=self.headers)
root = self.html(r.text)
og_title = root.xpath('//meta[@property="og:title"]/@content')
if og_title:
title = og_title[0].replace(' - EPORNER', '').strip()
else:
h1 = "".join(root.xpath('//h1//text()'))
title = re.sub(r"\s*(\d+\s*min.*)$", "", h1).strip() or "爱看AV"
img_elem = root.xpath('//meta[@property="og:image"]/@content') or root.xpath('//img[@id="mainvideoimg"]/@src')
thumbnail = img_elem[0] if img_elem else ""
meta_desc = (root.xpath('//meta[@name="description"]/@content') or root.xpath('//meta[@property="og:description"]/@content'))
desc_text = meta_desc[0] if meta_desc else ""
dur_match = re.search(r"Duration:\s*([0-9:]+)", desc_text)
duration = dur_match.group(1) if dur_match else ""
encoded_url = self.e64(url)
play_url = f"播放${encoded_url}"
vod = {
"vod_id": url,
"vod_name": title,
"vod_pic": thumbnail,
"vod_remarks": duration,
"vod_content": desc_text.strip(),
"vod_play_from": "🍑Play",
"vod_play_url": play_url
}
result['list'] = [vod]
return result
def playerContent(self, flag, id, vipFlags):
result = {}
url = self.d64(id)
if not url.startswith('http'):
url = self.host + url
r = self.fetch(url, headers=self.headers)
html_content = r.text
pattern = r"vid\s*=\s*'([^']+)';\s*[\w*\.]+hash\s*=\s*['\"]([\da-f]{32})"
match = re.search(pattern, html_content)
if match:
vid, hash_val = match.groups()
hash_code = ''.join((self.encode_base_n(int(hash_val[i:i + 8], 16), 36) for i in range(0, 32, 8)))
xhr_url = f"{self.host}/xhr/video/{vid}?hash={hash_code}&device=generic&domain=www.eporner.com&fallback=false&embed=false&supportedFormats=mp4"
xhr_headers = {
**self.headers,
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json, text/javascript, */*; q=0.01'
}
resp = self.fetch(xhr_url, headers=xhr_headers)
data = json.loads(resp.text)
if data.get("available", True):
sources_block = data.get("sources", {})
sources = sources_block.get("mp4", {})
final_url = None
if sources:
quality_sorted = sorted(sources.keys(), key=lambda q: int(re.sub(r"\D", "", q) or 0), reverse=True)
best_quality = quality_sorted[0]
final_url = sources[best_quality].get("src")
else:
hls = sources_block.get("hls")
if isinstance(hls, dict):
final_url = hls.get("src")
elif isinstance(hls, list) and hls:
final_url = hls[0].get("src")
best_quality = "hls"
if final_url:
result["parse"] = 0
result["url"] = final_url
result["header"] = {
'User-Agent': self.headers['User-Agent'],
'Referer': url,
'Origin': self.host,
'Accept': '*/*'
}
return result
def encode_base_n(self, num, n, table=None):
FULL_TABLE = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
if not table:
table = FULL_TABLE[:n]
if n > len(table):
raise ValueError('base %d exceeds table length %d' % (n, len(table)))
if num == 0:
return table[0]
ret = ''
while num:
ret = table[num % n] + ret
num = num // n
return ret
def e64(self, text):
return b64encode(text.encode()).decode()
def d64(self, encoded_text):
return b64decode(encoded_text.encode()).decode()
def html(self, content):
from lxml import etree
return etree.HTML(content)
def fetch(self, url, headers=None, timeout=None):
import requests, ssl
ssl._create_default_https_context = ssl._create_unverified_context
if headers is None:
headers = self.headers
if timeout is None:
timeout = self.timeout
for _ in range(self.retries + 1):
resp = requests.get(url, headers=headers, timeout=timeout, verify=False, cookies=getattr(self, 'cookies', None))
resp.encoding = 'utf-8'
return resp
time.sleep(1)
raise Exception("Fetch failed")
def _parse_video_list(self, root):
videos = []
items = root.xpath('//div[contains(@class, "mb") and @data-id]')
for item in items:
link = item.xpath('.//a[contains(@href, "/video-")]/@href')
if not link:
continue
vod_id = self.host + link[0]
title = "".join(item.xpath('.//p[contains(@class, "mbtit")]//text()')).strip()
img = item.xpath('.//img/@src')
thumbnail = img[0] if img else ""
duration = "".join(item.xpath('.//span[contains(@class,"mbtim")]/text()')).strip()
videos.append({
"vod_id": vod_id,
"vod_name": title or "爱看AV",
"vod_pic": thumbnail,
"vod_remarks": duration
})
return videos
def _map_tid_to_api(self, tid: str):
params = {"query": "all", "order": "latest", "gay": "0", "per_page": 30}
t = (tid or '').strip('/').lower()
if t.startswith('best-videos'):
params["order"] = "most-popular"
elif t.startswith('top-rated'):
params["order"] = "top-rated"
elif t.startswith('cat/4k-porn'):
params["query"] = "4k"
elif t.startswith('cat/gay'):
params["gay"] = "2"
params["order"] = "latest"
else:
params["gay"] = "0"
return params
def _api_search(self, query="all", page=1, order="latest", gay="0", per_page=30, thumbsize="medium"):
base = f"{self.host}/api/v2/video/search/"
q = {
"query": query,
"per_page": per_page,
"page": page,
"thumbsize": thumbsize,
"order": order,
"format": "json"
}
if gay is not None:
q["gay"] = gay
url = base + "?" + urllib.parse.urlencode(q)
r = self.fetch(url, headers={**self.headers, 'X-Requested-With': 'XMLHttpRequest'})
data = json.loads(r.text)
return self._parse_api_list(data)
def _parse_api_list(self, data: dict):
videos = []
for v in (data or {}).get('videos', []):
vurl = v.get('url') or ''
title = v.get('title') or ''
thumb = (v.get('default_thumb') or {}).get('src') or ''
remarks = v.get('length_min') or ''
videos.append({
"vod_id": vurl if vurl.startswith('http') else (self.host + vurl),
"vod_name": title or "爱看AV",
"vod_pic": thumb,
"vod_remarks": remarks
})
return videos

379
PY1/fullhd.py Normal file
View File

@@ -0,0 +1,379 @@
import requests
from bs4 import BeautifulSoup
import re
from base.spider import Spider
import sys
import json
import base64
import urllib.parse
from Crypto.Cipher import ARC4
from Crypto.Util.Padding import unpad
import binascii
sys.path.append('..')
xurl = "https://www.fullhd.xxx/zh/"
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'
}
pm = ''
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 extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
if pl == 3:
plx = []
while True:
start_index = text.find(start_str)
if start_index == -1:
break
end_index = text.find(end_str, start_index + len(start_str))
if end_index == -1:
break
middle_text = text[start_index + len(start_str):end_index]
plx.append(middle_text)
text = text.replace(start_str + middle_text + end_str, '')
if len(plx) > 0:
purl = ''
for i in range(len(plx)):
matches = re.findall(start_index1, plx[i])
output = ""
for match in matches:
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
if match3:
number = match3.group(1)
else:
number = 0
if 'http' not in match[0]:
output += f"#{'📽️' + match[1]}${number}{xurl}{match[0]}"
else:
output += f"#{'📽️' + match[1]}${number}{match[0]}"
output = output[1:]
purl = purl + output + "$$$"
purl = purl[:-3]
return purl
else:
return ""
else:
start_index = text.find(start_str)
if start_index == -1:
return ""
end_index = text.find(end_str, start_index + len(start_str))
if end_index == -1:
return ""
if pl == 0:
middle_text = text[start_index + len(start_str):end_index]
return middle_text.replace("\\", "")
if pl == 1:
middle_text = text[start_index + len(start_str):end_index]
matches = re.findall(start_index1, middle_text)
if matches:
jg = ' '.join(matches)
return jg
if pl == 2:
middle_text = text[start_index + len(start_str):end_index]
matches = re.findall(start_index1, middle_text)
if matches:
new_list = [f'{item}' for item in matches]
jg = '$$$'.join(new_list)
return jg
def homeContent(self, filter):
result = {}
result = {"class": [
{"type_id": "latest-updates", "type_name": "最新视频🌠"},
{"type_id": "top-rated", "type_name": "最佳视频🌠"},
{"type_id": "most-popular", "type_name": "热门影片🌠"},
{"type_id": "networks/brazzers-com", "type_name": "Brazzers🌠"},
{"type_id": "networks/tushy-com", "type_name": "Tushy🌠"},
{"type_id": "networks/naughtyamerica-com", "type_name": "Naughtyamerica🌠"},
{"type_id": "sites/sexmex", "type_name": "Sexmex🌠"},
{"type_id": "sites/passion-hd", "type_name": "Passion-HD🌠"},
{"type_id": "categories/animation", "type_name": "Animation🌠"},
{"type_id": "categories/18-years-old", "type_name": "Teen🌠"},
{"type_id": "categories/pawg", "type_name": "Pawg🌠"},
{"type_id": "categories/thong", "type_name": "Thong🌠"},
{"type_id": "categories/stockings", "type_name": "Stockings🌠"},
{"type_id": "categories/jav-uncensored", "type_name": "JAV🌠"},
{"type_id": "categories/pantyhose", "type_name": "Pantyhose🌠"}
],
}
return result
def homeVideoContent(self):
videos = []
try:
detail = requests.get(url=xurl, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
# Get videos from different sections
sections = {
"latest-updates": "最新视频",
"top-rated": "最佳视频",
"most-popular": "热门影片"
}
for section_id, section_name in sections.items():
section = doc.find('div', id=f"list_videos_videos_watched_right_now_items")
if not section:
continue
vods = section.find_all('div', class_="item")
for vod in vods:
names = vod.find_all('a')
name = names[0]['title'] if names and 'title' in names[0].attrs else section_name
ids = vod.find_all('a')
id = ids[0]['href'] if ids else ""
# 获取图片 - 适配两种不同的img标签结构
pic = ""
# 第一种方式查找带有data-src属性的img标签
pics = vod.find('img', class_="lazyload")
if pics and pics.get('data-src'):
pic = pics['data-src']
# 第二种方式查找带有src属性的img标签
if not pic:
pics = vod.find('img', class_="thumb_img")
if pics and pics.get('src'):
pic = pics['src']
# 如果找到了图片但URL不完整添加基础URL
if pic and 'http' not in pic:
pic = xurl + pic
remarks = vod.find('span', class_="duration")
remark = remarks.text.strip() if remarks else ""
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
result = {'list': videos}
return result
except Exception as e:
print(f"Error in homeVideoContent: {str(e)}")
return {'list': []}
def categoryContent(self, cid, pg, filter, ext):
result = {}
videos = []
try:
if pg and int(pg) > 1:
url = f'{xurl}/{cid}/{pg}/'
else:
url = f'{xurl}/{cid}/'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
section = doc.find('div', class_="list-videos")
if section:
vods = section.find_all('div', class_="item")
for vod in vods:
names = vod.find_all('a')
name = names[0]['title'] if names and 'title' in names[0].attrs else ""
ids = vod.find_all('a')
id = ids[0]['href'] if ids else ""
# 获取图片 - 适配两种不同的img标签结构
pic = ""
# 第一种方式查找带有data-src属性的img标签
pics = vod.find('img', class_="lazyload")
if pics and pics.get('data-src'):
pic = pics['data-src']
# 第二种方式查找带有src属性的img标签
if not pic:
pics = vod.find('img', class_="thumb_img")
if pics and pics.get('src'):
pic = pics['src']
# 如果找到了图片但URL不完整添加基础URL
if pic and 'http' not in pic:
pic = xurl + pic
remarks = vod.find('span', class_="duration")
remark = remarks.text.strip() if remarks else ""
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
except Exception as e:
print(f"Error in categoryContent: {str(e)}")
result = {
'list': videos,
'page': pg,
'pagecount': 9999,
'limit': 90,
'total': 999999
}
return result
def detailContent(self, ids):
global pm
did = ids[0]
result = {}
videos = []
playurl = ''
if 'http' not in did:
did = xurl + did
res1 = requests.get(url=did, headers=headerx)
res1.encoding = "utf-8"
res = res1.text
content = '👉' + self.extract_middle_text(res,'<h1>','</h1>', 0)
yanuan = self.extract_middle_text(res, '<span>Pornstars:</span>','</div>',1, 'href=".*?">(.*?)</a>')
bofang = did
videos.append({
"vod_id": did,
"vod_actor": yanuan,
"vod_director": '',
"vod_content": content,
"vod_play_from": '老僧酿酒',
"vod_play_url": bofang
})
result['list'] = videos
return result
def playerContent(self, flag, id, vipFlags):
parts = id.split("http")
xiutan = 0
if xiutan == 0:
if len(parts) > 1:
before_https, after_https = parts[0], 'http' + parts[1]
res = requests.get(url=after_https, headers=headerx)
res = res.text
url2 = self.extract_middle_text(res, '<video', '</video>', 0).replace('\\', '')
soup = BeautifulSoup(url2, 'html.parser')
first_source = soup.find('source')
src_value = first_source.get('src')
response = requests.head(src_value, allow_redirects=False)
if response.status_code == 302:
redirect_url = response.headers['Location']
response = requests.head(redirect_url, allow_redirects=False)
if response.status_code == 302:
redirect_url = response.headers['Location']
result = {}
result["parse"] = xiutan
result["playUrl"] = ''
result["url"] = redirect_url
result["header"] = headerx
return result
def searchContentPage(self, key, quick, page):
result = {}
videos = []
if not page:
page = '1'
if page == '1':
url = f'{xurl}/search/{key}/'
else:
url = f'{xurl}/search/{key}/{str(page)}/'
try:
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
section = doc.find('div', class_="list-videos")
if section:
vods = section.find_all('div', class_="item")
for vod in vods:
names = vod.find_all('a')
name = names[0]['title'] if names and 'title' in names[0].attrs else ""
ids = vod.find_all('a')
id = ids[0]['href'] if ids else ""
# 获取图片 - 适配两种不同的img标签结构
pic = ""
# 第一种方式查找带有data-src属性的img标签
pics = vod.find('img', class_="lazyload")
if pics and pics.get('data-src'):
pic = pics['data-src']
# 第二种方式查找带有src属性的img标签
if not pic:
pics = vod.find('img', class_="thumb_img")
if pics and pics.get('src'):
pic = pics['src']
# 如果找到了图片但URL不完整添加基础URL
if pic and 'http' not in pic:
pic = xurl + pic
remarks = vod.find('span', class_="duration")
remark = remarks.text.strip() if remarks else ""
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
except Exception as e:
print(f"Error in searchContentPage: {str(e)}")
result = {
'list': videos,
'page': page,
'pagecount': 9999,
'limit': 90,
'total': 999999
}
return result
def searchContent(self, key, quick):
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

234
PY1/gay.py Normal file
View File

@@ -0,0 +1,234 @@
# -*- coding: utf-8 -*-
# @Author : Doubebly
# @Time : 2025/9/9 20:36
import sys
import requests
from lxml import etree
from urllib.parse import quote, unquote
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def getName(self):
return self.vod.name
def init(self, extend):
self.vod = Vod(extend, self.getProxyUrl())
def getDependence(self):
return []
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def liveContent(self, url):
return self.vod.liveContent(url)
def homeContent(self, filter):
return self.vod.homeContent(filter)
def homeVideoContent(self):
return self.vod.homeVideoContent()
def categoryContent(self, cid, page, filter, ext):
return self.vod.categoryContent(cid, page, filter, ext)
def detailContent(self, did):
return self.vod.detailContent(did)
def searchContent(self, key, quick, page='1'):
return self.vod.searchContent(key, quick, page)
def playerContent(self, flag, pid, vipFlags):
return self.vod.playerContent(flag, pid, vipFlags)
def localProxy(self, params):
if params['type'] == 'img':
try:
content = self.vod.decrypt_img(unquote(params['url']))
return [200, "application/octet-stream", content]
except Exception as e:
print(f"Image decryption failed: {e}")
return [500, "text/plain", b"Image Error"]
return [404, "text/plain", b""]
pass
def destroy(self):
return '正在Destroy'
class Vod:
def __init__(self, extend='{}', proxy_url=""):
self.debug = False
self.getProxyUrl = proxy_url
self.log(f'{proxy_url}')
self.name = 'GAY'
self.error_play_url = 'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-720p.mp4'
self.home_url = 'https://91nt.com'
self.headers = {
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36",
'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
'accept-language': "zh-CN,zh;q=0.9",
'cache-control': "no-cache",
'pragma': "no-cache",
'priority': "u=0, i",
'referer': "https://91nt.com/",
'sec-ch-ua': "\"Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Google Chrome\";v=\"140\"",
'sec-ch-ua-mobile': "?0",
'sec-ch-ua-platform': "\"Windows\"",
'sec-fetch-dest': "document",
'sec-fetch-mode': "navigate",
'sec-fetch-site': "same-origin",
'sec-fetch-user': "?1",
'upgrade-insecure-requests': "1",
}
def homeContent(self, filter):
self.log('执行了, homeContent')
return {'class': [{'type_id': 'play', 'type_name': '精选G片'},
{'type_id': 'xrbj', 'type_name': '鲜肉薄肌'},
{'type_id': 'wtns', 'type_name': '无套内射'},
{'type_id': 'zfyh', 'type_name': '制服诱惑'},
{'type_id': 'dmfj', 'type_name': '耽美天菜'},
{'type_id': 'jrmn', 'type_name': '肌肉猛男'},
{'type_id': 'rhgv', 'type_name': '日韩GV'},
{'type_id': 'omjd', 'type_name': '欧美巨屌'},
{'type_id': 'drqp', 'type_name': '多人群交'},
{'type_id': 'kjys', 'type_name': '口交颜射'},
{'type_id': 'tjsm', 'type_name': '调教SM'}],
'filters': {'play': [{'key': 'tag',
'name': '分类',
'value': [{'n': '当前最热', 'v': 'popular'},
{'n': '最近更新', 'v': 'new'},
{'n': '正在播放', 'v': 'watchings'},
{'n': '小蓝原创', 'v': 'xiaolan'},
{'n': '本月最热', 'v': 'mon'},
{'n': '10分钟以上', 'v': '10min'},
{'n': '20分钟以上', 'v': '20min'},
{'n': '本月收藏', 'v': 'collect'},
{'n': '高清', 'v': 'hd'},
{'n': '每月最热', 'v': 'every'},
{'n': '本月讨论', 'v': 'current'},
{'n': '收藏最多', 'v': 'most'}]}]}}
def homeVideoContent(self):
self.log('执行了, homeVideoContent')
data_list = []
url = f'{self.home_url}/videos/all/new'
res = requests.get(url, headers=self.headers)
root = etree.HTML(res.text.encode('utf-8'))
data = root.xpath('//li/div[@class="video-item"]')
for i in data:
data_list.append(
{
'vod_id': i.xpath('./div[1]/a/@href')[0].strip(),
'vod_name': i.xpath('./div[1]/a/text()')[0].strip(),
'vod_pic': f"{self.getProxyUrl}&type=img&url={quote(i.xpath('.//img/@data-src')[0])}",
'vod_remarks': i.xpath('.//div[contains(@class, "text-sm")]/text()')[0]
}
)
return {'list': data_list, 'parse': 0, 'jx': 0}
def categoryContent(self, cid, page, filter, ext):
self.log('执行了, categoryContent')
data_list = []
if cid == 'play':
url = f'{self.home_url}/videos/all/watchings/{page}'
else:
url = f'{self.home_url}/videos/category/{cid}/{page}'
res = requests.get(url, headers=self.headers)
root = etree.HTML(res.text.encode('utf-8'))
data = root.xpath('//li/div[@class="video-item"]')
for i in data:
data_list.append(
{
'vod_id': i.xpath('./div[1]/a/@href')[0].strip(),
'vod_name': i.xpath('./div[1]/a/text()')[0].strip(),
'vod_pic': f"{self.getProxyUrl}&type=img&url={quote(i.xpath('.//img/@data-src')[0])}",
'vod_remarks': i.xpath('.//div[contains(@class, "text-sm")]/text()')[0]
}
)
return {'list': data_list, 'parse': 0, 'jx': 0}
def detailContent(self, did):
ids = did[0]
url = f'{self.home_url}{ids}'
res = requests.get(url, headers=self.headers)
root = etree.HTML(res.text.encode('utf-8'))
play_url = root.xpath('//div[@class="player-container"]/div/@data-url')[0]
play_name = root.xpath('//div[@class="player-container"]/div/@data-video')[0]
video_list = []
video_list.append(
{
'type_name': '',
'vod_id': ids,
'vod_name': '',
'vod_remarks': '',
'vod_year': '',
'vod_area': '',
'vod_actor': '',
'vod_director': '',
'vod_content': '',
'vod_play_from': self.name,
'vod_play_url': f'{play_name}${play_url}'
}
)
return {"list": video_list, 'parse': 0, 'jx': 0}
def searchContent(self, key, quick, page='1'):
self.log('执行了, searchContent')
data_list = []
url = f'{self.home_url}/videos/search/{key}/{page}'
res = requests.get(url, headers=self.headers)
root = etree.HTML(res.text.encode('utf-8'))
data = root.xpath('//li/div[@class="video-item"]')
for i in data:
# vod_id = i.xpath('./div[1]/a/@href')[0].strip()
# vod_name = i.xpath('./div[1]/a/text()')[0].strip()
# vod_pic = i.xpath('.//img/@data-src')[0]
# vod_remarks = i.xpath('.//div[contains(@class, "text-sm")]/text()')[0]
# self.log(f"{vod_id}, {vod_name}, {vod_pic}, {vod_remarks}")
data_list.append(
{
'vod_id': i.xpath('./div[1]/a/@href')[0].strip(),
'vod_name': i.xpath('./div[1]/a/text()')[0].strip().decode('utf-8'),
'vod_pic': f"{self.getProxyUrl}&type=img&url={quote(i.xpath('.//img/@data-src')[0])}",
'vod_remarks': i.xpath('.//div[contains(@class, "text-sm")]/text()')[0]
}
)
return {'list': data_list, 'parse': 0, 'jx': 0}
def playerContent(self, flag, pid, vipFlags):
return {'url': pid, 'parse': 0, 'jx': 0}
def liveContent(self, url):
pass
def decrypt_img(self, url):
self.log('执行了, decrypt_img')
key_str = 'f5d965df75336270'
iv_str = '97b60394abc2fbe1'
key = key_str.encode('utf-8')
iv = iv_str.encode('utf-8')
encrypted_bytes = requests.get(url, timeout=5).content
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_bytes = cipher.decrypt(encrypted_bytes)
final_data = unpad(decrypted_bytes, AES.block_size)
return final_data
def log(self, msg):
if self.debug:
try:
requests.post('http://192.168.31.12:5000/log', data=msg, timeout=1)
except Exception as e:
print(e)
if __name__ == '__main__':
pass

197
PY1/kemon.py Normal file
View File

@@ -0,0 +1,197 @@
import sys, re, threading, json, requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
from base.spider import Spider
class Spider(Spider):
creators_cache = []; post_image_cache = {}
img_pattern = re.compile(r'src=["\']([^"\']+)["\']')
img_exts = {'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'avif'}
vid_exts = {'mp4', 'mkv', 'mov', 'webm', 'm4v', 'avi', 'flv'}
def getName(self): return "Kemono"
def init(self, extend=""):
super().init(extend)
self.base_url = "https://kemono.cr"; self.api_url = "https://kemono.cr/api/v1"; self.img_base = "https://kemono.cr/data"
self.session = requests.Session()
retries = Retry(total=3, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
adapter = HTTPAdapter(pool_connections=20, pool_maxsize=20, max_retries=retries)
self.session.mount('https://', adapter); self.session.mount('http://', adapter)
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', 'Referer': f"{self.base_url}/", 'Accept': 'text/css,*/*;q=0.1', 'Accept-Language': 'en-US,en;q=0.9', 'Connection': 'keep-alive'}
self.session.headers.update(self.headers)
def destroy(self):
if hasattr(self, 'session'): self.session.close()
def fetch(self, url, headers=None, is_api=False):
req_headers = headers or self.headers
if is_api or '/api/' in url: req_headers['Accept'] = 'text/css'
return self.session.get(url, headers=req_headers, timeout=30, verify=False)
def homeContent(self, filter):
services = [("Patreon", "patreon"), ("Pixiv Fanbox", "fanbox"), ("Fantia", "fantia"), ("Discord", "discord"), ("Gumroad", "gumroad"), ("SubscribeStar", "subscribestar"), ("Boosty", "boosty"), ("DLsite", "dlsite")]
result = [{"type_name": "Popular Recent", "type_id": "popular"}] + [{"type_name": n, "type_id": i} for n, i in services]
if not self.creators_cache: threading.Thread(target=self._load_creators_task).start()
return {'class': result}
def _load_creators_task(self):
resp = self.fetch(f"{self.api_url}/creators", is_api=True)
if resp and resp.status_code == 200: self.creators_cache = resp.json()
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg)
handlers = {'popular': lambda: self._get_popular_posts(pg), 'similar###': lambda: self._get_similar_list(tid, pg), 'creator###': lambda: self._get_post_list(tid, pg), 'post###': lambda: self._handle_image_popup_safely(tid)}
for prefix, handler in handlers.items():
if tid.startswith(prefix): return handler()
return self._get_creator_list(tid, pg)
def _get_creator_list(self, service_id, pg):
if not self.creators_cache:
self._load_creators_task()
if not self.creators_cache: return {'list': [{'vod_id': 'err', 'vod_name': 'Loading...', 'vod_pic': ''}]}
filtered = [c for c in self.creators_cache if c.get('service') == service_id]
filtered.sort(key=lambda x: x.get('favorited', 0), reverse=True)
limit = 20; total = len(filtered); start, end = (pg - 1) * limit, pg * limit
style = {"type": "list", "ratio": "1.1"}; icon_prefix = f"https://img.kemono.cr/icons/{service_id}/"
vlist = [{'vod_id': f"creator###{service_id}###{item['id']}", 'vod_name': item.get('name'), 'vod_pic': icon_prefix + item['id'], 'vod_tag': 'folder', 'vod_remarks': f"{item.get('favorited', 0)}", "style": style} for item in filtered[start:end]]
return {'list': vlist, 'page': pg, 'pagecount': (total + limit - 1) // limit + 1, 'limit': limit, 'total': total}
def _get_similar_list(self, tid, pg):
_, service, user_id = tid.split('###')
resp = self.fetch(f"{self.api_url}/{service}/user/{user_id}/recommended", is_api=True)
if not resp or resp.status_code != 200: return {'list': [{'vod_id': 'err', 'vod_name': 'API Error', 'vod_pic': ''}]}
creators = resp.json()
if not creators: return {'list': [{'vod_id': 'err', 'vod_name': 'No Similar Artists', 'vod_pic': ''}]}
creators.sort(key=lambda x: x.get('score', 0), reverse=True)
limit = 20; total = len(creators); start, end = (pg - 1) * limit, pg * limit
style = {"type": "list", "ratio": "1.1"}
vlist = [{'vod_id': f"creator###{item['service']}###{item['id']}", 'vod_name': item.get('name'), 'vod_pic': f"https://img.kemono.cr/icons/{item['service']}/{item['id']}", 'vod_tag': 'folder', 'vod_remarks': f"R: {int(item.get('score', 0)*100)}", "style": style} for item in creators[start:end]]
return {'list': vlist, 'page': pg, 'pagecount': (total + limit - 1) // limit + 1, 'limit': limit, 'total': total}
def _get_post_list(self, tid, pg):
_, service, user_id = tid.split('###')
resp = self.fetch(f"{self.api_url}/{service}/user/{user_id}/posts?o={(pg - 1) * 50}", is_api=True)
vlist = []
if resp and resp.status_code == 200:
for post in resp.json():
files = [post.get('file', {})] + post.get('attachments', [])
cover_path = next((f.get('path') for f in files if f.get('path') and f['path'].split('.')[-1].lower() in self.img_exts), None)
cover = f"https://img.kemono.cr/thumbnail/data{cover_path}" if cover_path else "https://kemono.cr/static/kemono-logo.svg"
has_video = bool(post.get('videos')) or any(f.get('path', '').split('.')[-1].lower() in self.vid_exts for f in files)
style = {"type": "list", "ratio": "1.1"}
vlist.append({'vod_id': f"{'video' if has_video else 'post'}###{service}###{user_id}###{post.get('id')}", 'vod_name': post.get('title', 'Untitled'), 'vod_pic': cover, 'vod_remarks': '▶ Video' if has_video else post.get('published', '')[:10], 'vod_tag': 'folder' if not has_video else '', 'style': style})
return {'list': vlist, 'page': pg, 'pagecount': pg + 1 if len(vlist) >= 50 else pg, 'limit': 50, 'total': 99999}
def _get_popular_posts(self, pg):
resp = self.fetch(f"{self.api_url}/posts/popular?period=recent&o={(pg - 1) * 50}", is_api=True)
vlist = []; total_count = 99999
if resp and resp.status_code == 200:
data = resp.json(); total_count = data.get('props', {}).get('count', 99999)
for post in data.get('posts', []):
files = [post.get('file', {})] + post.get('attachments', [])
cover_path = next((f.get('path') for f in files if f.get('path') and f['path'].split('.')[-1].lower() in self.img_exts), None)
cover = f"https://img.kemono.cr/thumbnail/data{cover_path}" if cover_path else "https://kemono.cr/static/kemono-logo.svg"
has_video = bool(post.get('videos')) or any(f.get('path', '').split('.')[-1].lower() in self.vid_exts for f in files)
style = {"type": "rect", "ratio": 1.33}
vlist.append({'vod_id': f"{'video' if has_video else 'post'}###{post.get('service')}###{post.get('user')}###{post.get('id')}", 'vod_name': post.get('title', 'Untitled'), 'vod_pic': cover, 'vod_remarks': '▶ Video' if has_video else post.get('published', '')[:10], 'vod_tag': 'folder' if not has_video else '', 'style': style})
return {'list': vlist, 'page': pg, 'pagecount': (total_count + 49) // 50, 'limit': 50, 'total': total_count}
def detailContent(self, ids):
tid = ids[0]
if not tid.startswith('video###'): return {'list': []}
_, service, user_id, post_id = tid.split('###')
resp = self.fetch(f"{self.api_url}/{service}/user/{user_id}/post/{post_id}", is_api=True)
if not resp or resp.status_code != 200: return {'list': []}
data = resp.json(); post = data.get('post', data); play_list = []
for v in post.get('videos', []):
if v.get('path'):
base = v.get('server', '') or ''; path = v.get('path', '')
if not path.startswith('/data'): path = '/data' + path
play_list.append(f"{v.get('name', 'Video')}${base}{path}")
if not play_list:
for idx, f in enumerate([post.get('file')] + post.get('attachments', [])):
if f and f.get('path') and f['path'].split('.')[-1].lower() in self.vid_exts:
full_url = self.img_base + f['path'] if not f['path'].startswith('http') else f['path']
play_list.append(f"{f.get('name', f'Part {idx+1}')}${full_url}")
files = [post.get('file', {})]
first_img = next((f.get('path') for f in files if f.get('path') and f['path'].split('.')[-1].lower() in self.img_exts), None)
pic = f"https://img.kemono.cr/thumbnail/data{first_img}" if first_img else ""
link_data = {'id': f"similar###{service}###{user_id}", 'name': '★ Similar Artists'}
director_link = f"[a=cr:{json.dumps(link_data)}/]★ 查找相似画师 (Find Similar)[/a]"
vod = {"vod_id": tid, "vod_name": post.get('title', 'Untitled'), "vod_pic": pic, "type_name": service, "vod_year": post.get('published', '')[:4], "vod_pubdate": post.get('published', '')[:10], "vod_area": "Kemono", "vod_remarks": f"{post.get('favorited', 0)}", "vod_director": director_link, "vod_content": re.sub(r'<[^>]+>', '', post.get('content', '')).strip()[:500], "vod_play_from": "KemonoPlayer", "vod_play_url": "#".join(play_list) if play_list else ""}
return {"list": [vod]}
def playerContent(self, flag, id, vipFlags): return {'parse': 0, 'url': id, 'header': {'User-Agent': self.headers['User-Agent']}}
def searchContent(self, key, quick, pg=1):
if not self.creators_cache: self._load_creators_task()
pg, limit = int(pg), 20; key_lower = key.lower()
results = [c for c in self.creators_cache if key_lower in c.get('name', '').lower()]
results.sort(key=lambda x: x.get('favorited', 0), reverse=True)
start, end = (pg - 1) * limit, pg * limit
style = {"type": "list", "ratio": "1.1"}
vlist = [{'vod_id': f"creator###{item['service']}###{item['id']}", 'vod_name': item['name'], 'vod_pic': f"https://img.kemono.cr/icons/{item['service']}/{item['id']}", 'vod_tag': 'folder', 'vod_remarks': item['service'], "style": style} for item in results[start:end]]
if pg == 1 and results:
top_match = results[0]
resp = self.fetch(f"{self.api_url}/{top_match['service']}/user/{top_match['id']}/recommended", is_api=True)
if resp and resp.status_code == 200:
rec_data = resp.json(); rec_data.sort(key=lambda x: x.get('score', 0), reverse=True)
if rec_data:
vlist.append({'vod_id': 'ignore', 'vod_name': '=== 相似画师 (Similar) ===', 'vod_pic': 'https://kemono.cr/static/kemono-logo.svg', 'vod_tag': '', 'vod_remarks': '', 'style': style})
vlist.extend({'vod_id': f"creator###{item['service']}###{item['id']}", 'vod_name': item.get('name'), 'vod_pic': f"https://img.kemono.cr/icons/{item['service']}/{item['id']}", 'vod_tag': 'folder', 'vod_remarks': f"R: {int(item.get('score', 0)*100)}", "style": style} for item in rec_data)
return {'list': vlist, 'page': pg, 'pagecount': (len(results) + limit - 1) // limit + 1, 'limit': limit, 'total': len(results)}
def _handle_image_popup_safely(self, tid):
def load_images_and_show():
_, service, user_id, post_id = tid.split('###')
resp = self.fetch(f"{self.api_url}/{service}/user/{user_id}/post/{post_id}", is_api=True)
if not resp or resp.status_code != 200: return
data = resp.json(); post = data.get('post', data); image_urls = []
for f in [post.get('file')] + post.get('attachments', []):
if f and f.get('path') and f['path'].split('.')[-1].lower() in self.img_exts: image_urls.append(self.img_base + f['path'] if not f['path'].startswith('http') else f['path'])
if post.get('content'):
for img_url in self.img_pattern.findall(post['content']):
if img_url.split('.')[-1].lower() in self.img_exts:
if img_url.startswith('//'): img_url = 'https:' + img_url
elif img_url.startswith('/'): img_url = self.base_url + img_url
image_urls.append(img_url)
if not image_urls: return
cache_key = f"{service}_{user_id}_{post_id}"; self.post_image_cache[cache_key] = image_urls; self._show_popup_dialog(cache_key)
threading.Thread(target=load_images_and_show).start()
return {'list': [{'vod_id': 'ignore', 'vod_name': 'Gallery Loading...', 'vod_pic': '', 'vod_remarks': 'View'}], 'page': 1, 'pagecount': 1, 'limit': 1, 'total': 1}
def _show_popup_dialog(self, cache_key):
def launch_popup():
try:
from java import jclass, dynamic_proxy
from java.lang import Runnable
JClass = jclass("java.lang.Class"); AT = JClass.forName("android.app.ActivityThread"); currentAT = AT.getMethod("currentActivityThread").invoke(None)
mActivities = AT.getDeclaredField("mActivities"); mActivities.setAccessible(True); values = mActivities.get(currentAT).values()
try: records = values.toArray()
except: records = values.getClass().getMethod("toArray").invoke(values)
act = None
for record in records:
try:
recordClass = record.getClass(); pausedField = recordClass.getDeclaredField("paused"); pausedField.setAccessible(True)
if not pausedField.getBoolean(record): activityField = recordClass.getDeclaredField("activity"); activityField.setAccessible(True); act = activityField.get(record); break
except: continue
if not act: return
class UiRunner(dynamic_proxy(Runnable)):
def __init__(self, func): super().__init__(); self.func = func
def run(self):
try: self.func()
except: pass
def show_dialog():
try:
Dialog = jclass("android.app.Dialog"); WebView = jclass("android.webkit.WebView"); ColorDrawable = jclass("android.graphics.drawable.ColorDrawable"); Color = jclass("android.graphics.Color")
d = Dialog(act); d.requestWindowFeature(1); win = d.getWindow(); win.getDecorView().setPadding(0, 0, 0, 0); win.setBackgroundDrawable(ColorDrawable(Color.BLACK)); win.setLayout(-1, -1)
w = WebView(act); ws = w.getSettings(); ws.setJavaScriptEnabled(True); ws.setDomStorageEnabled(True); w.setBackgroundColor(Color.BLACK)
images = self.post_image_cache.get(cache_key, [])
img_tags = [f'<div class="image-item"><img src="{url}" loading="lazy" referrerpolicy="no-referrer"></div>' for url in images]
html = f"""<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="referrer" content="no-referrer"><style>body{{margin:0;padding:5px;background:#121212}}.image-grid{{display:grid;grid-template-columns:repeat(auto-fill,minmax(100%,1fr));gap:5px}}.image-item{{background:#222;border-radius:4px;overflow:hidden}}.image-item img{{width:100%;height:auto;display:block}}</style></head><body><div class="image-grid">{"".join(img_tags)}</div></body></html>"""
w.loadDataWithBaseURL(None, html, "text/html", "utf-8", None); d.setContentView(w); d.show()
except: pass
act.getWindow().getDecorView().post(UiRunner(show_dialog))
except: pass
threading.Thread(target=launch_popup).start()

246
PY1/kemono优化.py Normal file
View File

@@ -0,0 +1,246 @@
# coding: utf-8
import sys, re, threading, socket, urllib.parse, http.server, socketserver
import requests
from base.spider import Spider
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
try:
from requests.packages import urllib3
urllib3.disable_warnings()
except: pass
class ThreadedHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
daemon_threads = True
class SlideshowHandler(http.server.BaseHTTPRequestHandler):
def log_message(self, format, *args): pass
def log_error(self, format, *args): pass
def do_GET(self):
spider = getattr(self.server, 'spider', None)
if not spider: return
try:
path_info = urllib.parse.urlparse(self.path)
path, query = path_info.path, urllib.parse.parse_qs(path_info.query)
if path == '/gallery.html': self._handle_gallery(spider, query)
elif path == '/image': self._handle_image(spider, query)
else: self.send_error(404)
except: self.send_error(500)
def _handle_gallery(self, spider, query):
try:
cache_key = query.get('id', [''])[0]
images = spider.post_image_cache.get(cache_key, [])
if not images:
self.send_response(200); self.end_headers()
self.wfile.write(b"<h2 style='color:#ccc;text-align:center;margin-top:20%'>Loading...</h2>")
return
img_tags = [f'<div class="image-item"><img src="{url}" loading="lazy" referrerpolicy="no-referrer"></div>' for url in images]
html = f"""<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>body{{margin:0;padding:5px;background:#121212}}.image-grid{{display:grid;grid-template-columns:repeat(auto-fill,minmax(100%,1fr));gap:5px}}.image-item{{background:#222;border-radius:4px;overflow:hidden}}.image-item img{{width:100%;height:auto;display:block}}</style></head><body><div class="image-grid">{"".join(img_tags)}</div></body></html>"""
self.send_response(200); self.send_header('Content-type', 'text/html; charset=utf-8'); self.end_headers()
self.wfile.write(html.encode('utf-8'))
except: pass
def _handle_image(self, spider, query):
try:
target = query.get('url', [''])[0]
if not target: return self.send_error(400)
headers = {'User-Agent': spider.headers['User-Agent']}
with requests.get(target, headers=headers, proxies=spider.proxies, stream=True, verify=False, timeout=30) as resp:
if resp.status_code == 200:
self.send_response(resp.status_code)
self.send_header('Content-Type', resp.headers.get('Content-Type', 'image/jpeg'))
self.send_header('Cache-Control', 'max-age=86400')
self.end_headers()
for chunk in resp.iter_content(chunk_size=65536):
if chunk: self.wfile.write(chunk)
else: self.send_error(resp.status_code)
except: self.send_error(404)
class Spider(Spider):
server = None
server_port = 0
creators_cache = []
post_image_cache = {}
img_pattern = re.compile(r'src=["\']([^"\']+)["\']')
img_exts = {'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'avif'}
vid_exts = {'mp4', 'mkv', 'mov', 'webm', 'm4v', 'avi', 'flv'}
def getName(self): return "Kemono"
def init(self, extend=""):
try:
super().init(extend)
self.base_url = "https://kemono.cr"
self.api_url = "https://kemono.cr/api/v1"
self.img_base = "https://kemono.cr/data"
self.proxies = {'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
self.session = requests.Session()
self.session.proxies.update(self.proxies)
retries = Retry(total=3, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
adapter = HTTPAdapter(pool_connections=20, pool_maxsize=20, max_retries=retries)
self.session.mount('https://', adapter); self.session.mount('http://', adapter)
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', 'Referer': f"{self.base_url}/", 'Accept': 'text/css,*/*;q=0.1', 'Accept-Language': 'en-US,en;q=0.9', 'Connection': 'keep-alive'}
self.session.headers.update(self.headers)
self._start_server()
except: pass
def destroy(self):
if self.server:
try: self.server.shutdown(); self.server.server_close()
except: pass
self.server = None
if hasattr(self, 'session'): self.session.close()
def _start_server(self):
if self.server: return
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', 0)); self.server_port = sock.getsockname()[1]; sock.close()
self.server = ThreadedHTTPServer(('0.0.0.0', self.server_port), SlideshowHandler)
self.server.spider = self
t = threading.Thread(target=self.server.serve_forever); t.daemon = True; t.start()
except: pass
def fetch(self, url, headers=None, is_api=False):
try:
req_headers = headers or self.headers
if is_api or '/api/' in url: req_headers['Accept'] = 'text/css'
return self.session.get(url, headers=req_headers, timeout=30, verify=False)
except: return None
def _proxify_url(self, target):
if not target: return ""
if target.startswith('//'): target = 'https:' + target
return f"http://127.0.0.1:{self.server_port}/image?url={urllib.parse.quote(target)}"
def homeContent(self, filter):
services = [{"type_name": n, "type_id": i} for n, i in [("Patreon", "patreon"), ("Pixiv Fanbox", "fanbox"), ("Fantia", "fantia"), ("Discord", "discord"), ("Gumroad", "gumroad"), ("SubscribeStar", "subscribestar"), ("Afdian", "afdian"), ("Boosty", "boosty"), ("DLsite", "dlsite")]]
if not self.creators_cache: threading.Thread(target=self._load_creators_task).start()
return {'class': services}
def _load_creators_task(self):
try:
resp = self.fetch(f"{self.api_url}/creators", is_api=True)
if resp and resp.status_code == 200: self.creators_cache = resp.json()
except: pass
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg)
if tid.startswith('post###'): return self._handle_image_popup_safely(tid)
elif tid.startswith('creator###'): return self._get_post_list(tid, pg)
else: return self._get_creator_list(tid, pg)
def _get_creator_list(self, service_id, pg):
limit = 20
if not self.creators_cache:
self._load_creators_task()
if not self.creators_cache: return {'list': [{'vod_id': 'err', 'vod_name': 'Loading...', 'vod_pic': ''}]}
filtered = [c for c in self.creators_cache if c.get('service') == service_id]
filtered.sort(key=lambda x: x.get('favorited', 0), reverse=True)
total = len(filtered); start, end = (pg - 1) * limit, pg * limit
style = {"type": "list", "ratio": "1.1"}
icon_prefix = f"https://img.kemono.cr/icons/{service_id}/"
vlist = [{'vod_id': f"creator###{service_id}###{item['id']}", 'vod_name': item.get('name'), 'vod_pic': self._proxify_url(icon_prefix + item['id']), 'vod_tag': 'folder', 'vod_remarks': f"{item.get('favorited', 0)}", "style": style} for item in filtered[start:end]]
return {'list': vlist, 'page': pg, 'pagecount': (total + limit - 1) // limit + 1, 'limit': limit, 'total': total}
def _get_post_list(self, tid, pg):
try:
_, service, user_id = tid.split('###')
resp = self.fetch(f"{self.api_url}/{service}/user/{user_id}/posts?o={(pg - 1) * 50}", is_api=True)
vlist = []
if resp and resp.status_code == 200:
for post in resp.json():
files = [post.get('file', {})] + post.get('attachments', [])
cover_path = next((f.get('path') for f in files if f.get('path') and f['path'].split('.')[-1].lower() in self.img_exts), None)
cover = f"https://img.kemono.cr/thumbnail/data{cover_path}" if cover_path else "https://kemono.cr/static/kemono-logo.svg"
has_video = bool(post.get('videos'))
if not has_video: has_video = any(f.get('path', '').split('.')[-1].lower() in self.vid_exts for f in files)
style = {"type": "list", "ratio": "1.1"}
vlist.append({'vod_id': f"{'video' if has_video else 'post'}###{service}###{user_id}###{post.get('id')}", 'vod_name': post.get('title', 'Untitled'), 'vod_pic': self._proxify_url(cover), 'vod_remarks': '▶ Video' if has_video else post.get('published', '')[:10], 'vod_tag': 'folder' if not has_video else '', 'style': style})
return {'list': vlist, 'page': pg, 'pagecount': pg + 1 if len(vlist) >= 50 else pg, 'limit': 50, 'total': 99999}
except: return {'list': []}
def detailContent(self, ids):
try:
tid = ids[0]
if not tid.startswith('video###'): return {'list': []}
_, service, user_id, post_id = tid.split('###')
resp = self.fetch(f"{self.api_url}/{service}/user/{user_id}/post/{post_id}", is_api=True)
if not resp or resp.status_code != 200: return {'list': []}
data = resp.json(); post = data.get('post', data); play_list = []
if post.get('videos'):
for v in post['videos']:
if v.get('path'):
base = v.get('server', '') or ''; path = v.get('path', '')
if not path.startswith('/data'): path = '/data' + path
play_list.append(f"{v.get('name', 'Video')}${base}{path}")
if not play_list:
for idx, f in enumerate([post.get('file')] + post.get('attachments', [])):
if f and f.get('path') and f['path'].split('.')[-1].lower() in self.vid_exts:
full_url = self.img_base + f['path'] if not f['path'].startswith('http') else f['path']
play_list.append(f"{f.get('name', f'Part {idx+1}')}${full_url}")
files = [post.get('file', {})]
first_img = next((f.get('path') for f in files if f.get('path') and f['path'].split('.')[-1].lower() in self.img_exts), None)
pic = self._proxify_url(f"https://img.kemono.cr/thumbnail/data{first_img}") if first_img else ""
vod = {"vod_id": tid, "vod_name": post.get('title', 'Untitled'), "vod_pic": pic, "type_name": service, "vod_year": post.get('published', '')[:4], "vod_pubdate": post.get('published', '')[:10], "vod_area": "Kemono", "vod_remarks": f"{post.get('favorited', 0)}", "vod_content": re.sub(r'<[^>]+>', '', post.get('content', '')).strip()[:500], "vod_play_from": "KemonoPlayer", "vod_play_url": "#".join(play_list) if play_list else "No Video$http://127.0.0.1/"}
return {"list": [vod]}
except: return {'list': []}
def playerContent(self, flag, id, vipFlags):
return {'parse': 0, 'url': id, 'header': {'User-Agent': self.headers['User-Agent']}}
def searchContent(self, key, quick, pg=1):
if not self.creators_cache: self._load_creators_task()
pg, limit = int(pg), 20; key_lower = key.lower()
results = [c for c in self.creators_cache if key_lower in c.get('name', '').lower()]
results.sort(key=lambda x: x.get('favorited', 0), reverse=True)
start, end = (pg - 1) * limit, pg * limit
style = {"type": "list", "ratio": "1.1"}
vlist = [{'vod_id': f"creator###{item['service']}###{item['id']}", 'vod_name': item['name'], 'vod_pic': self._proxify_url(f"https://img.kemono.cr/icons/{item['service']}/{item['id']}"), 'vod_tag': 'folder', 'vod_remarks': item['service'], "style": style} for item in results[start:end]]
return {'list': vlist, 'page': pg, 'pagecount': (len(results) + limit - 1) // limit + 1, 'limit': limit, 'total': len(results)}
def _handle_image_popup_safely(self, tid):
def load_images_and_show():
try:
_, service, user_id, post_id = tid.split('###')
resp = self.fetch(f"{self.api_url}/{service}/user/{user_id}/post/{post_id}", is_api=True)
if not resp or resp.status_code != 200: return
data = resp.json(); post = data.get('post', data); image_urls = []
for f in [post.get('file')] + post.get('attachments', []):
if f and f.get('path') and f['path'].split('.')[-1].lower() in self.img_exts:
image_urls.append(self.img_base + f['path'] if not f['path'].startswith('http') else f['path'])
if post.get('content'):
for img_url in self.img_pattern.findall(post['content']):
if img_url.split('.')[-1].lower() in self.img_exts:
if img_url.startswith('//'): img_url = 'https:' + img_url
elif img_url.startswith('/'): img_url = self.base_url + img_url
image_urls.append(img_url)
if not image_urls: return
seen = set(); proxied_urls = []
for u in image_urls:
if u not in seen: proxied_urls.append(self._proxify_url(u)); seen.add(u)
cache_key = f"{service}_{user_id}_{post_id}"
self.post_image_cache[cache_key] = proxied_urls
self._show_popup_dialog(cache_key)
except: pass
threading.Thread(target=load_images_and_show).start()
return {'list': [{'vod_id': 'ignore', 'vod_name': 'Gallery Loading...', 'vod_pic': '', 'vod_remarks': 'View'}], 'page': 1, 'pagecount': 1, 'limit': 1, 'total': 1}
def _show_popup_dialog(self, cache_key):
def launch_popup():
try:
from java import jclass, dynamic_proxy
from java.lang import Runnable
JClass = jclass("java.lang.Class")
AT = JClass.forName("android.app.ActivityThread")
currentAT = AT.getMethod("currentActivityThread").invoke(None)
mActivities = AT.getDeclaredField("mActivities"); mActivities.setAccessible(True)
values = mActivities.get(currentAT).values()
try: records = values.toArray()
except: records = values.getClass().getMethod("toArray").invoke(values)
act = None
for record in records:
try:
recordClass = record.getClass(); pausedField = recordClass.getDeclaredField("paused"); pausedField.setAccessible(True)
if not pausedField.getBoolean(record):
activityField = recordClass.getDeclaredField("activity"); activityField.setAccessible(True); act = activityField.get(record); break
except: continue
if not act: return
class UiRunner(dynamic_proxy(Runnable)):
def __init__(self, func): super().__init__(); self.func = func
def run(self):
try: self.func()
except: pass
def show_dialog():
try:
Dialog = jclass("android.app.Dialog"); WebView = jclass("android.webkit.WebView"); ColorDrawable = jclass("android.graphics.drawable.ColorDrawable"); Color = jclass("android.graphics.Color")
d = Dialog(act); d.requestWindowFeature(1); win = d.getWindow(); win.getDecorView().setPadding(0, 0, 0, 0); win.setBackgroundDrawable(ColorDrawable(Color.BLACK)); win.setLayout(-1, -1)
w = WebView(act); ws = w.getSettings(); ws.setJavaScriptEnabled(True); ws.setDomStorageEnabled(True); w.setBackgroundColor(Color.BLACK)
w.loadUrl(f"http://127.0.0.1:{self.server_port}/gallery.html?id={cache_key}"); d.setContentView(w); d.show()
except: pass
act.getWindow().getDecorView().post(UiRunner(show_dialog))
except: pass
threading.Thread(target=launch_popup).start()

212
PY1/lavAPP.py Normal file
View File

@@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import sys
from base64 import b64encode, b64decode
from Crypto.Hash import MD5, SHA256
sys.path.append('..')
from base.spider import Spider
from Crypto.Cipher import AES
import json
import time
class Spider(Spider):
def getName(self):
return "lav"
def init(self, extend=""):
self.id = self.ms(str(int(time.time() * 1000)))[:16]
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def action(self, action):
pass
def destroy(self):
pass
host = "http://sir_new.tiansexyl.tv"
t = str(int(time.time() * 1000))
headers = {'User-Agent': 'okhttp-okgo/jeasonlzy', 'Connection': 'Keep-Alive',
'Content-Type': 'application/x-www-form-urlencoded'}
def homeContent(self, filter):
cateManual = {"演员": "actor", "分类": "avsearch", }
classes = []
for k in cateManual:
classes.append({'type_name': k, 'type_id': cateManual[k]})
j = {'code': 'homePage', 'mod': 'down', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
'oauth_id': self.id}
body = self.aes(j)
data = self.post(f'{self.host}/api.php?t={str(int(time.time() * 1000))}', data=body, headers=self.headers).json()['data']
data1 = self.aes(data, False)['data']
self.r = data1['r']
for i, d in enumerate(data1['avTag']):
# if i == 4:
# break
classes.append({'type_name': d['name'], 'type_id': d['tag']})
resutl = {}
resutl["class"] = classes
return resutl
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
id = tid.split("@@")
result = {}
result["page"] = pg
result["pagecount"] = 9999
result["limit"] = 90
result["total"] = 999999
if id[0] == 'avsearch':
if pg == '1':
j = {'code': 'avsearch', 'mod': 'search', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
'oauth_id': self.id}
if len(id) > 1:
j = {'code': 'find', 'mod': 'tag', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
'oauth_id': self.id, 'type': 'av', 'dis': 'new', 'page': str(pg), 'tag': id[1]}
elif id[0] == 'actor':
j = {'mod': 'actor', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv', 'app_type': 'rn',
'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn', 'oauth_id': self.id,
'page': str(pg), 'filter': ''}
if len(id) > 1:
j = {'code': 'eq', 'mod': 'actor', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
'oauth_id': self.id, 'page': str(pg), 'id': id[1], 'actor': id[2]}
else:
j = {'code': 'search', 'mod': 'av', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
'oauth_id': self.id, 'page': str(pg), 'tag': id[0]}
body = self.aes(j)
data = self.post(f'{self.host}/api.php?t={str(int(time.time() * 1000))}', data=body, headers=self.headers).json()['data']
data1 = self.aes(data, False)['data']
videos = []
if tid == 'avsearch' and len(id) == 1:
for item in data1:
videos.append({"vod_id": id[0] + "@@" + str(item.get('tags')), 'vod_name': item.get('name'),
'vod_pic': self.imgs(item.get('ico')), 'vod_tag': 'folder',
'style': {"type": "rect", "ratio": 1.33}})
elif tid == 'actor' and len(id) == 1:
for item in data1:
videos.append({"vod_id": id[0] + "@@" + str(item.get('id')) + "@@" + item.get('name'),
'vod_name': item.get('name'), 'vod_pic': self.imgs(item.get('cover')),
'vod_tag': 'folder', 'style': {"type": "oval"}})
else:
for item in data1:
if item.get('_id'):
videos.append({"vod_id": str(item.get('id')), 'vod_name': item.get('title'),
'vod_pic': self.imgs(item.get('cover_thumb') or item.get('cover_full')),
'vod_remarks': item.get('good'), 'style': {"type": "rect", "ratio": 1.33}})
result["list"] = videos
return result
def detailContent(self, ids):
id = ids[0]
j = {'code': 'detail', 'mod': 'av', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
'oauth_id': self.id, 'id': id}
body = self.aes(j)
data = self.post(f'{self.host}/api.php?t={str(int(time.time() * 1000))}', data=body, headers=self.headers).json()['data']
data1 = self.aes(data, False)['line']
vod = {}
play = []
for itt in data1:
a = itt['line'].get('s720')
if a:
b = a.split('.')
b[0] = 'https://m3u8'
a = '.'.join(b)
play.append(itt['info']['tips'] + "$" + a)
break
vod["vod_play_from"] = 'LAV'
vod["vod_play_url"] = "#".join(play)
result = {"list": [vod]}
return result
def searchContent(self, key, quick, pg="1"):
pass
def playerContent(self, flag, id, vipFlags):
url = self.getProxyUrl() + "&url=" + b64encode(id.encode('utf-8')).decode('utf-8') + "&type=m3u8"
self.hh = {'User-Agent': 'dd', 'Connection': 'Keep-Alive', 'Referer': self.r}
result = {}
result["parse"] = 0
result["url"] = url
result["header"] = self.hh
return result
def localProxy(self, param):
url = param["url"]
if param.get('type') == "m3u8":
return self.vod(b64decode(url).decode('utf-8'))
else:
return self.img(url)
def vod(self, url):
data = self.fetch(url, headers=self.hh).text
key = bytes.fromhex("13d47399bda541b85e55830528d4e66f1791585b2d2216f23215c4c63ebace31")
iv = bytes.fromhex(data[:32])
data = data[32:]
cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
data_bytes = bytes.fromhex(data)
decrypted = cipher.decrypt(data_bytes)
encoded = decrypted.decode("utf-8").replace("\x08", "")
return [200, "application/vnd.apple.mpegur", encoded]
def imgs(self, url):
return self.getProxyUrl() + '&url=' + url
def img(self, url):
type = url.split('.')[-1]
data = self.fetch(url).text
key = bytes.fromhex("ba78f184208d775e1553550f2037f4af22cdcf1d263a65b4d5c74536f084a4b2")
iv = bytes.fromhex(data[:32])
data = data[32:]
cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
data_bytes = bytes.fromhex(data)
decrypted = cipher.decrypt(data_bytes)
return [200, f"image/{type}", decrypted]
def ms(self, data, m=False):
h = MD5.new()
if m:
h = SHA256.new()
h.update(data.encode('utf-8'))
return h.hexdigest()
def aes(self, data, operation=True):
key = bytes.fromhex("620f15cfdb5c79c34b3940537b21eda072e22f5d7151456dec3932d7a2b22c53")
t = str(int(time.time()))
ivt = self.ms(t)
if operation:
data = json.dumps(data, separators=(',', ':'))
iv = bytes.fromhex(ivt)
else:
iv = bytes.fromhex(data[:32])
data = data[32:]
cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
if operation:
data_bytes = data.encode('utf-8')
encrypted = cipher.encrypt(data_bytes)
ep = f'{ivt}{encrypted.hex()}'
edata = f"data={ep}&timestamp={t}0d27dfacef1338483561a46b246bf36d"
sign = self.ms(self.ms(edata, True))
edata = f"timestamp={t}&data={ep}&sign={sign}"
return edata
else:
data_bytes = bytes.fromhex(data)
decrypted = cipher.decrypt(data_bytes)
return json.loads(decrypted.decode('utf-8'))

1196
PY1/leaflow_checkin.py Normal file

File diff suppressed because it is too large Load Diff

362
PY1/onlyfans gv.py Normal file
View File

@@ -0,0 +1,362 @@
# -*- coding: utf-8 -*-
#author:嗷呜群fans&claude4⚡
import json
import sys
import re
import time
from base64 import b64encode
from urllib.parse import urljoin, urlencode
import requests
from pyquery import PyQuery as pq
from requests import Session
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
try:
self.proxies = json.loads(extend) if extend else {}
except:
self.proxies = {}
if isinstance(self.proxies, dict) and 'proxy' in self.proxies and isinstance(self.proxies['proxy'], dict):
self.proxies = self.proxies['proxy']
fixed = {}
for k, v in (self.proxies or {}).items():
if isinstance(v, str) and not v.startswith('http'):
fixed[k] = f'http://{v}'
else:
fixed[k] = v
self.proxies = fixed
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/142.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.3,en;q=0.2',
'Referer': 'https://gayvidsclub.com/',
'Origin': 'https://gayvidsclub.com',
}
self.host = "https://gayvidsclub.com"
self.session = Session()
self.session.proxies.update(self.proxies)
self.session.headers.update(self.headers)
def getName(self):
return "GayVidsClub"
def isVideoFormat(self, url):
return '.m3u8' in url or '.mp4' in url
def manualVideoCheck(self):
return True
def destroy(self):
pass
def homeContent(self, filter):
result = {}
cateManual = {
"最新": "/all-gay-porn/",
"COAT": "/all-gay-porn/coat/",
"MEN'S RUSH.TV": "/all-gay-porn/mens-rush-tv/",
"HUNK CHANNEL": "/all-gay-porn/hunk-channel/",
"KO": "/all-gay-porn/ko/",
"EXFEED": "/all-gay-porn/exfeed/",
"BRAVO!": "/all-gay-porn/bravo/",
"STR8BOYS": "/all-gay-porn/str8boys/",
"G-BOT": "/all-gay-porn/g-bot/"
}
classes = []
filters = {}
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
data = self.fetchPage("/")
vlist = self.getlist(data("article"))
if not vlist:
data = self.fetchPage('/all-gay-porn/')
vlist = self.getlist(data("article"))
return {'list': vlist}
def categoryContent(self, tid, pg, filter, extend):
result = {}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
if pg == 1:
url = tid
else:
url = f"{tid}page/{pg}/"
data = self.fetchPage(url)
vdata = self.getlist(data("article"))
result['list'] = vdata
return result
def detailContent(self, ids):
data = self.fetchPage(ids[0])
title = data('h1').text().strip()
iframe_src = None
iframe_elem = data('iframe')
if iframe_elem:
iframe_src = iframe_elem.attr('src')
if not iframe_src:
scripts = data('script')
for script in scripts.items():
script_text = script.text()
if 'iframe' in script_text and 'src' in script_text:
matches = re.findall(r'iframe.*?src=[\'"](https?://[^\'"]+)[\'"]', script_text)
if matches:
iframe_src = matches[0]
break
# 获取海报图片 - 确保使用横版图片
vod_pic = ""
img_elem = data('img')
if img_elem:
vod_pic = img_elem.attr('src')
# 确保使用横版海报图
if vod_pic and ('poster' in vod_pic or 'cover' in vod_pic):
# 已经是横版图片,不做处理
pass
elif vod_pic:
# 尝试转换为横版图片
vod_pic = self.ensure_horizontal_poster(vod_pic)
vod = {
'vod_name': title,
'vod_content': 'GayVidsClub视频',
'vod_tag': 'GayVidsClub',
'vod_pic': vod_pic, # 添加海报图片
'vod_play_from': 'GayVidsClub',
'vod_play_url': ''
}
play_lines = []
if iframe_src:
if not iframe_src.startswith('http'):
iframe_src = urljoin(self.host, iframe_src)
play_lines.append(f"直连${self.e64(iframe_src)}")
play_lines.append(f"嗅探${self.e64(ids[0])}")
if iframe_src:
play_lines.append(f"阿里云盘解析${self.e64(iframe_src)}")
play_lines.append(f"夸克网盘解析${self.e64(iframe_src)}")
play_lines.append(f"115网盘解析${self.e64(iframe_src)}")
play_lines.append(f"迅雷解析${self.e64(iframe_src)}")
play_lines.append(f"PikPak解析${self.e64(iframe_src)}")
play_lines.append(f"手机推送${iframe_src}")
else:
fallback_url = ids[0]
play_lines.append(f"阿里云盘解析${self.e64(fallback_url)}")
play_lines.append(f"夸克网盘解析${self.e64(fallback_url)}")
play_lines.append(f"115网盘解析${self.e64(fallback_url)}")
play_lines.append(f"迅雷解析${self.e64(fallback_url)}")
play_lines.append(f"PikPak解析${self.e64(fallback_url)}")
play_lines.append(f"手机推送${fallback_url}")
vod['vod_play_url'] = '#'.join(play_lines)
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
if pg == 1:
url = f"/?s={key}"
else:
url = f"/page/{pg}/?s={key}"
data = self.fetchPage(url)
return {'list': self.getlist(data("article")), 'page': pg}
def playerContent(self, flag, id, vipFlags):
url = self.d64(id)
if "直连" in flag:
return {'parse': 0, 'url': url, 'header': self.headers}
elif "嗅探" in flag:
return {'parse': 1, 'url': url, 'header': self.headers}
elif "阿里云盘解析" in flag:
return self.parse_with_aliyun(url)
elif "夸克网盘解析" in flag:
return self.parse_with_quark(url)
elif "115网盘解析" in flag:
return self.parse_with_115(url)
elif "迅雷解析" in flag:
return self.parse_with_thunder(url)
elif "PikPak解析" in flag:
return self.parse_with_pikpak(url)
elif "手机推送" in flag:
return {'parse': 1, 'url': url, 'header': self.headers}
else:
return {'parse': 1, 'url': url, 'header': self.headers}
def fetchPage(self, url):
if not url.startswith('http'):
url = urljoin(self.host, url)
response = self.session.get(url)
return pq(response.text)
def getlist(self, items):
vlist = []
for item in items.items():
vid = item.find('a').attr('href')
img = item.find('img').attr('src')
name = item.find('h2').text()
if not name:
name = item.find('h3').text()
# 确保使用横版海报图
if img:
if '?' in img:
img = img.split('?')[0]
# 确保使用横版图片
img = self.ensure_horizontal_poster(img)
vlist.append({
'vod_id': vid,
'vod_name': name,
'vod_pic': img,
'vod_remarks': '',
'style': {'type': 'rect', 'ratio': 1.33} # 添加横版样式
})
return vlist
def ensure_horizontal_poster(self, img_url):
"""
确保使用横版海报图片
"""
if not img_url:
return img_url
# 如果已经是横版图片,直接返回
if 'poster' in img_url or 'cover' in img_url:
return img_url
# 尝试转换为横版图片
# 常见的竖版图片标识
vertical_indicators = ['thumb', 'vertical', 'portrait', 'square']
# 常见的横版图片标识
horizontal_indicators = ['poster', 'cover', 'horizontal', 'landscape']
# 检查是否是竖版图片
is_vertical = any(indicator in img_url for indicator in vertical_indicators)
if is_vertical:
# 尝试转换为横版图片
for v_indicator in vertical_indicators:
for h_indicator in horizontal_indicators:
if v_indicator in img_url:
# 替换竖版标识为横版标识
new_url = img_url.replace(v_indicator, h_indicator)
# 检查新URL是否有效
try:
response = self.session.head(new_url, timeout=3)
if response.status_code == 200:
return new_url
except:
continue
# 如果无法转换,尝试添加横版参数
if '?' in img_url:
new_url = img_url + '&type=horizontal'
else:
new_url = img_url + '?type=horizontal'
return new_url
return img_url
def e64(self, data):
return b64encode(data.encode()).decode()
def d64(self, data):
from base64 import b64decode
return b64decode(data).decode()
def parse_with_aliyun(self, url):
try:
parse_result = {
'parse': 1,
'url': url,
'header': self.headers,
'parse_type': 'aliyun',
'message': '使用阿里云盘解析服务'
}
return parse_result
except Exception as e:
return {'parse': 1, 'url': url, 'header': self.headers}
def parse_with_quark(self, url):
try:
parse_result = {
'parse': 1,
'url': url,
'header': self.headers,
'parse_type': 'quark',
'message': '使用夸克网盘解析服务'
}
return parse_result
except Exception as e:
return {'parse': 1, 'url': url, 'header': self.headers}
def parse_with_115(self, url):
try:
parse_result = {
'parse': 1,
'url': url,
'header': self.headers,
'parse_type': '115',
'message': '使用115网盘解析服务'
}
return parse_result
except Exception as e:
return {'parse': 1, 'url': url, 'header': self.headers}
def parse_with_thunder(self, url):
try:
parse_result = {
'parse': 1,
'url': url,
'header': self.headers,
'parse_type': 'thunder',
'message': '使用迅雷解析服务'
}
return parse_result
except Exception as e:
return {'parse': 1, 'url': url, 'header': self.headers}
def parse_with_pikpak(self, url):
try:
parse_result = {
'parse': 1,
'url': url,
'header': self.headers,
'parse_type': 'pikpak',
'message': '使用PikPak解析服务'
}
return parse_result
except Exception as e:
return {'parse': 1, 'url': url, 'header': self.headers}

Some files were not shown because too many files have changed in this diff Show More