294 lines
15 KiB
Python
294 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
|
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
|
|
|
from Crypto.Cipher import AES
|
|
from base.spider import Spider
|
|
from Crypto.Util.Padding import unpad,pad
|
|
from Crypto.Random import get_random_bytes
|
|
import re,os,sys,uuid,time,json,zlib,base64,urllib3,hashlib
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
sys.path.append('..')
|
|
|
|
class Spider(Spider):
|
|
def_headers2,config,host,token,req_uuid = {
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6299.95 Safari/537.36',
|
|
'Accept': 'application/json',
|
|
'Accept-Encoding': 'gzip',
|
|
'Content-Type': 'application/json',
|
|
'uuid': '',
|
|
'client_type': 'android',
|
|
'timestamp': '',
|
|
'sign': '',
|
|
'version': '',
|
|
'appkey': '',
|
|
'api_version': 'v1'
|
|
},{},'','',str(uuid.uuid4())
|
|
|
|
def init(self, extend=''):
|
|
try:
|
|
ext = json.loads(extend)
|
|
host = ext['host']
|
|
app_key = ext['appkey']
|
|
app_name = ext['name']
|
|
build_signature = ext['buildSignature']
|
|
build_number = ext['buildNumber']
|
|
version_name = ext['versionName']
|
|
package = ext['package']
|
|
login_path = ext.get('LoginPath', '/app/userInfo')
|
|
self.token = ext.get('token')
|
|
if not (host and app_key and app_key and app_name and build_signature and build_number and version_name and package): return
|
|
self.host = f"{host}/api"
|
|
self.def_headers2['appkey'] = app_key
|
|
self.def_headers2['version'] = ext['version']
|
|
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
|
payload = self.encrypt(f'{{"v":"{version_name}","n":"{app_name}","s":"{build_signature}","pl":"1","apiVersion":"v2","token":"","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
|
response = self.post(f'{self.host}/app/systemInit',data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False, timeout=30).text
|
|
data = json.loads(self.decrypt(response, self.req_uuid))
|
|
self.config['player'] = data['player']
|
|
self.config['parses'] = data['parser_api']
|
|
self.config['categories'] = data['categorys']['data']
|
|
if not self.token:
|
|
device_info_cache_key = f'99app_device_info_{app_key}_mPjOv6fHcE'
|
|
device_info = self.getCache(device_info_cache_key)
|
|
if not device_info:
|
|
device_info = {'did': str(uuid.uuid4()), 'install_time': int(time.time() * 1000)}
|
|
self.setCache(device_info_cache_key,device_info)
|
|
install_time,did = device_info['install_time'],device_info['did']
|
|
update_time = install_time
|
|
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
|
self.encrypt(f'{{"os":"android","name":"xiaomi","version":"15","sdkInt":32,"device":"xiaomi","brand":"xiaomi","manufacturer":"xiaomi","product":"b0q","hardware":"xiaomi","isPhysicalDevice":true,"androidId":"V417IR","bootloader":"unknown","display":"V417IR release-keys","host":"a11-gz01-test","tags":"release-keys","type":"user","finger":"xiaomi/b0q/b0q:15/V619IR/613:user/release-keys","app":{{"version":"{version_name}","name":"{app_name}","package":"{package}","buildNumber":"{build_number}","buildSignature":"{build_signature}","install":{install_time},"update":{update_time}}},"did":"{did}","apiVersion":"v2","channel":"","token":"","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
|
response2 = self.post(f'{self.host}{login_path}', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
|
|
device_data = json.loads(self.decrypt(response2, self.req_uuid))
|
|
try:
|
|
self.token = device_data['userInfo']['user_token']
|
|
except Exception:
|
|
self.token = uuid.uuid4().hex
|
|
except Exception:
|
|
self.host = ''
|
|
|
|
def homeContent(self, filter):
|
|
if not self.host: return None
|
|
classes = []
|
|
for i in self.config['categories']:
|
|
if isinstance(i,dict):
|
|
classes.append({'type_id': i['id'], 'type_name': i['name']})
|
|
return {'class': classes}
|
|
|
|
def homeVideoContent(self):
|
|
if not self.host: return None
|
|
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
|
payload = self.encrypt(f'{{"kw":"","page":"1","limit":21,"pid":"1","orderBy":"time","isCategory":1,"token":"","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
|
response = self.post(f'{self.host}/vod/search', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
|
|
data = json.loads(self.decrypt(response,self.req_uuid))
|
|
videos = self.arr2vods(data['data'])
|
|
return {'list': videos}
|
|
|
|
def categoryContent(self, tid, pg, filter, extend):
|
|
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
|
payload = self.encrypt(f'{{"kw":"","page":"{pg}","limit":21,"pid":"{tid}","orderBy":"time","isCategory":1,"token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
|
response = self.post(f'{self.host}/vod/search', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
|
|
data = json.loads(self.decrypt(response))
|
|
videos = self.arr2vods(data['data'])
|
|
return {'list': videos, 'pagecount': data['page_count'], 'page': pg}
|
|
|
|
def searchContent(self, key, quick, pg='1'):
|
|
if not self.host: return None
|
|
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
|
payload = self.encrypt(f'{{"kw":"{key}","page":{int(pg)},"limit":21,"orderBy":"vod_hits_month","sort":"desc","token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
|
response = self.post(f'{self.host}/vod/search', data=payload, headers=self.headers2(nonce,timestamp,payload),verify=False).text
|
|
data = json.loads(self.decrypt(response, self.req_uuid))
|
|
videos = self.arr2vods(data['data'])
|
|
return {'list': videos, 'pagecount': data['page_count'], 'page': pg}
|
|
|
|
def detailContent(self, ids):
|
|
if not self.host: return None
|
|
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
|
payload = self.encrypt(f'{{"id":"{ids[0]}","eps":"1","v":"2.0.0","pl":1,"token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
|
response = self.post(f'{self.host}/vod/detail', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
|
|
data = json.loads(self.decrypt(response))
|
|
data = data['data']
|
|
players = self.config['player']
|
|
resources = dict(zip(data['play_from'].split('$$$'), data['play_url'].split('$$$')))
|
|
player_sequence = sorted(players.keys(), key=lambda k: players[k]['sort'], reverse=True)
|
|
show2 = []
|
|
play_urls2 = []
|
|
for key in player_sequence:
|
|
if key in resources:
|
|
player = players[key]
|
|
if player['name'] != key:
|
|
show2.append(f"{player['name']}\u2005({key})")
|
|
else:
|
|
show2.append(key)
|
|
urls = resources[key].split('#')
|
|
urls2 = [f"{parts[0]}${key}@{parts[1]}" for url_part in urls if '$' in url_part and len(parts := url_part.split('$', 1)) > 1]
|
|
play_urls2.append('#'.join(urls2))
|
|
video = {
|
|
'vod_id': data['id'],
|
|
'vod_name': data['name'],
|
|
'vod_pic': data['pic'],
|
|
'vod_remarks': data['remarks'],
|
|
'vod_year': data['year'],
|
|
'vod_area': data['area'],
|
|
'vod_actor': data['actor'],
|
|
'vod_director': data['director'],
|
|
'vod_content': data['content'],
|
|
'vod_play_from': '$$$'.join(show2),
|
|
'vod_play_url': '$$$'.join(play_urls2),
|
|
'type_name': data['class']
|
|
}
|
|
return {'list': [video]}
|
|
|
|
def playerContent(self, flag, vid, vip_flags):
|
|
jx,sniff,url,play_headers = 0,0,'',{'User-Agent':'Lavf/58.12.100'}
|
|
webview_headers = {
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 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.9',
|
|
'Accept-Encoding': 'gzip, deflate',
|
|
'upgrade-insecure-requests': '1',
|
|
'sec-fetch-site': 'none',
|
|
'sec-fetch-mode': 'navigate',
|
|
'sec-fetch-user': '?1',
|
|
'sec-fetch-dest': 'document',
|
|
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7'
|
|
}
|
|
play_from, raw_url = vid.split('@')
|
|
try:
|
|
player = self.config['player'][play_from]
|
|
try:
|
|
server_play_headers = json.loads(player['headers'])
|
|
if isinstance(server_play_headers, dict):
|
|
play_headers.update(server_play_headers)
|
|
except Exception:
|
|
pass
|
|
if player['type'] == 0:
|
|
url = raw_url
|
|
elif player['type'] == 1:
|
|
if player['isParse'] == 1:
|
|
try:
|
|
parse_rule = player['parseUrl'].split(',')
|
|
except Exception:
|
|
parse_rule = []
|
|
webview_parses = []
|
|
for i in self.config['parses']:
|
|
try:
|
|
if parse_rule and not str(i['id']) in parse_rule:
|
|
continue
|
|
elif not(i['api_supports_code']) or i['api_supports_code'] and play_from in i['api_supports_code'].split(','):
|
|
if i['api_type'] == 'webview':
|
|
if i['api_url']:
|
|
webview_parses.append(i)
|
|
continue
|
|
elif i['api_type'] == 'json' and i['is_server_parser'] == 1:
|
|
parse_id = i['id']
|
|
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
|
payload = self.encrypt(f'{{"id":{parse_id},"url":"{raw_url}","token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
|
response = self.post(f'{self.host}/app/vodParser', data=payload, headers=self.headers2(nonce,timestamp,payload),verify=False).text
|
|
data = json.loads(self.decrypt(response, self.req_uuid))
|
|
play_url = data['data']
|
|
else:
|
|
jx_headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6299.95 Safari/537.36'}
|
|
response = self.fetch(f"{i['api_url']}{raw_url}", headers=jx_headers, verify=False).json()
|
|
play_url = response[i['idx_play_url']]
|
|
if play_url.startswith('http'):
|
|
url = play_url
|
|
break
|
|
except Exception:
|
|
continue
|
|
if not url:
|
|
for k in webview_parses:
|
|
try:
|
|
if parse_rule and not str(k['id']) in parse_rule:
|
|
continue
|
|
else:
|
|
if k['api_url'] and k['api_url'].startswith('http'):
|
|
return { 'jx': 0, 'parse': 1, 'url': f"{k['api_url']}{url}", 'header': play_headers}
|
|
except Exception:
|
|
continue
|
|
elif player['type'] == 2:
|
|
url, sniff = raw_url, 1
|
|
play_headers = webview_headers
|
|
except Exception:
|
|
pass
|
|
if not url:
|
|
url = vid
|
|
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', url):
|
|
jx = 1
|
|
return { 'jx': jx, 'parse': sniff, 'url': url, 'header': play_headers}
|
|
|
|
def decrypt(self, data, key=''):
|
|
try:
|
|
if not key: key = self.req_uuid
|
|
encrypted_bytes = base64.b64decode(data)
|
|
if len(encrypted_bytes) < 16: raise ValueError
|
|
cipher = AES.new(key.replace('-', '').encode('utf-8'), AES.MODE_CBC, encrypted_bytes[:16])
|
|
decrypted_padded = cipher.decrypt(encrypted_bytes[16:])
|
|
decrypted_raw = unpad(decrypted_padded, AES.block_size)
|
|
deflated_data = zlib.decompress(decrypted_raw)
|
|
return deflated_data.decode('utf-8')
|
|
except zlib.error:
|
|
raise ValueError
|
|
except Exception:
|
|
raise ValueError
|
|
|
|
def encrypt(self, data, key=''):
|
|
try:
|
|
if not key: key = self.req_uuid
|
|
data_bytes = data.encode('utf-8')
|
|
padded_data = pad(data_bytes, AES.block_size)
|
|
iv = get_random_bytes(AES.block_size)
|
|
key_bytes = key.replace('-', '').encode('utf-8')
|
|
cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
|
|
encrypted_bytes = cipher.encrypt(padded_data)
|
|
return base64.b64encode(iv + encrypted_bytes).decode('utf-8')
|
|
except Exception:
|
|
raise ValueError
|
|
|
|
def arr2vods(self, arr):
|
|
videos = []
|
|
for i in arr:
|
|
videos.append({
|
|
'vod_id': i['id'],
|
|
'vod_name': i['name'],
|
|
'vod_pic': i['pic'],
|
|
'vod_remarks': i['remarks'],
|
|
'vod_year': i['year'],
|
|
'vod_content': i.get('blurb'),
|
|
'type_name': i.get('class'),
|
|
'vod_area': i.get('area'),
|
|
'vod_actor': i.get('actor'),
|
|
'vod_director': i.get('director')
|
|
})
|
|
return videos
|
|
|
|
def headers2(self,nonce, timestamp, payload=''):
|
|
return {
|
|
**self.def_headers2,
|
|
'uuid': self.req_uuid,
|
|
'timestamp': timestamp,
|
|
'sign': self.sign(payload, timestamp, self.req_uuid,nonce, self.def_headers2['appkey']),
|
|
'nonce': nonce
|
|
}
|
|
|
|
def sign(self, body, timestamp, nonce, req_uuid, app_key):
|
|
combined = f'{body}:{timestamp}:{nonce}:{req_uuid}:{app_key}'
|
|
sha256_hash = hashlib.sha256(combined.encode('utf-8'))
|
|
return sha256_hash.hexdigest()
|
|
|
|
def nonce(self):
|
|
return base64.b64encode(os.urandom(16)).decode('utf-8')
|
|
|
|
def getName(self):
|
|
pass
|
|
|
|
def isVideoFormat(self, url):
|
|
pass
|
|
|
|
def manualVideoCheck(self):
|
|
pass
|
|
|
|
def destroy(self):
|
|
pass
|
|
|
|
def localProxy(self, param):
|
|
pass |