feat(PyramidStore): 初始化项目并添加基础配置文件

添加 .gitignore 忽略子仓库的 .git 目录
添加 LICENSE 文件,使用 GNU General Public License v3.0
添加 README.md 说明文档,包含调试示例、免责声明和配置说明
添加 base/localProxy.py 基础代理配置文件
添加版本控制图片文件(二进制差异)
```
This commit is contained in:
2025-10-23 02:14:43 +08:00
commit 3572e29279
356 changed files with 120993 additions and 0 deletions

432
py/4kvm.py Normal file
View File

@@ -0,0 +1,432 @@
# -*- coding: utf-8 -*-
# 4K影视插件 - 优化版本
import re
import sys
import json
from pyquery import PyQuery as pq
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
pass
def getName(self):
return "4k影视"
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host = 'https://www.4kvm.org'
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,en;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Sec-Ch-Ua': '"Not/A)Brand";v="8", "Chromium";v="134", "Google Chrome";v="134"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"macOS"',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
}
# 公共方法
def _normalize_url(self, url):
"""标准化URL处理"""
if not url:
return url
if url.startswith('//'):
return f"https:{url}"
elif url.startswith('/'):
return f"{self.host}{url}"
return url
def _extract_video_basic(self, item):
"""提取视频基本信息"""
try:
link = self._normalize_url(item('a').attr('href') or item('h3 a').attr('href') or item('.data h3 a').attr('href'))
if not link:
return None
title = (item('h3').text().strip() or item('.data h3').text().strip() or
item('img').attr('alt') or item('a').attr('title') or '未知标题')
img = self._normalize_url(item('img').attr('src') or item('img').attr('data-src'))
# 简化备注提取
remarks = (item('.rating, .imdb, .vote').text().strip() or
item('.year, .date, span').text().strip() or
item('.type, .genre, .tag').text().strip())
return {
'vod_id': link,
'vod_name': title,
'vod_pic': img or '',
'vod_remarks': remarks,
'vod_year': ''
}
except:
return None
def _get_episode_count(self, season_data, page_html):
"""智能检测集数"""
# 方法1: 精确容器检测
episode_container = season_data('.jujiepisodios')
if episode_container:
episode_links = episode_container('a')
episode_numbers = [int(link.text().strip()) for link in episode_links.items()
if link.text().strip().isdigit() and 1 <= int(link.text().strip()) <= 200]
if episode_numbers:
return max(episode_numbers)
# 方法2: JavaScript数据提取
video_matches = re.findall(r'video.*?=.*?\[(.*?)\]', page_html, re.IGNORECASE | re.DOTALL)
for match in video_matches:
if '"name":' in match:
episode_names = re.findall(r'"name"\s*:\s*(\d+)', match)
if len(episode_names) >= 5:
episode_numbers = sorted(set(int(name) for name in episode_names))
if episode_numbers[0] == 1 and episode_numbers[-1] - episode_numbers[0] == len(episode_numbers) - 1:
return max(episode_numbers)
# 方法3: 文本模式匹配
page_text = season_data.text()
for pattern in [r'共(\d+)集', r'全(\d+)集', r'更新至(\d+)集', r'第(\d+)集']:
matches = re.findall(pattern, page_text)
if matches:
return max(int(m) for m in matches if m.isdigit())
# 默认值
return 24 if season_data('iframe, video, .player') else 1
def homeContent(self, filter):
try:
data = self.getpq(self.fetch(self.host, headers=self.headers).text)
classes = []
# 简化分类提取
nav_items = data('header .head-main-nav ul.main-header > li')
for k in nav_items.items():
main_link = k.children('a').eq(0)
link = main_link.attr('href')
name = main_link.text().strip()
if link and name and name not in ['首页', '影片下载']:
link = self._normalize_url(link)
class_info = {'type_name': name, 'type_id': link}
if '电视剧' in name or 'tvshows' in link:
class_info['filter_type'] = 'tvshows'
classes.append(class_info)
# 子分类
for sub_item in k('ul li').items():
sub_link = self._normalize_url(sub_item('a').attr('href'))
sub_name = sub_item('a').text().strip()
if sub_link and sub_name:
sub_class_info = {'type_name': f"{name}-{sub_name}", 'type_id': sub_link}
if '电视剧' in name or 'tvshows' in sub_link:
sub_class_info['filter_type'] = 'tvshows'
classes.append(sub_class_info)
return {'class': classes, 'list': self.getHomeList(data)}
except Exception as e:
return {'class': [], 'list': []}
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
try:
url = tid if pg == '1' else f"{tid}/page/{pg}" if '?' not in tid else f"{tid}&page={pg}"
data = self.getpq(self.fetch(url, headers=self.headers).text)
video_list = self.getVideoList(data)
if '电视剧' in url or 'tvshows' in url:
video_list = self.filterTVShowsOnly(video_list)
return {'list': video_list, 'page': int(pg), 'pagecount': 9999, 'limit': 30, 'total': 999999}
except Exception as e:
return {'list': [], 'page': int(pg), 'pagecount': 1, 'limit': 30, 'total': 0}
def detailContent(self, ids):
try:
first_id = next(iter(ids)) if hasattr(ids, '__iter__') and not isinstance(ids, str) else ids
data = self.getpq(self.fetch(first_id, headers=self.headers).text)
# 基本信息提取
vod = {
'vod_id': first_id,
'vod_name': data('.sheader h1, h1').text().strip(),
'vod_pic': self._normalize_url(data('.sheader .poster img, .poster img').attr('src')),
'vod_content': data('.sbox .wp-content, #info .wp-content').text().strip(),
'vod_year': '', 'vod_area': '', 'vod_remarks': '', 'vod_actor': '', 'vod_director': ''
}
# 提取分类
genres = data('.sgeneros a')
if genres:
vod['type_name'] = ', '.join(g.text() for g in genres.items())
# 播放链接处理
play_options = data('#playeroptions ul li, .dooplay_player_option')
if play_options:
play_links = self._extract_play_options(play_options, first_id)
else:
season_links = data('.seasons-list a, .season-item a, .se-c a, .se-a a, .seasons a')
play_links = self.getSeasonEpisodes(season_links) if season_links else [f"播放${first_id}"]
vod['vod_play_from'] = '4K影视'
vod['vod_play_url'] = '#'.join(play_links)
return {'list': [vod]}
except Exception as e:
return {'list': []}
def _extract_play_options(self, play_options, first_id):
"""提取播放选项"""
play_links = []
for option in play_options.items():
title = option('.title, span.title').text().strip() or '播放'
server = option('.server, span.server').text().strip()
if server:
title = f"{title}-{server}"
data_post = option.attr('data-post')
data_nume = option.attr('data-nume')
data_type = option.attr('data-type')
if data_post and data_nume:
play_url = f"{first_id}?post={data_post}&nume={data_nume}&type={data_type}"
play_links.append(f"{title}${play_url}")
return play_links
def searchContent(self, key, quick, pg="1"):
try:
search_url = f"{self.host}/xssearch?s={key}"
if pg != "1":
search_url += f"&p={pg}"
data = self.getpq(self.fetch(search_url, headers=self.headers).text)
raw_results = self.getVideoList(data)
filtered_results = self.filterSearchResults(raw_results, key)
return {'list': filtered_results, 'page': int(pg)}
except Exception as e:
return {'list': [], 'page': int(pg)}
def playerContent(self, flag, id, vipFlags):
try:
# 解析播放参数
data_post = data_nume = data_type = None
if '?' in id:
base_url, params = id.split('?', 1)
param_dict = dict(param.split('=', 1) for param in params.split('&') if '=' in param)
data_post = param_dict.get('post')
data_nume = param_dict.get('nume')
data_type = param_dict.get('type')
# API调用
if data_post and data_nume:
try:
api_url = f"{self.host}/wp-json/dooplayer/v1/post/{data_post}"
api_response = self.fetch(api_url, headers=self.headers,
params={'type': data_type or 'movie', 'source': data_nume})
if api_response.status_code == 200:
api_data = api_response.json()
if 'embed_url' in api_data:
embed_url = api_data['embed_url']
parse_flag = 0 if any(ext in embed_url.lower() for ext in ['.m3u8', '.mp4', '.flv', '.avi']) else 1
return {'parse': parse_flag, 'url': embed_url, 'header': self.headers}
except:
pass
# 页面解析回退
page_url = base_url if '?' in id else id
data = self.getpq(self.fetch(page_url, headers=self.headers).text)
# 查找播放源
iframe = data('iframe.metaframe, .dooplay_player iframe, .player iframe').attr('src')
if iframe:
iframe = self._normalize_url(iframe)
parse_flag = 0 if any(ext in iframe.lower() for ext in ['.m3u8', '.mp4', '.flv']) else 1
return {'parse': parse_flag, 'url': iframe, 'header': self.headers}
video_src = self._normalize_url(data('video source, video').attr('src'))
if video_src:
return {'parse': 0, 'url': video_src, 'header': self.headers}
return {'parse': 1, 'url': page_url, 'header': self.headers}
except Exception as e:
return {'parse': 1, 'url': id, 'header': self.headers}
def localProxy(self, param):
pass
def liveContent(self, url):
pass
def getHomeList(self, data):
"""获取首页推荐列表"""
videos = []
items = data('article, .module .content .items .item, .movies-list article')
for item in items.items():
video_info = self._extract_video_basic(item)
if video_info:
videos.append(video_info)
return videos
def getVideoList(self, data):
"""获取视频列表"""
videos = []
items = data('article, .items article, .content article, .search-results article')
for item in items.items():
video_info = self._extract_video_basic(item)
if video_info:
videos.append(video_info)
return videos
def extractVideoInfo(self, item):
"""兼容性方法已优化为_extract_video_basic"""
return self._extract_video_basic(item)
def getpq(self, text):
"""创建PyQuery对象"""
try:
return pq(text)
except:
try:
return pq(text.encode('utf-8'))
except:
return pq('')
def filterSearchResults(self, results, search_key):
"""过滤和排序搜索结果"""
if not results or not search_key:
return results
search_key_lower = search_key.lower().strip()
search_words = search_key_lower.split()
scored_results = []
for result in results:
title = result.get('vod_name', '').lower()
# 计算相关性分数
if search_key_lower == title:
score = 100
elif search_key_lower in title:
score = 80
elif title.startswith(search_key_lower):
score = 70
elif all(word in title for word in search_words):
score = 60
else:
word_matches = sum(1 for word in search_words if word in title)
if word_matches > 0:
score = 30 + (word_matches * 10)
else:
continue
# 内容类型加分
if '' in search_key_lower and 'tvshows' in result.get('vod_id', ''):
score += 5
elif '电影' in search_key_lower and 'movies' in result.get('vod_id', ''):
score += 5
scored_results.append((score, result))
# 排序和过滤
scored_results.sort(key=lambda x: x[0], reverse=True)
min_score = 30 if len(search_words) > 1 else 40
filtered = [result for score, result in scored_results if score >= min_score]
# 如果结果太少,放宽标准
if len(filtered) < 3 and len(scored_results) > 3:
filtered = [result for score, result in scored_results[:10]]
return filtered
def filterTVShowsOnly(self, video_list):
"""过滤电视剧分类中的电影内容"""
if not video_list:
return video_list
filtered_videos = []
movie_keywords = ['/movies/', '/movie/', 'Movie', '电影']
tvshow_keywords = ['/tvshows/', '/tvshow/', '/seasons/', 'TV', '', '', '']
for video in video_list:
vod_id = video.get('vod_id', '')
vod_name = video.get('vod_name', '')
vod_remarks = video.get('vod_remarks', '')
# 检查是否是电影
is_movie = any(keyword in vod_id for keyword in movie_keywords[:3])
if is_movie:
continue
# 检查是否是电视剧
is_tvshow = (any(keyword in vod_id for keyword in tvshow_keywords[:3]) or
any(keyword in vod_name + vod_remarks for keyword in tvshow_keywords[3:]))
if is_tvshow or not is_movie:
filtered_videos.append(video)
return filtered_videos
def getSeasonEpisodes(self, season_links):
"""获取电视剧每个季的集数信息"""
play_links = []
try:
for season in season_links.items():
season_title = season.text().strip() or '第1季'
season_url = self._normalize_url(season.attr('href'))
if not season_url:
continue
try:
season_resp = self.fetch(season_url, headers=self.headers)
if season_resp.status_code == 200:
season_data = self.getpq(season_resp.text)
episode_count = self._get_episode_count(season_data, season_resp.text)
# 限制集数范围(提高上限以支持长篇动漫)
episode_count = min(max(episode_count, 1), 500)
# 生成播放链接
if episode_count == 1:
play_links.append(f"{season_title}${season_url}")
else:
clean_title = season_title.split('已完結')[0].split('更新')[0].strip()
for ep_num in range(1, episode_count + 1):
episode_title = f"{clean_title}{ep_num}"
episode_url = f"{season_url}?ep={ep_num}"
play_links.append(f"{episode_title}${episode_url}")
else:
play_links.append(f"{season_title}${season_url}")
except Exception:
play_links.append(f"{season_title}${season_url}")
except Exception:
pass
return play_links

97
py/APPV2.py Normal file
View File

@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import sys
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
'''
example:
{
"key": "py_appV2",
"name": "xxx",
"type": 3,
"searchable": 1,
"quickSearch": 1,
"filterable": 1,
"api": "./py/APPV2.py",
"ext": "http://cmsyt.lyyytv.cn"
}
'''
self.host=extend
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
headers = {
'User-Agent': 'okhttp/4.12.0',
}
def homeContent(self, filter):
data = self.fetch(f"{self.host}//api.php/app/nav?token=",headers=self.headers).json()
keys = ["class", "area", "lang", "year", "letter", "by", "sort"]
filters = {}
classes = []
for item in data['list']:
has_non_empty_field = False
jsontype_extend = item["type_extend"]
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
for key in keys:
if key in jsontype_extend and jsontype_extend[key].strip() != "":
has_non_empty_field = True
break
if has_non_empty_field:
filters[str(item["type_id"])] = []
for dkey in jsontype_extend:
if dkey in keys and jsontype_extend[dkey].strip() != "":
values = jsontype_extend[dkey].split(",")
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
value.strip() != ""]
filters[str(item["type_id"])].append({"key": dkey, "name": dkey, "value": value_array})
result = {}
result["class"] = classes
result["filters"] = filters
return result
def homeVideoContent(self):
data=self.fetch(f"{self.host}/api.php/app/index_video?token=",headers=self.headers).json()
videos=[]
for item in data['list']:videos.extend(item['vlist'])
return {'list':videos}
def categoryContent(self, tid, pg, filter, extend):
params = {'tid':tid,'class':extend.get('class',''),'area':extend.get('area',''),'lang':extend.get('lang',''),'year':extend.get('year',''),'limit':'18','pg':pg}
data=self.fetch(f"{self.host}/api.php/app/video",params=params,headers=self.headers).json()
return data
def detailContent(self, ids):
data=self.fetch(f"{self.host}/api.php/app/video_detail?id={ids[0]}",headers=self.headers).json()
return {'list':[data['data']]}
def searchContent(self, key, quick, pg="1"):
data=self.fetch(f"{self.host}/api.php/app/search?text={key}&pg={pg}",headers=self.headers).json()
videos=data['list']
for item in data['list']:
item.pop('type', None)
return {'list':videos,'page':pg}
def playerContent(self, flag, id, vipFlags):
return {'jx':1,'playUrl':'','parse': 1, 'url': id, 'header': self.headers}
def localProxy(self, param):
pass

190
py/UVod.py Normal file
View File

@@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
import sys,json,time,base64,random,string,hashlib
from urllib.parse import urlencode,quote
from base.spider import Spider
from Crypto.Cipher import AES,PKCS1_v1_5
from Crypto.PublicKey import RSA
from Crypto.Util.Padding import pad,unpad
class Spider(Spider):
def __init__(self):
super().__init__()
self.base_url = 'https://api-h5.uvod.tv'; self.web_url = 'https://www.uvod.tv'; self.token = ''; self._iv = b"abcdefghijklmnop"
self._client_private = """-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJ4FBai1Y6my4+fc
8AD5tyYzxgN8Q7M/PuFv+8i1Xje8ElXYVwzvYd1y/cNxwgW4RX0tDy9ya562V33x
6SyNr29DU6XytOeOlOkxt3gd5169K4iFaJ0l0wA4koMTcCAYVxC9B4+zzS5djYmF
MuRGfYgKYNH99vfY7BZjdAY68ty5AgMBAAECgYB1rbvHJj5wVF7Rf4Hk2BMDCi9+
zP4F8SW88Y6KrDbcPt1QvOonIea56jb9ZCxf4hkt3W6foRBwg86oZo2FtoZcpCJ+
rFqUM2/wyV4CuzlL0+rNNSq7bga7d7UVld4hQYOCffSMifyF5rCFNH1py/4Dvswm
pi5qljf+dPLSlxXl2QJBAMzPJ/QPAwcf5K5nngQtbZCD3nqDFpRixXH4aUAIZcDz
S1RNsHrT61mEwZ/thQC2BUJTQNpGOfgh5Ecd1MnURwsCQQDFhAFfmvK7svkygoKX
t55ARNZy9nmme0StMOfdb4Q2UdJjfw8+zQNtKFOM7VhB7ijHcfFuGsE7UeXBe20n
g/XLAkEAv9SoT2hgJaQxxUk4MCF8pgddstJlq8Z3uTA7JMa4x+kZfXTm/6TOo6I8
2VbXZLsYYe8op0lvsoHMFvBSBljV0QJBAKhxyoYRa98dZB5qZRskciaXTlge0WJk
kA4vvh3/o757izRlQMgrKTfng1GVfIZFqKtnBiIDWTXQw2N9cnqXtH8CQAx+CD5t
l1iT0cMdjvlMg2two3SnpOjpo7gALgumIDHAmsVWhocLtcrnJI032VQSUkNnLq9z
EIfmHDz0TPTNHBQ=
-----END PRIVATE KEY-----
"""
self._client_public = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeBQWotWOpsuPn3PAA+bcmM8YD
fEOzPz7hb/vItV43vBJV2FcM72Hdcv3DccIFuEV9LQ8vcmuetld98eksja9vQ1Ol
8rTnjpTpMbd4HedevSuIhWidJdMAOJKDE3AgGFcQvQePs80uXY2JhTLkRn2ICmDR
/fb32OwWY3QGOvLcuQIDAQAB
-----END PUBLIC KEY-----
"""
self._server_public = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeBQWotWOpsuPn3PAA+bcmM8YD
fEOzPz7hb/vItV43vBJV2FcM72Hdcv3DccIFuEV9LQ8vcmuetld98eksja9vQ1Ol
8rTnjpTpMbd4HedevSuIhWidJdMAOJKDE3AgGFcQvQePs80uXY2JhTLkRn2ICmDR
/fb32OwWY3QGOvLcuQIDAQAB
-----END PUBLIC KEY-----
"""
def getName(self): return "UVOD"
def init(self, extend=""):
try: cfg = json.loads(extend) if isinstance(extend, str) and extend.strip().startswith('{') else extend if isinstance(extend, dict) else {}
except Exception: cfg = {}
self.base_url = cfg.get('base_url', self.base_url); self.token = cfg.get('token', self.token)
return self.homeContent(False)
def isVideoFormat(self, url): return any(x in url.lower() for x in ['.m3u8', '.mp4']) if url else False
def manualVideoCheck(self): return False
def destroy(self): pass
def _random_key(self, n=32):
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for _ in range(n))
def _encrypt(self, plain_text: str) -> str:
aes_key = self._random_key(32).encode('utf-8')
cipher = AES.new(aes_key, AES.MODE_CBC, iv=self._iv)
ct_b64 = base64.b64encode(cipher.encrypt(pad(plain_text.encode('utf-8'), AES.block_size))).decode('utf-8')
rsa_pub = RSA.import_key(self._server_public); rsa_cipher = PKCS1_v1_5.new(rsa_pub)
rsa_b64 = base64.b64encode(rsa_cipher.encrypt(aes_key)).decode('utf-8')
return f"{ct_b64}.{rsa_b64}"
def _decrypt(self, enc_text: str) -> str:
try:
parts = enc_text.split('.'); ct_b64, rsa_b64 = parts
rsa_priv = RSA.import_key(self._client_private)
aes_key = PKCS1_v1_5.new(rsa_priv).decrypt(base64.b64decode(rsa_b64), None)
cipher = AES.new(aes_key, AES.MODE_CBC, iv=self._iv)
pt = unpad(cipher.decrypt(base64.b64decode(ct_b64)), AES.block_size)
return pt.decode('utf-8', 'ignore')
except Exception: return enc_text
def _build_headers(self, path: str, payload: dict):
ts = str(int(time.time() * 1000)); token = self.token or ''
if path == '/video/latest':
parent_id = payload.get('parent_category_id', 101); text = f"-parent_category_id={parent_id}-{ts}"
elif path == '/video/list':
keyword = payload.get('keyword')
if keyword: keyword = quote(str(keyword), safe='').lower(); text = f"-keyword={keyword}&need_fragment=1&page=1&pagesize=42&sort_type=asc-{ts}"
else: page = payload.get('page', 1); pagesize = payload.get('pagesize', 42); parent_id = payload.get('parent_category_id', ''); text = f"-page={page}&pagesize={pagesize}&parent_category_id={parent_id}&sort_type=asc-{ts}"
elif path == '/video/info': text = f"-id={payload.get('id', '')}-{ts}"
elif path == '/video/source': quality = payload.get('quality', ''); fragment_id = payload.get('video_fragment_id', ''); video_id = payload.get('video_id', ''); text = f"-quality={quality}&video_fragment_id={fragment_id}&video_id={video_id}-{ts}"
else: filtered = {k: v for k, v in (payload or {}).items() if v not in (0, '0', '', False, None)}; query = urlencode(sorted(filtered.items()), doseq=True).lower(); text = f"{token}-{query}-{ts}"
sig = hashlib.md5(text.encode('utf-8')).hexdigest()
return {'Content-Type': 'application/json', 'X-TOKEN': token, 'X-TIMESTAMP': ts, 'X-SIGNATURE': sig, 'Origin': self.web_url, 'Referer': self.web_url + '/', 'Accept': '*/*', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'}
def _post_api(self, path: str, payload: dict):
url = self.base_url.rstrip('/') + path
try:
body = self._encrypt(json.dumps(payload, ensure_ascii=False)); headers = self._build_headers(path, payload)
rsp = self.post(url, data=body, headers=headers, timeout=15)
if rsp.status_code != 200 or not rsp.text: return None
txt = rsp.text.strip(); obj = None
try: dec = self._decrypt(txt); obj = json.loads(dec)
except:
try: obj = json.loads(txt)
except: pass
if isinstance(obj, dict) and obj.get('error') == 0: return obj.get('data')
return None
except Exception: return None
def homeContent(self, filter):
data = self._post_api('/video/category', {}); lst = (data.get('list') or data.get('category') or []) if isinstance(data, dict) else (data or []); classes = []
for it in lst:
cid = it.get('id') or it.get('category_id') or it.get('value'); name = it.get('name') or it.get('label') or it.get('title')
if cid and name: classes.append({'type_name': str(name), 'type_id': str(cid)})
if not classes: classes = [{'type_name': '电影', 'type_id': '100'}, {'type_name': '电视剧', 'type_id': '101'}, {'type_name': '综艺', 'type_id': '102'}, {'type_name': '动漫', 'type_id': '103'}, {'type_name': '体育', 'type_id': '104'}, {'type_name': '纪录片', 'type_id': '105'}, {'type_name': '粤台专区', 'type_id': '106'}]
return {'class': classes}
def homeVideoContent(self):
data = self._post_api('/video/latest', {'parent_category_id': 101})
if isinstance(data, dict): lst = data.get('video_latest_list') or data.get('list') or data.get('rows') or data.get('items') or []
elif isinstance(data, list): lst = data
else: lst = []
videos = []
for k in lst:
vid = k.get('id') or k.get('video_id') or k.get('videoId')
if vid: videos.append({'vod_id': str(vid), 'vod_name': k.get('title') or k.get('name') or '', 'vod_pic': k.get('poster') or k.get('cover') or k.get('pic') or '', 'vod_remarks': k.get('score') or k.get('remarks') or ''})
return {'list': videos}
def categoryContent(self, tid, pg, filter, extend):
page = int(pg) if str(pg).isdigit() else 1
payload = {'parent_category_id': str(tid), 'category_id': None, 'language': None, 'year': None, 'region': None, 'state': None, 'keyword': '', 'paid': None, 'page': page, 'pagesize': 42, 'sort_field': '', 'sort_type': 'asc'}
if isinstance(extend, dict):
for k in ['category_id', 'year', 'region', 'state', 'keyword']:
if extend.get(k): payload[k] = extend[k]
data = self._post_api('/video/list', payload)
if isinstance(data, dict): lst = data.get('video_list') or data.get('list') or data.get('rows') or data.get('items') or []; total = data.get('total', 999999)
elif isinstance(data, list): lst = data; total = 999999
else: lst, total = [], 0
videos = []
for k in lst:
vid = k.get('id') or k.get('video_id') or k.get('videoId')
if vid: videos.append({'vod_id': str(vid), 'vod_name': k.get('title') or k.get('name') or '', 'vod_pic': k.get('poster') or k.get('cover') or k.get('pic') or '', 'vod_remarks': k.get('score') or ''})
return {'list': videos, 'page': page, 'pagecount': 9999, 'limit': 24, 'total': total}
def detailContent(self, ids):
vid = ids[0]; data = self._post_api('/video/info', {'id': vid}) or {}; video_info = data.get('video', {}) if isinstance(data, dict) else {}; fragments = data.get('video_fragment_list', []) if isinstance(data, dict) else []; play_urls = []
if fragments:
for fragment in fragments:
name = fragment.get('symbol', '播放'); fragment_id = fragment.get('id', ''); qualities = fragment.get('qualities', [])
if fragment_id and qualities:
max_quality = max(qualities) if qualities else 4
play_urls.append(f"{name}${vid}|{fragment_id}|[{max_quality}]")
if not play_urls: play_urls.append(f"播放${vid}")
vod = {'vod_id': str(vid), 'vod_name': video_info.get('title') or video_info.get('name') or '', 'vod_pic': video_info.get('poster') or video_info.get('cover') or video_info.get('pic') or '', 'vod_year': video_info.get('year') or '', 'vod_remarks': video_info.get('duration') or '', 'vod_content': video_info.get('description') or video_info.get('desc') or '', 'vod_play_from': '优汁🍑源', 'vod_play_url': '#'.join(play_urls) + '$$$'}
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
page = int(pg) if str(pg).isdigit() else 1
payload = {'parent_category_id': None, 'category_id': None, 'language': None, 'year': None, 'region': None, 'state': None, 'keyword': key, 'paid': None, 'page': page, 'pagesize': 42, 'sort_field': '', 'sort_type': 'asc', 'need_fragment': 1}
data = self._post_api('/video/list', payload)
if isinstance(data, dict): lst = data.get('video_list') or data.get('list') or data.get('rows') or data.get('items') or []
elif isinstance(data, list): lst = data
else: lst = []
videos = []
for k in lst:
vid = k.get('id') or k.get('video_id') or k.get('videoId')
if vid: videos.append({'vod_id': str(vid), 'vod_name': k.get('title') or k.get('name') or '', 'vod_pic': k.get('poster') or k.get('cover') or k.get('pic') or '', 'vod_remarks': k.get('score') or ''})
return {'list': videos}
def _extract_first_media(self, obj):
if not obj: return None
if isinstance(obj, str): s = obj.strip(); return s if self.isVideoFormat(s) else None
if isinstance(obj, (dict, list)):
for v in (obj.values() if isinstance(obj, dict) else obj):
r = self._extract_first_media(v)
if r: return r
return None
def playerContent(self, flag, id, vipFlags):
parts = id.split('|'); video_id = parts[0]
if len(parts) >= 3:
fragment_id = parts[1]; qualities_str = parts[2].strip('[]').replace(' ', ''); qualities = [q.strip() for q in qualities_str.split(',') if q.strip()]; quality = qualities[0] if qualities else '4'
payload = {'video_id': video_id, 'video_fragment_id': int(fragment_id) if str(fragment_id).isdigit() else fragment_id, 'quality': int(quality) if str(quality).isdigit() else quality, 'seek': None}
else: payload = {'video_id': video_id, 'video_fragment_id': 1, 'quality': 4, 'seek': None}
data = self._post_api('/video/source', payload) or {}
url = (data.get('video', {}).get('url', '') or data.get('url') or data.get('playUrl') or data.get('play_url') or self._extract_first_media(data) or '')
if not url: return {'parse': 1, 'url': id}
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36', 'Referer': self.web_url + '/', 'Origin': self.web_url}
return {'parse': 0, 'url': url, 'header': headers}
def localProxy(self, param): return None

349
py/adult/4K数毛.py Normal file
View File

@@ -0,0 +1,349 @@
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 ""
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": '💗FullHD💗',
"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

435
py/adult/911.py Normal file
View File

@@ -0,0 +1,435 @@
# -*- coding: utf-8 -*-
import json
import random
import re
import sys
import threading
import time
import requests
from base64 import b64decode, b64encode
from urllib.parse import urlparse, urljoin
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from bs4 import BeautifulSoup
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend="{}"):
config = json.loads(extend)
self.domin = config.get('site', "https://911blw.com")
self.proxies = config.get('proxy', {}) or {}
self.plp = config.get('plp', '')
self.backup_urls = ["https://hlj.fun", "https://911bl16.com"]
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.domin] + self.backup_urls)
self.headers.update({'Origin': self.host, 'Referer': f"{self.host}/"})
# 缓存主机信息
self.getcnh()
def getName(self):
return "911爆料网"
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeContent(self, filter):
result = {}
classes = []
# 分类列表根据911爆料网的实际分类
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
# 首页推荐内容
html = self.fetch_page(f"{self.host}/")
if html:
soup = BeautifulSoup(html, 'html.parser')
articles = soup.select('article, .post-item, .article-item')
result['list'] = self.getlist(articles)
else:
result['list'] = []
return result
def homeVideoContent(self):
# 首页推荐视频
html = self.fetch_page(f"{self.host}/category/jrgb/1/")
videos = self.extract_content(html, f"{self.host}/category/jrgb/1/")
return {'list': videos}
def categoryContent(self, tid, pg, filter, extend):
if '@folder' in tid:
# 文件夹类型内容
id = tid.replace('@folder', '')
videos = self.getfod(id)
else:
# 普通分类内容
url = f"{self.host}{tid}{pg}/" if pg != "1" else f"{self.host}{tid}"
html = self.fetch_page(url)
if html:
soup = BeautifulSoup(html, 'html.parser')
articles = soup.select('article, .post-item, .article-item, ul.row li')
videos = self.getlist(articles, tid)
else:
videos = []
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]}"
html = self.fetch_page(url)
if not html:
return {'list': []}
soup = BeautifulSoup(html, 'html.parser')
vod = {'vod_play_from': '911爆料网'}
try:
# 提取标签信息
clist = []
tags = soup.select('.tags .keywords a, .tagcloud a, a[rel="tag"]')
for tag in tags:
title = tag.get_text(strip=True)
href = tag.get('href', '')
if href and title:
clist.append('[a=cr:' + json.dumps({'id': href, 'name': title}) + '/]' + title + '[/a]')
vod['vod_content'] = '点击展开↓↓↓\n'+' '.join(clist) if clist else soup.select_one('.post-content, .entry-content').get_text(strip=True)[:200] + '...'
except:
title_elem = soup.select_one('h1, .post-title, .entry-title')
vod['vod_content'] = title_elem.get_text(strip=True) if title_elem else "无简介"
try:
# 提取播放列表类似51吸瓜的dplayer方式
plist = []
# 方式1检查dplayer
dplayers = soup.select('.dplayer, [data-config]')
for c, player in enumerate(dplayers, start=1):
config_str = player.get('data-config', '{}')
try:
config = json.loads(config_str)
if 'video' in config and 'url' in config['video']:
plist.append(f"视频{c}${config['video']['url']}")
except:
pass
# 方式2检查视频标签
if not plist:
video_tags = soup.select('video source, video[src]')
for c, video in enumerate(video_tags, start=1):
src = video.get('src') or ''
if src:
plist.append(f"视频{c}${src}")
# 方式3检查iframe
if not plist:
iframes = soup.select('iframe[src]')
for c, iframe in enumerate(iframes, start=1):
src = iframe.get('src', '')
if src and ('player' in src or 'video' in src):
plist.append(f"视频{c}${src}")
# 方式4从脚本中提取
if not plist:
scripts = soup.find_all('script')
for script in scripts:
if script.string:
# 查找m3u8、mp4等视频链接
video_matches = re.findall(r'(https?://[^\s"\']*\.(?:m3u8|mp4|flv|ts|mkv)[^\s"\']*)', script.string)
for c, match in enumerate(video_matches, start=1):
plist.append(f"视频{c}${match}")
vod['vod_play_url'] = '#'.join(plist) if plist else f"请检查页面,可能没有视频${url}"
except Exception as e:
print(f"详情页解析错误: {e}")
vod['vod_play_url'] = f"解析错误${url}"
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
url = f"{self.host}/search/{key}/{pg}/"
html = self.fetch_page(url)
if html:
soup = BeautifulSoup(html, 'html.parser')
articles = soup.select('article, .post-item, .article-item, ul.row li')
videos = self.getlist(articles)
else:
videos = []
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
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)
# 检查是否需要AES解密根据文件类型判断
if url.endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')):
# 普通图片直接返回
return [200, res.headers.get('Content-Type'), res.content]
else:
# 加密内容进行AES解密
return [200, res.headers.get('Content-Type'), self.aesimg(res.content)]
except Exception as e:
print(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:
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 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 fetch_page(self, url, use_backup=False):
original_url = url
if use_backup:
for backup in self.backup_urls:
test_url = url.replace(self.domin, backup)
try:
time.sleep(1)
res = requests.get(test_url, headers=self.headers, proxies=self.proxies, 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
try:
time.sleep(1)
res = requests.get(original_url, headers=self.headers, proxies=self.proxies, timeout=10)
res.raise_for_status()
res.encoding = "utf-8"
text = res.text
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 getcnh(self):
try:
html = self.fetch_page(f"{self.host}/about.html")
if html:
soup = BeautifulSoup(html, 'html.parser')
link = soup.select_one('a[href]')
if link:
url = link.get('href')
parsed_url = urlparse(url)
host = parsed_url.scheme + "://" + parsed_url.netloc
self.setCache('host_911blw', host)
except Exception as e:
print(f"获取主机信息错误: {str(e)}")
def host_late(self, url_list):
if not url_list:
return self.domin
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 url_list:
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 getfod(self, id):
url = f"{self.host}{id}"
html = self.fetch_page(url)
if not html:
return []
soup = BeautifulSoup(html, 'html.parser')
videos = []
# 查找文件夹内容
content = soup.select_one('.post-content, .entry-content')
if content:
# 移除不需要的元素
for elem in content.select('.txt-apps, .line, blockquote, .tags, .content-tabs'):
elem.decompose()
# 提取标题和链接
headings = content.select('h2, h3, h4')
paragraphs = content.select('p')
for i, heading in enumerate(headings):
title = heading.get_text(strip=True)
if i < len(paragraphs):
link = paragraphs[i].select_one('a')
if link:
videos.append({
'vod_id': link.get('href', ''),
'vod_name': link.get_text(strip=True),
'vod_pic': f"{self.getProxyUrl()}&url={self.e64(link.get('data-img', ''))}",
'vod_remarks': title
})
return videos
def getlist(self, articles, tid=''):
videos = []
is_folder = '/mrdg' in tid
for article in articles:
try:
# 标题
title_elem = article.select_one('h2, h3, .headline, .title, a[title]')
name = title_elem.get_text(strip=True) if title_elem else ""
# 链接
link_elem = article.select_one('a')
href = link_elem.get('href', '') if link_elem else ""
# 日期/备注
date_elem = article.select_one('time, .date, .published')
remarks = date_elem.get_text(strip=True) if date_elem else ""
# 图片(使用吸瓜的方式)
pic = None
script_elem = article.select_one('script')
if script_elem and script_elem.string:
base64_match = re.search(r'base64,[\'"]?([A-Za-z0-9+/=]+)[\'"]?', script_elem.string)
if base64_match:
encoded_url = base64_match.group(1)
pic = f"{self.getProxyUrl()}&url={self.e64(encoded_url)}"
if not pic:
img_elem = article.select_one('img[data-xkrkllgl]')
if img_elem and img_elem.get('data-xkrkllgl'):
encoded_url = img_elem.get('data-xkrkllgl')
pic = f"{self.getProxyUrl()}&url={self.e64(encoded_url)}"
if not pic:
img_elem = article.select_one('img')
if img_elem:
for attr in ["data-lazy-src", "data-original", "data-src", "src"]:
pic = img_elem.get(attr)
if pic:
pic = urljoin(self.host, pic)
break
if name and href:
videos.append({
'vod_id': f"{href}{'@folder' if is_folder else ''}",
'vod_name': name.replace('\n', ' '),
'vod_pic': pic,
'vod_remarks': remarks,
'vod_tag': 'folder' if is_folder else '',
'style': {"type": "rect", "ratio": 1.33}
})
except Exception as e:
print(f"列表项解析错误: {e}")
continue
return videos
if __name__ == "__main__":
spider = Spider()
spider.init('{"site": "https://911blw.com"}')
# 测试首页
result = spider.homeContent({})
print(f"首页分类: {len(result['class'])}")
print(f"首页内容: {len(result['list'])}")
# 测试分类
result = spider.categoryContent("/category/jrgb/", "1", False, {})
print(f"分类内容: {len(result['list'])}")
# 测试搜索
result = spider.searchContent("测试", False, "1")
print(f"搜索结果: {len(result['list'])}")

84
py/adult/91RB.py Normal file
View File

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

118
py/adult/cam4.py Normal file
View File

@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
import json
import time
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "Cam4直播"
def init(self, extend=""):
self.base = "https://zh.cam4.com"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
return self
def homeContent(self, filter):
classes = [
{"type_id": "all", "type_name": "全部"},
{"type_id": "female", "type_name": "女性"},
{"type_id": "male", "type_name": "男性"},
{"type_id": "couples", "type_name": "情侣"},
{"type_id": "shemale", "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

214
py/adult/javxbb.py Normal file
View File

@@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-
#author 🍑
import json
import re
import os
import sys
import requests
from requests.exceptions import RequestException
try:
from pyquery import PyQuery as pq
except Exception:
pq = None
from base.spider import Spider
class Spider(Spider):
name = 'Javbobo'
host = 'https://javbobo.com'
def init(self, extend=""):
try:
self.extend = json.loads(extend) if extend else {}
except Exception:
self.extend = {}
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/142.0',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': f'{self.host}/',
'Origin': self.host,
'Connection': 'keep-alive',
}
self.session = requests.Session()
self.session.headers.update(self.headers)
def getName(self):
return self.name
def isVideoFormat(self, url):
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):
result = {}
try:
cateManual = [
{'type_name': '日本有碼', 'type_id': '47'},
{'type_name': '日本無碼', 'type_id': '48'},
{'type_name': '國產AV', 'type_id': '49'},
{'type_name': '網紅主播', 'type_id': '50'},
]
result['class'] = cateManual
result['filters'] = {}
except Exception:
pass
return result
def homeVideoContent(self):
return self.categoryContent('', '1', False, {})
def categoryContent(self, tid, pg, filter, extend):
pg = str(pg)
result = {'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999, 'list': []}
try:
url = self.host
if tid:
if str(tid).startswith('http'):
url = str(tid)
if pg != '1': url = f"{url}{'&' if '?' in url else '?'}page={pg}"
elif str(tid).startswith('/'):
url = f"{self.host}{tid}"
if pg != '1': url = f"{url}{'&' if '?' in url else '?'}page={pg}"
else:
url = f"{self.host}/vod/index.html?type_id={tid}"
if pg != '1': url = f"{self.host}/vod/index.html?page={pg}&type_id={tid}"
resp = self.session.get(url, timeout=30)
resp.raise_for_status()
if pq is None: raise RuntimeError('PyQuery 未安装,无法解析列表页面')
doc = pq(resp.text)
def _parse_list(doc):
vlist = []
seen = set()
for a in doc('a[href*="/vod/player.html"]').items():
href = a.attr('href') or ''
if not href: continue
full = href if href.startswith('http') else f"{self.host}{href}"
m = re.search(r'[?&]id=(\d+)', full)
if not m: continue
vid = m.group(1)
if vid in seen: continue
seen.add(vid)
img_el = a('img')
title = img_el.attr('alt') or a.attr('title') or (a.text() or '').strip()
if not title:
li = a.parents('li').eq(0)
title = li.find('h1,h2,h3').text().strip() if li else ''
if not title: title = f"视频{vid}"
img = img_el.attr('src') or img_el.attr('data-src') or ''
if img and not img.startswith('http'): img = f"{self.host}{img}"
vlist.append({
'vod_id': full, 'vod_name': title, 'vod_pic': img, 'vod_remarks': '',
'style': {'ratio': 1.33, 'type': 'rect'}
})
if len(vlist) >= 90: break
return vlist
result['list'] = _parse_list(doc)
page_numbers = []
for a in doc('a[href*="/vod/index.html?page="]').items():
t = (a.text() or '').strip()
if t.isdigit(): page_numbers.append(int(t))
if page_numbers: result['pagecount'] = max(page_numbers)
except Exception:
result['list'] = []
return result
def detailContent(self, ids):
try:
url = ids[0] if isinstance(ids, list) else str(ids)
if not url: return {'list': []}
if not url.startswith('http'): url = f"{self.host}/vod/player.html?id={url}"
resp = self.session.get(url, timeout=30)
resp.raise_for_status()
html = resp.text
if pq is None: raise RuntimeError('PyQuery 未安装,无法解析详情页面')
doc = pq(html)
title = doc('meta[property="og:title"]').attr('content') or doc('h1').text().strip() or 'Javbobo 视频'
vod_pic = doc('meta[property="og:image"]').attr('content') or ''
if not vod_pic:
img_el = doc('img').eq(0)
vod_pic = img_el.attr('src') or img_el.attr('data-src') or ''
if vod_pic and not vod_pic.startswith('http'): vod_pic = f"{self.host}{vod_pic}"
line_id = None
m = re.search(r"lineId\s*=\s*Number\('?(\d+)'?\)", html)
if m: line_id = m.group(1)
if not line_id:
m = re.search(r"var\s+Iyplayer\s*=\s*\{[^}]*id:(\d+)", html)
if m: line_id = m.group(1)
play_id = line_id or url
vod = {
'vod_name': title, 'vod_pic': vod_pic, 'vod_content': '',
'vod_play_from': 'Javbobo', 'vod_play_url': f'正片${play_id}'
}
return {'list': [vod]}
except Exception:
return {'list': []}
def searchContent(self, key, quick, pg="1"):
try:
params = {'wd': key}
url = f"{self.host}/index.html"
resp = self.session.get(url, params=params, timeout=30)
resp.raise_for_status()
if pq is None: raise RuntimeError('PyQuery 未安装,无法解析搜索页面')
doc = pq(resp.text)
vlist = []
seen = set()
for a in doc('a[href*="/vod/player.html"]').items():
href = a.attr('href') or ''
if not href: continue
full = href if href.startswith('http') else f"{self.host}{href}"
m = re.search(r'[?&]id=(\d+)', full)
if not m: continue
vid = m.group(1)
if vid in seen: continue
seen.add(vid)
img_el = a('img')
title = img_el.attr('alt') or a.attr('title') or (a.text() or '').strip()
img = img_el.attr('src') or img_el.attr('data-src') or ''
if img and not img.startswith('http'): img = f"{self.host}{img}"
vlist.append({
'vod_id': full, 'vod_name': title or f'视频{vid}', 'vod_pic': img,
'vod_remarks': '', 'style': {'ratio': 1.33, 'type': 'rect'}
})
if len(vlist) >= 60: break
return {'list': vlist, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
except Exception:
return {'list': []}
def playerContent(self, flag, id, vipFlags):
try:
line_id = None
sid = str(id or '')
if re.fullmatch(r'\d+', sid):
line_id = sid
elif sid.startswith('http'):
if self.isVideoFormat(sid):
headers = {'User-Agent': self.headers['User-Agent'], 'Referer': f'{self.host}/'}
return {'parse': 0, 'url': sid, 'header': headers}
html = self.session.get(sid, timeout=30).text
m = re.search(r"lineId\s*=\s*Number\('?(\d+)'?\)", html)
if m: line_id = m.group(1)
if not line_id:
m = re.search(r"var\s+Iyplayer\s*=\s*\{[^}]*id:(\d+)", html)
if m: line_id = m.group(1)
else:
if sid.startswith('/'): page_url = f"{self.host}{sid}"
else: page_url = f"{self.host}/vod/player.html?id={sid}"
html = self.session.get(page_url, timeout=30).text
m = re.search(r"lineId\s*=\s*Number\('?(\d+)'?\)", html)
if m: line_id = m.group(1)
if not line_id:
m = re.search(r"var\s+Iyplayer\s*=\s*\{[^}]*id:(\d+)", html)
if m: line_id = m.group(1)
if not line_id: raise ValueError('未能获取到播放线路ID(lineId)')
api = f"{self.host}/openapi/playline/{line_id}"
r = self.session.get(api, timeout=30)
txt = r.text.strip()
j = None
try: j = r.json()
except Exception: j = None
if isinstance(j, str):
try: j = json.loads(j)
except Exception: j = None
if not isinstance(j, dict):
try: j = json.loads(txt)
except Exception: j = {}
m3u8_url = ''
if isinstance(j, dict): m3u8_url = j.get('info', {}).get('file') or j.get('file') or ''
headers = {'User-Agent': self.headers['User-Agent'], 'Referer': f'{self.host}/'}
return {'parse': 0, 'url': m3u8_url, 'header': headers}
except Exception:
return {'parse': 0, 'url': '', 'header': {}}

329
py/adult/javxx.py Normal file
View File

@@ -0,0 +1,329 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import gzip
import html
import json
import re
import sys
import base64
from base64 import b64decode
from urllib.parse import unquote, urlparse
import requests
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.proxies = config.get('proxy', {})
self.plp = config.get('plp', '')
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host = 'https://javxx.com'
contr = 'cn'
conh = f'{host}/{contr}'
headers = {
'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,en;q=0.8',
'referer': f'{conh}/',
'sec-ch-ua': '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',
}
gcate = 'H4sIAAAAAAAAA6tWejan4dm0DUpWCkp5qeVKOkrPm9e+nL4CxM/ILwHygfIv9k8E8YtSk1PzwELTFzxf0AgSKs0DChXnF6WmwIWfbW55OWcTqqRuTmpiNljN8427n3asBsmmp+YVpRaDtO2Z8nTiDJBQYnIJUKgYLPq0Y9uTvXOeTm0DSeQCdReBRJ9vBmqfDhIqTi3KhGhf0P587T6QUElierFSLQCk4MAf0gAAAA=='
flts = 'H4sIAAAAAAAAA23QwYrCMBAG4FeRnH0CX0WKBDJiMRpoY0WkIOtFXLQU1IoEFFHWw4qHPazgii/TRPctNKK1Ro/zz8cM/PkmKkMD5TLIZQ5HWVTFFUiNHqY1PeebyNOxAxSwCwWCOWitMxmEcttW0VKJKfKzN4kJAfLk1O9OdmemKzF+B8f2+j9aPVacEdwoeDbU3TuJd93LgdPXx1F8PmAdoEwNqTaBDFemrLAqL72hSnReqcuvDkgCRUsGkfqenw59AxaxxxybP9uRuFjkW5reai7alIOTKjoJzKoxpUnDvWG8bcnlj/obyHCcKi95JxeTeN9LEcu3zoYr9GndAQAA'
actft = 'H4sIAAAAAAAAA22UTUsbURSG/0qYtQMxZvIhIvidxI/oVpEy6GiCmpFkEhEpVBcqikYprV2kG6GkhYK2XRbxzziT+C88c2/OnLnnunznec47zJ3LWTsydpxDYzRhVJzqdsUzhoyavecoD1r2bjN8snZktEIwPJI0h0fSoRqL/vW33p9/xsehyLLgcZ4sETUrDcNp6pJRt2A4TV0yapYFwxZ1yahbMGxRl4yalYHhDHXJqFswnKEuGTUrC8NZ6pJRt2A4S10yalYOhnPUJaNuwXCOumTUrDwM56lLRrTWQ29wNzaa+7GLIRO/FRPYM9F7+hV8f6D3TCKZ5GQKyRQn00imOZlBMsPJLJJZTuaQzHFSQFLgpIikyEkJSYmTeSTznCwgWeBkEckiJ0tIljgpIylzsoxkmZMVJCucrCJZRRL/9/a2E/v3MvF/H14cLBlLpJL+32OqTyXNVHTJRFCxZaaiYREUDMuFVo0IKrZM2jEiKBjWCS0XEVRsmbRVRFAwLBBaJyIoGHZCPpoeT2TkZ8fPruHW4xt1EPnpCTyo8buf/ZsreseG26x5CPvd09f72+DL4+tZmxTP3bQPP7SqzkEDxZf/F8Hdj373pNe5JPHAcXZ2mRk8tP3bn9zcc2te5R016JzrasMTnrMZiZ1Pfvsu+H3ff75m4pbdcutVT3W/dsAND279DSxD8pmOBgAA'
def homeContent(self, filter):
data = self.getpq(requests.get(f"{self.conh}", headers=self.headers, proxies=self.proxies).text)
result = {}
cate = self.ungzip(self.gcate)
classes = []
filters = {}
for k, j in cate.items():
classes.append({
'type_name': k,
'type_id': j
})
if j == 'actresses':
fts = self.ungzip(self.actft)
else:
fts = self.ungzip(self.flts)
filters[j] = fts
result['class'] = classes
result['filters'] = filters
result['list'] = self.getvl(data('.vid-items .item'))
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
videos = []
if tid in ['genres', 'makers', 'series', 'tags']:
gggg = tid if tid == 'series' else tid[:-1]
pagecount = 1
data = self.getpq(requests.get(f"{self.conh}/{tid}", headers=self.headers, proxies=self.proxies).text)
for i in data(f'.term-items.{gggg} .item').items():
videos.append({
'vod_id': i('a').attr('href'),
'vod_name': i('h2').text(),
'vod_remarks': i('.meta').text(),
'vod_tag': 'folder',
'style': {"type": "rect", "ratio": 2}
})
elif tid == 'actresses':
params = {
'height': extend.get('height'),
"cup": extend.get('cup'),
"sort": extend.get('sort'),
'age': extend.get('age'),
"page": pg
}
c_params = {k: v for k, v in params.items() if v}
data = self.getpq(
requests.get(f"{self.conh}/{tid}", headers=self.headers, params=c_params, proxies=self.proxies).text)
pagecount = self.getpgc(data('ul.pagination li').eq(-1))
for i in data('.chanel-items .item').items():
i = i('.main')
videos.append({
'vod_id': i('.info a').attr('href'),
'vod_name': i('.info h2').text(),
'vod_pic': i('.avatar img').attr('src'),
'vod_year': i('.meta div div').eq(-1).text(),
'vod_remarks': i('.meta div div').eq(0).text(),
'vod_tag': 'folder',
'style': {"type": "oval", "ratio": 0.75}
})
else:
tid = tid.split('_click')[0].replace(f"/{self.contr}/", "")
params = {
"filter": extend.get('filter'),
"sort": extend.get('sort'),
"page": pg
}
c_params = {k: v for k, v in params.items() if v}
data = self.getpq(
requests.get(f"{self.conh}/{tid}", params=c_params, headers=self.headers, proxies=self.proxies).text)
videos = self.getvl(data('.vid-items .item'))
pagecount = self.getpgc(data('ul.pagination li').eq(-1))
result = {}
result['list'] = videos
result['page'] = pg
result['pagecount'] = pagecount
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
data = self.getpq(requests.get(f"{self.host}{ids[0]}", headers=self.headers, proxies=self.proxies).text)
dv = data('#video-details')
pnpn = {
'老僧酿酒、名妓读经': f"{data('#video-info h1').text()}${data('#video-files div').attr('data-url')}",
'书生玩剑': '#'.join(
[f"{i('.info .title span').eq(-1).text()}$_gggb_{i('.info .title').attr('href')}" for i in
data('.main .vid-items .item').items()]),
'将军作文': '#'.join([f"{i('.info .title span').eq(-1).text()}$_gggb_{i('.info .title').attr('href')}" for i in
data('.vid-items.side .item').items()])
}
n, p = [], []
for k, v in pnpn.items():
if v:
n.append(k)
p.append(v)
vod = {
'vod_content': dv('.content').text(),
'vod_play_from': '$$$'.join(n),
'vod_play_url': '$$$'.join(p)
}
a, b, c, d = [], [], [], []
for i in dv('.meta div').items():
if re.search(r'发布日期', i('label').text()):
vod['vod_year'] = i('span').text()
elif re.search(r'演员', i('label').text()):
a.extend(['[a=cr:' + json.dumps(
{'id': f"{j.attr('href')}_click", 'name': j.text()}) + '/]' + j.text() + '[/a]' for j in
i('a').items()])
elif re.search(r'制作商|系列', i('label').text()):
b.extend(['[a=cr:' + json.dumps(
{'id': f"{j.attr('href')}_click", 'name': j.text()}) + '/]' + j.text() + '[/a]' for j in
i('a').items()])
elif re.search(r'标签', i('label').text()):
c.extend(['[a=cr:' + json.dumps(
{'id': f"{j.attr('href')}_click", 'name': j.text()}) + '/]' + j.text() + '[/a]' for j in
i('a').items()])
elif re.search(r'类别', i('label').text()):
d.extend(['[a=cr:' + json.dumps(
{'id': f"{j.attr('href')}_click", 'name': j.text()}) + '/]' + j.text() + '[/a]' for j in
i('a').items()])
vod.update({'vod_actor': ' '.join(a), 'vod_director': ' '.join(b), 'vod_remarks': ' '.join(c),
'vod_content': ' '.join(d) + '\n' + vod['vod_content']})
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
params = {'keyword': key, 'page': pg}
data = self.getpq(
requests.get(f"{self.conh}/search", headers=self.headers, params=params, proxies=self.proxies).text)
return {'list': self.getvl(data('.vid-items .item')), 'page': pg}
def playerContent(self, flag, id, vipFlags):
# 处理跳转标识,获取初始加密地址
if id.startswith('_gggb_'):
data = self.getpq(
requests.get(f"{self.host}{id.replace('_gggb_', '')}", headers=self.headers).text)
id = data('#video-files div').attr('data-url')
# 解密初始URL
url = self.de_url(id)
parsed_url = urlparse(url)
durl = parsed_url.scheme + "://" + parsed_url.netloc
video_id = parsed_url.path.split('/')[-1]
# 生成加密的token
tkid = self.encrypt_video_id(video_id)
data_url = f"{durl}/stream?token={tkid}"
# 请求视频流数据
response = requests.get(data_url, timeout=10)
data = response.json()
# 解密媒体数据
media = data["result"]["media"]
decrypted_media = self.decrypt_media(media)
decrypted_data = json.loads(decrypted_media)
playeurl = decrypted_data["stream"]
# 构建请求头并返回结果
headers = {
'user-agent': self.headers['user-agent'],
'origin': durl,
'referer': f"{durl}/"
}
return {'parse': 0, 'url': playeurl, 'header': headers}
def encrypt_video_id(self, video_id, key=None):
"""使用指定密钥对视频ID进行XOR加密并Base64编码"""
if key is None:
key = "kBxSj373GhC18iOc" # 默认密钥
# XOR加密
key_bytes = key.encode('utf-8')
encrypted_bytes = []
for i, char in enumerate(video_id):
key_byte = key_bytes[i % len(key_bytes)]
encrypted_byte = ord(char) ^ key_byte
encrypted_bytes.append(encrypted_byte)
# Base64编码
encrypted_base64 = base64.b64encode(bytes(encrypted_bytes)).decode('utf-8')
return encrypted_base64
def decrypt_media(self, encrypted_media, key="kBxSj373GhC18iOc"):
"""使用指定密钥解密媒体数据"""
# Base64解码
encrypted_bytes = base64.b64decode(encrypted_media)
# XOR解密
key_bytes = key.encode('utf-8')
decrypted_chars = []
for i, byte in enumerate(encrypted_bytes):
key_byte = key_bytes[i % len(key_bytes)]
decrypted_char = byte ^ key_byte
decrypted_chars.append(chr(decrypted_char))
# 组合成字符串并URL解码
decrypted_text = ''.join(decrypted_chars)
url_decoded_text = unquote(decrypted_text)
return url_decoded_text
def localProxy(self, param):
pass
def liveContent(self, url):
pass
def getvl(self, data):
videos = []
for i in data.items():
img = i('.img')
imgurl = img('.image img').attr('src')
if imgurl:
imgurl = imgurl.replace("/s360/", "/s1080/")
videos.append({
'vod_id': img('a').attr('href'),
'vod_name': i('.info .title').text(),
'vod_pic': imgurl,
'vod_year': i('.info .meta div').eq(-1).text(),
'vod_remarks': i('.duration').text(),
'style': {"type": "rect", "ratio": 1.33}
})
return videos
def de_url(self, encoded_str):
decoded = b64decode(encoded_str).decode()
key = "G9zhUyphqPWZGWzZ" # 更新为第一个密钥
result = []
for i, char in enumerate(decoded):
key_char = key[i % len(key)]
decrypted_char = chr(ord(char) ^ ord(key_char))
result.append(decrypted_char)
return unquote(''.join(result))
def getpgc(self, data):
try:
if data:
if data('a'):
return int(data('a').attr('href').split('page=')[-1])
else:
return int(data.text())
else:
raise Exception("获取页数失败")
except:
return 1
def p_qjs(self, js_code):
try:
from com.whl.quickjs.wrapper import QuickJSContext
ctx = QuickJSContext.create()
jctx = ctx.evaluate(js_code)
code = jctx.strip().split('const posterUrl', 1)[0].split('{', 1)[-1]
result = ctx.evaluate(f"{code}\nJSON.stringify(media)")
ctx.destroy()
return json.loads(result)
except Exception as e:
self.log(f"执行失败: {e}")
return []
def ungzip(self, data):
result = gzip.decompress(b64decode(data)).decode()
return json.loads(result)
def getpq(self, data):
try:
return pq(data)
except Exception as e:
print(f"{str(e)}")
return pq(data.encode('utf-8'))

669
py/adult/香蕉.py Normal file
View File

@@ -0,0 +1,669 @@
# coding=utf-8
#!/usr/bin/python
import sys
sys.path.append('..')
from base.spider import Spider
import json
import time
import urllib.parse
import re
import requests
from lxml import etree
from urllib.parse import urljoin
class Spider(Spider):
def getName(self):
return "苹果视频"
def init(self, extend=""):
self.host = "https://618041.xyz"
self.api_host = "https://h5.xxoo168.org"
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',
'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
}
# 定义特殊分区ID列表包含所有需要特殊处理的分类
self.special_categories = ['13', '14', '33', '53', '32', '52', '9']
self.log(f"苹果视频爬虫初始化完成,主站: {self.host}")
def html(self, content):
"""将HTML内容转换为可查询的对象"""
try:
return etree.HTML(content)
except:
self.log("HTML解析失败")
return None
def regStr(self, pattern, string, index=1):
"""正则表达式提取字符串"""
try:
match = re.search(pattern, string, re.IGNORECASE)
if match and len(match.groups()) >= index:
return match.group(index)
except:
pass
return ""
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def homeContent(self, filter):
"""获取首页内容和分类"""
result = {}
# 只保留指定的分类
classes = [
{'type_id': '618041.xyz_1', 'type_name': '全部视频'},
{'type_id': '618041.xyz_13', 'type_name': '香蕉精品'},
{'type_id': '618041.xyz_22', 'type_name': '制服诱惑'},
{'type_id': '618041.xyz_6', 'type_name': '国产视频'},
{'type_id': '618041.xyz_8', 'type_name': '清纯少女'},
{'type_id': '618041.xyz_9', 'type_name': '辣妹大奶'},
{'type_id': '618041.xyz_10', 'type_name': '女同专属'},
{'type_id': '618041.xyz_11', 'type_name': '素人出演'},
{'type_id': '618041.xyz_12', 'type_name': '角色扮演'},
{'type_id': '618041.xyz_20', 'type_name': '人妻熟女'},
{'type_id': '618041.xyz_23', 'type_name': '日韩剧情'},
{'type_id': '618041.xyz_21', 'type_name': '经典伦理'},
{'type_id': '618041.xyz_7', 'type_name': '成人动漫'},
{'type_id': '618041.xyz_14', 'type_name': '精品二区'},
{'type_id': '618041.xyz_53', 'type_name': '动漫中字'},
{'type_id': '618041.xyz_52', 'type_name': '日本无码'},
{'type_id': '618041.xyz_33', 'type_name': '中文字幕'},
{'type_id': '618041.xyz_32', 'type_name': '国产自拍'}
]
result['class'] = classes
try:
rsp = self.fetch(self.host, headers=self.headers)
doc = self.html(rsp.text)
videos = self._get_videos(doc, limit=20)
result['list'] = videos
except Exception as e:
self.log(f"首页获取出错: {str(e)}")
result['list'] = []
return result
def homeVideoContent(self):
"""分类定义 - 兼容性方法"""
return {
'class': [
{'type_id': '618041.xyz_1', 'type_name': '全部视频'},
{'type_id': '618041.xyz_13', 'type_name': '香蕉精品'},
{'type_id': '618041.xyz_22', 'type_name': '制服诱惑'},
{'type_id': '618041.xyz_6', 'type_name': '国产视频'},
{'type_id': '618041.xyz_8', 'type_name': '清纯少女'},
{'type_id': '618041.xyz_9', 'type_name': '辣妹大奶'},
{'type_id': '618041.xyz_10', 'type_name': '女同专属'},
{'type_id': '618041.xyz_11', 'type_name': '素人出演'},
{'type_id': '618041.xyz_12', 'type_name': '角色扮演'},
{'type_id': '618041.xyz_20', 'type_name': '人妻熟女'},
{'type_id': '618041.xyz_23', 'type_name': '日韩剧情'},
{'type_id': '618041.xyz_21', 'type_name': '经典伦理'},
{'type_id': '618041.xyz_7', 'type_name': '成人动漫'},
{'type_id': '618041.xyz_14', 'type_name': '精品二区'},
{'type_id': '618041.xyz_53', 'type_name': '动漫中字'},
{'type_id': '618041.xyz_52', 'type_name': '日本无码'},
{'type_id': '618041.xyz_33', 'type_name': '中文字幕'},
{'type_id': '618041.xyz_32', 'type_name': '国产自拍'}
]
}
def categoryContent(self, tid, pg, filter, extend):
"""分类内容 - 修改为使用固定页数设置"""
try:
domain, type_id = tid.split('_')
url = f"https://{domain}/index.php/vod/type/id/{type_id}.html"
if pg and pg != '1':
url = url.replace('.html', f'/page/{pg}.html')
self.log(f"访问分类URL: {url}")
rsp = self.fetch(url, headers=self.headers)
doc = self.html(rsp.text)
# 在这里将 type_id 传递给 _get_videos 方法
videos = self._get_videos(doc, category_id=type_id, limit=20)
# 使用固定页数设置,而不是尝试从页面解析
pagecount = 999
total = 19980
return {
'list': videos,
'page': int(pg),
'pagecount': pagecount,
'limit': 20,
'total': total
}
except Exception as e:
self.log(f"分类内容获取出错: {str(e)}")
return {'list': []}
def searchContent(self, key, quick, pg="1"):
"""搜索功能 - 完全修复版"""
try:
# 构造搜索URL
search_url = f"{self.host}/index.php/vod/type/id/1/wd/{urllib.parse.quote(key)}/page/{pg}.html"
self.log(f"搜索URL: {search_url}")
# 发送请求
rsp = self.fetch(search_url, headers=self.headers)
if not rsp or rsp.status_code != 200:
self.log("搜索请求失败")
return {'list': []}
# 解析HTML
doc = self.html(rsp.text)
if not doc:
self.log("搜索页面解析失败")
return {'list': []}
# 提取搜索结果
videos = self._get_videos(doc, limit=20)
# 尝试从页面提取分页信息
pagecount = 5 # 默认值
total = 100 # 默认值
# 尝试从分页元素中提取真实的分页信息
page_elements = doc.xpath('//div[@class="mypage"]/a')
if page_elements and len(page_elements) > 0:
try:
# 查找尾页链接
last_page = None
for elem in page_elements:
href = elem.xpath('./@href')[0]
if '尾页' in elem.text or 'page/' in href:
last_page = href
break
if last_page:
# 从尾页URL中提取页码
page_match = re.search(r'/page/(\d+)\.html', last_page)
if page_match:
pagecount = int(page_match.group(1))
total = pagecount * 20 # 估算总数
except:
pass
return {
'list': videos,
'page': int(pg),
'pagecount': pagecount,
'limit': 20,
'total': total
}
except Exception as e:
self.log(f"搜索出错: {str(e)}")
return {'list': []}
def detailContent(self, ids):
"""详情页面 - 特别处理特殊分区的链接"""
try:
vid = ids[0]
# 检查是否是特殊分区的链接
if vid.startswith('special_'):
# 解析特殊分区ID格式: special_{category_id}_{video_id}_{encoded_url}
parts = vid.split('_')
if len(parts) >= 4:
category_id = parts[1]
video_id = parts[2]
encoded_url = '_'.join(parts[3:])
play_url = urllib.parse.unquote(encoded_url)
self.log(f"特殊分区视频,直接使用链接: {play_url}")
# 从播放链接中提取视频URL
parsed_url = urllib.parse.urlparse(play_url)
query_params = urllib.parse.parse_qs(parsed_url.query)
video_url = query_params.get('v', [''])[0]
pic_url = query_params.get('b', [''])[0]
title_encrypted = query_params.get('m', [''])[0]
# 解码标题
title = self._decrypt_title(title_encrypted)
return {
'list': [{
'vod_id': vid,
'vod_name': title,
'vod_pic': pic_url,
'vod_remarks': '',
'vod_year': '',
'vod_play_from': '直接播放',
'vod_play_url': f"第1集${play_url}"
}]
}
# 常规处理
if '_' in vid and len(vid.split('_')) > 2:
domain, category_id, video_id = vid.split('_')
else:
domain, video_id = vid.split('_')
detail_url = f"https://{domain}/index.php/vod/detail/id/{video_id}.html"
self.log(f"访问详情URL: {detail_url}")
rsp = self.fetch(detail_url, headers=self.headers)
doc = self.html(rsp.text)
video_info = self._get_detail(doc, rsp.text, vid)
return {'list': [video_info]} if video_info else {'list': []}
except Exception as e:
self.log(f"详情获取出错: {str(e)}")
return {'list': []}
def playerContent(self, flag, id, vipFlags):
"""播放链接 - 特别处理特殊分区的链接"""
try:
self.log(f"获取播放链接: flag={flag}, id={id}")
# 检查是否是特殊分区的链接
if id.startswith('special_'):
# 解析特殊分区ID格式: special_{category_id}_{video_id}_{encoded_url}
parts = id.split('_')
if len(parts) >= 4:
category_id = parts[1]
video_id = parts[2]
encoded_url = '_'.join(parts[3:])
play_url = urllib.parse.unquote(encoded_url)
self.log(f"特殊分区视频,直接使用链接: {play_url}")
# 从播放链接中提取视频URL
parsed_url = urllib.parse.urlparse(play_url)
query_params = urllib.parse.parse_qs(parsed_url.query)
video_url = query_params.get('v', [''])[0]
if video_url:
# 确保URL是完整的
if video_url.startswith('//'):
video_url = 'https:' + video_url
elif not video_url.startswith('http'):
video_url = urljoin(self.host, video_url)
self.log(f"从特殊链接中提取到视频地址: {video_url}")
return {'parse': 0, 'playUrl': '', 'url': video_url}
# 检查传入的ID是否为完整URL如果是则直接解析
if id.startswith('http'):
self.log("ID 是一个完整URL直接解析参数")
parsed_url = urllib.parse.urlparse(id)
query_params = urllib.parse.parse_qs(parsed_url.query)
# 尝试获取视频参数
video_url = query_params.get('v', [''])[0]
if not video_url:
# 尝试其他可能的参数名
for key in query_params:
if key in ['url', 'src', 'file']:
video_url = query_params[key][0]
break
if video_url:
# 解码可能的URL编码
video_url = urllib.parse.unquote(video_url)
# 确保URL是完整的
if video_url.startswith('//'):
video_url = 'https:' + video_url
elif not video_url.startswith('http'):
# 尝试添加基本域名
video_url = urljoin(self.host, video_url)
self.log(f"从 URL 参数中提取到视频地址: {video_url}")
return {'parse': 0, 'playUrl': '', 'url': video_url}
else:
self.log("URL 中没有找到视频参数,尝试从页面提取")
# 请求页面并提取视频链接
rsp = self.fetch(id, headers=self.headers)
if rsp and rsp.status_code == 200:
video_url = self._extract_direct_video_url(rsp.text)
if video_url:
self.log(f"从页面提取到视频地址: {video_url}")
return {'parse': 0, 'playUrl': '', 'url': video_url}
self.log("无法从页面提取视频链接返回原始URL")
return {'parse': 1, 'playUrl': '', 'url': id}
# 从新的 id 格式中提取视频ID和分类ID
if id.count('_') >= 2:
parts = id.split('_')
video_id = parts[-1]
category_id = parts[1]
else:
video_id = id.split('_')[-1]
category_id = ''
self.log(f"视频ID: {video_id}, 分类ID: {category_id}")
# 对于特殊分类,使用直接解析播放页面的方式
if category_id in self.special_categories:
self.log("特殊分类,尝试从详情页提取直接播放链接")
# 构造播放页面URL
play_page_url = f"{self.host}/index.php/vod/play/id/{video_id}.html"
# 请求播放页面
rsp = self.fetch(play_page_url, headers=self.headers)
if rsp and rsp.status_code == 200:
# 从页面提取视频链接
video_url = self._extract_direct_video_url(rsp.text)
if video_url:
self.log(f"从播放页面提取到视频地址: {video_url}")
return {'parse': 0, 'playUrl': '', 'url': video_url}
# 如果提取失败回退到API方式
self.log("从播放页面提取失败尝试API方式")
return self._get_video_by_api(id, video_id)
else:
# 其他分类使用API方式
self.log("使用API方式获取视频地址")
return self._get_video_by_api(id, video_id)
except Exception as e:
self.log(f"播放链接获取出错: {str(e)}")
if '_' in id:
domain, play_id = id.split('_')
play_url = f"https://{domain}/html/kkyd.html?m={play_id}"
else:
play_url = f"{self.host}/html/kkyd.html?m={id}"
return {'parse': 1, 'playUrl': '', 'url': play_url}
def _get_video_by_api(self, id, video_id):
"""通过API获取视频地址"""
try:
api_url = f"{self.api_host}/api/v2/vod/reqplay/{video_id}"
self.log(f"请求API获取视频地址: {api_url}")
api_headers = self.headers.copy()
api_headers.update({
'Referer': f"{self.host}/",
'Origin': self.host,
'X-Requested-With': 'XMLHttpRequest'
})
api_response = self.fetch(api_url, headers=api_headers)
if api_response and api_response.status_code == 200:
data = api_response.json()
self.log(f"API响应: {data}")
if data.get('retcode') == 3:
video_url = data.get('data', {}).get('httpurl_preview', '')
else:
video_url = data.get('data', {}).get('httpurl', '')
if video_url:
video_url = video_url.replace('?300', '')
self.log(f"从API获取到视频地址: {video_url}")
return {'parse': 0, 'playUrl': '', 'url': video_url}
else:
self.log("API响应中没有找到视频地址")
else:
self.log(f"API请求失败状态码: {api_response.status_code if api_response else '无响应'}")
if '_' in id:
domain, play_id = id.split('_')
play_url = f"https://{domain}/html/kkyd.html?m={play_id}"
else:
play_url = f"{self.host}/html/kkyd.html?m={id}"
self.log(f"API请求失败回退到播放页面: {play_url}")
return {'parse': 1, 'playUrl': '', 'url': play_url}
except Exception as e:
self.log(f"API方式获取视频出错: {str(e)}")
if '_' in id:
domain, play_id = id.split('_')
play_url = f"https://{domain}/html/kkyd.html?m={play_id}"
else:
play_url = f"{self.host}/html/kkyd.html?m={id}"
return {'parse': 1, 'playUrl': '', 'url': play_url}
def _extract_direct_video_url(self, html_content):
"""从HTML内容中提取直接播放链接 (优化版)"""
try:
# 首先尝试提取明显的视频链接
patterns = [
r'v=([^&]+\.(?:m3u8|mp4))',
r'"url"\s*:\s*["\']([^"\']+\.(?:mp4|m3u8))["\']',
r'src\s*=\s*["\']([^"\']+\.(?:mp4|m3u8))["\']',
r'http[^\s<>"\'?]+\.(?:mp4|m3u8)'
]
for pattern in patterns:
matches = re.findall(pattern, html_content, re.IGNORECASE)
for match in matches:
if isinstance(match, tuple):
match = match[0]
extracted_url = match.replace('\\', '')
extracted_url = urllib.parse.unquote(extracted_url)
if extracted_url.startswith('//'):
extracted_url = 'https:' + extracted_url
elif extracted_url.startswith('http'):
return extracted_url
return None
except Exception as e:
self.log(f"提取直接播放URL出错: {str(e)}")
return None
def _get_videos(self, doc, category_id=None, limit=None):
"""获取影片列表 - 根据实际网站结构"""
try:
videos = []
elements = doc.xpath('//a[@class="vodbox"]')
self.log(f"找到 {len(elements)} 个vodbox元素")
for elem in elements:
video = self._extract_video(elem, category_id)
if video:
videos.append(video)
return videos[:limit] if limit and videos else videos
except Exception as e:
self.log(f"获取影片列表出错: {str(e)}")
return []
def _extract_video(self, element, category_id=None):
"""提取影片信息 - 特别处理特殊分区的链接"""
try:
link = element.xpath('./@href')[0]
if link.startswith('/'):
link = self.host + link
# 检查是否是特殊分区的链接
is_special_link = 'ar-kk.html' in link or 'ar.html' in link
# 对于特殊分区直接使用链接本身作为ID
if is_special_link and category_id in self.special_categories:
# 提取链接中的参数
parsed_url = urllib.parse.urlparse(link)
query_params = urllib.parse.parse_qs(parsed_url.query)
# 获取视频ID从v参数中提取
video_url = query_params.get('v', [''])[0]
if video_url:
# 从视频URL中提取ID
video_id_match = re.search(r'/([a-f0-9-]+)/video\.m3u8', video_url)
if video_id_match:
video_id = video_id_match.group(1)
else:
# 如果没有匹配到,使用哈希值
video_id = str(hash(link) % 1000000)
else:
video_id = str(hash(link) % 1000000)
# 对于特殊分区保留完整的链接作为vod_id的一部分
final_vod_id = f"special_{category_id}_{video_id}_{urllib.parse.quote(link)}"
else:
# 常规处理
vod_id = self.regStr(r'm=(\d+)', link)
if not vod_id:
vod_id = str(hash(link) % 1000000)
final_vod_id = f"618041.xyz_{vod_id}"
if category_id:
final_vod_id = f"618041.xyz_{category_id}_{vod_id}"
# 提取标题
title_elem = element.xpath('.//p[@class="km-script"]/text()')
if not title_elem:
title_elem = element.xpath('.//p[contains(@class, "script")]/text()')
if not title_elem:
title_elem = element.xpath('.//p/text()')
if not title_elem:
title_elem = element.xpath('.//h3/text()')
if not title_elem:
title_elem = element.xpath('.//h4/text()')
if not title_elem:
self.log(f"未找到标题元素,跳过该视频")
return None
title_encrypted = title_elem[0].strip()
title = self._decrypt_title(title_encrypted)
# 提取图片
pic_elem = element.xpath('.//img/@data-original')
if not pic_elem:
pic_elem = element.xpath('.//img/@src')
pic = pic_elem[0] if pic_elem else ''
if pic:
if pic.startswith('//'):
pic = 'https:' + pic
elif pic.startswith('/'):
pic = self.host + pic
return {
'vod_id': final_vod_id,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': '',
'vod_year': ''
}
except Exception as e:
self.log(f"提取影片信息出错: {str(e)}")
return None
def _decrypt_title(self, encrypted_text):
"""解密标题 - 使用网站的解密算法"""
try:
decrypted_chars = []
for char in encrypted_text:
code_point = ord(char)
decrypted_code = code_point ^ 128
decrypted_char = chr(decrypted_code)
decrypted_chars.append(decrypted_char)
decrypted_text = ''.join(decrypted_chars)
return decrypted_text
except Exception as e:
self.log(f"标题解密失败: {str(e)}")
return encrypted_text
def _get_detail(self, doc, html_content, vid):
"""获取详情信息 (优化版) - 修复播放源提取问题"""
try:
title = self._get_text(doc, ['//h1/text()', '//title/text()'])
pic = self._get_text(doc, ['//div[contains(@class,"dyimg")]//img/@src', '//img[contains(@class,"poster")]/@src'])
if pic and pic.startswith('/'):
pic = self.host + pic
desc = self._get_text(doc, ['//div[contains(@class,"yp_context")]/text()', '//div[contains(@class,"introduction")]//text()'])
actor = self._get_text(doc, ['//span[contains(text(),"主演")]/following-sibling::*/text()'])
director = self._get_text(doc, ['//span[contains(text(),"导演")]/following-sibling::*/text()'])
play_from = []
play_urls = []
# 使用更灵活的正则匹配来查找播放链接
player_link_patterns = [
re.compile(r'href="(.*?ar\.html.*?)"'),
re.compile(r'href="(.*?kkyd\.html.*?)"'),
re.compile(r'href="(.*?ar-kk\.html.*?)"')
]
player_links = []
for pattern in player_link_patterns:
matches = pattern.findall(html_content)
player_links.extend(matches)
if player_links:
episodes = []
for link in player_links:
full_url = urljoin(self.host, link)
episodes.append(f"第1集${full_url}")
if episodes:
play_from.append("默认播放源")
play_urls.append('#'.join(episodes))
if not play_from:
self.log("未找到播放源元素,无法定位播放源列表")
return {
'vod_id': vid,
'vod_name': title,
'vod_pic': pic,
'type_name': '',
'vod_year': '',
'vod_area': '',
'vod_remarks': '',
'vod_actor': actor,
'vod_director': director,
'vod_content': desc,
'vod_play_from': '默认播放源',
'vod_play_url': f"第1集${vid}"
}
return {
'vod_id': vid,
'vod_name': title,
'vod_pic': pic,
'type_name': '',
'vod_year': '',
'vod_area': '',
'vod_remarks': '',
'vod_actor': actor,
'vod_director': director,
'vod_content': desc,
'vod_play_from': '$$$'.join(play_from),
'vod_play_url': '$$$'.join(play_urls)
}
except Exception as e:
self.log(f"获取详情出错: {str(e)}")
return None
def _get_text(self, doc, selectors):
"""通用文本提取"""
for selector in selectors:
try:
texts = doc.xpath(selector)
for text in texts:
if text and text.strip():
return text.strip()
except:
continue
return ''
def log(self, message):
"""日志输出"""
print(f"[苹果视频] {message}")
def fetch(self, url, headers=None, method='GET', data=None, timeout=10):
"""网络请求"""
try:
if headers is None:
headers = self.headers
if method == 'GET':
response = requests.get(url, headers=headers, timeout=timeout, verify=False)
else:
response = requests.post(url, headers=headers, data=data, timeout=timeout, verify=False)
return response
except Exception as e:
self.log(f"网络请求失败: {url}, 错误: {str(e)}")
return None
# 注册爬虫
if __name__ == '__main__':
from base.spider import Spider as BaseSpider
BaseSpider.register(Spider())

275
py/redflix带字幕版.py Normal file
View File

@@ -0,0 +1,275 @@
# -*- coding: utf-8 -*-
# 🍑
import json
import re
import sys
import os
from pyquery import PyQuery as pq
from base.spider import Spider
class Spider(Spider):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="136", "Google Chrome";v="136"',
'origin': 'https://redflix.co',
'referer': 'https://redflix.co/',
}
def init(self, extend=""):
self.site = 'https://redflix.co'
self.chost, self.token = self.gettoken()
self.phost = 'https://image.tmdb.org/t/p/w500'
self.servers = {
'vidfast': 'https://vidfast.pro',
'vidrock': 'https://vidrock.net',
'vidlink': 'https://vidlink.pro',
'videasy': 'https://player.videasy.net',
}
self.server_order = ['vidfast', 'vidrock', 'vidlink', 'videasy']
self.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="136", "Google Chrome";v="136"',
'origin': self.site,
'referer': f'{self.site}/',
'accept': 'application/json'
})
pass
def getName(self):
return "Redflix"
def isVideoFormat(self, url):
return '.m3u8' in url or '.mp4' in url
def manualVideoCheck(self):
return True
def destroy(self):
pass
def homeContent(self, filter):
result = {}
cate = {
"电影": "movie",
"剧集": "tv"
}
classes = []
filters = {}
for k, j in cate.items():
classes.append({'type_name': k, 'type_id': j})
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
data = self.fetch(
f"{self.chost}/trending/all/day",
params={'api_key': self.token, 'language': 'en-US', 'page': 1},
headers=self.headers
).json()
return {'list': self.getlist(data.get('results', []))}
def categoryContent(self, tid, pg, filter, extend):
params = {'page': pg, 'api_key': self.token, 'language': 'en-US'}
data = self.fetch(f'{self.chost}/discover/{tid}', params=params, headers=self.headers).json()
result = {
'list': self.getlist(data.get('results', []), tid),
'page': pg,
'pagecount': 9999,
'limit': 90,
'total': 999999
}
return result
def detailContent(self, ids):
path = ids[0]
v = self.fetch(
f'{self.chost}{path}',
params={'api_key': self.token, 'language': 'en-US', 'append_to_response': 'videos'},
headers=self.headers
).json()
is_movie = '/movie/' in path
if is_movie:
play_str = f"{v.get('title') or v.get('name')}${path}"
else:
seasons = v.get('seasons') or []
play_items = [
f"{i.get('name')}${path}/{i.get('season_number')}/1" for i in seasons if i.get('season_number')
]
play_str = '#'.join(play_items) if play_items else f"{v.get('name')}${path}/1/1"
vod = {
'vod_name': v.get('title') or v.get('name'),
'vod_year': (v.get('release_date') or v.get('last_air_date') or '')[:4],
'vod_area': v.get('original_language') or '',
'vod_remarks': v.get('tagline') or '',
'vod_content': v.get('overview') or '',
'vod_play_from': 'Redflix',
'vod_play_url': play_str
}
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
data = self.fetch(
f'{self.chost}/search/multi',
params={'query': key, 'page': pg, 'api_key': self.token, 'language': 'en-US', 'include_adult': 'false'},
headers=self.headers
).json()
return {'list': self.getlist(data.get('results', [])), 'page': pg}
def playerContent(self, flag, id, vipFlags):
try:
media_type, tmdb_id, season, episode = self._parse_play_id(id)
s = season or '1'
e = episode or '1'
subs = []
def _map_lang(label: str) -> str:
name = (label or '').lower()
table = {
'english': 'en', 'arabic': 'ar', 'chinese': 'zh', 'zh': 'zh', '简体': 'zh-CN', '繁體': 'zh-TW',
'croatian': 'hr', 'czech': 'cs', 'danish': 'da', 'dutch': 'nl', 'finnish': 'fi', 'french': 'fr',
'german': 'de', 'greek': 'el', 'hungarian': 'hu', 'indonesian': 'id', 'italian': 'it',
'japanese': 'ja', 'korean': 'ko', 'norwegian': 'no', 'persian': 'fa', 'polish': 'pl',
'portuguese (br)': 'pt-BR', 'portuguese': 'pt', 'romanian': 'ro', 'russian': 'ru',
'serbian': 'sr', 'spanish': 'es', 'swedish': 'sv', 'turkish': 'tr', 'thai': 'th', 'vietnamese': 'vi'
}
if name in table:
return table[name]
for k, v in table.items():
if name.startswith(k) or k in name:
return v
return ''
try:
if media_type == 'tv':
sub_api = f"https://s.vdrk.site/subfetch.php?id={tmdb_id}&s={s}&e={e}"
else:
sub_api = f"https://s.vdrk.site/subfetch.php?id={tmdb_id}"
hdr = self.jxh().copy()
hdr.update({'referer': 'https://vidrock.net/'})
resp = self.fetch(sub_api, headers=hdr, timeout=10)
if resp is not None and resp.status_code == 200:
try:
items = resp.json()
except Exception:
items = json.loads(resp.text or '[]')
if (not items) and media_type == 'tv':
try:
resp2 = self.fetch(f"https://s.vdrk.site/subfetch.php?id={tmdb_id}", headers=hdr, timeout=10)
if resp2 is not None and resp2.status_code == 200:
try:
items = resp2.json()
except Exception:
items = json.loads(resp2.text or '[]')
except Exception:
pass
for it in items or []:
u = it.get('file') or it.get('url') or it.get('src')
name = it.get('label') or it.get('name') or 'Subtitle'
if not u:
continue
low = u.lower()
fmt = 'application/x-subrip' if ('srt' in low) else 'text/vtt'
subs.append({'url': u, 'name': name, 'lang': _map_lang(name), 'format': fmt})
except Exception as _:
pass
for sid in self.server_order:
domain = self.servers.get(sid)
if not domain:
continue
if media_type == 'movie':
embed = f"{domain}/movie/{tmdb_id}"
else:
if sid == 'vidfast':
embed = f"{domain}/tv/{tmdb_id}/{s}/{e}?autoNext=true&nextButton=false&title=true&poster=true&autoPlay=true"
elif sid == 'vidrock':
embed = f"{domain}/tv/{tmdb_id}/{s}/{e}?autoplay=true&autonext=true"
elif sid == 'vidlink':
params = "primaryColor=63b8bc&secondaryColor=a2a2a2&iconColor=eefdec&icons=default&player=default&title=true&poster=true&autoplay=true&nextbutton=true"
embed = f"{domain}/tv/{tmdb_id}/{s}/{e}?{params}"
elif sid == 'videasy':
embed = f"{domain}/tv/{tmdb_id}/{s}/{e}?nextEpisode=true&autoplayNextEpisode=true&episodeSelector=true&color=8B5CF6"
else:
embed = f"{domain}/embed/{'movie' if media_type=='movie' else 'tv'}/{tmdb_id}{'' if media_type=='movie' else f'/{s}/{e}'}"
return {'parse': 1, 'url': embed, 'header': self.jxh(), 'subs': subs}
fallback = f"{self.site}/{media_type}/{tmdb_id}/watch"
return {'parse': 1, 'url': fallback, 'header': self.jxh(), 'subs': subs}
except Exception as e:
self.log(f'Redflix playerContent error: {e}')
return {'parse': 1, 'url': f"{self.site}{id if id.startswith('/') else '/' + id}", 'header': self.jxh()}
def getlist(self, data, tid=''):
videos = []
for i in data or []:
media_type = tid or i.get('media_type')
if media_type not in ('movie', 'tv'):
continue
vid = i.get('id')
if not vid:
continue
name = i.get('title') or i.get('name') or ''
poster = i.get('backdrop_path') or i.get('poster_path') or ''
videos.append({
'vod_id': f"/{media_type}/{vid}",
'vod_name': name,
'vod_pic': f"{self.phost}{poster}",
'vod_remarks': ''
})
return videos
def jxh(self):
header = self.headers.copy()
header.update({'referer': f'{self.site}/', 'origin': self.site})
header.pop('authorization', None)
return header
def _parse_play_id(self, id_str):
m = re.match(r'^/(movie|tv)/(\d+)(?:/(\d+)/(\d+))?$', id_str or '')
if not m:
if '/movie/' in id_str:
return 'movie', re.findall(r'/movie/(\d+)', id_str)[0], None, None
elif '/tv/' in id_str:
parts = re.findall(r'/tv/(\d+)(?:/(\d+)/(\d+))?', id_str)[0]
return 'tv', parts[0], (parts[1] or '1') if len(parts) > 1 else '1', (parts[2] or '1') if len(parts) > 2 else '1'
else:
raise ValueError('Unrecognized play id')
media_type, tmdb_id, season, episode = m.groups()
return media_type, tmdb_id, season, episode
def gettoken(self):
hosts = [self.site]
paths = ['/', '/movies', '/tv-shows']
key_pattern = re.compile(r'TMDB_API_KEY\s*[:=]\s*[\"\']([A-Za-z0-9]+)[\"\']')
for host in hosts:
for path in paths:
try:
hdr = self.headers.copy()
hdr.update({'origin': host, 'referer': f'{host}/'})
html = self.fetch(f'{host}{path}', headers=hdr, timeout=10).text
mod = pq(html)('script[type="module"]').attr('src') or ''
if not mod:
continue
murl = mod if mod.startswith('http') else f'{host}{mod}'
mjs = self.fetch(murl, headers=hdr, timeout=10).text
m = key_pattern.search(mjs)
if m:
return 'https://api.themoviedb.org/3', m.group(1)
mw = re.search(r'player-watch-([\w-]+)\.js', mjs)
if mw:
pw = f"{host}/assets/player-watch-{mw.group(1)}.js"
pjs = self.fetch(pw, headers=hdr, timeout=10).text
m2 = key_pattern.search(pjs)
if m2:
return 'https://api.themoviedb.org/3', m2.group(1)
except Exception as e:
self.log(f'gettoken error: {e}')
continue
return 'https://api.themoviedb.org/3', '524c16f6e2a0a13c49ff7b99d27b5efb'

219
py/云速影视.py Normal file
View File

@@ -0,0 +1,219 @@
import re
import sys
from Crypto.Hash import MD5
sys.path.append("..")
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from urllib.parse import quote, urlparse
from base64 import b64encode, b64decode
import json
import time
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = self.gethost()
self.did=self.getdid()
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def action(self, action):
pass
def destroy(self):
pass
def homeContent(self, filter):
data = self.getdata("/api.php/getappapi.index/initV119")
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
"sort": "排序"}
filters = {}
classes = []
json_data = data["type_list"]
homedata = data["banner_list"][8:]
for item in json_data:
if item["type_name"] == "全部":
continue
has_non_empty_field = False
jsontype_extend = json.loads(item["type_extend"])
homedata.extend(item["recommend_list"])
jsontype_extend["sort"] = "最新,最热,最赞"
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
for key in dy:
if key in jsontype_extend and jsontype_extend[key].strip() != "":
has_non_empty_field = True
break
if has_non_empty_field:
filters[str(item["type_id"])] = []
for dkey in jsontype_extend:
if dkey in dy and jsontype_extend[dkey].strip() != "":
values = jsontype_extend[dkey].split(",")
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
value.strip() != ""]
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
result = {}
result["class"] = classes
result["filters"] = filters
result["list"] = homedata[1:]
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
"class": extend.get('class', '全部')}
result = {}
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
result["list"] = data["recommend_list"]
result["page"] = pg
result["pagecount"] = 9999
result["limit"] = 90
result["total"] = 999999
return result
def detailContent(self, ids):
body = f"vod_id={ids[0]}"
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
vod = data["vod"]
play = []
names = []
for itt in data["vod_play_list"]:
a = []
names.append(itt["player_info"]["show"])
for it in itt['urls']:
it['user_agent'] = itt["player_info"].get("user_agent")
it["parse"] = itt["player_info"].get("parse")
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
play.append("#".join(a))
vod["vod_play_from"] = "$$$".join(names)
vod["vod_play_url"] = "$$$".join(play)
result = {"list": [vod]}
return result
def searchContent(self, key, quick, pg="1"):
body = f"keywords={key}&type_id=0&page={pg}"
data = self.getdata("/api.php/getappapi.index/searchList", body)
result = {"list": data["search_list"], "page": pg}
return result
def playerContent(self, flag, id, vipFlags):
ids = json.loads(self.d64(id))
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
try:
if re.search(r'url=', ids['parse_api_url']):
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
url = data.get('url') or data['data'].get('url')
else:
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
url = json.loads(b)['url']
if 'error' in url: raise ValueError(f"解析失败: {url}")
p = 0
except Exception as e:
print('错误信息:', e)
url, p = ids['url'], 1
if re.search(r'\.jpg|\.png|\.jpeg', url):
url = self.Mproxy(url)
result = {}
result["parse"] = p
result["url"] = url
result["header"] = h
return result
def localProxy(self, param):
return self.Mlocal(param)
def gethost(self):
headers = {
'User-Agent': 'okhttp/3.14.9'
}
response = self.fetch('https://jingyu-1312635929.cos.ap-nanjing.myqcloud.com/1.json',headers=headers).text
return response.strip()
def getdid(self):
did=self.getCache('did')
if not did:
t = str(int(time.time()))
did = self.md5(t)
self.setCache('did', did)
return did
def aes(self, text, b=None):
key = b"4d83b87c4c5ea111"
cipher = AES.new(key, AES.MODE_CBC, key)
if b:
ct_bytes = cipher.encrypt(pad(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 pt.decode("utf-8")
def header(self):
t = str(int(time.time()))
header = {"Referer": self.host,
"User-Agent": "okhttp/3.14.9", "app-version-code": "300", "app-ui-mode": "light",
"app-api-verify-time": t, "app-user-device-id": self.did,
"app-api-verify-sign": self.aes(t, True),
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
return header
def getdata(self, path, data=None):
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
data1 = self.aes(vdata)
return json.loads(data1)
def Mproxy(self, url):
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=m3u8"
def Mlocal(self, param, header=None):
url = self.d64(param["url"])
ydata = self.fetch(url, headers=header, allow_redirects=False)
data = ydata.content.decode('utf-8')
if ydata.headers.get('Location'):
url = ydata.headers['Location']
data = self.fetch(url, headers=header).content.decode('utf-8')
parsed_url = urlparse(url)
durl = parsed_url.scheme + "://" + parsed_url.netloc
lines = data.strip().split('\n')
for index, string in enumerate(lines):
if '#EXT' not in string and 'http' not in string:
last_slash_index = string.rfind('/')
lpath = string[:last_slash_index + 1]
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
data = '\n'.join(lines)
return [200, "application/vnd.apple.mpegur", data]
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()

301
py/优酷视频.py Normal file
View File

@@ -0,0 +1,301 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import quote
from Crypto.Hash import MD5
import requests
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.session = requests.Session()
self.session.headers.update(self.headers)
self.session.cookies.update(self.cookie)
self.get_ctoken()
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host='https://www.youku.com'
shost='https://search.youku.com'
h5host='https://acs.youku.com'
ihost='https://v.youku.com'
headers = {
'User-Agent': 'Mozilla/5.0 (; Windows 10.0.26100.3194_64 ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Electron/14.2.0 Safari/537.36 Node/14.17.0 YoukuDesktop/9.2.60 UOSYouku (2.0.1)-Electron(UTDID ZYmGMAAAACkDAMU8hbiMmYdd;CHANNEL official;ZREAL 0;BTYPE TM2013;BRAND TIMI;BUILDVER 9.2.60.1001)',
'Referer': f'{host}/'
}
cookie={
"__ysuid": "17416134165380iB",
"__aysid": "1741613416541WbD",
"xlly_s": "1",
"isI18n": "false",
"cna": "bNdVIKmmsHgCAXW9W6yrQ1/s",
"__ayft": "1741672162330",
"__arpvid": "1741672162331FBKgrn-1741672162342",
"__ayscnt": "1",
"__aypstp": "1",
"__ayspstp": "3",
"tfstk": "gZbiib4JpG-6DqW-B98_2rwPuFrd1fTXQt3vHEp4YpJIBA3OgrWcwOi90RTOo9XVQ5tAM5NcK_CP6Ep97K2ce1XDc59v3KXAgGFLyzC11ET2n8U8yoyib67M3xL25e8gS8pbyzC1_ET4e8URWTsSnHv2uh8VTeJBgEuN3d-ELQAWuKWV36PHGpJ2uEWVTxvicLX1ewyUXYSekxMf-CxMEqpnoqVvshvP_pABOwvXjL5wKqeulm52np_zpkfCDGW9Ot4uKFIRwZtP7vP9_gfAr3KEpDWXSIfWRay-DHIc_Z-hAzkD1i5Ooi5LZ0O5YO_1mUc476YMI3R6xzucUnRlNe_zemKdm172xMwr2L7CTgIkbvndhFAVh3_YFV9Ng__52U4SQKIdZZjc4diE4EUxlFrfKmiXbBOHeP72v7sAahuTtWm78hRB1yV3tmg9bBOEhWVnq5KwOBL5."
}
def homeContent(self, filter):
result = {}
categories = ["电视剧", "电影", "综艺", "动漫", "少儿", "纪录片", "文化", "亲子", "教育", "搞笑", "生活",
"体育", "音乐", "游戏"]
classes = [{'type_name': category, 'type_id': category} for category in categories]
filters = {}
self.typeid = {}
with ThreadPoolExecutor(max_workers=len(categories)) as executor:
tasks = {
executor.submit(self.cf, {'type': category}, True): category
for category in categories
}
for future in as_completed(tasks):
try:
category = tasks[future]
session, ft = future.result()
filters[category] = ft
self.typeid[category] = session
except Exception as e:
print(f"处理分类 {tasks[future]} 时出错: {str(e)}")
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
try:
vlist = []
params={"ms_codes":"2019061000","params":"{\"debug\":0,\"gray\":0,\"pageNo\":1,\"utdid\":\"ZYmGMAAAACkDAMU8hbiMmYdd\",\"userId\":\"\",\"bizKey\":\"YOUKU_WEB\",\"appPackageKey\":\"com.youku.YouKu\",\"showNodeList\":0,\"reqSubNode\":0,\"nodeKey\":\"WEBHOME\",\"bizContext\":\"{\\\"spmA\\\":\\\"a2hja\\\"}\"}","system_info":"{\"device\":\"pcweb\",\"os\":\"pcweb\",\"ver\":\"1.0.0.0\",\"userAgent\":\"Mozilla/5.0 (; Windows 10.0.26100.3194_64 ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Electron/14.2.0 Safari/537.36 Node/14.17.0 YoukuDesktop/9.2.60 UOSYouku (2.0.1)-Electron(UTDID ZYmGMAAAACkDAMU8hbiMmYdd;CHANNEL official;ZREAL 0;BTYPE TM2013;BRAND TIMI;BUILDVER 9.2.60.1001)\",\"guid\":\"1590141704165YXe\",\"appPackageKey\":\"com.youku.pcweb\",\"young\":0,\"brand\":\"\",\"network\":\"\",\"ouid\":\"\",\"idfa\":\"\",\"scale\":\"\",\"operator\":\"\",\"resolution\":\"\",\"pid\":\"\",\"childGender\":0,\"zx\":0}"}
data=self.getdata(f'{self.h5host}/h5/mtop.youku.columbus.home.query/1.0/',params)
okey=list(data['data'].keys())[0]
for i in data['data'][okey]['data']['nodes'][0]['nodes'][-1]['nodes'][0]['nodes']:
if i.get('nodes') and i['nodes'][0].get('data'):
i=i['nodes'][0]['data']
if i.get('assignId'):
vlist.append({
'vod_id': i['assignId'],
'vod_name': i.get('title'),
'vod_pic': i.get('vImg') or i.get('img'),
'vod_year': i.get('mark',{}).get('data',{}).get('text'),
'vod_remarks': i.get('summary')
})
return {'list': vlist}
except Exception as e:
print(f"处理主页视频数据时出错: {str(e)}")
return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
result = {}
vlist = []
result['page'] = pg
result['limit'] = 90
result['total'] = 999999
pagecount = 9999
params = {'type': tid}
id = self.typeid[tid]
params.update(extend)
if pg == '1':
id=self.cf(params)
data=self.session.get(f'{self.host}/category/data?session={id}&params={quote(json.dumps(params))}&pageNo={pg}').json()
try:
data=data['data']['filterData']
for i in data['listData']:
if i.get('videoLink') and 's=' in i['videoLink']:
vlist.append({
'vod_id': i.get('videoLink').split('s=')[-1],
'vod_name': i.get('title'),
'vod_pic': i.get('img'),
'vod_year': i.get('rightTagText'),
'vod_remarks': i.get('summary')
})
self.typeid[tid]=quote(json.dumps(data['session']))
except:
pagecount=pg
result['list'] = vlist
result['pagecount'] = pagecount
return result
def detailContent(self, ids):
try:
data=self.session.get(f'{self.ihost}/v_getvideo_info/?showId={ids[0]}').json()
v=data['data']
vod = {
'type_name': v.get('showVideotype'),
'vod_year': v.get('lastUpdate'),
'vod_remarks': v.get('rc_title'),
'vod_actor': v.get('_personNameStr'),
'vod_content': v.get('showdesc'),
'vod_play_from': '优酷',
'vod_play_url': ''
}
params={"biz":"new_detail_web2","videoId":v.get('vid'),"scene":"web_page","componentVersion":"3","ip":data.get('ip'),"debug":0,"utdid":"ZYmGMAAAACkDAMU8hbiMmYdd","userId":0,"platform":"pc","nextSession":"","gray":0,"source":"pcNoPrev","showId":ids[0]}
sdata,index=self.getinfo(params)
pdata=sdata['nodes']
if index > len(pdata):
batch_size = len(pdata)
total_batches = ((index + batch_size - 1) // batch_size) - 1
ssj = json.loads(sdata['data']['session'])
with ThreadPoolExecutor(max_workers=total_batches) as executor:
futures = []
for batch in range(total_batches):
start = batch_size + 1 + (batch * batch_size)
end = start + batch_size - 1
next_session = ssj.copy()
next_session.update({
"itemStartStage": start,
"itemEndStage": min(end, index)
})
current_params = params.copy()
current_params['nextSession'] = json.dumps(next_session)
futures.append((start, executor.submit(self.getvinfo, current_params)))
futures.sort(key=lambda x: x[0])
for _, future in futures:
try:
result = future.result()
pdata.extend(result['nodes'])
except Exception as e:
print(f"Error fetching data: {str(e)}")
vod['vod_play_url'] = '#'.join([f"{i['data'].get('title')}${i['data']['action'].get('value')}" for i in pdata])
return {'list': [vod]}
except Exception as e:
print(e)
return {'list': [{'vod_play_from': '哎呀翻车啦', 'vod_play_url': f'呜呜呜${self.host}'}]}
def searchContent(self, key, quick, pg="1"):
data=self.session.get(f'{self.shost}/api/search?pg={pg}&keyword={key}').json()
vlist = []
for i in data['pageComponentList']:
if i.get('commonData') and (i['commonData'].get('showId') or i['commonData'].get('realShowId')):
i=i['commonData']
vlist.append({
'vod_id': i.get('showId') or i.get('realShowId'),
'vod_name': i['titleDTO'].get('displayName'),
'vod_pic': i['posterDTO'].get('vThumbUrl'),
'vod_year': i.get('feature'),
'vod_remarks': i.get('updateNotice')
})
return {'list': vlist, 'page': pg}
def playerContent(self, flag, id, vipFlags):
return {'jx':1,'parse': 1, 'url': f"{self.ihost}/video?vid={id}", 'header': ''}
def localProxy(self, param):
pass
def cf(self,params,b=False):
response = self.session.get(f'{self.host}/category/data?params={quote(json.dumps(params))}&optionRefresh=1&pageNo=1').json()
data=response['data']['filterData']
session=quote(json.dumps(data['session']))
if b:
return session,self.get_filter_data(data['filter']['filterData'][1:])
return session
def process_key(self, key):
if '_' not in key:
return key
parts = key.split('_')
result = parts[0]
for part in parts[1:]:
if part:
result += part[0].upper() + part[1:]
return result
def get_filter_data(self, data):
result = []
try:
for item in data:
if not item.get('subFilter'):
continue
first_sub = item['subFilter'][0]
if not first_sub.get('filterType'):
continue
filter_item = {
'key': self.process_key(first_sub['filterType']),
'name': first_sub['title'],
'value': []
}
for sub in item['subFilter']:
if 'value' in sub:
filter_item['value'].append({
'n': sub['title'],
'v': sub['value']
})
if filter_item['value']:
result.append(filter_item)
except Exception as e:
print(f"处理筛选数据时出错: {str(e)}")
return result
def get_ctoken(self):
data=self.session.get(f'{self.h5host}/h5/mtop.ykrec.recommendservice.recommend/1.0/?jsv=2.6.1&appKey=24679788')
def md5(self,t,text):
h = MD5.new()
token=self.session.cookies.get('_m_h5_tk').split('_')[0]
data=f"{token}&{t}&24679788&{text}"
h.update(data.encode('utf-8'))
return h.hexdigest()
def getdata(self, url, params, recursion_count=0, max_recursion=3):
data = json.dumps(params)
t = int(time.time() * 1000)
jsdata = {
'appKey': '24679788',
't': t,
'sign': self.md5(t, data),
'data': data
}
response = self.session.get(url, params=jsdata)
if '令牌过期' in response.text:
if recursion_count >= max_recursion:
raise Exception("达到最大递归次数,无法继续请求")
self.get_ctoken()
return self.getdata(url, params, recursion_count + 1, max_recursion)
else:
return response.json()
def getvinfo(self,params):
body = {
"ms_codes": "2019030100",
"params": json.dumps(params),
"system_info": "{\"os\":\"iku\",\"device\":\"iku\",\"ver\":\"9.2.9\",\"appPackageKey\":\"com.youku.iku\",\"appPackageId\":\"pcweb\"}"
}
data = self.getdata(f'{self.h5host}/h5/mtop.youku.columbus.gateway.new.execute/1.0/', body)
okey = list(data['data'].keys())[0]
i = data['data'][okey]['data']
return i
def getinfo(self,params):
i = self.getvinfo(params)
jdata=i['nodes'][0]['nodes'][3]
info=i['data']['extra']['episodeTotal']
if i['data']['extra']['showCategory'] in ['电影','游戏']:
jdata = i['nodes'][0]['nodes'][4]
return jdata,info

790
py/偷乐短剧.py Normal file
View File

@@ -0,0 +1,790 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 偷乐短剧爬虫
import sys
import json
import re
import time
import urllib.parse
import requests
from bs4 import BeautifulSoup
# 导入基础类
sys.path.append('../../')
try:
from base.spider import Spider
except ImportError:
# 本地调试时的替代实现
class Spider:
def init(self, extend=""):
pass
class Spider(Spider):
def __init__(self):
# 网站主URL
self.siteUrl = "https://www.toule.top"
# 根据网站实际结构,分类链接格式为: /index.php/vod/show/class/分类名/id/1.html
# 分类ID映射 - 从网站中提取的分类
self.cateManual = {
"男频": "/index.php/vod/show/class/%E7%94%B7%E9%A2%91/id/1.html",
"女频": "/index.php/vod/show/class/%E5%A5%B3%E9%A2%91/id/1.html",
"都市": "/index.php/vod/show/class/%E9%83%BD%E5%B8%82/id/1.html",
"赘婿": "/index.php/vod/show/class/%E8%B5%98%E5%A9%BF/id/1.html",
"战神": "/index.php/vod/show/class/%E6%88%98%E7%A5%9E/id/1.html",
"古代言情": "/index.php/vod/show/class/%E5%8F%A4%E4%BB%A3%E8%A8%80%E6%83%85/id/1.html",
"现代言情": "/index.php/vod/show/class/%E7%8E%B0%E4%BB%A3%E8%A8%80%E6%83%85/id/1.html",
"历史": "/index.php/vod/show/class/%E5%8E%86%E5%8F%B2/id/1.html",
"玄幻": "/index.php/vod/show/class/%E7%8E%84%E5%B9%BB/id/1.html",
"搞笑": "/index.php/vod/show/class/%E6%90%9E%E7%AC%91/id/1.html",
"甜宠": "/index.php/vod/show/class/%E7%94%9C%E5%AE%A0/id/1.html",
"励志": "/index.php/vod/show/class/%E5%8A%B1%E5%BF%97/id/1.html",
"逆袭": "/index.php/vod/show/class/%E9%80%86%E8%A2%AD/id/1.html",
"穿越": "/index.php/vod/show/class/%E7%A9%BF%E8%B6%8A/id/1.html",
"古装": "/index.php/vod/show/class/%E5%8F%A4%E8%A3%85/id/1.html"
}
# 请求头
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Referer": "https://www.toule.top/",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
}
# 缓存
self.cache = {}
self.cache_timeout = {}
def getName(self):
return "偷乐短剧"
def init(self, extend=""):
# 初始化方法,可以留空
return
def isVideoFormat(self, url):
"""判断是否为视频格式"""
video_formats = ['.mp4', '.m3u8', '.ts', '.flv', '.avi', '.mkv', '.mov', '.rmvb', '.3gp']
for format in video_formats:
if format in url.lower():
return True
return False
def manualVideoCheck(self):
"""是否需要手动检查视频"""
return False
# 工具方法 - 网络请求
def fetch(self, url, headers=None, data=None, method="GET"):
"""统一的网络请求方法"""
try:
if headers is None:
headers = self.headers.copy()
if method.upper() == "GET":
response = requests.get(url, headers=headers, params=data, timeout=10,verify=False)
else: # POST
response = requests.post(url, headers=headers, data=data, timeout=10,verify=False)
response.raise_for_status()
response.encoding = response.apparent_encoding or 'utf-8'
return response
except Exception as e:
self.log(f"请求失败: {url}, 错误: {str(e)}", "ERROR")
return None
# 缓存方法
def getCache(self, key, timeout=3600):
"""获取缓存数据"""
if key in self.cache and key in self.cache_timeout:
if time.time() < self.cache_timeout[key]:
return self.cache[key]
else:
del self.cache[key]
del self.cache_timeout[key]
return None
def setCache(self, key, value, timeout=3600):
"""设置缓存数据"""
self.cache[key] = value
self.cache_timeout[key] = time.time() + timeout
# 日志方法
def log(self, msg, level='INFO'):
"""记录日志"""
levels = {
'DEBUG': 0,
'INFO': 1,
'WARNING': 2,
'ERROR': 3
}
current_level = 'INFO' # 可以设置为DEBUG以获取更多信息
if levels.get(level, 4) >= levels.get(current_level, 1):
print(f"[{level}] {time.strftime('%Y-%m-%d %H:%M:%S')} - {msg}")
# 辅助方法 - 从URL中提取视频ID
def extractVodId(self, url):
"""从URL中提取视频ID"""
# 路径格式: /index.php/vod/play/id/9024/sid/1/nid/1.html
match = re.search(r'/id/(\d+)/', url)
if match:
return match.group(1)
return ""
# 辅助方法 - 从网页内容中提取分类
def extractCategories(self, text):
"""从网页内容中提取分类标签"""
cats = []
# 匹配标签字符串,例如: "男频,逆袭,亲情,短剧"
if "," in text:
parts = text.split(",")
for part in parts:
part = part.strip()
if part and part != "短剧":
cats.append(part)
return cats
# 主要接口实现
def homeContent(self, filter):
"""获取首页分类及内容"""
result = {}
classes = []
# 从缓存获取
cache_key = 'home_classes'
cached_classes = self.getCache(cache_key)
if cached_classes:
classes = cached_classes
else:
# 使用预定义的分类
for k, v in self.cateManual.items():
classes.append({
'type_id': v, # 使用完整URL路径作为type_id
'type_name': k
})
# 保存到缓存
self.setCache(cache_key, classes, 24*3600) # 缓存24小时
result['class'] = classes
# 获取首页推荐视频
videos = self.homeVideoContent().get('list', [])
result['list'] = videos
return result
def homeVideoContent(self):
"""获取首页推荐视频内容"""
result = {'list': []}
videos = []
# 从缓存获取
cache_key = 'home_videos'
cached_videos = self.getCache(cache_key)
if cached_videos:
return {'list': cached_videos}
try:
response = self.fetch(self.siteUrl)
if response and response.status_code == 200:
html = response.text
soup = BeautifulSoup(html, 'html.parser')
# 查找最新更新区域
latest_section = soup.find('h2', text=lambda t: t and '最新更新' in t)
if latest_section:
container = latest_section.parent # 获取容器
if container:
# 查找所有 li.item 元素
items = container.find_all('li', class_='item')
for item in items:
try:
# 获取链接和标题
title_link = item.find('h3')
if not title_link:
continue
title = title_link.text.strip()
# 获取第一个链接作为详情页链接
link_tag = item.find('a')
if not link_tag:
continue
link = link_tag.get('href', '')
if not link.startswith('http'):
link = urllib.parse.urljoin(self.siteUrl, link)
# 提取ID
vid = self.extractVodId(link)
if not vid:
continue
# 获取图片
img_tag = item.find('img')
img_url = ""
if img_tag:
img_url = img_tag.get('src', img_tag.get('data-src', ''))
if img_url and not img_url.startswith('http'):
img_url = urllib.parse.urljoin(self.siteUrl, img_url)
# 获取备注信息
remarks = ""
remarks_tag = item.find('span', class_='remarks')
if remarks_tag:
remarks = remarks_tag.text.strip()
# 获取标签信息
tags = ""
tags_tag = item.find('span', class_='tags')
if tags_tag:
tags = tags_tag.text.strip()
# 合并备注和标签
if remarks and tags:
remarks = f"{remarks} | {tags}"
elif tags:
remarks = tags
# 构建视频项
videos.append({
'vod_id': vid,
'vod_name': title,
'vod_pic': img_url,
'vod_remarks': remarks
})
except Exception as e:
self.log(f"处理视频项时出错: {str(e)}", "ERROR")
continue
# 保存到缓存
self.setCache(cache_key, videos, 3600) # 缓存1小时
except Exception as e:
self.log(f"获取首页视频内容发生错误: {str(e)}", "ERROR")
result['list'] = videos
return result
def categoryContent(self, tid, pg, filter, extend):
"""获取分类内容"""
result = {}
videos = []
# 处理页码
if pg is None:
pg = 1
else:
pg = int(pg)
# 构建分类URL - tid是完整的URL路径
if tid.startswith("/"):
# 替换页码URL格式可能像: /index.php/vod/show/class/男频/id/1.html
if pg > 1:
if "html" in tid:
category_url = tid.replace(".html", f"/page/{pg}.html")
else:
category_url = f"{tid}/page/{pg}.html"
else:
category_url = tid
full_url = urllib.parse.urljoin(self.siteUrl, category_url)
else:
# 如果tid不是URL路径可能是旧版分类ID尝试查找对应URL
category_url = ""
for name, url in self.cateManual.items():
if name == tid:
category_url = url
break
if not category_url:
self.log(f"未找到分类ID对应的URL: {tid}", "ERROR")
result['list'] = []
result['page'] = pg
result['pagecount'] = 1
result['limit'] = 0
result['total'] = 0
return result
# 处理页码
if pg > 1:
if "html" in category_url:
category_url = category_url.replace(".html", f"/page/{pg}.html")
else:
category_url = f"{category_url}/page/{pg}.html"
full_url = urllib.parse.urljoin(self.siteUrl, category_url)
# 请求分类页
try:
response = self.fetch(full_url)
if response and response.status_code == 200:
html = response.text
soup = BeautifulSoup(html, 'html.parser')
# 查找视频项根据实际HTML结构调整
items = soup.find_all('li', class_='item')
for item in items:
try:
# 获取链接和标题
title_tag = item.find('h3')
if not title_tag:
continue
title = title_tag.text.strip()
# 获取链接
link_tag = item.find('a')
if not link_tag:
continue
link = link_tag.get('href', '')
if not link.startswith('http'):
link = urllib.parse.urljoin(self.siteUrl, link)
# 提取ID
vid = self.extractVodId(link)
if not vid:
continue
# 获取图片
img_tag = item.find('img')
img_url = ""
if img_tag:
img_url = img_tag.get('src', img_tag.get('data-src', ''))
if img_url and not img_url.startswith('http'):
img_url = urllib.parse.urljoin(self.siteUrl, img_url)
# 获取备注信息
remarks = ""
remarks_tag = item.find('span', class_='remarks')
if remarks_tag:
remarks = remarks_tag.text.strip()
# 获取标签信息
tags = ""
tags_tag = item.find('span', class_='tags')
if tags_tag:
tags = tags_tag.text.strip()
# 合并备注和标签
if remarks and tags:
remarks = f"{remarks} | {tags}"
elif tags:
remarks = tags
# 构建视频项
videos.append({
'vod_id': vid,
'vod_name': title,
'vod_pic': img_url,
'vod_remarks': remarks
})
except Exception as e:
self.log(f"处理分类视频项时出错: {str(e)}", "ERROR")
continue
# 查找分页信息
# 默认值
total = len(videos)
pagecount = 1
limit = 20
# 尝试查找分页元素
pagination = soup.find('ul', class_='page')
if pagination:
# 查找最后一页的链接
last_page_links = pagination.find_all('a')
for link in last_page_links:
page_text = link.text.strip()
if page_text.isdigit():
pagecount = max(pagecount, int(page_text))
except Exception as e:
self.log(f"获取分类内容发生错误: {str(e)}", "ERROR")
result['list'] = videos
result['page'] = pg
result['pagecount'] = pagecount
result['limit'] = limit
result['total'] = total
return result
def detailContent(self, ids):
"""获取详情内容"""
result = {}
if not ids or len(ids) == 0:
return result
# 视频ID
vid = ids[0]
# 构建播放页URL
play_url = f"{self.siteUrl}/index.php/vod/play/id/{vid}/sid/1/nid/1.html"
try:
response = self.fetch(play_url)
if not response or response.status_code != 200:
return result
html = response.text
soup = BeautifulSoup(html, 'html.parser')
# 提取视频基本信息
# 标题
title = ""
title_tag = soup.find('h1', class_='items-title')
if title_tag:
title = title_tag.text.strip()
# 图片
pic = ""
pic_tag = soup.find('img', class_='thumb')
if pic_tag:
pic = pic_tag.get('src', '')
if pic and not pic.startswith('http'):
pic = urllib.parse.urljoin(self.siteUrl, pic)
# 简介
desc = ""
desc_tag = soup.find('div', class_='text-content')
if desc_tag:
desc = desc_tag.text.strip()
# 标签/分类
tags = []
tags_container = soup.find('span', class_='items-tags')
if tags_container:
tag_links = tags_container.find_all('a')
for tag in tag_links:
tag_text = tag.text.strip()
if tag_text:
tags.append(tag_text)
# 提取播放列表
play_from = "偷乐短剧"
play_list = []
# 查找播放列表区域
play_area = soup.find('div', class_='swiper-wrapper')
if play_area:
# 查找所有剧集链接
episode_links = play_area.find_all('a')
for ep in episode_links:
ep_title = ep.text.strip()
ep_url = ep.get('href', '')
if ep_url:
# 直接使用URL作为ID
if not ep_url.startswith('http'):
ep_url = urllib.parse.urljoin(self.siteUrl, ep_url)
# 提取集数信息
ep_num = ep_title
if ep_num.isdigit():
ep_num = f"{ep_num}"
play_list.append(f"{ep_num}${ep_url}")
# 如果没有找到播放列表,查找播放按钮
if not play_list:
play_btn = soup.find('a', class_='btn-play')
if play_btn:
play_url = play_btn.get('href', '')
if play_url:
if not play_url.startswith('http'):
play_url = urllib.parse.urljoin(self.siteUrl, play_url)
play_list.append(f"播放${play_url}")
# 如果仍然没有找到播放链接使用播放页URL
if not play_list:
play_url = f"{self.siteUrl}/index.php/vod/play/id/{vid}/sid/1/nid/1.html"
play_list.append(f"播放${play_url}")
# 提取更多信息(导演、演员等)
director = ""
actor = ""
year = ""
area = ""
remarks = ""
# 查找备注信息
meta_items = soup.find_all('div', class_='meta-item')
for item in meta_items:
item_title = item.find('span', class_='item-title')
item_content = item.find('span', class_='item-content')
if item_title and item_content:
title_text = item_title.text.strip()
content_text = item_content.text.strip()
if "导演" in title_text:
director = content_text
elif "主演" in title_text:
actor = content_text
elif "年份" in title_text:
year = content_text
elif "地区" in title_text:
area = content_text
elif "简介" in title_text:
if not desc:
desc = content_text
elif "状态" in title_text:
remarks = content_text
# 如果没有从meta-item中获取到remarks
if not remarks:
remarks_tag = soup.find('span', class_='remarks')
if remarks_tag:
remarks = remarks_tag.text.strip()
# 构建标准数据结构
vod = {
"vod_id": vid,
"vod_name": title,
"vod_pic": pic,
"vod_year": year,
"vod_area": area,
"vod_remarks": remarks,
"vod_actor": actor,
"vod_director": director,
"vod_content": desc,
"type_name": ",".join(tags),
"vod_play_from": play_from,
"vod_play_url": "#".join(play_list)
}
result = {
'list': [vod]
}
except Exception as e:
self.log(f"获取详情内容时出错: {str(e)}", "ERROR")
return result
def searchContent(self, key, quick, pg=1):
"""搜索功能"""
result = {}
videos = []
# 构建搜索URL和参数
search_url = f"{self.siteUrl}/index.php/vod/search.html"
params = {"wd": key}
try:
response = self.fetch(search_url, data=params)
if response and response.status_code == 200:
html = response.text
soup = BeautifulSoup(html, 'html.parser')
# 查找搜索结果项
search_items = soup.find_all('li', class_='item')
for item in search_items:
try:
# 获取标题
title_tag = item.find('h3')
if not title_tag:
continue
title = title_tag.text.strip()
# 获取链接
link_tag = item.find('a')
if not link_tag:
continue
link = link_tag.get('href', '')
if not link.startswith('http'):
link = urllib.parse.urljoin(self.siteUrl, link)
# 提取视频ID
vid = self.extractVodId(link)
if not vid:
continue
# 获取图片
img_tag = item.find('img')
img_url = ""
if img_tag:
img_url = img_tag.get('src', img_tag.get('data-src', ''))
if img_url and not img_url.startswith('http'):
img_url = urllib.parse.urljoin(self.siteUrl, img_url)
# 获取备注信息
remarks = ""
remarks_tag = item.find('span', class_='remarks')
if remarks_tag:
remarks = remarks_tag.text.strip()
# 获取标签信息
tags = ""
tags_tag = item.find('span', class_='tags')
if tags_tag:
tags = tags_tag.text.strip()
# 合并备注和标签
if remarks and tags:
remarks = f"{remarks} | {tags}"
elif tags:
remarks = tags
# 构建视频项
videos.append({
'vod_id': vid,
'vod_name': title,
'vod_pic': img_url,
'vod_remarks': remarks
})
except Exception as e:
self.log(f"处理搜索结果时出错: {str(e)}", "ERROR")
continue
except Exception as e:
self.log(f"搜索功能发生错误: {str(e)}", "ERROR")
result['list'] = videos
return result
def searchContentPage(self, key, quick, pg=1):
return self.searchContent(key, quick, pg)
def playerContent(self, flag, id, vipFlags):
"""获取播放内容"""
result = {}
try:
# 判断是否已经是视频URL
if self.isVideoFormat(id):
result["parse"] = 0
result["url"] = id
result["playUrl"] = ""
result["header"] = json.dumps(self.headers)
return result
# 判断是否是完整的页面URL
if id.startswith(('http://', 'https://')):
play_url = id
# 尝试作为相对路径处理
elif id.startswith('/'):
play_url = urllib.parse.urljoin(self.siteUrl, id)
# 假设是视频ID构建播放页面URL
else:
# 检查是否是"视频ID_集数"格式
parts = id.split('_')
if len(parts) > 1 and parts[0].isdigit():
vid = parts[0]
nid = parts[1]
play_url = f"{self.siteUrl}/index.php/vod/play/id/{vid}/sid/1/nid/{nid}.html"
else:
# 直接当作视频ID处理
play_url = f"{self.siteUrl}/index.php/vod/play/id/{id}/sid/1/nid/1.html"
# 访问播放页获取真实播放地址
try:
self.log(f"正在解析播放页面: {play_url}")
response = self.fetch(play_url)
if response and response.status_code == 200:
html = response.text
# 查找player_aaaa变量
player_match = re.search(r'var\s+player_aaaa\s*=\s*({.*?});', html, re.DOTALL)
if player_match:
try:
player_data = json.loads(player_match.group(1))
if 'url' in player_data:
video_url = player_data['url']
if not video_url.startswith('http'):
video_url = urllib.parse.urljoin(self.siteUrl, video_url)
self.log(f"从player_aaaa获取到视频地址: {video_url}")
result["parse"] = 0
result["url"] = video_url
result["playUrl"] = ""
result["header"] = json.dumps(self.headers)
return result
except json.JSONDecodeError as e:
self.log(f"解析player_aaaa JSON出错: {str(e)}", "ERROR")
# 如果player_aaaa解析失败尝试其他方式
# 1. 查找video标签
video_match = re.search(r'<video[^>]*src=["\'](.*?)["\']', html)
if video_match:
video_url = video_match.group(1)
if not video_url.startswith('http'):
video_url = urllib.parse.urljoin(self.siteUrl, video_url)
self.log(f"从video标签找到视频地址: {video_url}")
result["parse"] = 0
result["url"] = video_url
result["playUrl"] = ""
result["header"] = json.dumps(self.headers)
return result
# 2. 查找iframe
iframe_match = re.search(r'<iframe[^>]*src=["\'](.*?)["\']', html)
if iframe_match:
iframe_url = iframe_match.group(1)
if not iframe_url.startswith('http'):
iframe_url = urllib.parse.urljoin(self.siteUrl, iframe_url)
self.log(f"找到iframe正在解析: {iframe_url}")
# 访问iframe内容
iframe_response = self.fetch(iframe_url)
if iframe_response and iframe_response.status_code == 200:
iframe_html = iframe_response.text
# 在iframe内容中查找视频地址
iframe_video_match = re.search(r'(https?://[^\'"]+\.(mp4|m3u8|ts))', iframe_html)
if iframe_video_match:
video_url = iframe_video_match.group(1)
self.log(f"从iframe中找到视频地址: {video_url}")
result["parse"] = 0
result["url"] = video_url
result["playUrl"] = ""
result["header"] = json.dumps({
"User-Agent": self.headers["User-Agent"],
"Referer": iframe_url
})
return result
# 3. 查找任何可能的视频URL
url_match = re.search(r'(https?://[^\'"]+\.(mp4|m3u8|ts))', html)
if url_match:
video_url = url_match.group(1)
self.log(f"找到可能的视频地址: {video_url}")
result["parse"] = 0
result["url"] = video_url
result["playUrl"] = ""
result["header"] = json.dumps(self.headers)
return result
except Exception as e:
self.log(f"解析播放地址时出错: {str(e)}", "ERROR")
# 如果所有方式都失败,返回外部解析标志
self.log("未找到直接可用的视频地址,需要外部解析", "WARNING")
result["parse"] = 1 # 表示需要外部解析
result["url"] = play_url # 返回播放页面URL
result["playUrl"] = ""
result["header"] = json.dumps(self.headers)
except Exception as e:
self.log(f"获取播放内容时出错: {str(e)}", "ERROR")
return result
def localProxy(self, param):
"""本地代理"""
return [404, "text/plain", {}, "Not Found"]

222
py/光速影视.py Normal file
View File

@@ -0,0 +1,222 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import re
import sys
from Crypto.Hash import MD5
sys.path.append('..')
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from urllib.parse import quote, urlparse
from base64 import b64encode, b64decode
import json
import time
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = self.gethost()
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def action(self, action):
pass
def destroy(self):
pass
def homeContent(self, filter):
data = self.getdata("/api.php/getappapi.index/initV119")
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
"sort": "排序"}
filters = {}
classes = []
json_data = data["type_list"]
homedata = data["banner_list"][8:]
for item in json_data:
if item["type_name"] == "全部":
continue
has_non_empty_field = False
jsontype_extend = json.loads(item["type_extend"])
homedata.extend(item["recommend_list"])
jsontype_extend["sort"] = "最新,最热,最赞"
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
for key in dy:
if key in jsontype_extend and jsontype_extend[key].strip() != "":
has_non_empty_field = True
break
if has_non_empty_field:
filters[str(item["type_id"])] = []
for dkey in jsontype_extend:
if dkey in dy and jsontype_extend[dkey].strip() != "":
values = jsontype_extend[dkey].split(",")
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
value.strip() != ""]
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
result = {}
result["class"] = classes
result["filters"] = filters
result["list"] = homedata[1:]
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
"class": extend.get('class', '全部')}
result = {}
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
result["list"] = data["recommend_list"]
result["page"] = pg
result["pagecount"] = 9999
result["limit"] = 90
result["total"] = 999999
return result
def detailContent(self, ids):
body = f"vod_id={ids[0]}"
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
vod = data["vod"]
play = []
names = []
for itt in data["vod_play_list"]:
a = []
names.append(itt["player_info"]["show"])
for it in itt['urls']:
it['user_agent']=itt["player_info"].get("user_agent")
it["parse"]=itt["player_info"].get("parse")
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
play.append("#".join(a))
vod["vod_play_from"] = "$$$".join(names)
vod["vod_play_url"] = "$$$".join(play)
result = {"list": [vod]}
return result
def searchContent(self, key, quick, pg="1"):
body = f"keywords={key}&type_id=0&page={pg}"
data = self.getdata("/api.php/getappapi.index/searchList", body)
result = {"list": data["search_list"], "page": pg}
return result
def playerContent(self, flag, id, vipFlags):
ids = json.loads(self.d64(id))
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
try:
if re.search(r'url=', ids['parse_api_url']):
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
url = data.get('url') or data['data'].get('url')
else:
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
url = json.loads(b)['url']
if 'error' in url: raise ValueError(f"解析失败: {url}")
p = 0
except Exception as e:
print('错误信息:', e)
url, p = ids['url'], 1
if re.search(r'\.jpg|\.png|\.jpeg', url):
url = self.Mproxy(url)
result = {}
result["parse"] = p
result["url"] = url
result["header"] = h
return result
def localProxy(self, param):
return self.Mlocal(param)
def gethost(self):
headers = {
'User-Agent': 'okhttp/3.14.9'
}
host = self.fetch('https://jingyu-1312635929.cos.ap-nanjing.myqcloud.com/1.json',
headers=headers).text.strip()
return host
phend = {
'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 11; M2012K10C Build/RP1A.200720.011)',
'allowCrossProtocolRedirects': 'true'
}
def aes(self, operation, text):
key = "4d83b87c4c5ea111".encode("utf-8")
iv = key
if operation == "encrypt":
cipher = AES.new(key, AES.MODE_CBC, iv)
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
ct = b64encode(ct_bytes).decode("utf-8")
return ct
elif operation == "decrypt":
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
return pt.decode("utf-8")
def header(self):
t = str(int(time.time()))
header = {"Referer":self.host,
"User-Agent": "okhttp/3.14.9", "app-version-code": "300", "app-ui-mode": "light",
"app-api-verify-time": t, "app-user-device-id": self.md5(t),
"app-api-verify-sign": self.aes("encrypt", t),
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
return header
def getdata(self, path, data=None):
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
data1 = self.aes("decrypt", vdata)
return json.loads(data1)
def Mproxy(self, url):
return self.getProxyUrl() + "&url=" + b64encode(url.encode('utf-8')).decode('utf-8') + "&type=m3u8"
def Mlocal(self, param,header=None):
url = self.d64(param["url"])
ydata = self.fetch(url, headers=header, allow_redirects=False)
data = ydata.content.decode('utf-8')
if ydata.headers.get('Location'):
url = ydata.headers['Location']
data = self.fetch(url, headers=header).content.decode('utf-8')
parsed_url = urlparse(url)
durl = parsed_url.scheme + "://" + parsed_url.netloc
lines = data.strip().split('\n')
for index, string in enumerate(lines):
if '#EXT' not in string and 'http' not in string:
last_slash_index = string.rfind('/')
lpath = string[:last_slash_index + 1]
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
data = '\n'.join(lines)
return [200, "application/vnd.apple.mpegur", data]
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()

329
py/剧王短剧.py Normal file
View File

@@ -0,0 +1,329 @@
# coding=utf-8
# !/usr/bin/python
"""
作者 丢丢喵推荐 🚓 内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
====================Diudiumiao====================
"""
from Crypto.Util.Padding import unpad
from Crypto.Util.Padding import pad
from urllib.parse import unquote
from Crypto.Cipher import ARC4
from urllib.parse import quote
from base.spider import Spider
from Crypto.Cipher import AES
from datetime import datetime
from bs4 import BeautifulSoup
from base64 import b64decode
import urllib.request
import urllib.parse
import datetime
import binascii
import requests
import base64
import json
import time
import sys
import re
import os
sys.path.append('..')
xurl = "https://djw1.com"
headerx = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
}
class Spider(Spider):
global xurl
global headerx
def getName(self):
return "首页"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def 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 = {"class": []}
detail = requests.get(url=xurl + "/all/", headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
soups = doc.find_all('section', class_="container items")
for soup in soups:
vods = soup.find_all('li')
for vod in vods:
id = vod.find('a')['href']
name = vod.text.strip()
result["class"].append({"type_id": id, "type_name": "" + name})
return result
def homeVideoContent(self):
pass
def categoryContent(self, cid, pg, filter, ext):
result = {}
videos = []
if pg:
page = int(pg)
else:
page = 1
url = f'{cid}page/{str(page)}/'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
soups = doc.find_all('section', class_="container items")
for soup in soups:
vods = soup.find_all('li')
for vod in vods:
name = vod.find('img')['alt']
ids = vod.find('a', class_="image-line")
id = ids['href']
pic = vod.find('img')['src']
remark = self.extract_middle_text(str(vod), 'class="remarks light">', '<', 0)
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": '▶️' + remark
}
videos.append(video)
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
did = ids[0]
result = {}
videos = []
xianlu = ''
bofang = ''
if 'http' not in did:
did = xurl + did
res = requests.get(url=did, headers=headerx)
res.encoding = "utf-8"
res = res.text
doc = BeautifulSoup(res, "lxml")
url = 'https://fs-im-kefu.7moor-fs1.com/ly/4d2c3f00-7d4c-11e5-af15-41bf63ae4ea0/1732707176882/jiduo.txt'
response = requests.get(url)
response.encoding = 'utf-8'
code = response.text
name = self.extract_middle_text(code, "s1='", "'", 0)
Jumps = self.extract_middle_text(code, "s2='", "'", 0)
content = '集多为您介绍剧情📢' + self.extract_middle_text(res,'class="info-detail">','<', 0)
remarks = self.extract_middle_text(res, 'class="info-mark">', '<', 0)
year = self.extract_middle_text(res, 'class="info-addtime">', '<', 0)
if name not in content:
bofang = Jumps
xianlu = '1'
else:
soups = doc.find('div', class_="ep-list-items")
soup = soups.find_all('a')
for sou in soup:
id = sou['href']
name = sou.text.strip()
bofang = bofang + name + '$' + id + '#'
bofang = bofang[:-1]
xianlu = '专线'
videos.append({
"vod_id": did,
"vod_remarks": remarks,
"vod_year": year,
"vod_content": content,
"vod_play_from": xianlu,
"vod_play_url": bofang
})
result['list'] = videos
return result
def playerContent(self, flag, id, vipFlags):
res = requests.get(url=id, headers=headerx)
res.encoding = "utf-8"
res = res.text
url = self.extract_middle_text(res, '"wwm3u8":"', '"', 0).replace('\\', '')
result = {}
result["parse"] = 0
result["playUrl"] = ''
result["url"] = url
result["header"] = headerx
return result
def searchContentPage(self, key, quick, pg):
result = {}
videos = []
if pg:
page = int(pg)
else:
page = 1
url = f'{xurl}/search/{key}/page/{str(page)}/'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
soups = doc.find_all('section', class_="container items")
for soup in soups:
vods = soup.find_all('li')
for vod in vods:
name = vod.find('img')['alt']
ids = vod.find('a', class_="image-line")
id = ids['href']
pic = vod.find('img')['src']
remark = self.extract_middle_text(str(vod), 'class="remarks light">', '<', 0)
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": '▶️' + remark
}
videos.append(video)
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
return self.searchContentPage(key, quick, '1')
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None

414
py/努努影院.py Normal file
View File

@@ -0,0 +1,414 @@
# coding=utf-8
# !/usr/bin/python
"""
作者 丢丢喵 🚓 内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
====================Diudiumiao====================
"""
from Crypto.Util.Padding import unpad
from Crypto.Util.Padding import pad
from urllib.parse import unquote
from Crypto.Cipher import ARC4
from urllib.parse import quote
from base.spider import Spider
from Crypto.Cipher import AES
from datetime import datetime
from bs4 import BeautifulSoup
from base64 import b64decode
import urllib.request
import urllib.parse
import datetime
import binascii
import requests
import base64
import json
import time
import sys
import re
import os
sys.path.append('..')
xurl = "https://nnyy.la"
headerx = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
}
class Spider(Spider):
global xurl
global headerx
def getName(self):
return "首页"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def split_id_by_at(self, id):
return id.split("@")
def split_first_part_by_comma(self, first_part):
return first_part.split(",")
def parse_url_dictionary(self, response_text):
pattern = r'urlDictionary\[(\d+)\]\[(\d+)\]\s*=\s*"([^"]+)"'
matches = re.findall(pattern, response_text)
url_dictionary = {}
for key1, key2, value in matches:
key1_int = int(key1)
key2_int = int(key2)
if key1_int not in url_dictionary:
url_dictionary[key1_int] = {}
url_dictionary[key1_int][key2_int] = value
return url_dictionary
def get_url_from_dictionary(self, url_dictionary, indices):
primary_index = int(indices[0].strip())
secondary_index = int(indices[1].strip())
return url_dictionary[primary_index][secondary_index]
def decrypt_url(self, encrypted_hex, key="i_love_you"):
encrypted_bytes = bytes.fromhex(encrypted_hex)
s = list(range(256))
j = 0
key_bytes = key.encode('utf-8')
key_length = len(key_bytes)
for i in range(256):
j = (j + s[i] + key_bytes[i % key_length]) % 256
s[i], s[j] = s[j], s[i]
i = 0
j = 0
decrypted_bytes = bytearray(len(encrypted_bytes))
for k in range(len(encrypted_bytes)):
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
keystream_byte = s[(s[i] + s[j]) % 256]
decrypted_bytes[k] = encrypted_bytes[k] ^ keystream_byte
return decrypted_bytes.decode('utf-8')
def _extract_play_sources(self, soups):
xianlu = ''
for item in soups:
vods = item.find_all('dt')
for sou in vods:
name = sou.text.strip()
xianlu = xianlu + name + '$$$'
xianlu = xianlu[:-3]
return xianlu
def _extract_play_urls(self, soups1, did):
bofang = ''
for item in soups1:
vods1 = item.find_all('a')
for sou1 in vods1:
id1 = sou1['onclick']
numbers = re.findall(r'\((.*?)\)', id1)[0] if re.findall(r'\((.*?)\)', id1) else ""
id = f"{numbers}@{did}"
name = sou1.text.strip()
bofang = bofang + name + '$' + id + '#'
bofang = bofang[:-1] + '$$$'
bofang = bofang[:-3]
return bofang
def _extract_content(self, res):
content_raw = self.extract_middle_text(res, '剧情简介:<span>', '<', 0).replace('\n', '')
return '😸丢丢为您介绍剧情📢' + (content_raw if content_raw is not None else "暂无剧情介绍")
def _extract_director(self, res):
director_raw = self.extract_middle_text(res, '导演:', '</div>', 1, 'href=".*?">(.*?)</a>')
return director_raw if director_raw is not None and director_raw.strip() != "" else "暂无导演介绍"
def _extract_actor(self, res):
actor_raw = self.extract_middle_text(res, '主演:', '</div>', 1, 'href=".*?">(.*?)</a>')
return actor_raw if actor_raw is not None and actor_raw.strip() != "" else "暂无主演介绍"
def _extract_remarks(self, res):
remarks_raw = self.extract_middle_text(res, '类型:', '</div>', 1, 'href=".*?">(.*?)</a>')
return remarks_raw if remarks_raw is not None and remarks_raw.strip() != "" else "暂无类型介绍"
def _extract_area(self, res):
area_raw = self.extract_middle_text(res, '制片国家/地区:', '</div>', 1, 'href=".*?">(.*?)</a>')
return area_raw if area_raw is not None and area_raw.strip() != "" else "暂无国家/地区介绍"
def _extract_year(self, doc):
years = doc.find('h1', class_="product-title")
year = years.text.strip() if years else '暂无年份介绍'
return year.replace('\n', '打分:')
def _extract_video_items(self, vods):
videos = []
for vod in vods:
name = vod.find('img')['alt']
ids = vod.find('a', class_="thumbnail")
id = ids['href']
pic = vod.find('img')['data-src']
remarks = vod.find('div', class_="note")
remark = remarks.text.strip()
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
return videos
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 = {"class": []}
detail = requests.get(url=xurl, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
soups = doc.find_all('div', class_="nav")
for soup in soups:
vods = soup.find_all('a')
for vod in vods:
name = vod.text.strip()
skip_names = ["首页"]
if name in skip_names:
continue
id = vod['href'].replace('/', '')
result["class"].append({"type_id": id, "type_name": name})
result["class"].append({"type_id": "duanju", "type_name": "短剧"})
return result
def homeVideoContent(self):
videos = []
detail = requests.get(url=xurl, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
soups = doc.find('div', class_="bd")
vods = soups.find_all('li')
videos = self._extract_video_items(vods)
result = {'list': videos}
return result
def categoryContent(self, cid, pg, filter, ext):
result = {}
videos = []
if pg:
page = int(pg)
else:
page = 1
url = f'{xurl}/{cid}/?page={str(page)}'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
soups = doc.find_all('div', class_="lists-content")
for soup in soups:
vods = soup.find_all('li')
videos = self._extract_video_items(vods)
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
did = ids[0]
result = {}
videos = []
if 'http' not in did:
did = xurl + did
detail = requests.get(url=did, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
content = self._extract_content(res)
director = self._extract_director(res)
actor = self._extract_actor(res)
remarks = self._extract_remarks(res)
area = self._extract_area(res)
year = self._extract_year(doc)
soups = doc.find_all('div', class_="playlists")
xianlu = self._extract_play_sources(soups)
soups1 = doc.find_all('ul', class_="sort-list")
bofang = self._extract_play_urls(soups1, did)
videos.append({
"vod_id": did,
"vod_director": director,
"vod_actor": actor,
"vod_remarks": remarks,
"vod_year": year,
"vod_area": area,
"vod_content": content,
"vod_play_from": xianlu,
"vod_play_url": bofang
})
result['list'] = videos
return result
def playerContent(self, flag, id, vipFlags):
fenge = self.split_id_by_at(id)
fenge1 = self.split_first_part_by_comma(fenge[0])
detail = requests.get(url=fenge[1], headers=headerx)
detail.encoding = "utf-8"
res = detail.text
url_dictionary = self.parse_url_dictionary(res)
result = self.get_url_from_dictionary(url_dictionary, fenge1)
url = self.decrypt_url(result)
result = {}
result["parse"] = 0
result["playUrl"] = ''
result["url"] = url
result["header"] = headerx
return result
def searchContentPage(self, key, quick, pg):
result = {}
videos = []
url = f'{xurl}/search?wd={key}'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
soups = doc.find_all('div', class_="lists-content")
for item in soups:
vods = item.find_all('li')
videos = self._extract_video_items(vods)
result['list'] = videos
result['page'] = pg
result['pagecount'] = 1
result['limit'] = 90
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
return self.searchContentPage(key, quick, '1')
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None

314
py/哔哩直播.py Normal file
View File

@@ -0,0 +1,314 @@
# coding=utf-8
# !/usr/bin/python
"""
作者 丢丢喵 🚓 内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
====================Diudiumiao====================
"""
from Crypto.Util.Padding import unpad
from Crypto.Util.Padding import pad
from urllib.parse import unquote
from Crypto.Cipher import ARC4
from urllib.parse import quote
from base.spider import Spider
from Crypto.Cipher import AES
from datetime import datetime
from bs4 import BeautifulSoup
from base64 import b64decode
import urllib.request
import urllib.parse
import datetime
import binascii
import requests
import base64
import json
import time
import sys
import re
import os
sys.path.append('..')
xurl = "https://search.bilibili.com"
xurl1 = "https://api.live.bilibili.com"
headerx = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0'
}
class Spider(Spider):
global xurl
global xurl1
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": "", "type_name": "舞蹈"},
{"type_id": "音乐", "type_name": "音乐"},
{"type_id": "手游", "type_name": "手游"},
{"type_id": "网游", "type_name": "网游"},
{"type_id": "单机游戏", "type_name": "单机游戏"},
{"type_id": "虚拟主播", "type_name": "虚拟主播"},
{"type_id": "电台", "type_name": "电台"},
{"type_id": "体育", "type_name": "体育"},
{"type_id": "聊天", "type_name": "聊天"},
{"type_id": "娱乐", "type_name": "娱乐"},
{"type_id": "电影", "type_name": "影视"},
{"type_id": "新闻", "type_name": "新闻"}]
}
return result
def homeVideoContent(self):
pass
def categoryContent(self, cid, pg, filter, ext):
result = {}
videos = []
if pg:
page = int(pg)
else:
page = 1
url = f'{xurl}/live?keyword={cid}&page={str(page)}'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
soups = doc.find_all('div', class_="video-list-item")
for vod in soups:
names = vod.find('h3', class_="bili-live-card__info--tit")
name = names.text.strip().replace('直播中', '')
id = names.find('a')['href']
id = self.extract_middle_text(id, 'bilibili.com/', '?', 0)
pic = vod.find('img')['src']
if 'http' not in pic:
pic = "https:" + pic
remarks = vod.find('a', class_="bili-live-card__info--uname")
remark = remarks.text.strip()
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
did = ids[0]
result = {}
videos = []
xianlu = ''
bofang = ''
url = f'{xurl1}/xlive/web-room/v2/index/getRoomPlayInfo?room_id={did}&platform=web&protocol=0,1&format=0,1,2&codec=0,1'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
data = detail.json()
content = '欢迎观看哔哩直播'
setup = data['data']['playurl_info']['playurl']['stream']
nam = 0
for vod in setup:
try:
host = vod['format'][nam]['codec'][0]['url_info'][1]['host']
except (KeyError, IndexError):
continue
base = vod['format'][nam]['codec'][0]['base_url']
extra = vod['format'][nam]['codec'][0]['url_info'][1]['extra']
id = host + base + extra
nam = nam + 1
namc = f"{nam}号线路"
bofang = bofang + namc + '$' + id + '#'
bofang = bofang[:-1]
xianlu = '哔哩专线'
videos.append({
"vod_id": did,
"vod_content": content,
"vod_play_from": xianlu,
"vod_play_url": bofang
})
result['list'] = videos
return result
def playerContent(self, flag, id, vipFlags):
result = {}
result["parse"] = 0
result["playUrl"] = ''
result["url"] = id
result["header"] = headerx
return result
def searchContentPage(self, key, quick, pg):
result = {}
videos = []
if pg:
page = int(pg)
else:
page = 1
url = f'{xurl}/live?keyword={key}&page={str(page)}'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
soups = doc.find_all('div', class_="video-list-item")
for vod in soups:
names = vod.find('h3', class_="bili-live-card__info--tit")
name = names.text.strip().replace('直播中', '')
id = names.find('a')['href']
id = self.extract_middle_text(id, 'bilibili.com/', '?', 0)
pic = vod.find('img')['src']
if 'http' not in pic:
pic = "https:" + pic
remarks = vod.find('a', class_="bili-live-card__info--uname")
remark = remarks.text.strip()
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
return self.searchContentPage(key, quick, '1')
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None

468
py/哔哩视频.py Normal file

File diff suppressed because one or more lines are too long

146
py/嗨皮影视.py Normal file
View File

@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import sys
sys.path.append('..')
from base.spider import Spider
import requests
class Spider(Spider):
def init(self, extend=""):
pass
def getName(self):
return "hitv"
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeContent(self, filter):
result = {}
cateManual = {
# "直播": "live",
'排行榜': 'rank',
"电影": "1",
"剧集": "2",
"综艺": "3",
"动画": "4",
"短片": "5"
}
classes = []
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
result['class'] = classes
return result
host = "https://wys.upfuhn.com"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/80.0.3987.149 Safari/537.36"
}
def list(self, list):
videos = []
for it in list:
videos.append({
"vod_id": it['video_site_id'],
"vod_name": it['video_name'],
"vod_pic": it['video_horizontal_url'] or it['video_vertical_url'],
"vod_remarks": it['newest_series_num'],
"vod_year": it['years'],
})
return videos
def homeVideoContent(self):
url = f'{self.host}/v1/ys_video_sites/hot?t=1'
data = requests.get(url, headers=self.headers).json()
videos = self.list(data['data']['data'])
result = {'list': videos}
return result
def categoryContent(self, tid, pg, filter, extend):
path = f'/v1/ys_video_sites?t={tid}&s_t=0&a&y&o=0&ps=21&pn={pg}'
rank = False
if tid == 'rank':
if pg == 1:
path = f'/v1/ys_video_sites/ranking'
rank = True
else:
path = ''
# elif tid == 'live' and pg == 1:
# path = f'/v1/ys_live_tvs'
videos = []
result = {}
try:
data = requests.get(self.host + path, headers=self.headers).json()
if rank:
for video in data['data']:
videos.extend(data['data'][video])
else:
videos = data['data']['data']
result = {}
result['list'] = self.list(videos)
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
except:
result['list'] = []
return result
def detailContent(self, ids):
tid = ids[0]
url = f'{self.host}/v1/ys_video_series/by_vid/{tid}'
data = requests.get(url, headers=self.headers).json()
data1 = data['data']['ys_video_site']
urls = []
for it in data['data']['data']:
urls.append(it['series_num'] + '$' + it['video_url'])
vod = {
'vod_name': data1['video_name'],
'type_name': data1['tag'],
'vod_year': data1['years'],
'vod_area': data1['area'],
'vod_director': data1['main_actor'],
'vod_content': data1['video_desc'],
'vod_play_from': '嗨皮在线',
'vod_play_url': '#'.join(urls),
}
result = {
'list': [
vod
]
}
return result
def searchContent(self, key, quick, pg=1):
url = f'{self.host}/v1/ys_video_sites/search?s={key}&o=0&ps=200&pn={pg}'
data = requests.get(url, headers=self.headers).json()
videos = data['data']['video_sites']
if data['data']['first_video_series'] is not None:
videos = [data['data']['first_video_series']] + videos
result = {}
result['list'] = self.list(videos)
result['page'] = pg
return result
def playerContent(self, flag, id, vipFlags):
result = {
'url': id,
'parse': 0,
'header': self.headers
}
return result
def localProxy(self, param):
pass

488
py/小鸭看看.py Normal file
View File

@@ -0,0 +1,488 @@
# coding=utf-8
import re
import json
import time
from urllib.parse import quote, urljoin, urlparse, parse_qs
import sys
# 导入外部库,现在可以正常使用
from bs4 import BeautifulSoup
import gzip
sys.path.append("..")
from base.spider import Spider
class Spider(Spider):
def __init__(self):
self.name = "小鸭子看看"
self.hosts = {
"main": "https://xiaoyakankan.com",
"tw": "https://tw.xiaoyakankan.com"
}
self.default_host = "tw"
self.ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
# 视频格式支持
self.VIDEO_FORMATS = ['.m3u8', '.mp4', '.flv', '.avi', '.mkv', '.mov']
def getName(self):
return self.name
def init(self, extend=""):
if extend:
try:
config = json.loads(extend)
if config.get("host") in self.hosts:
self.default_host = config["host"]
self.log(f"已切换默认域名至:{self.hosts[self.default_host]}", "INFO")
except:
self.log("初始化参数解析失败使用默认tw子域名", "WARNING")
def log(self, msg, level="INFO"):
print(f"[{level}] [{self.name}] {time.strftime('%Y-%m-%d %H:%M:%S')} - {msg}")
def get_current_host(self):
return self.hosts[self.default_host]
def homeContent(self, filter):
result = {}
result['class'] = [
{"type_name": "电影", "type_id": "10", "land": "1", "filters": [
{"key": "class", "name": "类型", "value": [
{"n": "全部", "v": "10"},
{"n": "动作片", "v": "1001"},
{"n": "喜剧片", "v": "1002"},
{"n": "爱情片", "v": "1003"},
{"n": "科幻片", "v": "1004"},
{"n": "恐怖片", "v": "1005"},
{"n": "剧情片", "v": "1006"},
{"n": "战争片", "v": "1007"},
{"n": "纪录片", "v": "1008"},
{"n": "微电影", "v": "1009"},
{"n": "动漫电影", "v": "1010"},
{"n": "奇幻片", "v": "1011"},
{"n": "动画片", "v": "1013"},
{"n": "犯罪片", "v": "1014"},
{"n": "悬疑片", "v": "1016"},
{"n": "欧美片", "v": "1017"},
{"n": "邵氏电影", "v": "1019"},
{"n": "同性片", "v": "1021"},
{"n": "家庭片", "v": "1024"},
{"n": "古装片", "v": "1025"},
{"n": "历史片", "v": "1026"},
{"n": "4K电影", "v": "1027"}
]}
]},
{"type_name": "连续剧", "type_id": "11", "land": "1", "filters": [
{"key": "class", "name": "地区类型", "value": [
{"n": "全部", "v": "11"},
{"n": "国产剧", "v": "1101"},
{"n": "香港剧", "v": "1102"},
{"n": "台湾剧", "v": "1105"},
{"n": "韩国剧", "v": "1103"},
{"n": "欧美剧", "v": "1104"},
{"n": "日本剧", "v": "1106"},
{"n": "泰国剧", "v": "1108"},
{"n": "港台剧", "v": "1110"},
{"n": "日韩剧", "v": "1111"},
{"n": "东南亚剧", "v": "1112"},
{"n": "海外剧", "v": "1107"}
]}
]},
{"type_name": "综艺", "type_id": "12", "land": "1", "filters": [
{"key": "class", "name": "地区类型", "value": [
{"n": "全部", "v": "12"},
{"n": "内地综艺", "v": "1201"},
{"n": "港台综艺", "v": "1202"},
{"n": "日韩综艺", "v": "1203"},
{"n": "欧美综艺", "v": "1204"},
{"n": "国外综艺", "v": "1205"}
]}
]},
{"type_name": "动漫", "type_id": "13", "land": "1", "filters": [
{"key": "class", "name": "地区类型", "value": [
{"n": "全部", "v": "13"},
{"n": "国产动漫", "v": "1301"},
{"n": "日韩动漫", "v": "1302"},
{"n": "欧美动漫", "v": "1303"},
{"n": "海外动漫", "v": "1305"},
{"n": "里番", "v": "1307"}
]}
]},
{"type_name": "福利", "type_id": "15", "land": "1", "filters": [
{"key": "class", "name": "地区类型", "value": [
{"n": "全部", "v": "15"},
{"n": "韩国情色片", "v": "1551"},
{"n": "日本情色片", "v": "1552"},
{"n": "大陆情色片", "v": "1555"},
{"n": "香港情色片", "v": "1553"},
{"n": "台湾情色片", "v": "1554"},
{"n": "美国情色片", "v": "1556"},
{"n": "欧洲情色片", "v": "1557"},
{"n": "印度情色片", "v": "1558"},
{"n": "东南亚情色片", "v": "1559"},
{"n": "其它情色片", "v": "1550"}
]}
]}
]
# 将所有筛选器数据添加进 result['filters'] 中
result['filters'] = {
"10": result['class'][0]['filters'],
"11": result['class'][1]['filters'],
"12": result['class'][2]['filters'],
"13": result['class'][3]['filters'],
"15": result['class'][4]['filters'],
}
return result
def homeVideoContent(self):
try:
url = self.get_current_host()
r = self.fetch(url, headers={"User-Agent": self.ua})
if r.status_code != 200:
self.log(f"首页推荐请求失败,状态码:{r.status_code}", "ERROR")
return {'list': []}
# 使用新的正则表达式来获取视频列表项
pattern = r'<a class="link" href="(/post/[^"]+\.html)".*?<img[^>]*data-src="([^"]+)".*?alt="([^"]+)".*?(?:<div class="tag1[^>]*>([^<]+)</div>)?.*?(?:<div class="tag2">([^<]+)</div>)?'
matches = re.findall(pattern, r.text, re.DOTALL)
video_list = []
for match in matches[:12]: # 限制12个结果
try:
link, img_src, title, tag1, tag2 = match
vod_id_match = re.search(r'/post/(.*?)\.html', link)
if not vod_id_match:
continue
vod_id = vod_id_match.group(1)
# 组合备注信息
remarks = []
if tag1:
remarks.append(tag1.strip())
if tag2:
remarks.append(tag2.strip())
vod_remarks = " / ".join(remarks) if remarks else "最新"
# 处理图片URL
if img_src.startswith('//'):
img_url = 'https:' + img_src
elif not img_src.startswith('http'):
img_url = urljoin(self.get_current_host(), img_src)
else:
img_url = img_src
vod = {
'vod_id': vod_id,
'vod_name': title.strip(),
'vod_pic': img_url,
'vod_remarks': vod_remarks
}
video_list.append(vod)
except Exception as e:
self.log(f"首页推荐项解析失败:{str(e)}", "ERROR")
continue
self.log(f"首页推荐成功解析{len(video_list)}个项", "INFO")
return {'list': video_list}
except Exception as e:
self.log(f"首页推荐内容获取失败:{str(e)}", "ERROR")
return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
result = {'list': [], 'page': pg, 'pagecount': 1, 'limit': 40, 'total': 0}
try:
# 修复:检查 extend 参数,以支持筛选功能
filter_tid = tid
if extend and 'class' in extend and extend['class']:
filter_tid = extend['class']
# 修复分类URL构建使用 filter_tid
url = f"{self.get_current_host()}/cat/{filter_tid}"
if int(pg) > 1:
url = f"{url}-{pg}"
url = f"{url}.html"
r = self.fetch(url, headers={"User-Agent": self.ua})
if r.status_code != 200:
self.log(f"分类页请求失败URL{url},状态码:{r.status_code}", "ERROR")
return result
# 修复:使用更健壮的正则来提取所有视频列表项
items = re.findall(r'<div class="item">(.*?)<a class="title"', r.text, re.DOTALL)
for item in items:
try:
link_match = re.search(r'<a class="link" href="(/post/[^"]+\.html)"', item)
img_match = re.search(r'<img[^>]*data-src="([^"]+)"', item)
title_match = re.search(r'data-src="[^"]+" alt="([^"]+)"', item)
tag1_match = re.search(r'<div class="tag1[^>]*>([^<]+)</div>', item)
tag2_match = re.search(r'<div class="tag2">([^<]+)</div>', item)
if not link_match or not img_match or not title_match:
continue
link = link_match.group(1)
img_src = img_match.group(1)
title = title_match.group(1).strip()
vod_id = re.search(r'/post/(.*?)\.html', link).group(1)
remarks = []
if tag1_match:
remarks.append(tag1_match.group(1).strip())
if tag2_match:
remarks.append(tag2_match.group(1).strip())
vod_remarks = " / ".join(remarks) if remarks else "分类内容"
# 处理图片URL
if img_src.startswith('//'):
img_url = 'https:' + img_src
elif not img_src.startswith('http'):
img_url = urljoin(self.get_current_host(), img_src)
else:
img_url = img_src
vod = {
'vod_id': vod_id,
'vod_name': title,
'vod_pic': img_url,
'vod_remarks': vod_remarks
}
result['list'].append(vod)
except Exception as e:
self.log(f"分类项解析失败:{str(e)}", "ERROR")
continue
# 修复:使用更健壮的正则来提取分页信息
page_pattern = r'/cat/\d+-(\d+)\.html'
page_matches = re.findall(page_pattern, r.text)
if page_matches:
page_nums = [int(num) for num in page_matches if num.isdigit()]
result['pagecount'] = max(page_nums) if page_nums else 1
else:
result['pagecount'] = int(pg)
self.log(f"分类{tid}{pg}页:解析{len(result['list'])}", "INFO")
return result
except Exception as e:
self.log(f"分类内容获取失败:{str(e)}", "ERROR")
return result
def detailContent(self, ids):
result = {"list": []}
if not ids:
return result
vod_id = ids[0]
try:
detail_url = f"{self.get_current_host()}/post/{vod_id}.html"
r = self.fetch(detail_url, headers={"User-Agent": self.ua})
if r.status_code != 200:
self.log(f"详情页请求失败,状态码:{r.status_code}", "ERROR")
return result
# 提取标题
title_pattern = r'<title>([^<]+)</title>'
title_match = re.search(title_pattern, r.text)
title = title_match.group(1).replace(" - 小鴨看看", "").strip() if title_match else "未知标题"
# 提取封面图
cover_pattern = r'data-poster="([^"]+)"'
cover_match = re.search(cover_pattern, r.text)
cover_url = ""
if cover_match:
cover_url = cover_match.group(1)
if cover_url.startswith('//'):
cover_url = 'https:' + cover_url
elif not cover_url.startswith('http'):
cover_url = urljoin(self.get_current_host(), cover_url)
# 提取描述
desc_pattern = r'<meta name="description" content="([^"]*)"'
desc_match = re.search(desc_pattern, r.text)
desc = desc_match.group(1) if desc_match else ""
# 提取播放线路和剧集
play_sources = []
play_urls = []
# 从JavaScript中提取播放信息
script_pattern = r'var pp\s*=\s*({.*?});'
script_match = re.search(script_pattern, r.text, re.DOTALL)
if script_match:
try:
pp_data = json.loads(script_match.group(1))
if 'lines' in pp_data:
for line in pp_data['lines']:
if len(line) >= 4:
line_name = line[1] if isinstance(line[1], str) else f"线路{line[0]}"
play_sources.append(line_name)
episodes = []
urls = line[3] if isinstance(line[3], list) and len(line[3]) > 0 else []
for idx, url in enumerate(urls):
if isinstance(url, str) and any(url.endswith(fmt) for fmt in self.VIDEO_FORMATS):
# 修复集数命名逻辑
episode_name_match = re.search(r'ep-([\d\w]+)', url)
if episode_name_match:
episode_name = f"{episode_name_match.group(1)}"
elif len(urls) == 1:
episode_name = "全集"
else:
episode_name = f"{idx+1}"
episodes.append(f"{episode_name}${url}")
if episodes:
play_urls.append("#".join(episodes))
except Exception as e:
self.log(f"解析JavaScript播放信息失败{str(e)}", "ERROR")
# 如果没有从JS中提取到尝试从HTML中提取
if not play_sources:
source_pattern = r'<div class="source"[^>]*>.*?<span class="name">([^<]+)</span>.*?<div class="list">(.*?)</div>'
source_matches = re.findall(source_pattern, r.text, re.DOTALL)
for source_name, list_html in source_matches:
play_sources.append(source_name.strip())
# 提取剧集
episode_pattern = r'<a[^>]*data-sou_idx="\d+"[^>]*>([^<]+)</a>'
episode_matches = re.findall(episode_pattern, list_html)
episodes = []
for idx, ep_name in enumerate(episode_matches):
episodes.append(f"{ep_name.strip()}${idx}")
if episodes:
play_urls.append("#".join(episodes))
vod = {
"vod_id": vod_id,
"vod_name": title,
"vod_pic": cover_url,
"vod_content": desc,
"vod_play_from": "$$$".join(play_sources) if play_sources else "",
"vod_play_url": "$$$".join(play_urls) if play_urls else ""
}
result["list"].append(vod)
self.log(f"详情页解析成功ID{vod_id}", "INFO")
return result
except Exception as e:
self.log(f"详情页解析失败ID{vod_id},错误:{str(e)}", "ERROR")
return result
def playerContent(self, flag, id, vipFlags):
try:
# 如果id已经是URL直接返回
if id.startswith('http'):
return {
"parse": 0,
"playUrl": '',
"url": id,
"header": {
"User-Agent": self.ua,
"Referer": self.get_current_host() + "/"
}
}
# 这是一个简单的播放器URL解析如果原始URL本身就是有效的播放地址就直接返回
return {
"parse": 0,
"playUrl": '',
"url": id,
"header": {
"User-Agent": self.ua,
"Referer": self.get_current_host() + "/"
}
}
except Exception as e:
self.log(f"播放地址解析失败:{str(e)}", "ERROR")
return {"parse": 0, "playUrl": '', "url": id, "header": {"User-Agent": self.ua}}
def searchContent(self, key, quick):
result = {"list": []}
try:
# 构造Google搜索URL带站点限定
google_search_url = f"https://www.google.com/search?q={quote(key)}&sitesearch=xiaoyakankan.com"
self.log(f"构造Google搜索URL: {google_search_url}")
headers = {
"User-Agent": self.ua,
"Referer": self.get_current_host(),
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive"
}
r = self.fetch(google_search_url, headers=headers, timeout=10)
if r.status_code != 200:
self.log(f"Google搜索请求失败状态码{r.status_code},内容:{r.text[:200]}", "ERROR")
return result
# 处理gzip压缩响应
if 'gzip' in r.headers.get('Content-Encoding', ''):
r._content = gzip.decompress(r.content)
try:
soup = BeautifulSoup(r.text, 'html.parser')
# 寻找包含链接和标题的元素
search_results_div = soup.find_all('div', {'data-hveid': re.compile(r'\d+')})
for item in search_results_div:
a_tag = item.find('a', href=re.compile(r'https?://'))
if not a_tag:
continue
link = a_tag['href']
# 检查链接是否属于目标站点
if self.get_current_host() not in link:
continue
# 尝试从链接中提取影片ID和标题
vod_id_match = re.search(r'/post/([^/]+)\.html', link)
if not vod_id_match:
continue
vod_id = vod_id_match.group(1)
title_tag = item.find('h3')
title = title_tag.text.strip() if title_tag else "未知标题"
vod = {
"vod_id": vod_id,
"vod_name": title,
# Google搜索结果通常不包含影片封面这里留空
"vod_pic": "",
"vod_remarks": "Google搜索结果"
}
result["list"].append(vod)
except Exception as e:
self.log(f"解析Google搜索结果失败{str(e)}", "ERROR")
return result
self.log(f"Google搜索成功解析{len(result['list'])}个项", "INFO")
return result
except Exception as e:
self.log(f"搜索内容获取失败:{str(e)}", "ERROR")
return result
def isVideoFormat(self, url):
"""判断是否为视频格式"""
return any(url.lower().endswith(fmt) for fmt in self.VIDEO_FORMATS)
def manualVideoCheck(self):
pass
def localProxy(self, param):
pass

169
py/想看影视.py Normal file
View File

@@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import re
import sys
from Crypto.Hash import MD5
sys.path.append("..")
import json
import time
from pyquery import PyQuery as pq
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 action(self, action):
pass
def destroy(self):
pass
host = 'https://www.lreeok.vip'
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': 'application/json, text/javascript, */*; q=0.01',
'sec-ch-ua-platform': '"macOS"',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="134", "Google Chrome";v="134"',
'Origin': host,
'Referer': f"{host}/",
}
def homeContent(self, filter):
data = self.getpq(self.fetch(self.host, headers=self.headers).text)
result = {}
classes = []
for k in data('.head-more.box a').items():
i = k.attr('href')
if i and '/vod' in i:
classes.append({
'type_name': k.text(),
'type_id': re.search(r'\d+', i).group(0)
})
result['class'] = classes
result['list'] = self.getlist(data('.border-box.diy-center .public-list-div'))
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
body = {'type': tid, 'class': '', 'area': '', 'lang': '', 'version': '', 'state': '', 'letter': '', 'page': pg}
data = self.post(f"{self.host}/index.php/api/vod", headers=self.headers, data=self.getbody(body)).json()
result = {}
result['list'] = data['list']
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
data = self.getpq(self.fetch(f"{self.host}/voddetail/{ids[0]}.html", headers=self.headers).text)
v = data('.detail-info.lightSpeedIn .slide-info')
vod = {
'vod_year': v.eq(-1).text(),
'vod_remarks': v.eq(0).text(),
'vod_actor': v.eq(3).text(),
'vod_director': v.eq(2).text(),
'vod_content': data('.switch-box #height_limit').text()
}
np = data('.anthology.wow.fadeInUp')
ndata = np('.anthology-tab .swiper-wrapper .swiper-slide')
pdata = np('.anthology-list .anthology-list-box ul')
play, names = [], []
for i in range(len(ndata)):
n = ndata.eq(i)('a')
n('span').remove()
names.append(n.text())
vs = []
for v in pdata.eq(i)('li').items():
vs.append(f"{v.text()}${v('a').attr('href')}")
play.append('#'.join(vs))
vod["vod_play_from"] = "$$$".join(names)
vod["vod_play_url"] = "$$$".join(play)
result = {"list": [vod]}
return result
def searchContent(self, key, quick, pg="1"):
# data = self.getpq(self.fetch(f"{self.host}/vodsearch/{key}----------{pg}---.html", headers=self.headers).text)
# return {'list': self.getlist(data('.row-right .search-box .public-list-bj')), 'page': pg}
data = self.fetch(
f"{self.host}/index.php/ajax/suggest?mid={pg}&wd={key}&limit=999&timestamp={int(time.time() * 1000)}",
headers=self.headers).json()
videos = []
for i in data['list']:
videos.append({
'vod_id': i['id'],
'vod_name': i['name'],
'vod_pic': i['pic']
})
return {'list': videos, 'page': pg}
def playerContent(self, flag, id, vipFlags):
h, p = {"User-Agent": "okhttp/3.14.9"}, 1
url = f"{self.host}{id}"
data = self.getpq(self.fetch(url, headers=self.headers).text)
try:
jstr = data('.player .player-left script').eq(0).text()
jsdata = json.loads(jstr.split('aaa=')[-1])
body = {'url': jsdata['url']}
if not re.search(r'\.m3u8|\.mp4', body['url']):
data = self.post(f"{self.host}/okplay/api_config.php", headers=self.headers,
data=self.getbody(body)).json()
url = data.get('url') or data.get('data', {}).get('url')
p = 0
except Exception as e:
print('错误信息:', e)
pass
result = {}
result["parse"] = p
result["url"] = url
result["header"] = h
return result
def localProxy(self, param):
pass
def getbody(self, params):
t = int(time.time())
h = MD5.new()
h.update(f"DS{t}DCC147D11943AF75".encode('utf-8'))
key = h.hexdigest()
params.update({'time': t, 'key': key})
return params
def getlist(self, data):
videos = []
for i in data.items():
id = i('a').attr('href')
if id:
id = re.search(r'\d+', id).group(0)
img = i('img').attr('data-src')
if img and 'url=' in img: img = f'{self.host}{img}'
videos.append({
'vod_id': id,
'vod_name': i('img').attr('alt'),
'vod_pic': img,
'vod_remarks': i('.public-prt').text() or i('.public-list-prb').text()
})
return videos
def getpq(self, data):
try:
return pq(data)
except Exception as e:
print(f"{str(e)}")
return pq(data.encode('utf-8'))

343
py/星芽短剧.py Normal file
View File

@@ -0,0 +1,343 @@
# coding = utf-8
# !/usr/bin/python
"""
"""
from Crypto.Util.Padding import unpad
from Crypto.Util.Padding import pad
from urllib.parse import unquote
from Crypto.Cipher import ARC4
from urllib.parse import quote
from base.spider import Spider
from Crypto.Cipher import AES
from bs4 import BeautifulSoup
from base64 import b64decode
import urllib.request
import urllib.parse
import binascii
import requests
import base64
import json
import time
import sys
import re
import os
sys.path.append('..')
xurl = "https://app.whjzjx.cn"
headers = {
'User-Agent': 'Linux; Android 12; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.101 Mobile Safari/537.36'
}
headerf = {
"platform": "1",
"user_agent": "Mozilla/5.0 (Linux; Android 9; V1938T Build/PQ3A.190705.08211809; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36",
"content-type": "application/json; charset=utf-8"
}
times = int(time.time() * 1000)
data = {
"device": "2a50580e69d38388c94c93605241fb306",
"package_name": "com.jz.xydj",
"android_id": "ec1280db12795506",
"install_first_open": True,
"first_install_time": 1752505243345,
"last_update_time": 1752505243345,
"report_link_url": "",
"authorization": "",
"timestamp": times
}
plain_text = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
key = "B@ecf920Od8A4df7"
key_bytes = key.encode('utf-8')
plain_bytes = plain_text.encode('utf-8')
cipher = AES.new(key_bytes, AES.MODE_ECB)
padded_data = pad(plain_bytes, AES.block_size)
ciphertext = cipher.encrypt(padded_data)
encrypted = base64.b64encode(ciphertext).decode('utf-8')
response = requests.post("https://u.shytkjgs.com/user/v3/account/login", headers=headerf, data=encrypted)
response_data = response.json()
Authorization = response_data['data']['token']
headerx = {
'authorization': Authorization,
'platform': '1',
'version_name': '3.8.3.1'
}
class Spider(Spider):
global xurl
global headerx
global headers
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": "1", "type_name": "剧场"},
{"type_id": "3", "type_name": "新剧"},
{"type_id": "2", "type_name": "热播"},
{"type_id": "7", "type_name": "星选"},
{"type_id": "5", "type_name": "阳光"}],
}
return result
def homeVideoContent(self):
videos = []
url= f'{xurl}/v1/theater/home_page?theater_class_id=1&class2_id=4&page_num=1&page_size=24'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
if detail.status_code == 200:
data = detail.json()
for vod in data['data']['list']:
name = vod['theater']['title']
id = vod['theater']['id']
pic = vod['theater']['cover_url']
remark = vod['theater']['play_amount_str']
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
result = {'list': videos}
return result
def categoryContent(self, cid, pg, filter, ext):
result = {}
videos = []
url = f'{xurl}/v1/theater/home_page?theater_class_id={cid}&page_num={pg}&page_size=24'
detail = requests.get(url=url,headers=headerx)
detail.encoding = "utf-8"
if detail.status_code == 200:
data = detail.json()
for vod in data['data']['list']:
name = vod['theater']['title']
id = vod['theater']['id']
pic = vod['theater']['cover_url']
remark = vod['theater']['theme']
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
did = ids[0]
result = {}
videos = []
xianlu = ''
bofang = ''
url = f'{xurl}/v2/theater_parent/detail?theater_parent_id={did}'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
if detail.status_code == 200:
data = detail.json()
url = 'https://fs-im-kefu.7moor-fs1.com/ly/4d2c3f00-7d4c-11e5-af15-41bf63ae4ea0/1732707176882/jiduo.txt'
response = requests.get(url)
response.encoding = 'utf-8'
code = response.text
name = self.extract_middle_text(code, "s1='", "'", 0)
Jumps = self.extract_middle_text(code, "s2='", "'", 0)
content = '剧情:' + data['data']['introduction']
area = data['data']['desc_tags'][0]
remarks = data['data']['filing']
# 修复剧集只有一集的问题 - 检查theaters数据是否存在且不为空
if 'theaters' in data['data'] and data['data']['theaters']:
for sou in data['data']['theaters']:
id = sou['son_video_url']
name = sou['num']
bofang = bofang + str(name) + '$' + id + '#'
bofang = bofang[:-1] if bofang.endswith('#') else bofang
xianlu = '星芽'
else:
# 如果没有theaters数据检查是否有单个视频URL
if 'video_url' in data['data'] and data['data']['video_url']:
bofang = '1$' + data['data']['video_url']
xianlu = '星芽'
else:
bofang = Jumps
xianlu = '1'
videos.append({
"vod_id": did,
"vod_content": content,
"vod_remarks": remarks,
"vod_area": area,
"vod_play_from": xianlu,
"vod_play_url": bofang
})
result['list'] = videos
return result
def playerContent(self, flag, id, vipFlags):
result = {}
result["parse"] = 0
result["playUrl"] = ''
result["url"] = id
result["header"] = headers
return result
def searchContentPage(self, key, quick, page):
result = {}
videos = []
payload = {
"text": key
}
url = f"{xurl}/v3/search"
detail = requests.post(url=url, headers=headerx, json=payload)
if detail.status_code == 200:
detail.encoding = "utf-8"
data = detail.json()
for vod in data['data']['theater']['search_data']:
name = vod['title']
id = vod['id']
pic = vod['cover_url']
remark = vod['score_str']
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
result['list'] = videos
result['page'] = page
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
return self.searchContentPage(key, quick, '1')
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None

380
py/河马短剧.py Normal file
View File

@@ -0,0 +1,380 @@
# -*- coding: utf-8 -*-
import requests
import re
import json
import traceback
import sys
from urllib.parse import quote
sys.path.append('../../')
try:
from base.spider import Spider
except ImportError:
# 定义一个基础接口类,用于本地测试
class Spider:
def init(self, extend=""):
pass
class Spider(Spider):
def __init__(self):
self.siteUrl = "https://www.kuaikaw.cn"
self.cateManual = {
"甜宠": "462",
"古装仙侠": "1102",
"现代言情": "1145",
"青春": "1170",
"豪门恩怨": "585",
"逆袭": "417-464",
"重生": "439-465",
"系统": "1159",
"总裁": "1147",
"职场商战": "943"
}
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 Edg/120.0.0.0",
"Referer": self.siteUrl,
"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,en;q=0.8"
}
def getName(self):
return "河马短剧"
def init(self, extend=""):
return
def fetch(self, url, headers=None, retry=2):
"""统一的网络请求接口"""
if headers is None:
headers = self.headers
for i in range(retry + 1):
try:
response = requests.get(url, headers=headers, timeout=10, allow_redirects=True)
response.raise_for_status()
return response
except Exception as e:
if i == retry:
print(f"请求异常: {url}, 错误: {str(e)}")
return None
continue
def isVideoFormat(self, url):
video_formats = ['.mp4', '.mkv', '.avi', '.wmv', '.m3u8', '.flv', '.rmvb']
return any(format in url.lower() for format in video_formats)
def manualVideoCheck(self):
return False
def homeContent(self, filter):
result = {}
classes = [{'type_name': k, 'type_id': v} for k, v in self.cateManual.items()]
result['class'] = classes
try:
result['list'] = self.homeVideoContent()['list']
except:
result['list'] = []
return result
def homeVideoContent(self):
videos = []
try:
response = self.fetch(self.siteUrl)
if not response:
return {'list': []}
html_content = response.text
next_data_pattern = r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>'
next_data_match = re.search(next_data_pattern, html_content, re.DOTALL)
if not next_data_match:
return {'list': []}
next_data_json = json.loads(next_data_match.group(1))
page_props = next_data_json.get("props", {}).get("pageProps", {})
# 处理轮播图数据
if "bannerList" in page_props:
for banner in page_props["bannerList"]:
if banner.get("bookId"):
videos.append({
"vod_id": f"/drama/{banner['bookId']}",
"vod_name": banner.get("bookName", ""),
"vod_pic": banner.get("coverWap", ""),
"vod_remarks": f"{banner.get('statusDesc', '')} {banner.get('totalChapterNum', '')}".strip()
})
# 处理SEO分类推荐
if "seoColumnVos" in page_props:
for column in page_props["seoColumnVos"]:
for book in column.get("bookInfos", []):
if book.get("bookId"):
videos.append({
"vod_id": f"/drama/{book['bookId']}",
"vod_name": book.get("bookName", ""),
"vod_pic": book.get("coverWap", ""),
"vod_remarks": f"{book.get('statusDesc', '')} {book.get('totalChapterNum', '')}".strip()
})
# 去重处理
seen = set()
unique_videos = []
for video in videos:
key = (video["vod_id"], video["vod_name"])
if key not in seen:
seen.add(key)
unique_videos.append(video)
except Exception as e:
print(f"获取首页推荐内容出错: {e}")
unique_videos = []
return {'list': unique_videos}
def categoryContent(self, tid, pg, filter, extend):
result = {'list': [], 'page': pg, 'pagecount': 1, 'limit': 20, 'total': 0}
url = f"{self.siteUrl}/browse/{tid}/{pg}"
response = self.fetch(url)
if not response:
return result
html_content = response.text
next_data_match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html_content, re.DOTALL)
if not next_data_match:
return result
try:
next_data_json = json.loads(next_data_match.group(1))
page_props = next_data_json.get("props", {}).get("pageProps", {})
current_page = page_props.get("page", 1)
total_pages = page_props.get("pages", 1)
book_list = page_props.get("bookList", [])
videos = []
for book in book_list:
if book.get("bookId"):
videos.append({
"vod_id": f"/drama/{book['bookId']}",
"vod_name": book.get("bookName", ""),
"vod_pic": book.get("coverWap", ""),
"vod_remarks": f"{book.get('statusDesc', '')} {book.get('totalChapterNum', '')}".strip()
})
result.update({
'list': videos,
'page': int(current_page),
'pagecount': total_pages,
'limit': len(videos),
'total': len(videos) * total_pages if videos else 0
})
except Exception as e:
print(f"分类内容获取出错: {e}")
return result
def searchContent(self, key, quick, pg=1):
return self.searchContentPage(key, quick, pg)
def searchContentPage(self, key, quick, pg=1):
result = {'list': [], 'page': pg, 'pagecount': 1, 'limit': 20, 'total': 0}
search_url = f"{self.siteUrl}/search?searchValue={quote(key)}&page={pg}"
response = self.fetch(search_url)
if not response:
return result
html_content = response.text
next_data_match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html_content, re.DOTALL)
if not next_data_match:
return result
try:
next_data_json = json.loads(next_data_match.group(1))
page_props = next_data_json.get("props", {}).get("pageProps", {})
total_pages = page_props.get("pages", 1)
book_list = page_props.get("bookList", [])
videos = []
for book in book_list:
if book.get("bookId"):
videos.append({
"vod_id": f"/drama/{book['bookId']}",
"vod_name": book.get("bookName", ""),
"vod_pic": book.get("coverWap", ""),
"vod_remarks": f"{book.get('statusDesc', '')} {book.get('totalChapterNum', '')}".strip()
})
result.update({
'list': videos,
'pagecount': total_pages,
'total': len(videos) * total_pages if videos else 0
})
except Exception as e:
print(f"搜索内容出错: {e}")
return result
def detailContent(self, ids):
result = {'list': []}
if not ids:
return result
vod_id = ids[0]
if not vod_id.startswith('/drama/'):
vod_id = f'/drama/{vod_id}'
drama_url = f"{self.siteUrl}{vod_id}"
response = self.fetch(drama_url)
if not response:
return result
html = response.text
next_data_match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html, re.DOTALL)
if not next_data_match:
return result
try:
next_data = json.loads(next_data_match.group(1))
page_props = next_data.get("props", {}).get("pageProps", {})
book_info = page_props.get("bookInfoVo", {})
chapter_list = page_props.get("chapterList", [])
if not book_info.get("bookId"):
return result
# 基本信息
categories = [c.get("name", "") for c in book_info.get("categoryList", [])]
performers = [p.get("name", "") for p in book_info.get("performerList", [])]
vod = {
"vod_id": vod_id,
"vod_name": book_info.get("title", ""),
"vod_pic": book_info.get("coverWap", ""),
"type_name": ",".join(categories),
"vod_year": "",
"vod_area": book_info.get("countryName", ""),
"vod_remarks": f"{book_info.get('statusDesc', '')} {book_info.get('totalChapterNum', '')}".strip(),
"vod_actor": ", ".join(performers),
"vod_director": "",
"vod_content": book_info.get("introduction", "")
}
# 处理剧集
play_urls = self.processEpisodes(vod_id, chapter_list)
if play_urls:
vod['vod_play_from'] = '河马剧场'
vod['vod_play_url'] = '$$$'.join(play_urls)
result['list'] = [vod]
except Exception as e:
print(f"详情页解析出错: {e}")
traceback.print_exc()
return result
def processEpisodes(self, vod_id, chapter_list):
play_urls = []
episodes = []
for chapter in chapter_list:
chapter_id = chapter.get("chapterId", "")
chapter_name = chapter.get("chapterName", "")
if not chapter_id or not chapter_name:
continue
# 尝试获取直接视频链接
video_url = self.getDirectVideoUrl(chapter)
if video_url:
episodes.append(f"{chapter_name}${video_url}")
continue
# 回退方案
episodes.append(f"{chapter_name}${vod_id}${chapter_id}${chapter_name}")
if episodes:
play_urls.append("#".join(episodes))
return play_urls
def getDirectVideoUrl(self, chapter):
if "chapterVideoVo" not in chapter or not chapter["chapterVideoVo"]:
return None
video_info = chapter["chapterVideoVo"]
for key in ["mp4", "mp4720p", "vodMp4Url"]:
if key in video_info and video_info[key] and ".mp4" in video_info[key].lower():
return video_info[key]
return None
def playerContent(self, flag, id, vipFlags):
result = {
"parse": 0,
"url": id,
"header": json.dumps(self.headers)
}
# 如果已经是视频链接直接返回
if 'http' in id and ('.mp4' in id or '.m3u8' in id):
return result
# 解析参数
parts = id.split('$')
if len(parts) < 2:
return result
drama_id = parts[0].replace('/drama/', '')
chapter_id = parts[1]
# 尝试获取视频链接
video_url = self.getEpisodeVideoUrl(drama_id, chapter_id)
if video_url:
result["url"] = video_url
return result
def getEpisodeVideoUrl(self, drama_id, chapter_id):
episode_url = f"{self.siteUrl}/episode/{drama_id}/{chapter_id}"
response = self.fetch(episode_url)
if not response:
return None
html = response.text
# 方法1: 从NEXT_DATA提取
next_data_match = re.search(r'<script id="__NEXT_DATA__".*?>(.*?)</script>', html, re.DOTALL)
if next_data_match:
try:
next_data = json.loads(next_data_match.group(1))
page_props = next_data.get("props", {}).get("pageProps", {})
chapter_info = page_props.get("chapterInfo", {})
if chapter_info and "chapterVideoVo" in chapter_info:
video_info = chapter_info["chapterVideoVo"]
for key in ["mp4", "mp4720p", "vodMp4Url"]:
if key in video_info and video_info[key] and ".mp4" in video_info[key].lower():
return video_info[key]
except:
pass
# 方法2: 直接从HTML提取
mp4_matches = re.findall(r'(https?://[^"\']+\.mp4)', html)
if mp4_matches:
for url in mp4_matches:
if chapter_id in url or drama_id in url:
return url
return mp4_matches[0]
return None
def localProxy(self, param):
return [200, "video/MP2T", {}, param]
def destroy(self):
pass

301
py/火车影视.py Normal file
View File

@@ -0,0 +1,301 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import sys
from urllib.parse import urlparse
sys.path.append("..")
import re
import hashlib
import hmac
import random
import string
from Crypto.Util.Padding import unpad
from concurrent.futures import ThreadPoolExecutor
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5, AES
from base64 import b64encode, b64decode
import json
import time
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.device = self.device_id()
self.host = self.gethost()
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def action(self, action):
pass
def destroy(self):
pass
def homeContent(self, filter):
result = {}
filters = {}
classes = []
bba = self.url()
data = self.fetch(f"{self.host}/api/v1/app/config?pack={bba[0]}&signature={bba[1]}", headers=self.header()).text
data1 = self.aes(data)
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
"sort": "排序"}
data1['data']['movie_screen']['sort'].pop(0)
for item in data1['data']['movie_screen']['sort']:
item['n'] = item.pop('name')
item['v'] = item.pop('value')
for item in data1['data']['movie_screen']['filter']:
has_non_empty_field = False
classes.append({"type_name": item["name"], "type_id": str(item["id"])})
for key in dy:
if key in item and item[key]:
has_non_empty_field = True
break
if has_non_empty_field:
filters[str(item["id"])] = []
filters[str(item["id"])].append(
{"key": 'sort', "name": '排序', "value": data1['data']['movie_screen']['sort']})
for dkey in item:
if dkey in dy and item[dkey]:
item[dkey].pop(0)
value_array = [
{"n": value.strip(), "v": value.strip()}
for value in item[dkey]
if value.strip() != ""
]
filters[str(item["id"])].append(
{"key": dkey, "name": dy[dkey], "value": value_array}
)
result["class"] = classes
result["filters"] = filters
return result
def homeVideoContent(self):
bba = self.url()
url = f'{self.host}/api/v1/movie/index_recommend?pack={bba[0]}&signature={bba[1]}'
data = self.fetch(url, headers=self.header()).json()
videos = []
for item in data['data']:
if len(item['list']) > 0:
for it in item['list']:
try:
videos.append(self.voides(it))
except Exception as e:
continue
result = {"list": videos}
return result
def categoryContent(self, tid, pg, filter, extend):
body = {"type_id": tid, "sort": extend.get("sort", "by_default"), "class": extend.get("class", "类型"),
"area": extend.get("area", "地区"), "year": extend.get("year", "年份"), "page": str(pg),
"pageSize": "21"}
result = {}
list = []
bba = self.url(body)
url = f"{self.host}/api/v1/movie/screen/list?pack={bba[0]}&signature={bba[1]}"
data = self.fetch(url, headers=self.header()).json()['data']['list']
for item in data:
list.append(self.voides(item))
result["list"] = list
result["page"] = pg
result["pagecount"] = 9999
result["limit"] = 90
result["total"] = 999999
return result
def detailContent(self, ids):
body = {"id": ids[0]}
bba = self.url(body)
url = f'{self.host}/api/v1/movie/detail?pack={bba[0]}&signature={bba[1]}'
data = self.fetch(url, headers=self.header()).json()['data']
video = {'vod_name': data.get('name'), 'type_name': data.get('type_name'), 'vod_year': data.get('year'),
'vod_area': data.get('area'), 'vod_remarks': data.get('dynami'), 'vod_content': data.get('content')}
play = []
names = []
tasks = []
for itt in data["play_from"]:
name = itt["name"]
a = []
if len(itt["list"]) > 0:
names.append(name)
play.append(self.playeach(itt['list']))
else:
tasks.append({"movie_id": ids[0], "from_code": itt["code"]})
names.append(name)
if tasks:
with ThreadPoolExecutor(max_workers=len(tasks)) as executor:
results = executor.map(self.playlist, tasks)
for result in results:
if result:
play.append(result)
else:
play.append("")
video["vod_play_from"] = "$$$".join(names)
video["vod_play_url"] = "$$$".join(play)
result = {"list": [video]}
return result
def searchContent(self, key, quick, pg=1):
body = {"keyword": key, "sort": "", "type_id": "0", "page": str(pg), "pageSize": "10",
"res_type": "by_movie_name"}
bba = self.url(body)
url = f"{self.host}/api/v1/movie/search?pack={bba[0]}&signature={bba[1]}"
data = self.fetch(url, headers=self.header()).json()['data'].get('list')
videos = []
for it in data:
try:
videos.append(self.voides(it))
except Exception as e:
continue
result = {"list": videos, "page": pg}
return result
def playerContent(self, flag, id, vipFlags):
url = id
if not re.search(r"\.m3u8|\.mp4", url):
try:
data = json.loads(b64decode(id.encode('utf-8')).decode('utf-8'))
bba = self.url(data)
data2 = self.fetch(f"{self.host}/api/v1/movie_addr/parse_url?pack={bba[0]}&signature={bba[1]}",
headers=self.header()).json()['data']
url = data2.get('play_url') or data2.get('download_url')
except Exception as e:
pass
if re.search(r'\.jpg|\.png|\.jpeg', url):
url = self.Mproxy(url)
result = {}
result["parse"] = 0
result["url"] = url
result["header"] = {'user-agent': 'okhttp/4.9.2'}
return result
def localProxy(self, param):
return self.Mlocal(param)
def Mproxy(self, url):
return self.getProxyUrl() + "&url=" + b64encode(url.encode('utf-8')).decode('utf-8') + "&type=m3u8"
def Mlocal(self, param,header=None):
url = self.d64(param["url"])
ydata = self.fetch(url, headers=header, allow_redirects=False)
data = ydata.content.decode('utf-8')
if ydata.headers.get('Location'):
url = ydata.headers['Location']
data = self.fetch(url, headers=header).content.decode('utf-8')
parsed_url = urlparse(url)
durl = parsed_url.scheme + "://" + parsed_url.netloc
lines = data.strip().split('\n')
for index, string in enumerate(lines):
if '#EXT' not in string and 'http' not in string:
last_slash_index = string.rfind('/')
lpath = string[:last_slash_index + 1]
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
data = '\n'.join(lines)
return [200, "application/vnd.apple.mpegur", data]
def device_id(self):
characters = string.ascii_lowercase + string.digits
random_string = ''.join(random.choices(characters, k=32))
return random_string
def gethost(self):
try:
url = 'https://dns.alidns.com/dns-query'
headers = {
'User-Agent': 'okhttp/4.9.2',
'Accept': 'application/dns-message'
}
params = {
'dns': 'AAABAAABAAAAAAAACWJmbTExYXM5ZgdmdXFpeXVuAmNuAAAcAAE'
}
response = self.fetch(url, headers=headers, params=params)
host=self.parse_dns_name(response.content, 12)
return f"https://{host}"
except:
return "https://bfm11as9f.fuqiyun.cn"
def parse_dns_name(self, data, offset):
parts = []
while True:
length = data[offset]
if length == 0:
break
offset += 1
parts.append(data[offset:offset + length].decode('utf-8'))
offset += length
return '.'.join(parts)
def header(self):
headers = {
'User-Agent': 'Android',
'Accept': 'application/prs.55App.v2+json',
'timestamp': str(int(time.time())),
'x-client-setting': '{"pure-mode":0}',
'x-client-uuid': '{"device_id":' + self.device + '}, "type":1,"brand":"Redmi", "model":"M2012K10C", "system_version":30, "sdk_version":"3.1.0.7"}',
'x-client-version': '3096 '
}
return headers
def url(self, id=None):
if not id:
id = {}
id["timestamp"] = str(int(time.time()))
public_key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA02F/kPg5A2NX4qZ5JSns+bjhVMCC6JbTiTKpbgNgiXU+Kkorg6Dj76gS68gB8llhbUKCXjIdygnHPrxVHWfzmzisq9P9awmXBkCk74Skglx2LKHa/mNz9ivg6YzQ5pQFUEWS0DfomGBXVtqvBlOXMCRxp69oWaMsnfjnBV+0J7vHbXzUIkqBLdXSNfM9Ag5qdRDrJC3CqB65EJ3ARWVzZTTcXSdMW9i3qzEZPawPNPe5yPYbMZIoXLcrqvEZnRK1oak67/ihf7iwPJqdc+68ZYEmmdqwunOvRdjq89fQMVelmqcRD9RYe08v+xDxG9Co9z7hcXGTsUquMxkh29uNawIDAQAB'
encrypted_text = json.dumps(id)
public_key = RSA.import_key(b64decode(public_key))
cipher = PKCS1_v1_5.new(public_key)
encrypted_message = cipher.encrypt(encrypted_text.encode('utf-8'))
encrypted_message_base64 = b64encode(encrypted_message).decode('utf-8')
result = encrypted_message_base64.replace('+', '-').replace('/', '_').replace('=', '')
key = '635a580fcb5dc6e60caa39c31a7bde48'
sign = hmac.new(key.encode(), result.encode(), hashlib.md5).hexdigest()
return result, sign
def playlist(self, body):
try:
bba = self.url(body)
url = f'{self.host}/api/v1/movie_addr/list?pack={bba[0]}&signature={bba[1]}'
data = self.fetch(url, headers=self.header()).json()['data']
return self.playeach(data)
except Exception:
return []
def playeach(self, data):
play_urls = []
for it in data:
if re.search(r"mp4|m3u8", it["play_url"]):
play_urls.append(f"{it['episode_name']}${it['play_url']}")
else:
vd={"from_code": it['from_code'], "play_url": it['play_url'], "episode_id": it['episode_id'], "type": "play"}
play_urls.append(
f"{it['episode_name']}${b64encode(json.dumps(vd).encode('utf-8')).decode('utf-8')}"
)
return '#'.join(play_urls)
def voides(self, item):
if item['name'] or item['title']:
voide = {
"vod_id": item.get('id') or item.get('click'),
'vod_name': item.get('name') or item.get('title'),
'vod_pic': item.get('cover') or item.get('image'),
'vod_year': item.get('year') or item.get('label'),
'vod_remarks': item.get('dynamic') or item.get('sub_title')
}
return voide
def aes(self, text):
text = text.replace('-', '+').replace('_', '/') + '=='
key = b"e6d5de5fcc51f53d"
iv = b"2f13eef7dfc6c613"
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size).decode("utf-8")
return json.loads(pt)

248
py/爱奇艺.py Normal file
View File

@@ -0,0 +1,248 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import random
import sys
from base64 import b64encode, b64decode
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urlencode
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.did = self.random_str(32)
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
rhost = 'https://www.iqiyi.com'
hhost='https://mesh.if.iqiyi.com'
dhost='https://miniapp.iqiyi.com'
headers = {
'Origin': rhost,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36',
'Referer': f'{rhost}/',
}
def homeContent(self, filter):
result = {}
cateManual = {
"全部": "1009",
"电影": "1",
"剧集": "2",
"综艺": "6",
"动漫": "4",
"儿童": "15",
"微剧": "35",
"纪录片": "3"
}
classes = []
filters = {}
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
with ThreadPoolExecutor(max_workers=len(classes)) as executor:
results = executor.map(self.getf, classes)
for id, ft in results:
if len(ft):filters[id] = ft
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
data=self.fetch(f'{self.hhost}/portal/lw/v5/channel/recommend?v=13.014.21150', headers=self.headers).json()
vlist = []
for i in data['items'][1:]:
for j in i['video'][0]['data']:
id = j.get('firstId')
pic=j.get('prevue',{}).get('image_url') or j.get('album_image_url_hover')
if id and pic:
pu=j.get('prevue',{}).get('page_url') or j.get('page_url').split('?')[0]
id = f'{id}@{self.e64(pu)}'
vlist.append({
'vod_id': id,
'vod_name': j.get('display_name'),
'vod_pic': pic,
'vod_year': j.get('sns_score'),
'vod_remarks': j.get('dq_updatestatus') or j.get('rank_prefix')
})
return {'list':vlist}
def categoryContent(self, tid, pg, filter, extend):
if pg == "1":
self.sid = ''
new_data = {'mode':'24'}
for key, value in extend.items():
if value:
key_value_pairs = self.d64(value).split(',')
for pair in key_value_pairs:
k, v = pair.split('=')
if k in new_data:
new_data[k] += "," + v
else:
new_data[k] = v
path=f'/portal/lw/videolib/data?uid=&passport_id=&ret_num=60&version=13.014.21150&device_id={self.did}&channel_id={tid}&page_id={pg}&session={self.sid}&os=&conduit_id=&vip=0&auth&recent_selected_tag=&ad=%5B%7B%22lm%22:%225%22,%22ai%22:%225%22,%22fp%22:%226%22,%22sei%22:%22Sa867aa9d326e2bd8654d8c2a8636055e%22,%22position%22:%22library%22%7D%5D&adExt=%7B%22r%22:%221.2.1-ares6-pure%22%7D&dfp=a12f96215b2f7842a98c082799ca0c3d9236be00946701b106829754d8ece3aaf8&filter={urlencode(new_data)}'
data=self.fetch(f'{self.hhost}{path}', headers=self.headers).json()
self.sid = data['session']
videos = []
for i in data['data']:
id = i.get('firstId') or i.get('tv_id')
if not id:
id=i.get('play_url').split(';')[0].split('=')[-1]
if id and not i.get('h'):
id=f'{id}@{self.e64(i.get("page_url"))}'
videos.append({
'vod_id': id,
'vod_name': i.get('display_name'),
'vod_pic': i.get('album_image_url_hover'),
'vod_year': i.get('sns_score'),
'vod_remarks': i.get('dq_updatestatus') or i.get('pay_mark')
})
result = {}
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
ids = ids[0].split('@')
ids[-1] = self.d64(ids[-1])
data = self.fetch(f'{self.dhost}/h5/mina/baidu/play/body/v1/{ids[0]}/', headers=self.headers).json()
v=data['data']['playInfo']
vod = {
'vod_name': v.get('albumName'),
'type_name': v.get('tags'),
'vod_year': v.get('albumYear'),
'vod_remarks': v.get('updateStrategy'),
'vod_actor': v.get('mainActors'),
'vod_director': v.get('directors'),
'vod_content': v.get('albumDesc'),
'vod_play_from': '爱奇艺',
'vod_play_url': ''
}
if data.get('data') and data['data'].get('videoList') and data['data']['videoList'].get('videos'):
purl=[f'{i["shortTitle"]}${i["pageUrl"]}' for i in data['data']['videoList']['videos']]
pg=data['data']['videoList'].get('totalPages')
if pg and pg > 1:
id = v['albumId']
pages = list(range(2, pg + 1))
page_results = {}
with ThreadPoolExecutor(max_workers=10) as executor:
future_to_page = {
executor.submit(self.fetch_page_data, page, id): page
for page in pages
}
for future in as_completed(future_to_page):
page = future_to_page[future]
try:
result = future.result()
page_results[page] = result
except Exception as e:
print(f"Error fetching page {page}: {e}")
for page in sorted(page_results.keys()):
purl.extend(page_results[page])
vod['vod_play_url'] = '#'.join(purl)
else:
vdata=self.fetch(f'{self.dhost}/h5/mina/baidu/play/head/v1/{ids[0]}/', headers=self.headers).json()
v=vdata['data']['playInfo']
vod = {
'vod_name': v.get('shortTitle'),
'type_name': v.get('channelName'),
'vod_year': v.get('year'),
'vod_remarks': v.get('focus'),
'vod_actor': v.get('mainActors'),
'vod_director': v.get('directors'),
'vod_content': v.get('desc'),
'vod_play_from': '爱奇艺',
'vod_play_url': f'{v.get("shortTitle")}${ids[-1]}'
}
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
data=self.fetch(f'{self.hhost}/portal/lw/search/homePageV3?key={key}&current_page={pg}&mode=1&source=input&suggest=&version=13.014.21150&pageNum={pg}&pageSize=25&pu=&u={self.did}&scale=150&token=&userVip=0&conduit=&vipType=-1&os=&osShortName=win10&dataType=&appMode=', headers=self.headers).json()
videos = []
vdata=data['data']['templates']
for i in data['data']['templates']:
if i.get('intentAlbumInfos'):
vdata=[{'albumInfo': c} for c in i['intentAlbumInfos']]+vdata
for i in vdata:
if i.get('albumInfo') and (i['albumInfo'].get('playQipuId','') or i['albumInfo'].get('qipuId')) and i['albumInfo'].get('pageUrl'):
b=i['albumInfo']
id=f"{(b.get('playQipuId','') or b.get('qipuId'))}@{self.e64(b.get('pageUrl'))}"
videos.append({
'vod_id': id,
'vod_name': b.get('title'),
'vod_pic': b.get('img'),
'vod_year': (b.get('year',{}) or {}).get('value'),
'vod_remarks': b.get('subscriptContent') or b.get('channel') or b.get('vipTips')
})
return {'list':videos,'page':pg}
def playerContent(self, flag, id, vipFlags):
return {'jx':1,'parse': 1, 'url': id, 'header': ''}
def localProxy(self, param):
pass
def fetch_page_data(self, page, id):
try:
url = f'{self.dhost}/h5/mina/avlist/{page}/{id}/'
data = self.fetch(url, headers=self.headers).json()
return [f'{i["shortTitle"]}${i["pageUrl"]}' for i in data['data']['videoList']['videos']]
except:
return []
def getf(self,body):
data=self.fetch(f'{self.hhost}/portal/lw/videolib/tag?channel_id={body["type_id"]}&tagAdd=&selected_tag_name=&version=13.014.21150&device={self.did}&uid=', headers=self.headers).json()
ft = []
# for i in data[:-1]:
for i in data:
try:
value_array = [{"n": value['text'], "v": self.e64(value['tag_param'])} for value in i['tags'] if
value.get('tag_param')]
ft.append({"key": i['group'], "name": i['group'], "value": value_array})
except:
print(i)
return (body['type_id'], ft)
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: str):
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 random_str(self,length=16):
hex_chars = '0123456789abcdef'
return ''.join(random.choice(hex_chars) for _ in range(length))

109
py/爱看短剧.py Normal file
View File

@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import sys
from pyquery import PyQuery as pq
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='http://www.toule.top'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
'Referer':f'{host}/',
'Origin':host
}
def homeContent(self, filter):
data=self.getpq()
result = {}
classes = []
for k in data('.swiper-wrapper .swiper-slide').items():
classes.append({
'type_name': k.text(),
'type_id': k.text()
})
result['class'] = classes
result['list'] = self.getlist(data('.container.items ul li'))
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
data=self.getpq(f"/index.php/vod/show/class/{tid}/id/1/page/{pg}.html")
result = {}
result['list'] = self.getlist(data('.container.items ul li'))
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
data=self.getpq(ids[0])
v=data('.container.detail-content')
vod = {
'vod_remarks': v('.items-tags a').text(),
'vod_content': v('.text-content .detail').text(),
'vod_play_from': '爱看短剧',
'vod_play_url': '#'.join([f"{i.text()}${i('a').attr('href')}" for i in data('.swiper-wrapper .swiper-slide').items()])
}
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
data=self.getpq(f"/index.php/vod/search/page/{pg}/wd/{key}.html")
return {'list':self.getlist(data('.container.items ul li')),'page':pg}
def playerContent(self, flag, id, vipFlags):
data=self.getpq(id)
try:
jstr=data('.player-content script').eq(0).text()
jt=json.loads(jstr.split('=',1)[-1])
p,url=0,jt['url']
except Exception as e:
print(f"获取播放地址失败: {e}")
p,url=1,f'{self.host}{id}'
return {'parse': p, 'url': url, 'header': self.headers}
def localProxy(self, param):
pass
def liveContent(self, url):
pass
def getpq(self, path=''):
data=self.fetch(f"{self.host}{path}",headers=self.headers).text
try:
return pq(data)
except Exception as e:
print(f"{str(e)}")
return pq(data.encode('utf-8'))
def getlist(self,data):
videos = []
for i in data.items():
videos.append({
'vod_id': i('.image-line').attr('href'),
'vod_name': i('img').attr('alt'),
'vod_pic': i('img').attr('src'),
'vod_remarks': i('.remarks.light').text()
})
return videos

279
py/猎手影视.py Normal file
View File

@@ -0,0 +1,279 @@
# coding=utf-8
# !/usr/bin/python
# by嗷呜(finally)
import sys
import os
sys.path.append("..")
import re
import hashlib
import hmac
import random
import string
from Crypto.Util.Padding import unpad
from concurrent.futures import ThreadPoolExecutor
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5, AES
from base64 import b64encode, b64decode
import json
import time
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "电影猎手"
def init(self, extend=""):
self.device = self.device_id()
self.host = self.gethost()
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def action(self, action):
pass
def destroy(self):
pass
t = str(int(time.time()))
def homeContent(self, filter):
result = {}
filters = {}
classes = []
bba = self.url()
data = self.fetch(f"{self.host}/api/v1/app/config?pack={bba[0]}&signature={bba[1]}", headers=self.header()).text
data1 = self.aes(data)
dy = {"class":"类型","area":"地区","lang":"语言","year":"年份","letter":"字母","by":"排序","sort":"排序"}
data1['data']['movie_screen']['sort'].pop(0)
for item in data1['data']['movie_screen']['sort']:
item['n'] = item.pop('name')
item['v'] = item.pop('value')
for item in data1['data']['movie_screen']['filter']:
has_non_empty_field = False
classes.append({"type_name": item["name"], "type_id": str(item["id"])})
for key in dy:
if key in item and item[key]:
has_non_empty_field = True
break
if has_non_empty_field:
filters[str(item["id"])] = []
filters[str(item["id"])].append(
{"key": 'sort', "name": '排序', "value": data1['data']['movie_screen']['sort']})
for dkey in item:
if dkey in dy and item[dkey]:
item[dkey].pop(0)
value_array = [
{"n": value.strip(), "v": value.strip()}
for value in item[dkey]
if value.strip() != ""
]
filters[str(item["id"])].append(
{"key": dkey, "name": dy[dkey], "value": value_array}
)
result["class"] = classes
result["filters"] = filters
return result
def homeVideoContent(self):
bba = self.url()
url = f'{self.host}/api/v1/movie/index_recommend?pack={bba[0]}&signature={bba[1]}'
data = self.fetch(url, headers=self.header()).json()
videos = []
for item in data['data']:
if len(item['list']) > 0:
for it in item['list']:
try:
videos.append(self.voides(it))
except Exception as e:
continue
result = {"list": videos}
return result
def categoryContent(self, tid, pg, filter, extend):
body = {"type_id": tid, "sort": extend.get("sort", "by_default"), "class": extend.get("class", "类型"),
"area": extend.get("area", "地区"), "year": extend.get("year", "年份"), "page": str(pg),
"pageSize": "21"}
result = {}
list = []
bba = self.url(body)
url = f"{self.host}/api/v1/movie/screen/list?pack={bba[0]}&signature={bba[1]}"
data = self.fetch(url, headers=self.header()).json()['data']['list']
for item in data:
list.append(self.voides(item))
result["list"] = list
result["page"] = pg
result["pagecount"] = 9999
result["limit"] = 90
result["total"] = 999999
return result
def detailContent(self, ids):
body = {"id": ids[0]}
bba = self.url(body)
url = f'{self.host}/api/v1/movie/detail?pack={bba[0]}&signature={bba[1]}'
data = self.fetch(url, headers=self.header()).json()['data']
video = {'vod_name': data.get('name'),'type_name': data.get('type_name'),'vod_year': data.get('year'),'vod_area': data.get('area'),'vod_remarks': data.get('dynami'),'vod_content': data.get('content')}
play = []
names = []
tasks = []
for itt in data["play_from"]:
name = itt["name"]
a = []
if len(itt["list"]) > 0:
names.append(name)
play.append(self.playeach(itt['list']))
else:
tasks.append({"movie_id": ids[0], "from_code": itt["code"]})
names.append(name)
if tasks:
with ThreadPoolExecutor(max_workers=len(tasks)) as executor:
results = executor.map(self.playlist, tasks)
for result in results:
if result:
play.append(result)
else:
play.append("")
video["vod_play_from"] = "$$$".join(names)
video["vod_play_url"] = "$$$".join(play)
result = {"list": [video]}
return result
def searchContent(self, key, quick, pg=1):
body = {"keyword": key, "sort": "", "type_id": "0", "page": str(pg), "pageSize": "10",
"res_type": "by_movie_name"}
bba = self.url(body)
url = f"{self.host}/api/v1/movie/search?pack={bba[0]}&signature={bba[1]}"
data = self.fetch(url, headers=self.header()).json()['data'].get('list')
videos = []
for it in data:
try:
videos.append(self.voides(it))
except Exception as e:
continue
result = {"list": videos, "page": pg}
return result
def playerContent(self, flag, id, vipFlags):
url = id
if "m3u8" not in url and "mp4" not in url:
try:
add = id.split('|||')
data = {"from_code": add[0], "play_url": add[1], "episode_id": add[2], "type": "play"}
bba = self.url(data)
data2 = self.fetch(f"{self.host}/api/v1/movie_addr/parse_url?pack={bba[0]}&signature={bba[1]}",
headers=self.header()).json()['data']
url = data2.get('play_url') or data2.get('download_url')
try:
url1 = self.fetch(url, headers=self.header(), allow_redirects=False).headers['Location']
if url1 and "http" in url1:
url = url1
except:
pass
except Exception as e:
pass
if '.jpg' in url or '.jpeg' in url or '.png' in url:
url = self.getProxyUrl() + "&url=" + b64encode(url.encode('utf-8')).decode('utf-8') + "&type=m3u8"
result = {}
result["parse"] = 0
result["url"] = url
result["header"] = {'user-agent': 'okhttp/4.9.2'}
return result
def localProxy(self, param):
url = b64decode(param["url"]).decode('utf-8')
durl = url[:url.rfind('/')]
data = self.fetch(url, headers=self.header()).content.decode("utf-8")
lines = data.strip().split('\n')
for index, string in enumerate(lines):
# if 'URI="' in string and 'http' not in string:
# lines[index] = index
# 暂时预留,貌似用不到
if '#EXT' not in string and 'http' not in string:
lines[index] = durl + ('' if string.startswith('/') else '/') + string
data = '\n'.join(lines)
return [200, "application/vnd.apple.mpegur", data]
def device_id(self):
characters = string.ascii_lowercase + string.digits
random_string = ''.join(random.choices(characters, k=32))
return random_string
def gethost(self):
headers = {
'User-Agent': 'okhttp/4.9.2',
'Connection': 'Keep-Alive',
}
response = self.fetch('https://app-site.ecoliving168.com/domain_v5.json', headers=headers).json()
url = response['api_service'].replace('/api/', '')
return url
def header(self):
headers = {
'User-Agent': 'Android',
'Accept': 'application/prs.55App.v2+json',
'timestamp': self.t,
'x-client-setting': '{"pure-mode":1}',
'x-client-uuid': '{"device_id":' + self.device + '}, "type":1,"brand":"Redmi", "model":"M2012K10C", "system_version":30, "sdk_version":"3.1.0.7"}',
'x-client-version': '3096 '
}
return headers
def url(self, id=None):
if not id:
id = {}
id["timestamp"] = self.t
public_key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA02F/kPg5A2NX4qZ5JSns+bjhVMCC6JbTiTKpbgNgiXU+Kkorg6Dj76gS68gB8llhbUKCXjIdygnHPrxVHWfzmzisq9P9awmXBkCk74Skglx2LKHa/mNz9ivg6YzQ5pQFUEWS0DfomGBXVtqvBlOXMCRxp69oWaMsnfjnBV+0J7vHbXzUIkqBLdXSNfM9Ag5qdRDrJC3CqB65EJ3ARWVzZTTcXSdMW9i3qzEZPawPNPe5yPYbMZIoXLcrqvEZnRK1oak67/ihf7iwPJqdc+68ZYEmmdqwunOvRdjq89fQMVelmqcRD9RYe08v+xDxG9Co9z7hcXGTsUquMxkh29uNawIDAQAB'
encrypted_text = json.dumps(id)
public_key = RSA.import_key(b64decode(public_key))
cipher = PKCS1_v1_5.new(public_key)
encrypted_message = cipher.encrypt(encrypted_text.encode('utf-8'))
encrypted_message_base64 = b64encode(encrypted_message).decode('utf-8')
result = encrypted_message_base64.replace('+', '-').replace('/', '_').replace('=', '')
key = '635a580fcb5dc6e60caa39c31a7bde48'
sign = hmac.new(key.encode(), result.encode(), hashlib.md5).hexdigest()
return result, sign
def playlist(self, body):
try:
bba = self.url(body)
url = f'{self.host}/api/v1/movie_addr/list?pack={bba[0]}&signature={bba[1]}'
data = self.fetch(url, headers=self.header()).json()['data']
return self.playeach(data)
except Exception:
return []
def playeach(self,data):
play_urls = []
for it in data:
if re.search(r"mp4|m3u8", it["play_url"]):
play_urls.append(f"{it['episode_name']}${it['play_url']}")
else:
play_urls.append(
f"{it['episode_name']}${it['from_code']}|||{it['play_url']}|||{it['episode_id']}"
)
return '#'.join(play_urls)
def voides(self, item):
if item['name'] or item['title']:
voide = {
"vod_id": item.get('id') or item.get('click'),
'vod_name': item.get('name') or item.get('title'),
'vod_pic': item.get('cover') or item.get('image'),
'vod_year': item.get('year') or item.get('label'),
'vod_remarks': item.get('dynamic') or item.get('sub_title')
}
return voide
def aes(self, text):
text = text.replace('-', '+').replace('_', '/') + '=='
key = b"e6d5de5fcc51f53d"
iv = b"2f13eef7dfc6c613"
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size).decode("utf-8")
return json.loads(pt)

156
py/甜圈短剧.py Normal file
View File

@@ -0,0 +1,156 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import sys
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
pass
def getName(self):
return "甜圈短剧"
def isVideoFormat(self, url):
return True
def manualVideoCheck(self):
return False
def destroy(self):
pass
# 更新为新的域名
ahost = 'https://mov.cenguigui.cn'
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-platform': '"macOS"',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="134", "Google Chrome";v="134"',
'DNT': '1',
'sec-ch-ua-mobile': '?0',
'Sec-Fetch-Site': 'cross-site',
'Sec-Fetch-Mode': 'no-cors',
'Sec-Fetch-Dest': 'video',
'Sec-Fetch-Storage-Access': 'active',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
def homeContent(self, filter):
result = {'class': [{'type_id': '推荐榜', 'type_name': '🔥 推荐榜'},
{'type_id': '新剧', 'type_name': '🎬 新剧'},
{'type_id': '逆袭', 'type_name': '🎬 逆袭'},
{'type_id': '霸总', 'type_name': '🎬 霸总'},
{'type_id': '现代言情', 'type_name': '🎬 现代言情'},
{'type_id': '打脸虐渣', 'type_name': '🎬 打脸虐渣'},
{'type_id': '豪门恩怨', 'type_name': '🎬 豪门恩怨'},
{'type_id': '神豪', 'type_name': '🎬 神豪'},
{'type_id': '马甲', 'type_name': '🎬 马甲'},
{'type_id': '都市日常', 'type_name': '🎬 都市日常'},
{'type_id': '战神归来', 'type_name': '🎬 战神归来'},
{'type_id': '小人物', 'type_name': '🎬 小人物'},
{'type_id': '女性成长', 'type_name': '🎬 女性成长'},
{'type_id': '大女主', 'type_name': '🎬 大女主'},
{'type_id': '穿越', 'type_name': '🎬 穿越'},
{'type_id': '都市修仙', 'type_name': '🎬 都市修仙'},
{'type_id': '强者回归', 'type_name': '🎬 强者回归'},
{'type_id': '亲情', 'type_name': '🎬 亲情'},
{'type_id': '古装', 'type_name': '🎬 古装'},
{'type_id': '重生', 'type_name': '🎬 重生'},
{'type_id': '闪婚', 'type_name': '🎬 闪婚'},
{'type_id': '赘婿逆袭', 'type_name': '🎬 赘婿逆袭'},
{'type_id': '虐恋', 'type_name': '🎬 虐恋'},
{'type_id': '追妻', 'type_name': '🎬 追妻'},
{'type_id': '天下无敌', 'type_name': '🎬 天下无敌'},
{'type_id': '家庭伦理', 'type_name': '🎬 家庭伦理'},
{'type_id': '萌宝', 'type_name': '🎬 萌宝'},
{'type_id': '古风权谋', 'type_name': '🎬 古风权谋'},
{'type_id': '职场', 'type_name': '🎬 职场'},
{'type_id': '奇幻脑洞', 'type_name': '🎬 奇幻脑洞'},
{'type_id': '异能', 'type_name': '🎬 异能'},
{'type_id': '无敌神医', 'type_name': '🎬 无敌神医'},
{'type_id': '古风言情', 'type_name': '🎬 古风言情'},
{'type_id': '传承觉醒', 'type_name': '🎬 传承觉醒'},
{'type_id': '现言甜宠', 'type_name': '🎬 现言甜宠'},
{'type_id': '奇幻爱情', 'type_name': '🎬 奇幻爱情'},
{'type_id': '乡村', 'type_name': '🎬 乡村'},
{'type_id': '历史古代', 'type_name': '🎬 历史古代'},
{'type_id': '王妃', 'type_name': '🎬 王妃'},
{'type_id': '高手下山', 'type_name': '🎬 高手下山'},
{'type_id': '娱乐圈', 'type_name': '🎬 娱乐圈'},
{'type_id': '强强联合', 'type_name': '🎬 强强联合'},
{'type_id': '破镜重圆', 'type_name': '🎬 破镜重圆'},
{'type_id': '暗恋成真', 'type_name': '🎬 暗恋成真'},
{'type_id': '民国', 'type_name': '🎬 民国'},
{'type_id': '欢喜冤家', 'type_name': '🎬 欢喜冤家'},
{'type_id': '系统', 'type_name': '🎬 系统'},
{'type_id': '真假千金', 'type_name': '🎬 真假千金'},
{'type_id': '龙王', 'type_name': '🎬 龙王'},
{'type_id': '校园', 'type_name': '🎬 校园'},
{'type_id': '穿书', 'type_name': '🎬 穿书'},
{'type_id': '女帝', 'type_name': '🎬 女帝'},
{'type_id': '团宠', 'type_name': '🎬 团宠'},
{'type_id': '年代爱情', 'type_name': '🎬 年代爱情'},
{'type_id': '玄幻仙侠', 'type_name': '🎬 玄幻仙侠'},
{'type_id': '青梅竹马', 'type_name': '🎬 青梅竹马'},
{'type_id': '悬疑推理', 'type_name': '🎬 悬疑推理'},
{'type_id': '皇后', 'type_name': '🎬 皇后'},
{'type_id': '替身', 'type_name': '🎬 替身'},
{'type_id': '大叔', 'type_name': '🎬 大叔'},
{'type_id': '喜剧', 'type_name': '🎬 喜剧'},
{'type_id': '剧情', 'type_name': '🎬 剧情'}]}
return result
def homeVideoContent(self):
return []
def categoryContent(self, tid, pg, filter, extend):
params = {
'classname': tid,
'offset': str((int(pg) - 1)),
}
# 更新请求路径为 /duanju/api.php
data = self.fetch(f'{self.ahost}/duanju/api.php', params=params, headers=self.headers).json()
videos = []
for k in data['data']:
videos.append({
'vod_id': k.get('book_id'),
'vod_name': k.get('title'),
'vod_pic': k.get('cover'),
'vod_year': k.get('score'),
'vod_remarks': f"{k.get('sub_title')}|{k.get('episode_cnt')}"
})
result = {}
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
# 更新请求路径为 /duanju/api.php
v = self.fetch(f'{self.ahost}/duanju/api.php', params={'book_id': ids[0]}, headers=self.headers).json()
vod = {
'vod_id': ids[0],
'vod_name': v.get('title'),
'type_name': v.get('category'),
'vod_year': v.get('time'),
'vod_remarks': v.get('duration'),
'vod_content': v.get('desc'),
'vod_play_from': '爱看短剧',
'vod_play_url': '#'.join([f"{i['title']}${i['video_id']}" for i in v['data']])
}
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
return self.categoryContent(key, pg, True, {})
def playerContent(self, flag, id, vipFlags):
# 更新请求路径为 /duanju/api.php
data = self.fetch(f'{self.ahost}/duanju/api.php', params={'video_id': id}, headers=self.headers).json()
return {'parse': 0, 'url': data['data']['url'], 'header': self.headers}
def localProxy(self, param):
pass

180
py/界影视.py Normal file
View File

@@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
# @Author : Doubebly
# @Time : 2025/1/21 23:07
import hashlib
import re
import sys
import time
import requests
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "JieYingShi"
def init(self, extend):
self.home_url = 'https://www.hkybqufgh.com'
self.error_url = 'https://json.doube.eu.org/error/4gtv/index.m3u8'
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
}
def getDependence(self):
return []
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def homeContent(self, filter):
return {'class': [
{
'type_id': '1',
'type_name': '电影'
},
{
'type_id': '2',
'type_name': '电视剧'
},
{
'type_id': '4',
'type_name': '动漫'
},
{
'type_id': '3',
'type_name': '综艺'
}
]}
def homeVideoContent(self):
a = self.get_data(self.home_url)
return {'list': a, 'parse': 0, 'jx': 0}
def categoryContent(self, cid, page, filter, ext):
url = self.home_url + f'/vod/show/id/{cid}/page/{page}'
data = self.get_data(url)
return {'list': data, 'parse': 0, 'jx': 0}
def detailContent(self, did):
ids = did[0]
data = self.get_detail_data(ids)
return {"list": data, 'parse': 0, 'jx': 0}
def searchContent(self, key, quick, page='1'):
if int(page) > 1:
return {'list': [], 'parse': 0, 'jx': 0}
url = self.home_url + f'/vod/search/{key}'
data = self.get_data(url)
return {'list': data, 'parse': 0, 'jx': 0}
def playerContent(self, flag, pid, vipFlags):
url = self.get_play_data(pid)
return {"url": url, "header": self.headers, "parse": 1, "jx": 0}
def localProxy(self, params):
pass
def destroy(self):
return '正在Destroy'
def get_data(self, url):
data = []
try:
res = requests.get(url, headers=self.headers)
if res.status_code != 200:
return data
vod_id_s = re.findall(r'\\"vodId\\":(.*?),', res.text)
vod_name_s = re.findall(r'\\"vodName\\":\\"(.*?)\\"', res.text)
vod_pic_s = re.findall(r'\\"vodPic\\":\\"(.*?)\\"', res.text)
vod_remarks_s = re.findall(r'\\"vodRemarks\\":\\"(.*?)\\"', res.text)
for i in range(len(vod_id_s)):
data.append(
{
'vod_id': vod_id_s[i],
'vod_name': vod_name_s[i],
'vod_pic': vod_pic_s[i],
'vod_remarks': vod_remarks_s[i],
}
)
except requests.RequestException as e:
print(e)
return data
def get_detail_data(self, ids):
url = self.home_url + f'/api/mw-movie/anonymous/video/detail?id={ids}'
t = str(int(time.time() * 1000))
headers = self.get_headers(t, f'id={ids}&key=cb808529bae6b6be45ecfab29a4889bc&t={t}')
try:
res = requests.get(url, headers=headers)
if res.status_code != 200:
return []
i = res.json()['data']
urls = []
for ii in res.json()['data']['episodeList']:
name = ii['name']
url = ii['nid']
urls.append(f'{name}${ids}-{url}')
data = {
'type_name': i['vodClass'],
'vod_id': i['vodId'],
'vod_name': i['vodName'],
'vod_remarks': i['vodRemarks'],
'vod_year': i['vodYear'],
'vod_area': i['vodArea'],
'vod_actor': i['vodActor'],
'vod_director': i['vodDirector'],
'vod_content': i['vodContent'],
'vod_play_from': '默认',
'vod_play_url': '#'.join(urls),
}
return [data]
except requests.RequestException as e:
print(e)
return []
def get_play_data(self, play):
info = play.split('-')
_id = info[0]
_pid = info[1]
url = self.home_url + f'/api/mw-movie/anonymous/v2/video/episode/url?id={_id}&nid={_pid}'
t = str(int(time.time() * 1000))
headers = self.get_headers(t, f'id={_id}&nid={_pid}&key=cb808529bae6b6be45ecfab29a4889bc&t={t}')
try:
res = requests.get(url, headers=headers)
if res.status_code != 200:
return self.error_url
return res.json()['data']['list'][0]['url']
except requests.RequestException as e:
print(e)
return self.error_url
@staticmethod
def get_headers(t, e):
sign = hashlib.sha1(hashlib.md5(e.encode()).hexdigest().encode()).hexdigest()
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'Accept': 'application/json, text/plain, */*',
'sign': sign,
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
't': t,
'referer': 'https://www.hkybqufgh.com/',
}
return headers
if __name__ == '__main__':
pass

147
py/绝对影视.py Normal file
View File

@@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import base64
import re
import sys
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=""):
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host = 'https://www.jdys.art'
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-platform': '"macOS"',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="134", "Google Chrome";v="134"',
'dnt': '1',
'sec-ch-ua-mobile': '?0',
'origin': host,
'sec-fetch-site': 'cross-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'referer': f'{host}/',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'priority': 'u=1, i',
}
def homeContent(self, filter):
data = self.getpq(self.fetch(self.host, headers=self.headers).text)
result = {}
classes = []
for k in list(data('.navtop .navlist li').items())[:9]:
classes.append({
'type_name': k('a').text(),
'type_id': k('a').attr('href'),
})
result['class'] = classes
result['list'] = self.getlist(data('.mi_btcon .bt_img ul li'))
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
data = self.getpq(self.fetch(f"{tid}{'' if pg == '1' else f'page/{pg}/'}", headers=self.headers).text)
result = {}
result['list'] = self.getlist(data('.mi_cont .bt_img ul li'))
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
data = self.getpq(self.fetch(ids[0], headers=self.headers).text)
data2 = data('.moviedteail_list li')
vod = {
'vod_name': data('.dytext h1').text(),
'type_name': data2.eq(0).text(),
'vod_year': data2.eq(2).text(),
'vod_area': data2.eq(1).text(),
'vod_remarks': data2.eq(4).text(),
'vod_actor': data2.eq(7).text(),
'vod_director': data2.eq(5).text(),
'vod_content': data('.yp_context').text().strip()
}
vdata = data('.paly_list_btn a')
play = []
for i in vdata.items():
a = i.text() + "$" + i.attr.href
play.append(a)
vod["vod_play_from"] = "在线播放"
vod["vod_play_url"] = "#".join(play)
result = {"list": [vod]}
return result
def searchContent(self, key, quick, pg="1"):
data = self.getpq(self.fetch(f"{self.host}/page/{pg}/?s={key}", headers=self.headers).text)
return {'list': self.getlist(data('.mi_cont .bt_img ul li')), 'page': pg}
def playerContent(self, flag, id, vipFlags):
data = self.getpq(self.fetch(id, headers=self.headers).text)
try:
sc = data('.videoplay script').eq(-1).text()
strd = re.findall(r'var\s+[^=]*=\s*"([^"]*)";', sc)
kdata = re.findall(r'parse\((.*?)\);', sc)
jm = self.aes(strd[0], kdata[0].replace('"', ''), kdata[1].replace('"', ''))
url = re.search(r'url: "(.*?)"', jm).group(1)
p = 0
except:
p = 1
url = id
result = {}
result["parse"] = p
result["url"] = url
result["header"] = self.headers
return result
def localProxy(self, param):
pass
def getpq(self, text):
try:
return pq(text)
except Exception as e:
print(f"{str(e)}")
return pq(text.encode('utf-8'))
def getlist(self, data):
videos = []
for i in data.items():
videos.append({
'vod_id': i('a').attr('href'),
'vod_name': i('a img').attr('alt'),
'vod_pic': i('a img').attr('src'),
'vod_remarks': i('.dycategory').text(),
'vod_year': i('.dyplayinfo').text() or i('.rating').text(),
})
return videos
def aes(self, word, key, iv):
key = key.encode('utf-8')
iv = iv.encode('utf-8')
encrypted_data = base64.b64decode(word)
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_data = cipher.decrypt(encrypted_data)
decrypted_data = unpad(decrypted_data, AES.block_size)
return decrypted_data.decode('utf-8')

767
py/网络直播.py Normal file
View File

@@ -0,0 +1,767 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import re
import sys
import time
from base64 import b64decode, b64encode
from urllib.parse import parse_qs
import requests
from pyquery import PyQuery as pq
sys.path.append('..')
from base.spider import Spider
from concurrent.futures import ThreadPoolExecutor
class Spider(Spider):
def init(self, extend=""):
tid = 'douyin'
headers = self.gethr(0, tid)
response = requests.head(self.hosts[tid], headers=headers)
ttwid = response.cookies.get('ttwid')
headers.update({
'authority': self.hosts[tid].split('//')[-1],
'cookie': f'ttwid={ttwid}' if ttwid else ''
})
self.dyheaders = 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/126.0.0.0 Safari/537.36 Edg/126.0.0.0"
},
{
"User-Agent": "Dart/3.4 (dart:io)"
}
]
excepturl = 'https://www.baidu.com'
hosts = {
"huya": ["https://www.huya.com","https://mp.huya.com"],
"douyin": "https://live.douyin.com",
"douyu": "https://www.douyu.com",
"wangyi": "https://cc.163.com",
"bili": ["https://api.live.bilibili.com", "https://api.bilibili.com"]
}
referers = {
"huya": "https://live.cdn.huya.com",
"douyin": "https://live.douyin.com",
"douyu": "https://m.douyu.com",
"bili": "https://live.bilibili.com"
}
playheaders = {
"wangyi": {
"User-Agent": "ExoPlayer",
"Connection": "Keep-Alive",
"Icy-MetaData": "1"
},
"bili": {
'Accept': '*/*',
'Icy-MetaData': '1',
'referer': referers['bili'],
'user-agent': headers[0]['User-Agent']
},
'douyin': {
'User-Agent': 'libmpv',
'Icy-MetaData': '1'
},
'huya': {
'User-Agent': 'ExoPlayer',
'Connection': 'Keep-Alive',
'Icy-MetaData': '1'
},
'douyu': {
'User-Agent': 'libmpv',
'Icy-MetaData': '1'
}
}
def process_bili(self):
try:
self.blfdata = self.fetch(
f'{self.hosts["bili"][0]}/room/v1/Area/getList?need_entrance=1&parent_id=0',
headers=self.gethr(0, 'bili')
).json()
return ('bili', [{'key': 'cate', 'name': '分类',
'value': [{'n': i['name'], 'v': str(i['id'])}
for i in self.blfdata['data']]}])
except Exception as e:
print(f"bili处理错误: {e}")
return 'bili', None
def process_douyin(self):
try:
data = self.getpq(self.hosts['douyin'], headers=self.dyheaders)('script')
for i in data.items():
if 'categoryData' in i.text():
content = i.text()
start = content.find('{')
end = content.rfind('}') + 1
if start != -1 and end != -1:
json_str = content[start:end]
json_str = json_str.replace('\\"', '"')
try:
self.dyifdata = json.loads(json_str)
return ('douyin', [{'key': 'cate', 'name': '分类',
'value': [{'n': i['partition']['title'],
'v': f"{i['partition']['id_str']}@@{i['partition']['title']}"}
for i in self.dyifdata['categoryData']]}])
except json.JSONDecodeError as e:
print(f"douyin解析错误: {e}")
return 'douyin', None
except Exception as e:
print(f"douyin请求或处理错误: {e}")
return 'douyin', None
def process_douyu(self):
try:
self.dyufdata = self.fetch(
f'{self.referers["douyu"]}/api/cate/list',
headers=self.headers[1]
).json()
return ('douyu', [{'key': 'cate', 'name': '分类',
'value': [{'n': i['cate1Name'], 'v': str(i['cate1Id'])}
for i in self.dyufdata['data']['cate1Info']]}])
except Exception as e:
print(f"douyu错误: {e}")
return 'douyu', None
def homeContent(self, filter):
result = {}
cateManual = {
"虎牙": "huya",
"抖音": "douyin",
"斗鱼": "douyu",
"网易": "wangyi"
}
classes = []
filters = {
'huya': [{'key': 'cate', 'name': '分类',
'value': [{'n': '网游', 'v': '1'}, {'n': '单机', 'v': '2'},
{'n': '娱乐', 'v': '8'}, {'n': '手游', 'v': '3'}]}]
}
with ThreadPoolExecutor(max_workers=3) as executor:
futures = {
executor.submit(self.process_bili): 'bili',
executor.submit(self.process_douyin): 'douyin',
executor.submit(self.process_douyu): 'douyu'
}
for future in futures:
platform, filter_data = future.result()
if filter_data:
filters[platform] = filter_data
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
vdata = []
result = {}
pagecount = 9999
result['page'] = pg
result['limit'] = 90
result['total'] = 999999
if tid == 'wangyi':
vdata, pagecount = self.wyccContent(tid, pg, filter, extend, vdata)
elif 'bili' in tid:
vdata, pagecount = self.biliContent(tid, pg, filter, extend, vdata)
elif 'huya' in tid:
vdata, pagecount = self.huyaContent(tid, pg, filter, extend, vdata)
elif 'douyin' in tid:
vdata, pagecount = self.douyinContent(tid, pg, filter, extend, vdata)
elif 'douyu' in tid:
vdata, pagecount = self.douyuContent(tid, pg, filter, extend, vdata)
result['list'] = vdata
result['pagecount'] = pagecount
return result
def wyccContent(self, tid, pg, filter, extend, vdata):
params = {
'format': 'json',
'start': (int(pg) - 1) * 20,
'size': '20',
}
response = self.fetch(f'{self.hosts[tid]}/api/category/live/', params=params, headers=self.headers[0]).json()
for i in response['lives']:
if i.get('cuteid'):
bvdata = self.buildvod(
vod_id=f"{tid}@@{i['cuteid']}",
vod_name=i.get('title'),
vod_pic=i.get('cover'),
vod_remarks=i.get('nickname'),
style={"type": "rect", "ratio": 1.33}
)
vdata.append(bvdata)
return vdata, 9999
def biliContent(self, tid, pg, filter, extend, vdata):
if extend.get('cate') and pg == '1' and 'click' not in tid:
for i in self.blfdata['data']:
if str(i['id']) == extend['cate']:
for j in i['list']:
v = self.buildvod(
vod_id=f"click_{tid}@@{i['id']}@@{j['id']}",
vod_name=j.get('name'),
vod_pic=j.get('pic'),
vod_tag=1,
style={"type": "oval", "ratio": 1}
)
vdata.append(v)
return vdata, 1
else:
path = f'/xlive/web-interface/v1/second/getListByArea?platform=web&sort=online&page_size=30&page={pg}'
if 'click' in tid:
ids = tid.split('_')[1].split('@@')
tid = ids[0]
path = f'/xlive/web-interface/v1/second/getList?platform=web&parent_area_id={ids[1]}&area_id={ids[-1]}&sort_type=&page={pg}'
data = self.fetch(f'{self.hosts[tid][0]}{path}', headers=self.gethr(0, tid)).json()
for i in data['data']['list']:
if i.get('roomid'):
data = self.buildvod(
f"{tid}@@{i['roomid']}",
i.get('title'),
i.get('cover'),
i.get('watched_show', {}).get('text_large'),
0,
i.get('uname'),
style={"type": "rect", "ratio": 1.33}
)
vdata.append(data)
return vdata, 9999
def huyaContent(self, tid, pg, filter, extend, vdata):
if extend.get('cate') and pg == '1' and 'click' not in tid:
id = extend.get('cate')
data = self.fetch(f'{self.referers[tid]}/liveconfig/game/bussLive?bussType={id}',
headers=self.headers[1]).json()
for i in data['data']:
v = self.buildvod(
vod_id=f"click_{tid}@@{int(i['gid'])}",
vod_name=i.get('gameFullName'),
vod_pic=f'https://huyaimg.msstatic.com/cdnimage/game/{int(i["gid"])}-MS.jpg',
vod_tag=1,
style={"type": "oval", "ratio": 1}
)
vdata.append(v)
return vdata, 1
else:
gid = ''
if 'click' in tid:
ids = tid.split('_')[1].split('@@')
tid = ids[0]
gid = f'&gameId={ids[1]}'
data = self.fetch(f'{self.hosts[tid][0]}/cache.php?m=LiveList&do=getLiveListByPage&tagAll=0{gid}&page={pg}',
headers=self.headers[1]).json()
for i in data['data']['datas']:
if i.get('profileRoom'):
v = self.buildvod(
f"{tid}@@{i['profileRoom']}",
i.get('introduction'),
i.get('screenshot'),
str(int(i.get('totalCount', '1')) / 10000) + '',
0,
i.get('nick'),
style={"type": "rect", "ratio": 1.33}
)
vdata.append(v)
return vdata, 9999
def douyinContent(self, tid, pg, filter, extend, vdata):
if extend.get('cate') and pg == '1' and 'click' not in tid:
ids = extend.get('cate').split('@@')
for i in self.dyifdata['categoryData']:
c = i['partition']
if c['id_str'] == ids[0] and c['title'] == ids[1]:
vlist = i['sub_partition'].copy()
vlist.insert(0, {'partition': c})
for j in vlist:
j = j['partition']
v = self.buildvod(
vod_id=f"click_{tid}@@{j['id_str']}@@{j['type']}",
vod_name=j.get('title'),
vod_pic='https://p3-pc-weboff.byteimg.com/tos-cn-i-9r5gewecjs/pwa_v3/512x512-1.png',
vod_tag=1,
style={"type": "oval", "ratio": 1}
)
vdata.append(v)
return vdata, 1
else:
path = f'/webcast/web/partition/detail/room/?aid=6383&app_name=douyin_web&live_id=1&device_platform=web&count=15&offset={(int(pg) - 1) * 15}&partition=720&partition_type=1'
if 'click' in tid:
ids = tid.split('_')[1].split('@@')
tid = ids[0]
path = f'/webcast/web/partition/detail/room/?aid=6383&app_name=douyin_web&live_id=1&device_platform=web&count=15&offset={(int(pg) - 1) * 15}&partition={ids[1]}&partition_type={ids[-1]}&req_from=2'
data = self.fetch(f'{self.hosts[tid]}{path}', headers=self.dyheaders).json()
for i in data['data']['data']:
v = self.buildvod(
vod_id=f"{tid}@@{i['web_rid']}",
vod_name=i['room'].get('title'),
vod_pic=i['room']['cover'].get('url_list')[0],
vod_year=i.get('user_count_str'),
vod_remarks=i['room']['owner'].get('nickname'),
style={"type": "rect", "ratio": 1.33}
)
vdata.append(v)
return vdata, 9999
def douyuContent(self, tid, pg, filter, extend, vdata):
if extend.get('cate') and pg == '1' and 'click' not in tid:
for i in self.dyufdata['data']['cate2Info']:
if str(i['cate1Id']) == extend['cate']:
v = self.buildvod(
vod_id=f"click_{tid}@@{i['cate2Id']}",
vod_name=i.get('cate2Name'),
vod_pic=i.get('icon'),
vod_remarks=i.get('count'),
vod_tag=1,
style={"type": "oval", "ratio": 1}
)
vdata.append(v)
return vdata, 1
else:
path = f'/japi/weblist/apinc/allpage/6/{pg}'
if 'click' in tid:
ids = tid.split('_')[1].split('@@')
tid = ids[0]
path = f'/gapi/rkc/directory/mixList/2_{ids[1]}/{pg}'
url = f'{self.hosts[tid]}{path}'
data = self.fetch(url, headers=self.headers[1]).json()
for i in data['data']['rl']:
v = self.buildvod(
vod_id=f"{tid}@@{i['rid']}",
vod_name=i.get('rn'),
vod_pic=i.get('rs16'),
vod_year=str(int(i.get('ol', 1)) / 10000) + '',
vod_remarks=i.get('nn'),
style={"type": "rect", "ratio": 1.33}
)
vdata.append(v)
return vdata, 9999
def detailContent(self, ids):
ids = ids[0].split('@@')
if ids[0] == 'wangyi':
vod = self.wyccDetail(ids)
elif ids[0] == 'bili':
vod = self.biliDetail(ids)
elif ids[0] == 'huya':
vod = self.huyaDetail(ids)
elif ids[0] == 'douyin':
vod = self.douyinDetail(ids)
elif ids[0] == 'douyu':
vod = self.douyuDetail(ids)
return {'list': [vod]}
def wyccDetail(self, ids):
try:
vdata = self.getpq(f'{self.hosts[ids[0]]}/{ids[1]}', self.headers[0])('script').eq(-1).text()
def get_quality_name(vbr):
if vbr <= 600:
return "标清"
elif vbr <= 1000:
return "高清"
elif vbr <= 2000:
return "超清"
else:
return "蓝光"
data = json.loads(vdata)['props']['pageProps']['roomInfoInitData']
name = data['live'].get('title', ids[0])
vod = self.buildvod(vod_name=data.get('keywords_suffix'), vod_remarks=data['live'].get('title'),
vod_content=data.get('description_suffix'))
resolution_data = data['live']['quickplay']['resolution']
all_streams = {}
sorted_qualities = sorted(resolution_data.items(),
key=lambda x: x[1]['vbr'],
reverse=True)
for quality, data in sorted_qualities:
vbr = data['vbr']
quality_name = get_quality_name(vbr)
for cdn_name, url in data['cdn'].items():
if cdn_name not in all_streams and type(url) == str and url.startswith('http'):
all_streams[cdn_name] = []
if isinstance(url, str) and url.startswith('http'):
all_streams[cdn_name].extend([quality_name, url])
plists = []
names = []
for i, (cdn_name, stream_list) in enumerate(all_streams.items(), 1):
names.append(f'线路{i}')
pstr = f"{name}${ids[0]}@@{self.e64(json.dumps(stream_list))}"
plists.append(pstr)
vod['vod_play_from'] = "$$$".join(names)
vod['vod_play_url'] = "$$$".join(plists)
return vod
except Exception as e:
return self.handle_exception(e)
def biliDetail(self, ids):
try:
vdata = self.fetch(
f'{self.hosts[ids[0]][0]}/xlive/web-room/v1/index/getInfoByRoom?room_id={ids[1]}&wts={int(time.time())}',
headers=self.gethr(0, ids[0])).json()
v = vdata['data']['room_info']
vod = self.buildvod(
vod_name=v.get('title'),
type_name=v.get('parent_area_name') + '/' + v.get('area_name'),
vod_remarks=v.get('tags'),
vod_play_from=v.get('title'),
)
data = self.fetch(
f'{self.hosts[ids[0]][0]}/xlive/web-room/v2/index/getRoomPlayInfo?room_id={ids[1]}&protocol=0%2C1&format=0%2C1%2C2&codec=0%2C1&platform=web',
headers=self.gethr(0, ids[0])).json()
vdnams = data['data']['playurl_info']['playurl']['g_qn_desc']
all_accept_qns = []
streams = data['data']['playurl_info']['playurl']['stream']
for stream in streams:
for format_item in stream['format']:
for codec in format_item['codec']:
if 'accept_qn' in codec:
all_accept_qns.append(codec['accept_qn'])
max_accept_qn = max(all_accept_qns, key=len) if all_accept_qns else []
quality_map = {
item['qn']: item['desc']
for item in vdnams
}
quality_names = [f"{quality_map.get(qn)}${ids[0]}@@{ids[1]}@@{qn}" for qn in max_accept_qn]
vod['vod_play_url'] = "#".join(quality_names)
return vod
except Exception as e:
return self.handle_exception(e)
def huyaDetail(self, ids):
try:
vdata = self.fetch(f'{self.hosts[ids[0]][1]}/cache.php?m=Live&do=profileRoom&roomid={ids[1]}',
headers=self.headers[0]).json()
v = vdata['data']['liveData']
vod = self.buildvod(
vod_name=v.get('introduction'),
type_name=v.get('gameFullName'),
vod_director=v.get('nick'),
vod_remarks=v.get('contentIntro'),
)
data = dict(reversed(list(vdata['data']['stream'].items())))
names = []
plist = []
for stream_type, stream_data in data.items():
if isinstance(stream_data, dict) and 'multiLine' in stream_data and 'rateArray' in stream_data:
names.append(f"线路{len(names) + 1}")
qualities = sorted(
stream_data['rateArray'],
key=lambda x: (x['iBitRate'], x['sDisplayName']),
reverse=True
)
cdn_urls = []
for cdn in stream_data['multiLine']:
quality_urls = []
for quality in qualities:
quality_name = quality['sDisplayName']
bit_rate = quality['iBitRate']
base_url = cdn['url']
if bit_rate > 0:
if '.m3u8' in base_url:
new_url = base_url.replace(
'ratio=2000',
f'ratio={bit_rate}'
)
else:
new_url = base_url.replace(
'imgplus.flv',
f'imgplus_{bit_rate}.flv'
)
else:
new_url = base_url
quality_urls.extend([quality_name, new_url])
encoded_urls = self.e64(json.dumps(quality_urls))
cdn_urls.append(f"{cdn['cdnType']}${ids[0]}@@{encoded_urls}")
if cdn_urls:
plist.append('#'.join(cdn_urls))
vod['vod_play_from'] = "$$$".join(names)
vod['vod_play_url'] = "$$$".join(plist)
return vod
except Exception as e:
return self.handle_exception(e)
def douyinDetail(self, ids):
url = f'{self.hosts[ids[0]]}/webcast/room/web/enter/?aid=6383&app_name=douyin_web&live_id=1&device_platform=web&enter_from=web_live&web_rid={ids[1]}&room_id_str=&enter_source=&Room-Enter-User-Login-Ab=0&is_need_double_stream=false&cookie_enabled=true&screen_width=1980&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Edge&browser_version=125.0.0.0'
data = self.fetch(url, headers=self.dyheaders).json()
try:
vdata = data['data']['data'][0]
vod = self.buildvod(
vod_name=vdata['title'],
vod_remarks=vdata['user_count_str'],
)
resolution_data = vdata['stream_url']['live_core_sdk_data']['pull_data']['options']['qualities']
stream_json = vdata['stream_url']['live_core_sdk_data']['pull_data']['stream_data']
stream_json = json.loads(stream_json)
available_types = []
if any(sdk_key in stream_json['data'] and 'main' in stream_json['data'][sdk_key] for sdk_key in
stream_json['data']):
available_types.append('main')
if any(sdk_key in stream_json['data'] and 'backup' in stream_json['data'][sdk_key] for sdk_key in
stream_json['data']):
available_types.append('backup')
plist = []
for line_type in available_types:
format_arrays = {'flv': [], 'hls': [], 'lls': []}
qualities = sorted(resolution_data, key=lambda x: x['level'], reverse=True)
for quality in qualities:
sdk_key = quality['sdk_key']
if sdk_key in stream_json['data'] and line_type in stream_json['data'][sdk_key]:
stream_info = stream_json['data'][sdk_key][line_type]
if stream_info.get('flv'):
format_arrays['flv'].extend([quality['name'], stream_info['flv']])
if stream_info.get('hls'):
format_arrays['hls'].extend([quality['name'], stream_info['hls']])
if stream_info.get('lls'):
format_arrays['lls'].extend([quality['name'], stream_info['lls']])
format_urls = []
for format_name, url_array in format_arrays.items():
if url_array:
encoded_urls = self.e64(json.dumps(url_array))
format_urls.append(f"{format_name}${ids[0]}@@{encoded_urls}")
if format_urls:
plist.append('#'.join(format_urls))
names = ['线路1', '线路2'][:len(plist)]
vod['vod_play_from'] = "$$$".join(names)
vod['vod_play_url'] = "$$$".join(plist)
return vod
except Exception as e:
return self.handle_exception(e)
def douyuDetail(self, ids):
headers = self.gethr(0, zr=f'{self.hosts[ids[0]]}/{ids[1]}')
try:
data = self.fetch(f'{self.hosts[ids[0]]}/betard/{ids[1]}', headers=headers).json()
vname = data['room']['room_name']
vod = self.buildvod(
vod_name=vname,
vod_remarks=data['room'].get('second_lvl_name'),
vod_director=data['room'].get('nickname'),
)
vdata = self.fetch(f'{self.hosts[ids[0]]}/swf_api/homeH5Enc?rids={ids[1]}', headers=headers).json()
json_body = vdata['data']
json_body = {"html": self.douyu_text(json_body[f'room{ids[1]}']), "rid": ids[1]}
sign = self.post('http://alive.nsapps.cn/api/AllLive/DouyuSign', json=json_body, headers=self.headers[1]).json()['data']
body = f'{sign}&cdn=&rate=-1&ver=Douyu_223061205&iar=1&ive=1&hevc=0&fa=0'
body=self.params_to_json(body)
nubdata = self.post(f'{self.hosts[ids[0]]}/lapi/live/getH5Play/{ids[1]}', data=body, headers=headers).json()
plist = []
names = []
for i,x in enumerate(nubdata['data']['cdnsWithName']):
names.append(f'线路{i+1}')
d = {'sign': sign, 'cdn': x['cdn'], 'id': ids[1]}
plist.append(
f'{vname}${ids[0]}@@{self.e64(json.dumps(d))}@@{self.e64(json.dumps(nubdata["data"]["multirates"]))}')
vod['vod_play_from'] = "$$$".join(names)
vod['vod_play_url'] = "$$$".join(plist)
return vod
except Exception as e:
return self.handle_exception(e)
def douyu_text(self, text):
function_positions = [m.start() for m in re.finditer('function', text)]
total_functions = len(function_positions)
if total_functions % 2 == 0:
target_index = total_functions // 2 + 1
else:
target_index = (total_functions - 1) // 2 + 1
if total_functions >= target_index:
cut_position = function_positions[target_index - 1]
ctext = text[4:cut_position]
return re.sub(r'eval\(strc\)\([\w\d,]+\)', 'strc', ctext)
return text
def searchContent(self, key, quick, pg="1"):
pass
def playerContent(self, flag, id, vipFlags):
try:
ids = id.split('@@')
p = 1
if ids[0] in ['wangyi', 'douyin','huya']:
p, url = 0, json.loads(self.d64(ids[1]))
elif ids[0] == 'bili':
p, url = self.biliplay(ids)
elif ids[0] == 'huya':
p, url = 0, json.loads(self.d64(ids[1]))
elif ids[0] == 'douyu':
p, url = self.douyuplay(ids)
return {'parse': p, 'url': url, 'header': self.playheaders[ids[0]]}
except Exception as e:
return {'parse': 1, 'url': self.excepturl, 'header': self.headers[0]}
def biliplay(self, ids):
try:
data = self.fetch(
f'{self.hosts[ids[0]][0]}/xlive/web-room/v2/index/getRoomPlayInfo?room_id={ids[1]}&protocol=0,1&format=0,2&codec=0&platform=web&qn={ids[2]}',
headers=self.gethr(0, ids[0])).json()
urls = []
line_index = 1
for stream in data['data']['playurl_info']['playurl']['stream']:
for format_item in stream['format']:
for codec in format_item['codec']:
for url_info in codec['url_info']:
full_url = f"{url_info['host']}/{codec['base_url'].lstrip('/')}{url_info['extra']}"
urls.extend([f"线路{line_index}", full_url])
line_index += 1
return 0, urls
except Exception as e:
return 1, self.excepturl
def douyuplay(self, ids):
try:
sdata = json.loads(self.d64(ids[1]))
headers = self.gethr(0, zr=f'{self.hosts[ids[0]]}/{sdata["id"]}')
ldata = json.loads(self.d64(ids[2]))
result_obj = {}
with ThreadPoolExecutor(max_workers=len(ldata)) as executor:
futures = [
executor.submit(
self.douyufp,
sdata,
quality,
headers,
self.hosts[ids[0]],
result_obj
) for quality in ldata
]
for future in futures:
future.result()
result = []
for bit in sorted(result_obj.keys(), reverse=True):
result.extend(result_obj[bit])
if result:
return 0, result
return 1, self.excepturl
except Exception as e:
return 1, self.excepturl
def douyufp(self, sdata, quality, headers, host, result_obj):
try:
body = f'{sdata["sign"]}&cdn={sdata["cdn"]}&rate={quality["rate"]}'
body=self.params_to_json(body)
data = self.post(f'{host}/lapi/live/getH5Play/{sdata["id"]}',
data=body, headers=headers).json()
if data.get('data'):
play_url = data['data']['rtmp_url'] + '/' + data['data']['rtmp_live']
bit = quality.get('bit', 0)
if bit not in result_obj:
result_obj[bit] = []
result_obj[bit].extend([quality['name'], play_url])
except Exception as e:
print(f"Error fetching {quality['name']}: {str(e)}")
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 josn_to_params(self, params, skip_empty=False):
query = []
for k, v in params.items():
if skip_empty and not v:
continue
query.append(f"{k}={v}")
return "&".join(query)
def params_to_json(self, query_string):
parsed_data = parse_qs(query_string)
result = {key: value[0] for key, value in parsed_data.items()}
return result
def buildvod(self, vod_id='', vod_name='', vod_pic='', vod_year='', vod_tag='', vod_remarks='', style='',
type_name='', vod_area='', vod_actor='', vod_director='',
vod_content='', vod_play_from='', vod_play_url=''):
vod = {
'vod_id': vod_id,
'vod_name': vod_name,
'vod_pic': vod_pic,
'vod_year': vod_year,
'vod_tag': 'folder' if vod_tag else '',
'vod_remarks': vod_remarks,
'style': style,
'type_name': type_name,
'vod_area': vod_area,
'vod_actor': vod_actor,
'vod_director': vod_director,
'vod_content': vod_content,
'vod_play_from': vod_play_from,
'vod_play_url': vod_play_url
}
vod = {key: value for key, value in vod.items() if value}
return vod
def getpq(self, url, headers=None, cookies=None):
data = self.fetch(url, headers=headers, cookies=cookies).text
try:
return pq(data)
except Exception as e:
print(f"解析页面错误: {str(e)}")
return pq(data.encode('utf-8'))
def gethr(self, index, rf='', zr=''):
headers = self.headers[index]
if zr:
headers['referer'] = zr
else:
headers['referer'] = f"{self.referers[rf]}/"
return headers
def handle_exception(self, e):
print(f"报错: {str(e)}")
return {'vod_play_from': '哎呀翻车啦', 'vod_play_url': f'翻车啦${self.excepturl}'}

93
py/美帕影视.py Normal file
View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import sys
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "mp"
def init(self, extend=""):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host = 'https://g.c494.com'
header = {
'User-Agent': 'Dart/2.10 (dart:io)',
'platform_version': 'RP1A.200720.011',
'version': '2.2.3',
'copyright': 'xiaogui',
'platform': 'android',
'client_name': '576O5p+P5b2x6KeG',
}
def homeContent(self, filter):
data = self.fetch(f'{self.host}/api.php/app/nav?token=', headers=self.header).json()
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
"sort": "排序"}
filters = {}
classes = []
json_data = data["list"]
for item in json_data:
has_non_empty_field = False
jsontype_extend = item["type_extend"]
classes.append({"type_name": item["type_name"], "type_id": str(item["type_id"])})
for key in dy:
if key in jsontype_extend and jsontype_extend[key].strip() != "":
has_non_empty_field = True
break
if has_non_empty_field:
filters[str(item["type_id"])] = []
for dkey in jsontype_extend:
if dkey in dy and jsontype_extend[dkey].strip() != "":
values = jsontype_extend[dkey].split(",")
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
value.strip() != ""]
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
result = {}
result["class"] = classes
result["filters"] = filters
return result
def homeVideoContent(self):
rsp = self.fetch(f"{self.host}/api.php/app/index_video?token=", headers=self.header)
root = rsp.json()['list']
videos = [item for vodd in root for item in vodd['vlist']]
return {'list': videos}
def categoryContent(self, tid, pg, filter, extend):
parms = {"pg": pg, "tid": tid, "class": extend.get("class", ""), "area": extend.get("area", ""),
"lang": extend.get("lang", ""), "year": extend.get("year", ""), "token": ""}
data = self.fetch(f'{self.host}/api.php/app/video', params=parms, headers=self.header).json()
return data
def detailContent(self, ids):
parms = {"id": ids[0], "token": ""}
data = self.fetch(f'{self.host}/api.php/app/video_detail', params=parms, headers=self.header).json()
vod = data['data']
vod.pop('pause_advert_list', None)
vod.pop('init_advert_list', None)
vod.pop('vod_url_with_player', None)
return {"list": [vod]}
def searchContent(self, key, quick, pg='1'):
parms = {'pg': pg, 'text': key, 'token': ''}
data = self.fetch(f'{self.host}/api.php/app/search', params=parms, headers=self.header).json()
return data
def playerContent(self, flag, id, vipFlags):
return {"parse": 0, "url": id, "header": {'User-Agent': 'User-Agent: Lavf/58.12.100'}}
def localProxy(self, param):
pass

323
py/腾讯视频.py Normal file
View File

@@ -0,0 +1,323 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import sys
import uuid
import copy
sys.path.append('..')
from base.spider import Spider
from concurrent.futures import ThreadPoolExecutor, as_completed
class Spider(Spider):
def init(self, extend=""):
self.dbody = {
"page_params": {
"channel_id": "",
"filter_params": "sort=75",
"page_type": "channel_operation",
"page_id": "channel_list_second_page"
}
}
self.body = self.dbody
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host = 'https://v.qq.com'
apihost = 'https://pbaccess.video.qq.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',
'origin': host,
'referer': f'{host}/'
}
def homeContent(self, filter):
cdata = {
"电视剧": "100113",
"电影": "100173",
"综艺": "100109",
"纪录片": "100105",
"动漫": "100119",
"少儿": "100150",
"短剧": "110755"
}
result = {}
classes = []
filters = {}
for k in cdata:
classes.append({
'type_name': k,
'type_id': cdata[k]
})
with ThreadPoolExecutor(max_workers=len(classes)) as executor:
futures = [executor.submit(self.get_filter_data, item['type_id']) for item in classes]
for future in futures:
cid, data = future.result()
if not data.get('data', {}).get('module_list_datas'):
continue
filter_dict = {}
try:
items = data['data']['module_list_datas'][-1]['module_datas'][-1]['item_data_lists']['item_datas']
for item in items:
if not item.get('item_params', {}).get('index_item_key'):
continue
params = item['item_params']
filter_key = params['index_item_key']
if filter_key not in filter_dict:
filter_dict[filter_key] = {
'key': filter_key,
'name': params['index_name'],
'value': []
}
filter_dict[filter_key]['value'].append({
'n': params['option_name'],
'v': params['option_value']
})
except (IndexError, KeyError):
continue
filters[cid] = list(filter_dict.values())
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
json_data = {'page_context':None,'page_params':{'page_id':'100101','page_type':'channel','skip_privacy_types':'0','support_click_scan':'1','new_mark_label_enabled':'1','ams_cookies':'',},'page_bypass_params':{'params':{'caller_id':'','data_mode':'default','page_id':'','page_type':'channel','platform_id':'2','user_mode':'default',},'scene':'channel','abtest_bypass_id':'',}}
data = self.post(f'{self.apihost}/trpc.vector_layout.page_view.PageService/getPage',headers=self.headers, json=json_data).json()
vlist = []
for it in data['data']['CardList'][0]['children_list']['list']['cards']:
if it.get('params'):
p = it['params']
tag = json.loads(p.get('uni_imgtag', '{}') or p.get('imgtag', '{}') or '{}')
id = it.get('id') or p.get('cid')
name = p.get('mz_title') or p.get('title')
if name and 'http' not in id:
vlist.append({
'vod_id': id,
'vod_name': name,
'vod_pic': p.get('image_url'),
'vod_year': tag.get('tag_2', {}).get('text'),
'vod_remarks': tag.get('tag_4', {}).get('text')
})
return {'list': vlist}
def categoryContent(self, tid, pg, filter, extend):
result = {}
params = {
"sort": extend.get('sort', '75'),
"attr": extend.get('attr', '-1'),
"itype": extend.get('itype', '-1'),
"ipay": extend.get('ipay', '-1'),
"iarea": extend.get('iarea', '-1'),
"iyear": extend.get('iyear', '-1'),
"theater": extend.get('theater', '-1'),
"award": extend.get('award', '-1'),
"recommend": extend.get('recommend', '-1')
}
if pg == '1':
self.body = self.dbody.copy()
self.body['page_params']['channel_id'] = tid
self.body['page_params']['filter_params'] = self.josn_to_params(params)
data = self.post(
f'{self.apihost}/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData?video_appid=1000005&vplatform=2&vversion_name=8.9.10&new_mark_label_enabled=1',
json=self.body, headers=self.headers).json()
ndata = data['data']
if ndata['has_next_page']:
result['pagecount'] = 9999
self.body['page_context'] = ndata['next_page_context']
else:
result['pagecount'] = int(pg)
vlist = []
for its in ndata['module_list_datas'][-1]['module_datas'][-1]['item_data_lists']['item_datas']:
id = its.get('item_params', {}).get('cid')
if id:
p = its['item_params']
tag = json.loads(p.get('uni_imgtag', '{}') or p.get('imgtag', '{}') or '{}')
name = p.get('mz_title') or p.get('title')
pic = p.get('new_pic_hz') or p.get('new_pic_vt')
vlist.append({
'vod_id': id,
'vod_name': name,
'vod_pic': pic,
'vod_year': tag.get('tag_2', {}).get('text'),
'vod_remarks': tag.get('tag_4', {}).get('text')
})
result['list'] = vlist
result['page'] = pg
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
vbody = {"page_params":{"req_from":"web","cid":ids[0],"vid":"","lid":"","page_type":"detail_operation","page_id":"detail_page_introduction"},"has_cache":1}
body = {"page_params":{"req_from":"web_vsite","page_id":"vsite_episode_list","page_type":"detail_operation","id_type":"1","page_size":"","cid":ids[0],"vid":"","lid":"","page_num":"","page_context":"","detail_page_type":"1"},"has_cache":1}
with ThreadPoolExecutor(max_workers=2) as executor:
future_detail = executor.submit(self.get_vdata, vbody)
future_episodes = executor.submit(self.get_vdata, body)
vdata = future_detail.result()
data = future_episodes.result()
pdata = self.process_tabs(data, body, ids)
if not pdata:
return self.handle_exception(None, "No pdata available")
try:
star_list = vdata['data']['module_list_datas'][0]['module_datas'][0]['item_data_lists']['item_datas'][
0].get('sub_items', {}).get('star_list', {}).get('item_datas', [])
actors = [star['item_params']['name'] for star in star_list]
names = ['腾讯视频', '预告片']
plist, ylist = self.process_pdata(pdata, ids)
if not plist:
del names[0]
if not ylist:
del names[1]
vod = self.build_vod(vdata, actors, plist, ylist, names)
return {'list': [vod]}
except Exception as e:
return self.handle_exception(e, "Error processing detail")
def searchContent(self, key, quick, pg="1"):
headers = self.headers.copy()
headers.update({'Content-Type': 'application/json'})
body = {'version':'25021101','clientType':1,'filterValue':'','uuid':str(uuid.uuid4()),'retry':0,'query':key,'pagenum':int(pg)-1,'pagesize':30,'queryFrom':0,'searchDatakey':'','transInfo':'','isneedQc':True,'preQid':'','adClientInfo':'','extraInfo':{'isNewMarkLabel':'1','multi_terminal_pc':'1','themeType':'1',},}
data = self.post(f'{self.apihost}/trpc.videosearch.mobile_search.MultiTerminalSearch/MbSearch?vplatform=2',
json=body, headers=headers).json()
vlist = []
vname=["电视剧", "电影", "综艺", "纪录片", "动漫", "少儿", "短剧"]
v=data['data']['normalList']['itemList']
d=data['data']['areaBoxList'][0]['itemList']
q=v+d
if v[0].get('doc') and v[0]['doc'].get('id') =='MainNeed':q=d+v
for k in q:
if k.get('doc') and k.get('videoInfo') and k['doc'].get('id') and '外站' not in k['videoInfo'].get('subTitle') and k['videoInfo'].get('title') and k['videoInfo'].get('typeName') in vname:
img_tag = k.get('videoInfo', {}).get('imgTag')
if img_tag is not None and isinstance(img_tag, str):
try:
tag = json.loads(img_tag)
except json.JSONDecodeError as e:
tag = {}
else:
tag = {}
pic = k.get('videoInfo', {}).get('imgUrl')
vlist.append({
'vod_id': k['doc']['id'],
'vod_name': self.removeHtmlTags(k['videoInfo']['title']),
'vod_pic': pic,
'vod_year': k['videoInfo'].get('typeName') +' '+ tag.get('tag_2', {}).get('text', ''),
'vod_remarks': tag.get('tag_4', {}).get('text', '')
})
return {'list': vlist, 'page': pg}
def playerContent(self, flag, id, vipFlags):
ids = id.split('@')
url = f"{self.host}/x/cover/{ids[0]}/{ids[1]}.html"
return {'jx':1,'parse': 1, 'url': url, 'header': ''}
def localProxy(self, param):
pass
def get_filter_data(self, cid):
hbody = self.dbody.copy()
hbody['page_params']['channel_id'] = cid
data = self.post(
f'{self.apihost}/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData?video_appid=1000005&vplatform=2&vversion_name=8.9.10&new_mark_label_enabled=1',
json=hbody, headers=self.headers).json()
return cid, data
def get_vdata(self, body):
try:
vdata = self.post(
f'{self.apihost}/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData?video_appid=3000010&vplatform=2&vversion_name=8.2.96',
json=body, headers=self.headers
).json()
return vdata
except Exception as e:
print(f"Error in get_vdata: {str(e)}")
return {'data': {'module_list_datas': []}}
def process_pdata(self, pdata, ids):
plist = []
ylist = []
for k in pdata:
if k.get('item_id'):
pid = f"{k['item_params']['union_title']}${ids[0]}@{k['item_id']}"
if '预告' in k['item_params']['union_title']:
ylist.append(pid)
else:
plist.append(pid)
return plist, ylist
def build_vod(self, vdata, actors, plist, ylist, names):
d = vdata['data']['module_list_datas'][0]['module_datas'][0]['item_data_lists']['item_datas'][0]['item_params']
urls = []
if plist:
urls.append('#'.join(plist))
if ylist:
urls.append('#'.join(ylist))
vod = {
'type_name': d.get('sub_genre', ''),
'vod_name': d.get('title', ''),
'vod_year': d.get('year', ''),
'vod_area': d.get('area_name', ''),
'vod_remarks': d.get('holly_online_time', '') or d.get('hotval', ''),
'vod_actor': ','.join(actors),
'vod_content': d.get('cover_description', ''),
'vod_play_from': '$$$'.join(names),
'vod_play_url': '$$$'.join(urls)
}
return vod
def handle_exception(self, e, message):
print(f"{message}: {str(e)}")
return {'list': [{'vod_play_from': '哎呀翻车啦', 'vod_play_url': '翻车啦#555'}]}
def process_tabs(self, data, body, ids):
try:
pdata = data['data']['module_list_datas'][-1]['module_datas'][-1]['item_data_lists']['item_datas']
tabs = data['data']['module_list_datas'][-1]['module_datas'][-1]['module_params'].get('tabs')
if tabs and len(json.loads(tabs)):
tabs = json.loads(tabs)
remaining_tabs = tabs[1:]
task_queue = []
for tab in remaining_tabs:
nbody = copy.deepcopy(body)
nbody['page_params']['page_context'] = tab['page_context']
task_queue.append(nbody)
with ThreadPoolExecutor(max_workers=10) as executor:
future_map = {executor.submit(self.get_vdata, task): idx for idx, task in enumerate(task_queue)}
results = [None] * len(task_queue)
for future in as_completed(future_map.keys()):
idx = future_map[future]
results[idx] = future.result()
for result in results:
if result:
page_data = result['data']['module_list_datas'][-1]['module_datas'][-1]['item_data_lists'][
'item_datas']
pdata.extend(page_data)
return pdata
except Exception as e:
print(f"Error processing episodes: {str(e)}")
return []
def josn_to_params(self, params, skip_empty=False):
query = []
for k, v in params.items():
if skip_empty and not v:
continue
query.append(f"{k}={v}")
return "&".join(query)

205
py/芒果视频.py Normal file
View File

@@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
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
rhost='https://www.mgtv.com'
host='https://pianku.api.mgtv.com'
vhost='https://pcweb.api.mgtv.com'
mhost='https://dc.bz.mgtv.com'
shost='https://mobileso.bz.mgtv.com'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.61 Chrome/126.0.6478.61 Not/A)Brand/8 Safari/537.36',
'origin': rhost,
'referer': f'{rhost}/'
}
def homeContent(self, filter):
result = {}
cateManual = {
"电影": "3",
"电视剧": "2",
"综艺": "1",
"动画": "50",
"少儿": "10",
"纪录片": "51",
"教育": "115"
}
classes = []
filters = {}
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
with ThreadPoolExecutor(max_workers=len(classes)) as executor:
results = executor.map(self.getf, classes)
for id, ft in results:
if len(ft):filters[id] = ft
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
data=self.fetch(f'{self.mhost}/dynamic/v1/channel/index/0/0/0/1000000/0/0/17/1354?type=17&version=5.0&t={str(int(time.time()*1000))}&_support=10000000', headers=self.headers).json()
videoList = []
for i in data['data']:
if i.get('DSLList') and len(i['DSLList']):
for j in i['DSLList']:
if j.get('data') and j['data'].get('items') and len(j['data']['items']):
for k in j['data']['items']:
videoList.append({
'vod_id': k["videoId"],
'vod_name': k['videoName'],
'vod_pic': k['img'],
'vod_year': k.get('cornerTitle'),
'vod_remarks': k.get('time') or k.get('desc'),
})
return {'list':videoList}
def categoryContent(self, tid, pg, filter, extend):
body={
'allowedRC': '1',
'platform': 'pcweb',
'channelId': tid,
'pn': pg,
'pc': '80',
'hudong': '1',
'_support': '10000000'
}
body.update(extend)
data=self.fetch(f'{self.host}/rider/list/pcweb/v3', params=body, headers=self.headers).json()
videoList = []
for i in data['data']['hitDocs']:
videoList.append({
'vod_id': i["playPartId"],
'vod_name': i['title'],
'vod_pic': i['img'],
'vod_year': (i.get('rightCorner',{}) or {}).get('text') or i.get('year'),
'vod_remarks': i['updateInfo']
})
result = {}
result['list'] = videoList
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
vbody={'allowedRC': '1', 'vid': ids[0], 'type': 'b', '_support': '10000000'}
vdata=self.fetch(f'{self.vhost}/video/info', params=vbody, headers=self.headers).json()
d=vdata['data']['info']['detail']
vod = {
'vod_name': vdata['data']['info']['title'],
'type_name': d.get('kind'),
'vod_year': d.get('releaseTime'),
'vod_area': d.get('area'),
'vod_lang': d.get('language'),
'vod_remarks': d.get('updateInfo'),
'vod_actor': d.get('leader'),
'vod_director': d.get('director'),
'vod_content': d.get('story'),
'vod_play_from': '芒果TV',
'vod_play_url': ''
}
data,pdata=self.fetch_page_data('1', ids[0],True)
pagecount=data['data'].get('total_page') or 1
if int(pagecount)>1:
pages = list(range(2, pagecount+1))
page_results = {}
with ThreadPoolExecutor(max_workers=10) as executor:
future_to_page = {
executor.submit(self.fetch_page_data, page, ids[0]): page
for page in pages
}
for future in as_completed(future_to_page):
page = future_to_page[future]
try:
result = future.result()
page_results[page] = result
except Exception as e:
print(f"Error fetching page {page}: {e}")
for page in sorted(page_results.keys()):
pdata.extend(page_results[page])
vod['vod_play_url'] = '#'.join(pdata)
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
data=self.fetch(f'{self.shost}/applet/search/v1?channelCode=mobile-wxap&q={key}&pn={pg}&pc=10&_support=10000000', headers=self.headers).json()
videoList = []
for i in data['data']['contents']:
if i.get('data') and len(i['data']):
k = i['data'][0]
if k.get('vid') and k.get('img'):
try:
videoList.append({
'vod_id': k['vid'],
'vod_name': k['title'],
'vod_pic': k['img'],
'vod_year': (i.get('rightTopCorner',{}) or {}).get('text') or i.get('year'),
'vod_remarks': '/'.join(i.get('desc',[])),
})
except:
print(k)
return {'list':videoList,'page':pg}
def playerContent(self, flag, id, vipFlags):
id=f'{self.rhost}{id}'
return {'jx':1,'parse': 1, 'url': id, 'header': ''}
def localProxy(self, param):
pass
def getf(self, body):
params = {
'allowedRC': '1',
'channelId': body['type_id'],
'platform': 'pcweb',
'_support': '10000000',
}
data = self.fetch(f'{self.host}/rider/config/channel/v1', params=params, headers=self.headers).json()
ft = []
for i in data['data']['listItems']:
try:
value_array = [{"n": value['tagName'], "v": value['tagId']} for value in i['items'] if
value.get('tagName')]
ft.append({"key": i['eName'], "name": i['typeName'], "value": value_array})
except:
print(i)
return body['type_id'], ft
def fetch_page_data(self, page, id, b=False):
body = {'version': '5.5.35', 'video_id': id, 'page': page, 'size': '30',
'platform': '4', 'src': 'mgtv', 'allowedRC': '1', '_support': '10000000'}
data = self.fetch(f'{self.vhost}/episode/list', params=body, headers=self.headers).json()
ldata = [f'{i["t3"]}${i["url"]}' for i in data['data']['list']]
if b:
return data, ldata
else:
return ldata

182
py/追星影视.py Normal file
View File

@@ -0,0 +1,182 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import random
import string
import sys
from base64 import b64decode, b64encode
from urllib.parse import quote, unquote
sys.path.append('..')
import concurrent.futures
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://xy.51gy.top'
headers = {
'User-Agent': 'okhttp/4.9.1',
'mark-time': 'null',
'fn-api-version': '3.1.9',
'versionCode': '19',
'product': 'gysg',
'sg': '22664e555e0015684f988833803b3055',
}
def homeContent(self, filter):
data=self.fetch(f"{self.host}/api.php/vod/type", headers=self.headers).json()
result,filters,videos = {},{},[]
classes = [{'type_id': i['type_name'], 'type_name': i['type_name']} for i in data['list'][1:]]
body={'token':'', 'type_id':data['list'][0]['type_id']}
ldata=self.post(f"{self.host}/api.php/vod/category", data=body, headers=self.headers).json()
for i in ldata['data']['banner']:
videos.append({
'vod_id':i.get('vod_id'),
'vod_name':i.get('vod_name'),
'vod_pic':i.get('vod_pic_thumb')
})
with concurrent.futures.ThreadPoolExecutor(max_workers=len(classes)) as executor:
future_to_aid = {executor.submit(self.fts, aid): aid for aid in classes}
for future in concurrent.futures.as_completed(future_to_aid):
aid = future_to_aid[future]
try:
aid_id, fts = future.result()
filters[aid_id] = fts
except Exception as e:
print(f"Error processing aid {aid}: {e}")
result['class'] = classes
result['filters'] = filters
result['list'] = videos
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
params={'state':extend.get('state',tid) or tid,'class':extend.get('classes','全部'),'area':extend.get('area','全部'),'year':extend.get('year','全部'),'lang':extend.get('lang','全部'),'version':extend.get('version','全部'),'pg':pg}
data=self.fetch(f"{self.host}/api.php/vod/list", params=params, headers=self.headers).json()
result = {}
videos = []
for i in data['data']['list']:
if str(i.get('vod_id', 0)) != '0':
videos.append({
'vod_id': i.get('vod_id'),
'vod_name': i.get('vod_name'),
'vod_pic': i.get('vod_pic'),
'vod_year': f"{i.get('vod_score')}",
'vod_remarks': i.get('vod_remarks')
})
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
body={'ids':ids[0],'uni_code':self.getunc(),'ac':'detail','token':''}
data=self.post(f"{self.host}/api.php/vod/detail2", data=body, headers=self.headers).json()
v=data['data']
vod = {
'type_name': v.get('type_name'),
'vod_year': v.get('vod_year'),
'vod_area': v.get('vod_area'),
'vod_lang': v.get('vod_lang'),
'vod_remarks': v.get('vod_remarks'),
'vod_actor': v.get('vod_actor'),
'vod_director': v.get('vod_director'),
'vod_content': v.get('vod_content')
}
n,p=[],[]
for i in v['vod_play_list']:
pp=i['player_info']
n.append(pp['show'])
np=[]
for j in i['urls']:
cd={'parse':pp.get('parse'),'url':j['url'],'headers':pp.get('headers')}
np.append(f"{j['name']}${self.e64(json.dumps(cd))}")
p.append('#'.join(np))
vod.update({'vod_play_from':'$$$'.join(n),'vod_play_url':'$$$'.join(p)})
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
data=self.fetch(f"{self.host}/api.php/vod/search", params={'keywords':key,'type':'1','pg':pg}, headers=self.headers).json()
return {'list':data['list'],'page':pg}
def playerContent(self, flag, id, vipFlags):
ids=json.loads(self.d64(id))
headers = {}
urls=ids['url']
if ids.get('headers'):
hs=ids['headers'].split('=>',1)
headers[hs[0].strip()]=hs[-1].strip()
if isinstance(ids.get('parse'), list) and len(ids['parse']) > 0:
urls=[]
for i,x in enumerate(ids['parse']):
su=f"{self.getProxyUrl()}&url={quote(x+ids['url'])}"
urls.extend([f'解析{i+1}',su])
return {'parse': 0, 'url': urls, 'header': headers}
def localProxy(self, param):
try:
body = {'url':unquote(param['url'])}
data=self.post(f"{self.host}/api.php/vod/m_jie_xi", data=body, headers=self.headers).json()
url=data.get('url') or data['data'].get('url')
return [302,'video/MP2T',None,{'Location':url}]
except:
return []
def liveContent(self, url):
pass
def fts(self, tdata):
params={'state':tdata['type_id'],'pg':'1'}
data = self.fetch(f"{self.host}/api.php/vod/list", params=params, headers=self.headers).json()
ftks = ["classes", "area", "lang", "year", "version", "state"]
filter = [
{
'name': k,
'key': k,
'value': [{'n': i, 'v': i} for i in v.split(',')]
}
for k, v in data['data']['classes']["type_extend"].items()
if k in ftks and v
]
return tdata['type_id'],filter
def getunc(self):
chars = string.ascii_lowercase + string.digits
data = ''.join(random.choice(chars) for _ in range(16))
return self.e64(data)
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:
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:
return ""

225
py/金牌影视.py Normal file
View File

@@ -0,0 +1,225 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import sys
import threading
import uuid
import requests
sys.path.append('..')
from base.spider import Spider
import time
from Crypto.Hash import MD5, SHA1
class Spider(Spider):
'''
配置示例:
{
"key": "xxxx",
"name": "xxxx",
"type": 3,
"api": ".所在路径/金牌.py",
"searchable": 1,
"quickSearch": 1,
"filterable": 1,
"changeable": 1,
"ext": {
"site": "https://www.jiabaide.cn,域名2,域名3"
}
},
'''
def init(self, extend=""):
if extend:
hosts=json.loads(extend)['site']
self.host = self.host_late(hosts)
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeContent(self, filter):
cdata = self.fetch(f"{self.host}/api/mw-movie/anonymous/get/filer/type", headers=self.getheaders()).json()
fdata = self.fetch(f"{self.host}/api/mw-movie/anonymous/v1/get/filer/list", headers=self.getheaders()).json()
result = {}
classes = []
filters={}
for k in cdata['data']:
classes.append({
'type_name': k['typeName'],
'type_id': str(k['typeId']),
})
sort_values = [{"n": "最近更新", "v": "2"},{"n": "人气高低", "v": "3"}, {"n": "评分高低", "v": "4"}]
for tid, d in fdata['data'].items():
current_sort_values = sort_values.copy()
if tid == '1':
del current_sort_values[0]
filters[tid] = [
{"key": "type", "name": "类型",
"value": [{"n": i["itemText"], "v": i["itemValue"]} for i in d["typeList"]]},
*([] if not d["plotList"] else [{"key": "v_class", "name": "剧情",
"value": [{"n": i["itemText"], "v": i["itemText"]}
for i in d["plotList"]]}]),
{"key": "area", "name": "地区",
"value": [{"n": i["itemText"], "v": i["itemText"]} for i in d["districtList"]]},
{"key": "year", "name": "年份",
"value": [{"n": i["itemText"], "v": i["itemText"]} for i in d["yearList"]]},
{"key": "lang", "name": "语言",
"value": [{"n": i["itemText"], "v": i["itemText"]} for i in d["languageList"]]},
{"key": "sort", "name": "排序", "value": current_sort_values}
]
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
data1 = self.fetch(f"{self.host}/api/mw-movie/anonymous/v1/home/all/list", headers=self.getheaders()).json()
data2=self.fetch(f"{self.host}/api/mw-movie/anonymous/home/hotSearch",headers=self.getheaders()).json()
data=[]
for i in data1['data'].values():
data.extend(i['list'])
data.extend(data2['data'])
vods=self.getvod(data)
return {'list':vods}
def categoryContent(self, tid, pg, filter, extend):
params = {
"area": extend.get('area', ''),
"filterStatus": "1",
"lang": extend.get('lang', ''),
"pageNum": pg,
"pageSize": "30",
"sort": extend.get('sort', '1'),
"sortBy": "1",
"type": extend.get('type', ''),
"type1": tid,
"v_class": extend.get('v_class', ''),
"year": extend.get('year', '')
}
data = self.fetch(f"{self.host}/api/mw-movie/anonymous/video/list?{self.js(params)}", headers=self.getheaders(params)).json()
result = {}
result['list'] = self.getvod(data['data']['list'])
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
data=self.fetch(f"{self.host}/api/mw-movie/anonymous/video/detail?id={ids[0]}",headers=self.getheaders({'id':ids[0]})).json()
vod=self.getvod([data['data']])[0]
vod['vod_play_from']='金牌'
vod['vod_play_url'] = '#'.join(
f"{i['name'] if len(vod['episodelist']) > 1 else vod['vod_name']}${ids[0]}@@{i['nid']}" for i in
vod['episodelist'])
vod.pop('episodelist', None)
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
params = {
"keyword": key,
"pageNum": pg,
"pageSize": "8",
"sourceCode": "1"
}
data=self.fetch(f"{self.host}/api/mw-movie/anonymous/video/searchByWord?{self.js(params)}",headers=self.getheaders(params)).json()
vods=self.getvod(data['data']['result']['list'])
return {'list':vods,'page':pg}
def playerContent(self, flag, id, vipFlags):
self.header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.61 Chrome/126.0.6478.61 Not/A)Brand/8 Safari/537.36',
'sec-ch-ua-platform': '"Windows"',
'DNT': '1',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
'sec-ch-ua-mobile': '?0',
'Origin': self.host,
'Referer': f'{self.host}/'
}
ids=id.split('@@')
pdata = self.fetch(f"{self.host}/api/mw-movie/anonymous/v2/video/episode/url?clientType=1&id={ids[0]}&nid={ids[1]}",headers=self.getheaders({'clientType':'1','id': ids[0], 'nid': ids[1]})).json()
vlist=[]
for i in pdata['data']['list']:vlist.extend([i['resolutionName'],i['url']])
return {'parse':0,'url':vlist,'header':self.header}
def localProxy(self, param):
pass
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, 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 md5(self, sign_key):
md5_hash = MD5.new()
md5_hash.update(sign_key.encode('utf-8'))
md5_result = md5_hash.hexdigest()
return md5_result
def js(self, param):
return '&'.join(f"{k}={v}" for k, v in param.items())
def getheaders(self, param=None):
if param is None:param = {}
t=str(int(time.time()*1000))
param['key']='cb808529bae6b6be45ecfab29a4889bc'
param['t']=t
sha1_hash = SHA1.new()
sha1_hash.update(self.md5(self.js(param)).encode('utf-8'))
sign = sha1_hash.hexdigest()
deviceid = str(uuid.uuid4())
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.61 Chrome/126.0.6478.61 Not/A)Brand/8 Safari/537.36',
'Accept': 'application/json, text/plain, */*',
'sign': sign,
't': t,
'deviceid':deviceid
}
return headers
def convert_field_name(self, field):
field = field.lower()
if field.startswith('vod') and len(field) > 3:
field = field.replace('vod', 'vod_')
if field.startswith('type') and len(field) > 4:
field = field.replace('type', 'type_')
return field
def getvod(self, array):
return [{self.convert_field_name(k): v for k, v in item.items()} for item in array]

147
py/锦鲤短剧.py Normal file
View File

@@ -0,0 +1,147 @@
from base.spider import Spider
import re,sys,json
sys.path.append('..')
class Spider(Spider):
api_host = 'https://api.jinlidj.com'
origin = 'https://www.jinlidj.com'
api_path = '/api/search'
headers = {
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
'Content-Type': "application/json",
'accept-language': "zh-CN,zh;q=0.9",
'cache-control': "no-cache",
'origin': origin,
'pragma': "no-cache",
'priority': "u=1, i",
'referer': origin+'/',
'sec-ch-ua': "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"",
'sec-ch-ua-mobile': "?0",
'sec-ch-ua-platform': "\"Windows\"",
'sec-fetch-dest': "empty",
'sec-fetch-mode': "cors",
'sec-fetch-site': "same-site"
}
def homeContent(self, filter):
return {'class': [{'type_id': 1, 'type_name': '情感关系'}, {'type_id': 2, 'type_name': '成长逆袭'}, {'type_id': 3, 'type_name': '奇幻异能'}, {'type_id': 4, 'type_name': '战斗热血'}, {'type_id': 5, 'type_name': '伦理现实'}, {'type_id': 6, 'type_name': '时空穿越'}, {'type_id': 7, 'type_name': '权谋身份'}]}
def homeVideoContent(self):
payload = {
"page": 1,
"limit": 24,
"type_id": "",
"year": "",
"keyword": ""
}
response = self.post(f"{self.api_host}{self.api_path}", data=json.dumps(payload), headers=self.headers).json()
data = response['data']
videos = []
for i in data['list']:
videos.append({
'vod_id': i.get('vod_id'),
'vod_name': i.get('vod_name'),
'vod_class': i.get('vod_class'),
'vod_pic': i.get('vod_pic'),
'vod_year': i.get('vod_year'),
'vod_remarks': i.get('vod_total')+'',
'vod_score': i.get('vod_score')
})
return {'list': videos}
def detailContent(self, ids):
response = self.post(f'{self.api_host}/api/detail/{ids[0]}', data=json.dumps({}), headers=self.headers).json()
data = response['data']
videos = []
vod_play_url = ''
for name,url in data['player'].items():
vod_play_url += f'{name}${url}#'
vod_play_url.rstrip('#')
videos.append({
'vod_id': data.get('vod_id'),
'vod_name': data.get('vod_name'),
'vod_content': data.get('vod_blurb'),
'vod_remarks': '集数:' + data.get('vod_total'),
"vod_director": data.get('vod_director'),
"vod_actor": data.get('vod_actor'),
'vod_year': data.get('vod_year'),
'vod_area': data.get('vod_area'),
'vod_play_from': '锦鲤短剧',
'vod_play_url': vod_play_url
})
return {'list': videos}
def searchContent(self, key, quick, pg="1"):
payload = {
"page": pg,
"limit": 24,
"type_id": "",
"keyword": key
}
response = self.post(f'{self.api_host}{self.api_path}', data=json.dumps(payload), headers=self.headers).json()
data = response['data']
videos = []
for i in data['list']:
videos.append({
"vod_id": i['vod_id'],
"vod_name": i['vod_name'],
"vod_class": i['vod_class'],
"vod_pic": i['vod_pic'],
'vod_year': i.get('vod_year'),
"vod_remarks": i['vod_total'] + ''
})
return {'list': videos, 'page': pg, 'total': data['total'], 'limit': 24}
def categoryContent(self, tid, pg, filter, extend):
payload = {
"page": pg,
"limit": 24,
"type_id": tid,
"year": "",
"keyword": ""
}
response = self.post(f'{self.api_host}{self.api_path}', data=json.dumps(payload), headers=self.headers).json()
data = response['data']
videos = []
for i in data['list']:
videos.append({
'vod_id': i.get('vod_id'),
'vod_name': i.get('vod_name'),
'vod_class': i.get('vod_class'),
'vod_pic': i.get('vod_pic'),
'vod_remarks': i.get('vod_total')+'',
'vod_year': i.get('vod_year'),
'vod_score': i.get('vod_score')
})
return {'list': videos}
def playerContent(self, flag, id, vipflags):
parse = 0
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'}
try:
response = self.fetch(id, headers=self.headers).text
match = re.search(r'let\s+data\s*=\s*(\{[^}]*http[^}]*\});', response, re.IGNORECASE)
data = match.group(1)
data2 = json.loads(data)
url = data2['url']
except Exception:
url, parse, header = id, 1, self.headers
return {'parse': parse, 'url': url,'header': header}
def init(self, extend=''):
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def localProxy(self, param):
pass

220
py/零度影视.py Normal file
View File

@@ -0,0 +1,220 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import random
import sys
from base64 import b64encode, b64decode
from concurrent.futures import ThreadPoolExecutor
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
did=self.getdid()
self.headers.update({'deviceId': did})
token=self.gettk()
self.headers.update({'token': token})
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host='http://ldys.sq1005.top'
headers = {
'User-Agent': 'okhttp/4.12.0',
'client': 'app',
'deviceType': 'Android'
}
def homeContent(self, filter):
data=self.post(f"{self.host}/api/v1/app/screen/screenType", headers=self.headers).json()
result = {}
cate = {
"类型": "classify",
"地区": "region",
"年份": "year"
}
sort={
'key':'sreecnTypeEnum',
'name': '排序',
'value':[{'n':'最新','v':'NEWEST'},{'n':'人气','v':'POPULARITY'},{'n':'评分','v':'COLLECT'},{'n':'热搜','v':'HOT'}]
}
classes = []
filters = {}
for k in data['data']:
classes.append({
'type_name': k['name'],
'type_id': k['id']
})
filters[k['id']] = []
for v in k['children']:
filters[k['id']].append({
'name': v['name'],
'key': cate[v['name']],
'value':[{'n':i['name'],'v':i['name']} for i in v['children']]
})
filters[k['id']].append(sort)
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
jdata={"condition":64,"pageNum":1,"pageSize":40}
data=self.post(f"{self.host}/api/v1/app/recommend/recommendSubList", headers=self.headers, json=jdata).json()
return {'list':self.getlist(data['data']['records'])}
def categoryContent(self, tid, pg, filter, extend):
jdata = {
'condition': {
'sreecnTypeEnum': 'NEWEST',
'typeId': tid,
},
'pageNum': int(pg),
'pageSize': 40,
}
jdata['condition'].update(extend)
data = self.post(f"{self.host}/api/v1/app/screen/screenMovie", headers=self.headers, json=jdata).json()
result = {}
result['list'] = self.getlist(data['data']['records'])
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
ids = ids[0].split('@@')
jdata = {"id": int(ids[0]), "typeId": ids[-1]}
v = self.post(f"{self.host}/api/v1/app/play/movieDesc", headers=self.headers, json=jdata).json()
v = v['data']
vod = {
'type_name': v.get('classify'),
'vod_year': v.get('year'),
'vod_area': v.get('area'),
'vod_actor': v.get('star'),
'vod_director': v.get('director'),
'vod_content': v.get('introduce'),
'vod_play_from': '',
'vod_play_url': ''
}
c = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=jdata).json()
l = c['data']['moviePlayerList']
n = {str(i['id']): i['moviePlayerName'] for i in l}
m = jdata.copy()
m.update({'playerId': str(l[0]['id'])})
pd = self.getv(m, c['data']['episodeList'])
if len(l)-1:
with ThreadPoolExecutor(max_workers=len(l)-1) as executor:
future_to_player = {executor.submit(self.getd, jdata, player): player for player in l[1:]}
for future in future_to_player:
try:
o,p = future.result()
pd.update(self.getv(o,p))
except Exception as e:
print(f"请求失败: {e}")
w, e = [],[]
for i, x in pd.items():
if x:
w.append(n[i])
e.append(x)
vod['vod_play_from'] = '$$$'.join(w)
vod['vod_play_url'] = '$$$'.join(e)
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
jdata={
"condition": {
"value": key
},
"pageNum": int(pg),
"pageSize": 40
}
data=self.post(f"{self.host}/api/v1/app/search/searchMovie", headers=self.headers, json=jdata).json()
return {'list':self.getlist(data['data']['records']),'page':pg}
def playerContent(self, flag, id, vipFlags):
jdata=json.loads(self.d64(id))
data = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=jdata).json()
try:
params={'playerUrl':data['data']['url'],'playerId':jdata['playerId']}
pd=self.fetch(f"{self.host}/api/v1/app/play/analysisMovieUrl", headers=self.headers, params=params).json()
url,p=pd['data'],0
except Exception as e:
print(f"请求失败: {e}")
url,p=data['data']['url'],0
return {'parse': p, 'url': url, 'header': {'User-Agent': 'okhttp/4.12.0'}}
def localProxy(self, param):
pass
def liveContent(self, url):
pass
def gettk(self):
data=self.fetch(f"{self.host}/api/v1/app/user/visitorInfo", headers=self.headers).json()
return data['data']['token']
def getdid(self):
did=self.getCache('ldid')
if not did:
hex_chars = '0123456789abcdef'
did =''.join(random.choice(hex_chars) for _ in range(16))
self.setCache('ldid',did)
return did
def getd(self,jdata,player):
x = jdata.copy()
x.update({'playerId': str(player['id'])})
response = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=x).json()
return x, response['data']['episodeList']
def getv(self,d,c):
f={d['playerId']:''}
g=[]
for i in c:
j=d.copy()
j.update({'episodeId':str(i['id'])})
g.append(f"{i['episode']}${self.e64(json.dumps(j))}")
f[d['playerId']]='#'.join(g)
return f
def getlist(self,data):
videos = []
for i in data:
videos.append({
'vod_id': f"{i['id']}@@{i['typeId']}",
'vod_name': i.get('name'),
'vod_pic': i.get('cover'),
'vod_year': i.get('year'),
'vod_remarks': i.get('totalEpisode')
})
return videos
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 ""