xxxx
This commit is contained in:
265
PY1/103.py
Normal file
265
PY1/103.py
Normal 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
319
PY1/18.py
Normal 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
290
PY1/1AV研究所.py
Normal 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
335
PY1/4K.py
Normal 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
330
PY1/51吸瓜.py
Normal 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
404
PY1/51吸瓜动态版.py
Normal 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
318
PY1/58视频.py
Normal 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
110
PY1/91.py
Normal 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
236
PY1/911.py
Normal 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
84
PY1/91RB1.py
Normal 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
186
PY1/91rb.py
Normal 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
313
PY1/AppMuou.py
Normal 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
168
PY1/Appfox.py
Normal 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
187
PY1/ApptoV5无加密.py
Normal 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
47
PY1/Chaturbate.py
Normal 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
165
PY1/DSYS.py
Normal 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('&', '&')
|
||||
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
599
PY1/E佬通用视频 (3).py
Normal 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('"', '"').replace(''', "'").replace('&', '&')
|
||||
|
||||
# 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
298
PY1/E佬通用视频.py
Normal 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
102
PY1/FeiApp.py
Normal 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
251
PY1/JAV目录.py
Normal 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
301
PY1/Phb.py
Normal 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
301
PY1/Pornhub.py
Normal 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
196
PY1/ThePorn.py
Normal 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
270
PY1/Xhm.py
Normal 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
276
PY1/Xvd.py
Normal 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
260
PY1/Xvideos.py
Normal 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'))
|
||||
BIN
PY1/__pycache__/103.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/103.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/18.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/18.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/4k.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/4k.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/51吸瓜动态版.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/51吸瓜动态版.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/91.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/91.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/911.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/911.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/91RB1.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/91RB1.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/91rb.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/91rb.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/Chaturbate.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/Chaturbate.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/DSYS.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/DSYS.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/E佬通用视频 (3).cpython-311.pyc
Normal file
BIN
PY1/__pycache__/E佬通用视频 (3).cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/E佬通用视频.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/E佬通用视频.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/NU.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/NU.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/Phb.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/Phb.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/ThePorn.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/ThePorn.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/Xhamster.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/Xhamster.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/Xhm.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/Xhm.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/Xvd.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/Xvd.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/Xvideos.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/Xvideos.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/boyfriend.show.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/boyfriend.show.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/cam4.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/cam4.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/epo.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/epo.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/fullhd.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/fullhd.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/gay.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/gay.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/kemon.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/kemon.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/lavAPP.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/lavAPP.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/onlyfans gv.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/onlyfans gv.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/pornhub.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/pornhub.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/py_mrav.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/py_mrav.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/py_stgay.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/py_stgay.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/py_stripchat.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/py_stripchat.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/py_色播聚合.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/py_色播聚合.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/rou.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/rou.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/tube8.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/tube8.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/uaa有声.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/uaa有声.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/y_139fm多分类.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/y_139fm多分类.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/yyds直播.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/yyds直播.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/一个区.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/一个区.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/七区.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/七区.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/七哥大洋马.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/七哥大洋马.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/中文X直播.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/中文X直播.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/乐哥欧美.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/乐哥欧美.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/乐哥玩物社区.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/乐哥玩物社区.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/九个区.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/九个区.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/今日看料.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/今日看料.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/四虎视频.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/四虎视频.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/好色tv.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/好色tv.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/妲己.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/妲己.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/小红薯APP.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/小红薯APP.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/小黄书[密].cpython-311.pyc
Normal file
BIN
PY1/__pycache__/小黄书[密].cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/推特APP.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/推特APP.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/有声小说.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/有声小说.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/玩具社区.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/玩具社区.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/瑟瑟.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/瑟瑟.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/神秘影院.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/神秘影院.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/禁片天堂.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/禁片天堂.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/菠萝七区.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/菠萝七区.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/韩国色情电影.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/韩国色情电影.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/香蕉.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/香蕉.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/黄色仓库.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/黄色仓库.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/黑料.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/黑料.cpython-311.pyc
Normal file
Binary file not shown.
BIN
PY1/__pycache__/黑料弹幕版.cpython-311.pyc
Normal file
BIN
PY1/__pycache__/黑料弹幕版.cpython-311.pyc
Normal file
Binary file not shown.
392
PY1/avjoy.py
Normal file
392
PY1/avjoy.py
Normal 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
179
PY1/badnews (1).py
Normal 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'
|
||||
|
||||
# ===== DM(H动漫)=========
|
||||
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
271
PY1/boyfriend.show.py
Normal 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
118
PY1/cam4.py
Normal 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
255
PY1/epo.py
Normal 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
379
PY1/fullhd.py
Normal 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
234
PY1/gay.py
Normal 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
197
PY1/kemon.py
Normal 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
246
PY1/kemono优化.py
Normal 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
212
PY1/lavAPP.py
Normal 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}×tamp={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
1196
PY1/leaflow_checkin.py
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user