Files
2026-07-05 04:58:38 +00:00

482 lines
20 KiB
Python

# -*- coding: utf-8 -*-
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
from Crypto.Cipher import AES
from base.spider import Spider
from Crypto.Util.Padding import unpad
from urllib.parse import unquote_plus
import re,sys,time,json,base64,hashlib,urllib3
from Crypto.Util.number import bytes_to_long,long_to_bytes
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sys.path.append('..')
class DecryptShijie:
def __call__(self, str_data, z=False):
return self.decrypt(str_data, z)
@staticmethod
def decrypt(str_data, z=False):
if not str_data:
return None if z else str_data
trim_str = str_data.strip()
if trim_str.lower().startswith('<!doctype html') or trim_str.lower().startswith('<html'):
return None if z else str_data
try:
json_obj = json.loads(str_data)
if json_obj.get('code') != 1 or json_obj.get('msg') in ['退出成功', '热搜词库']:
return None if z else str_data
except Exception:
pass
clean_str = str_data.replace('"', '').replace('\\/', '/')
prefix = ''
if clean_str.startswith('lvdou+'):
prefix = 'lvdou+'
elif clean_str.startswith('lvDou+'):
prefix = 'lvDou+'
data_after_prefix = clean_str[len(prefix):] if prefix else clean_str
if len(data_after_prefix) <= 32: return None if z else clean_str
main_key = data_after_prefix[:32]
if len(main_key) != 32 or not main_key.isalnum(): return None if z else clean_str
encrypted_data = data_after_prefix[32:]
return DecryptShijie.core_decrypt(main_key, encrypted_data, z, clean_str)
@staticmethod
def core_decrypt(main_key, encrypted_data, z, origin_str):
if not encrypted_data.strip() or not DecryptShijie.is_valid_base64(encrypted_data):
return None if z else origin_str
try:
decrypted_raw = DecryptShijie.aes_decrypt(encrypted_data, main_key)
if not decrypted_raw or not decrypted_raw.strip():
return None if z else origin_str
except Exception:
return None if z else origin_str
try:
return DecryptShijie.de_obfuscate(decrypted_raw)
except Exception:
return decrypted_raw
@staticmethod
def aes_decrypt(encrypted_data, main_key):
key_hash = hashlib.sha256((main_key + 'encryption_key_salt_2024').encode()).digest()
iv_hash = hashlib.sha256((main_key + 'iv_salt_2024').encode()).digest()[:16]
if len(key_hash) != 32 or len(iv_hash) != 16: raise ValueError
cipher = AES.new(key_hash, AES.MODE_CBC, iv_hash)
decrypted = unpad(cipher.decrypt(base64.b64decode(encrypted_data)), AES.block_size)
return decrypted.decode()
@staticmethod
def de_obfuscate(data):
try:
json_obj = json.loads(data)
except Exception:
return data
if '_f' not in json_obj or not isinstance(json_obj['_f'], dict): return data
field_map = json_obj['_f']
required_fields = ['data', 'timestamp', 'checksum']
if not all(field in field_map for field in required_fields): return data
if not all(field in json_obj for field in [field_map['data'], field_map['timestamp'], field_map['checksum']]): return data
encoded_data = json_obj[field_map['data']]
if not DecryptShijie.is_valid_base64(encoded_data): return data
try:
decoded_data = base64.b64decode(encoded_data)
except Exception:
return data
return decoded_data.decode()
@staticmethod
def is_valid_base64(s):
if not s or len(s) % 4 != 0:
return False
try:
if re.match(r'^[A-Za-z0-9+/]*={0,2}$', s):
base64.b64decode(s, validate=True)
return True
except Exception:
pass
return False
class Spider(Spider):
headers = {'User-Agent': 'okhttp/4.12.0'}
def localProxy(self, params):
try:
if params.get('type') == 'ryjx':
data = self.ry_jx(params)
elif params.get('type') == 'ryccjx':
data = self.cc_jx(params)
else:
data = '请按要求传参'
return [200, 'text/json;charset=utf-8', data]
except Exception:
data = self.json_encode({"code": "400", "success": "0", "msg": "处理出错"})
return [400, 'text/json;charset=utf-8', data]
def ry_jx(self, params):
error_msg = self.json_encode({"code": "400", "success": "0", "msg": "URL或API为空"})
try:
v = params['v']
api = unquote_plus(params['api'])
if not (v and api.startswith('http')):
return error_msg
except Exception:
return error_msg
if self.no_parse(params):
return self.json_encode({"code": "-0", "success": "0", "msg": "链接不支持"})
retry = int(params.get('retry', 0))
timeout = params.get('timeout')
msg,data2 = None,None
for i in range(retry+1):
msg,data2 = None,None
try:
data = self.fetch(f'{api}{v}', headers=self.headers, timeout=timeout or 20, verify=False).json()
except Exception:
msg = self.json_encode({"code": "-1", "success": "0", "msg": "请求或json解析失败"})
continue
dat_vurl = data.get('url', '')
if not dat_vurl:
return self.json_encode(data)
video_url = self.common_decrypt(dat_vurl)
if not self.is_valid_url(video_url) or video_url == v:
msg = self.json_encode({"code": "-3", "success": "0", "msg": "解密或解析失败"})
continue
if re.match(r'^https?://.*/api/index/Video/\?.*&act=ZhiBaiQq[0-9]*\.m3u8$', video_url, flags=re.IGNORECASE) or video_url == 'https://kjjsaas-sh.oss-cn-shanghai.aliyuncs.com/u/3401405881/20240818-936952-fc31b16575e80a7562cdb1f81a39c6b0.mp4':
msg = self.json_encode({"code": "-4", "success": "0", "msg": "解析结果为无效地址"})
continue
data['url'] = video_url
if 'url' in data:
return self.arr2json(v, data)
data2 = self.json_encode(data)
return msg or data2 or self.json_encode({"code": "-5", "success": "0", "msg": "解析失败"})
def cc_jx(self, params):
v = params.get('v')
if not v: return '链接为空'
api = unquote_plus(params.get('api'))
if not api: return 'api为空'
if 'act=jxjm' not in api:
return 'api不支持'
appkey = params.get('appkey')
if not appkey: return 'appkey为空'
key = params.get('key')
if not key: return 'key为空'
timeout = params.get('timeout')
if self.no_parse(params):
return self.json_encode({"code": "-0", "success": "0", "msg": "链接不支持"})
timestamp = str(int(time.time()))
jx_api, line_id = api.split('id=',1)
payload = {
'url': v, 'id': str(line_id), 't': timestamp,
'sign': hashlib.md5(f'url={v}&id={line_id}&t={timestamp}&{appkey}'.encode('utf-8')).hexdigest()
}
headers = {
'User-Agent': 'okhttp/3.12.11',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
'Accept-Language': 'zh-CN,zh;q=0.8'
}
try:
response = self.post(f'{jx_api}url={v}&app=10000', data=payload, headers=headers, verify=False, timeout=timeout or 10).text
except Exception as e:
return self.json_encode({"code": "-1","success": "0","msg": "请求失败", "error": str(e)})
try:
data = json.loads(self.cc_decrypt(response, key))
except Exception as e:
return self.json_encode({"code": "-1", "success": "0", "msg": "解密或解析失败", "error": str(e), 'raw_data': response})
if 'url' in data:
return self.arr2json(v, data)
return self.json_encode(data)
def arr2json(self, v, data):
remove_key = ['type', 'ip', 'player', 'From', 'From_Url']
for key in remove_key:
data.pop(key, None)
if re.match(r'^https://(?:m|www)\.bilibili\.com/', v, re.I):
if 'Referer' not in data:
data['Referer'] = 'https://www.bilibili.com/'
if 'User-Agent' not in data:
data[
'User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36'
elif re.match(r'^https://(?:m|www)\.mgtv\.com/', v, re.I):
if 'User-Agent' not in data or data.get(
'User-Agent') == 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36':
data['User-Agent'] = 'MGDS/Android/2.0.5'
data['from'] = v
return self.json_encode(data)
def no_parse(self, params):
v = params.get('v')
play_from = params.get('from')
play_prefix = params.get('prefix')
play_include = params.get('include')
if not isinstance(v, str) or not v:
return False
if not any([play_from, play_prefix, play_include]): return False
play_from_map = {
'qq': 'qq.com',
'qiyi': 'iqiyi.com',
'youku': 'youku.com',
'mgtv': 'mgtv.com',
'bili': 'bilibili.com'
}
def split_param(param):
if isinstance(param, str):
return [p.strip() for p in param.split(',') if p.strip()]
return []
is_all = False
if play_from == 'all':
is_all = any(domain in v for domain in play_from_map.values())
is_from = False
if not is_all:
from_parts = split_param(play_from)
if from_parts: is_from = any((domain := play_from_map.get(key)) is not None and domain in v for key in from_parts)
is_prefix = False
prefix_parts = split_param(play_prefix)
if prefix_parts:
is_prefix = any(v.startswith(prefix) for prefix in prefix_parts)
is_include = False
include_parts = split_param(play_include)
if include_parts:
is_include = any(inc in v for inc in include_parts)
final_result = is_all or is_from or is_prefix or is_include
return not final_result
def common_decrypt(self, dat_vurl):
if re.match(r'^https?://baidu\.con/', dat_vurl, re.I):
if dat_vurl.count(':') > 2:
result = self.ui9_decode(dat_vurl)
if result: return result
result = self.decode(dat_vurl)
if result and self.is_valid_url(result): return result
if result:
result = self.ld_xor(result)
if result: return result
result = self.ld_xor(dat_vurl)
if result: return result
result = self.yd_xor(dat_vurl)
if result: return result
elif 'https://vod.baidu.com/' in dat_vurl:
result = self.ld_xor(dat_vurl)
if result: return result
elif '6max.con' in dat_vurl:
result = self.yd2(dat_vurl)
if result: return result
elif 'lvDou+' in dat_vurl:
result = DecryptShijie()(dat_vurl)
if result: return result
elif dat_vurl.startswith('https://vod-parses.baidu.com/'):
result = self.ui8(dat_vurl)
if result: return result
return None
def ui9_decode(self, input_str):
try:
cleaned_input = re.sub(r'https://baidu\.con/|lvDou\+', '', input_str)
if cleaned_input.count(':') < 2: return None
parts = cleaned_input.split(':', 3)
if len(parts) < 4: return None
_, key, iv, data_part = parts
cipher = AES.new(key.encode(), AES.MODE_CBC, iv.encode())
decrypted = unpad(cipher.decrypt(base64.b64decode(data_part)), AES.block_size)
return decrypted.decode()
except Exception:
return None
def decode(self, d):
original_d = d
try:
while re.match(r'^https?://baidu\.con/', d, re.I):
data = re.sub(r'^https?://baidu\.con/', '', d, flags=re.I)
if len(data) < 16:
return None
key = data[:16].encode()
encrypted_data = data[16:]
cipher = AES.new(key, AES.MODE_CBC, key)
decrypted = unpad(cipher.decrypt(base64.b64decode(encrypted_data)), AES.block_size)
d = decrypted.decode()
return d if d != original_d else None
except Exception:
return None
def ld_xor(self, d):
if not d: return None
original_d = d
try:
if 'https://baidu.con/' in d or 'https://vod.baidu.com/' in d or d.startswith('2423'):
d = d.replace('https://baidu.con/', '').replace('https://vod.baidu.com/', '')
if d.startswith("2423"): d = d[6:]
substring = str(int(time.time()))[:8]
decoded = base64.b64decode(d)
sb = ''
length = len(substring)
length2 = len(decoded)
for i in range(length2):
sb += chr(decoded[i] ^ ord(substring[i % length]))
d = base64.b64decode(sb).decode()
return d if d != original_d else None
except Exception:
return None
def yd_xor(self, d):
if not d or 'baidu.con/' not in d: return None
try:
parts = d.split('baidu.con/', 1)
if len(parts) < 2: return None
encoded_part = parts[1]
decoded = base64.b64decode(encoded_part)
split_parts = decoded.split(b'|', 1)
if len(split_parts) != 2: return None
str2, str3 = split_parts
key = 'k3yM@$k2024'
key_length = len(key)
sb2 = ''
for i in range(len(str2)):
char = chr(str2[i] ^ ord(key[i % key_length]))
sb2 += char
sb3 = ''
sb2_length = len(sb2)
if sb2_length == 0: return None
for i2 in range(len(str3)):
pos1 = (i2 + 5) % sb2_length
pos2 = i2 % sb2_length
xor_value = (ord(sb2[pos1]) + ord(sb2[pos2])) % 256
char = chr(str3[i2] ^ xor_value)
sb3 += char
return sb3
except Exception:
return None
def yd2(self, d):
if not d or '6max.con' not in d: return None
try:
data = d.replace('https://6max.con/', '')
if len(data) < 16: return None
key = data[:16][::-1]
encrypted_data = data[16:]
cipher = AES.new(key.encode(), AES.MODE_CBC, key.encode())
decrypted = unpad(cipher.decrypt(base64.b64decode(encrypted_data)), AES.block_size)
return decrypted.decode()
except Exception:
return None
def cc_decrypt(self, data, key):
cipher = AES.new(key[:16].encode('utf-8'), AES.MODE_CBC, key[-16:].encode('utf-8'))
decrypted = unpad(cipher.decrypt(base64.b64decode(data)), AES.block_size)
return decrypted.decode('utf-8')
def ui8_key(self, data):
def remove_padding(data_bytes):
if len(data_bytes) > 10 and data_bytes[0:2] == b'\x00\x02':
separator_index = -1
for i in range(2, len(data_bytes)):
if data_bytes[i] == 0x00:
separator_index = i
break
if separator_index != -1 and separator_index >= 10:
return data_bytes[separator_index + 1:]
if len(data_bytes) > 10 and data_bytes[0:2] == b'\x00\x01':
separator_index = data_bytes.find(b'\x00', 2)
if separator_index != -1:
return data_bytes[separator_index + 1:]
leading_zeros = 0
for i, byte in enumerate(data_bytes):
if byte == 0x00:
leading_zeros += 1
else:
break
if 4 < leading_zeros < len(data_bytes) - 4:
return data_bytes[leading_zeros:]
return data_bytes
try:
ciphertext = base64.b64decode(data)
e = 65537
key_size = 256
n = 21079562076490917015219587480852512299132282846588972509613626067512096161389328018952967457235862130835563213420479656634103575049300097161679204081658154497272581125245352803881834652019229809107820308002912749854520391970510245623300240009991722378306164668314002824501199929254049911331868110702469664803250702178374575823923934377743766444104491434605938939890181211241005585699358654598872394352374955459144334648826707577459995980217087014729507004998084715621911009102107688390105352539795259098849457413742825469024104523030276553490595865329453646012415093918163886593950079624681378051572920908409755103897
if len(ciphertext) > key_size: raise ValueError
data_int = bytes_to_long(ciphertext)
if data_int >= n: raise ValueError
result_bytes = long_to_bytes(pow(data_int, e, n))
if len(result_bytes) < key_size:
padding_length = key_size - len(result_bytes)
result_bytes = b'\x00' * padding_length + result_bytes
cleaned_bytes = remove_padding(result_bytes)
try:
result = cleaned_bytes.decode('utf-8')
return result
except UnicodeDecodeError:
printable_bytes = bytes(b for b in cleaned_bytes if 32 <= b <= 126)
if printable_bytes:
try:
return printable_bytes.decode('utf-8')
except UnicodeDecodeError:
return None
return None
except Exception:
raise ValueError
def ui8(self, data):
try:
if not data: return None
processed = data.replace('https://vod-parses.baidu.com/', '').replace('"', '')
parts = processed.split('81238', 1)
if len(parts) < 2 or not parts[1]: return None
aes_encrypted_part = parts[0]
rsa_encrypted_part = parts[1]
rsa_decrypted = self.ui8_key(rsa_encrypted_part)
aes_key_iv = rsa_decrypted.split('|', 1)
if len(aes_key_iv) < 2: return None
aes_key = aes_key_iv[0].encode('utf-8')
aes_iv = aes_key_iv[1].encode('utf-8')
if len(aes_key) != 16 or len(aes_iv) != 16: raise ValueError
aes_encrypted_bytes = base64.b64decode(aes_encrypted_part)
cipher = AES.new(aes_key, AES.MODE_CBC, aes_iv)
decrypted_padded = cipher.decrypt(aes_encrypted_bytes)
decrypted = unpad(decrypted_padded, AES.block_size).decode('utf-8')
return decrypted.strip('"')
except Exception:
return None
def is_valid_url(self, url):
"""验证是否为有效的URL"""
if not url or url == 'null':
return False
return re.match(r'https?://.*', url, re.I) is not None
def json_encode(self,arr):
return json.dumps(arr, ensure_ascii=False, indent=2)
def init(self, extend=''):
pass
def homeContent(self, filter):
pass
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
pass
def searchContent(self, key, quick, pg='1'):
pass
def detailContent(self, ids):
pass
def playerContent(self, flag, id, vipflags):
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass