更新
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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}¶ms={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
|
||||
|
||||
@@ -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"]
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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}¤t_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))
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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}'}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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 ""
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
@@ -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 ""
|
||||
Reference in New Issue
Block a user