Sync all projects
This commit is contained in:
@@ -0,0 +1,475 @@
|
||||
{
|
||||
"sites": [
|
||||
{
|
||||
"key": "八戒影视[APP]",
|
||||
"name": "八戒影视[APP]",
|
||||
"type": 3,
|
||||
"api": "./app/八戒影视.py"
|
||||
},
|
||||
{
|
||||
"key": "proxy_drama",
|
||||
"name": "proxy_drama",
|
||||
"type": 3,
|
||||
"hide": 1,
|
||||
"searchable": 0,
|
||||
"quickserch": 0,
|
||||
"changeable": 0,
|
||||
"api": "./app/proxy_drama.py"
|
||||
},
|
||||
{
|
||||
"key": "新浪资源[APP]",
|
||||
"name": "新浪资源[APP]",
|
||||
"type": 3,
|
||||
"changeable": 0,
|
||||
"api": "./app/新浪资源.py"
|
||||
},
|
||||
{
|
||||
"key": "独播库[APP]",
|
||||
"name": "独播库[APP]",
|
||||
"type": 3,
|
||||
"api": "./app/独播库.py"
|
||||
},
|
||||
{
|
||||
"key": "壹影视[APP]",
|
||||
"name": "壹影视[APP]",
|
||||
"type": 3,
|
||||
"api": "./app/壹影视.py"
|
||||
},
|
||||
{
|
||||
"key": "剧迷TV[APP]",
|
||||
"name": "剧迷TV[APP]",
|
||||
"type": 3,
|
||||
"api": "./app/看客TV.py",
|
||||
"ext": {
|
||||
"CosUrl": "http://2025-1329689796.cos.ap-guangzhou.myqcloud.com/jumi1.0/app1.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "美剧侠[APP]",
|
||||
"name": "美剧侠[APP]",
|
||||
"type": 3,
|
||||
"api": "./app/美剧侠.py"
|
||||
},
|
||||
{
|
||||
"key": "热播影视大全[RJAPP]",
|
||||
"name": "热播影视大全[RJAPP]",
|
||||
"type": 3,
|
||||
"api": "./app/RJAPP.py",
|
||||
"ext": "http://v.rbotv.cn"
|
||||
},
|
||||
{
|
||||
"key": "欣欣影视[XinJie]",
|
||||
"name": "欣欣影视[XinJie]",
|
||||
"type": 3,
|
||||
"api": "./app/XinJie.py",
|
||||
"ext": {
|
||||
"host": "usv46c9uRCE/PugcvJY1Pc/BM6KQv8vKYzpgVFmTPv8wdb18jQRMx9ElUPtnlHCd",
|
||||
"key": "505cab36b1111416",
|
||||
"iv": "0e832063ac451b27"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "咕噜映画[Appfox]",
|
||||
"name": "咕噜映画[Appfox]",
|
||||
"type": 3,
|
||||
"changeable": 0,
|
||||
"api": "./app/Appfox.py",
|
||||
"ext": {
|
||||
"host": "http://www.yingsw.top",
|
||||
"timeout": 8,
|
||||
"ver": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "追番达人[Appfox]",
|
||||
"name": "追番达人[Appfox]",
|
||||
"type": 3,
|
||||
"changeable": 0,
|
||||
"api": "./app/Appfox.py",
|
||||
"ext": {
|
||||
"host": "https://fox.zfdr.app/fox.json",
|
||||
"ver": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "全影视PRO[FeiApp]",
|
||||
"name": "全影视PRO[FeiApp]",
|
||||
"type": 3,
|
||||
"changeable": 0,
|
||||
"api": "./app/FeiApp.py",
|
||||
"ext": "http://ysb.yy-fun.cc/feiapp"
|
||||
},
|
||||
{
|
||||
"key": "番喜[ApptoV5]",
|
||||
"name": "番喜[ApptoV5]",
|
||||
"type": 3,
|
||||
"api": "./app/ApptoV5无加密.py",
|
||||
"ext": "http://118.89.203.120:8762"
|
||||
},
|
||||
{
|
||||
"key": "哔嘀影视[AppMuou]",
|
||||
"name": "哔嘀影视[AppMuou]",
|
||||
"type": 3,
|
||||
"api": "./app/AppMuou.py",
|
||||
"ext": {
|
||||
"host": "https://app.app.bidiys.com",
|
||||
"api": "/info.php",
|
||||
"name": "哔嘀影视",
|
||||
"version": "4.1.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "BILFUN影视大全[AppMuou]",
|
||||
"name": "BILFUN影视大全[AppMuou]",
|
||||
"type": 3,
|
||||
"api": "./app/AppMuou.py",
|
||||
"ext": {
|
||||
"host": "http://physical.centos.chat",
|
||||
"api": "/info.php",
|
||||
"name": "BILFUN影视大全",
|
||||
"version": "4.1.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "嘶哩嘶哩[skapp]",
|
||||
"name": "嘶哩嘶哩[skapp]",
|
||||
"type": 3,
|
||||
"api": "./app/skapp.py",
|
||||
"ext": {
|
||||
"host": "https://silisili-1251358327.cos.ap-shanghai.myqcloud.com/skapp/api.txt",
|
||||
"key": "ygcnbcznazmyrbdn",
|
||||
"iv": "6571230062143708"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "即看影视[skapp]",
|
||||
"name": "即看影视[skapp]",
|
||||
"type": 3,
|
||||
"api": "./app/skapp.py",
|
||||
"ext": {
|
||||
"host": "https://skyappdata-1321528676.cos.accelerate.myqcloud.com/4kapp/appipr.txt",
|
||||
"key": "ygcnbckhcuvygdyb",
|
||||
"iv": "4023892775143708"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "看客联盟[skapp]",
|
||||
"name": "看客联盟[skapp]",
|
||||
"type": 3,
|
||||
"api": "./app/skapp.py",
|
||||
"ext": {
|
||||
"host": "https://kankelm.cn:2024/appdomain.txt",
|
||||
"key": "ygcnbcvybqqckwqy",
|
||||
"iv": "1583560747143708"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "2k动漫[skapp]",
|
||||
"name": "2k动漫[skapp]",
|
||||
"type": 3,
|
||||
"api": "./app/skapp.py",
|
||||
"ext": {
|
||||
"host": "https://dmsk.oss-rg-china-mainland.aliyuncs.com/dmapp/dmapi.txt",
|
||||
"key": "ygcnbcobcegtgigg",
|
||||
"iv": "4058263969143708"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "麦田影院[AppV2]",
|
||||
"name": "麦田影院[AppV2]",
|
||||
"type": 3,
|
||||
"changeable": 0,
|
||||
"api": "./app/AppV2.py",
|
||||
"ext": "https://build.mt320.com/domain.json$/mtys.php/v6"
|
||||
},
|
||||
{
|
||||
"key": "星马视[AppV2]",
|
||||
"name": "星马视[AppV2]",
|
||||
"type": 3,
|
||||
"api": "./app/AppV2.py",
|
||||
"ext": "http://xingma.apidge.com/icciu_api.php/v1.vod"
|
||||
},
|
||||
{
|
||||
"key": "gotto[AppV2]",
|
||||
"name": "gotto[AppV2]",
|
||||
"type": 3,
|
||||
"api": "./app/AppV2.py",
|
||||
"ext": {
|
||||
"api": "http://m51.gotto.top/api.php/v1.vod",
|
||||
"ua": "okhttp/4.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "咕咕动漫[getapp3]",
|
||||
"name": "咕咕动漫[getapp3]",
|
||||
"type": 3,
|
||||
"api": "./app/getapp3.4.6.py",
|
||||
"ext": {"host": "https://www.gugu3.com", "key": "nKfZ8KX6JTNWRzTD"}
|
||||
},
|
||||
{
|
||||
"key": "jxsp[Hmys]",
|
||||
"name": "九霄视频[Hmys]",
|
||||
"type": 3,
|
||||
"api": "./app/Hmys.py",
|
||||
"ext": {
|
||||
"host": "https://dy.stxbed.com",
|
||||
"app_id": "jiuxiao",
|
||||
"versionCode": "10000",
|
||||
"UMENG_CHANNEL": "guan"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "YIYI影视[99APP]",
|
||||
"name": "YIYI影视[99APP]",
|
||||
"type": 3,
|
||||
"api": "./app/99APP2.py",
|
||||
"ext": {
|
||||
"host": "http://99app.appttt.cn",
|
||||
"appkey": "835013444649af6d88aca7bdf1846e56",
|
||||
"version": "bfc18c00-c866-46cb-8d7b-121c39b942d4",
|
||||
"versionName": "2.0.0",
|
||||
"name": "YIYI影视",
|
||||
"package": "com.yingfu.mobile.android.yiyiys",
|
||||
"buildNumber": "2001",
|
||||
"buildSignature": "CBDA00CF3F8921712231DA40AE69E446487CF4238D9EA976C6B4A729DA7F0995"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "落攻阁影视[99APP]",
|
||||
"name": "落攻阁影视[99APP]",
|
||||
"type": 3,
|
||||
"api": "./app/99APP2.py",
|
||||
"ext": {
|
||||
"host": "https://api.jciyuan.com",
|
||||
"appkey": "5ac60e53fbe93a890f0851fef0009d38",
|
||||
"version": "bfc18c00-c866-46cb-8d7b-121c39b942d4",
|
||||
"versionName": "6.1.0",
|
||||
"name": "落攻阁影视",
|
||||
"package": "com.yuaiweiwu.aixue.lggys",
|
||||
"buildNumber": "2001",
|
||||
"buildSignature": "CBDA00CF3F8921712231DA40AE69E446487CF4238D9EA976C6B4A729DA7F0995"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "开端[APP]",
|
||||
"name": "开端[APP]",
|
||||
"type": 3,
|
||||
"api": "./app/开端.py"
|
||||
},
|
||||
{
|
||||
"key": "247看[APP]",
|
||||
"name": "247看[APP]",
|
||||
"type": 3,
|
||||
"api": "./app/247看.py",
|
||||
"ext": {
|
||||
"parse": {
|
||||
"NBY": ["https://api.nbyjson.top:7788/api/?key=kc4uiyuJnSYO2s91XW&url="],
|
||||
"ATQP1": ["https://apijx.tufun.top/api/?key=HiCspU6U0zmpjHmZ6r&url="],
|
||||
"qq|qiyi|mgtv|youku|bilibili": ["https://api.huohua.live/api/?key=8Isa9iRrf6kZhGVCzq&url="]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "proxy_artjx",
|
||||
"name": "proxy_artjx",
|
||||
"type": 3,
|
||||
"hide": 1,
|
||||
"searchable": 0,
|
||||
"quickserch": 0,
|
||||
"changeable": 0,
|
||||
"api": "./app/proxy_artjx.py"
|
||||
},
|
||||
{
|
||||
"key": "proxy_artjx",
|
||||
"name": "proxy_artjx",
|
||||
"type": 3,
|
||||
"hide": 1,
|
||||
"searchable": 0,
|
||||
"quickserch": 0,
|
||||
"changeable": 0,
|
||||
"api": "./app/proxy_artjx.py"
|
||||
},
|
||||
{
|
||||
"key": "proxy_ryjx",
|
||||
"name": "proxy_ryjx",
|
||||
"type": 3,
|
||||
"hide": 1,
|
||||
"searchable": 0,
|
||||
"quickserch": 0,
|
||||
"changeable": 0,
|
||||
"api": "./app/proxy_ryjx.py"
|
||||
},
|
||||
{
|
||||
"key": "proxy_fyjx",
|
||||
"name": "proxy_fyjx",
|
||||
"type": 3,
|
||||
"hide": 1,
|
||||
"searchable": 0,
|
||||
"quickserch": 0,
|
||||
"changeable": 0,
|
||||
"api": "./app/proxy_fyjx.py"
|
||||
}
|
||||
],
|
||||
"wallpaper": "https://picsum.photos/1080",
|
||||
"parses": [
|
||||
{
|
||||
"name": "麒麟",
|
||||
"type": 1,
|
||||
"url": "proxy://do=py&siteKey=proxy_artjx&type=artjx&from=all&api=https://svip.qlplayer.cyou/api.php&v="
|
||||
},
|
||||
{
|
||||
"name": "西瓜影院",
|
||||
"type": 1,
|
||||
"url": "proxy://do=py&siteKey=proxy_artjx&type=artjx&from=all&api=https://ppl.sszzyy.com/api.php&v="
|
||||
},
|
||||
{
|
||||
"name": "F1解析",
|
||||
"type": 1,
|
||||
"url": "proxy://do=py&siteKey=proxy_artjx&type=artjx&from=all&api=https://f1c.cc/p/api.php&key=LXFMQW7294RBTE10&iv=Y9D2H6Z5K81NRC3P&v="
|
||||
},
|
||||
{
|
||||
"name": "清流影视",
|
||||
"type": 1,
|
||||
"url": "proxy://do=py&siteKey=proxy_ryjx&type=ryjx&from=all&prefix=YDYD&api=http://110.42.41.52:5625/api/index?parsesId=5%26appid=10000%26videoUrl=&v="
|
||||
},
|
||||
{
|
||||
"name": "清流影视2",
|
||||
"type": 1,
|
||||
"url": "proxy://do=py&siteKey=proxy_ryjx&type=ryjx&from=all&prefix=YDYD&api=http://110.42.41.52:5625/api/index?parsesId=5%26appid=10000%26videoUrl=&v="
|
||||
},
|
||||
{
|
||||
"name": "710影视",
|
||||
"type": 1,
|
||||
"url": "proxy://do=py&siteKey=proxy_ryjx&type=ryjx&from=all&api=http://14.103.161.168:3366/addons/lvdoubox/api.index?id=12%26url=&v="
|
||||
},
|
||||
{
|
||||
"name": "星耀",
|
||||
"type": 1,
|
||||
"url": "proxy://do=py&siteKey=proxy_ryjx&type=ryjx&from=all&prefix=XYTY,YYNB,NSYS,XYDM,XYJP,XYGZ&include=-quark-,-abaidu-,-uc-,-a123-&api=http://125.208.22.184:6698/api/index?parsesId=550266%26appid=10000%26videoUrl=&v="
|
||||
},
|
||||
{
|
||||
"name": "烈羽云",
|
||||
"type": 1,
|
||||
"url": "proxy://do=py&siteKey=proxy_ryjx&type=ryjx&from=all&prefix=LYY,JYY&api=http://lieyuyunui6.qijiyun.vip/api/index?parsesId=1%26appid=10000%26videoUrl=&v="
|
||||
},
|
||||
{
|
||||
"name": "ikun",
|
||||
"type": 0,
|
||||
"url": "https://jx.hls.one/?url="
|
||||
},
|
||||
{
|
||||
"name": "77",
|
||||
"type": 0,
|
||||
"url": "https://jx.77flv.cc/?url="
|
||||
},
|
||||
{
|
||||
"name": "A6",
|
||||
"type": 0,
|
||||
"url": "https://pl.a6club.com/player/analysis.php?v="
|
||||
},
|
||||
{
|
||||
"name": "8090",
|
||||
"type": 0,
|
||||
"url": "https://www.8090g.cn/?url="
|
||||
},
|
||||
{
|
||||
"name": "M3u8TV",
|
||||
"type": 0,
|
||||
"url": "https://jx.m3u8.tv/jiexi/?url="
|
||||
},
|
||||
{
|
||||
"name": "CK",
|
||||
"type": 0,
|
||||
"url": "https://www.ckplayer.vip/jiexi/?url="
|
||||
},
|
||||
{
|
||||
"name": "冰豆",
|
||||
"type": 0,
|
||||
"url": "https://bd.jx.cn/?url="
|
||||
},
|
||||
{
|
||||
"name": "XY",
|
||||
"type": 0,
|
||||
"url": "https://jx.xymp4.cc/?url="
|
||||
},
|
||||
{
|
||||
"name": "推荐",
|
||||
"type": 1,
|
||||
"url": "https://zy.qiaoji8.com/gouzi.php?url=94b07e0b2c0e8244&url="
|
||||
},
|
||||
{
|
||||
"name": "臻享",
|
||||
"type": 1,
|
||||
"url": "https://150.138.78.37:4399/api?key=94b07e0b2c0e8244&url="
|
||||
},
|
||||
{
|
||||
"name": "优选",
|
||||
"type": 1,
|
||||
"url": "http://1.94.221.189:88/algorithm.php?url="
|
||||
}
|
||||
],
|
||||
"flags": [],
|
||||
"ads": [],
|
||||
"spider": "",
|
||||
"lives": [
|
||||
{
|
||||
"name": "综合直播",
|
||||
"type": 0,
|
||||
"playerType": 2,
|
||||
"url": "https://ds65.tv1288.xyz"
|
||||
},
|
||||
{
|
||||
"name": "Kimentanm",
|
||||
"type": 0,
|
||||
"url": "https://gh.927223.xyz/https://raw.githubusercontent.com/Kimentanm/aptv/master/m3u/iptv.m3u",
|
||||
"playerType": 2
|
||||
},
|
||||
{
|
||||
"name": "develop202",
|
||||
"type": 0,
|
||||
"url": "https://gh.927223.xyz/https://raw.githubusercontent.com/develop202/migu_video/refs/heads/main/interface.txt",
|
||||
"playerType": 2,
|
||||
"epg": "http://diyp5.112114.xyz/?ch={name}&date={date}",
|
||||
"logo": "https://epg.112114.xyz/logo/{name}.png"
|
||||
},
|
||||
{
|
||||
"name": "范明明(需开启V6网络)",
|
||||
"type": 0,
|
||||
"url": "https://nos.netease.com/ysf/3d75a78a0fc7ede372c03598d6d10367.m3u",
|
||||
"playerType": 2
|
||||
},
|
||||
{
|
||||
"name": "虎牙一起看",
|
||||
"type": 0,
|
||||
"url": "https://sub.ottiptv.cc/huyayqk.m3u",
|
||||
"playerType": 2,
|
||||
"timeout": 10,
|
||||
"ua": "okHttp/Mod-1.5.0.0"
|
||||
},
|
||||
{
|
||||
"name": "斗鱼一起看",
|
||||
"type": 0,
|
||||
"url": "https://sub.ottiptv.cc/douyuyqk.m3u",
|
||||
"playerType": 2,
|
||||
"timeout": 10,
|
||||
"ua": "okHttp/Mod-1.5.0.0"
|
||||
},
|
||||
{
|
||||
"name": "B站直播",
|
||||
"type": 0,
|
||||
"url": "https://sub.ottiptv.cc/bililive.m3u",
|
||||
"playerType": 2,
|
||||
"timeout": 10,
|
||||
"ua": "okHttp/Mod-1.5.0.0"
|
||||
},
|
||||
{
|
||||
"name": "YY轮播",
|
||||
"type": 0,
|
||||
"url": "https://sub.ottiptv.cc/yylunbo.m3u",
|
||||
"playerType": 2,
|
||||
"timeout": 10,
|
||||
"ua": "okHttp/Mod-1.5.0.0"
|
||||
}
|
||||
],
|
||||
"ijk": [{"group":"软解码","options":[{"category":4,"name":"opensles","value":"0"},{"category":4,"name":"overlay-format","value":"842225234"},{"category":4,"name":"framedrop","value":"1"},{"category":4,"name":"soundtouch","value":"1"},{"category":4,"name":"start-on-prepared","value":"1"},{"category":1,"name":"http-detect-range-support","value":"0"},{"category":1,"name":"fflags","value":"fastseek"},{"category":2,"name":"skip_loop_filter","value":"48"},{"category":4,"name":"reconnect","value":"1"},{"category":4,"name":"enable-accurate-seek","value":"0"},{"category":4,"name":"mediacodec","value":"0"},{"category":4,"name":"mediacodec-auto-rotate","value":"0"},{"category":4,"name":"mediacodec-handle-resolution-change","value":"0"},{"category":4,"name":"mediacodec-hevc","value":"0"},{"category":1,"name":"dns_cache_timeout","value":"600000000"}]},{"group":"硬解码","options":[{"category":4,"name":"opensles","value":"0"},{"category":4,"name":"overlay-format","value":"842225234"},{"category":4,"name":"framedrop","value":"1"},{"category":4,"name":"soundtouch","value":"1"},{"category":4,"name":"start-on-prepared","value":"1"},{"category":1,"name":"http-detect-range-support","value":"0"},{"category":1,"name":"fflags","value":"fastseek"},{"category":2,"name":"skip_loop_filter","value":"48"},{"category":4,"name":"reconnect","value":"1"},{"category":4,"name":"enable-accurate-seek","value":"0"},{"category":4,"name":"mediacodec","value":"1"},{"category":4,"name":"mediacodec-auto-rotate","value":"1"},{"category":4,"name":"mediacodec-handle-resolution-change","value":"1"},{"category":4,"name":"mediacodec-hevc","value":"1"},{"category":1,"name":"dns_cache_timeout","value":"600000000"}]}]
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import re,sys,json,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host,parse = {
|
||||
'User-Agent': "Dart/3.9 (dart:io)",
|
||||
'Accept': "application/json",
|
||||
'Accept-Encoding': "gzip",
|
||||
'x-api-key': "ETMy41jT5qtCFc61Le8rUpTk2MzWGyIP",
|
||||
'x-requested-with': "XMLHttpRequest",
|
||||
'content-type': "application/json"
|
||||
},'',{}
|
||||
|
||||
def init(self, extend=''):
|
||||
ext = json.loads(extend)
|
||||
self.host = ext.get('host','https://app.247kan.com')
|
||||
x_api_key = ext.get('xapikey')
|
||||
if x_api_key: self.headers['x_api_key'] = x_api_key
|
||||
self.parse = ext.get('parse',{})
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
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': '纪录片'}]}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/api/home?featured_category_ids=14', headers=self.headers, verify=False).json()
|
||||
videos = []
|
||||
for i in response['data'].values():
|
||||
videos.extend(i)
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/api/categories/{tid}/videos?page={pg}&limit=21&sort=year&area=&language=&year=&actor=&director=&letter=&tag=&genre=&trending=false&top250=false&highscore=false', headers=self.headers, verify=False).json()
|
||||
return {'list': response['data']['videos'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/api/videos/search?q={key}&page={pg}&limit=20', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
return {'list': data['videos'], 'pagecount': data['total_pages'], 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/api/videos/{ids[0]}', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
video = {
|
||||
'vod_id': data['vod_id'],
|
||||
'vod_name': data['vod_name'],
|
||||
'vod_pic': data['vod_pic'],
|
||||
'vod_remarks': data['vod_remarks'],
|
||||
'vod_year': data['vod_year'],
|
||||
'vod_area': data['vod_area'],
|
||||
'vod_actor': ','.join(data['vod_actor']),
|
||||
'vod_director': ','.join(data['vod_director']),
|
||||
'vod_content': data['vod_content'],
|
||||
'vod_play_from': data['vod_play_from'],
|
||||
'vod_play_url': data['vod_play_url'],
|
||||
'type_name': ','.join(data['vod_class'])
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
jx, url = 0, ''
|
||||
parse = self.parse
|
||||
if parse:
|
||||
for i, j in parse.items():
|
||||
if flag in i:
|
||||
for k in j:
|
||||
try:
|
||||
response = self.fetch(f'{k}{id}', headers=self.headers, verify=False).json()
|
||||
play_url = response['url']
|
||||
if play_url.startswith('http'):
|
||||
url, jx = play_url, 0
|
||||
break
|
||||
except (KeyError, ValueError, Exception):
|
||||
continue
|
||||
if url.startswith('http'):
|
||||
break
|
||||
if not url:
|
||||
if re.match(r'https?:\/\/.*\.(m3u8|mp4|flv)', id):
|
||||
jx, url = 0, id
|
||||
else:
|
||||
jx, url = 1, id
|
||||
if url.startswith('NBY'):
|
||||
jx, url = 0, ''
|
||||
return {'jx': jx, 'parse': '0', 'url': url, 'header': {'User-Agent':'Mozilla/5.0 (Linux; Android 10; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Mobile Safari/537.36','Connection':'Keep-Alive','Accept-Encoding':'gzip'}}
|
||||
|
||||
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,149 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
import re,sys,time,random,secrets,hashlib,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host,device_id = 'https://qqqys.com',''
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/api.php/app/index/home', headers=self.headers(), verify=False).json()
|
||||
categories = response['data']['categories']
|
||||
videos,classes = [],[]
|
||||
for i in categories:
|
||||
classes.append({'type_id': i['type_name'],'type_name': i['type_name']})
|
||||
videos.extend(self.arr2vods(i['videos']))
|
||||
return {'class': classes, 'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(f'{self.host}/api.php/app/filter/vod?type_name={tid}&page={pg}&sort=hits', headers=self.headers(), verify=False).json()
|
||||
return {'list': self.arr2vods(response['data']), 'pagecount':response['pageCount'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
response = self.fetch(f'{self.host}/api.php/app/search/index?wd={key}&page={pg}&limit=15', headers=self.headers(), verify=False).json()
|
||||
videos = self.arr2vods(response['data'])
|
||||
return {'list': videos, 'pagecount':response['pageCount'], 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f'{self.host}/api.php/app/vod/get_detail?vod_id={ids[0]}', headers=self.headers(), verify=False).json()
|
||||
data = response['data'][0]
|
||||
shows,play_urls = [],[]
|
||||
raw_shows = data['vod_play_from'].split('$$$')
|
||||
raw_urls_list = data['vod_play_url'].split('$$$')
|
||||
for show_code, urls_str in zip(raw_shows, raw_urls_list):
|
||||
need_parse,is_show,name,urls = 0,0,show_code,[]
|
||||
for i in response['vodplayer']:
|
||||
if i['from'] == show_code:
|
||||
is_show = 1
|
||||
need_parse = i['decode_status']
|
||||
if show_code.casefold() != i['show'].casefold():
|
||||
name = f"{i['show']}\u2005({show_code})"
|
||||
break
|
||||
if is_show == 1:
|
||||
for url_item in urls_str.split('#'):
|
||||
if '$' in url_item:
|
||||
episode, url = url_item.split('$',1)
|
||||
urls.append(f"{episode}${show_code}@{int(need_parse)}@{url}")
|
||||
if urls:
|
||||
play_urls.append('#'.join(urls))
|
||||
shows.append(name)
|
||||
video = {
|
||||
'vod_id': data['vod_id'],
|
||||
'vod_name': data['vod_name'],
|
||||
'vod_pic': data['vod_pic'],
|
||||
'vod_remarks': data['vod_remarks'],
|
||||
'vod_year': data['vod_year'],
|
||||
'vod_area': data['vod_area'],
|
||||
'vod_actor': data['vod_actor'],
|
||||
'vod_director': data['vod_director'],
|
||||
'vod_content': data['vod_content'],
|
||||
'vod_play_from': '$$$'.join(shows),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': data['vod_class']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
play_from, need_parse, raw_url = vid.split('@', 2)
|
||||
jx,url = 0,''
|
||||
if need_parse == '1':
|
||||
try:
|
||||
response = self.fetch(f'{self.host}/api.php/app/decode/url/?url={raw_url}&vodFrom={play_from}',headers=self.headers(),timeout=30,verify=False).json()
|
||||
play_url = response['data']
|
||||
if play_url.startswith('http'): url = play_url
|
||||
except Exception:
|
||||
pass
|
||||
if not url:
|
||||
url = raw_url
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', raw_url):
|
||||
jx = 1
|
||||
return { 'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent': 'com.sunshine.tv/1.2.0 (Linux;Android 15) AndroidXMedia3/1.4.1'}}
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
for i in arr:
|
||||
type_name = i.get('type_name')
|
||||
if i.get('vod_class'):
|
||||
type_name = f"{type_name},{i.get('vod_class','')}"
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': i['vod_remarks'],
|
||||
'type_name': type_name,
|
||||
'vod_year': i.get('vod_year')
|
||||
})
|
||||
return videos
|
||||
|
||||
def headers(self):
|
||||
timestamp = str(int(time.time()))
|
||||
nonce = ''.join(random.choice('0123456789') for _ in range(3))
|
||||
ver,pkg = '3','com.sunshine.tv'
|
||||
sign_str = f"finger=SF-C3B2B41F6EFFFF9869176CF68F6790E8F07506FC88632C94B4F5F0430D5498CA&id={pkg}&nonce={nonce}&sk=SK-thanks&time={timestamp}&v={ver}"
|
||||
sign = hashlib.sha256(sign_str.encode('utf-8')).hexdigest().upper()
|
||||
if not self.device_id:
|
||||
device_id_cache_key = 'com.sunshine.tv_3qys_B7k7Dt56Rn'
|
||||
self.device_id = self.getCache(device_id_cache_key)
|
||||
if not(isinstance(self.device_id,str) and len(self.device_id) == 16):
|
||||
self.device_id = ''.join(secrets.choice('0123456789abcdef') for _ in range(16))
|
||||
self.setCache(device_id_cache_key, self.device_id)
|
||||
return {
|
||||
'User-Agent': 'okhttp/4.12.0',
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'x-aid': pkg,
|
||||
'x-ave': ver,
|
||||
'x-time': timestamp,
|
||||
'x-nonc': nonce,
|
||||
'x-sign': sign,
|
||||
'x-device-id': self.device_id,
|
||||
'x-device-brand': 'vivo',
|
||||
'x-device-model': 'V2309A',
|
||||
'x-update-id': '0245861b-2ebf-5524-389d-f983830651ec'
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
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,154 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import re,sys,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36',
|
||||
'accept-language': 'zh-CN,zh;q=0.9',
|
||||
'cache-control': 'no-cache',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
|
||||
'sec-ch-ua-mobile': "?0",
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': "empty",
|
||||
'sec-fetch-mode': "cors",
|
||||
'sec-fetch-site': "same-origin"
|
||||
},'https://qqqys.com'
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/api.php/index/home', headers=self.headers, verify=False).json()
|
||||
categories = response['data']['categories']
|
||||
videos,classes = [],[]
|
||||
for i in categories:
|
||||
classes.append({'type_id': i['type_name'],'type_name': i['type_name']})
|
||||
videos.extend(self.json2vods(i['videos']))
|
||||
return {'class': classes, 'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(f'{self.host}/api.php/filter/vod?type_name={tid}&page={pg}&sort=hits', headers=self.headers, verify=False).json()
|
||||
return {'list': self.json2vods(response['data']), 'pagecount':response['pageCount'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
response = self.fetch(f'{self.host}/api.php/search/index?wd={key}&page={pg}&limit=15', headers=self.headers, verify=False).json()
|
||||
videos = self.json2vods(response['data'])
|
||||
return {'list': videos, 'pagecount':response['pageCount'], 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f'{self.host}/api.php/vod/get_detail?vod_id={ids[0]}', headers=self.headers, verify=False).json()
|
||||
data = response['data'][0]
|
||||
shows,play_urls = [],[]
|
||||
raw_shows = data['vod_play_from'].split('$$$')
|
||||
raw_urls_list = data['vod_play_url'].split('$$$')
|
||||
for show_code, urls_str in zip(raw_shows, raw_urls_list):
|
||||
need_parse, is_show, name, urls = 0, 0, show_code, []
|
||||
for i in response['vodplayer']:
|
||||
if i['from'] == show_code:
|
||||
is_show = 1
|
||||
need_parse = i['decode_status']
|
||||
if show_code.casefold() != i['show'].casefold():
|
||||
name = f"{i['show']}\u2005({show_code})"
|
||||
break
|
||||
if is_show == 1:
|
||||
for url_item in urls_str.split('#'):
|
||||
if '$' in url_item:
|
||||
episode, url = url_item.split('$', 1)
|
||||
urls.append(f"{episode}${show_code}@{int(need_parse)}@{url}")
|
||||
if urls:
|
||||
play_urls.append('#'.join(urls))
|
||||
shows.append(name)
|
||||
video = {
|
||||
'vod_id': data['vod_id'],
|
||||
'vod_name': data['vod_name'],
|
||||
'vod_pic': data['vod_pic'],
|
||||
'vod_remarks': data['vod_remarks'],
|
||||
'vod_year': data['vod_year'],
|
||||
'vod_area': data['vod_area'],
|
||||
'vod_actor': data['vod_actor'],
|
||||
'vod_director': data['vod_director'],
|
||||
'vod_content': data['vod_content'],
|
||||
'vod_play_from': '$$$'.join(shows),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': data['vod_class']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
play_from, need_parse, raw_url = vid.split('@', 2)
|
||||
jx,url = 0,''
|
||||
if need_parse == '1':
|
||||
auth_token = ''
|
||||
for i in range(2):
|
||||
try:
|
||||
response = self.fetch(f'{self.host}/api.php/decode/url/?url={raw_url}&vodFrom={play_from}{auth_token}',headers=self.headers,timeout=30,verify=False).json()
|
||||
if response['code'] == 2 and 'challenge' in response:
|
||||
token = self.run_js(response['challenge'])
|
||||
auth_token = f'&token={token}'
|
||||
play_url = response['data']
|
||||
if play_url.startswith('http'):
|
||||
url = play_url
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if not url:
|
||||
url = raw_url
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', raw_url):
|
||||
jx = 1
|
||||
return { 'jx': jx, 'parse': 0, 'url': url, 'header': self.headers['User-Agent']}
|
||||
|
||||
def json2vods(self,arr):
|
||||
videos = []
|
||||
for i in arr:
|
||||
type_name = i.get('type_name')
|
||||
if i.get('vod_class'):
|
||||
type_name = f"{type_name},{i.get('vod_class','')}"
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': i['vod_remarks'],
|
||||
'type_name': type_name,
|
||||
'vod_year': i.get('vod_year')
|
||||
})
|
||||
return videos
|
||||
|
||||
def run_js(self,js_code):
|
||||
from com.whl.quickjs.wrapper import QuickJSContext, QuickJSException
|
||||
context = None
|
||||
try:
|
||||
context = QuickJSContext.create()
|
||||
result = context.evaluate(js_code)
|
||||
if hasattr(result, "getPointer"): result = context.stringify(result)
|
||||
return result
|
||||
except QuickJSException:
|
||||
return ''
|
||||
finally:
|
||||
if context: context.close()
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
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,196 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from urllib.parse import quote_plus, unquote_plus
|
||||
import os,sys,json,random,base64,urllib3,hashlib,binascii
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host,cid,device_id,appid,channel,targ,proxy_url = {
|
||||
'User-Agent': "okhttp/3.12.13",
|
||||
'Accept-Encoding': "gzip",
|
||||
'accept-language': "zh-CN,zh;q=0.8"
|
||||
}, 'https://api.dyapis.cn',{'6'},'','__UNI__687ABC2','guanfang',{b'\xc2\xd7\xc0\xed'.decode('gbk'),b'\xb1\xa1\xa6\xe2'.decode('big5'),b'\xba\xd6\xa7Q'.decode('big5')},''
|
||||
|
||||
def init(self, extend=''):
|
||||
if not self.host: return
|
||||
try:
|
||||
self.proxy_url = f'{self.getProxyUrl(True)}&type=47dyttpic'
|
||||
except Exception:
|
||||
self.proxy_url = 'http://127.0.0.1:9978/proxy?do=py&type=47dyttpic'
|
||||
device_id_cache_key = 'cc.vod.dytt_device_id_9e93d6a8c03487d2'
|
||||
self.device_id = self.getCache(device_id_cache_key)
|
||||
if not(self.device_id and isinstance(self.device_id,str) and len(self.device_id) == 32):
|
||||
self.device_id = self.random_32md5()
|
||||
self.setCache(device_id_cache_key, self.device_id)
|
||||
payload = {'data': self.encrypt(f'{{"user_uuid":"{self.device_id}","appid":"{self.appid}","channel":"{self.channel}","user_token":"","invite":"{self.channel}","uuid":"{self.device_id}","version":"2.0","platform":"1101","versionCode":"105","token":"{self.device_id}"}}')}
|
||||
response = self.post(f"{self.host}/api/app/home", data=payload, headers=self.headers, verify=False).json()
|
||||
if 'data' not in response: return
|
||||
data = json.loads(self.decrypt(response['data']))
|
||||
for i in data['type']:
|
||||
if any(j in i['name'] for j in self.targ):
|
||||
self.cid.add(str(i['id']))
|
||||
|
||||
def homeContent(self, filter):
|
||||
payload = {'data': self.encrypt(f'{{"user_uuid":"{self.device_id}","appid":"{self.appid}","channel":"{self.channel}","user_token":"","invite":"{self.channel}","uuid":"{self.device_id}","version":"2.0","platform":"1101","versionCode":"105","token":"{self.device_id}"}}')}
|
||||
response = self.post(f"{self.host}/api/app/vodClass", data=payload, headers=self.headers, verify=False).json()
|
||||
if 'data' not in response: return
|
||||
data = json.loads(self.decrypt(response['data']))
|
||||
classes = []
|
||||
for i in data['type']:
|
||||
if any(j in i['name'] for j in self.targ):
|
||||
self.cid.add(str(i['id']))
|
||||
else:
|
||||
if isinstance(i, dict) and i['name'] not in {'推荐','全部', b'\xba\xd6\xa7Q'.decode('big5')}:
|
||||
classes.append({'type_id': i['id'], 'type_name': i['name']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
payload = {'data': self.encrypt(f'{{"user_uuid":"{self.device_id}","appid":"{self.appid}","channel":"{self.channel}","user_token":"","invite":"{self.channel}","uuid":"{self.device_id}","version":"1.0.5","platform":"1101","versionCode":"105","token":"{self.device_id}"}}')}
|
||||
response = self.post(f"{self.host}/api/app/topicIndexList", data=payload, headers=self.headers, verify=False).json()
|
||||
if 'data' not in response: return response
|
||||
data = json.loads(self.decrypt(response['data']))
|
||||
videos = []
|
||||
for i in data['list']:
|
||||
videos.extend(self.arr2vods(i['vod_list']))
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
payload = {'data':self.encrypt(f'{{"area":"","year":"","channel":"{self.channel}","sort":"","type":"","uuid":"{self.device_id}","version":"1.0.5","platform":"1101","versionCode":"105","token":"{self.device_id}","user_uuid":"{self.device_id}","type_pid":"{tid}","appid":"{self.appid}","user_token":"","invite":"{self.channel}","page":"{pg}","lang":"","class":""}}')}
|
||||
response = self.post(f"{self.host}/api/app/vodList", data=payload, headers=self.headers, verify=False).json()
|
||||
if 'data' not in response: return response
|
||||
data = json.loads(self.decrypt(response['data']))
|
||||
return {'list': self.arr2vods(data['list']),'pagecount': data['page_total'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
payload = {'data': self.encrypt(f'{{"user_uuid":"{self.device_id}","appid":"{self.appid}","channel":"{self.channel}","user_token":"","invite":"{self.channel}","page":"{pg}","uuid":"{self.device_id}","version":"1.0.5","wd":"{key}","platform":"1101","versionCode":"105","token":"{self.device_id}"}}')}
|
||||
response = self.post(f"{self.host}/api/app/searchList", data=payload, headers=self.headers, verify=False).json()
|
||||
if 'data' not in response: return response
|
||||
data = json.loads(self.decrypt(response['data']))
|
||||
return {'list': self.arr2vods(data['list']),'pagecount': data['page_total'], 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
payload = {'data': self.encrypt(f'{{"user_uuid":"{self.device_id}","appid":"{self.appid}","channel":"{self.channel}","user_token":"","invite":"{self.channel}","id":"{ids[0]}","uuid":"{self.device_id}","version":"1.0.5","platform":"1101","versionCode":"105","token":"{self.device_id}"}}')}
|
||||
response = self.post(f"{self.host}/api/app/vodDetails", data=payload, headers=self.headers, verify=False).json()
|
||||
if 'data' not in response: return response
|
||||
data = json.loads(self.decrypt(response['data']))
|
||||
if str(data['type_id']) in self.cid: return None
|
||||
show, play_urls = [], []
|
||||
for i in data['vod_play_url']:
|
||||
urls = []
|
||||
for j in i['list']:
|
||||
urls.append(f"{j['name']}${j['url']}")
|
||||
play_urls.append('#'.join(urls))
|
||||
if i['key'] == i['name']:
|
||||
name = i['key']
|
||||
else:
|
||||
name = f"{i['name']}\u2005({i['key']})"
|
||||
show.append(name)
|
||||
video = {
|
||||
'vod_id': data['vod_id'],
|
||||
'vod_name': data['vod_name'],
|
||||
'vod_pic': data['vod_pic'],
|
||||
'vod_remarks': data['vod_remarks'],
|
||||
'vod_year': data['vod_year'],
|
||||
'vod_area': data['vod_area'],
|
||||
'vod_actor': ','.join(data['vod_actor']),
|
||||
'vod_director': ','.join(data['vod_director']),
|
||||
'vod_content': data['vod_content'],
|
||||
'vod_play_from': '$$$'.join(show),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': ','.join(data['vod_class'])
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, url, vip_flags):
|
||||
return { 'jx': 0, 'parse': 0, 'url': url, 'header': {'User-Agent':'okhttp/3.12.13','Accept-Encoding':'gzip'}}
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == '47dyttpic':
|
||||
return self.dytt47pic(params['url'])
|
||||
return None
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
try:
|
||||
len_cipher = len(ciphertext)
|
||||
if len_cipher < 48:
|
||||
iv_hex = ciphertext[len_cipher - 32:]
|
||||
encrypted_data_hex = ciphertext[:len_cipher - 32]
|
||||
else:
|
||||
iv_hex = ciphertext[16:48]
|
||||
encrypted_data_hex = ciphertext[:16] + ciphertext[48:]
|
||||
iv_bytes = binascii.unhexlify(iv_hex)
|
||||
encrypted_data_bytes = binascii.unhexlify(encrypted_data_hex.upper())
|
||||
cipher = AES.new("UeFk58Si151OmxqH".encode('utf-8'), AES.MODE_CFB, iv=iv_bytes, segment_size=128)
|
||||
decrypted_bytes = cipher.decrypt(encrypted_data_bytes)
|
||||
return decrypted_bytes.decode('utf-8')
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def encrypt(self,plaintext):
|
||||
try:
|
||||
iv = os.urandom(16)
|
||||
cipher = AES.new("UeFk58Si151OmxqH".encode('utf-8'), AES.MODE_CFB, iv=iv, segment_size=128)
|
||||
encrypted_bytes = cipher.encrypt(plaintext.encode('utf-8'))
|
||||
encrypted_hex = binascii.hexlify(encrypted_bytes).decode('utf-8')
|
||||
iv_hex = binascii.hexlify(iv).decode('utf-8')
|
||||
return encrypted_hex[:16] + iv_hex + encrypted_hex[16:]
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def dytt47pic(self,url):
|
||||
url = unquote_plus(url)
|
||||
headers = self.headers
|
||||
try:
|
||||
prefix = 'https://img.duanjuzy.com/12345.php?img='
|
||||
if url.startswith(prefix):
|
||||
raw_url = url[len(prefix):]
|
||||
if raw_url.startswith('aHR0c'):
|
||||
raw_url2 = base64.b64decode(raw_url.encode('utf-8'),validate=True).decode('utf8')
|
||||
if raw_url2.startswith(('http://','https://')):
|
||||
url = raw_url2
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if not 'dyapis.cn' in url: headers['User-Agent'] = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/537.36'
|
||||
response = self.fetch(url, headers=headers, verify=False).content
|
||||
try:
|
||||
data = base64.b64decode(response,validate=True)
|
||||
except Exception:
|
||||
data = response
|
||||
except Exception:
|
||||
data = ''
|
||||
return [400, 'application/octet-stream', data]
|
||||
return [200, 'application/octet-stream', data]
|
||||
|
||||
def arr2vods(self,arr):
|
||||
videos = []
|
||||
for i in arr:
|
||||
if not str(i['type_id']) in self.cid:
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': f"{self.proxy_url}&url={quote_plus(i['vod_pic'])}",
|
||||
'vod_remarks': i['vod_remarks']
|
||||
})
|
||||
return videos
|
||||
|
||||
def random_32md5(self):
|
||||
random_string = ''.join(random.choices('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=16))
|
||||
return hashlib.md5(random_string.encode('utf-8')).hexdigest()
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,136 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import re,sys,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host,dkey = {
|
||||
'User-Agent': 'Dart/3.8 (dart:io)',
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'content-type': 'application/json'
|
||||
}, '',{ds.decode('gbk') for ds in [b'\xc2\xd7\xc0\xed',b'\xb8\xa3\xc0\xfb',b'\xcd\xac\xd0\xd4',b'\xc7\xe9\xc9\xab']}
|
||||
|
||||
def init(self, extend=""):
|
||||
def_host = 'http://110.42.110.226:9393'
|
||||
try:
|
||||
response = self.fetch('http://8.155.164.214:9393/domain/current', headers=self.headers, verify=False).json()
|
||||
domain = response['data']['domain']
|
||||
if domain:
|
||||
self.host = f'http://{domain}'
|
||||
else:
|
||||
self.host = def_host
|
||||
except Exception:
|
||||
self.host = def_host
|
||||
|
||||
def homeContent(self, filter):
|
||||
response = self.fetch(f'{self.host}/api/homeConfig/list', headers=self.headers, verify=False).json()
|
||||
classes = []
|
||||
for i in response['data']:
|
||||
if isinstance(i,dict):
|
||||
classes.append({'type_id': i['typeId'], 'type_name': i['typeName']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f'{self.host}/api/recommend/list', headers=self.headers, verify=False).json()
|
||||
videos = []
|
||||
for i in response['data']:
|
||||
response2 = self.fetch(f"{self.host}/api/recommend/contentList?recommendId={i['recommendId']}", headers=self.headers, verify=False).json()
|
||||
videos.extend(self.arr2vods(response2['data']))
|
||||
break
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(f'{self.host}/api/video/list?typeId={tid}&page={pg}&pageSize=20&sort=latest', headers=self.headers, verify=False).json()
|
||||
videos = self.arr2vods(response['data'])
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
response = self.fetch(f'{self.host}/api/video/search?keyword={key}&page={pg}&pageSize=20', headers=self.headers, verify=False).json()
|
||||
videos = self.arr2vods(response['data'])
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f'{self.host}/api/video/detail/{ids[0]}', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
shows, play_urls = [], []
|
||||
for show, episodes in data['videoListMap'].items():
|
||||
name = show
|
||||
for k,l in data.get('videoNameMap',{}).items():
|
||||
if k == show:
|
||||
if l.lower() != k.lower():
|
||||
name = f"{l}\u2005({k})"
|
||||
else:
|
||||
name = l
|
||||
break
|
||||
urls = []
|
||||
for j in episodes:
|
||||
urls.append(f"{j['episodeName']}${show}@{j['episodeUrl']}")
|
||||
play_urls.append('#'.join(urls))
|
||||
shows.append(name)
|
||||
video = {
|
||||
'vod_id': data['vodId'],
|
||||
'vod_name': data['vodName'],
|
||||
'vod_pic': data['vodPic'],
|
||||
'vod_remarks': data['vodRemarks'],
|
||||
'vod_year': data['vodYear'],
|
||||
'vod_area': data['vodArea'],
|
||||
'vod_actor': data['vodActor'],
|
||||
'vod_director': data['vodDirector'],
|
||||
'vod_content': data['vodContent'],
|
||||
'vod_play_from': '$$$'.join(shows),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': data['vodClass']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
jx,url = 0,''
|
||||
play_from, raw_url = vid.split('@',1)
|
||||
try:
|
||||
response = self.fetch(f'{self.host}/api/resolve/getPlayUrl?url={raw_url}&resolveCode={play_from}', headers=self.headers, verify=False).json()
|
||||
play_url = response['data']['url']
|
||||
if play_url.startswith('http'): url = play_url
|
||||
except Exception:
|
||||
pass
|
||||
if not url:
|
||||
url = raw_url
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', vid):
|
||||
jx = 1
|
||||
return { 'jx': jx, 'parse': '0', 'url': url, 'header': {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'}}
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
if isinstance(arr, list):
|
||||
for i in arr:
|
||||
vod_class = i.get('vodClass')
|
||||
if not(isinstance(vod_class, str) and any(i in vod_class for i in self.dkey)):
|
||||
videos.append({
|
||||
'vod_id': i.get('vodId',i.get('videoId')),
|
||||
'vod_name': i['vodName'],
|
||||
'vod_pic': i.get('vodPic'),
|
||||
'vod_remarks': i.get('vodRemarks'),
|
||||
'vod_year': i.get('vodYear'),
|
||||
'vod_content': i.get('vodBlurb'),
|
||||
'type_name': vod_class
|
||||
})
|
||||
return videos
|
||||
|
||||
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,387 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,time,json,urllib3,base64,requests
|
||||
from urllib.parse import urljoin,quote_plus,unquote_plus
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
def_headers,host,key,iv,categories,dj_id,block_id,bn,dn = {'User-Agent': 'Dart/3.8 (dart:io)', 'Accept-Encoding': 'gzip'},'','','','','',[],b'\xe6\x83\x85\xe8\x89\xb2',''
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
try:
|
||||
if isinstance(extend, str) and extend.startswith('http'):
|
||||
self.host = extend
|
||||
else:
|
||||
config = json.loads(extend)
|
||||
self.host = config.get('host')
|
||||
self.key = config.get('key')
|
||||
self.iv = config.get('iv')
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
self.host = self.host or 'https://1026a.shreade.com:8999'
|
||||
response = self.post(f'{self.host}/addons/appto/app.php/tindex/home_config2', headers=self.headers(), verify=False).json()
|
||||
data = self.decode(response['data'])
|
||||
block_id = []
|
||||
for i in data['viphome']:
|
||||
block_id.append(i['id'])
|
||||
self.block_id = block_id
|
||||
self.dn = {bs.decode('gbk') for bs in {b'\xb8\xa3\xc0\xfb',b'\xc7\xe9\xc9\xab',b'\xc2\xdc\xc0\xf2',b'\xc2\xd7\xc0\xed',b'\xcd\xac\xd0\xd4',b'\xc7\xe9\xc8\xa4'}}
|
||||
for j in data.get('collections',[]):
|
||||
if j.get('title') == '短剧':
|
||||
self.dj_id = str(j.get('id')) or '3'
|
||||
self.categories = {'itemsValue': data.get('itemsValue',[]), 'collections': data.get('collections',[])}
|
||||
except Exception:
|
||||
self.host = ''
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
input_data = self.categories
|
||||
short_drama_map = {}
|
||||
for item in input_data.get('collections', []):
|
||||
if item.get('short') == '1':
|
||||
short_drama_map[item['title']] = f"short@{item['title']}"
|
||||
result = {'class': [], 'filters': {}}
|
||||
items_value_map = {item['title']: item for item in input_data['itemsValue']}
|
||||
for item in input_data['itemsValue']:
|
||||
title = item['title']
|
||||
if title == '全部': continue
|
||||
type_id = title
|
||||
if title in short_drama_map:
|
||||
type_id = short_drama_map[title]
|
||||
result['class'].append({'type_id': type_id, 'type_name': title})
|
||||
for class_item in result['class']:
|
||||
type_id = class_item['type_id']
|
||||
type_name = class_item['type_name']
|
||||
query_title = type_name
|
||||
if '@' in type_id:
|
||||
query_title = type_id.split('@', 1)[1]
|
||||
if query_title in items_value_map:
|
||||
item = items_value_map[query_title]
|
||||
filters = []
|
||||
if 'Classes' in item:
|
||||
classes = [cls.strip() for cls in item['Classes'].split(',') if cls.strip()]
|
||||
unique_classes = []
|
||||
seen_classes = set()
|
||||
for cls in classes:
|
||||
if cls not in seen_classes and not any(rule in cls for rule in self.dn):
|
||||
unique_classes.append(cls)
|
||||
seen_classes.add(cls)
|
||||
if unique_classes:
|
||||
class_values = [{'n': cls, 'v': cls} for cls in unique_classes]
|
||||
filters.append({'key': 'class', 'name': '类型', 'value': class_values})
|
||||
if 'Areas' in item:
|
||||
areas = [area.strip() for area in item['Areas'].split(',') if area.strip()]
|
||||
unique_areas = []
|
||||
seen_areas = set()
|
||||
for area in areas:
|
||||
if area not in seen_areas:
|
||||
unique_areas.append(area)
|
||||
seen_areas.add(area)
|
||||
if unique_areas:
|
||||
area_values = [{'n': area, 'v': area} for area in unique_areas]
|
||||
filters.append({'key': 'area', 'name': '地区', 'value': area_values})
|
||||
if 'Years' in item:
|
||||
years = [year.strip() for year in item['Years'].split(',') if year.strip()]
|
||||
unique_years, seen_years = [], set()
|
||||
for year in years:
|
||||
if year not in seen_years:
|
||||
unique_years.append(year)
|
||||
seen_years.add(year)
|
||||
if unique_years:
|
||||
year_values = [{'n': year, 'v': year} for year in unique_years]
|
||||
filters.append({'key': 'year', 'name': '年份', 'value': year_values})
|
||||
sort_values = [{'n':'最新','v':'Time'},{'n':'评分','v':'Score'},{'n':'人气','v':'Hits'}]
|
||||
filters.append({'key': 'sort', 'name': '排序', 'value': sort_values})
|
||||
result['filters'][type_id] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
payload = {
|
||||
'Id': '0',
|
||||
'Type': '1',
|
||||
'Page': '1',
|
||||
'Limit': '10'
|
||||
}
|
||||
response = self.post(f'{self.host}/addons/appto/app.php/tindex/home_vod_list2', data=payload, headers=self.headers(), verify=False).json()
|
||||
data = self.decode(response['data'])
|
||||
vods = []
|
||||
for i in data['sections']:
|
||||
vods.extend(i['vods'])
|
||||
vods.extend(data['vods'])
|
||||
videos = self.arr2vods(vods)
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, ext):
|
||||
if not self.host: return None
|
||||
tab = '影视'
|
||||
if isinstance(tid,str) and (tid.startswith('short@') or '短剧' in tid):
|
||||
tab, tid = '短剧', tid.lstrip(tid)
|
||||
payload = {
|
||||
'Page': str(pg),
|
||||
'Limit': '44',
|
||||
'Tab': tab,
|
||||
'Type': tid,
|
||||
'Class': ext.get('class', ''),
|
||||
'Year': ext.get('year', ''),
|
||||
'Area': ext.get('area', '中国大陆'),
|
||||
'Sort': ext.get('sort', '')
|
||||
}
|
||||
response = self.post(f'{self.host}/addons/appto/app.php/tindex/page_vod_lists', data=payload, headers=self.headers(), verify=False).json()
|
||||
data = self.decode(response['data'])
|
||||
videos = self.arr2vods(data['list'])
|
||||
return {'list': videos, 'pagecount': self.page_count(data['limit'],data['total']), 'pg': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
url = f'{self.host}/addons/appto/app.php/tindex/search_film'
|
||||
videos = []
|
||||
payload = {
|
||||
'Limit': '10',
|
||||
'Page': pg,
|
||||
'Search': key,
|
||||
'type': None
|
||||
}
|
||||
response = self.post(url, data=payload, headers=self.headers(), verify=False).json()
|
||||
data = self.decode(response['data'])
|
||||
vods = data['vods']
|
||||
videos.extend(self.arr2vods(vods['list']))
|
||||
return {'list': videos, 'pagecount': self.page_count(vods['limit'],vods['total']), 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.host: return None
|
||||
payload = {'id': ids[0]}
|
||||
response = self.post(f'{self.host}/addons/appto/app.php/tindex/page_player', data=payload, headers=self.headers(), verify=False).json()
|
||||
data = self.decode(response['data'])
|
||||
if data['type_id'] in self.block_id:
|
||||
return {'list': []}
|
||||
if not data['group_id'] == 0:
|
||||
return {'list': []}
|
||||
play_from, play_urls = [], []
|
||||
for i in data['vod_play_list']:
|
||||
urls = []
|
||||
for j in i['urls']:
|
||||
if str(data['type_id']) == self.dj_id:
|
||||
urls.append(f"{j['name']}$direct@{j['url']}")
|
||||
else:
|
||||
urls.append(f"{j['name']}${j['url']}")
|
||||
if urls:
|
||||
play_urls.append('#'.join(urls))
|
||||
play_from.append(i['from'])
|
||||
video = {
|
||||
'vod_id': data['vod_id'],
|
||||
'vod_name': data['vod_name'],
|
||||
'vod_pic': (data.get('vod_pic_thumb') or data.get('vod_pic_vertical') or data.get('vod_pic') or '').replace('mac://','https://'),
|
||||
'vod_content': data.get('vod_blurb'),
|
||||
'vod_remarks': data.get('vod_remarks'),
|
||||
'vod_year': data.get('vod_year'),
|
||||
'vod_area': data.get('vod_area'),
|
||||
'vod_actor': data.get('vod_actor'),
|
||||
'vod_director': data.get('vod_director'),
|
||||
'vod_play_from': '$$$'.join(play_from),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': data.get('vod_class')
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_Flags):
|
||||
url = ''
|
||||
if vid.startswith('direct@'):
|
||||
url = vid.lstrip('direct@')
|
||||
elif '.m3u8' in vid:
|
||||
try:
|
||||
proxy = f'{self.getProxyUrl(True)}&type=58ys'
|
||||
except Exception:
|
||||
proxy = 'http://127.0.0.1:9978/proxy?do=py&type=58ys'
|
||||
url = f'{proxy}&url={quote_plus(vid)}'
|
||||
return {'jx': 0, 'playUrl': '', 'parse': 0, 'url': url,'header': self.def_headers}
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == '58ys':
|
||||
url = unquote_plus(params['url'])
|
||||
data = self.m3u8_58ys(url)
|
||||
return [200, "application/vnd.apple.mpegurl", data]
|
||||
return None
|
||||
|
||||
def m3u8_58ys(self, url, retries=3, timeout=10):
|
||||
current_url = url
|
||||
while True:
|
||||
try:
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
response = requests.get(current_url, timeout=timeout, headers=self.def_headers)
|
||||
response.raise_for_status()
|
||||
content = response.text
|
||||
break
|
||||
except (requests.RequestException, ValueError) as e:
|
||||
if attempt == retries - 1:
|
||||
raise Exception()
|
||||
base_url = current_url.rsplit('/', 1)[0] + '/'
|
||||
lines = content.strip().split('\n')
|
||||
is_master_playlist = any(line.startswith('#EXT-X-STREAM-INF:') for line in lines)
|
||||
if is_master_playlist:
|
||||
highest_bandwidth = 0
|
||||
best_playlist_url = None
|
||||
bandwidth_regex = re.compile(r'BANDWIDTH=(\d+)')
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith('#EXT-X-STREAM-INF:'):
|
||||
match = bandwidth_regex.search(line)
|
||||
if match:
|
||||
bandwidth = int(match.group(1))
|
||||
if i + 1 < len(lines) and not lines[i + 1].startswith('#'):
|
||||
playlist_url = lines[i + 1].strip()
|
||||
if not playlist_url.startswith(('http://', 'https://')):
|
||||
playlist_url = urljoin(base_url, playlist_url)
|
||||
if bandwidth > highest_bandwidth:
|
||||
highest_bandwidth = bandwidth
|
||||
best_playlist_url = playlist_url
|
||||
if best_playlist_url:
|
||||
current_url = best_playlist_url
|
||||
continue
|
||||
else:
|
||||
raise Exception()
|
||||
key_regex = re.compile(r'#EXT-X-KEY:(.*)URI="([^"]+)"(.*)')
|
||||
segment_regex = re.compile(r'#EXTINF:([\d.]+),')
|
||||
m3u8_output = []
|
||||
first_segment_index = -1
|
||||
segment_durations = []
|
||||
segment_indices = []
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith('#EXTINF:'):
|
||||
match = segment_regex.search(line)
|
||||
if match:
|
||||
duration = float(match.group(1))
|
||||
segment_durations.append(duration)
|
||||
segment_indices.append(i)
|
||||
modified_remove_start_indices = []
|
||||
if len(segment_durations) >= 2:
|
||||
second_duration_str = "{0:.3f}".format(segment_durations[1])
|
||||
if second_duration_str.endswith('67'):
|
||||
modified_remove_start_indices = segment_indices[:2]
|
||||
elif len(segment_durations) >= 3:
|
||||
third_duration_str = "{0:.3f}".format(segment_durations[2])
|
||||
if third_duration_str.endswith('67'):
|
||||
modified_remove_start_indices = segment_indices[:3]
|
||||
elif len(segment_durations) >= 5:
|
||||
dur3 = round(segment_durations[2], 3)
|
||||
dur4 = round(segment_durations[3], 3)
|
||||
dur5 = round(segment_durations[4], 3)
|
||||
if dur3 == dur4 == dur5:
|
||||
a = dur3
|
||||
dur1 = round(segment_durations[0], 3)
|
||||
dur2 = round(segment_durations[1], 3)
|
||||
if not (dur1 == dur2 == a):
|
||||
sum_dur1_dur2 = round(segment_durations[0] + segment_durations[1], 3)
|
||||
if sum_dur1_dur2 == 4.0:
|
||||
modified_remove_start_indices = segment_indices[:2]
|
||||
lines_to_remove = set()
|
||||
for seg_idx in modified_remove_start_indices:
|
||||
lines_to_remove.add(seg_idx)
|
||||
if seg_idx + 1 < len(lines):
|
||||
lines_to_remove.add(seg_idx + 1)
|
||||
for i, line in enumerate(lines):
|
||||
line = line.strip()
|
||||
if not line: continue
|
||||
if line.startswith('#EXT-X-KEY:'):
|
||||
match = key_regex.search(line)
|
||||
if match:
|
||||
prefix = match.group(1)
|
||||
key_url = match.group(2)
|
||||
suffix = match.group(3)
|
||||
if not key_url.startswith(('http:', 'https:')):
|
||||
key_url = urljoin(base_url, key_url)
|
||||
updated_key_line = f'#EXT-X-KEY:{prefix}URI="{key_url}"{suffix}'
|
||||
m3u8_output.append(updated_key_line)
|
||||
continue
|
||||
if i in lines_to_remove:
|
||||
continue
|
||||
if not line.startswith('#') and i > first_segment_index:
|
||||
segment_url = line
|
||||
if not segment_url.startswith(('http:', 'https:')):
|
||||
segment_url = urljoin(base_url, segment_url)
|
||||
m3u8_output.append(segment_url)
|
||||
else:
|
||||
m3u8_output.append(line)
|
||||
if first_segment_index == -1 and line.startswith('#EXTINF:'):
|
||||
first_segment_index = i
|
||||
if not any(not line.startswith('#') for line in m3u8_output):
|
||||
raise Exception()
|
||||
if not m3u8_output or not m3u8_output[0].startswith('#EXTM3U'):
|
||||
m3u8_output.insert(0, '#EXTM3U')
|
||||
return '\n'.join(m3u8_output)
|
||||
except Exception as e:
|
||||
return f"#EXTM3U\n#EXT-X-ERROR:{str(e)}"
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
if arr and isinstance(arr, list):
|
||||
for i in arr:
|
||||
for k in self.dn:
|
||||
if k in i.get('vod_class'): continue
|
||||
if i['group_id'] != 0: continue
|
||||
if 'payload' in i or 'action' in i or 'banner' in i['vod_class'] or i['type_id'] in self.block_id or self.bn.decode('utf-8') in i['vod_class'] or b'\xe4\xbc\x9a\xe5\x91\x98'.decode('utf-8') in i.get('vod_type_name', ''): continue
|
||||
if i.get('vod_total', 0) > 1:
|
||||
remarks = f"{i['vod_total']}集"
|
||||
else:
|
||||
remarks = f"评分:{i['vod_score']}"
|
||||
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_thumb') or i.get('vod_pic_vertical') or i.get('vod_pic') or '').replace('mac://', 'https://'),
|
||||
'vod_score': i.get('vod_score'),
|
||||
'vod_remarks': remarks or i.get('vod_remarks'),
|
||||
})
|
||||
return videos
|
||||
|
||||
def decode(self, ciphertext):
|
||||
try:
|
||||
if isinstance(ciphertext, dict):
|
||||
return ciphertext
|
||||
elif isinstance(ciphertext, str):
|
||||
key = self.key or '58928cae68092afc'
|
||||
iv = self.iv or 'e9d732a1edcdcc0a'
|
||||
ciphertext = base64.b64decode(ciphertext)
|
||||
key_bytes = key.encode('utf-8')
|
||||
iv_bytes = iv.encode('utf-8')
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size)
|
||||
return json.loads(decrypted_data.decode('utf-8'))
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def page_count(self,limit, total):
|
||||
try:
|
||||
if limit <= 0:
|
||||
raise ValueError()
|
||||
return 1 if total <= 0 else (total + limit - 1) // limit
|
||||
except Exception:
|
||||
return 1
|
||||
|
||||
def headers(self):
|
||||
return {
|
||||
**self.def_headers,
|
||||
'version': '1.0.0',
|
||||
'timestamp': str(int(time.time() * 1000)),
|
||||
'clienttype': 'mobile'
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,294 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import unpad,pad
|
||||
from Crypto.Random import get_random_bytes
|
||||
import re,os,sys,uuid,time,json,zlib,base64,urllib3,hashlib
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
def_headers2,config,host,token,req_uuid = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6299.95 Safari/537.36',
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'Content-Type': 'application/json',
|
||||
'uuid': '',
|
||||
'client_type': 'android',
|
||||
'timestamp': '',
|
||||
'sign': '',
|
||||
'version': '',
|
||||
'appkey': '',
|
||||
'api_version': 'v1'
|
||||
},{},'','',str(uuid.uuid4())
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
ext = json.loads(extend)
|
||||
host = ext['host']
|
||||
app_key = ext['appkey']
|
||||
app_name = ext['name']
|
||||
build_signature = ext['buildSignature']
|
||||
build_number = ext['buildNumber']
|
||||
version_name = ext['versionName']
|
||||
package = ext['package']
|
||||
login_path = ext.get('LoginPath', '/app/userInfo')
|
||||
self.token = ext.get('token')
|
||||
if not (host and app_key and app_key and app_name and build_signature and build_number and version_name and package): return
|
||||
self.host = f"{host}/api"
|
||||
self.def_headers2['appkey'] = app_key
|
||||
self.def_headers2['version'] = ext['version']
|
||||
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
||||
payload = self.encrypt(f'{{"v":"{version_name}","n":"{app_name}","s":"{build_signature}","pl":"1","apiVersion":"v2","token":"","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
||||
response = self.post(f'{self.host}/app/systemInit',data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False, timeout=30).text
|
||||
data = json.loads(self.decrypt(response, self.req_uuid))
|
||||
self.config['player'] = data['player']
|
||||
self.config['parses'] = data['parser_api']
|
||||
self.config['categories'] = data['categorys']['data']
|
||||
if not self.token:
|
||||
device_info_cache_key = f'99app_device_info_{app_key}_mPjOv6fHcE'
|
||||
device_info = self.getCache(device_info_cache_key)
|
||||
if not device_info:
|
||||
device_info = {'did': str(uuid.uuid4()), 'install_time': int(time.time() * 1000)}
|
||||
self.setCache(device_info_cache_key,device_info)
|
||||
install_time,did = device_info['install_time'],device_info['did']
|
||||
update_time = install_time
|
||||
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
||||
self.encrypt(f'{{"os":"android","name":"xiaomi","version":"15","sdkInt":32,"device":"xiaomi","brand":"xiaomi","manufacturer":"xiaomi","product":"b0q","hardware":"xiaomi","isPhysicalDevice":true,"androidId":"V417IR","bootloader":"unknown","display":"V417IR release-keys","host":"a11-gz01-test","tags":"release-keys","type":"user","finger":"xiaomi/b0q/b0q:15/V619IR/613:user/release-keys","app":{{"version":"{version_name}","name":"{app_name}","package":"{package}","buildNumber":"{build_number}","buildSignature":"{build_signature}","install":{install_time},"update":{update_time}}},"did":"{did}","apiVersion":"v2","channel":"","token":"","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
||||
response2 = self.post(f'{self.host}{login_path}', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
|
||||
device_data = json.loads(self.decrypt(response2, self.req_uuid))
|
||||
try:
|
||||
self.token = device_data['userInfo']['user_token']
|
||||
except Exception:
|
||||
self.token = uuid.uuid4().hex
|
||||
except Exception:
|
||||
self.host = ''
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
classes = []
|
||||
for i in self.config['categories']:
|
||||
if isinstance(i,dict):
|
||||
classes.append({'type_id': i['id'], 'type_name': i['name']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
||||
payload = self.encrypt(f'{{"kw":"","page":"1","limit":21,"pid":"1","orderBy":"time","isCategory":1,"token":"","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
||||
response = self.post(f'{self.host}/vod/search', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
|
||||
data = json.loads(self.decrypt(response,self.req_uuid))
|
||||
videos = self.arr2vods(data['data'])
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
||||
payload = self.encrypt(f'{{"kw":"","page":"{pg}","limit":21,"pid":"{tid}","orderBy":"time","isCategory":1,"token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
||||
response = self.post(f'{self.host}/vod/search', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
|
||||
data = json.loads(self.decrypt(response))
|
||||
videos = self.arr2vods(data['data'])
|
||||
return {'list': videos, 'pagecount': data['page_count'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
||||
payload = self.encrypt(f'{{"kw":"{key}","page":{int(pg)},"limit":21,"orderBy":"vod_hits_month","sort":"desc","token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
||||
response = self.post(f'{self.host}/vod/search', data=payload, headers=self.headers2(nonce,timestamp,payload),verify=False).text
|
||||
data = json.loads(self.decrypt(response, self.req_uuid))
|
||||
videos = self.arr2vods(data['data'])
|
||||
return {'list': videos, 'pagecount': data['page_count'], 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.host: return None
|
||||
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
||||
payload = self.encrypt(f'{{"id":"{ids[0]}","eps":"1","v":"2.0.0","pl":1,"token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
||||
response = self.post(f'{self.host}/vod/detail', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
|
||||
data = json.loads(self.decrypt(response))
|
||||
data = data['data']
|
||||
players = self.config['player']
|
||||
resources = dict(zip(data['play_from'].split('$$$'), data['play_url'].split('$$$')))
|
||||
player_sequence = sorted(players.keys(), key=lambda k: players[k]['sort'], reverse=True)
|
||||
show2 = []
|
||||
play_urls2 = []
|
||||
for key in player_sequence:
|
||||
if key in resources:
|
||||
player = players[key]
|
||||
if player['name'] != key:
|
||||
show2.append(f"{player['name']}\u2005({key})")
|
||||
else:
|
||||
show2.append(key)
|
||||
urls = resources[key].split('#')
|
||||
urls2 = [f"{parts[0]}${key}@{parts[1]}" for url_part in urls if '$' in url_part and len(parts := url_part.split('$', 1)) > 1]
|
||||
play_urls2.append('#'.join(urls2))
|
||||
video = {
|
||||
'vod_id': data['id'],
|
||||
'vod_name': data['name'],
|
||||
'vod_pic': data['pic'],
|
||||
'vod_remarks': data['remarks'],
|
||||
'vod_year': data['year'],
|
||||
'vod_area': data['area'],
|
||||
'vod_actor': data['actor'],
|
||||
'vod_director': data['director'],
|
||||
'vod_content': data['content'],
|
||||
'vod_play_from': '$$$'.join(show2),
|
||||
'vod_play_url': '$$$'.join(play_urls2),
|
||||
'type_name': data['class']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
jx,sniff,url,play_headers = 0,0,'',{'User-Agent':'Lavf/58.12.100'}
|
||||
webview_headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'upgrade-insecure-requests': '1',
|
||||
'sec-fetch-site': 'none',
|
||||
'sec-fetch-mode': 'navigate',
|
||||
'sec-fetch-user': '?1',
|
||||
'sec-fetch-dest': 'document',
|
||||
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7'
|
||||
}
|
||||
play_from, raw_url = vid.split('@')
|
||||
try:
|
||||
player = self.config['player'][play_from]
|
||||
try:
|
||||
server_play_headers = json.loads(player['headers'])
|
||||
if isinstance(server_play_headers, dict):
|
||||
play_headers.update(server_play_headers)
|
||||
except Exception:
|
||||
pass
|
||||
if player['type'] == 0:
|
||||
url = raw_url
|
||||
elif player['type'] == 1:
|
||||
if player['isParse'] == 1:
|
||||
try:
|
||||
parse_rule = player['parseUrl'].split(',')
|
||||
except Exception:
|
||||
parse_rule = []
|
||||
webview_parses = []
|
||||
for i in self.config['parses']:
|
||||
try:
|
||||
if parse_rule and not str(i['id']) in parse_rule:
|
||||
continue
|
||||
elif not(i['api_supports_code']) or i['api_supports_code'] and play_from in i['api_supports_code'].split(','):
|
||||
if i['api_type'] == 'webview':
|
||||
if i['api_url']:
|
||||
webview_parses.append(i)
|
||||
continue
|
||||
elif i['api_type'] == 'json' and i['is_server_parser'] == 1:
|
||||
parse_id = i['id']
|
||||
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
|
||||
payload = self.encrypt(f'{{"id":{parse_id},"url":"{raw_url}","token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
|
||||
response = self.post(f'{self.host}/app/vodParser', data=payload, headers=self.headers2(nonce,timestamp,payload),verify=False).text
|
||||
data = json.loads(self.decrypt(response, self.req_uuid))
|
||||
play_url = data['data']
|
||||
else:
|
||||
jx_headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6299.95 Safari/537.36'}
|
||||
response = self.fetch(f"{i['api_url']}{raw_url}", headers=jx_headers, verify=False).json()
|
||||
play_url = response[i['idx_play_url']]
|
||||
if play_url.startswith('http'):
|
||||
url = play_url
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if not url:
|
||||
for k in webview_parses:
|
||||
try:
|
||||
if parse_rule and not str(k['id']) in parse_rule:
|
||||
continue
|
||||
else:
|
||||
if k['api_url'] and k['api_url'].startswith('http'):
|
||||
return { 'jx': 0, 'parse': 1, 'url': f"{k['api_url']}{url}", 'header': play_headers}
|
||||
except Exception:
|
||||
continue
|
||||
elif player['type'] == 2:
|
||||
url, sniff = raw_url, 1
|
||||
play_headers = webview_headers
|
||||
except Exception:
|
||||
pass
|
||||
if not url:
|
||||
url = vid
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', url):
|
||||
jx = 1
|
||||
return { 'jx': jx, 'parse': sniff, 'url': url, 'header': play_headers}
|
||||
|
||||
def decrypt(self, data, key=''):
|
||||
try:
|
||||
if not key: key = self.req_uuid
|
||||
encrypted_bytes = base64.b64decode(data)
|
||||
if len(encrypted_bytes) < 16: raise ValueError
|
||||
cipher = AES.new(key.replace('-', '').encode('utf-8'), AES.MODE_CBC, encrypted_bytes[:16])
|
||||
decrypted_padded = cipher.decrypt(encrypted_bytes[16:])
|
||||
decrypted_raw = unpad(decrypted_padded, AES.block_size)
|
||||
deflated_data = zlib.decompress(decrypted_raw)
|
||||
return deflated_data.decode('utf-8')
|
||||
except zlib.error:
|
||||
raise ValueError
|
||||
except Exception:
|
||||
raise ValueError
|
||||
|
||||
def encrypt(self, data, key=''):
|
||||
try:
|
||||
if not key: key = self.req_uuid
|
||||
data_bytes = data.encode('utf-8')
|
||||
padded_data = pad(data_bytes, AES.block_size)
|
||||
iv = get_random_bytes(AES.block_size)
|
||||
key_bytes = key.replace('-', '').encode('utf-8')
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
|
||||
encrypted_bytes = cipher.encrypt(padded_data)
|
||||
return base64.b64encode(iv + encrypted_bytes).decode('utf-8')
|
||||
except Exception:
|
||||
raise ValueError
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
for i in arr:
|
||||
videos.append({
|
||||
'vod_id': i['id'],
|
||||
'vod_name': i['name'],
|
||||
'vod_pic': i['pic'],
|
||||
'vod_remarks': i['remarks'],
|
||||
'vod_year': i['year'],
|
||||
'vod_content': i.get('blurb'),
|
||||
'type_name': i.get('class'),
|
||||
'vod_area': i.get('area'),
|
||||
'vod_actor': i.get('actor'),
|
||||
'vod_director': i.get('director')
|
||||
})
|
||||
return videos
|
||||
|
||||
def headers2(self,nonce, timestamp, payload=''):
|
||||
return {
|
||||
**self.def_headers2,
|
||||
'uuid': self.req_uuid,
|
||||
'timestamp': timestamp,
|
||||
'sign': self.sign(payload, timestamp, self.req_uuid,nonce, self.def_headers2['appkey']),
|
||||
'nonce': nonce
|
||||
}
|
||||
|
||||
def sign(self, body, timestamp, nonce, req_uuid, app_key):
|
||||
combined = f'{body}:{timestamp}:{nonce}:{req_uuid}:{app_key}'
|
||||
sha256_hash = hashlib.sha256(combined.encode('utf-8'))
|
||||
return sha256_hash.hexdigest()
|
||||
|
||||
def nonce(self):
|
||||
return base64.b64encode(os.urandom(16)).decode('utf-8')
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,158 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
import re,sys,json,base64,urllib3
|
||||
from Crypto.Util.Padding import unpad
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,iv, key,host = {
|
||||
'User-Agent': "okhttp/3.12.0",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip"
|
||||
}, '1234567890123456', '', ''
|
||||
|
||||
def init(self, extend=""):
|
||||
try:
|
||||
host = extend.strip()
|
||||
if host.startswith('http'): self.host = host
|
||||
response = self.fetch(f'{self.host}/public/?service=App.Mov.GetTypeList', headers=self.headers, verify=False).text
|
||||
data = self.decode(response)
|
||||
sign_start = ''
|
||||
for i in data['Data']:
|
||||
if str(i['type_id']) == '1':
|
||||
sign_start = i['type_union']
|
||||
break
|
||||
response2 = self.fetch(f'{self.host}/public/?service=App.Mov.GetAdType', headers=self.headers, verify=False).text
|
||||
data2 = self.decode(response2)
|
||||
sign_end = data2['Data']['tmp']
|
||||
key = f'{sign_start}{sign_end}'[:16]
|
||||
if len(key) == 16: self.key = key
|
||||
except Exception:
|
||||
self.host = ''
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
return {'class': [{'type_id': 1, 'type_name': '电影'}, {'type_id': 2, 'type_name': '连续剧'}, {'type_id': 3, 'type_name': '综艺'}, {'type_id': 4, 'type_name': '动漫'}]}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/public/?service=App.Mov.GetHomeLevel', headers=self.headers, verify=False).text
|
||||
data = self.decode(response)
|
||||
videos = []
|
||||
for i in data.values():
|
||||
if isinstance(i, dict):
|
||||
for j in i.values():
|
||||
for k in j:
|
||||
videos.append({
|
||||
'vod_id': k['vod_id'],
|
||||
'vod_name': k['vod_name'],
|
||||
'vod_pic': k['vod_pic'],
|
||||
'vod_remarks': k['vod_remarks'],
|
||||
'vod_year': k['vod_year'],
|
||||
'vod_content': k['vod_content'],
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/public/?service=App.Mov.GetOnlineList&type_id={tid}&page={pg}&limit=18', headers=self.headers, verify=False).text
|
||||
data = self.decode(response)
|
||||
videos = []
|
||||
for i in data.get('Data',[]):
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': i['vod_remarks'],
|
||||
'vod_year': i['vod_year'],
|
||||
'vod_content': i['vod_content'],
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/public/?service=App.Mov.SearchVod&key={key}', headers=self.headers, verify=False).text
|
||||
data = self.decode(response)
|
||||
videos = []
|
||||
for i in data.get('Data',[]):
|
||||
if isinstance(i,dict):
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': i['vod_remarks'],
|
||||
'vod_year': i['vod_year'],
|
||||
'vod_content': i['vod_content']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f'{self.host}/public/?service=App.Mov.GetOnlineMvById&vodid={ids[0]}', headers=self.headers, verify=False).text
|
||||
data = self.decode(response)
|
||||
for i in data.get('Data', []):
|
||||
if isinstance(i, dict):
|
||||
videos = {
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': i['vod_remarks'],
|
||||
'vod_year': i['vod_year'],
|
||||
'vod_area': i['vod_area'],
|
||||
'vod_actor': i['vod_actor'],
|
||||
'vod_content': i['vod_content'],
|
||||
'vod_play_from': i['vod_play_from'],
|
||||
'vod_play_url': i['vod_play_url'],
|
||||
'type_name': i['vod_class'],
|
||||
}
|
||||
return {'list': [videos]}
|
||||
return None
|
||||
return None
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
jx,parse,url = 0,0,''
|
||||
if re.match(r'https?:\/\/.*\.(m3u8|mp4|flv|mkv)', id):
|
||||
url = id
|
||||
else:
|
||||
try:
|
||||
response = self.fetch(f'{self.host}/public/?service=App.Mov.GetMvJXUrlByUrl&url={id}', headers=self.headers, verify=False).text
|
||||
data = self.decode(response)
|
||||
raw_url = data['Data']['url']
|
||||
paly_url = self.decrypt(raw_url)
|
||||
if paly_url.startswith('http'):
|
||||
url = paly_url
|
||||
except Exception:
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', id): url,jx = id,1
|
||||
return { 'jx': jx, 'parse': parse, 'url': url, 'header': {'User-Agent': 'com.gjkj.zxysdq/1.1.0 (Linux;Android 12) ExoPlayerLib/2.12.3'}}
|
||||
|
||||
def decrypt(self, base64_ciphertext):
|
||||
try:
|
||||
ciphertext = base64.b64decode(base64_ciphertext)
|
||||
cipher = AES.new(self.key.encode('utf-8'), AES.MODE_CBC, self.iv.encode('utf-8'))
|
||||
plaintext_bytes = cipher.decrypt(ciphertext)
|
||||
decrypted = unpad(plaintext_bytes, AES.block_size)
|
||||
return decrypted.decode('utf-8')
|
||||
except Exception:
|
||||
raise Exception
|
||||
|
||||
def decode(self, data):
|
||||
return json.loads(data[1:] if data.startswith('\ufeff') else data)
|
||||
|
||||
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,287 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,time,json,base64,hashlib,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,player,host,name,data_key,data_iv,cms_host,jx_api = {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 MUOUAPP/10.8.4506.400',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'brand-model': 'xiaomi',
|
||||
'app-device': 'nodata',
|
||||
'app-time': '',
|
||||
'sys-version': '12',
|
||||
'device': '811397239bddf2e7',
|
||||
'os': 'Android',
|
||||
'app-version': ''
|
||||
},{},'', '', '', '', '', ''
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
try:
|
||||
config = json.loads(extend)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
config = {}
|
||||
name = config.get('name', 'muou')
|
||||
self.headers['app-version'] = config.get('version', '4.2.0')
|
||||
self.host = config['host']
|
||||
if not re.match(r'^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(\/)?$', self.host):
|
||||
self.host = self.fetch(self.host, headers=self.headers, timeout=10, verify=False).text.rstrip('/')
|
||||
timestamp = int(time.time())
|
||||
self.headers['app-time'] = str(timestamp)
|
||||
api = config.get('api', '/app_info.php')
|
||||
salt = ''
|
||||
if api.endswith('app_info.php'): salt = 'muouapp'
|
||||
inner_sha1 = hashlib.sha1(f"{timestamp}{name}".encode('utf-8')).hexdigest()
|
||||
outer_sha1 = hashlib.sha1(f"{timestamp}{inner_sha1}{salt}".encode('utf-8')).hexdigest()
|
||||
payload = {'t': timestamp, 'n': inner_sha1, 'm': outer_sha1}
|
||||
res = self.post(f'{self.host}{api}', data=payload, headers=self.headers, verify=False).json()
|
||||
data = res['data']
|
||||
a,e,s = res.get('a'),res.get('e'),res.get('s')
|
||||
if a and e and s:
|
||||
data = self.t(data, s, e)
|
||||
else:
|
||||
a = str(res['time'])
|
||||
key = hashlib.md5(a.encode('utf-8')).hexdigest()[:16]
|
||||
iv = hashlib.md5(outer_sha1.encode('utf-8')).hexdigest()[:16]
|
||||
dat3 = json.loads(self.decrypt(data, key, iv))
|
||||
self.data_key = hashlib.md5(dat3['key'].encode('utf-8')).hexdigest()[:16]
|
||||
self.data_iv = hashlib.md5(dat3['iv'].encode('utf-8')).hexdigest()[:16]
|
||||
self.cms_host = dat3['HBqq'].rstrip('/')
|
||||
jx_api = dat3.get('HBrjjg', '')
|
||||
if jx_api.startswith('http'):
|
||||
self.jx_api = jx_api
|
||||
except Exception:
|
||||
self.cms_host = ''
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.cms_host:
|
||||
return {'list': []}
|
||||
self.headers['app-time'] = str(int(time.time()))
|
||||
try:
|
||||
response = self.fetch(f'{self.cms_host}/api.php/v1.vod/types', headers=self.headers, verify=False).text
|
||||
except Exception as e:
|
||||
return {"class": [], "filters": {}}
|
||||
try:
|
||||
data = json.loads(response) or {}
|
||||
except json.JSONDecodeError:
|
||||
try:
|
||||
data = json.loads(self.decrypt(response))
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return {"class": [], "filters": {}}
|
||||
filter_keys = {"class", "area", "lang", "year", "letter", "by", "sort"}
|
||||
filters, classes = {}, []
|
||||
typelist = data.get('data', {}).get('typelist', [])
|
||||
for item in typelist:
|
||||
type_id = str(item["type_id"])
|
||||
classes.append({"type_name": item["type_name"], "type_id": type_id})
|
||||
extend = item.get("type_extend", {})
|
||||
type_filters = []
|
||||
for key, value_str in extend.items():
|
||||
if key not in filter_keys: continue
|
||||
stripped = value_str.strip()
|
||||
if not stripped: continue
|
||||
values = [v.strip() for v in stripped.split(",") if v.strip()]
|
||||
if not values: continue
|
||||
type_filters.append({"key": key, "name": key, "value": [{"n": v, "v": v} for v in values] })
|
||||
if type_filters:
|
||||
filters[type_id] = type_filters
|
||||
return {"class": classes, "filters": filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.cms_host:
|
||||
return {'list': []}
|
||||
self.headers['app-time'] = str(int(time.time()))
|
||||
response = self.fetch(f'{self.cms_host}/api.php/v1.vod/HomeIndex?page=&limit=6', headers=self.headers, verify=False).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
videos = []
|
||||
for i in data['data']:
|
||||
if i.get('vod_list'):
|
||||
vod_list = i['vod_list']
|
||||
for j in vod_list:
|
||||
pic = j.get('vod_pic')
|
||||
if pic:
|
||||
if not pic.startswith('http'):
|
||||
j['vod_pic'] = self.cms_host + pic
|
||||
videos.extend(vod_list)
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.cms_host:
|
||||
return {'list': []}
|
||||
self.headers['app-time'] = str(int(time.time()))
|
||||
response = self.fetch(f"{self.cms_host}/api.php/v1.vod?type={tid}&class={extend.get('class', '')}&area={extend.get('area', '')}&year={extend.get('year', '')}&by=time&page={pg}&limit=18", headers=self.headers, verify=False).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
videos = data['data']['list']
|
||||
for item in data['data']['list']:
|
||||
pic = item.get('vod_pic', '')
|
||||
if pic:
|
||||
if not pic.startswith('http'):
|
||||
item['vod_pic'] = self.cms_host + pic
|
||||
item.pop('type', None)
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
if not self.cms_host:
|
||||
return {'list': []}
|
||||
self.headers['app-time'] = str(int(time.time()))
|
||||
response = self.fetch(f'{self.cms_host}/api.php/v1.vod?wd={key}&limit=18&page={pg}', headers=self.headers, verify=False).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
videos = data['data']['list']
|
||||
for item in data['data']['list']:
|
||||
item.pop('type', None)
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
self.headers['app-time'] = str(int(time.time()))
|
||||
response = self.fetch(f'{self.cms_host}/api.php/v1.vod/detail?vod_id={ids[0]}', headers=self.headers, verify=False).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
data = data['data']
|
||||
if data == '':
|
||||
return {'list': []}
|
||||
show, vod_play_url = [], []
|
||||
for i, j in data['vod_play_list'].items():
|
||||
show.append(j['player_info']['show'])
|
||||
urls = j.get('urls', {})
|
||||
play_url = []
|
||||
if isinstance(urls, dict):
|
||||
for i2, j2 in urls.items():
|
||||
play_url.append(f"{j2['name']}${j2['from']}@{j2['url']}")
|
||||
vod_play_url.append('#'.join(play_url))
|
||||
data['vod_play_from'] = '$$$'.join(show)
|
||||
data['vod_play_url'] = '$$$'.join(vod_play_url)
|
||||
data.pop('vod_play_list')
|
||||
data.pop('type')
|
||||
return {'list': [data]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
play_from, raw_url = vid.split('@')
|
||||
jx,url,playurl,play_headers = 1,raw_url,'',{'User-Agent': self.headers['User-Agent']}
|
||||
try:
|
||||
if not self.player:
|
||||
res = self.fetch(f'{self.host}/api.php?action=playerinfo',headers=self.headers, verify=False).text
|
||||
data = self.decrypt(res)
|
||||
self.player = json.loads(data).get('data',{})
|
||||
playerinfo = self.player.get('playerinfo', [])
|
||||
for i in playerinfo:
|
||||
play_jx = i.get('playerjiekou','')
|
||||
if i.get('playername') == play_from and play_jx.startswith('http'):
|
||||
response = self.fetch(f'{play_jx}{raw_url}&playerkey={play_from}',headers=self.headers,verify=False).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
if str(data.get('code','')) == '403':
|
||||
playurl = ''
|
||||
else:
|
||||
playurl = data['url']
|
||||
jx = 0
|
||||
playeruas = self.player.get('playerua', [])
|
||||
for j in playeruas:
|
||||
try:
|
||||
if j['player'] == play_from and re.search(j['matching'], playurl):
|
||||
play_headers.update(self.playerua_to_dict(j['playerua']))
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
playurl = ''
|
||||
|
||||
if playurl.startswith('http'):
|
||||
url = playurl
|
||||
else:
|
||||
if re.search(r'^https?[^\s]*\.(m3u8|mp4|flv)', raw_url, re.I):
|
||||
url = raw_url
|
||||
jx = 0
|
||||
else:
|
||||
try:
|
||||
response = self.fetch(self.jx_api + raw_url,headers=self.headers,verify=False).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
playurl = data.get('url','')
|
||||
if playurl.startswith('http'):
|
||||
jx,url = 0,playurl
|
||||
else:
|
||||
jx,url = 1,raw_url
|
||||
except Exception as e:
|
||||
jx,url = 1,raw_url
|
||||
if url.startswith('NBY-'):
|
||||
jx.url = 0,''
|
||||
return {'jx': jx,'parse': 0, 'url': url,'header': play_headers}
|
||||
|
||||
def playerua_to_dict(self, playerua_str):
|
||||
headers = {}
|
||||
lines = playerua_str.replace("\r\n", "\n").split("\n")
|
||||
for line in lines:
|
||||
if not line.strip():
|
||||
continue
|
||||
if ':' in line:
|
||||
key, value = line.split(":", 1)
|
||||
key = key.strip().lower()
|
||||
value = value.strip()
|
||||
headers[key] = value
|
||||
return headers
|
||||
|
||||
def decrypt(self,data, key='', iv=''):
|
||||
if not(key or iv):
|
||||
key = self.data_key
|
||||
iv = self.data_iv
|
||||
key_bytes = key.encode('utf-8')
|
||||
iv_bytes = iv.encode('utf-8')
|
||||
encrypted_data = base64.b64decode(data)
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
decrypted_padded = cipher.decrypt(encrypted_data)
|
||||
decrypted = unpad(decrypted_padded, AES.block_size)
|
||||
return decrypted.decode('utf-8')
|
||||
|
||||
def t(self, s, v, v1):
|
||||
if s is not None and s != '':
|
||||
n = len(s)
|
||||
if v < 0 or v1 < 0:
|
||||
raise ValueError("参数不能为负数")
|
||||
if v + v1 <= n:
|
||||
return s[v:n - v1]
|
||||
else:
|
||||
return ''
|
||||
return s
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,330 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 基于原作者 @嗷呜 版本修改
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
from urllib.parse import urlparse, urlencode
|
||||
import re,sys,time,json,urllib3,hashlib,datetime
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
api,host,apisignkey,datasignkey,detail_type,search_data = '','','','','',''
|
||||
headers = {'User-Agent': 'okhttp/4.12.0',}
|
||||
|
||||
def init(self, extend=""):
|
||||
ext = extend.rstrip()
|
||||
if ext.startswith('http'):
|
||||
self.api = ext.rstrip('/')
|
||||
else:
|
||||
arr = json.loads(ext)
|
||||
api = arr['api'].rstrip('/')
|
||||
if not api.startswith('http'): return
|
||||
self.api = api
|
||||
ua = arr.get('ua')
|
||||
if ua: self.headers['User-Agent'] = ua
|
||||
if self.api.endswith(('v1.vod', 'v1.xvod')):
|
||||
self.detail_type = arr.get('detail_type', '')
|
||||
self.apisignkey = arr.get('apisignkey', '')
|
||||
if self.apisignkey:
|
||||
self.datasignkey = arr.get('datasignkey', '6QQNUsP3PkD2ajJCPCY8')
|
||||
if '$' in self.api:
|
||||
host_, path = self.api.split('$', 1)
|
||||
data = self.fetch(host_, headers=self.headers, verify=False).text
|
||||
try:
|
||||
data2 = json.loads(data)
|
||||
if isinstance(data2, list): data2 = data2[0]
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data2 = data
|
||||
if data2 and isinstance(data2, str) and data2.startswith('http'):
|
||||
self.api = data2.rstrip('/') + path
|
||||
self.host = self.domain(self.api)
|
||||
|
||||
def homeContent(self, filter):
|
||||
if self.api.endswith(('v1.vod','v1.xvod')):
|
||||
path = '/types'
|
||||
if self.apisignkey and self.datasignkey:
|
||||
path = self.datasign(path)
|
||||
data = self.fetch(f"{self.api}{path}", headers=self.headers, verify=False).text
|
||||
data = json.loads(data[1:] if data.startswith('\ufeff') else data)
|
||||
data = data['data']
|
||||
else:
|
||||
data = self.fetch(f"{self.api}/nav?token=", headers=self.headers, verify=False).json()
|
||||
keys = ["class", "area", "lang", "year", "letter", "by", "sort"]
|
||||
filters = {}
|
||||
classes = []
|
||||
for item in data.get('list',data.get('typelist',data.get('data',[]))):
|
||||
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})
|
||||
return {"class": classes, "filters": filters}
|
||||
|
||||
def domain(self,url):
|
||||
parsed_url = urlparse(url)
|
||||
protocol = parsed_url.scheme
|
||||
domain = parsed_url.netloc
|
||||
if protocol and domain:
|
||||
return f"{protocol}://{domain}"
|
||||
elif domain:
|
||||
return domain
|
||||
else:
|
||||
return ''
|
||||
|
||||
def homeVideoContent(self):
|
||||
if self.api.endswith(('v1.vod','v1.xvod')):
|
||||
path = '/vodPhbAll'
|
||||
if self.apisignkey and self.datasignkey:
|
||||
keytime = self.keytime()
|
||||
path += self.datasign(f'?apikey={self.apikey()}&keytime={keytime}',keytime)
|
||||
data = self.fetch(f"{self.api}{path}", headers=self.headers, verify=False).text
|
||||
data = json.loads(data[1:] if data.startswith('\ufeff') else data)
|
||||
data = data['data']
|
||||
else:
|
||||
data = self.fetch(f"{self.api}/index_video?token=", headers=self.headers, verify=False).json()
|
||||
videos = []
|
||||
if self.api.endswith(('v1.vod','v1.xvod')):
|
||||
for item in data['list']: videos.extend(item.get('vod_list'))
|
||||
else:
|
||||
for item in data.get('list',data.get('data',[])): videos.extend(item['vlist'])
|
||||
return {'list': self.pic_add_domain(videos)}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if self.api.endswith(('v1.vod','v1.xvod')):
|
||||
path = f"?type={tid}&class={extend.get('class', '')}&lang={extend.get('lang', '')}&area={extend.get('area', '')}&year={extend.get('year', '')}&by=&page={pg}&limit=9"
|
||||
if self.apisignkey and self.datasignkey:
|
||||
keytime = self.keytime()
|
||||
path = self.datasign(f'{path}&apikey={self.apikey()}&keytime={keytime}' ,keytime)
|
||||
data = self.fetch(f"{self.api}{path}", headers=self.headers, verify=False).text
|
||||
data = json.loads(data[1:] if data.startswith('\ufeff') else data)
|
||||
data = data['data']
|
||||
else:
|
||||
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.api}/video", params=params, headers=self.headers, verify=False).json()
|
||||
if 'data' in data:
|
||||
data = {'list':data['data']}
|
||||
return self.pic_add_domain(data)
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
if self.api.endswith(('v1.vod','v1.xvod')):
|
||||
path = f"?page={pg}&limit=10&wd={key}"
|
||||
if self.apisignkey and self.datasignkey:
|
||||
keytime = self.keytime()
|
||||
path = self.datasign(f'{path}&apikey={self.apikey()}&keytime={keytime}',keytime)
|
||||
else:
|
||||
path = f"/search?text={key}&pg={pg}"
|
||||
data = self.fetch(f"{self.api}{path}", headers=self.headers, verify=False).text
|
||||
data = json.loads(data[1:] if data.startswith('\ufeff') else data)
|
||||
if self.api.endswith(('v1.vod','v1.xvod')) and self.detail_type == 'search':
|
||||
self.search_data = data
|
||||
data2 = data.get('list',data.get('data',[]))
|
||||
if 'type' in data2:
|
||||
for item in data2:
|
||||
item.pop('type', None)
|
||||
if not 'list' in data2:
|
||||
data2 = {'list': data2, 'page': pg}
|
||||
return self.pic_add_domain(data2)
|
||||
|
||||
def detailContent(self, ids):
|
||||
if self.detail_type == 'search':
|
||||
for i in self.search_data['data']['list']:
|
||||
if str(i['vod_id']) == str(ids[0]):
|
||||
data = i
|
||||
break
|
||||
else:
|
||||
if self.api.endswith(('v1.vod', 'v1.xvod')):
|
||||
path = f'/detail?vod_id={ids[0]}&rel_limit=10'
|
||||
if self.apisignkey and self.datasignkey:
|
||||
keytime = self.keytime()
|
||||
path = self.datasign(f'{path}&apikey={self.apikey()}&keytime={keytime}', keytime)
|
||||
else:
|
||||
path = f'/video_detail?id={ids[0]}'
|
||||
data = self.fetch(f"{self.api}{path}", headers=self.headers, verify=False).text
|
||||
data = json.loads(data[1:] if data.startswith('\ufeff') else data)
|
||||
data = data['data']
|
||||
if 'vod_info' in data:
|
||||
data = data['vod_info']
|
||||
show, vod_play_url = [], []
|
||||
if 'vod_url_with_player' in data:
|
||||
for i in data['vod_url_with_player']:
|
||||
if i['code'] == i['name']:
|
||||
show.append(i['name'])
|
||||
else:
|
||||
show.append(f"{i['name']}\u2005({i['code']})")
|
||||
parse_api = i.get('parse_api', '')
|
||||
parse_secret = i.get('parse_secret', 0)
|
||||
if parse_api and parse_api.startswith('http') and not parse_secret:
|
||||
url = i.get('url', '')
|
||||
if url: url = '#'.join([i + '@' + parse_api for i in url.split('#')])
|
||||
vod_play_url.append(url)
|
||||
else:
|
||||
vod_play_url.append(i.get('url', ''))
|
||||
data.pop('vod_url_with_player')
|
||||
if 'vod_play_list' in data:
|
||||
vod_play_list = data['vod_play_list']
|
||||
for i in vod_play_list.values() if isinstance(vod_play_list, dict) else vod_play_list:
|
||||
parses = []
|
||||
player_info = i['player_info']
|
||||
if player_info['show'] == i['from']:
|
||||
show.append(player_info['show'])
|
||||
else:
|
||||
show.append(f"{player_info['show']}\u2005({i['from']})")
|
||||
parse = player_info.get('parse')
|
||||
parse2 = player_info.get('parse2')
|
||||
features = player_info.get('features')
|
||||
if parse and isinstance(parse, str) and parse.startswith('http'):
|
||||
parses.append(parse.strip().replace('..', '.'))
|
||||
if parse2 and isinstance(parse2, str) and parse2.startswith('http') and parse2 != parse:
|
||||
parses.append(parse2.strip().replace('..', '.'))
|
||||
parses = ','.join(parses)
|
||||
url = []
|
||||
urls = i['urls']
|
||||
for j in urls.values() if isinstance(urls, dict) else urls:
|
||||
if parses:
|
||||
if features and self.check_rematches(features, j['url']):
|
||||
url.append(f"{j['name']}${j['url']}")
|
||||
else:
|
||||
url.append(f"{j['name']}${j['url']}@{parses}")
|
||||
else:
|
||||
url.append(f"{j['name']}${j['url']}")
|
||||
url = '#'.join(url)
|
||||
vod_play_url.append(url)
|
||||
if 'vod_play_list' in data:
|
||||
data.pop('vod_play_list')
|
||||
if 'rel_vods' in data:
|
||||
data.pop('rel_vods')
|
||||
if 'type' in data:
|
||||
data.pop('type')
|
||||
if show:
|
||||
data['vod_play_from'] = '$$$'.join(show)
|
||||
if vod_play_url:
|
||||
data['vod_play_url'] = '$$$'.join(vod_play_url)
|
||||
return {'list': self.pic_add_domain([data])}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
video_pattern = re.compile(r'https?:\/\/.*\.(?:m3u8|mp4|flv)')
|
||||
jx, url, ua = 0, '', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
|
||||
if '@' in id:
|
||||
rawurl, jxapi = id.split('@', 1)
|
||||
jxapis = jxapi.split(',') if ',' in jxapi else [jxapi]
|
||||
for jxapi_ in jxapis:
|
||||
try:
|
||||
res = self.fetch(f"{jxapi_}{rawurl}", headers=self.headers, timeout=10, verify=False).text
|
||||
res = json.loads(res[1:] if res.startswith('\ufeff') else res)
|
||||
url = res.get('url', '')
|
||||
if url.startswith('http'):
|
||||
jxua = res.get('ua')
|
||||
if jxua:
|
||||
ua = jxua
|
||||
except Exception:
|
||||
url = ''
|
||||
continue
|
||||
if url.startswith('http'):
|
||||
jx = 0
|
||||
else:
|
||||
url = rawurl
|
||||
jx = 0 if video_pattern.match(rawurl) else 1
|
||||
else:
|
||||
url = id
|
||||
jx = 0 if video_pattern.match(id) else 1
|
||||
if url.startswith('NBY'):
|
||||
jx, url = 0, ''
|
||||
return {'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent': ua}}
|
||||
|
||||
def pic_add_domain(self, videos):
|
||||
try:
|
||||
if not videos: return videos
|
||||
items = videos.get('list', videos) if isinstance(videos, dict) else videos
|
||||
for item in items:
|
||||
if not isinstance(item, dict): continue
|
||||
vod_pic = item.get('vod_pic')
|
||||
if not vod_pic or vod_pic.startswith('http'): continue
|
||||
host = self.host + '/' if not vod_pic.startswith('/') else self.host
|
||||
item['vod_pic'] = f"{host}{vod_pic}"
|
||||
return videos
|
||||
except Exception:
|
||||
return videos
|
||||
|
||||
def check_rematches(self, features, target):
|
||||
patterns = [p.strip() for p in features.split(',') if p.strip()]
|
||||
for pattern in patterns:
|
||||
try:
|
||||
if re.match(pattern, target):
|
||||
return True
|
||||
except re.error:
|
||||
pass
|
||||
return False
|
||||
|
||||
def keytime(self):
|
||||
return str(int(datetime.datetime.now().timestamp()))
|
||||
|
||||
def md5(self, str):
|
||||
hash_obj = hashlib.md5()
|
||||
hash_obj.update(str.encode('utf-8'))
|
||||
return hash_obj.hexdigest()
|
||||
|
||||
def apikey(self):
|
||||
date = datetime.datetime.now()
|
||||
year = str(date.year)
|
||||
hour = str(date.hour)
|
||||
minute = str(date.minute)
|
||||
if len(hour) < 2: hour = "0" + hour
|
||||
if len(minute) < 2: minute = "0" + minute
|
||||
str_value = self.apisignkey
|
||||
sign_str = f"{year}:{hour}:{year}:{minute}:{str_value}"
|
||||
return self.md5(sign_str)
|
||||
|
||||
def datasign(self, url='', timestamp=''):
|
||||
parsed_url = urlparse(url)
|
||||
query_params = self._parse_query_params(parsed_url.query)
|
||||
if not timestamp: timestamp = str(time.time())
|
||||
query_params["timestamp"] = timestamp
|
||||
sorted_params = sorted(query_params.items(), key=lambda x: x[0])
|
||||
sign = self._generate_signature(sorted_params)
|
||||
query_params["datasign"] = sign
|
||||
new_query = urlencode(query_params)
|
||||
return parsed_url._replace(query=new_query).geturl()
|
||||
|
||||
def _parse_query_params(self, query_str):
|
||||
params = {}
|
||||
if not query_str:
|
||||
return params
|
||||
for param in query_str.split('&'):
|
||||
if '=' not in param:
|
||||
continue
|
||||
key, value = param.split('=', 1)
|
||||
if value:
|
||||
params[key] = value
|
||||
return params
|
||||
|
||||
def _generate_signature(self, sorted_params):
|
||||
param_str = '&'.join([f"{k}={v}" for k, v in sorted_params])
|
||||
raw_sign_str = f"{param_str}{self.datasignkey}"
|
||||
return hashlib.md5(raw_sign_str.encode('utf-8')).hexdigest()
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,339 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,time,json,base64,secrets,urllib3,hashlib
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,timeout,ver,uas,parses,play_config,custom_parses,host,froms,detail,custom_first,category,cms,app_key,app_sign = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8',
|
||||
'Cache-Control': 'no-cache'
|
||||
},5,2,{},{},{},{},'','','','','','','',''
|
||||
|
||||
def init(self, extend=''):
|
||||
ext = extend.strip()
|
||||
if ext.startswith('http'):
|
||||
host = ext
|
||||
else:
|
||||
arr = json.loads(ext)
|
||||
host = arr['host']
|
||||
self.app_key = arr.get('key')
|
||||
self.app_sign = arr.get('sign')
|
||||
self.ver = arr.get('ver')
|
||||
cms = arr.get('cms', '').rstrip('/')
|
||||
if re.match(r'^https?://.*/vod', cms):
|
||||
if '?' in cms:
|
||||
cms += '&'
|
||||
else:
|
||||
cms += '?'
|
||||
self.cms = cms
|
||||
self.froms = arr.get('from', '')
|
||||
self.custom_parses = arr.get('parse', {})
|
||||
self.custom_first = arr.get('custom_first', 0)
|
||||
self.category = arr.get('category', 1)
|
||||
ua = arr.get('ua')
|
||||
if ua:
|
||||
if isinstance(ua,str):
|
||||
self.headers['User-Agent'] = ua
|
||||
elif isinstance(ua,dict):
|
||||
self.uas = {'host': ua.get('host'),'config':ua.get('config'),'home':ua.get('home'),'category':ua.get('category'),'search':ua.get('search'),'parse':ua.get('parse'),'player':ua.get('player')}
|
||||
self.timeout = arr.get('timeout', 5)
|
||||
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(/)?$', host):
|
||||
headers = self.headers.copy()
|
||||
custom_ua = self.uas.get('host')
|
||||
if custom_ua: headers['User-Agent'] = custom_ua
|
||||
host_ = self.fetch(host, headers=headers, verify=False, timeout=self.timeout).text
|
||||
try:
|
||||
host_ = json.loads(host_)['apiDomain']
|
||||
except json.JSONDecodeError:
|
||||
host_ = host_.strip()
|
||||
if host_.startswith('http'): host = host_
|
||||
self.host = host.rstrip('/')
|
||||
auth_val = bool(self.app_key and self.app_sign)
|
||||
if self.ver == 3 and not auth_val:
|
||||
this_headers2 = self.headers2()
|
||||
custom_ua = self.uas.get('config')
|
||||
if custom_ua: this_headers2['User-Agent'] = custom_ua
|
||||
config_response = self.fetch(f'{self.host}/api.php/Appfox/config', headers=this_headers2, verify=False, timeout=self.timeout).json()
|
||||
config = config_response.get('data',{})
|
||||
app_key, app_sign = config.get('app_key'), config.get('app_sign')
|
||||
if app_key and app_sign:
|
||||
self.app_key, self.app_sign = app_key, app_sign
|
||||
player_list = config_response.get('data', {}).get('playerList', [])
|
||||
jiexi_data_list = config_response.get('data', {}).get('jiexiDataList', [])
|
||||
if player_list:
|
||||
self.play_config = {'playerList': player_list, 'jiexiDataList': jiexi_data_list}
|
||||
elif auth_val:
|
||||
self.ver = 3
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host or self.category == 0: return None
|
||||
headers = self.headers.copy()
|
||||
custom_ua = self.uas.get('home')
|
||||
if custom_ua: headers['User-Agent'] = custom_ua
|
||||
if self.cms and not self.category == 2:
|
||||
class_url = self.cms.rstrip('&')
|
||||
class_url = class_url.replace('&ac=videolist','').replace('ac=videolist&','').replace('ac=videolist','')
|
||||
class_url = class_url.replace('ac=detail', 'ac=list')
|
||||
classes = self.fetch(f"{class_url}", headers=headers, verify=False, timeout=self.timeout).json()['class']
|
||||
data = self.fetch(f"{self.cms.strip('&')}", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
data['class'] = classes
|
||||
if self.category == 2:
|
||||
for i in data.get('list',[]):
|
||||
i['vod_id'] = f"msearch:{i['vod_id']}"
|
||||
else:
|
||||
response = self.fetch(f'{self.host}/api.php/Appfox/init', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
classes = []
|
||||
for i in response['data']['type_list']:
|
||||
classes.append({'type_id': i['type_id'],'type_name': i['type_name']})
|
||||
data = {'class': classes}
|
||||
return data
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host or self.category == 0: return None
|
||||
if self.cms and not self.category == 2: return None
|
||||
headers = self.headers.copy()
|
||||
custom_ua = self.uas.get('homeVideo',self.uas.get('home'))
|
||||
if custom_ua: headers['User-Agent'] = custom_ua
|
||||
if self.ver == 2 or self.ver == 3:
|
||||
response = self.fetch(f'{self.host}/api.php/appfox/nav', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
navigationId = ''
|
||||
for i in response['data']:
|
||||
if isinstance(i,dict):
|
||||
navigationId = i['navigationId']
|
||||
break
|
||||
if not navigationId: return None
|
||||
path = f'nav_video?id={navigationId}'
|
||||
else:
|
||||
path = 'index'
|
||||
response = self.fetch(f'{self.host}/api.php/Appfox/{path}', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
data = response['data']
|
||||
videos = []
|
||||
for i in data:
|
||||
for j in i.get('banner', []):
|
||||
videos.append(j)
|
||||
for k in i.get('categories', []):
|
||||
for l in k.get('videos',[]):
|
||||
videos.append(l)
|
||||
if videos and self.category == 2:
|
||||
for i in videos:
|
||||
i['vod_id'] = f"msearch:{i['vod_id']}"
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
category_ua = self.uas.get('category')
|
||||
if category_ua: headers['User-Agent'] = category_ua
|
||||
if self.cms and not self.category == 2:
|
||||
data = self.fetch(f'{self.cms}pg={pg}&t={tid}', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
else:
|
||||
response = self.fetch(f"{self.host}/api.php/Appfox/vodList?type_id={tid}&class=全部&area=全部&lang=全部&year=全部&sort=最新&page={pg}", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
videos = []
|
||||
for i in response['data']['recommend_list']:
|
||||
videos.append(i)
|
||||
data = {'list': videos}
|
||||
if self.category == 2:
|
||||
if isinstance(data, dict):
|
||||
for i in data.get('list'):
|
||||
i['vod_id'] = f"msearch:{i['vod_id']}"
|
||||
return data
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
search_ua = self.uas.get('search')
|
||||
if search_ua: headers['User-Agent'] = search_ua
|
||||
if self.cms:
|
||||
cms = self.cms
|
||||
if '?' in cms: cms = cms.split('?')[0] + '?'
|
||||
response = self.fetch(f'{cms}ac=detail&wd={key}', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
self.detail = response['list']
|
||||
else:
|
||||
if self.ver == 3:
|
||||
body = f'{{"ac":"detail","wd":"{key}","pg":"{pg}"}}'
|
||||
this_headers2 = self.headers2(body)
|
||||
if search_ua: this_headers2['User-Agent'] = search_ua
|
||||
res = self.post(f'{self.host}/api.php/appfoxs/vod', headers=this_headers2, data=body, verify=False, timeout=self.timeout).text
|
||||
try:
|
||||
response = json.loads(res)
|
||||
except Exception:
|
||||
response = json.loads(self.decrypt(res))
|
||||
else:
|
||||
path = f"{self.host}/api.php/Appfox/vod?ac=detail&wd={key}"
|
||||
if self.froms: path += '&from=' + self.froms
|
||||
response = self.fetch(path, headers=headers, verify=False, timeout=self.timeout).json()
|
||||
self.detail = response['list']
|
||||
return response
|
||||
|
||||
def detailContent(self, ids):
|
||||
headers = self.headers.copy()
|
||||
detail_ua = self.uas.get('detail',self.uas.get('search'))
|
||||
if detail_ua:
|
||||
headers['User-Agent'] = detail_ua
|
||||
video = next((i.copy() for i in self.detail if str(i['vod_id']) == str(ids[0])), None)
|
||||
if not video:
|
||||
if self.cms:
|
||||
cms = self.cms
|
||||
if '?' in cms: cms = cms.split('?')[0] + '?'
|
||||
response = self.fetch(f'{cms}ac=detail&ids={ids[0]}', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
video = response.get('list')[0]
|
||||
else:
|
||||
if self.ver == 3:
|
||||
body = f'{{"ac":"detail","ids":"{ids[0]}"}}'
|
||||
this_headers2 = self.headers2(body)
|
||||
if detail_ua:
|
||||
this_headers2['User-Agent'] = detail_ua
|
||||
res = self.post(f"{self.host}/api.php/appfoxs/vod",data=body, headers=this_headers2, verify=False, timeout=self.timeout).text
|
||||
try:
|
||||
detail_response = json.loads(res)
|
||||
except Exception:
|
||||
detail_response = json.loads(self.decrypt(res))
|
||||
else:
|
||||
detail_response = self.fetch(f"{self.host}/api.php/Appfox/vod?ac=detail&ids={ids[0]}", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
video = detail_response.get('list')[0]
|
||||
if not video: return {'list': []}
|
||||
play_from = video['vod_play_from'].split('$$$')
|
||||
play_urls = video['vod_play_url'].split('$$$')
|
||||
try:
|
||||
headers = self.headers.copy()
|
||||
if self.ver == 3:
|
||||
headers = self.headers2()
|
||||
custom_ua = self.uas.get('config')
|
||||
if custom_ua: headers['User-Agent'] = custom_ua
|
||||
play_config = self.play_config
|
||||
if not play_config:
|
||||
if self.ver == 3:
|
||||
res = self.fetch(f"{self.host}/api.php/appfoxs/config", headers=headers, verify=False, timeout=self.timeout).text
|
||||
try:
|
||||
config_response = json.loads(res)
|
||||
except Exception:
|
||||
config_response = json.loads(self.decrypt(res))
|
||||
else:
|
||||
config_response = self.fetch(f"{self.host}/api.php/Appfox/config", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
self.play_config = {'playerList': config_response.get('data', {}).get('playerList', []), 'jiexiDataList': config_response.get('data', {}).get('jiexiDataList', [])}
|
||||
player_list = self.play_config.get('playerList', [])
|
||||
jiexi_data_list = self.play_config.get('jiexiDataList', [])
|
||||
except Exception:
|
||||
return {'list': [video]}
|
||||
player_map = {player['playerCode']: player for player in player_list}
|
||||
processed_play_urls = []
|
||||
for idx, play_code in enumerate(play_from):
|
||||
if play_code in player_map:
|
||||
player_info = player_map[play_code]
|
||||
if player_info['playerCode'] != player_info['playerName']:
|
||||
play_from[idx] = f"{player_info['playerName']}\u2005({play_code})"
|
||||
if idx < len(play_urls):
|
||||
urls = play_urls[idx].split('#')
|
||||
processed_urls = []
|
||||
for url in urls:
|
||||
parts = url.split('$')
|
||||
if len(parts) >= 2:
|
||||
parts[1] = f"{play_code}@{parts[1]}"
|
||||
processed_urls.append('$'.join(parts))
|
||||
else:
|
||||
processed_urls.append(url)
|
||||
processed_play_urls.append('#'.join(processed_urls))
|
||||
video['vod_play_from'] = '$$$'.join(play_from)
|
||||
video['vod_play_url'] = '$$$'.join(processed_play_urls)
|
||||
self.parses = {p['playerCode']: p['url'] for p in jiexi_data_list if p.get('url', '').startswith('http')}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
play_from, raw_url = id.split('@', 1)
|
||||
jx, parse, parsed = 0, 0, 0
|
||||
headers = self.headers.copy()
|
||||
parse_ua = self.uas.get('parse')
|
||||
if parse_ua: headers['User-Agent'] = parse_ua
|
||||
player_ua = self.uas.get('player','Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1')
|
||||
url = raw_url
|
||||
parses_main = []
|
||||
if self.custom_first == 1:
|
||||
parses_main.append(self.custom_parses)
|
||||
parses_main.append(self.parses)
|
||||
else:
|
||||
parses_main.append(self.parses)
|
||||
parses_main.append(self.custom_parses)
|
||||
for parses2 in parses_main:
|
||||
if not parsed and not re.match(r'https?://.*\.(m3u8|mp4|flv|mkv)', url):
|
||||
for key, parsers in parses2.items():
|
||||
if play_from not in key:
|
||||
continue
|
||||
if isinstance(parsers,list):
|
||||
for parser in parsers:
|
||||
if parser.startswith('parse:'):
|
||||
url,jx,parse = parser.split('parse:')[1] + raw_url,0,1
|
||||
break
|
||||
try:
|
||||
response = self.fetch(f"{parser}{raw_url}", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
if response.get('url', '').startswith('http'):
|
||||
url, parsed = response['url'], 1
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
else:
|
||||
if parsers.startswith('parse:'):
|
||||
url,jx,parse = parsers.split('parse:')[1] + raw_url,0,1
|
||||
break
|
||||
try:
|
||||
response = self.fetch(f"{parsers}{raw_url}", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
if response.get('url', '').startswith('http'):
|
||||
url, parsed = response['url'], 1
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if parsed or parse:
|
||||
break
|
||||
if parsed or parse:
|
||||
break
|
||||
if not(re.match(r'https?://.*\.(m3u8|mp4|flv|mkv)', url) or parsed == 1):
|
||||
jx = 1
|
||||
return { 'jx': jx, 'parse': parse, 'url': url, 'header': {'User-Agent': player_ua}}
|
||||
|
||||
def decrypt(self,data):
|
||||
try:
|
||||
cipher_data = base64.b64decode(data)
|
||||
k = self.md5(self.app_key)[:16].encode('utf-8')
|
||||
cipher = AES.new(k, AES.MODE_CBC, k[::-1])
|
||||
plaintext_padded = cipher.decrypt(cipher_data)
|
||||
plaintext = unpad(plaintext_padded, AES.block_size)
|
||||
return plaintext.decode('utf-8')
|
||||
except Exception:
|
||||
raise ValueError
|
||||
|
||||
def headers2(self,body=''):
|
||||
timestamp = int(time.time() * 1000)
|
||||
nonce = secrets.choice(range(100000, 1000000))
|
||||
sign = self.md5(f'{self.app_sign}{self.app_key}{timestamp}{nonce}{body}')
|
||||
return {
|
||||
'User-Agent': self.headers['User-Agent'],
|
||||
'Accept-Encoding': 'gzip',
|
||||
'x-security-auth': f'{timestamp}|{nonce}|{sign}',
|
||||
'content-type': 'application/json; charset=utf-8'
|
||||
}
|
||||
|
||||
def md5(self,data):
|
||||
return hashlib.md5(data.encode('utf-8')).hexdigest()
|
||||
|
||||
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,187 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习及爬虫技术交流。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
"""
|
||||
{
|
||||
"key": "xxx",
|
||||
"name": "xxx",
|
||||
"type": 3,
|
||||
"api": "./ApptoV5无加密.py",
|
||||
"ext": "http://domain.com"
|
||||
}
|
||||
"""
|
||||
|
||||
import re,sys,uuid
|
||||
from base.spider import Spider
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host,config,local_uuid,parsing_config = '','','',[]
|
||||
headers = {
|
||||
'User-Agent': "Dart/2.19 (dart:io)",
|
||||
'Accept-Encoding': "gzip",
|
||||
'appto-local-uuid': local_uuid
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
host = extend.strip()
|
||||
if not host.startswith('http'):
|
||||
return {}
|
||||
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?/?$', host):
|
||||
host_=self.fetch(host).json()
|
||||
self.host = host_['domain']
|
||||
else:
|
||||
self.host = host
|
||||
self.local_uuid = str(uuid.uuid4())
|
||||
response = self.fetch(f'{self.host}/apptov5/v1/config/get?p=android&__platform=android', headers=self.headers).json()
|
||||
config = response['data']
|
||||
self.config = config
|
||||
parsing_conf = config['get_parsing']['lists']
|
||||
parsing_config = {}
|
||||
for i in parsing_conf:
|
||||
if len(i['config']) != 0:
|
||||
label = []
|
||||
for j in i['config']:
|
||||
if j['type'] == 'json':
|
||||
label.append(j['label'])
|
||||
parsing_config.update({i['key']:label})
|
||||
self.parsing_config = parsing_config
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f'初始化异常:{e}')
|
||||
return {}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f"{self.host}/apptov5/v1/vod/getVod?id={ids[0]}",headers=self.headers).json()
|
||||
data3 = response['data']
|
||||
videos = []
|
||||
vod_play_url = ''
|
||||
vod_play_from = ''
|
||||
for i in data3['vod_play_list']:
|
||||
play_url = ''
|
||||
for j in i['urls']:
|
||||
play_url += f"{j['name']}${i['player_info']['from']}@{j['url']}#"
|
||||
vod_play_from += i['player_info']['show'] + '$$$'
|
||||
vod_play_url += play_url.rstrip('#') + '$$$'
|
||||
vod_play_url = vod_play_url.rstrip('$$$')
|
||||
vod_play_from = vod_play_from.rstrip('$$$')
|
||||
videos.append({
|
||||
'vod_id': data3.get('vod_id'),
|
||||
'vod_name': data3.get('vod_name'),
|
||||
'vod_content': data3.get('vod_content'),
|
||||
'vod_remarks': data3.get('vod_remarks'),
|
||||
'vod_director': data3.get('vod_director'),
|
||||
'vod_actor': data3.get('vod_actor'),
|
||||
'vod_year': data3.get('vod_year'),
|
||||
'vod_area': data3.get('vod_area'),
|
||||
'vod_play_from': vod_play_from,
|
||||
'vod_play_url': vod_play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
url = f"{self.host}/apptov5/v1/search/lists?wd={key}&page={pg}&type=&__platform=android"
|
||||
response = self.fetch(url, headers=self.headers).json()
|
||||
data = response['data']['data']
|
||||
for i in data:
|
||||
if i.get('vod_pic').startswith('mac://'):
|
||||
i['vod_pic'] = i['vod_pic'].replace('mac://', 'http://', 1)
|
||||
return {'list': data, 'page': pg, 'total': response['data']['total']}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
default_ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
|
||||
parsing_config = self.parsing_config
|
||||
parts = id.split('@')
|
||||
if len(parts) != 2:
|
||||
return {'parse': 0, 'url': id, 'header': {'User-Agent': default_ua}}
|
||||
playfrom, rawurl = parts
|
||||
label_list = parsing_config.get(playfrom)
|
||||
if not label_list:
|
||||
return {'parse': 0, 'url': rawurl, 'header': {'User-Agent': default_ua}}
|
||||
result = {'parse': 1, 'url': rawurl, 'header': {'User-Agent': default_ua}}
|
||||
for label in label_list:
|
||||
payload = {
|
||||
'play_url': rawurl,
|
||||
'label': label,
|
||||
'key': playfrom
|
||||
}
|
||||
try:
|
||||
response = self.post(
|
||||
f"{self.host}/apptov5/v1/parsing/proxy?__platform=android",
|
||||
data=payload,
|
||||
headers=self.headers
|
||||
).json()
|
||||
except Exception as e:
|
||||
print(f"请求异常: {e}")
|
||||
continue
|
||||
if not isinstance(response, dict):
|
||||
continue
|
||||
if response.get('code') == 422:
|
||||
continue
|
||||
data = response.get('data')
|
||||
if not isinstance(data, dict):
|
||||
continue
|
||||
url = data.get('url')
|
||||
if not url:
|
||||
continue
|
||||
ua = data.get('UA') or data.get('UserAgent') or default_ua
|
||||
result = {
|
||||
'parse': 0,
|
||||
'url': url,
|
||||
'header': {'User-Agent': ua}
|
||||
}
|
||||
break
|
||||
return result
|
||||
|
||||
def homeContent(self, filter):
|
||||
config = self.config
|
||||
if not config:
|
||||
return {}
|
||||
home_cate = config['get_home_cate']
|
||||
classes = []
|
||||
for i in home_cate:
|
||||
if isinstance(i.get('extend', []),dict):
|
||||
classes.append({'type_id': i['cate'], 'type_name': i['title']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f'{self.host}/apptov5/v1/home/data?id=1&mold=1&__platform=android',headers=self.headers).json()
|
||||
data = response['data']
|
||||
vod_list = []
|
||||
for i in data['sections']:
|
||||
for j in i['items']:
|
||||
vod_pic = j.get('vod_pic')
|
||||
if vod_pic.startswith('mac://'):
|
||||
vod_pic = vod_pic.replace('mac://', 'http://', 1)
|
||||
vod_list.append({
|
||||
"vod_id": j.get('vod_id'),
|
||||
"vod_name": j.get('vod_name'),
|
||||
"vod_pic": vod_pic,
|
||||
"vod_remarks": j.get('vod_remarks')
|
||||
})
|
||||
return {'list': vod_list}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(f"{self.host}/apptov5/v1/vod/lists?area={extend.get('area','')}&lang={extend.get('lang','')}&year={extend.get('year','')}&order={extend.get('sort','time')}&type_id={tid}&type_name=&page={pg}&pageSize=21&__platform=android", headers=self.headers).json()
|
||||
data = response['data']
|
||||
data2 = data['data']
|
||||
for i in data['data']:
|
||||
if i.get('vod_pic','').startswith('mac://'):
|
||||
i['vod_pic'] = i['vod_pic'].replace('mac://', 'http://', 1)
|
||||
return {'list': data2, 'page': pg, 'total': data['total']}
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,124 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import re,sys,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host = {
|
||||
'User-Agent': "okhttp/4.9.3",
|
||||
'Accept-Encoding': "gzip"
|
||||
},''
|
||||
|
||||
def init(self, extend=''):
|
||||
host = extend.strip()
|
||||
if host.startswith('http'):
|
||||
self.host = host.rstrip('/')
|
||||
|
||||
def homeContent(self, filter):
|
||||
response = self.fetch(f'{self.host}/api.php?type=getsort', headers=self.headers, verify=False).json()
|
||||
classes, filters = [], {}
|
||||
for i in response['list']:
|
||||
type_id = i['type_id']
|
||||
classes.append({'type_id': type_id, 'type_name': i['type_name']})
|
||||
if 'type_extend' in i:
|
||||
extend = i['type_extend']
|
||||
filter_list = []
|
||||
if 'class' in extend and extend['class']:
|
||||
value_list = [{"n": "全部", "v": "全部"}]
|
||||
for val in extend['class'].split(','):
|
||||
if val.strip():
|
||||
value_list.append({"n": val.strip(), "v": val.strip()})
|
||||
filter_list.append({"key": "class", "name": "类型", "init": "全部", "value": value_list})
|
||||
if 'year' in extend and extend['year']:
|
||||
value_list = [{"n": "全部", "v": "全部"}]
|
||||
for val in extend['year'].split(','):
|
||||
if val.strip():
|
||||
value_list.append({"n": val.strip(), "v": val.strip()})
|
||||
filter_list.append({"key": "year", "name": "年份", "init": "全部", "value": value_list})
|
||||
if filter_list:
|
||||
filters[type_id] = filter_list
|
||||
return {'class': classes, 'filters': filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f'{self.host}/api.php?type=getHome', headers=self.headers,verify=False).json()
|
||||
videos = []
|
||||
for i, j in response.items():
|
||||
if not isinstance(j, dict):
|
||||
continue
|
||||
lis = j.get('list')
|
||||
if isinstance(lis, list):
|
||||
videos.extend(lis)
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(f"{self.host}/api.php?type=getvod&type_id={tid}&page={pg}&tag={extend.get('class','全部')}&year={extend.get('year','全部')}", headers=self.headers,verify=False).json()
|
||||
return {'list': response['list'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
response = self.fetch(f'{self.host}/api.php?type=getsearch&text={key}', headers=self.headers,verify=False).json()
|
||||
for i in response['list']:
|
||||
if not i.get('vod_content'):
|
||||
i['vod_content'] = i['vod_blurb']
|
||||
return {'list': response['list'], 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f'{self.host}/api.php?type=getVodinfo&id={ids[0]}', headers=self.headers, verify=False).json()
|
||||
show = []
|
||||
vod_play_url = []
|
||||
for i in response['vod_player']['list']:
|
||||
if i.get('ps', '') == i.get('from', ''):
|
||||
show.append(i.get('from', ''))
|
||||
else:
|
||||
show.append(f"{i.get('ps', '').replace('(广告勿信)', '')}\u2005({i.get('from', '')})")
|
||||
play_url = i.get('url', '')
|
||||
vod_play_url.append('#'.join(f"{item}@{ids[0]}" for item in play_url.split('#')))
|
||||
videos = [{
|
||||
'vod_name': response.get('vod_name'),
|
||||
'vod_pic': response.get('vod_pic'),
|
||||
'vod_id': response.get('vod_id'),
|
||||
'vod_class': response.get('vod_class'),
|
||||
'vod_actor': response.get('vod_actor'),
|
||||
'vod_blurb': response.get('vod_blurb'),
|
||||
'vod_content': response.get('vod_blurb'),
|
||||
'vod_remarks': response.get('vod_remarks'),
|
||||
'vod_lang': response.get('vod_lang'),
|
||||
'vod_play_from': '$$$'.join(show),
|
||||
'vod_play_url': '$$$'.join(vod_play_url)
|
||||
}]
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
jx, ua, url = 0, 'Dalvik/2.1.0 (Linux; U; Android 14; Xiaomi 15 Build/SQ3A.220705.004)', ''
|
||||
ua2 = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
|
||||
vod_url, vod_id = id.split('@')
|
||||
try:
|
||||
response = self.fetch(f'{self.host}/api.php?type=jx&vodurl={vod_url}&vodid={vod_id}', headers=self.headers, verify=False).json()
|
||||
play_url = response['url']
|
||||
if url.startswith('http'):
|
||||
url = play_url
|
||||
except Exception:
|
||||
pass
|
||||
if not url:
|
||||
url = vod_url
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', vod_url):
|
||||
jx, ua = 1, ua2
|
||||
return {'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent': ua}}
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,342 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import DES3
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,time,json,base64,secrets,hashlib,urllib3
|
||||
from urllib.parse import quote,unquote,urljoin,urlparse
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
def __init__(self):
|
||||
self.headers = {
|
||||
'User-Agent': 'Android',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept': 'application/vnd.yourapi.v1.full+json',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'Device-Id': '',
|
||||
'Screen-Width': '2670',
|
||||
'Channel': 'guan',
|
||||
'Cur-Time': '',
|
||||
'Mob-Mfr': 'xiaomi',
|
||||
'prefersex': '1',
|
||||
'Mob-Model': 'xiaomi',
|
||||
'token': '',
|
||||
'Sys-Release': '15',
|
||||
'appid': '',
|
||||
'Version-Code': '',
|
||||
'Sys-Platform': 'Android',
|
||||
'Screen-Height': '1200',
|
||||
'timestamp': ''
|
||||
}
|
||||
self.play_header = {
|
||||
'User-Agent': 'Mozi'
|
||||
}
|
||||
self.host = ''
|
||||
self.play_domain = ''
|
||||
self.proxyurl = ''
|
||||
self.encrypt_domain = ''
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
ext = json.loads(extend.strip())
|
||||
host = ext.get('host').rstrip('/')
|
||||
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?/?$', host): return
|
||||
appid = ext['app_id']
|
||||
deviceid = ext.get('deviceid')
|
||||
version_code = ext['versionCode']
|
||||
channel = ext['UMENG_CHANNEL']
|
||||
self.encrypt_domain = ext.get('EncryptDomain','vT1RQRz8YzlzTgN26pIXNJ7Mi65juwSP')
|
||||
if not(appid and version_code and channel): return
|
||||
if deviceid:
|
||||
if not self.is_valid_android_id(deviceid): return
|
||||
else:
|
||||
deviceid_cache_key = f'Hmys_deviceid_{appid}_{channel}'
|
||||
deviceid = self.getCache(deviceid_cache_key)
|
||||
if not deviceid:
|
||||
deviceid = self.device_id()
|
||||
self.setCache(deviceid_cache_key,deviceid)
|
||||
self.host = host
|
||||
self.headers['appid'] = appid
|
||||
self.headers['Channel'] = channel
|
||||
self.headers['Device-Id'] = deviceid
|
||||
self.headers['Version-Code'] = version_code
|
||||
try:
|
||||
self.proxyurl = f'{self.getProxyUrl(True)}&type=hmys'
|
||||
except Exception:
|
||||
self.proxyurl = 'http://127.0.0.1:9978/proxy?do=py&type=hmys'
|
||||
self.login(host)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
headers['timestamp'] = self.timestamp()
|
||||
response = self.post(f'{self.host}/api/block/category_type', headers=headers, verify=False).json()
|
||||
data = json.loads(self.des3(response['data']))
|
||||
class_list = []
|
||||
for item in data['result']:
|
||||
class_list.append({ 'type_id': item['type_pid'], 'type_name': item['type_name']})
|
||||
filters_dict = {}
|
||||
for item in data['result']:
|
||||
type_id = item['type_pid']
|
||||
cate_list = [cate.strip() for cate in item['cate'].split(',')]
|
||||
cate_values = [{'n': cate, 'v': cate} for cate in cate_list]
|
||||
area_list = [area.strip() for area in item['area'].split(',')]
|
||||
area_values = [{'n': area, 'v': area} for area in area_list]
|
||||
year_list = [year.strip() for year in item['year'].split(',')]
|
||||
year_values = [{'n': year, 'v': year} for year in year_list]
|
||||
order_list = [order.strip() for order in item['order'].split(',')]
|
||||
order_values = [{'n': order, 'v': order} for order in order_list]
|
||||
filters_dict[str(type_id)] = [
|
||||
{'key': 'class', 'name': '类型', 'value': cate_values},
|
||||
{'key': 'area', 'name': '地区', 'value': area_values},
|
||||
{'key': 'year', 'name': '年份', 'value': year_values},
|
||||
{'key': 'sort', 'name': '排序', 'value': order_values}
|
||||
]
|
||||
return {'class': class_list, 'filters': filters_dict}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
headers['timestamp'] = self.timestamp()
|
||||
response = self.post(f'{self.host}/api/nav/list', headers=headers,verify=False).json()
|
||||
data = self.des3(response['data'])
|
||||
result = json.loads(data)['result']
|
||||
if not result: return None
|
||||
classes, videos, recommend_id = [], [], ''
|
||||
for i in result:
|
||||
if isinstance(i, dict):
|
||||
if i.get('nav_name') == '推荐':
|
||||
recommend_id = i.get('nav_id')
|
||||
break
|
||||
headers['timestamp'] = self.timestamp()
|
||||
response2 = self.post(f'{self.host}/api/nav/index',data={'nav_id': recommend_id or '253'}, headers=headers, verify=False).json()
|
||||
data2 = self.des3(response2['data'])
|
||||
result2 = json.loads(data2)['result']
|
||||
for item in result2:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
for block in item.get('block_list', []):
|
||||
if not isinstance(block, dict):
|
||||
continue
|
||||
for vod in block.get('vod_list', []):
|
||||
if vod.get('type_pid') != 1:
|
||||
remark = f"{vod['serial']}集全" if vod.get('is_end') == 1 else f"更新至{vod['serial']}集"
|
||||
else:
|
||||
remark = f"评分:{vod['score']}"
|
||||
videos.append({
|
||||
'vod_id': vod['vod_id'],
|
||||
'vod_name': vod['title'],
|
||||
'vod_pic': vod['pic'],
|
||||
'vod_remarks': remark
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, ext):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
headers['timestamp'] = self.timestamp()
|
||||
payload = {
|
||||
'area': ext.get('area','全部'),
|
||||
'cate': ext.get('class','全部'),
|
||||
'type_pid': tid,
|
||||
'year': ext.get('year','全部'),
|
||||
'length': '12',
|
||||
'page': pg,
|
||||
'order': ext.get('sort','最热')
|
||||
}
|
||||
response = self.post(f'{self.host}/api/block/category', data=payload, headers=headers, verify=False).json()
|
||||
data = json.loads(self.des3(response['data']))
|
||||
videos = []
|
||||
for i in data.get('result',[]):
|
||||
if i.get('type_pid') != 1:
|
||||
remark = f"{i['serial']}集全" if i.get('is_end') == 1 else f"更新至{i['serial']}集"
|
||||
else:
|
||||
remark = f"评分:{i['score']}"
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['title'],
|
||||
'vod_pic': i['pic'],
|
||||
'vod_remarks': remark
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
headers['timestamp'] = self.timestamp()
|
||||
payload = {'type_pid': "0", 'kw': key, 'pn': pg}
|
||||
response = self.post(f'{self.host}/api/search/result', data=payload, headers=headers, verify=False).json()
|
||||
data = json.loads(self.des3(response['data']))
|
||||
videos = []
|
||||
for i in data.get('result',[]):
|
||||
vod_remarks = f"{i['serial']}集" if i['type_pid'] != '1' else i['tags']
|
||||
if i['short_video'] == 1:
|
||||
vod_remarks += ',短剧'
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['title'],
|
||||
'vod_pic': i['pic'],
|
||||
'vod_remarks': vod_remarks,
|
||||
'vod_year': i['year']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
headers['timestamp'] = self.timestamp()
|
||||
payload = {'vod_id': ids[0]}
|
||||
response = self.post(f'{self.host}/api/vod/info', data=payload, headers=headers, verify=False).json()
|
||||
data = self.des3(response['data'])
|
||||
result = json.loads(data)['result']
|
||||
play_urls = []
|
||||
for i in result['map_list']:
|
||||
if isinstance(i, dict):
|
||||
play_urls.append(f"{i['title']}${ids[0]}@{i['id']}@{i['collection']}")
|
||||
video = {
|
||||
'vod_id': result['vod_id'],
|
||||
'vod_name': result['title'],
|
||||
'vod_pic': result['pic'],
|
||||
'vod_remarks': result['remarks'],
|
||||
'vod_year': result['year'],
|
||||
'vod_area': result['area'],
|
||||
'vod_actor': result['actor'],
|
||||
'vod_director': result['director'],
|
||||
'vod_content': result['intro'],
|
||||
'vod_play_from': '河马',
|
||||
'vod_play_url': '#'.join(play_urls),
|
||||
'type_name': result['tags']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
video_id, vod_map_id, collection = id.split('@', 2)
|
||||
headers = self.headers.copy()
|
||||
headers['timestamp'] = self.timestamp()
|
||||
payload = {
|
||||
'xz': "0",
|
||||
'vod_map_id': vod_map_id,
|
||||
'vod_id': video_id,
|
||||
'collection': collection
|
||||
}
|
||||
response = self.post(f'{self.host}/api/vod/play_url', data=payload, headers=headers, verify=False).json()
|
||||
data = self.des3(response['data'])
|
||||
result = json.loads(data)['result']
|
||||
try:
|
||||
ck = base64.b64decode(result['ck']).decode('utf-8')
|
||||
except Exception:
|
||||
ck = result['ck']
|
||||
vod_url = result['vod_url']
|
||||
check_url = result.get('check_url')
|
||||
|
||||
raw_url = f"{vod_url}?{ck}"
|
||||
if check_url:
|
||||
url = check_url
|
||||
else:
|
||||
url = self.proxyurl + '&url=' + quote(raw_url, safe='') if '.m3u8' in raw_url else vod_url
|
||||
|
||||
return {'jx': '0', 'parse': '0', 'url': url, 'header': self.play_header}
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "hmys":
|
||||
return self.hema_m3u8_proxy(params)
|
||||
return None
|
||||
|
||||
def login(self, host):
|
||||
if self.headers['token'] and self.play_domain: return
|
||||
self.headers['Cur-Time'] = self.timestamp()
|
||||
headers = self.headers.copy()
|
||||
headers['timestamp'] = self.timestamp()
|
||||
response = self.post(f'{host}/api/user/init', data={'password': '','account': ''}, headers=headers, verify=False).json()
|
||||
data = self.des3(response['data'])
|
||||
result = json.loads(data)['result']
|
||||
self.headers['token'] = result['user_info']['token']
|
||||
self.play_domain = result['sys_conf']['play_domain']
|
||||
self.host = result['sys_conf']['host_main']
|
||||
if not(self.headers['token'] or self.play_domain or self.play_domain):
|
||||
self.host = ''
|
||||
return
|
||||
headers['timestamp'] = self.timestamp()
|
||||
self.post(f'{self.host}/api/stats/login', data={'action': '6'}, headers=headers, verify=False)
|
||||
|
||||
def hema_m3u8_proxy(self, params):
|
||||
url = unquote(params['url'])
|
||||
if params.get('format') == "ts":
|
||||
data = {"Location": self.hls_sign(url), "Content-Length": "0"}
|
||||
return [302, "text/html; charset=utf-8", None, data]
|
||||
else:
|
||||
data = self.hema_modify_m3u8(url)
|
||||
return [200, "application/vnd.apple.mpegurl", data]
|
||||
|
||||
def hema_modify_m3u8(self, raw_url):
|
||||
ck = raw_url.split('?')[1]
|
||||
m3u8_url = self.hls_sign(raw_url)
|
||||
m3u8_content = self.fetch(m3u8_url, headers=self.play_header, verify=False).text
|
||||
content = m3u8_content if m3u8_content is not None else self.m3u8_content
|
||||
if content is None: raise ValueError("M3U8为空")
|
||||
parsed = urlparse(raw_url)
|
||||
base = f"{parsed.scheme}://{parsed.netloc}{parsed.path.rsplit('/', 1)[0]}/"
|
||||
output_lines = []
|
||||
for line in content.splitlines():
|
||||
stripped_line = line.strip()
|
||||
if stripped_line and not stripped_line.startswith('#'):
|
||||
if not stripped_line.startswith(('http://', 'https://')):
|
||||
full_url = urljoin(base, stripped_line)
|
||||
else:
|
||||
full_url = stripped_line
|
||||
signed_url = self.proxyurl + f'&format=ts&url=' + quote(full_url + '&' + ck,safe='')
|
||||
output_lines.append(signed_url)
|
||||
else:
|
||||
output_lines.append(line)
|
||||
return '\n'.join(output_lines)
|
||||
|
||||
def des3(self, base64_ciphertext):
|
||||
try:
|
||||
ciphertext = base64.b64decode(base64_ciphertext)
|
||||
key_bytes = 'ZT8g6QH2kS3Xj7G5wG4JtU1F'.encode('utf-8')
|
||||
iv_bytes = '51518888'.encode('utf-8')
|
||||
cipher = DES3.new(key_bytes, DES3.MODE_CBC, iv_bytes)
|
||||
plaintext_bytes = unpad(cipher.decrypt(ciphertext), DES3.block_size)
|
||||
return plaintext_bytes.decode('utf-8')
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
def hls_sign(self, url):
|
||||
replaceDomain = self.play_domain
|
||||
hex_time = self.hex_time()
|
||||
clean_url = url.split('?')[0]
|
||||
sign_data = clean_url.replace(replaceDomain, self.encrypt_domain) + hex_time
|
||||
token = hashlib.md5(sign_data.encode('utf-8')).hexdigest()
|
||||
separator = '&' if '?' in url else '?'
|
||||
return f"{url}{separator}wsSecret={token}&wsTime={hex_time}"
|
||||
|
||||
def is_valid_android_id(self, android_id):
|
||||
if not isinstance(android_id, str): return False
|
||||
return bool(re.fullmatch(r'^[0-9a-f]{16}$', android_id))
|
||||
|
||||
def device_id(self):
|
||||
return ''.join(secrets.choice('0123456789abcdef') for _ in range(16))
|
||||
|
||||
def timestamp(self):
|
||||
return str(int(time.time() * 1000))
|
||||
|
||||
def hex_time(self):
|
||||
return hex(int(time.time()))[2:]
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,130 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import sys,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host = {
|
||||
'User-Agent': "Dart/3.1 (dart:io)",
|
||||
'Accept-Encoding': "gzip"
|
||||
},'https://ksong520.com'
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/openapi/template/vod/category', headers=self.headers, verify=False).json()
|
||||
classes = []
|
||||
for i in response:
|
||||
if isinstance(i,dict):
|
||||
classes.append({'type_id': i['id'], 'type_name': i['name']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f'{self.host}/openapi/template/vod?hots=true&limit=12&random=true&order=10',headers=self.headers, verify=False).json()
|
||||
return {'list': self.json2vods(response)}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not isinstance(pg,int): pg = int(pg)
|
||||
pg2 = pg-1
|
||||
if pg2 > 0:
|
||||
offset = pg * 10 - 10
|
||||
else:
|
||||
offset = 0
|
||||
response = self.fetch(f'{self.host}/openapi/template/vod?limit=24&offset={offset}&categoryPid={tid}', headers=self.headers, verify=False).json()
|
||||
return {'list': self.json2vods(response), 'pagecount': self.page(response['total']), 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not isinstance(pg,int): pg = int(pg)
|
||||
pg2 = pg-1
|
||||
if pg2 > 0:
|
||||
offset = pg * 10 - 10
|
||||
else:
|
||||
offset = 0
|
||||
response = self.fetch(f'{self.host}/openapi/template/vod?limit=10&offset={offset}&keyword={key}', headers=self.headers, verify=False).json()
|
||||
return {'list': self.json2vods(response), 'pagecount': self.page(response['total']), 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f"{self.host}/openapi/template/vod/brief/{ids[0]}",headers=self.headers, verify=False).json()
|
||||
info = response['info']
|
||||
info2 = info['info']
|
||||
play_from, play_urls = [], []
|
||||
for i in info2['lines']:
|
||||
playline = i['playline']
|
||||
from_tag = next((j.get('tag') for j in playline if j.get('tag')), None)
|
||||
urls = [f"{j['name']}${j['id']}" for j in playline]
|
||||
play_urls.append('#'.join(urls))
|
||||
play_from.append(from_tag or i['name'])
|
||||
video = {
|
||||
'vod_id': ids[0],
|
||||
'vod_name': info2['title'],
|
||||
'vod_pic': info2['surfacePlot'],
|
||||
'vod_remarks': info2['note'],
|
||||
'vod_year': info2['year'],
|
||||
'vod_area': info2['region'],
|
||||
'vod_actor': info2['actors'],
|
||||
'vod_director': info2['directors'],
|
||||
'vod_content': info2['introduce'],
|
||||
'vod_play_from': '$$$'.join(play_from),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': info['category']['name']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, video_id, vip_flags):
|
||||
url = ''
|
||||
if video_id.startswith('http'):
|
||||
url = video_id
|
||||
else:
|
||||
try:
|
||||
response = self.fetch(f'{self.host}/openapi/playline/{video_id}', headers=self.headers, verify=False).json()
|
||||
play_url = response['info']['file']
|
||||
if play_url.startswith('http'): url = play_url
|
||||
except Exception:
|
||||
pass
|
||||
return { 'jx': 0, 'parse': 0, 'url': url, 'header': {'User-Agent':'ExoPlayer','Connection':'Keep-Alive','Accept-Encoding':'gzip'}}
|
||||
|
||||
def page(self, total, per_page=10):
|
||||
if not isinstance(total, int) or total < 0:
|
||||
raise ValueError("total 必须是非负整数")
|
||||
if not isinstance(per_page, int) or per_page <= 0:
|
||||
raise ValueError("per_page 必须是正整数")
|
||||
if total == 0: return 0
|
||||
div_result = total / per_page
|
||||
integer_part = int(div_result)
|
||||
if div_result == integer_part:
|
||||
return integer_part
|
||||
else:
|
||||
return integer_part + 1
|
||||
|
||||
def json2vods(self,arr):
|
||||
videos = []
|
||||
for i in arr['list']:
|
||||
videos.append({
|
||||
'vod_id': i['id'],
|
||||
'vod_name': i['name'],
|
||||
'vod_pic': i['horizontalPoster'],
|
||||
'vod_remarks': i['note'],
|
||||
'vod_content': i['introduce']
|
||||
})
|
||||
return videos
|
||||
|
||||
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,223 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
import sys,time,base64,secrets,hashlib,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers, host = {
|
||||
'User-Agent': 'okhttp/3.12.11',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip'
|
||||
}, ''
|
||||
|
||||
def init(self, extend=''):
|
||||
default_host = 'http://194.147.100.13'
|
||||
try:
|
||||
if not extend.startswith('http'):
|
||||
extend = default_host
|
||||
self.host = extend.strip().rstrip('/')
|
||||
except Exception:
|
||||
self.host = default_host
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/api.php/v2.vod/androidtypes', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
filters, class_list = {}, []
|
||||
for item in data:
|
||||
type_id = item.get('type_id', '')
|
||||
type_name = item.get('type_name', '')
|
||||
if not (type_id == '' and type_name == ''):
|
||||
class_list.append({'type_id': type_id,'type_name': type_name})
|
||||
classes = item.get('classes', [])
|
||||
areas = item.get('areas', [])
|
||||
years = item.get('years', [])
|
||||
filter_items = []
|
||||
class_values = [{'n': '全部', 'v': ''}]
|
||||
for cls in classes:
|
||||
n_val,v_val = cls,cls
|
||||
if not (n_val == '' and v_val == ''):
|
||||
class_values.append({'n': n_val, 'v': v_val})
|
||||
filter_items.append({'key': 'class','name': '类型','value': class_values})
|
||||
area_values = [{'n': '全部', 'v': ''}]
|
||||
for area in areas:
|
||||
n_val,v_val = area,area
|
||||
if not (n_val == '' and v_val == ''): area_values.append({'n': n_val, 'v': v_val})
|
||||
filter_items.append({'key': 'area','name': '地区','value': area_values})
|
||||
year_values = [{'n': '全部', 'v': ''}]
|
||||
for year in years:
|
||||
n_val,v_val = year,year
|
||||
if not (n_val == '' and v_val == ''):
|
||||
year_values.append({'n': n_val, 'v': v_val})
|
||||
filter_items.append({'key': 'year','name': '年份','value': year_values})
|
||||
filter_items.append({
|
||||
'key': 'sortby','name': '排序',
|
||||
'value': [{'n': '时间', 'v': 'updatetime'},{'n': '人气', 'v': 'hits'},{'n': '评分', 'v': 'score'}]
|
||||
})
|
||||
if type_id != '': filters[type_id] = filter_items
|
||||
return {'class': class_list, 'filters': filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f'{self.host}/api.php/v2.main/androidhome', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
videos = self.arr2vods(data.get('top', []))
|
||||
for i in data.get('list', []):
|
||||
if isinstance(i, dict):
|
||||
videos.extend(self.arr2vods(i.get('list', [])))
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
params = {
|
||||
k: v for k, v in {
|
||||
'page': pg,
|
||||
'type': tid,
|
||||
'area': extend.get('area'),
|
||||
'year': extend.get('year'),
|
||||
'sortby': extend.get('sortby'),
|
||||
'class': extend.get('class')
|
||||
}.items() if v is not None and v != ''
|
||||
}
|
||||
response = self.fetch(f'{self.host}/api.php/v2.vod/androidfilter10086',params=params, headers=self.headers, verify=False).json()
|
||||
videos = self.arr2vods(response.get('data', []))
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
response = self.fetch(f"{self.host}/api.php/v2.vod/androidsearch10086?page=1&wd={key}", headers=self.headers, verify=False).json()
|
||||
videos = self.arr2vods(response.get('data', []))
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f"{self.host}/api.php/v3.vod/androiddetail2?vod_id={ids[0]}", headers=self.headers2(), verify=False).json()
|
||||
data = response['data']
|
||||
play_urls = []
|
||||
for i in data['urls']:
|
||||
if not(i['key'] == '及时雨' or i['url'] == 'dlNQWVppbnZXVVZsZnRhMnRpTkVNT2JaTnpyS010VEs='):
|
||||
play_urls.append(f"{i['key']}${i['url']}")
|
||||
video = {
|
||||
'vod_id': data['id'],
|
||||
'vod_name': data['name'],
|
||||
'vod_pic': data['pic'],
|
||||
'vod_year': data['year'],
|
||||
'vod_area': data['area'],
|
||||
'vod_actor': data['actor'],
|
||||
'vod_director': data['director'],
|
||||
'vod_content': data['content'],
|
||||
'vod_play_from': 'LiteApple',
|
||||
'vod_play_url': '#'.join(play_urls),
|
||||
'type_name': data['className'],
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
parse, url, headers = 0, '', {'User-Agent':'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}
|
||||
if vid.startswith("JBN_"):
|
||||
url = "https://www.yangshipin.cn/tv/home?pid=" + vid[4:]
|
||||
parse, headers = 1, {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'}
|
||||
elif "HTTP" in vid.upper():
|
||||
url = vid
|
||||
else:
|
||||
url = 'http://c.xpgtv.net/m3u8/' + vid + '.m3u8'
|
||||
headers = {**self.headers2(), 'User-Agent': 'com.stub.StubApp/1.6.0 (Linux;Android 12) ExoPlayerLib/2.14.2'}
|
||||
return {'jx': 0, 'parse': parse, 'url': url, 'header': headers}
|
||||
|
||||
def headers2(self):
|
||||
token_param_cache_key = 'com.phoenix.tv_33ee1768b8ee539a_token_param'
|
||||
token_param = self.getCache(token_param_cache_key)
|
||||
if not token_param:
|
||||
token_param = F'{self.random32()}||||{self.random11()}||||unknown||xiaomi/b0q/b0q:12/V417IR/913:user/release-keys'
|
||||
self.setCache(token_param_cache_key, token_param)
|
||||
token2_cache_key = 'com.phoenix.tv_33ee1768b8ee539a_token2'
|
||||
token2 = self.getCache(token2_cache_key)
|
||||
if not token2:
|
||||
token2 = self.encrypt(self.random32())
|
||||
self.setCache(token2_cache_key, token2)
|
||||
timestamp = str(int(time.time()))
|
||||
version = 'XPGBOX com.phoenix.tv1.6.0'
|
||||
return {
|
||||
**self.headers.copy(),
|
||||
'token': self.encrypt(token_param),
|
||||
'token2': token2,
|
||||
'user_id': "XPGBOX",
|
||||
'version': version,
|
||||
'timestamp': timestamp,
|
||||
'hash': self.hash(f'{token_param}{version}{timestamp}'),
|
||||
'screenx': "1600",
|
||||
'screeny': "900"
|
||||
}
|
||||
|
||||
def cipher(self, data):
|
||||
bytes_key = "XPINGGUO".encode('utf-8')
|
||||
b_arr2 = bytearray(i % 256 for i in range(333))
|
||||
if len(bytes_key) > 0:
|
||||
i2 = 0
|
||||
i3 = 0
|
||||
for i4 in range(333):
|
||||
i5 = bytes_key[i2] & 0xFF
|
||||
b_val = b_arr2[i4]
|
||||
i3 = (i5 + (b_val & 0xFF) + i3) % 333
|
||||
b_arr2[i4], b_arr2[i3] = b_arr2[i3], b_val
|
||||
i2 = (i2 + 1) % len(bytes_key)
|
||||
b_arr3 = bytearray(len(data))
|
||||
i6 = 0
|
||||
i7 = 0
|
||||
for i8 in range(len(data)):
|
||||
i6 = (i6 + 1) % 333
|
||||
b2 = b_arr2[i6]
|
||||
i9 = b2 & 0xFF
|
||||
i7 = (i7 + i9) % 333
|
||||
b_arr2[i6], b_arr2[i7] = b_arr2[i7], b2
|
||||
i10 = ((b_arr2[i6] & 0xFF) + i9) % 333
|
||||
b_arr3[i8] = b_arr2[i10] ^ data[i8]
|
||||
return bytes(b_arr3)
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
if isinstance(arr, list):
|
||||
for i in arr:
|
||||
videos.append({
|
||||
'vod_id': i['id'],
|
||||
'vod_name': i['name'],
|
||||
'vod_pic': i['pic'],
|
||||
'vod_remarks': i['updateInfo'],
|
||||
'vod_year': i.get('year'),
|
||||
'vod_content': i.get('content')
|
||||
})
|
||||
return videos
|
||||
|
||||
def encrypt(self, s):
|
||||
try:
|
||||
byte_data = s.encode('utf-8')
|
||||
encrypted_data = self.cipher(byte_data)
|
||||
return base64.b64encode(encrypted_data).decode('utf-8')
|
||||
except UnicodeEncodeError:
|
||||
return ""
|
||||
|
||||
def random11(self):
|
||||
return ''.join(secrets.choice('0123456789ABCDEF') for _ in range(11))
|
||||
|
||||
def random32(self):
|
||||
random_bytes = secrets.token_bytes(32)
|
||||
return base64.b64encode(random_bytes).decode('utf-8')
|
||||
|
||||
def hash(self, s):
|
||||
hash_hex = hashlib.md5(s.encode()).hexdigest()
|
||||
return hash_hex[8:12]
|
||||
|
||||
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,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
import re,sys,time,urllib3,hashlib
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host,tab = {
|
||||
'User-Agent': 'okhttp-okgo/jeasonlzy',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8'
|
||||
},'',{'泰剧','日剧','美剧','台剧'}
|
||||
|
||||
def init(self, host=''):
|
||||
if host.startswith('http'): self.host = host
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = self.post(f'{self.host}/v3/type/top_type', data=self.payload(), headers=self.headers, verify=False).json()
|
||||
classes = []
|
||||
for i in response['data']['list']:
|
||||
if isinstance(i, dict):
|
||||
if i['type_name'] not in self.tab:
|
||||
classes.append({'type_id': i['type_id'],'type_name': i['type_name']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
response = self.post(f'{self.host}/v3/type/tj_vod', data=self.payload({'':''}), headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
videos = []
|
||||
videos.extend(self.arr2vods(data.get('cai',[])))
|
||||
videos.extend(self.arr2vods(data.get('loop', [])))
|
||||
for i in data.get('type_vod',[]):
|
||||
if isinstance(i,dict):
|
||||
if i['type_name'] not in self.tab:
|
||||
videos.extend(self.arr2vods(i.get('vod', [])))
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
response = self.post(f'{self.host}/v3/home/type_search', data=self.payload({'type_id':str(tid),'page':str(pg)}), headers=self.headers, verify=False).json()
|
||||
return {'list': self.arr2vods(response['data']['list']), 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
response = self.post(f'{self.host}/v3/home/search', data=self.payload({'keyword': key}), headers=self.headers, verify=False).json()
|
||||
videos = self.arr2vods(response['data']['list'])
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.host: return None
|
||||
response = self.post(f'{self.host}/v3/home/vod_details', data=self.payload({'vod_id':str(ids[0])}), headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
shows,play_urls = [],[]
|
||||
for i in data['vod_play_list']:
|
||||
parses = ','.join(i['parse_urls'])
|
||||
urls = []
|
||||
for j in i['urls']:
|
||||
urls.append(f"{j['name']}${j['url']}@{parses}@{i['ua']}@{i['referer']}")
|
||||
play_urls.append('#'.join(urls))
|
||||
name = re.sub(r'[\((](?:点击|换)[^)]*[\))]|[Z▶❤【].*', '', i.get('title', i.get('name', '')))
|
||||
if name == i['flag']:
|
||||
shows.append(name)
|
||||
else:
|
||||
shows.append(f"{name}({i['flag']})")
|
||||
video = {
|
||||
'vod_id': data['vod_id'],
|
||||
'vod_name': data['vod_name'],
|
||||
'vod_pic': data.get('vod_pic',data.get('vod_pic_thumb')),
|
||||
'vod_remarks': data['vod_remarks'],
|
||||
'vod_year': data['vod_year'],
|
||||
'vod_area': data['vod_area'],
|
||||
'vod_actor': data['vod_actor'],
|
||||
'vod_director': data['vod_director'],
|
||||
'vod_content': data['vod_content'],
|
||||
'vod_play_from': '$$$'.join(shows),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': data['vod_class']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
raw_url,parses,ua,referer = vid.split('@',3)
|
||||
jx,url,play_headers = 0,'',{}
|
||||
if ua:
|
||||
play_headers['User-Agent'] = ua
|
||||
if referer:
|
||||
play_headers['Referer'] = referer
|
||||
for parse in parses.split(','):
|
||||
try:
|
||||
if parse.startswith('http'):
|
||||
res = self.fetch(f'{parse}{raw_url}',headers=self.headers, verify=False).json()
|
||||
play_url = res['url']
|
||||
if play_url.startswith('http'):
|
||||
url = play_url
|
||||
except Exception:
|
||||
pass
|
||||
if not url and raw_url.startswith('http'):
|
||||
url = raw_url
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', raw_url):
|
||||
jx = 1
|
||||
return {'jx': jx, 'parse': 0, 'url': url, 'header': play_headers}
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
for i in arr:
|
||||
try:
|
||||
if i.get('vod_name') == '首页轮播': continue
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i.get('vod_pic', i.get('vod_pic_thumb')),
|
||||
'vod_remarks': i.get('vod_remarks'),
|
||||
'type_name': i.get('vod_class'),
|
||||
'vod_year': i.get('vod_year')
|
||||
})
|
||||
except Exception:
|
||||
continue
|
||||
return videos
|
||||
|
||||
def payload(self, payload={}):
|
||||
timestamp = str(int(time.time()))
|
||||
return {
|
||||
'sign': hashlib.md5(f'7gp0bnd2sr85ydii2j32pcypscoc4w6c7g5spl{timestamp}'.encode()).hexdigest(),
|
||||
**payload,
|
||||
'timestamp': timestamp
|
||||
}
|
||||
|
||||
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,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from urllib.parse import quote
|
||||
import re,sys,json,base64,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host, jiexi, headers,playua,key,iv = {
|
||||
'User-Agent': "okhttp/4.12.0",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip"
|
||||
}, '', '', '', '',''
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
ext = json.loads(extend)
|
||||
host = ext.get('host')
|
||||
self.key = ext.get('key')
|
||||
self.iv = ext.get('iv')
|
||||
if not host.startswith('http'):
|
||||
try:
|
||||
hostkey = ext.get('hostkey', 'Kkebx6vFqWMCNKwmaaeGOmnZBNzbQ1Bj')
|
||||
hostiv = ext.get('hostiv', 'cBqqjFQUqBeAJ61z')
|
||||
host = self.decrypt(host,hostkey,hostiv)
|
||||
except Exception:
|
||||
self.host = ''
|
||||
return
|
||||
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?/?$', host):
|
||||
headers = {'User-Agent': "XinJieApp/1.0",'Accept-Encoding': "gzip",'cache-control': "no-cache"}
|
||||
host = self.fetch(host,headers=headers,verify=False).json()['server']['url']
|
||||
if len(self.key) >= 16 and len(self.iv) >= 16 and host.startswith('http'): self.host = host.rstrip('/')
|
||||
self.playua = ext.get('playua', 'Dalvik/2.1.0 (Linux; U; Android 15; Xiaomi 15 Pro Build/AP2A.240905.003)')
|
||||
except Exception:
|
||||
self.host = ''
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/admin/duanjuc.php?page=1&limit=30', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
if response.get('encrypted') == 1:
|
||||
data_ = self.decrypt(response['data'])
|
||||
data = json.loads(data_)['data']
|
||||
classes, videos = [], []
|
||||
for i in data:
|
||||
if i['type_id'] != 0:
|
||||
classes.append({'type_id': i['type_id'], 'type_name': i['type_name']})
|
||||
for j in i['videos']:
|
||||
videos.append({
|
||||
'vod_id': j['vod_id'],
|
||||
'vod_name': j['vod_name'],
|
||||
'vod_pic': j['vod_pic'],
|
||||
'vod_remarks': j['vod_remarks'],
|
||||
'vod_year': j['vod_year']
|
||||
})
|
||||
return {'class': classes, 'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/admin/duanjusy.php?limit=20&page={pg}&type_id={tid}', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
if response.get('encrypted') == 1:
|
||||
data_ = self.decrypt(response['data'])
|
||||
data1 = json.loads(data_)
|
||||
data = data1['data']
|
||||
videos = []
|
||||
for i in data:
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': i['vod_remarks'],
|
||||
'vod_year': i['vod_year']
|
||||
})
|
||||
return {'list': videos, 'pagecount': data1['pagination']['total_pages']}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/admin/duanjusy.php?suggest={key}&limit=20&page={pg}', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
if response.get('encrypted') == 1:
|
||||
data_ = self.decrypt(response['data'])
|
||||
data1 = json.loads(data_)
|
||||
data = data1['data']
|
||||
videos = []
|
||||
for i in data:
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': i['vod_remarks'],
|
||||
'vod_year': i['vod_year'],
|
||||
'vod_content': i['vod_blurb']
|
||||
})
|
||||
return {'list': videos, 'pagecount': data1['pagination']['total_pages']}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f'{self.host}/admin/duanju.php?vod_id={ids[0]}', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
if response.get('encrypted') == 1:
|
||||
data_ = self.decrypt(response['data'])
|
||||
data = json.loads(data_)['data']
|
||||
jiexi = data.get('jiexi','')
|
||||
if jiexi.startswith('http'):
|
||||
self.jiexi = jiexi
|
||||
play_from, play_urls = [], []
|
||||
for source in data['play_sources']:
|
||||
play_from.append(f"{source['name']}\u2005({source['source_key']})")
|
||||
urls = source['url'].split('#')
|
||||
urls2 = [ '$'.join([parts[0], f"{source['source_key']}@{parts[1]}"]) for parts in [url.split('$') for url in urls] ]
|
||||
play_urls.append('#'.join(urls2))
|
||||
video = {
|
||||
'vod_id': data['vod_id'],
|
||||
'vod_name': data['vod_name'],
|
||||
'vod_pic': data['vod_pic'],
|
||||
'vod_remarks': data['vod_remarks'],
|
||||
'vod_year': data['vod_year'],
|
||||
'vod_area': data['vod_area'],
|
||||
'vod_actor': data['vod_actor'],
|
||||
'vod_director': data['vod_director'],
|
||||
'vod_content': data['vod_content'],
|
||||
'type_name': data['vod_class'],
|
||||
'vod_play_from': '$$$'.join(play_from),
|
||||
'vod_play_url': '$$$'.join(play_urls)
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
play_from, raw_url = id.split('@', 1)
|
||||
if self.jiexi:
|
||||
try:
|
||||
headers = {'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", 'Connection': "Keep-Alive", 'Accept-Encoding': "gzip"}
|
||||
response = self.fetch(f"{self.host}/admin/jiexi.php?url={quote(raw_url, safe='')}&source={play_from}", headers=headers, verify=False).json()
|
||||
if response.get('encrypted') == 1:
|
||||
data_ = self.decrypt(response['data'])
|
||||
data = json.loads(data_)
|
||||
play_url = data['url']
|
||||
url = play_url if play_url.startswith('http') else id
|
||||
ua = response.get('UA', self.playua)
|
||||
except Exception:
|
||||
url, ua = raw_url, self.playua
|
||||
else:
|
||||
url,ua = raw_url,self.playua
|
||||
return {'jx': '0','parse': '0','url': url,'header': {'User-Agent': ua}}
|
||||
|
||||
def decrypt(self, str, key='', iv=''):
|
||||
if not key or not iv: key, iv = self.key, self.iv
|
||||
key = key.ljust(32, '0')
|
||||
key_bytes = key.encode('utf-8')
|
||||
iv_bytes = iv.encode('utf-8')
|
||||
ciphertext = base64.b64decode(str)
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
plaintext = cipher.decrypt(ciphertext)
|
||||
padding_length = plaintext[-1]
|
||||
plaintext = plaintext[:-padding_length]
|
||||
return plaintext.decode('utf-8')
|
||||
|
||||
def homeVideoContent(self):
|
||||
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,184 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 基于原作者 @嗷呜 版本修改
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
import re,sys,json,base64,urllib3
|
||||
from Crypto.Util.Padding import unpad
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,api,key,iv = {
|
||||
'User-Agent': "Dart/3.7 (dart:io)",
|
||||
'Accept': "application/json",
|
||||
'Accept-Encoding': "gzip"
|
||||
},'','',''
|
||||
|
||||
def init(self, extend=""):
|
||||
try:
|
||||
ext = extend.rstrip()
|
||||
if ext.startswith('http'):
|
||||
self.api = ext.rstrip('/')
|
||||
else:
|
||||
arr = json.loads(ext)
|
||||
api = arr['api'].rstrip('/')
|
||||
if not api.startswith('http'): return
|
||||
self.api = api
|
||||
self.key = arr['key']
|
||||
self.iv = arr.get('iv',self.key)
|
||||
ua = arr.get('ua')
|
||||
if ua: self.headers['User-Agent'] = ua
|
||||
except Exception:
|
||||
self.api = ''
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.api: return None
|
||||
res = self.fetch(f"{self.api}/nav", headers=self.headers2(), verify=False).json()
|
||||
data = json.loads(self.decrypt(res['data']))
|
||||
keys = ["class", "area", "lang", "year", "letter", "by", "sort"]
|
||||
filters = {}
|
||||
classes = []
|
||||
for item in data:
|
||||
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})
|
||||
return {"class": classes, "filters": filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.api: return None
|
||||
res = self.fetch(f"{self.api}/index_video", headers=self.headers2(), verify=False).json()
|
||||
data = json.loads(self.decrypt(res['data']))
|
||||
videos = []
|
||||
for item in data: videos.extend(item['list'])
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.api: return None
|
||||
payload = {'page': pg, 'limit': 20, 'type_id': tid}
|
||||
filter_params = {
|
||||
k: v for k, v in {
|
||||
'class': extend.get('class'),
|
||||
'area': extend.get('area'),
|
||||
'lang': extend.get('lang'),
|
||||
'year': extend.get('year')
|
||||
}.items() if v
|
||||
}
|
||||
if filter_params:
|
||||
filter_params['sort'] = extend.get('sort', '最新')
|
||||
payload.update(filter_params)
|
||||
res = self.post(f"{self.api}/typeFilterVodList",data=json.dumps(payload), headers=self.headers2(), verify=False).json()
|
||||
data = json.loads(self.decrypt(res['data']))
|
||||
return {'list':data['recommend_list']}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.api: return None
|
||||
res = self.fetch(f"{self.api}/searchList?keywords={key}&page={pg}", headers=self.headers2({'Content-Type':'application/json'}), verify=False).json()
|
||||
data = json.loads(self.decrypt(res['data']))
|
||||
videos = data['search_list']
|
||||
for i in videos:
|
||||
if 'vod_blurb' in i:
|
||||
i['vod_content'] = i['vod_blurb']
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.api: return None
|
||||
res = self.post(f"{self.api}/video_detail",data=f'{{"vod_id":{ids[0]}}}', headers=self.headers2(), verify=False).json()
|
||||
data_ = json.loads(self.decrypt(res['data']))
|
||||
data = data_['vod_info']
|
||||
show, vod_play_url = [], []
|
||||
if 'vod_url_with_player' in data:
|
||||
for i in data['vod_url_with_player']:
|
||||
if i['code'] == i['name']:
|
||||
show.append(i['name'])
|
||||
else:
|
||||
show.append(f"{i['name']}\u2005({i['code']})")
|
||||
parse_api = i.get('parse_api', '')
|
||||
parse_secret = i.get('parse_secret', 0)
|
||||
if parse_api and parse_api.startswith('http') and not parse_secret:
|
||||
url = i.get('url', '')
|
||||
if url: url = '#'.join([i + '@' + parse_api for i in url.split('#')])
|
||||
vod_play_url.append(url)
|
||||
else:
|
||||
vod_play_url.append(i.get('url', ''))
|
||||
data.pop('vod_url_with_player')
|
||||
if show and vod_play_url:
|
||||
data['vod_play_from'] = '$$$'.join(show)
|
||||
data['vod_play_url'] = '$$$'.join(vod_play_url)
|
||||
return {'list': [data]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
jx,url,raw_url,ua = 0,'','','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
|
||||
if '@' in vid:
|
||||
raw_url, jxapi = vid.split('@', 1)
|
||||
jxapis = jxapi.split(',') if ',' in jxapi else [jxapi]
|
||||
for jxapi_ in jxapis:
|
||||
try:
|
||||
res = self.fetch(f"{jxapi_}{raw_url}", headers=self.headers, timeout=10, verify=False).text
|
||||
res = json.loads(res[1:] if res.startswith('\ufeff') else res)
|
||||
url = res.get('url', '')
|
||||
if url.startswith('http'):
|
||||
jxua = res.get('ua')
|
||||
if jxua:
|
||||
ua = jxua
|
||||
except Exception:
|
||||
url = ''
|
||||
continue
|
||||
else:
|
||||
url = vid
|
||||
if not url:
|
||||
url = raw_url
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', vid):
|
||||
url = 1
|
||||
return {'jx': jx,'parse': 0,'url': url,'header': {'User-Agent': ua}}
|
||||
|
||||
def check_rematches(self, features, target):
|
||||
patterns = [p.strip() for p in features.split(',') if p.strip()]
|
||||
for pattern in patterns:
|
||||
try:
|
||||
if re.match(pattern, target):
|
||||
return True
|
||||
except re.error as e:
|
||||
pass
|
||||
return False
|
||||
|
||||
def decrypt(self,data):
|
||||
try:
|
||||
if not self.key: return data
|
||||
ciphertext = base64.b64decode(data)
|
||||
cipher = AES.new(self.key.encode('utf-8'), AES.MODE_CBC, self.iv.encode('utf-8'))
|
||||
plaintext_bytes = unpad(cipher.decrypt(ciphertext), AES.block_size)
|
||||
return plaintext_bytes.decode('utf-8')
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def headers2(self,par={}):
|
||||
return {**self.headers,'Content-Type':'application/json'}
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,349 @@
|
||||
# coding = utf-8
|
||||
# !/usr/bin/python
|
||||
# 新时代青年 2025.06.25 getApp第三版
|
||||
# 基于原作者修改版本,仅限个人学习爬虫技术,严禁用于商业用途
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
import re,sys,time,uuid,json,base64,urllib3
|
||||
from Crypto.Util.Padding import pad,unpad
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
xurl,key,iv,init_data,search_verify = '','','','',''
|
||||
username, password, device_id = '', '', ''
|
||||
header = {'User-Agent': 'okhttp/3.14.9'}
|
||||
|
||||
def init(self, extend=''):
|
||||
ext=json.loads(extend.strip())
|
||||
host = ext['host']
|
||||
domain_set = set()
|
||||
if not re.match(r'^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(\/)?$',host):
|
||||
host_data = self.fetch(host, headers=self.header, timeout=10, verify=False).text
|
||||
domain_set.update([domain.strip() for domain in host_data.split('\n')])
|
||||
else:
|
||||
domain_set.add(host)
|
||||
ua = ext.get('ua')
|
||||
if ua: self.header['User-Agent'] = ua
|
||||
token = ext.get('token')
|
||||
if token:
|
||||
self.header['app-user-token'] = token
|
||||
else:
|
||||
self.username = ext.get('username')
|
||||
self.password = ext.get('password')
|
||||
self.device_id = ext.get('devideid')
|
||||
self.key = ext['datakey']
|
||||
self.iv = ext.get('dataiv', self.key)
|
||||
if self.device_id:
|
||||
self.header['app-user-device-id'] = self.device_id
|
||||
api = ext.get('api','/api.php/getappapi')
|
||||
if str(api) == '2':
|
||||
api = '/api.php/qijiappapi'
|
||||
for i in domain_set:
|
||||
try:
|
||||
self.xurl = i + api
|
||||
res = self.fetch(self.xurl + '.index/initV119', headers=self.header, timeout=(5,5), verify=False).json()
|
||||
encrypted_data = res['data']
|
||||
response = self.decrypt(encrypted_data)
|
||||
init_data = json.loads(response)
|
||||
except Exception:
|
||||
continue
|
||||
if init_data:
|
||||
self.init_data = init_data
|
||||
self.search_verify = init_data['config'].get('system_search_verify_status', False)
|
||||
break
|
||||
|
||||
def login(self):
|
||||
if self.username and self.password and self.device_id:
|
||||
payload = {
|
||||
'password': self.password,
|
||||
'code': "",
|
||||
'device_id': self.device_id,
|
||||
'user_name': self.username,
|
||||
'invite_code': "",
|
||||
'is_emulator': "0"
|
||||
}
|
||||
timestamp = str(int(time.time()))
|
||||
headers = self.header.copy()
|
||||
headers.update({
|
||||
'Accept-Encoding': "gzip",
|
||||
'app-version-code': "",
|
||||
'app-ui-mode': "light",
|
||||
'app-api-verify-time': timestamp,
|
||||
'app-api-verify-sign': self.encrypt(timestamp)
|
||||
})
|
||||
response = self.post(f'{self.xurl}.index/appLogin', data=payload, headers=headers).json()
|
||||
data = self.decrypt(response['data'])
|
||||
auth_token = json.loads(data)['user']['auth_token']
|
||||
self.header['app-user-token'] = auth_token
|
||||
|
||||
def homeContent(self, filter):
|
||||
kjson = self.init_data
|
||||
result = {"class": [], "filters": {}}
|
||||
for i in kjson['type_list']:
|
||||
if not(i['type_name'] in {'全部', 'QQ', 'juo.one'} or '企鹅群' in i['type_name']):
|
||||
result['class'].append({
|
||||
"type_id": i['type_id'],
|
||||
"type_name": i['type_name']
|
||||
})
|
||||
name_mapping = {'class': '类型', 'area': '地区', 'lang': '语言', 'year': '年份', 'sort': '排序'}
|
||||
filter_items = []
|
||||
for filter_type in i.get('filter_type_list', []):
|
||||
filter_name = filter_type.get('name')
|
||||
values = filter_type.get('list', [])
|
||||
if not values: continue
|
||||
value_list = [{"n": value, "v": value} for value in values]
|
||||
display_name = name_mapping.get(filter_name, filter_name)
|
||||
key = 'by' if filter_name == 'sort' else filter_name
|
||||
filter_items.append({
|
||||
"key": key,
|
||||
"name": display_name,
|
||||
"value": value_list
|
||||
})
|
||||
type_id = i.get('type_id')
|
||||
if filter_items:
|
||||
result["filters"][str(type_id)] = filter_items
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
kjson = self.init_data
|
||||
for i in kjson['type_list']:
|
||||
for item in i['recommend_list']:
|
||||
videos.append({
|
||||
"vod_id": item['vod_id'],
|
||||
"vod_name": item['vod_name'],
|
||||
"vod_pic": item['vod_pic'],
|
||||
"vod_remarks": item['vod_remarks']
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
videos = []
|
||||
payload = {
|
||||
'area': ext.get('area','全部'),
|
||||
'year': ext.get('year','全部'),
|
||||
'type_id': cid,
|
||||
'page': str(pg),
|
||||
'sort': ext.get('sort','最新'),
|
||||
'lang': ext.get('lang','全部'),
|
||||
'class': ext.get('class','全部')
|
||||
}
|
||||
res = self.post(f'{self.xurl}.index/typeFilterVodList', headers=self.header, data=payload, verify=False).json()
|
||||
encrypted_data = res['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)
|
||||
for i in kjson1['recommend_list']:
|
||||
id = i['vod_id']
|
||||
name = i['vod_name']
|
||||
pic = i['vod_pic']
|
||||
remarks = i['vod_remarks']
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
}
|
||||
videos.append(video)
|
||||
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
videos = []
|
||||
if 'xiaohys.com' in self.xurl:
|
||||
host = self.xurl.split('api.php')[0]
|
||||
data = self.fetch(f'{host}index.php/ajax/suggest?mid=1&wd={key}').json()
|
||||
for i in data['list']:
|
||||
videos.append({
|
||||
"vod_id": i['id'],
|
||||
"vod_name": i['name'],
|
||||
"vod_pic": i.get('pic')
|
||||
})
|
||||
else:
|
||||
payload = {
|
||||
'keywords': key,
|
||||
'type_id': "0",
|
||||
'page': str(pg)
|
||||
}
|
||||
if self.search_verify:
|
||||
verifi = self.verification()
|
||||
if verifi is None:
|
||||
return {'list':[]}
|
||||
payload['code'] = verifi['code']
|
||||
payload['key'] = verifi['uuid']
|
||||
res = self.post(f'{self.xurl}.index/searchList', data=payload, headers=self.header, verify=False).json()
|
||||
if not res.get('data'):
|
||||
return {'list':[] ,'msg': res.get('msg')}
|
||||
encrypted_data = res['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)
|
||||
for i in kjson1['search_list']:
|
||||
id = i['vod_id']
|
||||
name = i['vod_name']
|
||||
pic = i['vod_pic']
|
||||
remarks = i['vod_year'] + ' ' + i['vod_class']
|
||||
videos.append({
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
payload = {'vod_id': did}
|
||||
api_endpoints = ['vodDetail', 'vodDetail2']
|
||||
self.login()
|
||||
for endpoint in api_endpoints:
|
||||
response = self.post(f'{self.xurl}.index/{endpoint}', headers=self.header, data=payload, verify=False)
|
||||
if response.status_code == 200:
|
||||
response_data = response.json()
|
||||
if '到期' in response_data['msg'] or response_data['code'] == 0:
|
||||
return None
|
||||
encrypted_data = response_data['data']
|
||||
kjson1 = self.decrypt(encrypted_data)
|
||||
kjson = json.loads(kjson1)
|
||||
break
|
||||
videos, play_form, play_url = [], [], []
|
||||
lineid = 1
|
||||
name_count = {}
|
||||
for line in kjson['vod_play_list']:
|
||||
keywords = {'防走丢', '群', '防失群', '官网'}
|
||||
player_show = line['player_info']['show']
|
||||
if any(keyword in player_show for keyword in keywords):
|
||||
player_show = f'{lineid}线'
|
||||
line['player_info']['show'] = player_show
|
||||
count = name_count.get(player_show, 0) + 1
|
||||
name_count[player_show] = count
|
||||
if count > 1:
|
||||
line['player_info']['show'] = f"{player_show}{count}"
|
||||
play_form.append(line['player_info']['show'])
|
||||
parse = line['player_info']['parse']
|
||||
parse_type = line['player_info']['parse_type']
|
||||
player_parse_type = line['player_info']['player_parse_type']
|
||||
kurls = []
|
||||
for vod in line['urls']:
|
||||
token = 'token+' + vod['token']
|
||||
kurls.append(f"{str(vod['name'])}${parse},{vod['url']},{token},{player_parse_type},{parse_type}")
|
||||
play_url.append('#'.join(kurls))
|
||||
lineid += 1
|
||||
play_form = '$$$'.join(play_form)
|
||||
play_url = '$$$'.join(play_url)
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_name": kjson['vod']['vod_name'],
|
||||
"vod_actor": kjson['vod']['vod_actor'].replace('演员', ''),
|
||||
"vod_director": kjson['vod'].get('vod_director', '').replace('导演', ''),
|
||||
"vod_content": kjson['vod']['vod_content'],
|
||||
"vod_remarks": kjson['vod']['vod_remarks'],
|
||||
"vod_year": kjson['vod']['vod_year'],
|
||||
"vod_area": kjson['vod']['vod_area'],
|
||||
"vod_play_from": play_form,
|
||||
"vod_play_url": play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = ''
|
||||
aid = id.split(',')
|
||||
uid = aid[0]
|
||||
kurl = aid[1]
|
||||
token = aid[2].replace('token+', '')
|
||||
player_parse_type = aid[3]
|
||||
parse_type = aid[4]
|
||||
if parse_type == '0':
|
||||
res = {"parse": 0, "url": kurl, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
elif parse_type == '2':
|
||||
res = {"parse": 1, "url": uid+kurl, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
elif player_parse_type == '2':
|
||||
response = self.fetch(url=f'{uid}{kurl}',headers=self.header,verify=False)
|
||||
if response.status_code == 200:
|
||||
kjson1 = response.json()
|
||||
res = {"parse": 0, "url": kjson1['url'], "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
else:
|
||||
id1 = self.encrypt(kurl)
|
||||
payload = {
|
||||
'parse_api': uid,
|
||||
'url': id1,
|
||||
'player_parse_type': player_parse_type,
|
||||
'token': token
|
||||
}
|
||||
url1 = f"{self.xurl}.index/vodParse"
|
||||
response = self.post(url=url1, headers=self.header, data=payload, verify=False)
|
||||
if response.status_code == 200:
|
||||
response_data = response.json()
|
||||
encrypted_data = response_data['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)
|
||||
kjson2 = kjson1['json']
|
||||
kjson3 = json.loads(kjson2)
|
||||
url = kjson3['url']
|
||||
res = {"parse": 0, "playUrl": '', "url": url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
return res
|
||||
|
||||
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
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def decrypt(self, encrypted_data_b64):
|
||||
key_bytes = self.key.encode('utf-8')
|
||||
iv_bytes = self.iv.encode('utf-8')
|
||||
encrypted_data = base64.b64decode(encrypted_data_b64)
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
decrypted_padded = cipher.decrypt(encrypted_data)
|
||||
decrypted = unpad(decrypted_padded, AES.block_size)
|
||||
return decrypted.decode('utf-8')
|
||||
|
||||
def encrypt(self, sencrypted_data):
|
||||
key_bytes = self.key.encode('utf-8')
|
||||
iv_bytes = self.iv.encode('utf-8')
|
||||
data_bytes = sencrypted_data.encode('utf-8')
|
||||
padded_data = pad(data_bytes, AES.block_size)
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
encrypted_bytes = cipher.encrypt(padded_data)
|
||||
encrypted_data_b64 = base64.b64encode(encrypted_bytes).decode('utf-8')
|
||||
return encrypted_data_b64
|
||||
|
||||
def ocr(self, base64img):
|
||||
dat2 = self.post("https://api.nn.ci/ocr/b64/text", data=base64img, headers=self.header, verify=False).text
|
||||
if dat2:
|
||||
return dat2
|
||||
else:
|
||||
return None
|
||||
|
||||
def verification(self):
|
||||
random_uuid = str(uuid.uuid4())
|
||||
dat = self.fetch(f'{self.xurl}.verify/create?key={random_uuid}', headers=self.header, verify=False).content
|
||||
base64_img = base64.b64encode(dat).decode('utf-8')
|
||||
if not dat:
|
||||
return None
|
||||
code = self.ocr(base64_img)
|
||||
if not code:
|
||||
return None
|
||||
code = self.replace_code(code)
|
||||
if not (len(code) == 4 and code.isdigit()):
|
||||
return None
|
||||
return {'uuid': random_uuid, 'code': code}
|
||||
|
||||
def replace_code(self,text):
|
||||
replacements = {'y':'9','口':'0','q':'0','u':'0','o':'0','>':'1','d':'0','b':'8','已':'2','D':'0','五':'5'}
|
||||
if 1>2: repl={}
|
||||
if len(text) == 3:
|
||||
text = text.replace('566', '5066')
|
||||
text = text.replace('066', '1666')
|
||||
return ''.join(replacements.get(c, c) for c in text)
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
# coding = utf-8
|
||||
# !/usr/bin/python
|
||||
# 新时代青年 2025.06.25 getApp第三版
|
||||
# 基于原作者修改版本,仅限个人学习爬虫技术,严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import pad,unpad
|
||||
import re,sys,time,uuid,json,base64,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
xurl,key,iv,init_data,search_verify,suggest_search,username,password,device_id,version,get_type,def_headers= '','','','','','','', '', '','', '0',{'User-Agent': 'okhttp/3.14.9'}
|
||||
|
||||
def init(self, extend=''):
|
||||
ext=json.loads(extend.strip())
|
||||
api = str(ext.get('api'))
|
||||
if api == '2' or api == 'qiji':
|
||||
self.get_type = 'qiji'
|
||||
api = '/api.php/qijiappapi'
|
||||
elif api == 'flutter':
|
||||
self.get_type = 'flutter'
|
||||
api = '/api.php/getappapi'
|
||||
else:
|
||||
self.get_type = 'get'
|
||||
api = '/api.php/getappapi'
|
||||
host = ext['host']
|
||||
domain_set = set()
|
||||
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(/)?$',host):
|
||||
host_data = self.fetch(host, headers=self.get_headers(1), timeout=10, verify=False).text
|
||||
domain_set.update([domain.strip() for domain in host_data.split('\n')])
|
||||
else:
|
||||
domain_set.add(host)
|
||||
ua = ext.get('ua')
|
||||
if ua: self.def_headers['User-Agent'] = ua
|
||||
token = ext.get('token')
|
||||
if token:
|
||||
self.def_headers['app-user-token'] = token
|
||||
else:
|
||||
self.username = ext.get('username')
|
||||
self.password = ext.get('password')
|
||||
self.device_id = ext.get('devideid')
|
||||
self.version = str(ext.get('version',''))
|
||||
self.key = ext.get('key',ext.get('datakey'))
|
||||
if not self.key: return
|
||||
self.iv = ext.get('iv',ext.get('dataiv')) or self.key
|
||||
if self.device_id:
|
||||
self.def_headers['app-user-device-id'] = self.device_id
|
||||
for i in domain_set:
|
||||
try:
|
||||
self.xurl = i + api
|
||||
res = self.fetch(self.xurl + '.index/initV119', headers=self.get_headers(), timeout=(5, 5), verify=False).json()
|
||||
encrypted_data = res['data']
|
||||
response = self.decrypt(encrypted_data)
|
||||
init_data = json.loads(response)
|
||||
except Exception:
|
||||
continue
|
||||
if init_data:
|
||||
self.init_data = init_data
|
||||
self.search_verify = init_data['config'].get('system_search_verify_status', False)
|
||||
break
|
||||
|
||||
def homeContent(self, filter):
|
||||
kjson = self.init_data
|
||||
result = {"class": [], "filters": {}}
|
||||
for i in kjson['type_list']:
|
||||
if not(i['type_name'] in {'全部', 'QQ', 'juo.one'} or '企鹅群' in i['type_name']):
|
||||
result['class'].append({"type_id": i['type_id'], "type_name": i['type_name']})
|
||||
name_mapping = {'class': '类型', 'area': '地区', 'lang': '语言', 'year': '年份', 'sort': '排序'}
|
||||
filter_items = []
|
||||
for filter_type in i.get('filter_type_list', []):
|
||||
filter_name = filter_type.get('name')
|
||||
values = filter_type.get('list', [])
|
||||
if not values: continue
|
||||
value_list = [{"n": value, "v": value} for value in values]
|
||||
display_name = name_mapping.get(filter_name, filter_name)
|
||||
key = 'by' if filter_name == 'sort' else filter_name
|
||||
filter_items.append({ "key": key, "name": display_name, "value": value_list})
|
||||
type_id = i.get('type_id')
|
||||
if filter_items:
|
||||
result["filters"][str(type_id)] = filter_items
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
kjson = self.init_data
|
||||
for i in kjson['type_list']:
|
||||
videos.extend(self.arr2vods(i['recommend_list']))
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
payload = {
|
||||
'area': ext.get('area','全部'),
|
||||
'year': ext.get('year','全部'),
|
||||
'type_id': cid,
|
||||
'page': str(pg),
|
||||
'sort': ext.get('sort','最新'),
|
||||
'lang': ext.get('lang','全部'),
|
||||
'class': ext.get('class','全部')
|
||||
}
|
||||
res = self.post(f'{self.xurl}.index/typeFilterVodList', headers=self.get_headers(), data=payload, verify=False).json()
|
||||
encrypted_data = res['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)
|
||||
videos = self.arr2vods(kjson1['recommend_list'])
|
||||
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
videos = []
|
||||
if self.suggest_search == '1':
|
||||
host = self.xurl.split('api.php')[0]
|
||||
data = self.fetch(f'{host}index.php/ajax/suggest?mid=1&wd={key}').json()
|
||||
for i in data['list']:
|
||||
videos.append({
|
||||
"vod_id": i['id'],
|
||||
"vod_name": i['name'],
|
||||
"vod_pic": i.get('pic')
|
||||
})
|
||||
else:
|
||||
payload = {'keywords': key, 'type_id': "0", 'page': str(pg)}
|
||||
if self.search_verify:
|
||||
verifi = self.verification()
|
||||
if verifi is None:
|
||||
return {'list':[]}
|
||||
payload['code'] = verifi['code']
|
||||
payload['key'] = verifi['uuid']
|
||||
res = self.post(f'{self.xurl}.index/searchList', data=payload, headers=self.get_headers(), verify=False).json()
|
||||
if not res.get('data'):
|
||||
return {'list':[] ,'msg': res.get('msg')}
|
||||
encrypted_data = res['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)
|
||||
videos.extend(self.arr2vods(kjson1['search_list']))
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
payload = {'vod_id': ids[0]}
|
||||
if 'qijiappapi' in self.xurl:
|
||||
api_endpoints = ['vodDetail2', 'vodDetail3']
|
||||
else:
|
||||
api_endpoints = ['vodDetail']
|
||||
self.login()
|
||||
response = {}
|
||||
for endpoint in api_endpoints:
|
||||
try:
|
||||
response = self.post(f'{self.xurl}.index/{endpoint}', headers=self.get_headers(), data=payload, verify=False).json()
|
||||
except Exception:
|
||||
continue
|
||||
if '到期' in response['msg'] or response['code'] == 0:
|
||||
return None
|
||||
break
|
||||
if not response: return None
|
||||
encrypted_data = response['data']
|
||||
kjson1 = self.decrypt(encrypted_data)
|
||||
kjson = json.loads(kjson1)
|
||||
videos, play_form, play_url = [], [], []
|
||||
lineid = 1
|
||||
name_count = {}
|
||||
for line in kjson['vod_play_list']:
|
||||
keywords = {'防走丢', '群', '防失群', '官网'}
|
||||
player_show = line['player_info']['show']
|
||||
if any(keyword in player_show for keyword in keywords):
|
||||
player_show = f'{lineid}线'
|
||||
line['player_info']['show'] = player_show
|
||||
count = name_count.get(player_show, 0) + 1
|
||||
name_count[player_show] = count
|
||||
if count > 1:
|
||||
line['player_info']['show'] = f"{player_show}{count}"
|
||||
play_form.append(line['player_info']['show'])
|
||||
parse = line['player_info']['parse']
|
||||
parse_type = line['player_info']['parse_type']
|
||||
player_parse_type = line['player_info']['player_parse_type']
|
||||
kurls = []
|
||||
for vod in line['urls']:
|
||||
token = vod['token']
|
||||
kurls.append(f"{str(vod['name'])}${parse},{vod['url']},{token},{player_parse_type},{parse_type}")
|
||||
play_url.append('#'.join(kurls))
|
||||
lineid += 1
|
||||
play_form = '$$$'.join(play_form)
|
||||
play_url = '$$$'.join(play_url)
|
||||
videos.append({
|
||||
"vod_id": ids[0],
|
||||
"vod_name": kjson['vod']['vod_name'],
|
||||
"vod_actor": kjson['vod']['vod_actor'].replace('演员', ''),
|
||||
"vod_director": kjson['vod'].get('vod_director', '').replace('导演', ''),
|
||||
"vod_content": kjson['vod']['vod_content'],
|
||||
"vod_remarks": kjson['vod']['vod_remarks'],
|
||||
"vod_year": kjson['vod']['vod_year'],
|
||||
"vod_area": kjson['vod']['vod_area'],
|
||||
"vod_play_from": play_form,
|
||||
"vod_play_url": play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
url = ''
|
||||
uid, raw_url, token, player_parse_type, parse_type = vid.split(',', 4)
|
||||
if parse_type == '0':
|
||||
res = {"parse": 0, "url": raw_url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
elif parse_type == '2':
|
||||
res = {"parse": 1, "url": f'{uid}{raw_url}', "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
elif player_parse_type == '2':
|
||||
try:
|
||||
response = self.fetch(f'{uid}{raw_url}', headers=self.get_headers(1), verify=False).json()
|
||||
url = response['url']
|
||||
except Exception:
|
||||
pass
|
||||
res = {"parse": 0, "url": url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
else:
|
||||
id1 = self.encrypt(raw_url)
|
||||
payload = {
|
||||
'parse_api': uid,
|
||||
'url': id1,
|
||||
'player_parse_type': player_parse_type,
|
||||
'token': token
|
||||
}
|
||||
try:
|
||||
response = self.post(f"{self.xurl}.index/vodParse", headers=self.get_headers(), data=payload, verify=False).json()
|
||||
encrypted_data = response['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)['json']
|
||||
kjson2 = json.loads(kjson1)
|
||||
url = kjson2['url']
|
||||
except Exception:
|
||||
pass
|
||||
res = {"parse": 0, "playUrl": '', "url": url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
return res
|
||||
|
||||
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
|
||||
|
||||
def get_headers(self,main=0):
|
||||
get_type = self.get_type
|
||||
if get_type == 'qiji':
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.10.0',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip'
|
||||
}
|
||||
elif get_type == 'flutter':
|
||||
main_headers = {
|
||||
'User-Agent': 'Dart/3.5 (dart:io)',
|
||||
'Accept-Encoding': 'gzip'
|
||||
}
|
||||
if main == 1: return main_headers
|
||||
headers = {
|
||||
'User-Agent': '',
|
||||
'Accept-Encoding': '',
|
||||
'app-version-code': self.version,
|
||||
'app-os': 'android',
|
||||
'app-ui-mode': 'light',
|
||||
**main_headers
|
||||
}
|
||||
else:
|
||||
timestamp = str(int(time.time()))
|
||||
main_headers ={
|
||||
'User-Agent': 'okhttp/3.14.9',
|
||||
'Accept-Encoding': 'gzip'
|
||||
}
|
||||
if main == 1: return main_headers
|
||||
headers = {
|
||||
'User-Agent': '',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': '',
|
||||
'app-version-code': self.version,
|
||||
'app-ui-mode': 'light',
|
||||
'app-user-device-id': self.device_id,
|
||||
'app-api-verify-time': timestamp,
|
||||
'app-api-verify-sign': self.encrypt(timestamp),
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
**main_headers
|
||||
}
|
||||
return {**headers, **self.def_headers}
|
||||
|
||||
def login(self):
|
||||
if self.username and self.password and self.device_id:
|
||||
payload = {
|
||||
'password': self.password,
|
||||
'code': "",
|
||||
'device_id': self.device_id,
|
||||
'user_name': self.username,
|
||||
'invite_code': "",
|
||||
'is_emulator': "0"
|
||||
}
|
||||
response = self.post(f'{self.xurl}.index/appLogin', data=payload, headers=self.get_headers()).json()
|
||||
data = self.decrypt(response['data'])
|
||||
auth_token = json.loads(data)['user']['auth_token']
|
||||
self.def_headers['app-user-token'] = auth_token
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
if arr and isinstance(arr, list):
|
||||
for i in arr:
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i.get('vod_pic'),
|
||||
'vod_remarks': i.get('vod_remarks')
|
||||
})
|
||||
return videos
|
||||
|
||||
def decrypt(self, encrypted_data_b64):
|
||||
key_bytes = self.key.encode('utf-8')
|
||||
iv_bytes = self.iv.encode('utf-8')
|
||||
encrypted_data = base64.b64decode(encrypted_data_b64)
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
decrypted_padded = cipher.decrypt(encrypted_data)
|
||||
decrypted = unpad(decrypted_padded, AES.block_size)
|
||||
return decrypted.decode('utf-8')
|
||||
|
||||
def encrypt(self, sencrypted_data):
|
||||
key_bytes = self.key.encode('utf-8')
|
||||
iv_bytes = self.iv.encode('utf-8')
|
||||
data_bytes = sencrypted_data.encode('utf-8')
|
||||
padded_data = pad(data_bytes, AES.block_size)
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
encrypted_bytes = cipher.encrypt(padded_data)
|
||||
encrypted_data_b64 = base64.b64encode(encrypted_bytes).decode('utf-8')
|
||||
return encrypted_data_b64
|
||||
|
||||
def ocr(self, base64img):
|
||||
dat2 = self.post('https://api.nn.ci/ocr/b64/text', data=base64img, headers=self.get_headers(1), verify=False).text
|
||||
if dat2: return dat2
|
||||
return None
|
||||
|
||||
def verification(self):
|
||||
random_uuid = str(uuid.uuid4())
|
||||
dat = self.fetch(f'{self.xurl}.verify/create?key={random_uuid}', headers=self.get_headers(), verify=False).content
|
||||
base64_img = base64.b64encode(dat).decode('utf-8')
|
||||
if not dat:
|
||||
return None
|
||||
code = self.ocr(base64_img)
|
||||
if not code:
|
||||
return None
|
||||
code = self.replace_code(code)
|
||||
if not (len(code) == 4 and code.isdigit()):
|
||||
return None
|
||||
return {'uuid': random_uuid, 'code': code}
|
||||
|
||||
def replace_code(self,text):
|
||||
replacements = {'y':'9','口':'0','q':'0','u':'0','o':'0','>':'1','d':'0','b':'8','已':'2','D':'0','五':'5'}
|
||||
if len(text) == 3:
|
||||
text = text.replace('566', '5066').replace('066', '1666')
|
||||
return ''.join(replacements.get(c, c) for c in text)
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
@@ -0,0 +1,360 @@
|
||||
# coding = utf-8
|
||||
# !/usr/bin/python
|
||||
# 新时代青年 2025.06.25 getApp第三版
|
||||
# 基于原作者修改版本,仅限个人学习爬虫技术,严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import pad,unpad
|
||||
import re,sys,time,uuid,json,base64,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
xurl,key,iv,init_data,search_verify,suggest_search,username,password,device_id,version,get_type,def_headers= '','','','','','','', '', '','', '0',{}
|
||||
|
||||
def init(self, extend=''):
|
||||
ext=json.loads(extend.strip())
|
||||
api = str(ext.get('api'))
|
||||
if api == '2' or api == 'qiji':
|
||||
self.get_type = 'qiji'
|
||||
api = '/api.php/qijiappapi'
|
||||
elif api == 'flutter':
|
||||
self.get_type = 'flutter'
|
||||
api = '/api.php/getappapi'
|
||||
else:
|
||||
self.get_type = 'get'
|
||||
api = '/api.php/getappapi'
|
||||
host = ext['host']
|
||||
domain_set = set()
|
||||
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(/)?$',host):
|
||||
host_data = self.fetch(host, headers=self.get_headers(1), timeout=10, verify=False).text
|
||||
domain_set.update([domain.strip() for domain in host_data.split('\n')])
|
||||
else:
|
||||
domain_set.add(host)
|
||||
ua = ext.get('ua')
|
||||
if ua: self.def_headers['User-Agent'] = ua
|
||||
token = ext.get('token')
|
||||
if token:
|
||||
self.def_headers['app-user-token'] = token
|
||||
else:
|
||||
self.username = ext.get('username')
|
||||
self.password = ext.get('password')
|
||||
self.device_id = ext.get('devideid')
|
||||
self.version = str(ext.get('version',''))
|
||||
self.key = ext.get('key',ext.get('datakey'))
|
||||
if not self.key: return
|
||||
self.iv = ext.get('iv',ext.get('dataiv')) or self.key
|
||||
if self.device_id:
|
||||
self.def_headers['app-user-device-id'] = self.device_id
|
||||
for i in domain_set:
|
||||
try:
|
||||
self.xurl = i + api
|
||||
res = self.fetch(self.xurl + '.index/initV119', headers=self.get_headers(), timeout=(5, 5), verify=False).json()
|
||||
encrypted_data = res['data']
|
||||
response = self.decrypt(encrypted_data)
|
||||
init_data = json.loads(response)
|
||||
except Exception:
|
||||
continue
|
||||
if init_data:
|
||||
self.init_data = init_data
|
||||
self.search_verify = init_data['config'].get('system_search_verify_status', False)
|
||||
break
|
||||
|
||||
def homeContent(self, filter):
|
||||
kjson = self.init_data
|
||||
result = {"class": [], "filters": {}}
|
||||
for i in kjson['type_list']:
|
||||
if not(i['type_name'] in {'全部', 'QQ', 'juo.one'} or '企鹅群' in i['type_name']):
|
||||
result['class'].append({"type_id": i['type_id'], "type_name": i['type_name']})
|
||||
name_mapping = {'class': '类型', 'area': '地区', 'lang': '语言', 'year': '年份', 'sort': '排序'}
|
||||
filter_items = []
|
||||
for filter_type in i.get('filter_type_list', []):
|
||||
filter_name = filter_type.get('name')
|
||||
values = filter_type.get('list', [])
|
||||
if not values: continue
|
||||
value_list = [{"n": value, "v": value} for value in values]
|
||||
display_name = name_mapping.get(filter_name, filter_name)
|
||||
key = 'by' if filter_name == 'sort' else filter_name
|
||||
filter_items.append({ "key": key, "name": display_name, "value": value_list})
|
||||
type_id = i.get('type_id')
|
||||
if filter_items:
|
||||
result["filters"][str(type_id)] = filter_items
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
kjson = self.init_data
|
||||
for i in kjson['type_list']:
|
||||
videos.extend(self.arr2vods(i['recommend_list']))
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
payload = {
|
||||
'area': ext.get('area','全部'),
|
||||
'year': ext.get('year','全部'),
|
||||
'type_id': cid,
|
||||
'page': str(pg),
|
||||
'sort': ext.get('sort','最新'),
|
||||
'lang': ext.get('lang','全部'),
|
||||
'class': ext.get('class','全部')
|
||||
}
|
||||
res = self.post(f'{self.xurl}.index/typeFilterVodList', headers=self.get_headers(), data=payload, verify=False).json()
|
||||
encrypted_data = res['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)
|
||||
videos = self.arr2vods(kjson1['recommend_list'])
|
||||
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
videos = []
|
||||
if self.suggest_search == '1':
|
||||
host = self.xurl.split('api.php')[0]
|
||||
data = self.fetch(f'{host}index.php/ajax/suggest?mid=1&wd={key}').json()
|
||||
for i in data['list']:
|
||||
videos.append({
|
||||
"vod_id": i['id'],
|
||||
"vod_name": i['name'],
|
||||
"vod_pic": i.get('pic')
|
||||
})
|
||||
else:
|
||||
payload = {'keywords': key, 'type_id': "0", 'page': str(pg)}
|
||||
if self.search_verify:
|
||||
verifi = self.verification()
|
||||
if verifi is None:
|
||||
return {'list':[]}
|
||||
payload['code'] = verifi['code']
|
||||
payload['key'] = verifi['uuid']
|
||||
res = self.post(f'{self.xurl}.index/searchList', data=payload, headers=self.get_headers(), verify=False).json()
|
||||
if not res.get('data'):
|
||||
return {'list':[] ,'msg': res.get('msg')}
|
||||
encrypted_data = res['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)
|
||||
videos.extend(self.arr2vods(kjson1['search_list']))
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
payload = {'vod_id': ids[0]}
|
||||
if 'qijiappapi' in self.xurl:
|
||||
api_endpoints = ['vodDetail2', 'vodDetail3']
|
||||
else:
|
||||
api_endpoints = ['vodDetail']
|
||||
self.login()
|
||||
response = {}
|
||||
for endpoint in api_endpoints:
|
||||
try:
|
||||
response = self.post(f'{self.xurl}.index/{endpoint}', headers=self.get_headers(), data=payload, verify=False).json()
|
||||
except Exception:
|
||||
continue
|
||||
if '到期' in response['msg'] or response['code'] == 0:
|
||||
return None
|
||||
break
|
||||
if not response: return None
|
||||
encrypted_data = response['data']
|
||||
kjson1 = self.decrypt(encrypted_data)
|
||||
kjson = json.loads(kjson1)
|
||||
videos, play_form, play_url = [], [], []
|
||||
lineid = 1
|
||||
name_count = {}
|
||||
for line in kjson['vod_play_list']:
|
||||
keywords = {'防走丢', '群', '防失群', '官网'}
|
||||
player_show = line['player_info']['show']
|
||||
if any(keyword in player_show for keyword in keywords):
|
||||
player_show = f'{lineid}线'
|
||||
line['player_info']['show'] = player_show
|
||||
count = name_count.get(player_show, 0) + 1
|
||||
name_count[player_show] = count
|
||||
if count > 1:
|
||||
line['player_info']['show'] = f"{player_show}{count}"
|
||||
play_form.append(line['player_info']['show'])
|
||||
parse = line['player_info']['parse']
|
||||
parse_type = line['player_info']['parse_type']
|
||||
player_parse_type = line['player_info']['player_parse_type']
|
||||
kurls = []
|
||||
for vod in line['urls']:
|
||||
token = vod['token']
|
||||
kurls.append(f"{str(vod['name'])}${parse},{vod['url']},{token},{player_parse_type},{parse_type}")
|
||||
play_url.append('#'.join(kurls))
|
||||
lineid += 1
|
||||
play_form = '$$$'.join(play_form)
|
||||
play_url = '$$$'.join(play_url)
|
||||
videos.append({
|
||||
"vod_id": ids[0],
|
||||
"vod_name": kjson['vod']['vod_name'],
|
||||
"vod_actor": kjson['vod']['vod_actor'].replace('演员', ''),
|
||||
"vod_director": kjson['vod'].get('vod_director', '').replace('导演', ''),
|
||||
"vod_content": kjson['vod']['vod_content'],
|
||||
"vod_remarks": kjson['vod']['vod_remarks'],
|
||||
"vod_year": kjson['vod']['vod_year'],
|
||||
"vod_area": kjson['vod']['vod_area'],
|
||||
"vod_play_from": play_form,
|
||||
"vod_play_url": play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
url = ''
|
||||
uid, raw_url, token, player_parse_type, parse_type = vid.split(',', 4)
|
||||
if parse_type == '0':
|
||||
res = {"parse": 0, "url": raw_url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
elif parse_type == '2':
|
||||
res = {"parse": 1, "url": f'{uid}{raw_url}', "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
elif player_parse_type == '2':
|
||||
try:
|
||||
response = self.fetch(f'{uid}{raw_url}', headers=self.get_headers(1), verify=False).json()
|
||||
url = response['url']
|
||||
except Exception:
|
||||
pass
|
||||
res = {"parse": 0, "url": url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
else:
|
||||
id1 = self.encrypt(raw_url)
|
||||
payload = {
|
||||
'parse_api': uid,
|
||||
'url': id1,
|
||||
'player_parse_type': player_parse_type,
|
||||
'token': token
|
||||
}
|
||||
try:
|
||||
response = self.post(f"{self.xurl}.index/vodParse", headers=self.get_headers(), data=payload, verify=False).json()
|
||||
encrypted_data = response['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)['json']
|
||||
kjson2 = json.loads(kjson1)
|
||||
url = kjson2['url']
|
||||
except Exception:
|
||||
pass
|
||||
res = {"parse": 0, "playUrl": '', "url": url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
return res
|
||||
|
||||
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
|
||||
|
||||
def get_headers(self,main=0):
|
||||
get_type = self.get_type
|
||||
if get_type == 'qiji':
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.10.0',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip'
|
||||
}
|
||||
elif get_type == 'flutter':
|
||||
main_headers = {
|
||||
'User-Agent': 'Dart/3.5 (dart:io)',
|
||||
'Accept-Encoding': 'gzip'
|
||||
}
|
||||
if main == 1: return main_headers
|
||||
headers = {
|
||||
'User-Agent': '',
|
||||
'Accept-Encoding': '',
|
||||
'app-version-code': self.version,
|
||||
'app-os': 'android',
|
||||
'app-ui-mode': 'light',
|
||||
**main_headers
|
||||
}
|
||||
else:
|
||||
timestamp = str(int(time.time()))
|
||||
main_headers ={
|
||||
'User-Agent': 'okhttp/3.14.9',
|
||||
'Accept-Encoding': 'gzip'
|
||||
}
|
||||
if main == 1: return main_headers
|
||||
headers = {
|
||||
'User-Agent': '',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': '',
|
||||
'app-version-code': self.version,
|
||||
'app-ui-mode': 'light',
|
||||
'app-user-device-id': self.device_id,
|
||||
'app-api-verify-time': timestamp,
|
||||
'app-api-verify-sign': self.encrypt(timestamp),
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
**main_headers
|
||||
}
|
||||
return {**headers, **self.def_headers}
|
||||
|
||||
def login(self):
|
||||
if self.username and self.password and self.device_id:
|
||||
payload = {
|
||||
'password': self.password,
|
||||
'code': "",
|
||||
'device_id': self.device_id,
|
||||
'user_name': self.username,
|
||||
'invite_code': "",
|
||||
'is_emulator': "0"
|
||||
}
|
||||
response = self.post(f'{self.xurl}.index/appLogin', data=payload, headers=self.get_headers()).json()
|
||||
data = self.decrypt(response['data'])
|
||||
auth_token = json.loads(data)['user']['auth_token']
|
||||
self.def_headers['app-user-token'] = auth_token
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
if arr and isinstance(arr, list):
|
||||
for i in arr:
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i.get('vod_pic'),
|
||||
'vod_remarks': i.get('vod_remarks')
|
||||
})
|
||||
return videos
|
||||
|
||||
def decrypt(self, encrypted_data_b64):
|
||||
key_bytes = self.key.encode('utf-8')
|
||||
iv_bytes = self.iv.encode('utf-8')
|
||||
encrypted_data = base64.b64decode(encrypted_data_b64)
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
decrypted_padded = cipher.decrypt(encrypted_data)
|
||||
decrypted = unpad(decrypted_padded, AES.block_size)
|
||||
return decrypted.decode('utf-8')
|
||||
|
||||
def encrypt(self, sencrypted_data):
|
||||
key_bytes = self.key.encode('utf-8')
|
||||
iv_bytes = self.iv.encode('utf-8')
|
||||
data_bytes = sencrypted_data.encode('utf-8')
|
||||
padded_data = pad(data_bytes, AES.block_size)
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
encrypted_bytes = cipher.encrypt(padded_data)
|
||||
encrypted_data_b64 = base64.b64encode(encrypted_bytes).decode('utf-8')
|
||||
return encrypted_data_b64
|
||||
|
||||
def ocr(self, base64img):
|
||||
dat2 = self.post('https://api.nn.ci/ocr/b64/text', data=base64img, headers=self.get_headers(1), verify=False).text
|
||||
if dat2: return dat2
|
||||
return None
|
||||
|
||||
def verification(self):
|
||||
random_uuid = str(uuid.uuid4())
|
||||
dat = self.fetch(f'{self.xurl}.verify/create?key={random_uuid}', headers=self.get_headers(), verify=False).content
|
||||
base64_img = base64.b64encode(dat).decode('utf-8')
|
||||
if not dat:
|
||||
return None
|
||||
code = self.ocr(base64_img)
|
||||
if not code:
|
||||
return None
|
||||
code = self.replace_code(code)
|
||||
if not (len(code) == 4 and code.isdigit()):
|
||||
return None
|
||||
return {'uuid': random_uuid, 'code': code}
|
||||
|
||||
def replace_code(self,text):
|
||||
replacements = {'y':'9','口':'0','q':'0','u':'0','o':'0','>':'1','d':'0','b':'8','已':'2','D':'0','五':'5'}
|
||||
if len(text) == 3:
|
||||
text = text.replace('566', '5066').replace('066', '1666')
|
||||
return ''.join(replacements.get(c, c) for c in text)
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,json,base64,urllib3,hashlib
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host = {
|
||||
'User-Agent': 'Dart/3.8 (dart:io)',
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'x-appid': 'appidloliloli',
|
||||
'content-type': 'application/json'
|
||||
}, 'http://101.36.123.52'
|
||||
|
||||
def homeContent(self, filter):
|
||||
return {'class': [{'type_id':20,'type_name':'番剧'},{'type_id':31,'type_name':'美漫'},{'type_id':32,'type_name':'国漫'}]}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f'{self.host}/api/v1/ranking?type=hot_play&limit=30', headers=self.headers, verify=False).json()
|
||||
data = self.decrypt(response['data'],response['timestamp'])
|
||||
videos = json.loads(data)['data']['list']
|
||||
for i in videos:
|
||||
if 'vod_blurb' in i: i['vod_content'] = i['vod_blurb']
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(f'{self.host}/api/v1/filter?page={pg}&page_size=18&type_id={tid}&type_id_1=all', headers=self.headers, verify=False).json()
|
||||
data = json.loads(self.decrypt(response['data'],response['timestamp']))['data']
|
||||
videos = data['list']
|
||||
for i in videos:
|
||||
if 'vod_blurb' in i: i['vod_content'] = i['vod_blurb']
|
||||
return {'list': videos, 'pagecount': data['pagecount'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
response = self.fetch(f'{self.host}/api/v1/search?keyword={key}&page={pg}&page_size=20&fields=vod_id,vod_name,vod_pic,type_id,vod_blurb,vod_remarks,vod_lang,vod_year,vod_score,vod_total', headers=self.headers, verify=False).json()
|
||||
data = json.loads(self.decrypt(response['data'],response['timestamp']))['data']
|
||||
for i in data['list']:
|
||||
if 'vod_blurb' in i: i['vod_content'] = i['vod_blurb']
|
||||
return {'list': data['list'], 'pagecount': data['pagecount'], 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f'{self.host}/api/v1/video?vodid={ids[0]}', headers=self.headers, verify=False).json()
|
||||
data = json.loads(self.decrypt(response['data'],response['timestamp']))['data']
|
||||
return {'list': data['list']}
|
||||
|
||||
def playerContent(self, flag, url, vipflags):
|
||||
jx = 0
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', url):
|
||||
jx = 1
|
||||
return {'jx': jx, 'parse': '0', 'url': url, 'header': {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}}
|
||||
|
||||
def decrypt(self, ciphertext_b64, timestamp):
|
||||
iv = hashlib.sha256(str(timestamp)[:10].encode('utf-8')).digest()[:16]
|
||||
decoded_ciphertext = base64.b64decode(ciphertext_b64.encode('utf-8'))
|
||||
aes_cipher = AES.new(b'defaultkey123456', AES.MODE_CBC, iv)
|
||||
decrypted_with_zero_iv = aes_cipher.decrypt(decoded_ciphertext)
|
||||
unpadded_plaintext = unpad(decrypted_with_zero_iv, AES.block_size)
|
||||
return unpadded_plaintext.decode('utf-8')
|
||||
|
||||
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,177 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from urllib.parse import urlparse
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,time,json,base64,hashlib,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
def localProxy(self, params):
|
||||
try:
|
||||
if params.get('type') == 'artjx':
|
||||
data = self.art_jx(params)
|
||||
else:
|
||||
data = '请按要求传参'
|
||||
return [200, 'text/json;charset=utf-8', data]
|
||||
except Exception:
|
||||
data = self.json_encode({'code': '400', 'success': '0', 'msg': '处理出错'})
|
||||
return [400, 'text/json;charset=utf-8', data]
|
||||
|
||||
def art_jx(self, params):
|
||||
v = params.get('v')
|
||||
if not v: return '链接为空'
|
||||
timeout = params.get('timeout') or 10
|
||||
api = params.get('api')
|
||||
if not v: return 'api为空'
|
||||
key = params.get('key') or 'ARTPLAYER2023217'
|
||||
iv = params.get('iv') or 'Artplayerapiban1'
|
||||
md5_salt = params.get('md5Salt') or '123456789'
|
||||
timestamp = int(time.time())
|
||||
md5_key = hashlib.md5(f'{md5_salt}{v}{timestamp}'.encode('utf-8')).hexdigest()
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
|
||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'accept-language': 'zh-CN,zh;q=0.9',
|
||||
'cache-control': 'no-cache',
|
||||
'origin': self.domain(api),
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'sec-ch-ua': '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'x-requested-with': 'XMLHttpRequest'
|
||||
}
|
||||
body = {'url': v, 'time': timestamp, 'key': md5_key}
|
||||
try:
|
||||
data = self.post(api, data=body, headers=headers, timeout=timeout, verify=False, allow_redirects=True).json()
|
||||
except Exception:
|
||||
return '请求或解析失败'
|
||||
data.pop('user-agent', None)
|
||||
encrypted_url = data.get('url', '')
|
||||
if not encrypted_url:
|
||||
return self.json_encode({'code': '404', 'success': '0', 'msg': '解析失败', 'from_url': v})
|
||||
try:
|
||||
encrypted_bytes = base64.b64decode(encrypted_url)
|
||||
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
|
||||
decrypted_bytes = unpad(cipher.decrypt(encrypted_bytes), AES.block_size)
|
||||
url2 = decrypted_bytes.decode('utf-8')
|
||||
except Exception:
|
||||
return self.json_encode({'code': '-1', 'success': '0', 'msg': '解析失败', 'from_url': v})
|
||||
url_pattern = re.compile(r'^https?://', re.IGNORECASE)
|
||||
if not (url_pattern.match(url2) or url2 == v):
|
||||
return self.json_encode({'code': '-2', 'success': '0', 'msg': '解析失败', 'from_url': v})
|
||||
data['url'] = url2
|
||||
return self.arr2json(v, data)
|
||||
|
||||
def arr2json(self, v, data):
|
||||
remove_key = ['type', 'ip', 'player', 'From', 'From_Url']
|
||||
for key in remove_key:
|
||||
data.pop(key, None)
|
||||
if re.match(r'^https://(?:m|www)\.bilibili\.com/', v, re.I):
|
||||
if 'Referer' not in data:
|
||||
data['Referer'] = 'https://www.bilibili.com/'
|
||||
if 'User-Agent' not in data:
|
||||
data[
|
||||
'User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36'
|
||||
elif re.match(r'^https://(?:m|www)\.mgtv\.com/', v, re.I):
|
||||
if 'User-Agent' not in data or data.get(
|
||||
'User-Agent') == 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36':
|
||||
data['User-Agent'] = 'MGDS/Android/2.0.5'
|
||||
data['from'] = v
|
||||
return self.json_encode(data)
|
||||
|
||||
def no_parse(self, params):
|
||||
v = params.get('v')
|
||||
play_from = params.get('from')
|
||||
play_prefix = params.get('prefix')
|
||||
play_include = params.get('include')
|
||||
if not isinstance(v, str) or not v:
|
||||
return False
|
||||
if not any([play_from, play_prefix, play_include]): return False
|
||||
play_from_map = {
|
||||
'qq': 'qq.com',
|
||||
'qiyi': 'iqiyi.com',
|
||||
'youku': 'youku.com',
|
||||
'mgtv': 'mgtv.com',
|
||||
'bili': 'bilibili.com'
|
||||
}
|
||||
def split_param(param):
|
||||
if isinstance(param, str):
|
||||
return [p.strip() for p in param.split(',') if p.strip()]
|
||||
return []
|
||||
is_all = False
|
||||
if play_from == 'all':
|
||||
is_all = any(domain in v for domain in play_from_map.values())
|
||||
is_from = False
|
||||
if not is_all:
|
||||
from_parts = split_param(play_from)
|
||||
if from_parts: is_from = any(
|
||||
(domain := play_from_map.get(key)) is not None and domain in v for key in from_parts)
|
||||
is_prefix = False
|
||||
prefix_parts = split_param(play_prefix)
|
||||
if prefix_parts:
|
||||
is_prefix = any(v.startswith(prefix) for prefix in prefix_parts)
|
||||
is_include = False
|
||||
include_parts = split_param(play_include)
|
||||
if include_parts:
|
||||
is_include = any(inc in v for inc in include_parts)
|
||||
final_result = is_all or is_from or is_prefix or is_include
|
||||
return not final_result
|
||||
|
||||
def domain(self, url):
|
||||
try:
|
||||
parsed_url = urlparse(url)
|
||||
domain = parsed_url.netloc
|
||||
if not domain:
|
||||
parsed_url = urlparse(f'http://{url}')
|
||||
domain = parsed_url.netloc
|
||||
if not domain: domain = ''
|
||||
if '.' not in domain: return ''
|
||||
return domain
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
def json_encode(self,arr):
|
||||
return json.dumps(arr, ensure_ascii=False, indent=2)
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
pass
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
pass
|
||||
|
||||
def detailContent(self, ids):
|
||||
pass
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,185 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import traceback
|
||||
from base64 import b64encode
|
||||
from base.spider import Spider
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import quote_plus, unquote_plus, urlencode
|
||||
import sys,time,json,random,hashlib,urllib3
|
||||
from Crypto.Cipher import AES, PKCS1_v1_5
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
cache_host = {}
|
||||
def localProxy(self, params):
|
||||
if params.get('type') == 'drama':
|
||||
response = '获取域名失败'
|
||||
try:
|
||||
if params.get('from') == 'host':
|
||||
url = params.get('url')
|
||||
if url.startswith('http'):
|
||||
response = self.cache_host.get(url)
|
||||
if not(response and isinstance(response,str) and response.startswith('http')):
|
||||
res = self.fetch(url,headers={'User-Agent': 'okhttp/3.12.1'}).json()
|
||||
print('域名获取结果:',res)
|
||||
if res['domain'].startswith('http'):
|
||||
response = res['domain']
|
||||
self.cache_host[url] = response
|
||||
elif params.get('from') == 'paramsData':
|
||||
data = self.params_data(params)
|
||||
response = data
|
||||
return [200, 'text/text;charset=utf-8', response]
|
||||
except Exception as e:
|
||||
return [500, 'text/text;charset=utf-8', f'处理出错: {e}\n\n错误堆栈:\n{traceback.format_exc()}']
|
||||
return None
|
||||
|
||||
def params_data(self,params):
|
||||
ver_name = params.get('verName')
|
||||
ver = params.get('ver',ver_name.replace('.',''))
|
||||
app_name = params.get('AppName')
|
||||
pkg = params.get('pkg')
|
||||
public_key = params.get('publicKey').replace(' ','+')
|
||||
if not(ver and ver_name and app_name and pkg and public_key): return '请按要求传参'
|
||||
|
||||
# 设备信息
|
||||
android_id = '313c3eeeb2a098a1'
|
||||
mac = '02:00:00:00:00:00' # 网卡MAC地址
|
||||
model = '23113RKC6C' # 型号
|
||||
manufacturer = 'Xiaomi' # 制造商
|
||||
|
||||
# 计算UUID
|
||||
uuid = hashlib.md5(f"{android_id}{mac}{model}{manufacturer}".encode()).hexdigest().upper()
|
||||
|
||||
timestamp = self.get_13_digit_timestamp()
|
||||
random_str = self.random_str_function()
|
||||
|
||||
# AES加密生成sign
|
||||
aes_key = 'OC1A06E197EF10CF3F6058CA7A803B5E'.encode()
|
||||
cipher_aes = AES.new(aes_key, AES.MODE_ECB)
|
||||
sign_data = f"{timestamp}{random_str}".encode()
|
||||
sign_encrypted = cipher_aes.encrypt(pad(sign_data, AES.block_size))
|
||||
sign = b64encode(sign_encrypted).decode()
|
||||
|
||||
# 构建设备信息
|
||||
device_info = {
|
||||
"country": "CN",
|
||||
"vName": ver_name,
|
||||
"cpuId": "",
|
||||
"young": 0,
|
||||
"facturer": manufacturer,
|
||||
"pkg": pkg,
|
||||
"uuid": uuid,
|
||||
"resolution": "900x1600",
|
||||
"mac": quote_plus(mac),
|
||||
"sig": self.rsa_encrypt(f"{timestamp}{random_str}{ver}", public_key),
|
||||
"abid": "6249",
|
||||
"model": model,
|
||||
"plat": "android",
|
||||
"udid": uuid,
|
||||
"dpi": "240",
|
||||
"net": "1",
|
||||
"lang": "zh",
|
||||
"random_str": random_str,
|
||||
"brand": "Redmi", # 品牌
|
||||
"timestamp": timestamp,
|
||||
"density": "3.25",
|
||||
"appName": quote_plus(app_name),
|
||||
"cpu": "arm64-v8a",
|
||||
"chid": "10000",
|
||||
"carrier": quote_plus('移动'), # 网络运营商
|
||||
"sig2": sign[:8],
|
||||
"sig3": sign[8:],
|
||||
"_vOsCode": "32",
|
||||
"vOs": "12", # 安卓版本
|
||||
"vApp": ver,
|
||||
"device": "0",
|
||||
"androidID": android_id
|
||||
}
|
||||
|
||||
# JSON序列化并AES加密
|
||||
dat = json.dumps(device_info, ensure_ascii=False, separators=(',', ':'))
|
||||
|
||||
# AES-128-CBC加密
|
||||
aes_key2 = 'ed5fdsgucxumegqa'.encode()
|
||||
iv = 'ed5fdsgucxumegqa'.encode()
|
||||
cipher_aes2 = AES.new(aes_key2, AES.MODE_CBC, iv)
|
||||
dat_encrypted = cipher_aes2.encrypt(pad(dat.encode(), AES.block_size))
|
||||
dat_hex = dat_encrypted.hex()
|
||||
return dat_hex
|
||||
|
||||
def rsa_encrypt(self,plaintext, public_key_str):
|
||||
"""RSA加密"""
|
||||
# 构建PEM格式的公钥
|
||||
public_key_pem = f"-----BEGIN PUBLIC KEY-----\n{public_key_str}\n-----END PUBLIC KEY-----"
|
||||
|
||||
# 导入公钥
|
||||
rsa_key = RSA.import_key(public_key_pem)
|
||||
cipher_rsa = PKCS1_v1_5.new(rsa_key)
|
||||
|
||||
# 加密
|
||||
encrypted = cipher_rsa.encrypt(plaintext.encode())
|
||||
|
||||
# Base64编码
|
||||
return b64encode(encrypted).decode()
|
||||
|
||||
def random_str_function(self,length=16):
|
||||
"""生成随机字符串"""
|
||||
char_array = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||
result_str = ''
|
||||
i = 0
|
||||
|
||||
while i < length - 1:
|
||||
c = random.choice(char_array)
|
||||
if c not in result_str:
|
||||
result_str += c
|
||||
i += 1
|
||||
|
||||
return result_str + "="
|
||||
|
||||
def get_13_digit_timestamp(self):
|
||||
"""获取13位时间戳"""
|
||||
return int(round(time.time() * 1000))
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
pass
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
pass
|
||||
|
||||
def detailContent(self, ids):
|
||||
pass
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
sp = Spider()
|
||||
formatJo = None
|
||||
# formatJo = sp.localProxy({"type": "drama", "from": "host", "url":"https://1234-1349250429.cos.ap-hongkong.myqcloud.com/app.txt"}) # host
|
||||
formatJo = sp.localProxy({"type": "drama","from": "paramsData", "verName": "3.0.1.7", "AppName": "橘汁", "pkg": "com.tjjiangh.android", "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCp9Ek4wIlQAtwFnuBRlsFiow2tr%2B4UOciGeNKbY7nL74etUqUb6fvpOSOHhFEfaWlfwUpOB17x3JEL3No19nfjCeVYrYPjlJcgoqUWH%2FtfIfFAQWvtxBIBlKazkhw8d3ChysWmeWRikKqkBsVRY4oqNPuj4sjm6Zult0U4I4prRQIDAQAB"}) # paramsData
|
||||
print(formatJo)
|
||||
@@ -0,0 +1,187 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from Crypto.Cipher import AES, ARC4
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,time,json,base64,hashlib,urllib3,binascii
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
jx_api = ''
|
||||
|
||||
def localProxy(self, params):
|
||||
if params.get('type') == 'fyjx':
|
||||
try:
|
||||
data = self.jx(params)
|
||||
return [200, 'text/json;charset=utf-8', data]
|
||||
except Exception:
|
||||
data = self.json_encode({"code": "400", "success": "0", "msg": "处理出错"})
|
||||
return [400, 'text/json;charset=utf-8', data]
|
||||
return None
|
||||
|
||||
def jx(self,params):
|
||||
error_msg = self.json_encode({"code": "400", "success": "0", "msg": "URL或API为空"})
|
||||
v = params.get('v')
|
||||
if not v: return error_msg
|
||||
if self.no_parse(params):
|
||||
return self.json_encode({"code": "-0", "success": "0", "msg": "链接不支持"})
|
||||
timeout = params.get('timeout') or 15
|
||||
|
||||
app_key = 'HaTN33Xsne48P7HfhyQCajKsGFExSP2p'
|
||||
url = 'http://fy4k.fc8001.top/api.php?act=ini&app=10000&pay='
|
||||
key = self.domain(url)
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.12.11',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8'
|
||||
}
|
||||
if self.jx_api:
|
||||
response2 = self.fetch(f'{self.jx_api}{v}', headers=headers, timeout=timeout, verify=False).json()
|
||||
return self.arr2json(v, response2)
|
||||
else:
|
||||
timestamp = str(int(time.time()))
|
||||
payload = {'t': timestamp, 'sign': hashlib.md5(f'pay&t={timestamp}&{app_key}'.encode('utf-8')).hexdigest()}
|
||||
response = self.post(url, data=payload, headers=headers, timeout=timeout, verify=False).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except Exception:
|
||||
data = json.loads(self.rc4_decrypt(response,key))
|
||||
try:
|
||||
analysis = data['msg']['analysis']
|
||||
if not isinstance(analysis,list): ValueError()
|
||||
except Exception:
|
||||
return '获取analysis失败'
|
||||
res = ''
|
||||
for i in analysis:
|
||||
try:
|
||||
if i['encry'] == 'n' and i['url'].startswith('http'):
|
||||
response2 = self.fetch(f"{i['url']}{v}", headers=headers, timeout=timeout, verify=False).json()
|
||||
if response2['url'].startswith('http'):
|
||||
self.jx_api = i['url']
|
||||
res = self.arr2json(v, response2)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if not res: return '解析失败'
|
||||
return res
|
||||
|
||||
def no_parse(self, params):
|
||||
v = params.get('v')
|
||||
play_from = params.get('from')
|
||||
play_prefix = params.get('prefix')
|
||||
play_include = params.get('include')
|
||||
if not isinstance(v, str) or not v:
|
||||
return False
|
||||
if not any([play_from, play_prefix, play_include]): return False
|
||||
play_from_map = {
|
||||
'qq': 'qq.com',
|
||||
'qiyi': 'iqiyi.com',
|
||||
'youku': 'youku.com',
|
||||
'mgtv': 'mgtv.com',
|
||||
'bili': 'bilibili.com'
|
||||
}
|
||||
def split_param(param):
|
||||
if isinstance(param, str):
|
||||
return [p.strip() for p in param.split(',') if p.strip()]
|
||||
return []
|
||||
is_all = False
|
||||
if play_from == 'all':
|
||||
is_all = any(domain in v for domain in play_from_map.values())
|
||||
is_from = False
|
||||
if not is_all:
|
||||
from_parts = split_param(play_from)
|
||||
if from_parts: is_from = any((domain := play_from_map.get(key)) is not None and domain in v for key in from_parts)
|
||||
is_prefix = False
|
||||
prefix_parts = split_param(play_prefix)
|
||||
if prefix_parts:
|
||||
is_prefix = any(v.startswith(prefix) for prefix in prefix_parts)
|
||||
is_include = False
|
||||
include_parts = split_param(play_include)
|
||||
if include_parts:
|
||||
is_include = any(inc in v for inc in include_parts)
|
||||
final_result = is_all or is_from or is_prefix or is_include
|
||||
return not final_result
|
||||
|
||||
def cc_decrypt(self, data, key):
|
||||
cipher = AES.new(key[:16].encode('utf-8'), AES.MODE_CBC, key[-16:].encode('utf-8'))
|
||||
decrypted = unpad(cipher.decrypt(base64.b64decode(data)), AES.block_size)
|
||||
return decrypted.decode('utf-8')
|
||||
|
||||
def arr2json(self, v, data):
|
||||
remove_key = ['type', 'ip', 'player', 'From', 'From_Url']
|
||||
for key in remove_key:
|
||||
data.pop(key, None)
|
||||
if re.match(r'^https://(?:m|www)\.bilibili\.com/', v, re.I):
|
||||
if 'Referer' not in data:
|
||||
data['Referer'] = 'https://www.bilibili.com/'
|
||||
if 'User-Agent' not in data:
|
||||
data[
|
||||
'User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36'
|
||||
elif re.match(r'^https://(?:m|www)\.mgtv\.com/', v, re.I):
|
||||
if 'User-Agent' not in data or data.get(
|
||||
'User-Agent') == 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36':
|
||||
data['User-Agent'] = 'MGDS/Android/2.0.5'
|
||||
data['from'] = v
|
||||
return self.json_encode(data)
|
||||
|
||||
def rc4_decrypt(self, ciphertext_hex, key):
|
||||
key_bytes = key.encode('utf-8')
|
||||
ciphertext_bytes = binascii.unhexlify(ciphertext_hex)
|
||||
cipher = ARC4.new(key_bytes)
|
||||
plaintext_bytes = cipher.decrypt(ciphertext_bytes)
|
||||
return plaintext_bytes.decode('utf-8')
|
||||
|
||||
def domain(self, url):
|
||||
try:
|
||||
parsed_url = urlparse(url)
|
||||
domain = parsed_url.netloc
|
||||
if not domain:
|
||||
parsed_url = urlparse(f"http://{url}")
|
||||
domain = parsed_url.netloc
|
||||
if not domain: domain = ""
|
||||
if "." not in domain: return ""
|
||||
return domain
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def json_encode(self,arr):
|
||||
return json.dumps(arr, ensure_ascii=False, indent=2)
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
pass
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
pass
|
||||
|
||||
def detailContent(self, ids):
|
||||
pass
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,482 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import unpad
|
||||
from urllib.parse import unquote_plus
|
||||
import re,sys,time,json,base64,hashlib,urllib3
|
||||
from Crypto.Util.number import bytes_to_long,long_to_bytes
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class DecryptShijie:
|
||||
def __call__(self, str_data, z=False):
|
||||
return self.decrypt(str_data, z)
|
||||
|
||||
@staticmethod
|
||||
def decrypt(str_data, z=False):
|
||||
if not str_data:
|
||||
return None if z else str_data
|
||||
trim_str = str_data.strip()
|
||||
if trim_str.lower().startswith('<!doctype html') or trim_str.lower().startswith('<html'):
|
||||
return None if z else str_data
|
||||
try:
|
||||
json_obj = json.loads(str_data)
|
||||
if json_obj.get('code') != 1 or json_obj.get('msg') in ['退出成功', '热搜词库']:
|
||||
return None if z else str_data
|
||||
except Exception:
|
||||
pass
|
||||
clean_str = str_data.replace('"', '').replace('\\/', '/')
|
||||
prefix = ''
|
||||
if clean_str.startswith('lvdou+'):
|
||||
prefix = 'lvdou+'
|
||||
elif clean_str.startswith('lvDou+'):
|
||||
prefix = 'lvDou+'
|
||||
data_after_prefix = clean_str[len(prefix):] if prefix else clean_str
|
||||
if len(data_after_prefix) <= 32: return None if z else clean_str
|
||||
main_key = data_after_prefix[:32]
|
||||
if len(main_key) != 32 or not main_key.isalnum(): return None if z else clean_str
|
||||
encrypted_data = data_after_prefix[32:]
|
||||
return DecryptShijie.core_decrypt(main_key, encrypted_data, z, clean_str)
|
||||
|
||||
@staticmethod
|
||||
def core_decrypt(main_key, encrypted_data, z, origin_str):
|
||||
if not encrypted_data.strip() or not DecryptShijie.is_valid_base64(encrypted_data):
|
||||
return None if z else origin_str
|
||||
try:
|
||||
decrypted_raw = DecryptShijie.aes_decrypt(encrypted_data, main_key)
|
||||
if not decrypted_raw or not decrypted_raw.strip():
|
||||
return None if z else origin_str
|
||||
except Exception:
|
||||
return None if z else origin_str
|
||||
|
||||
try:
|
||||
return DecryptShijie.de_obfuscate(decrypted_raw)
|
||||
except Exception:
|
||||
return decrypted_raw
|
||||
|
||||
@staticmethod
|
||||
def aes_decrypt(encrypted_data, main_key):
|
||||
key_hash = hashlib.sha256((main_key + 'encryption_key_salt_2024').encode()).digest()
|
||||
iv_hash = hashlib.sha256((main_key + 'iv_salt_2024').encode()).digest()[:16]
|
||||
if len(key_hash) != 32 or len(iv_hash) != 16: raise ValueError
|
||||
cipher = AES.new(key_hash, AES.MODE_CBC, iv_hash)
|
||||
decrypted = unpad(cipher.decrypt(base64.b64decode(encrypted_data)), AES.block_size)
|
||||
return decrypted.decode()
|
||||
|
||||
@staticmethod
|
||||
def de_obfuscate(data):
|
||||
try:
|
||||
json_obj = json.loads(data)
|
||||
except Exception:
|
||||
return data
|
||||
if '_f' not in json_obj or not isinstance(json_obj['_f'], dict): return data
|
||||
field_map = json_obj['_f']
|
||||
required_fields = ['data', 'timestamp', 'checksum']
|
||||
if not all(field in field_map for field in required_fields): return data
|
||||
if not all(field in json_obj for field in [field_map['data'], field_map['timestamp'], field_map['checksum']]): return data
|
||||
encoded_data = json_obj[field_map['data']]
|
||||
if not DecryptShijie.is_valid_base64(encoded_data): return data
|
||||
try:
|
||||
decoded_data = base64.b64decode(encoded_data)
|
||||
except Exception:
|
||||
return data
|
||||
return decoded_data.decode()
|
||||
|
||||
@staticmethod
|
||||
def is_valid_base64(s):
|
||||
if not s or len(s) % 4 != 0:
|
||||
return False
|
||||
try:
|
||||
if re.match(r'^[A-Za-z0-9+/]*={0,2}$', s):
|
||||
base64.b64decode(s, validate=True)
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
class Spider(Spider):
|
||||
headers = {'User-Agent': 'okhttp/4.12.0'}
|
||||
|
||||
def localProxy(self, params):
|
||||
try:
|
||||
if params.get('type') == 'ryjx':
|
||||
data = self.ry_jx(params)
|
||||
elif params.get('type') == 'ryccjx':
|
||||
data = self.cc_jx(params)
|
||||
else:
|
||||
data = '请按要求传参'
|
||||
return [200, 'text/json;charset=utf-8', data]
|
||||
except Exception:
|
||||
data = self.json_encode({"code": "400", "success": "0", "msg": "处理出错"})
|
||||
return [400, 'text/json;charset=utf-8', data]
|
||||
|
||||
def ry_jx(self, params):
|
||||
error_msg = self.json_encode({"code": "400", "success": "0", "msg": "URL或API为空"})
|
||||
try:
|
||||
v = params['v']
|
||||
api = unquote_plus(params['api'])
|
||||
if not (v and api.startswith('http')):
|
||||
return error_msg
|
||||
except Exception:
|
||||
return error_msg
|
||||
if self.no_parse(params):
|
||||
return self.json_encode({"code": "-0", "success": "0", "msg": "链接不支持"})
|
||||
retry = int(params.get('retry', 0))
|
||||
timeout = params.get('timeout')
|
||||
msg,data2 = None,None
|
||||
for i in range(retry+1):
|
||||
msg,data2 = None,None
|
||||
try:
|
||||
data = self.fetch(f'{api}{v}', headers=self.headers, timeout=timeout or 20, verify=False).json()
|
||||
except Exception:
|
||||
msg = self.json_encode({"code": "-1", "success": "0", "msg": "请求或json解析失败"})
|
||||
continue
|
||||
dat_vurl = data.get('url', '')
|
||||
if not dat_vurl:
|
||||
return self.json_encode(data)
|
||||
video_url = self.common_decrypt(dat_vurl)
|
||||
if not self.is_valid_url(video_url) or video_url == v:
|
||||
msg = self.json_encode({"code": "-3", "success": "0", "msg": "解密或解析失败"})
|
||||
continue
|
||||
if re.match(r'^https?://.*/api/index/Video/\?.*&act=ZhiBaiQq[0-9]*\.m3u8$', video_url, flags=re.IGNORECASE) or video_url == 'https://kjjsaas-sh.oss-cn-shanghai.aliyuncs.com/u/3401405881/20240818-936952-fc31b16575e80a7562cdb1f81a39c6b0.mp4':
|
||||
msg = self.json_encode({"code": "-4", "success": "0", "msg": "解析结果为无效地址"})
|
||||
continue
|
||||
data['url'] = video_url
|
||||
if 'url' in data:
|
||||
return self.arr2json(v, data)
|
||||
data2 = self.json_encode(data)
|
||||
return msg or data2 or self.json_encode({"code": "-5", "success": "0", "msg": "解析失败"})
|
||||
|
||||
def cc_jx(self, params):
|
||||
v = params.get('v')
|
||||
if not v: return '链接为空'
|
||||
api = unquote_plus(params.get('api'))
|
||||
if not api: return 'api为空'
|
||||
if 'act=jxjm' not in api:
|
||||
return 'api不支持'
|
||||
appkey = params.get('appkey')
|
||||
if not appkey: return 'appkey为空'
|
||||
key = params.get('key')
|
||||
if not key: return 'key为空'
|
||||
timeout = params.get('timeout')
|
||||
if self.no_parse(params):
|
||||
return self.json_encode({"code": "-0", "success": "0", "msg": "链接不支持"})
|
||||
timestamp = str(int(time.time()))
|
||||
jx_api, line_id = api.split('id=',1)
|
||||
payload = {
|
||||
'url': v, 'id': str(line_id), 't': timestamp,
|
||||
'sign': hashlib.md5(f'url={v}&id={line_id}&t={timestamp}&{appkey}'.encode('utf-8')).hexdigest()
|
||||
}
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.12.11',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8'
|
||||
}
|
||||
try:
|
||||
response = self.post(f'{jx_api}url={v}&app=10000', data=payload, headers=headers, verify=False, timeout=timeout or 10).text
|
||||
except Exception as e:
|
||||
return self.json_encode({"code": "-1","success": "0","msg": "请求失败", "error": str(e)})
|
||||
|
||||
try:
|
||||
data = json.loads(self.cc_decrypt(response, key))
|
||||
except Exception as e:
|
||||
return self.json_encode({"code": "-1", "success": "0", "msg": "解密或解析失败", "error": str(e), 'raw_data': response})
|
||||
if 'url' in data:
|
||||
return self.arr2json(v, data)
|
||||
return self.json_encode(data)
|
||||
|
||||
def arr2json(self, v, data):
|
||||
remove_key = ['type', 'ip', 'player', 'From', 'From_Url']
|
||||
for key in remove_key:
|
||||
data.pop(key, None)
|
||||
if re.match(r'^https://(?:m|www)\.bilibili\.com/', v, re.I):
|
||||
if 'Referer' not in data:
|
||||
data['Referer'] = 'https://www.bilibili.com/'
|
||||
if 'User-Agent' not in data:
|
||||
data[
|
||||
'User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36'
|
||||
elif re.match(r'^https://(?:m|www)\.mgtv\.com/', v, re.I):
|
||||
if 'User-Agent' not in data or data.get(
|
||||
'User-Agent') == 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36':
|
||||
data['User-Agent'] = 'MGDS/Android/2.0.5'
|
||||
data['from'] = v
|
||||
return self.json_encode(data)
|
||||
|
||||
def no_parse(self, params):
|
||||
v = params.get('v')
|
||||
play_from = params.get('from')
|
||||
play_prefix = params.get('prefix')
|
||||
play_include = params.get('include')
|
||||
if not isinstance(v, str) or not v:
|
||||
return False
|
||||
if not any([play_from, play_prefix, play_include]): return False
|
||||
play_from_map = {
|
||||
'qq': 'qq.com',
|
||||
'qiyi': 'iqiyi.com',
|
||||
'youku': 'youku.com',
|
||||
'mgtv': 'mgtv.com',
|
||||
'bili': 'bilibili.com'
|
||||
}
|
||||
def split_param(param):
|
||||
if isinstance(param, str):
|
||||
return [p.strip() for p in param.split(',') if p.strip()]
|
||||
return []
|
||||
is_all = False
|
||||
if play_from == 'all':
|
||||
is_all = any(domain in v for domain in play_from_map.values())
|
||||
is_from = False
|
||||
if not is_all:
|
||||
from_parts = split_param(play_from)
|
||||
if from_parts: is_from = any((domain := play_from_map.get(key)) is not None and domain in v for key in from_parts)
|
||||
is_prefix = False
|
||||
prefix_parts = split_param(play_prefix)
|
||||
if prefix_parts:
|
||||
is_prefix = any(v.startswith(prefix) for prefix in prefix_parts)
|
||||
is_include = False
|
||||
include_parts = split_param(play_include)
|
||||
if include_parts:
|
||||
is_include = any(inc in v for inc in include_parts)
|
||||
final_result = is_all or is_from or is_prefix or is_include
|
||||
return not final_result
|
||||
|
||||
def common_decrypt(self, dat_vurl):
|
||||
if re.match(r'^https?://baidu\.con/', dat_vurl, re.I):
|
||||
if dat_vurl.count(':') > 2:
|
||||
result = self.ui9_decode(dat_vurl)
|
||||
if result: return result
|
||||
result = self.decode(dat_vurl)
|
||||
if result and self.is_valid_url(result): return result
|
||||
if result:
|
||||
result = self.ld_xor(result)
|
||||
if result: return result
|
||||
result = self.ld_xor(dat_vurl)
|
||||
if result: return result
|
||||
result = self.yd_xor(dat_vurl)
|
||||
if result: return result
|
||||
elif 'https://vod.baidu.com/' in dat_vurl:
|
||||
result = self.ld_xor(dat_vurl)
|
||||
if result: return result
|
||||
elif '6max.con' in dat_vurl:
|
||||
result = self.yd2(dat_vurl)
|
||||
if result: return result
|
||||
elif 'lvDou+' in dat_vurl:
|
||||
result = DecryptShijie()(dat_vurl)
|
||||
if result: return result
|
||||
elif dat_vurl.startswith('https://vod-parses.baidu.com/'):
|
||||
result = self.ui8(dat_vurl)
|
||||
if result: return result
|
||||
return None
|
||||
|
||||
def ui9_decode(self, input_str):
|
||||
try:
|
||||
cleaned_input = re.sub(r'https://baidu\.con/|lvDou\+', '', input_str)
|
||||
if cleaned_input.count(':') < 2: return None
|
||||
parts = cleaned_input.split(':', 3)
|
||||
if len(parts) < 4: return None
|
||||
_, key, iv, data_part = parts
|
||||
cipher = AES.new(key.encode(), AES.MODE_CBC, iv.encode())
|
||||
decrypted = unpad(cipher.decrypt(base64.b64decode(data_part)), AES.block_size)
|
||||
return decrypted.decode()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def decode(self, d):
|
||||
original_d = d
|
||||
try:
|
||||
while re.match(r'^https?://baidu\.con/', d, re.I):
|
||||
data = re.sub(r'^https?://baidu\.con/', '', d, flags=re.I)
|
||||
if len(data) < 16:
|
||||
return None
|
||||
key = data[:16].encode()
|
||||
encrypted_data = data[16:]
|
||||
cipher = AES.new(key, AES.MODE_CBC, key)
|
||||
decrypted = unpad(cipher.decrypt(base64.b64decode(encrypted_data)), AES.block_size)
|
||||
d = decrypted.decode()
|
||||
return d if d != original_d else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def ld_xor(self, d):
|
||||
if not d: return None
|
||||
original_d = d
|
||||
try:
|
||||
if 'https://baidu.con/' in d or 'https://vod.baidu.com/' in d or d.startswith('2423'):
|
||||
d = d.replace('https://baidu.con/', '').replace('https://vod.baidu.com/', '')
|
||||
if d.startswith("2423"): d = d[6:]
|
||||
substring = str(int(time.time()))[:8]
|
||||
decoded = base64.b64decode(d)
|
||||
sb = ''
|
||||
length = len(substring)
|
||||
length2 = len(decoded)
|
||||
for i in range(length2):
|
||||
sb += chr(decoded[i] ^ ord(substring[i % length]))
|
||||
d = base64.b64decode(sb).decode()
|
||||
return d if d != original_d else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def yd_xor(self, d):
|
||||
if not d or 'baidu.con/' not in d: return None
|
||||
try:
|
||||
parts = d.split('baidu.con/', 1)
|
||||
if len(parts) < 2: return None
|
||||
encoded_part = parts[1]
|
||||
decoded = base64.b64decode(encoded_part)
|
||||
split_parts = decoded.split(b'|', 1)
|
||||
if len(split_parts) != 2: return None
|
||||
str2, str3 = split_parts
|
||||
key = 'k3yM@$k2024'
|
||||
key_length = len(key)
|
||||
sb2 = ''
|
||||
for i in range(len(str2)):
|
||||
char = chr(str2[i] ^ ord(key[i % key_length]))
|
||||
sb2 += char
|
||||
sb3 = ''
|
||||
sb2_length = len(sb2)
|
||||
if sb2_length == 0: return None
|
||||
for i2 in range(len(str3)):
|
||||
pos1 = (i2 + 5) % sb2_length
|
||||
pos2 = i2 % sb2_length
|
||||
xor_value = (ord(sb2[pos1]) + ord(sb2[pos2])) % 256
|
||||
char = chr(str3[i2] ^ xor_value)
|
||||
sb3 += char
|
||||
return sb3
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def yd2(self, d):
|
||||
if not d or '6max.con' not in d: return None
|
||||
try:
|
||||
data = d.replace('https://6max.con/', '')
|
||||
if len(data) < 16: return None
|
||||
key = data[:16][::-1]
|
||||
encrypted_data = data[16:]
|
||||
cipher = AES.new(key.encode(), AES.MODE_CBC, key.encode())
|
||||
decrypted = unpad(cipher.decrypt(base64.b64decode(encrypted_data)), AES.block_size)
|
||||
return decrypted.decode()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def cc_decrypt(self, data, key):
|
||||
cipher = AES.new(key[:16].encode('utf-8'), AES.MODE_CBC, key[-16:].encode('utf-8'))
|
||||
decrypted = unpad(cipher.decrypt(base64.b64decode(data)), AES.block_size)
|
||||
return decrypted.decode('utf-8')
|
||||
|
||||
def ui8_key(self, data):
|
||||
def remove_padding(data_bytes):
|
||||
if len(data_bytes) > 10 and data_bytes[0:2] == b'\x00\x02':
|
||||
separator_index = -1
|
||||
for i in range(2, len(data_bytes)):
|
||||
if data_bytes[i] == 0x00:
|
||||
separator_index = i
|
||||
break
|
||||
if separator_index != -1 and separator_index >= 10:
|
||||
return data_bytes[separator_index + 1:]
|
||||
if len(data_bytes) > 10 and data_bytes[0:2] == b'\x00\x01':
|
||||
separator_index = data_bytes.find(b'\x00', 2)
|
||||
if separator_index != -1:
|
||||
return data_bytes[separator_index + 1:]
|
||||
leading_zeros = 0
|
||||
for i, byte in enumerate(data_bytes):
|
||||
if byte == 0x00:
|
||||
leading_zeros += 1
|
||||
else:
|
||||
break
|
||||
if 4 < leading_zeros < len(data_bytes) - 4:
|
||||
return data_bytes[leading_zeros:]
|
||||
return data_bytes
|
||||
|
||||
try:
|
||||
ciphertext = base64.b64decode(data)
|
||||
e = 65537
|
||||
key_size = 256
|
||||
n = 21079562076490917015219587480852512299132282846588972509613626067512096161389328018952967457235862130835563213420479656634103575049300097161679204081658154497272581125245352803881834652019229809107820308002912749854520391970510245623300240009991722378306164668314002824501199929254049911331868110702469664803250702178374575823923934377743766444104491434605938939890181211241005585699358654598872394352374955459144334648826707577459995980217087014729507004998084715621911009102107688390105352539795259098849457413742825469024104523030276553490595865329453646012415093918163886593950079624681378051572920908409755103897
|
||||
if len(ciphertext) > key_size: raise ValueError
|
||||
data_int = bytes_to_long(ciphertext)
|
||||
if data_int >= n: raise ValueError
|
||||
result_bytes = long_to_bytes(pow(data_int, e, n))
|
||||
if len(result_bytes) < key_size:
|
||||
padding_length = key_size - len(result_bytes)
|
||||
result_bytes = b'\x00' * padding_length + result_bytes
|
||||
cleaned_bytes = remove_padding(result_bytes)
|
||||
try:
|
||||
result = cleaned_bytes.decode('utf-8')
|
||||
return result
|
||||
except UnicodeDecodeError:
|
||||
printable_bytes = bytes(b for b in cleaned_bytes if 32 <= b <= 126)
|
||||
if printable_bytes:
|
||||
try:
|
||||
return printable_bytes.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
return None
|
||||
return None
|
||||
except Exception:
|
||||
raise ValueError
|
||||
|
||||
def ui8(self, data):
|
||||
try:
|
||||
if not data: return None
|
||||
processed = data.replace('https://vod-parses.baidu.com/', '').replace('"', '')
|
||||
parts = processed.split('81238', 1)
|
||||
if len(parts) < 2 or not parts[1]: return None
|
||||
aes_encrypted_part = parts[0]
|
||||
rsa_encrypted_part = parts[1]
|
||||
rsa_decrypted = self.ui8_key(rsa_encrypted_part)
|
||||
aes_key_iv = rsa_decrypted.split('|', 1)
|
||||
if len(aes_key_iv) < 2: return None
|
||||
aes_key = aes_key_iv[0].encode('utf-8')
|
||||
aes_iv = aes_key_iv[1].encode('utf-8')
|
||||
if len(aes_key) != 16 or len(aes_iv) != 16: raise ValueError
|
||||
aes_encrypted_bytes = base64.b64decode(aes_encrypted_part)
|
||||
cipher = AES.new(aes_key, AES.MODE_CBC, aes_iv)
|
||||
decrypted_padded = cipher.decrypt(aes_encrypted_bytes)
|
||||
decrypted = unpad(decrypted_padded, AES.block_size).decode('utf-8')
|
||||
return decrypted.strip('"')
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def is_valid_url(self, url):
|
||||
"""验证是否为有效的URL"""
|
||||
if not url or url == 'null':
|
||||
return False
|
||||
return re.match(r'https?://.*', url, re.I) is not None
|
||||
|
||||
def json_encode(self,arr):
|
||||
return json.dumps(arr, ensure_ascii=False, indent=2)
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
pass
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
pass
|
||||
|
||||
def detailContent(self, ids):
|
||||
pass
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,383 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from html.parser import HTMLParser
|
||||
from Crypto.Util.Padding import unpad
|
||||
import sys,time,uuid,hmac,json,random,base64,hashlib,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class _HTMLTextExtractor(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._text = []
|
||||
|
||||
def handle_data(self, data):
|
||||
self._text.append(data)
|
||||
|
||||
def get_text(self):
|
||||
return ''.join(self._text)
|
||||
|
||||
class Spider(Spider):
|
||||
channelList,vod_tabs,pic_groups,user,app,host,user_cache_key,targets = [], [], [],{},{},'','',{b'\xc2\xd7\xc0\xed'.decode('gbk'),b'\xba\xd6\xa7Q'.decode('big5'),b'\xb1\xa1\xa6\xe2'.decode('big5')}
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
if extend:
|
||||
ext = json.loads(extend)
|
||||
host, appid, package, channelid, version_name = (ext['host'],ext['appid'],ext['package'], ext['channelid'],ext['versionName'])
|
||||
if not(host.startswith('http') and package and channelid and version_name): return
|
||||
self.app = {'appid': appid,'package': package,'channelid': channelid,'version_name': version_name}
|
||||
self.host = host
|
||||
self.user_cache_key = f'{package}_user_e13cca1c0e630208'
|
||||
self.user = self.getCache(self.user_cache_key)
|
||||
if not(isinstance(self.user,dict)) or 'deviceid' not in self.user or 'deviceinfo' not in self.user:
|
||||
deviceid = self.random_android_id()
|
||||
uuid_ = str(uuid.uuid4())
|
||||
deviceinfo = base64.b64encode(f'{{"brand":"xiaomi","model":"xiaomi","type":"phone","resolutionX":"1600","resolutionY":"900","orientation":"1","osName":"android","osVersion":"15","osLevel":"32","abi":"arm64-v8a,armeabi-v7a,armeabi","androidId":"{deviceid}","uuid":"{uuid_}","gaid":""}}'.encode('utf-8')).decode('utf-8')
|
||||
self.user = {'deviceid': deviceid,'deviceinfo': deviceinfo}
|
||||
if not('devicecreatedat' in self.user and 'userid' in self.user and 'x-token' in self.user):
|
||||
self.login()
|
||||
path = '/v4/config/appInit.capi'
|
||||
params = {
|
||||
'os': 'android',
|
||||
'appId': self.app['appid'],
|
||||
'userLevel': '2'
|
||||
}
|
||||
response = self.fetch(f'{self.host}{path}', params=params, headers=self.headers(path, params), verify=False).content
|
||||
data = json.loads(self.decrypt(response))
|
||||
self.vod_tabs = data['vodTabs']
|
||||
self.pic_groups = data['groups']
|
||||
except Exception:
|
||||
self.host = ''
|
||||
|
||||
def login(self):
|
||||
if not self.host: return
|
||||
if 'userid' in self.user:
|
||||
self.user.pop('userid')
|
||||
if 'x-token' in self.user:
|
||||
self.user.pop('x-token')
|
||||
self.user['devicecreatedat'] = self.timestamp()
|
||||
path = '/user/anonymous'
|
||||
response = self.post(f'{self.host}{path}', headers=self.headers(path,'',{'apiver': 'v2'},'post'), verify=False).content
|
||||
data = self.decrypt(response)
|
||||
data = json.loads(data)['data']
|
||||
uses_id = data['id']
|
||||
token = data['token']
|
||||
if uses_id and token:
|
||||
self.user['userid'], self.user['x-token'] = uses_id, token
|
||||
if True or (data['teenMode']) != '1':
|
||||
path2 = '/user/teenMode'
|
||||
params2 = {'status': '1'}
|
||||
res = self.post(f'{self.host}{path2}', data=params2,headers=self.headers(path2, params2, {'apiver': 'v2'}, 'post'), verify=False).content
|
||||
res2 = json.loads(self.decrypt(res))['data']
|
||||
if str(res2['teenMode']) != '1':
|
||||
self.host = ''
|
||||
return
|
||||
self.setCache(self.user_cache_key,self.user)
|
||||
|
||||
def headers(self, path, params=None, header=None, method='get'):
|
||||
if header is None: header = {'x-cdn': '1'}
|
||||
sorted_params = ''
|
||||
if method == 'get' and params:
|
||||
sorted_keys = sorted(params.keys())
|
||||
sorted_params = '&'.join([f"{key}={value}" for key, value in zip(sorted_keys, [params[key] for key in sorted_keys])])
|
||||
timestamp = self.timestamp()
|
||||
deviceCreatedAt = ''
|
||||
devicecreatedat = self.user.get('devicecreatedat')
|
||||
if devicecreatedat:
|
||||
deviceCreatedAt = f"&deviceCreatedAt={self.user['devicecreatedat']}"
|
||||
sign = f"{method}|{path}|{sorted_params}|{timestamp}|appId={self.app['appid']}{deviceCreatedAt}&deviceId={self.user['deviceid']}&st=2"
|
||||
if 'userid' in self.user and self.user['userid']:
|
||||
sign += f"&userId={self.user['userid']}"
|
||||
sign += '|'
|
||||
return {
|
||||
'User-Agent': f"{self.app['package']}/{self.app['version_name']} Dalvik/2.1.0 (Linux; U; Android 12; SM-S9080 Build/3480d86.0)",
|
||||
**header,
|
||||
'appid': self.app['appid'],
|
||||
'os': 'android',
|
||||
'appversion': self.app['version_name'],
|
||||
'package': self.app['package'],
|
||||
'deviceid': '',
|
||||
'devicecreatedat': '',
|
||||
'deviceinfo': '',
|
||||
**self.user,
|
||||
'channelid': self.app['channelid'],
|
||||
'x-d-video': '1',
|
||||
'st': '2',
|
||||
'ts': timestamp,
|
||||
'sign': self.hmac_sha1(sign)
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
classes = []
|
||||
for i in self.vod_tabs:
|
||||
if i['text'] in {bytes(b ^ 90 for b in b'\xe0\x8c\xfd\x0b').decode('big5')}:continue
|
||||
if i['index'] != 1 and i['text'] in {'短剧','电影','剧集','动漫','综艺','纪录','综艺纪录'} and i['isAdult'] == 0:
|
||||
classes.append({'type_id': i['channelId'], 'type_name': i['text']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
path = '/v4/vod/home.capi'
|
||||
params = {
|
||||
'os': 'android',
|
||||
'appId': self.app['appid'],
|
||||
'userLevel': '2'
|
||||
}
|
||||
response = self.fetch(f'{self.host}{path}', params=params, headers=self.headers(path, params), verify=False).content
|
||||
data = self.decrypt(response)
|
||||
data = json.loads(data)['data']
|
||||
videos = []
|
||||
fl_channel_ids = {'5'}
|
||||
for i in data['blocks']:
|
||||
for k in self.targets:
|
||||
if k in data.get('topRightLabel','None'): return None
|
||||
if str(data.get('channelId','None')) in fl_channel_ids: return None
|
||||
if any(any(target in value for item in i.get('labels',[]) for value in item.values()) for target in self.targets): continue
|
||||
if 'header' in i and i.get('_vod','').startswith('section') and isinstance(i.get('data'),list):
|
||||
for j in i.get('data',[]):
|
||||
url = j['url']
|
||||
if url.startswith('vod/detail?vodId=') and str(j['id']) != 0 and str(j['channelId']) != 0 and '广告' not in j['topRightLabel'] and '广告' not in j['bottomLabel'] and 'browser' not in url:
|
||||
videos.append({
|
||||
'vod_id': j['id'],
|
||||
'vod_name': j['title'],
|
||||
'vod_pic': self.pic(j['imageGroup'],j['imagePath']),
|
||||
'vod_remarks': j['bottomLabel']
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
path = '/vod/channel/list.capi'
|
||||
params = {
|
||||
'channelId': tid,
|
||||
'sort': '3',
|
||||
'category': '',
|
||||
'area': '',
|
||||
'language': '',
|
||||
'year': '',
|
||||
'next': f'page={pg}',
|
||||
'os': 'android',
|
||||
'appId': self.app['appid'],
|
||||
'userLevel': '2'
|
||||
}
|
||||
response = self.fetch(f'{self.host}{path}',params=params, headers=self.headers(path, params), verify=False).content
|
||||
data = self.decrypt(response)
|
||||
data = json.loads(data)['data']
|
||||
videos = []
|
||||
for i in data['items']:
|
||||
url = i['url']
|
||||
if url.startswith('vod/detail?vodId=') and str(i['id']) != 0 and str(i['channelId']) != 0 and '广告' not in i['topRightLabel'] and '广告' not in i['bottomLabel'] and 'browser' not in url:
|
||||
videos.append({
|
||||
'vod_id': i['id'],
|
||||
'vod_name': i['title'],
|
||||
'vod_pic': self.pic(i['imageGroup'],i['imagePath']),
|
||||
'vod_remarks': i['bottomLabel'],
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
path = '/vod/search/query'
|
||||
params = {
|
||||
'channelId': '0',
|
||||
'k': key,
|
||||
'next': f'page={pg}',
|
||||
'os': 'android',
|
||||
'appId': self.app['appid'],
|
||||
'userChannel': self.app['channelid'],
|
||||
'userLevel': '2'
|
||||
}
|
||||
response = self.fetch(f'{self.host}{path}', params=params, headers=self.headers(path, params,{'apiver':'v2'}), verify=False).content
|
||||
data = self.decrypt(response)
|
||||
data = json.loads(data)['data']
|
||||
fl_channel_ids = {'5'}
|
||||
for k in data['tabs']:
|
||||
if 'channelId' in k and 'text' in k:
|
||||
if any(m in k['text'] for m in self.targets):
|
||||
fl_channel_ids.add(str(k['channelId']))
|
||||
videos = []
|
||||
for i in data['items']:
|
||||
url = i['url']
|
||||
if url.startswith('vod/detail?vodId=') and str(i['id']) != 0 and str(i['channelId']) != 0 and '广告' not in i['topRightLabel'] and '广告' not in i['bottomLabel'] and 'browser' not in url:
|
||||
try:
|
||||
if str(i['channelId']) in fl_channel_ids: continue
|
||||
if any(j in i['topRightLabel'] for j in self.targets): continue
|
||||
if any(any(target in value for item in i['labels'] for value in item.values()) for target in self.targets): continue
|
||||
except Exception:
|
||||
continue
|
||||
year = ''
|
||||
year_arr = i.get('year')
|
||||
if year_arr: year = year_arr.get('name',year_arr.get('id'))
|
||||
videos.append({
|
||||
'vod_id': url.replace('vod/detail?vodId=',''),
|
||||
'vod_name': self.html2text(i['title']),
|
||||
'vod_pic': self.pic(i['imageGroup'],i['imagePath']),
|
||||
'vod_remarks': i['bottomLabel'],
|
||||
'vod_year': year,
|
||||
'vod_content': i['summary']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.host: return None
|
||||
path = '/v2/vod/detail.capi'
|
||||
params = {
|
||||
'vodId': ids[0],
|
||||
'os': 'android',
|
||||
'appId': self.app['appid'],
|
||||
'userLevel': '2'
|
||||
}
|
||||
response = self.fetch(f'{self.host}{path}',params=params, headers=self.headers(path, params), verify=False).content
|
||||
data = self.decrypt(response)
|
||||
data = json.loads(data)['data']
|
||||
fl_channel_ids = {'5'}
|
||||
for i in self.vod_tabs:
|
||||
if str(i['isAdult']) == '1' or i['text'] in self.targets:
|
||||
fl_channel_ids.add(str(i['channelId']))
|
||||
for k in self.targets:
|
||||
if k in data['channelName'] or k in data['topRightLabel']: return None
|
||||
if str(data['channelId']) in fl_channel_ids: return None
|
||||
if any(any(target in value for item in data['labels'] for value in item.values()) for target in self.targets): return None
|
||||
show, play_urls = [], []
|
||||
i2 = 0
|
||||
for i in data['playSources']:
|
||||
if i2 > 1 or i2 > len(data['playSources']): break
|
||||
i2 += 1
|
||||
if i['total'] >= 1:
|
||||
if i['list']:
|
||||
urls = []
|
||||
for j in i['list']:
|
||||
urls.append(f"{j['title']}${j['playUrls'][0]['url']}")
|
||||
play_urls.append('#'.join(urls))
|
||||
else:
|
||||
play_urls.append(self.episode(ids[0], i['siteId'], i['episodeVodId']))
|
||||
show.append(f"{i['name']}\u2005({i['siteId']})")
|
||||
video = {
|
||||
'vod_id': ids[0],
|
||||
'vod_name': data['title'],
|
||||
'vod_pic': self.pic(data['imageGroup'],data['imagePath']),
|
||||
'vod_remarks': data['bottomLabel'],
|
||||
'vod_year': data.get('year', []).get('name'),
|
||||
'vod_area': self.labels2str(data['area']),
|
||||
'vod_actor': self.labels2str(data['actors']),
|
||||
'vod_director': self.labels2str(data['directors']),
|
||||
'vod_content': data['summary'],
|
||||
'vod_play_from': '$$$'.join(show),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': self.labels2str(data['labels'])
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
return { 'jx': 0, 'parse': 0, 'url': id, 'header': {'User-Agent': 'com.salmon.film.app.start.App/3.3.9 (Linux;Android 12) AndroidXMedia3/1.6.1','Accept-Encoding': 'gzip','Connection': 'Keep-Alive'}}
|
||||
|
||||
def episode(self,video_id,site_id,episode_id):
|
||||
path = '/v2/vod/episodes.capi'
|
||||
try:
|
||||
params = {
|
||||
'vodId': video_id,
|
||||
'siteId': site_id,
|
||||
'episodeVodId': episode_id,
|
||||
'os': 'android',
|
||||
'appId': self.app['appid'],
|
||||
'userLevel': '2'
|
||||
}
|
||||
response = self.fetch(f'{self.host}{path}', params=params, headers=self.headers(path, params), verify=False).content
|
||||
data = self.decrypt(response)
|
||||
data = json.loads(data)['data']
|
||||
urls = []
|
||||
for i in data:
|
||||
urls.append(f"{i['title']}${i['playUrls'][0]['url']}")
|
||||
play_urls = '#'.join(urls)
|
||||
except Exception:
|
||||
play_urls = ''
|
||||
return play_urls
|
||||
|
||||
def decrypt(self, data):
|
||||
cipher = AES.new('ayt5wy5afwmwrpb19k9s3psx3dymyd0n'.encode('utf-8'), AES.MODE_CBC, 'b3t069ijy7pirw0j'.encode('utf-8'))
|
||||
decrypted_bytes = cipher.decrypt(data)
|
||||
decrypted_bytes = unpad(decrypted_bytes, AES.block_size)
|
||||
return decrypted_bytes.decode('utf-8')
|
||||
|
||||
def hmac_sha1(self, data):
|
||||
hmac_obj = hmac.new('ksggsr4tp6difdo1c3im8fqd3g'.encode('utf-8'), data.encode('utf-8'), hashlib.sha1)
|
||||
return hmac_obj.hexdigest()
|
||||
|
||||
def labels2str(self,data):
|
||||
labels = []
|
||||
if isinstance(data,list):
|
||||
for i in data:
|
||||
label = i.get('name', i.get('id'))
|
||||
if label: labels.append(label)
|
||||
return ','.join(labels)
|
||||
|
||||
def pic(self,group,path):
|
||||
for i in self.pic_groups:
|
||||
if i['id'] == group:
|
||||
return f"{i['url'][0]['domain']}{path}"
|
||||
return f'https://vres.fnqty.com/vod1{path}'
|
||||
|
||||
def html2text(self,html_str):
|
||||
extractor = _HTMLTextExtractor()
|
||||
extractor.feed(html_str)
|
||||
return extractor.get_text()
|
||||
|
||||
def random_android_id(self):
|
||||
hex_chars = '0123456789abcdef'
|
||||
return ''.join(random.choice(hex_chars) for _ in range(16))
|
||||
|
||||
def timestamp(self):
|
||||
return str(int(time.time() * 1000))
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
sp = Spider()
|
||||
formatJo = sp.init() # 初始化
|
||||
# formatJo = sp.homeContent(False) # 筛选分类(首页 可选)
|
||||
formatJo = sp.homeVideoContent() # (首页 可选)
|
||||
# formatJo = sp.searchContent("仙逆",False,'2') # 搜索
|
||||
# formatJo = sp.categoryContent('1', '1', False, {}) # 分类
|
||||
# formatJo = sp.detailContent(['195930']) # 详情 异常:88051
|
||||
# formatJo = sp.playerContent("","https://107.148.194.118:21306/data3/jvods/hls/dhz/4/3361/24121023/3_195930_7779/1920/index.m3u8?appId=dsdy&sign=758726ec1dd41a394fe3b5b65ab504c1×tamp=1760847330&ref=1",{}) # 播放
|
||||
# formatJo = sp.localProxy({"":""}) # 代理
|
||||
print(formatJo)
|
||||
'''
|
||||
{
|
||||
"appid": "hkan",
|
||||
"channelid": "c300000",
|
||||
"versionName": "3.3.9",
|
||||
"host": "https://vcache.nxcse.cn",
|
||||
"package": "com.hkanC300000V250930.T120445"
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
'''
|
||||
{
|
||||
"appid": "dsdy",
|
||||
"channelid": "c100000",
|
||||
"versionName": "3.3.9",
|
||||
"host": "https://vcache.nxcse.cn",
|
||||
"package": "com.dsdyC100000V250916.T210207"
|
||||
}
|
||||
'''
|
||||
@@ -0,0 +1,236 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源收集自互联网,仅供个人学习与测试用途。
|
||||
# 严禁用于商业目的,请于下载后 24 小时内删除。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import unpad, pad
|
||||
import re,sys,time,json,base64,urllib3,hashlib,binascii
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.headers = {
|
||||
'User-Agent': "Dart/2.10 (dart:io)",
|
||||
'Accept-Encoding': "gzip",
|
||||
}
|
||||
self.host = ''
|
||||
self.key = ''
|
||||
self.iv = ''
|
||||
self.ckkey = ''
|
||||
self.ckiv = ''
|
||||
self.config = {}
|
||||
|
||||
def init(self, extend=""):
|
||||
try:
|
||||
ext = json.loads(extend.strip())
|
||||
host = ext['host']
|
||||
self.key = ext['key']
|
||||
self.iv = ext['iv']
|
||||
self.ckkey = ext.get('ckkey','ygcnbcrvaervztmw')
|
||||
self.ckiv = ext.get('ckiv','1212164105143708')
|
||||
if not host.startswith('http'):
|
||||
return None
|
||||
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?/?$', host):
|
||||
host = self.fetch(host,headers=self.headers,verify=False).text.strip()
|
||||
self.host = host.rstrip('/')
|
||||
if len(self.key) != 16 or len(self.iv) != 16:
|
||||
self.host = ''
|
||||
return None
|
||||
payload = {
|
||||
"sign": hashlib.md5(f'{self.key}{self.iv}'.encode('utf-8')).hexdigest(),
|
||||
"ck": self.ck_encrypt(f'{self.host}##5483##{int(time.time() * 1000)}##ckzmbc')
|
||||
}
|
||||
headers = self.headers.copy()
|
||||
headers.update({'Content-Type': "application/json",'content-type': "application/json; charset=utf-8"})
|
||||
response = self.post(f'{self.host}/get_config', data=json.dumps(payload), headers=headers, verify=False).text
|
||||
if response:
|
||||
token = self.sk_decrypt(response)
|
||||
if token:
|
||||
self.headers['authorization'] = 'Bearer ' + token
|
||||
config_ = self.fetch(f'{self.host}/app/config', headers=self.headers, verify=False).text
|
||||
config = self.sk_decrypt(config_)
|
||||
data = json.loads(config)
|
||||
direct_link = data.get('direct_link')
|
||||
direct_json_link = data.get('direct_json_link')
|
||||
app_trans_name = data.get('app_trans_name')
|
||||
if direct_link and isinstance(direct_link, str):
|
||||
if '|' in direct_link:
|
||||
direct_link_list = direct_link.split('|')
|
||||
else:
|
||||
direct_link_list = [direct_link]
|
||||
self.config['direct_link'] = direct_link_list
|
||||
if direct_json_link and isinstance(direct_json_link,str):
|
||||
if '|' in direct_json_link:
|
||||
direct_json_link_list = direct_json_link.split('|')
|
||||
else:
|
||||
direct_json_link_list = [direct_json_link]
|
||||
self.config['direct_json_link'] = direct_json_link_list
|
||||
if app_trans_name and isinstance(app_trans_name,list):
|
||||
self.config['app_trans_name'] = app_trans_name
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
try:
|
||||
response = self.fetch(f'{self.host}/sk-api/type/list', headers=self.headers, verify=False).text
|
||||
data_ = self.sk_decrypt(response)
|
||||
data = json.loads(data_)['data']
|
||||
classes = []
|
||||
for i in data:
|
||||
if isinstance(i, dict):
|
||||
classes.append({'type_id': i['type_id'], 'type_name': i['type_name']})
|
||||
except Exception:
|
||||
return {'class': []}
|
||||
filters = {}
|
||||
def fetch_filter(tid):
|
||||
try:
|
||||
url = f'{self.host}/sk-api/type/alltypeextend?typeId={tid}'
|
||||
resp = self.fetch(url, headers=self.headers, verify=False).text
|
||||
decrypted = self.sk_decrypt(resp)
|
||||
f_data = json.loads(decrypted)
|
||||
if f_data.get('code') == 200 and f_data.get('data'):
|
||||
data_obj = f_data['data']
|
||||
type_filters = []
|
||||
mappings = {
|
||||
'class': ('extendtype', '类型'),
|
||||
'area': ('area', '地区'),
|
||||
'lang': ('lang', '语言'),
|
||||
'year': ('year', '年份')
|
||||
}
|
||||
for api_key, val in mappings.items():
|
||||
filter_key, filter_name = val
|
||||
if data_obj.get(api_key):
|
||||
options = [x.strip() for x in data_obj[api_key].split(',') if x.strip()]
|
||||
if options:
|
||||
value_list = [{'n': '全部', 'v': ''}]
|
||||
for opt in options:
|
||||
value_list.append({'n': opt, 'v': opt})
|
||||
type_filters.append({'key': filter_key, 'name': filter_name, 'init': '', 'value': value_list})
|
||||
type_filters.append({
|
||||
'key': 'sort',
|
||||
'name': '排序',
|
||||
'init': 'updateTime',
|
||||
'value': [
|
||||
{'n': '最新', 'v': 'updateTime'},
|
||||
{'n': '人气', 'v': 'hot'},
|
||||
{'n': '评分', 'v': 'score'}
|
||||
]
|
||||
})
|
||||
return str(tid), type_filters
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
with ThreadPoolExecutor(max_workers=5) as executor:
|
||||
futures = [executor.submit(fetch_filter, c['type_id']) for c in classes]
|
||||
for future in as_completed(futures):
|
||||
res = future.result()
|
||||
if res:
|
||||
filters[res[0]] = res[1]
|
||||
return {'class': classes, 'filters': filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f'{self.host}/sk-api/vod/list?page=1&limit=12&type=randomlikeindex&area=&lang=&year=&mtype=',headers=self.headers, verify=False).text
|
||||
data_ = self.sk_decrypt(response)
|
||||
data = json.loads(data_)['data']
|
||||
return {'list': data}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
sort = extend.get('sort', 'updateTime')
|
||||
area = extend.get('area', '')
|
||||
lang = extend.get('lang', '')
|
||||
year = extend.get('year', '')
|
||||
extendtype = extend.get('extendtype', '')
|
||||
response = self.fetch(f"{self.host}/sk-api/vod/list?typeId={tid}&page={pg}&limit=18&type={sort}&area={area}&lang={lang}&year={year}&mtype=&extendtype={extendtype}", headers=self.headers, verify=False).text
|
||||
data_ = self.sk_decrypt(response)
|
||||
data = json.loads(data_)['data']
|
||||
return {'list': data, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
response = self.fetch(f"{self.host}/sk-api/search/pages?keyword={key}&page={pg}&limit=10&typeId=-1", headers=self.headers, verify=False).text
|
||||
data_ = self.sk_decrypt(response)
|
||||
data = json.loads(data_)['data']
|
||||
return {'list': data, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f"{self.host}/sk-api/vod/one?vodId={ids[0]}",headers=self.headers, verify=False).text
|
||||
data_ = self.sk_decrypt(response)
|
||||
data = json.loads(data_)['data']
|
||||
return {'list': [data]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
jx,url,direct_link = 0,'',0
|
||||
config = self.config
|
||||
direct_json_links = config.get('direct_json_link',[])
|
||||
direct_links = config.get('direct_link',[])
|
||||
direct_json = 0
|
||||
for i in direct_json_links:
|
||||
if i in id:
|
||||
direct_json = 1
|
||||
for i in direct_links:
|
||||
if i in id and i.startswith('http'):
|
||||
direct_link = 1
|
||||
if direct_json or not direct_link or not(id.startswith('http')):
|
||||
try:
|
||||
response = self.fetch(f'{self.host}/sk-api/vod/skjson?url={id}&skjsonindex=0', headers=self.headers, verify=False).text
|
||||
data_ = self.sk_decrypt(response)
|
||||
data = json.loads(data_)['data']
|
||||
play_url = data.get('url')
|
||||
if play_url.startswith('http'):
|
||||
url = play_url
|
||||
except Exception:
|
||||
pass
|
||||
if not url:
|
||||
url = id
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', id):
|
||||
jx = 1
|
||||
return { 'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}}
|
||||
|
||||
def ck_encrypt(self, str):
|
||||
key = self.ckkey.encode('utf-8')
|
||||
iv = self.ckiv.encode('utf-8')
|
||||
b64_1 = base64.b64encode(str.encode("utf-8"))
|
||||
b64_2 = base64.b64encode(b64_1)
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
padded_data = pad(b64_2, AES.block_size)
|
||||
encrypted_bytes = cipher.encrypt(padded_data)
|
||||
hex_encoded = encrypted_bytes.hex()
|
||||
hex_bytes = hex_encoded.encode('utf-8')
|
||||
final_ciphertext = base64.b64encode(hex_bytes).decode('utf-8')
|
||||
return final_ciphertext
|
||||
|
||||
def sk_decrypt(self,data):
|
||||
prefix = "FROMSKZZJM"
|
||||
if data.startswith('FROMSKZZJM'):
|
||||
try:
|
||||
encrypted_hex = data[len(prefix):]
|
||||
key = self.key.encode('utf-8')
|
||||
iv = self.iv.encode('utf-8')
|
||||
encrypted_data = binascii.unhexlify(encrypted_hex)
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
|
||||
return decrypted_data.decode('utf-8')
|
||||
except Exception:
|
||||
return None
|
||||
else:
|
||||
return data
|
||||
|
||||
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,366 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from urllib.parse import quote, unquote, urljoin, urlparse
|
||||
import re, sys, time, json, random, base64, hashlib, urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,play_header,host,play_domain,encrypt_domain,proxyurl = {
|
||||
'User-Agent': "okhttp/4.9.0",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip",
|
||||
'log-header': "I am the log request header.",
|
||||
'app_id': "",
|
||||
'channel_code': "",
|
||||
'cur_time': "",
|
||||
'device_id': "",
|
||||
'mob_mfr': "huawei",
|
||||
'mobmodel': "DCO-AL00",
|
||||
'package_name': "",
|
||||
'sign': "",
|
||||
'sys_platform': "2",
|
||||
'sysrelease': "12",
|
||||
'token': "",
|
||||
'version': "40000"
|
||||
},{'User-Agent': 'Mozi'},'','','',''
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
ext = json.loads(extend.strip())
|
||||
host = ext.get('host').rstrip('/')
|
||||
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?/?$', host): return None
|
||||
appid = ext.get('app_id').strip()
|
||||
deviceid = ext.get('deviceid').strip()
|
||||
version_code = ext.get('versionCode').strip()
|
||||
channel = ext.get('UMENG_CHANNEL').strip()
|
||||
package_name = ext.get('package_name').strip()
|
||||
self.encrypt_domain = ext.get('EncryptDomain').strip()
|
||||
if not(appid and deviceid and version_code and channel and package_name and self.encrypt_domain): return None
|
||||
if not self.is_valid_android_id(deviceid): return None
|
||||
self.headers['app_id'] = appid
|
||||
self.headers['channel_code'] = channel
|
||||
self.headers['device_id'] = deviceid
|
||||
self.headers['version'] = version_code
|
||||
self.headers['package_name'] = package_name
|
||||
try:
|
||||
self.proxyurl = f'{self.getProxyUrl(True)}&type=xiaocao'
|
||||
except Exception:
|
||||
self.proxyurl = 'http://127.0.0.1:9978/proxy?do=py&type=xiaocao'
|
||||
self.login(host)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
headers['cur_time'] = self.timestamp()
|
||||
headers['sign'] = self.sign(headers['cur_time'])
|
||||
response = self.post(f'{self.host}/api/type/get_list', headers=headers, verify=False).text
|
||||
data = json.loads(self.decrypt(response))
|
||||
classes = [{'type_id': i['id'], 'type_name': i['name']} for i in data.get('result', [])]
|
||||
if not any(i['type_id'] == 31 for i in classes):
|
||||
classes.append({'type_id': 31, 'type_name': '短剧'})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
headers['cur_time'] = self.timestamp()
|
||||
headers['sign'] = self.sign(headers['cur_time'])
|
||||
try:
|
||||
response = self.post(f'{self.host}/api/channel/get_list', headers=headers,verify=False).text
|
||||
data = self.decrypt(response)
|
||||
result = json.loads(data)['result']
|
||||
classes, videos, recommend_id = [], [], ''
|
||||
for i in result:
|
||||
if isinstance(i, dict):
|
||||
if i.get('channel_name') == '推荐':
|
||||
recommend_id = i.get('id')
|
||||
break
|
||||
headers['cur_time'] = self.timestamp()
|
||||
headers['sign'] = self.sign(headers['cur_time'])
|
||||
except Exception:
|
||||
recommend_id = '324'
|
||||
payload2 = {
|
||||
'psize': "100",
|
||||
'channel_id': recommend_id,
|
||||
'pn': "1"
|
||||
}
|
||||
response2 = self.post(f'{self.host}/api/channel/get_info',data=payload2, headers=headers, verify=False).text
|
||||
data2 = self.decrypt(response2)
|
||||
result2 = json.loads(data2)['result']
|
||||
for item in result2:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
for block in item.get('block_list', []):
|
||||
if not isinstance(block, dict):
|
||||
continue
|
||||
for vod in block.get('vod_list', []):
|
||||
if vod['type_pid'] == 1:
|
||||
remark = f"评分:{vod['vod_douban_score']}"
|
||||
elif vod['type_pid'] != 2 and vod['type_pid'] != 4 and vod['type_pid'] != 31:
|
||||
remark = vod['collection_new_title']
|
||||
if vod['type_pid'] == 3:
|
||||
remark = self.remove_invalid_episode(remark)
|
||||
else:
|
||||
remark = f"{vod['vod_serial']}集全" if vod.get('vod_isend') == 1 else f"更新至{vod['vod_serial']}集"
|
||||
videos.append({
|
||||
'vod_id': vod['id'],
|
||||
'vod_name': vod['vod_name'],
|
||||
'vod_pic': vod['vod_pic'],
|
||||
'vod_remarks': remark,
|
||||
'vod_year': vod['vod_year']
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
headers['cur_time'] = self.timestamp()
|
||||
headers['sign'] = self.sign(headers['cur_time'])
|
||||
payload = {
|
||||
'area': "",
|
||||
'year': "",
|
||||
'type_id': tid,
|
||||
'sort': "",
|
||||
'type': "",
|
||||
'pn': pg
|
||||
}
|
||||
response = self.post(f'{self.host}/api/search/screen', data=payload, headers=headers, verify=False).text
|
||||
data = json.loads(self.decrypt(response))
|
||||
videos = []
|
||||
for i in data.get('result',[]):
|
||||
if i['type_pid'] == 1:
|
||||
remark = f"评分:{i['vod_douban_score']}"
|
||||
elif i['type_pid'] != 2 and i['type_pid'] != 4 and i['type_pid'] != 31:
|
||||
remark = i['collection_new_title']
|
||||
if i['type_pid'] == 3:
|
||||
remark = self.remove_invalid_episode(remark)
|
||||
else:
|
||||
remark = f"{i['vod_serial']}集全" if i.get('vod_isend') == 1 else f"更新至{i['vod_serial']}集"
|
||||
videos.append({
|
||||
'vod_id': i['id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': remark,
|
||||
'vod_year': i['vod_year']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def remove_invalid_episode(self, content):
|
||||
pattern = r'第(.*?)集'
|
||||
def replace_match(match):
|
||||
middle_content = match.group(1)
|
||||
return middle_content
|
||||
processed_content = re.sub(pattern, replace_match, content)
|
||||
return processed_content
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
headers['cur_time'] = self.timestamp()
|
||||
headers['sign'] = self.sign(headers['cur_time'])
|
||||
payload = {'kw': key,'pn': pg}
|
||||
response = self.post(f'{self.host}/api/search/result', data=payload, headers=headers, verify=False).text
|
||||
data = json.loads(self.decrypt(response))
|
||||
videos = []
|
||||
for i in data.get('result',[]):
|
||||
if i['type_pid'] == 1:
|
||||
remark = f"评分:{i['vod_douban_score']}"
|
||||
elif i['type_pid'] != 2 and i['type_pid'] != 4 and i['type_pid'] != 31:
|
||||
remark = i['collection_new_title']
|
||||
if i['type_pid'] == 3:
|
||||
remark = self.remove_invalid_episode(remark)
|
||||
else:
|
||||
vod_serial = i['vod_serial']
|
||||
remark = f"{i['vod_tag']},短剧" if i['type_pid'] == 31 else f"{vod_serial}集全" if i.get('vod_isend') == 1 else f"更新至{vod_serial}集"
|
||||
videos.append({
|
||||
'vod_id': i['id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': remark,
|
||||
'vod_year': i['vod_year']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
headers['cur_time'] = self.timestamp()
|
||||
headers['sign'] = self.sign(headers['cur_time'])
|
||||
payload = {
|
||||
'sig': '',
|
||||
'nc_token': '',
|
||||
'code': '',
|
||||
'phone': '',
|
||||
'vod_id': ids[0],
|
||||
'session_id': ''
|
||||
}
|
||||
response = self.post(f'{self.host}/api/video/result', data=payload, headers=headers, verify=False).text
|
||||
data = self.decrypt(response)
|
||||
result = json.loads(data)['result']
|
||||
play_urls = []
|
||||
for i in result['vod_collection']:
|
||||
if isinstance(i, dict):
|
||||
play_urls.append(f"{i['title']}${i['vod_id']}@{i['id']}@{i['cur_time']}@{i['vod_token']}")
|
||||
video = {
|
||||
'vod_id': result['id'],
|
||||
'vod_name': result['vod_name'],
|
||||
'vod_pic': result['vod_pic'],
|
||||
'vod_remarks': result['remarks'],
|
||||
'vod_year': result['vod_year'],
|
||||
'vod_area': result['vod_area'],
|
||||
'vod_actor': result['vod_actor'],
|
||||
'vod_director': result['vod_director'],
|
||||
'vod_content': result['vod_blurb'],
|
||||
'vod_play_from': '小草',
|
||||
'vod_play_url': '#'.join(play_urls),
|
||||
'type_name': result['vod_tag']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
vod_id, collection_id, cur_time, vod_token = id.split('@', 3)
|
||||
headers = self.headers.copy()
|
||||
headers['cur_time'] = self.timestamp()
|
||||
headers['sign'] = self.sign(headers['cur_time'])
|
||||
payload = {
|
||||
'collection_id': collection_id,
|
||||
'sig': "",
|
||||
'nc_token': "",
|
||||
'code': "",
|
||||
'phone': "",
|
||||
'vod_id': vod_id,
|
||||
'session_id': "",
|
||||
'vod_token': vod_token,
|
||||
'cur_time': cur_time
|
||||
}
|
||||
response = self.post(f'{self.host}/api/video/collection', data=payload, headers=headers, verify=False).text
|
||||
data = self.decrypt(response)
|
||||
result = json.loads(data)['result']
|
||||
try:
|
||||
ck = base64.b64decode(result['ck']).decode('utf-8')
|
||||
except Exception:
|
||||
ck = result['ck']
|
||||
vod_url = result['vod_url']
|
||||
check_url = result.get('check_page_url')
|
||||
url = check_url or self.proxyurl + '&url=' + quote(f"{vod_url}?{ck}", safe='')
|
||||
return {'jx': '0', 'parse': '0', 'url': url, 'header': self.play_header}
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "xiaocao":
|
||||
return self.xiaocao_m3u8_proxy(params)
|
||||
return None
|
||||
|
||||
def login(self, host):
|
||||
if self.headers['token'] and self.host: return
|
||||
headers = self.headers.copy()
|
||||
headers['cur_time'] = self.timestamp()
|
||||
headers['sign'] = self.sign(headers['cur_time'])
|
||||
payload = {
|
||||
'invited_by': '',
|
||||
'ua': 'Mozilla/5.0 (Linux; Android 12; 23116PN5BC Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36',
|
||||
'is_install': '0'
|
||||
}
|
||||
response = self.post(f'{host}/api/public/init', data=payload, headers=headers, verify=False).text
|
||||
data = self.decrypt(response)
|
||||
result = json.loads(data)['result']
|
||||
self.headers['token'] = result['user_info']['token']
|
||||
self.host = result['sys_conf']['api_url'].rstrip('/')
|
||||
if not (self.headers['token'] or self.play_domain or self.play_domain):
|
||||
self.host = ''
|
||||
|
||||
def sign(self, cur_time):
|
||||
return self.md5(f"zD9[bM4~sF4~uY2){self.headers['device_id']}{cur_time}").upper()
|
||||
|
||||
def md5(self, str):
|
||||
md5_hash = hashlib.md5()
|
||||
md5_hash.update(str.encode('utf-8'))
|
||||
return md5_hash.hexdigest()
|
||||
|
||||
def xiaocao_m3u8_proxy(self, params):
|
||||
url = unquote(params['url'])
|
||||
if params.get('format') == "ts":
|
||||
data = {"Location": url + self.hls_sign(url), "Content-Length": "0"}
|
||||
return [302, "text/html; charset=utf-8", None, data]
|
||||
else:
|
||||
data = self.xiaocao_modify_m3u8(url)
|
||||
return [200, "application/vnd.apple.mpegurl", data]
|
||||
|
||||
def xiaocao_modify_m3u8(self, raw_url):
|
||||
ck = raw_url.split('?')[1]
|
||||
m3u8_url = raw_url + self.hls_sign(raw_url)
|
||||
m3u8_content = self.fetch(m3u8_url, headers=self.play_header, verify=False).text
|
||||
content = m3u8_content if m3u8_content is not None else self.m3u8_content
|
||||
if content is None: raise ValueError("M3U8为空")
|
||||
parsed = urlparse(raw_url)
|
||||
base = f"{parsed.scheme}://{parsed.netloc}{parsed.path.rsplit('/', 1)[0]}/"
|
||||
output_lines = []
|
||||
for line in content.splitlines():
|
||||
stripped_line = line.strip()
|
||||
if stripped_line and not stripped_line.startswith('#'):
|
||||
if not stripped_line.startswith(('http://', 'https://')):
|
||||
full_url = urljoin(base, stripped_line)
|
||||
else:
|
||||
full_url = stripped_line
|
||||
signed_url = self.proxyurl + f'&format=ts&url=' + quote(full_url + '&' + ck,safe='')
|
||||
output_lines.append(signed_url)
|
||||
else:
|
||||
output_lines.append(line)
|
||||
return '\n'.join(output_lines)
|
||||
|
||||
def decrypt(self, base64_ciphertext):
|
||||
try:
|
||||
ciphertext = base64.b64decode(base64_ciphertext)
|
||||
cipher = AES.new('aZ9$kU5%qI7=yC2=zH2#gM0@pX7^wF3a'.encode('utf-8'), AES.MODE_CBC, 'hY2&tN3]kF7,dL7='.encode('utf-8'))
|
||||
plaintext_bytes = cipher.decrypt(ciphertext)
|
||||
padding_len = plaintext_bytes[-1]
|
||||
plaintext_bytes = plaintext_bytes[:-padding_len]
|
||||
return plaintext_bytes.decode('utf-8')
|
||||
except Exception as e:
|
||||
raise Exception(f"解密失败: {str(e)}")
|
||||
|
||||
def get_url_path(self, url):
|
||||
parsed_url = urlparse(url)
|
||||
return parsed_url.path
|
||||
|
||||
def hls_sign(self, url):
|
||||
hex_time = self.hex_time()
|
||||
if '?' in url: url = url.split('?')[0]
|
||||
data = self.encrypt_domain + self.get_url_path(url) + hex_time
|
||||
return f"&wsSecret={self.md5(data)}&wsTime={hex_time}"
|
||||
|
||||
def is_valid_android_id(self, android_id):
|
||||
if not isinstance(android_id, str):
|
||||
return False
|
||||
pattern = r'^[0-9a-f]{16}$'
|
||||
return bool(re.fullmatch(pattern, android_id))
|
||||
|
||||
def random_device_id(self):
|
||||
chars = '0123456789abcdef'
|
||||
android_id = ''.join(random.choice(chars) for _ in range(16))
|
||||
return android_id
|
||||
|
||||
def timestamp(self):
|
||||
return str(int(time.time() * 1000))
|
||||
|
||||
def hex_time(self):
|
||||
return hex(int(time.time()))[2:]
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
import re,sys,uuid,json,hashlib,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,nextVal,host,appid,appkey,udid,bundlerId,source,version,versionCode = {
|
||||
'User-Agent': "Dart/3.1 (dart:io)",
|
||||
'Accept-Encoding': "gzip",
|
||||
'content-type': "application/json; charset=utf-8"
|
||||
},{'search':{'key':'','value':''},'category':{}},'','','','','','','',''
|
||||
|
||||
def init(self, extend=""):
|
||||
ext = json.loads(extend)
|
||||
hosts,appid,appkey,udid,bundlerId,source,version,versionCode = ext['host'],ext['appId'],ext['appkey'],ext['udid'],ext['bundlerId'],ext['source'],ext['version'],ext['versionCode']
|
||||
if not(hosts and appid and appkey and udid and bundlerId and source and version and versionCode): return
|
||||
self.appid = appid
|
||||
self.appkey = appkey
|
||||
self.udid = udid
|
||||
self.bundlerId = bundlerId
|
||||
self.source = source
|
||||
self.version = version
|
||||
self.versionCode = versionCode
|
||||
hosts_list = hosts.split(',')
|
||||
for i in hosts_list:
|
||||
if re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?/?$',i):
|
||||
self.host = i
|
||||
else:
|
||||
try:
|
||||
response = self.fetch(i, headers=self.headers,verify=False).json()
|
||||
for j in response:
|
||||
try:
|
||||
print(j)
|
||||
if j.startswith('http'):
|
||||
self.host = j
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if self.host: break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = self.post(f'{self.host}/v2/api/home/header', data=self.payload(), headers=self.headers, verify=False).json()
|
||||
classes = []
|
||||
for i in response['data']['channeList']:
|
||||
if isinstance(i,dict):
|
||||
classes.append({'type_id': i['channelId'], 'type_name': i['channelName']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
response = self.post(f'{self.host}/v2/api/home/body', data=self.payload(), headers=self.headers, verify=False).json()
|
||||
topic_list = response['data']['vodTopicList']
|
||||
videos = []
|
||||
for i in topic_list:
|
||||
if isinstance(i,dict):
|
||||
for j in i['vodList']:
|
||||
try:
|
||||
year = j['flags'].split(' / ')[0].strip()
|
||||
except Exception:
|
||||
year = None
|
||||
videos.append({
|
||||
'vod_id': j['vodId'],
|
||||
'vod_name': j['vodName'],
|
||||
'vod_pic': j['coverImg'],
|
||||
'vod_remarks': j['remark'] or f"评分:{j['score']}",
|
||||
'vod_year': year
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filters, extend):
|
||||
if not self.host: return None
|
||||
if tid.startswith('actor@'):
|
||||
if pg != '1': return None
|
||||
worker_id = tid.split('actor@',1)[1]
|
||||
try:
|
||||
worker_id = int(worker_id)
|
||||
except Exception:
|
||||
worker_id = worker_id
|
||||
payload = self.payload({'vodWorkerId':worker_id})
|
||||
path = 'vodWorker/detail'
|
||||
else:
|
||||
cache_nextVal_ = self.nextVal['category'].get(tid)
|
||||
if pg != '1' and not cache_nextVal_: return None
|
||||
if cache_nextVal_:
|
||||
cache_nextVal = cache_nextVal_
|
||||
else:
|
||||
cache_nextVal = ''
|
||||
payload = self.payload({
|
||||
'nextCount': 18,
|
||||
'nextVal': cache_nextVal,
|
||||
'queryValueJson': '[{"filerName":"channelId","filerValue":'+ tid +'}]',
|
||||
'sortType': '',
|
||||
})
|
||||
path = 'search/queryNow'
|
||||
response = self.post(f'{self.host}/v1/api/{path}', data=payload, headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
if data.get('hasNext') == 1:
|
||||
nextVal = data['nextVal']
|
||||
if nextVal:
|
||||
self.nextVal['category'][tid] = nextVal
|
||||
else:
|
||||
self.nextVal['category'][tid] = ''
|
||||
videos =[]
|
||||
for i in data.get('items',data.get('vodList',[])):
|
||||
try:
|
||||
year = i['flags'].split(' / ')[0].strip()
|
||||
except Exception:
|
||||
year = None
|
||||
videos.append({
|
||||
'vod_id': i['vodId'],
|
||||
'vod_name': i['vodName'],
|
||||
'vod_pic': i['coverImg'],
|
||||
'vod_remarks': i['remark'] or f"评分:{i['score']}",
|
||||
'vod_year': year,
|
||||
'vod_content': i['intro']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
if not self.host: return None
|
||||
if pg != '1': return None
|
||||
self.nextVal['search']['value'] = ''
|
||||
current_key = self.nextVal['search']['key']
|
||||
current_value = self.nextVal['search']['value']
|
||||
if current_key == key:
|
||||
if current_value == '' and str(pg) != '1':
|
||||
return None
|
||||
cache_nextVal = current_value or ''
|
||||
else:
|
||||
self.nextVal['search']['key'] = key
|
||||
cache_nextVal = ''
|
||||
payload = self.payload({
|
||||
"keyword": key,
|
||||
"nextVal": cache_nextVal
|
||||
})
|
||||
response = self.post(f'{self.host}/v1/api/search/search', data=payload, headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
if data['hasNext'] == 1:
|
||||
nextVal = data['nextVal']
|
||||
if nextVal:
|
||||
self.nextVal['search']['key'] = key
|
||||
self.nextVal['search']['value'] = nextVal
|
||||
else:
|
||||
self.nextVal['search']['value'] = ''
|
||||
videos = []
|
||||
for i in data['items']:
|
||||
try:
|
||||
year = i['flags'].split(' / ')[0].strip()
|
||||
except Exception:
|
||||
year = None
|
||||
videos.append({
|
||||
'vod_id': i['vodId'],
|
||||
'vod_name': i['vodName'],
|
||||
'vod_pic': i['coverImg'],
|
||||
'vod_remarks': i['remark'] or f"评分:{i['score']}",
|
||||
'vod_year': year,
|
||||
'vod_content': i['intro']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
try:
|
||||
video_id = int(ids[0])
|
||||
except Exception:
|
||||
video_id = ids[0]
|
||||
payload = self.payload({'vodId':video_id})
|
||||
response = self.post(f'{self.host}/v2/api/vodInfo/index', data=payload, headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
play_urls, play_from = [], []
|
||||
for i in data['playerList']:
|
||||
play_from.append(i['playerName'])
|
||||
urls = []
|
||||
for j in i['epList']:
|
||||
urls.append(f"{j['epName']}${j['epId']}")
|
||||
play_urls.append('#'.join(urls))
|
||||
video = {
|
||||
'vod_id': data['vodId'],
|
||||
'vod_name': data['vodName'],
|
||||
'vod_pic': data['coverImg'],
|
||||
'vod_remarks': data['updateRemark'],
|
||||
'vod_year': data['year'],
|
||||
'vod_area': data['areaName'],
|
||||
'vod_actor': self.actor(data['actorList']),
|
||||
'vod_director': self.actor(data['directorList']),
|
||||
'vod_content': data['intro'],
|
||||
'vod_play_from': '$$$'.join(play_from),
|
||||
'vod_play_url': '$$$'.join(play_urls)
|
||||
}
|
||||
return {'list':[video]}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
try:
|
||||
vodEpId = int(id)
|
||||
except Exception:
|
||||
vodEpId = id
|
||||
payload = self.payload({'vodEpId':vodEpId})
|
||||
response = self.post(f'{self.host}/v2/api/vodInfo/epDetail', data=payload, headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
urls = []
|
||||
for i in data:
|
||||
try:
|
||||
showName, vodResolution = i.get('showName'), i.get('vodResolution')
|
||||
if showName and vodResolution:
|
||||
payload2 = self.payload({'epId': vodEpId, 'vodResolution': vodResolution})
|
||||
response2 = self.post(f'{self.host}/v2/api/vodInfo/playUrl', data=payload2, headers=self.headers, verify=False).json()
|
||||
url = response2['data']['playUrl']
|
||||
if not url.startswith('http'): continue
|
||||
urls.append(showName)
|
||||
urls.append(url)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
continue
|
||||
headers = {
|
||||
'User-Agent': "ExoPlayer",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip"
|
||||
}
|
||||
return {'parse':0,'url':urls,'header':headers}
|
||||
|
||||
def actor(self,data):
|
||||
director_list = []
|
||||
try:
|
||||
for i in data:
|
||||
if isinstance(i, dict) and i.get('vodWorkerName'):
|
||||
if i.get('vodWorkerId'):
|
||||
director_list.append(f'[a=cr:{{"id":"actor@{i["vodWorkerId"]}","name":"{i["vodWorkerName"]}"}}/]{i["vodWorkerName"]}[/a]')
|
||||
else:
|
||||
director_list.append(i.get('vodWorkerName'))
|
||||
actor = ', '.join(director_list)
|
||||
except Exception:
|
||||
actor = None
|
||||
return actor
|
||||
|
||||
def payload(self, data=None):
|
||||
payload = {
|
||||
"appId": self.appid,
|
||||
"bundlerId": self.bundlerId
|
||||
}
|
||||
if data: payload.update(data)
|
||||
payload.update({
|
||||
"cus1tom": "cus3tom",
|
||||
"deviceInfo": "xiaomi",
|
||||
"osInfo": "15",
|
||||
"otherParam": "1",
|
||||
"patchNumber": 0,
|
||||
"requestId": str(uuid.uuid4()),
|
||||
"source": self.source,
|
||||
"udid": self.udid,
|
||||
"version": self.version,
|
||||
"versionCode": self.versionCode
|
||||
})
|
||||
return json.dumps(self.sign(payload))
|
||||
|
||||
def sign(self, data):
|
||||
param_str = self.dict_to_url_params(dict(sorted(data.items())))
|
||||
full_str = f"{param_str}&appKey={self.appkey}" if param_str else f"appKey={self.appkey}"
|
||||
return {**data, "sign": self.md5(full_str)}
|
||||
|
||||
def dict_to_url_params(self, d):
|
||||
return '&'.join(f"{key}={value}" for key, value in d.items() if value != "")
|
||||
|
||||
def md5(self,data):
|
||||
md5_hash = hashlib.md5()
|
||||
if isinstance(data, int): data = str(data)
|
||||
md5_hash.update(data.encode('utf-8'))
|
||||
return md5_hash.hexdigest()
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,132 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import re,sys,json,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers, host, detail_token, parse = {
|
||||
'User-Agent': "okhttp/4.12.0",
|
||||
'Accept-Encoding': "gzip",
|
||||
'content-type': "application/json",
|
||||
'cache-control': "no-cache"
|
||||
}, '','',{}
|
||||
|
||||
def init(self, extend=""):
|
||||
try:
|
||||
ext = json.loads(extend.strip())
|
||||
self.host = ext.get('host','https://video.xqkk.app')
|
||||
self.detail_token = ext.get('detailToken','osmqrsifhjxaftgzyugrychr')
|
||||
self.parse = ext.get('parse',{})
|
||||
return None
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/index.php/appapi/init', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
type_list = data['type_list']
|
||||
classes = []
|
||||
for i in type_list:
|
||||
if isinstance(i,dict):
|
||||
classes.append({'type_id': i['type_id'], 'type_name': i['type_name']})
|
||||
videos = []
|
||||
banner_list = data.get('banner_list',[])
|
||||
if isinstance(banner_list, list):
|
||||
for i in banner_list:
|
||||
link_type = i.get('link_type')
|
||||
if link_type == 1:
|
||||
videos.append({
|
||||
'vod_name': i.get('title'),
|
||||
'vod_pic': i.get('img_url'),
|
||||
'vod_id': i.get('link_value')
|
||||
})
|
||||
for j in type_list:
|
||||
recommend_list = j.get('recommend_list')
|
||||
if isinstance(recommend_list,list):
|
||||
videos.extend(recommend_list)
|
||||
return {'class': classes, 'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(f"{self.host}/index.php/appapi/typeFilterVodList?type_pid=0&page={pg}&class={extend.get('class','全部')}&year={extend.get('year','')}&area={extend.get('area','')}&lang={extend.get('lang','')}&sort={extend.get('sort','最热')}&type_id={tid}", headers=self.headers, verify=False).json()
|
||||
return {'list': response['data'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
response = self.fetch(f"{self.host}/index.php/appapi/searchList?type_id=0&keywords={key}&page={pg}", headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
for i in data:
|
||||
if 'vod_blurb' in i and 'vod_content' not in i:
|
||||
i['vod_content'] = i['vod_blurb']
|
||||
return {'list': data, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f"{self.host}/index.php/appapi/vodDetail?vod_id={ids[0]}&token={self.detail_token}",headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
video = data['vod']
|
||||
if 'vod_blurb' in video and 'vod_content' not in video:
|
||||
video['vod_content'] = video['vod_blurb']
|
||||
play_from_list = []
|
||||
play_urls_list = []
|
||||
for item in data.get('vod_play_list', []):
|
||||
play_from_name = item['play_from_name']
|
||||
play_from_code = item['play_from_code']
|
||||
if play_from_name != play_from_code:
|
||||
play_from_list.append(f'{play_from_name} ({play_from_code})')
|
||||
else:
|
||||
play_from_list.append(play_from_name)
|
||||
urls = []
|
||||
for url_info in item.get('play_urls', []):
|
||||
urls.append(f"{url_info['name']}${play_from_code}@{url_info['url']}")
|
||||
play_urls_list.append('#'.join(urls))
|
||||
video['vod_play_from'] = '$$$'.join(play_from_list)
|
||||
video['vod_play_url'] = '$$$'.join(play_urls_list)
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
jx,url = 0,''
|
||||
parse = self.parse
|
||||
play_from, raw_url = id.split('@')
|
||||
if parse:
|
||||
for i,j in parse.items():
|
||||
if play_from in i:
|
||||
for k in j:
|
||||
try:
|
||||
response = self.fetch(f'{k}{raw_url}', headers=self.headers, verify=False).json()
|
||||
play_url = response['url']
|
||||
if play_url.startswith('http'):
|
||||
url,jx = play_url, 0
|
||||
break
|
||||
except (KeyError, ValueError, Exception):
|
||||
continue
|
||||
if url.startswith('http'):
|
||||
break
|
||||
if not url:
|
||||
if re.match(r'https?:\/\/.*\.(m3u8|mp4|flv)', raw_url):
|
||||
jx, url = 0, raw_url
|
||||
else:
|
||||
jx, url = 1, raw_url
|
||||
if url.startswith('NBY'):
|
||||
jx, url = 0, ''
|
||||
return { 'jx': jx, 'parse': '0', 'url': url, 'header': {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}}
|
||||
|
||||
def homeVideoContent(self):
|
||||
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,122 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import re,sys,json,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
def_headers,host = {
|
||||
'User-Agent': 'rrsp.wang',
|
||||
'origin': '*',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'cross-site',
|
||||
'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
},'https://rrsp-api.kejiqianxian.com:60425'
|
||||
headers = {
|
||||
**def_headers,
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Content-Type': 'application/json',
|
||||
'accept-language': 'zh-CN'
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
return {'class':[{'type_id':'1','type_name':'电影'},{'type_id':'2','type_name':'电视剧'},{'type_id':'3','type_name':'综艺'},{'type_id':'5','type_name':'动漫'},{'type_id':'4','type_name':'纪录片'},{'type_id':'6','type_name':'短剧'},{'type_id':'7','type_name':'特别节目'},{'type_id':'8','type_name':'少儿内容'}]}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return self.categoryContent('',1,{},{})
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
payload = {
|
||||
'type': str(tid),
|
||||
'sort': 'vod_time',
|
||||
'area': '',
|
||||
'style': '',
|
||||
'time': '',
|
||||
'pay': '',
|
||||
'page': pg,
|
||||
'limit': '60'
|
||||
}
|
||||
response = self.post(f'{self.host}/api.php/main_program/moviesAll/', data=json.dumps(payload), headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
return {'list': self.arr2vods(data['list']), 'pagecount':data['pagecount'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if str(pg) != '1': return None
|
||||
response = self.post(f'{self.host}/api.php/search/syntheticalSearch/', data=f'{{"keyword": "{key}"}}', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
videos = []
|
||||
videos.extend(self.arr2vods(data['chasingFanCorrelation']))
|
||||
videos.extend(self.arr2vods(data['moviesCorrelation']))
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.post(f'{self.host}/api.php/player/details/', data=f'{{"id": "{ids[0]}"}}', headers=self.headers, verify=False).json()
|
||||
data = response['detailData']
|
||||
video = {
|
||||
'vod_id': data['vod_id'],
|
||||
'vod_name': data['vod_name'],
|
||||
'vod_pic': data['vod_pic'],
|
||||
'vod_remarks': data['vod_remarks'],
|
||||
'vod_year': data['vod_year'],
|
||||
'vod_area': data['vod_area'],
|
||||
'vod_actor': data['vod_actor'],
|
||||
'vod_director': data['vod_director'],
|
||||
'vod_content': data['vod_content'],
|
||||
'vod_play_from': data['vod_play_from'],
|
||||
'vod_play_url': data['vod_play_url'],
|
||||
'type_name': data['vod_class']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
jx,url = 0,''
|
||||
try:
|
||||
response = self.post(f'{self.host}/api.php/player/payVideoUrl/',data=f'{{"url":"{vid}"}}', headers=self.headers,timeout=30,verify=False).json()
|
||||
play_url = response['data']['url']
|
||||
if play_url.startswith('http'): url = play_url
|
||||
except Exception:
|
||||
pass
|
||||
if not url:
|
||||
url = vid
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', vid): jx = 1
|
||||
return { 'jx': jx, 'parse': 0, 'url': url, 'header': {**self.def_headers,'accept-language': 'zh-CN', 'referer':'https://docs.qq.com/'}}
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
for i in arr:
|
||||
if i['vod_serial'] == '1':
|
||||
remarks = f"{i['vod_serial']}集"
|
||||
else:
|
||||
remarks = f"评分:{i.get('vod_score',i.get('vod_douban_score'))}"
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': remarks,
|
||||
'vod_year': None
|
||||
})
|
||||
return videos
|
||||
|
||||
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 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import sys,json,urllib3
|
||||
import concurrent.futures
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host, userid, episode_list = '', '', []
|
||||
headers = {
|
||||
'User-Agent': "okhttp/4.12.0",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip",
|
||||
'Content-Type': "application/json;charset=UTF-8",
|
||||
'Cache-Control': "no-cache",
|
||||
'token': "",
|
||||
'deviceId': "",
|
||||
'client': "app",
|
||||
'deviceType': "Android"
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
self.headers['deviceId'] = '2d590b9842d064a1'
|
||||
response = self.fetch('http://osstexll.oss-rg-china-mainland.aliyuncs.com/domainPath.json', headers={'User-Agent': "okhttp/4.12.0",'Connection': "Keep-Alive",'Accept-Encoding': "gzip"}).json()
|
||||
self.host = response['url'][0]
|
||||
response = self.fetch(f'{self.host}/api/v1/app/user/visitorInfo', headers=self.headers).json()
|
||||
self.userid = response['data']['id']
|
||||
token = response['data']['token']
|
||||
self.headers['token'] = token
|
||||
|
||||
def homeContent(self, filter):
|
||||
response = self.post(f'{self.host}/api/v1/app/screen/screenType', headers=self.headers).json()
|
||||
data = response['data']
|
||||
classes = []
|
||||
for i in data:
|
||||
classes.append({'type_id': i['id'],'type_name': i['name']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.post(f'{self.host}/api/v1/app/recommend/recommendList', headers=self.headers).json()
|
||||
data = response['data']
|
||||
videos = []
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
future_to_id = {
|
||||
executor.submit(
|
||||
self.post,
|
||||
f'{self.host}/api/v1/app/recommend/recommendSubList',
|
||||
data=json.dumps({
|
||||
"condition": item['id'],
|
||||
"pageNum": 1,
|
||||
"pageSize": 6
|
||||
}),
|
||||
headers=self.headers
|
||||
): item['id'] for item in data
|
||||
}
|
||||
for future in concurrent.futures.as_completed(future_to_id):
|
||||
try:
|
||||
response = future.result().json()
|
||||
for video in response['data']['records']:
|
||||
videos.append({
|
||||
"vod_id": video['id'],
|
||||
"vod_name": video['name'],
|
||||
"vod_pic": video['cover']
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"Request failed for item {future_to_id[future]}: {str(e)}")
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
payload = {
|
||||
"condition": {
|
||||
"classify": "",
|
||||
"region": "",
|
||||
"sreecnTypeEnum": "NEWEST",
|
||||
"typeId": tid,
|
||||
"year": ""
|
||||
},
|
||||
"pageNum": pg,
|
||||
"pageSize": 40
|
||||
}
|
||||
response = self.post(f'{self.host}/api/v1/app/screen/screenMovie', data=json.dumps(payload), headers=self.headers).json()
|
||||
videos = []
|
||||
for i in response['data']['records']:
|
||||
videos.append({
|
||||
"vod_id": i['id'],
|
||||
"vod_name": i['name'],
|
||||
"vod_pic": i['cover'],
|
||||
"vod_remarks": i['area'],
|
||||
"vod_year": i['year']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
payload = {
|
||||
"condition": {
|
||||
"value": key
|
||||
},
|
||||
"pageNum": pg,
|
||||
"pageSize": 40
|
||||
}
|
||||
response = self.post(f'{self.host}/api/v1/app/search/searchMovie', data=json.dumps(payload), headers=self.headers).json()
|
||||
videos = []
|
||||
for i in response['data']['records']:
|
||||
videos.append({
|
||||
'vod_id': i['id'],
|
||||
'vod_name': i['name'],
|
||||
'vod_pic': i['cover'],
|
||||
'vod_remarks': i['area'],
|
||||
'vod_year': i['year'],
|
||||
'vod_area': i['area'],
|
||||
'vod_content': i['desc']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
payload = {
|
||||
"id": ids[0],
|
||||
"source": 0,
|
||||
"typeId": "M17",
|
||||
"userId": self.userid
|
||||
}
|
||||
response = self.post(f'{self.host}/api/v1/app/play/movieDetails', data=json.dumps(payload), headers=self.headers).json()
|
||||
data = response['data']
|
||||
currentplayerid = data['playerId']
|
||||
play_urls = []
|
||||
play_url = []
|
||||
show = []
|
||||
for i in data['episodeList']:
|
||||
play_url.append(f"{i['episode']}${ids[0]}@{currentplayerid}@{i['id']}@episode")
|
||||
play_urls.append('#'.join(play_url))
|
||||
moviePlayerList = data['moviePlayerList']
|
||||
for i2 in moviePlayerList:
|
||||
if i2['id'] == currentplayerid:
|
||||
show.append(i2['moviePlayerName'])
|
||||
for j in moviePlayerList:
|
||||
playerid = j['id']
|
||||
episodeTotal = j.get('episodeTotal')
|
||||
if playerid == currentplayerid or episodeTotal == None:
|
||||
continue
|
||||
play_url = []
|
||||
for k in range(1,episodeTotal + 1):
|
||||
play_url.append(f"第{k}集${k}@{playerid}@{ids[0]}@virtual")
|
||||
play_urls.append('#'.join(play_url))
|
||||
if j['moviePlayerName'] not in show:
|
||||
show.append(j['moviePlayerName'])
|
||||
payload = {
|
||||
"id": ids[0],
|
||||
"typeId": "M17"
|
||||
}
|
||||
response = self.post(f'{self.host}/api/v1/app/play/movieDesc', data=json.dumps(payload), headers=self.headers).json()
|
||||
data2 = response['data']
|
||||
video = {}
|
||||
video.update({
|
||||
'vod_id': data2['id'],
|
||||
'vod_name': data2['name'],
|
||||
'vod_pic': data2['cover'],
|
||||
'vod_content': data2['introduce'],
|
||||
'vod_year': data2['year'],
|
||||
'vod_area': data2['area'],
|
||||
'vod_remarks': '',
|
||||
'vod_score': data2['score'],
|
||||
'type_name': data2['classify'],
|
||||
'vod_director': data2['director'],
|
||||
'vod_play_from': '$$$'.join(show),
|
||||
'vod_play_url': '$$$'.join(play_urls)
|
||||
})
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
param, playerid, param2, param3 = id.split('@')
|
||||
if param3 == 'virtual':
|
||||
payload = {
|
||||
"episodeIndex": str(int(param)-1),
|
||||
"id": int(param2),
|
||||
"playerId": playerid,
|
||||
"source": 0,
|
||||
"typeId": "M16",
|
||||
"userId": self.userid
|
||||
}
|
||||
response = self.post(f'{self.host}/api/v1/app/play/movieDetails', data=json.dumps(payload), headers=self.headers).json()
|
||||
data = response['data']
|
||||
parse_url = data['url']
|
||||
playerid = data['playerId']
|
||||
else:
|
||||
payload = {
|
||||
"episodeId": param2,
|
||||
"id": param,
|
||||
"playerId": playerid,
|
||||
"source": 0,
|
||||
"typeId": "M16",
|
||||
"userId": self.userid
|
||||
}
|
||||
response = self.post(f'{self.host}/api/v1/app/play/movieDetails', data=json.dumps(payload), headers=self.headers).json()
|
||||
data = response['data']
|
||||
parse_url = data['url']
|
||||
playerid = data['playerId']
|
||||
response = self.fetch(f"{self.host}/api/v1/app/play/analysisMovieUrl?playerUrl={quote(parse_url,safe='')}&playerId={playerid}", headers=self.headers).json()
|
||||
url = response['data']
|
||||
|
||||
return { 'jx': '0', 'parse': '0', 'url': url, 'header': {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}}
|
||||
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,212 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import sys,json,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host = 'https://tdxl1.tudd4.com'
|
||||
pic_host = 'https://hdjsfm8q.tudou8.xyz/'
|
||||
headers = {
|
||||
'User-Agent': "Mozilla/5.0 (Linux; Android 12; SM-S9080 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36 uni-app Html5Plus/1.0 (Immersed/0.6666667)",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip",
|
||||
'Content-Type': "application/json",
|
||||
'token': "4DF7F118BAE48C3CB44C640D2C51E905640D2C51E905"
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
response = self.post(f'{self.host}/apicms.php/video/appxianlu', data=json.dumps({}),headers=self.headers,verify=False).json()
|
||||
data = response['data']
|
||||
for i in data:
|
||||
try:
|
||||
response = self.post(f'{i["payurl"]}/index.php/index/ceshu', headers=self.headers,verify=False).text
|
||||
if response == 'ok':
|
||||
self.host = i['payurl']
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
def homeContent(self, filter):
|
||||
payload = {
|
||||
"is_index_shouye": 1
|
||||
}
|
||||
response = self.post(f'{self.host}/apicms.php/video/gettypelists', data=json.dumps(payload), headers=self.headers,verify=False).json()
|
||||
data = response['data']
|
||||
classes = []
|
||||
for i in data:
|
||||
if i['type_id'] != -1:
|
||||
classes.append({'type_id':i['type_id'],'type_name':i['type_name']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
payload = {
|
||||
"type_id": -1,
|
||||
"current_index": 0
|
||||
}
|
||||
|
||||
response = self.post(f'{self.host}/apicms.php/video/getindexvideofenleixs', data=json.dumps(payload), headers=self.headers,verify=False).json()
|
||||
video = []
|
||||
video.extend(response.get('vodhenghots', []))
|
||||
for i in response.get('vodlists', []):
|
||||
video.extend(i.get('list',[]))
|
||||
videos = []
|
||||
for i in video:
|
||||
vod_pic = i.get('vod_pic','')
|
||||
if not vod_pic.startswith('http') and vod_pic:
|
||||
vod_pic = self.pic_host + vod_pic
|
||||
videos.append({
|
||||
'vod_id': i.get('vod_id'),
|
||||
'vod_name': i.get('vod_name'),
|
||||
'vod_class': i.get('vod_class'),
|
||||
'vod_pic': vod_pic,
|
||||
'vod_remarks': i.get('vod_remarks'),
|
||||
'vod_score': i.get('vod_score')
|
||||
})
|
||||
gglunbolists = response.get('gglunbolists', [])
|
||||
for i in gglunbolists:
|
||||
vod_class = i.get('vod_class')
|
||||
vod_class2 = ''
|
||||
if isinstance(vod_class, list):
|
||||
for j in vod_class:
|
||||
vod_class2 += j.get('bq_class', '')
|
||||
videos.insert(0,{
|
||||
'vod_id': i.get('vod_id'),
|
||||
'vod_name': i.get('vod_name'),
|
||||
'vod_class': vod_class2,
|
||||
'vod_pic': i.get('vod_pic_slide', i.get('vod_pic'))
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
payload = {
|
||||
"page": pg,
|
||||
"type_id": tid,
|
||||
"area_name": extend.get('area','全部地区'),
|
||||
"year_name": extend.get('year','全部年份'),
|
||||
"leixin_name": extend.get('class','全部类型'),
|
||||
"sort_id": extend.get('sort',2)
|
||||
}
|
||||
response = self.post(f'{self.host}/apicms.php/video/getsaixuanvideos', data=json.dumps(payload), headers=self.headers,verify=False).json()
|
||||
return {'list': response['data'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
payload = {
|
||||
"page": pg,
|
||||
"video_name": key
|
||||
}
|
||||
response = self.post(f'{self.host}/apicms.php/video/getsousuovideojieguo', data=json.dumps(payload), headers=self.headers,verify=False).json()
|
||||
data = response['data']
|
||||
videos = []
|
||||
for i in data:
|
||||
vod_pic = i.get('vod_pic', '')
|
||||
if not vod_pic.startswith('http'):
|
||||
vod_pic = self.pic_host + vod_pic
|
||||
vod_class = i.get('vod_class')
|
||||
vod_class2 = ''
|
||||
if isinstance(vod_class, list):
|
||||
for j in vod_class:
|
||||
vod_class2 += j.get('bq_class', '')
|
||||
videos.append({
|
||||
"vod_id": i['vod_id'],
|
||||
"vod_name": i['vod_name'],
|
||||
"vod_class": vod_class2,
|
||||
"vod_score": i.get('vod_score'),
|
||||
"vod_pic": vod_pic,
|
||||
'vod_year': i.get('vod_year'),
|
||||
"vod_remarks": i['vod_remarks']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
payload = {
|
||||
"vod_id": ids[0]
|
||||
}
|
||||
response = self.post(f'{self.host}/apicms.php/video/getplayxiangqingdata', data=json.dumps(payload), headers=self.headers,verify=False).json()
|
||||
data = response['data']
|
||||
videos = []
|
||||
vod_actor = ''
|
||||
actor = data.get('vod_actor',[])
|
||||
for i in actor:
|
||||
bq_actor = i.get('bq_actor')
|
||||
if bq_actor:
|
||||
vod_actor += bq_actor + ', '
|
||||
vod_director = ''
|
||||
director = data.get('vod_director', [])
|
||||
for i in director:
|
||||
bq_actor = i.get('bq_actor')
|
||||
if bq_actor:
|
||||
vod_director += bq_actor + ', '
|
||||
|
||||
vod_play_url = ''
|
||||
show = ''
|
||||
for i in data['vod_play_list']:
|
||||
show += i['xl_name'] + '$$$'
|
||||
urls = ''
|
||||
for j in i['urlsaz']:
|
||||
if j['ishdjx'] == 1:
|
||||
urls += f"{j.get('index',j.get('title',''))}${j['vid']}@{j['from']}@{data.get('play_index',0)}@{j['url']}#"
|
||||
else:
|
||||
urls += f"{j.get('index',j.get('title',''))}${j.get('url','')}#"
|
||||
urls = urls.rstrip('#')
|
||||
vod_play_url += urls + '$$$'
|
||||
show = show.rstrip('$$$')
|
||||
vod_play_url = vod_play_url.rstrip('$$$')
|
||||
|
||||
videos.append({
|
||||
'vod_id': data.get('vod_id'),
|
||||
'vod_name': data.get('vod_name'),
|
||||
'vod_content': data.get('vod_content'),
|
||||
'vod_blurb': data.get('vod_blurb'),
|
||||
'vod_remarks': data.get('vod_remarks'),
|
||||
"vod_director": vod_director.rstrip(', '),
|
||||
"vod_actor": vod_actor.rstrip(', '),
|
||||
'vod_year': data.get('vod_year'),
|
||||
'vod_area': data.get('vod_area'),
|
||||
'vod_play_from': show,
|
||||
'vod_play_url': vod_play_url
|
||||
})
|
||||
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
jx, ua = 0, 'io.dcloud.application.DCloudApplication/1.0.0.19 (Linux;Android 12) ExoPlayerLib/2.15.1'
|
||||
ua2 = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
|
||||
if '@' in id:
|
||||
vod_id, playfrom, play_index, vodhhplayurl = id.split('@')
|
||||
try:
|
||||
if vod_id and playfrom and vodhhplayurl:
|
||||
payload = {
|
||||
"vod_id": vod_id,
|
||||
"from": playfrom,
|
||||
"play_index": play_index,
|
||||
"vodhhplayurl": vodhhplayurl,
|
||||
"play_xuanlu": 0
|
||||
}
|
||||
response = self.post(f'{self.host}/apicms.php/video/gethuohuaurljiexi', data=json.dumps(payload), headers=self.headers,verify=False).json()
|
||||
url = response['data']
|
||||
if not url.startswith('http'):
|
||||
url, jx, ua = vodhhplayurl, 1, ua2
|
||||
except Exception:
|
||||
url, jx, ua = vodhhplayurl, 1 ,ua2
|
||||
else:
|
||||
url = id
|
||||
return {'jx': jx, 'parse': 0, 'url': url,'header': {'User-Agent': ua}}
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,146 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import re,sys,urllib3
|
||||
from datetime import datetime
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,player,host = {
|
||||
'User-Agent': 'Dart/3.9 (dart:io)',
|
||||
'Accept-Encoding': 'gzip'
|
||||
},{},''
|
||||
headers2 = {**headers,'content-type':'application/json'}
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
if extend.startswith('http'):
|
||||
host = extend
|
||||
else:
|
||||
host = "https://qiliniao-1331306839.cos.ap-chengdu.myqcloud.com/jg.txt"
|
||||
domain_set = set()
|
||||
if not re.match(r'^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(\/)?$', host):
|
||||
host_data = self.fetch(host, headers=self.headers, timeout=10, verify=False).text
|
||||
domain_set.update([domain.strip().rstrip('/') for domain in host_data.split('\n')])
|
||||
else:
|
||||
domain_set.add(host)
|
||||
for i in domain_set:
|
||||
try:
|
||||
res = self.fetch(f'{i}/success.txt', headers=self.headers, timeout=(5, 5), verify=False)
|
||||
if res.status_code == 200:
|
||||
self.host = i
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
self.host = ''
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/api.php/type/get_list', headers=self.headers2, verify=False).json()
|
||||
classes = []
|
||||
for i in response['info']['rows']:
|
||||
try:
|
||||
if isinstance(i,dict) and i['type_status'] == 1 and i['type_name'] not in {'漫画', '小说'}:
|
||||
classes.append({'type_id': i['type_id'], 'type_name': i['type_name']})
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
response2 = self.fetch(f'{self.host}/addons/getstar/api.index/shortVideoCategory', headers=self.headers2, verify=False).json()
|
||||
for j in response2['data']:
|
||||
try:
|
||||
classes.append({'type_id': j['id'], 'type_name': j['name']})
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
return self.fetch(f'{self.host}/index.php/ajax/data?mid=1&limit=100&page=1&level=7',headers=self.headers2, verify=False).json()
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
return self.fetch(f"{self.host}/index.php/ajax/data?mid=1&limit=20&page={pg}&tid={tid}&year={datetime.now().year}", headers=self.headers2, verify=False).json()
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
return self.fetch(f"{self.host}/index.php/ajax/data?mid=1&limit=20&page={pg}&wd={key}", headers=self.headers2, verify=False).json()
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.player:
|
||||
try:
|
||||
player_res = self.fetch(f'{self.host}/addons/getstar/api.index/getPlayerParse',headers=self.headers, verify=False).json()
|
||||
player_data = player_res['data']
|
||||
if isinstance(player_data, list):
|
||||
self.player = player_data
|
||||
except Exception:
|
||||
pass
|
||||
response = self.fetch(f"{self.host}/api.php/vod/get_detail?vod_id={ids[0]}",headers=self.headers, verify=False).json()
|
||||
data = response['info'][0]
|
||||
if data['vod_play_from'] and data['vod_play_url']:
|
||||
show_tmp = data['vod_play_from'].split('$$$')
|
||||
urls_tmp = data['vod_play_url'].split('$$$')
|
||||
shows, play_urls = [], []
|
||||
for show, urls in zip(show_tmp, urls_tmp):
|
||||
player,parse,is_open = {},'',0
|
||||
for i in self.player:
|
||||
if i and i.get('code') == show:
|
||||
is_open = 1
|
||||
name = i.get('name')
|
||||
if name and name != show:
|
||||
show = f'{name}\u2005({show})'
|
||||
parse = i.get('url')
|
||||
break
|
||||
if is_open == 0: continue
|
||||
urls2 = []
|
||||
valid_parts = [part for part in urls.split('#') if part]
|
||||
for part in valid_parts:
|
||||
url_prefix, url_suffix = part.split('$', 1)
|
||||
suffix = f"@{parse}" if parse else ''
|
||||
urls2.append(f"{url_prefix}${url_suffix}{suffix}")
|
||||
play_urls.append('#'.join(urls2))
|
||||
shows.append(show)
|
||||
data['vod_play_from'] = '$$$'.join(shows)
|
||||
data['vod_play_url'] = '$$$'.join(play_urls)
|
||||
return {'list': [data]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
raw_url, jx, url = vid, 0, ''
|
||||
if '@' in vid:
|
||||
raw_url, parse = vid.split('@',1)
|
||||
headers = {
|
||||
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||
'Accept-Encoding': "gzip"
|
||||
}
|
||||
try:
|
||||
response = self.fetch(f'{parse}{raw_url}',verify=False, headers=headers).json()
|
||||
play_url = response['url']
|
||||
if play_url.startswith('http') and play_url != raw_url:
|
||||
url = play_url
|
||||
except Exception:
|
||||
pass
|
||||
if not url:
|
||||
url = raw_url
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', raw_url):
|
||||
jx = 1
|
||||
return { 'jx': jx, 'parse': '0', 'url': url, 'header': {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36','Connection':'Keep-Alive','Accept-Encoding':'gzip'}}
|
||||
|
||||
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,236 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
from Crypto.PublicKey import RSA
|
||||
import re,sys,time,random,base64,hashlib,urllib3
|
||||
from Crypto.Util.number import bytes_to_long,long_to_bytes
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.host = 'https://aleig4ah.yiys05.com'
|
||||
self.token = ''
|
||||
self.app_id = ''
|
||||
self.headers = {
|
||||
'User-Agent': 'Android/OkHttp',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip'
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
if extend.startswith('http'):
|
||||
self.host = extend
|
||||
android_id_key = 'yiys_zNiOFyj0r4ux'
|
||||
self.app_id = self.getCache(android_id_key)
|
||||
if not self.app_id:
|
||||
self.app_id = ''.join(random.choice('0123456789abcdef') for _ in range(16))
|
||||
self.setCache(android_id_key, self.app_id)
|
||||
if not self.token:
|
||||
self.refresh_token()
|
||||
|
||||
def refresh_token(self):
|
||||
ts = str(int(time.time()))
|
||||
payload = {'appID': self.app_id, 'timestamp': ts}
|
||||
headers = {**self.headers, 'APP-ID': self.app_id, 'X-Auth-Flow': '1'}
|
||||
try:
|
||||
res = self.post(f'{self.host}/vod-app/index/getGenerateKey', data=payload, headers=headers, verify=False).json()
|
||||
if 'data' in res:
|
||||
self.token = self.rsa_public_decrypt(res['data'])
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
def get_signed_headers(self, params):
|
||||
h = {**self.headers, 'APP-ID': self.app_id, 'Authorization': ''}
|
||||
if params:
|
||||
sorted_keys = sorted(params.keys())
|
||||
query_str = "&".join([f"{k}={params[k]}" for k in sorted_keys])
|
||||
sign_str = f"{query_str}&token={self.token}"
|
||||
h['X-HASH-Data'] = hashlib.sha256(sign_str.encode('utf-8')).hexdigest()
|
||||
return h
|
||||
|
||||
def smart_request(self, url, method='GET', data=None, params=None):
|
||||
sign_payload = data if method == 'POST' else params
|
||||
headers = self.get_signed_headers(sign_payload)
|
||||
if method == 'GET':
|
||||
res = self.fetch(url, params=params, headers=headers, verify=False)
|
||||
else:
|
||||
res = self.post(url, data=data, headers=headers, verify=False)
|
||||
if res.status_code == 400 or not res.text:
|
||||
if self.refresh_token():
|
||||
headers = self.get_signed_headers(sign_payload)
|
||||
if method == 'GET':
|
||||
res = self.fetch(url, params=params, headers=headers, verify=False)
|
||||
else:
|
||||
res = self.post(url, data=data, headers=headers, verify=False)
|
||||
return res.json()
|
||||
|
||||
def homeContent(self, filter):
|
||||
ts = str(int(time.time()))
|
||||
params = {'timestamp': ts}
|
||||
res = self.smart_request(f'{self.host}/vod-app/type/list', params=params)
|
||||
classes, filters = [], {}
|
||||
def build_filter(key, name, values_str, is_sort=False):
|
||||
val_arr = []
|
||||
if not is_sort:
|
||||
val_arr.append({'n': '全部', 'v': ''})
|
||||
if values_str:
|
||||
splits = values_str.split(',')
|
||||
for s in splits:
|
||||
item = s.strip()
|
||||
if item:
|
||||
val_arr.append({'n': item, 'v': item})
|
||||
elif is_sort:
|
||||
val_arr = [
|
||||
{'n': '新上线', 'v': 'time'},
|
||||
{'n': '热播榜', 'v': 'hits_day'},
|
||||
{'n': '好评榜', 'v': 'score'}
|
||||
]
|
||||
return {
|
||||
'key': key,
|
||||
'name': name,
|
||||
'value': val_arr,
|
||||
'init': 'time' if is_sort else ''
|
||||
}
|
||||
for i in res.get('data', []):
|
||||
tid = str(i['typeId'])
|
||||
classes.append({'type_id': tid, 'type_name': i['typeName']})
|
||||
if 'type_extend_obj' in i:
|
||||
ext = i['type_extend_obj']
|
||||
type_filters = []
|
||||
if ext.get('class'):
|
||||
type_filters.append(build_filter('class', '类型', ext['class']))
|
||||
if ext.get('area'):
|
||||
type_filters.append(build_filter('area', '地区', ext['area']))
|
||||
if ext.get('lang'):
|
||||
type_filters.append(build_filter('lang', '语言', ext['lang']))
|
||||
if ext.get('year'):
|
||||
type_filters.append(build_filter('year', '年份', ext['year']))
|
||||
type_filters.append(build_filter('sort', '排序', '', True))
|
||||
if type_filters:
|
||||
filters[tid] = type_filters
|
||||
|
||||
return {'class': classes, 'filters': filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
ts = str(int(time.time()))
|
||||
params = {'timestamp': ts}
|
||||
res = self.smart_request(f'{self.host}/vod-app/rank/hotHits', params=params)
|
||||
videos = []
|
||||
for i in res.get('data', []):
|
||||
if 'vodBeans' in i:
|
||||
videos.extend(self.arr2vods(i['vodBeans']))
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
payload = {
|
||||
'tid': tid,
|
||||
'page': str(pg),
|
||||
'limit': '12',
|
||||
'timestamp': str(int(time.time())),
|
||||
'classType': extend.get('class', ''),
|
||||
'area': extend.get('area', ''),
|
||||
'lang': extend.get('lang', ''),
|
||||
'year': extend.get('year', ''),
|
||||
'by': extend.get('sort', 'time')
|
||||
}
|
||||
payload = {k: v for k, v in payload.items() if v}
|
||||
res = self.smart_request(f'{self.host}/vod-app/vod/list', method='POST', data=payload)
|
||||
data = res.get('data', {})
|
||||
return {
|
||||
'list': self.arr2vods(data.get('data', [])),
|
||||
'page': int(pg),
|
||||
'pagecount': data.get('totalPageCount', 1)
|
||||
}
|
||||
|
||||
def detailContent(self, ids):
|
||||
payload = {
|
||||
'tid': '',
|
||||
'timestamp': str(int(time.time())),
|
||||
'vodId': str(ids[0])
|
||||
}
|
||||
res = self.smart_request(f'{self.host}/vod-app/vod/info', method='POST', data=payload)
|
||||
data = res.get('data', {})
|
||||
show, play_urls = [], []
|
||||
if 'vodSources' in data:
|
||||
data['vodSources'].sort(key=lambda x: x.get('sort', 0))
|
||||
for i in data['vodSources']:
|
||||
urls = []
|
||||
if 'vodPlayList' in i and 'urls' in i['vodPlayList']:
|
||||
for j in i['vodPlayList']['urls']:
|
||||
urls.append(f"{j['name']}${i['sourceCode']}@{j['url']}")
|
||||
play_urls.append('#'.join(urls))
|
||||
show.append(i['sourceName'])
|
||||
video = {
|
||||
'vod_id': data['vodId'],
|
||||
'vod_name': data['vodName'],
|
||||
'vod_pic': data['vodPic'],
|
||||
'vod_remarks': data.get('vodRemark', ''),
|
||||
'vod_year': data.get('vodYear', ''),
|
||||
'vod_area': data.get('vodArea', ''),
|
||||
'vod_actor': data.get('vodActor', ''),
|
||||
'vod_content': data.get('vodContent', ''),
|
||||
'vod_play_from': '$$$'.join(show),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': data.get('vodClass', '')
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
payload = {
|
||||
'key': key,
|
||||
'limit': '20',
|
||||
'page': str(pg),
|
||||
'timestamp': str(int(time.time()))
|
||||
}
|
||||
res = self.smart_request(f'{self.host}/vod-app/vod/segSearch', method='POST', data=payload)
|
||||
data = res.get('data', {})
|
||||
return {'list': self.arr2vods(data.get('data', [])), 'page': int(pg)}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
jx = 0
|
||||
source_code, raw_url = vid.split('@', 1)
|
||||
payload = {
|
||||
'sourceCode': source_code,
|
||||
'timestamp': str(int(time.time())),
|
||||
'urlEncode': raw_url
|
||||
}
|
||||
try:
|
||||
res = self.smart_request(f'{self.host}/vod-app/vod/playUrl', method='POST', data=payload)
|
||||
url = res.get('data', {}).get('url', raw_url)
|
||||
except Exception:
|
||||
url = None
|
||||
if not url:
|
||||
url = raw_url
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', url):
|
||||
jx = 1
|
||||
return {'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent': self.headers['User-Agent']}}
|
||||
|
||||
def rsa_public_decrypt(self, ciphertext_base64):
|
||||
pub_key_str = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw4qpeOgv+MeXi57MVPqZF7SRmHR3FUelCTfrvI6vZ8kgTPpe1gMyP/8ZTvedTYjTDMqZBmn8o8Ym98yTx3zHaskPpmDR80e+rcRciPoYZcWNpwpFkrHp1l6Pjs9xHLXzf3U+N3a8QneY+jSMvgMbr00DC4XfvamfrkPMXQ+x9t3gNcP5YtuRhGFREBKP2q20gP783MCOBFwyxhZTIAsFiXrLkgZ97uaUAtqW6wtKR4HWpeaN+RLLxhBdnVjuMc9jaBl6sHMdSvTJgAajBTAd6LLA9cDmbGTxH7RGp//iZU86kFhxGl5yssZvBcx/K95ADeTmLKCsabexZVZ0Fu3dDQIDAQAB\n-----END PUBLIC KEY-----"
|
||||
key = RSA.import_key(pub_key_str)
|
||||
ciphertext = base64.b64decode(ciphertext_base64)
|
||||
c = bytes_to_long(ciphertext)
|
||||
m = pow(c, key.e, key.n)
|
||||
m_bytes = long_to_bytes(m)
|
||||
m_bytes = m_bytes.rjust(256, b'\x00')
|
||||
try:
|
||||
sep_idx = m_bytes.index(b'\x00', 2)
|
||||
return m_bytes[sep_idx + 1:].decode('utf-8')
|
||||
except:
|
||||
return m_bytes.decode('utf-8', 'ignore').strip()
|
||||
|
||||
def arr2vods(self, arr):
|
||||
return [{
|
||||
'vod_id': j['id'],
|
||||
'vod_name': j['name'],
|
||||
'vod_pic': j['vodPic'],
|
||||
'vod_remarks': j.get('vodRemarks', ''),
|
||||
'vod_year': j.get('vodYear', ''),
|
||||
'vod_content': j.get('vodBlurb', '')
|
||||
} for j in arr]
|
||||
@@ -0,0 +1,418 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.Padding import pad
|
||||
from Crypto.Cipher import AES, PKCS1_v1_5
|
||||
import sys,time,json,base64,urllib3,hashlib
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host, android_id, init_sign_salt, app_cert_sha1, private_key, token, timeout, headers = ('','','','','','','',
|
||||
{
|
||||
'User-Agent': "okhttp-okgo/jeasonlzy",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip",
|
||||
'Accept-Language': "zh-CN,zh;q=0.8",
|
||||
'sign': "",
|
||||
'devicename': "Xiaomi 15",
|
||||
'deviceos': "15",
|
||||
'bundleid': "",
|
||||
'versionname': "2.1.0",
|
||||
'versioncode': "2001000",
|
||||
'vendor': "Xiaomi",
|
||||
'chid': "26002",
|
||||
'subchid': "26002",
|
||||
'os': "1",
|
||||
'screenpx': "2670*1200",
|
||||
'nettype': "wifi",
|
||||
'audit': "1",
|
||||
'force': "1"
|
||||
})
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
config = json.loads(extend)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
config = {}
|
||||
self.timeout = config.get('timeout', 15)
|
||||
self.host = config.get('host', 'http://api-live.vfilm.life')
|
||||
self.android_id = config.get('android_id', '6617a62678360a86')
|
||||
self.init_sign_salt = config.get('init_sign_salt', 'lsdfnrtpmslscndy')
|
||||
self.app_cert_sha1 = config.get('app_cert_sha1', '70:27:C9:DC:98:96:75:CD:35:DB:0C:CE:AC:CA:84:0A:B7:1E:B5:7F')
|
||||
self.private_key = config.get('private_key', 'MIICWwIBAAKBgQDYJzTUOgYdR/eIhsjpNMYWQGYl3pBycwKDoL6KThpPwrZQ9+xv\nLSaPj92HQknVaWR/RD6tHVRysChoeqAFyyQUe4UXAYnJDNlurpELb5HUIBFgmO97\niIOJCK6zbmnHT6WOHYaODTqrmX6NBgLjoFiDYBPYxG1T/K1uZ47xQDHFQQIDAQAB\nAoGAEpT8Q6phUC8ZppD/wJya0tribSr++/fLJYmyF62zMVwp1DgcCUq2X+0cPD6E\nnmYbD53MTZGR6vId5y1ziEv4Y+nu5EUyDk1xeGIxojpLhxuRoCbBt+LMJ1YUxv6p\n6F4SNwQ10U78m829Ud50mJBvkt2Vg8607SUrWheydvWHyAECQQDvayhgX5XEFaha\nUtPp5pPIkKBqHnLGm4et8be/jIIFhY9CIJbKLsqc0OFwNvz46GtRQwrtHP7LxTEF\nYT0C6CahAkEA5x+OqN/iykZIHc6Z2qZfAiLjPnQJu9DTXC/kt3TlsCc3XPNkXlAu\nq786LluH6dzQfDbLpmODtzNWavfgCtE6oQJAdTsJKDdlg//+0UthTFSE5F48zfle\nxfT9+KQ1Duvj9oQxY3XFn/ZNa3+0A1hJgi977Oxg+z2JXYmOuU2lrDi0QQJAMWwA\nF4B4gIRy21zYbXbyDgTjzvEFO9I1wBrFr60hiH96STgKmFhRAozLpioQcCO1uToG\nZjgVbFFgA1Op5uZCwQJAL1ziHIphaoCpHnnESidt3Nlrzqj/5uEpdHu7ZvPuZYya\nU8e1AhjeP+zKvfJUiXwDGuDZLx5Xe0BK8Bu72sdKcQ==')
|
||||
self.headers['bundleid'] = config.get('bundleid', 'com.ytwl.fhtq')
|
||||
self.token = config.get('token', '')
|
||||
imsi_id = config.get('imsi_id', '1')
|
||||
self.headers['sign'] = self.sign_encrypt(f'jing##&&&wei##&&&fuwu##{imsi_id}&&&idian##&&&she##{self.android_id}&&&mdian##{self.android_id}&&&olian##&&&an##{self.android_id}')
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
timestamp = self.timestamp()
|
||||
payload = {
|
||||
'applock': "0",
|
||||
'ncode': self.init_sign(timestamp),
|
||||
'force': "1",
|
||||
'retime': timestamp
|
||||
}
|
||||
response = self.post(f'{self.host}/news/tv/columns', data=payload, headers=self.headers, timeout=self.timeout, verify=False).json()
|
||||
classes = []
|
||||
for i in response['data']['list']:
|
||||
if i['is_show_recommend'] == 1:
|
||||
home_class_id = i.get('column_id')
|
||||
continue
|
||||
classes.append({'type_id': i['column_id'], 'type_name': i['name']})
|
||||
timestamp = self.timestamp()
|
||||
payload = {
|
||||
'column_id': home_class_id or '164',
|
||||
'ncode': self.init_sign(timestamp),
|
||||
'page': "1",
|
||||
'retime': timestamp
|
||||
}
|
||||
response = self.post(f'{self.host}/news/tv/sectionsPageByColumn', data=payload, headers=self.headers, timeout=self.timeout, verify=False).json()
|
||||
section_list = response['data']['section_list']
|
||||
videos = []
|
||||
for i in section_list:
|
||||
for j in i.get('tv_list', []):
|
||||
videos.append({
|
||||
'vod_id': j.get('news_id'),
|
||||
'vod_name': j.get('title', j.get('sub_title')),
|
||||
'vod_pic': j.get('ver_pic')
|
||||
})
|
||||
return {'class': classes, 'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
timestamp = self.timestamp()
|
||||
payload = {
|
||||
'column_id': tid,
|
||||
'ncode': self.init_sign(timestamp),
|
||||
'page': pg,
|
||||
'retime': timestamp
|
||||
}
|
||||
response = self.post(f'{self.host}/news/tv/tvListByColumn', data=payload, headers=self.headers, timeout=self.timeout, verify=False).json()
|
||||
videos = []
|
||||
for i in response['data']['list']:
|
||||
up_count = i.get('up_count', '')
|
||||
if up_count:
|
||||
up_count = f'{up_count}集'
|
||||
videos.append({
|
||||
'vod_id': i.get('news_id'),
|
||||
'vod_name': i.get('title'),
|
||||
'vod_pic': i.get('ver_pic'),
|
||||
'vod_remarks': up_count,
|
||||
'vod_area': i.get('area'),
|
||||
'vod_class': i.get('cat'),
|
||||
'vod_score': i.get('score'),
|
||||
'vod_year': i.get('pubdate')
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
timestamp = self.timestamp()
|
||||
payload = {
|
||||
'ncode': self.init_sign(timestamp),
|
||||
'signKey': self.signKey(timestamp),
|
||||
'page': pg,
|
||||
'is_check': "0",
|
||||
'keyword': key,
|
||||
'retime': timestamp
|
||||
}
|
||||
response = self.post(f'{self.host}/search/wordinfo', data=payload, headers=self.headers, timeout=self.timeout, verify=False).json()
|
||||
videos = []
|
||||
for i in response['data']['search_list']:
|
||||
for j in i.get('list',[]):
|
||||
vod_remarks = j.get('up_count')
|
||||
if vod_remarks:
|
||||
vod_remarks = f'{vod_remarks}集'
|
||||
else:
|
||||
vod_remarks = j.get('news_type_name')
|
||||
videos.append({
|
||||
'vod_id': j.get('news_id'),
|
||||
'vod_name': j.get('origin_title',j.get('title')),
|
||||
'vod_pic': j.get('ver_pic'),
|
||||
'vod_content': j.get('desc'),
|
||||
'vod_remarks': vod_remarks,
|
||||
'vod_area': j.get('area'),
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
timestamp = self.timestamp()
|
||||
payload = {
|
||||
'ncode': self.init_sign(timestamp),
|
||||
'signKey': self.signKey(timestamp),
|
||||
'news_id': ids[0],
|
||||
'retime': timestamp
|
||||
}
|
||||
response = self.post(f'{self.host}/news/tv/detail', data=payload, headers=self.headers, timeout=self.timeout, verify=False).json()
|
||||
data = response['data']
|
||||
timestamp2 = self.timestamp()
|
||||
payload = {
|
||||
'next': "0",
|
||||
'pl_id': "",
|
||||
'playlink_num': "1",
|
||||
'ncode': self.init_sign(timestamp2),
|
||||
'format': "high",
|
||||
'mobile': "",
|
||||
'check': "0",
|
||||
'mpl_id': "",
|
||||
'news_id': ids[0],
|
||||
'retime': timestamp2,
|
||||
'resite': "",
|
||||
'signKey': self.signKey(timestamp2),
|
||||
'bid': "300",
|
||||
'retry': "0"
|
||||
}
|
||||
response = self.post(f'{self.host}/news/tv/multiDetail', data=payload, headers=self.headers, timeout=self.timeout, verify=False).json()
|
||||
data_ = response['data']['data']
|
||||
data2_ = self.decrypt(data_)
|
||||
data2 = json.loads(data2_)
|
||||
max_up_count = data2.get('max_up_count',data2.get('up_count'))
|
||||
news_id = data2['news_id']
|
||||
site_list_test = data2.get('test')
|
||||
if site_list_test:
|
||||
site_list = site_list_test.get('site_list',[])
|
||||
vod_play_froms = []
|
||||
vod_play_froms.extend(site_list)
|
||||
vod_play_froms = [str(item) for item in vod_play_froms]
|
||||
vod_play_urls = []
|
||||
for i in vod_play_froms:
|
||||
urls = []
|
||||
for j in range(1,int(max_up_count) + 1):
|
||||
urls.append(f"第{j}集${j}@{news_id}@{i}")
|
||||
vod_play_urls.append('#'.join(urls))
|
||||
videos = []
|
||||
up_count = data.get('up_count',data.get('max_up_count'))
|
||||
if up_count:
|
||||
up_count = f'{up_count}集'
|
||||
videos.append({
|
||||
'vod_id': data.get('news_id'),
|
||||
'vod_name': data.get('title'),
|
||||
'vod_content': data.get('desc'),
|
||||
'vod_director': data.get('dir'),
|
||||
'vod_actor': data.get('act'),
|
||||
'vod_class': data.get('cat'),
|
||||
'vod_remarks': up_count,
|
||||
'vod_area': data.get('area'),
|
||||
'vod_play_from': '$$$'.join(vod_play_froms),
|
||||
'vod_play_url': '$$$'.join(vod_play_urls)
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
jx, url, play_header = 0, '', {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 15; 24129PN74C Build/AP3A.240617.008)'}
|
||||
episodes, news_id, resite = id.split('@', 2)
|
||||
timestamp = self.timestamp()
|
||||
payload = {
|
||||
'next': "0",
|
||||
'pl_id': "",
|
||||
'playlink_num': episodes,
|
||||
'ncode': self.init_sign(timestamp),
|
||||
'format': 'high',
|
||||
'mobile': "",
|
||||
'check': "0",
|
||||
'mpl_id': "",
|
||||
'news_id': news_id,
|
||||
'retime': timestamp,
|
||||
'resite': resite,
|
||||
'signKey': self.signKey(timestamp),
|
||||
'bid': '300',
|
||||
'retry': "0"
|
||||
}
|
||||
response = self.post(f'{self.host}/news/tv/multiDetail', data=payload, headers=self.headers,timeout=self.timeout, verify=False).json()
|
||||
data_ = response['data']['data']
|
||||
data_ = self.decrypt(data_)
|
||||
data = json.loads(data_)
|
||||
url_list = data.get('url_list', [])
|
||||
if url_list:
|
||||
for i in url_list:
|
||||
play_url = i.get('surl', '')
|
||||
if play_url.startswith('http'):
|
||||
url = play_url
|
||||
break
|
||||
if url:
|
||||
return {'jx': 0, 'parse': 0, 'url': url, 'header': play_header}
|
||||
cp_data = data['cp_data']
|
||||
wanneng = cp_data.get('wanneng', '')
|
||||
parse_url = ''
|
||||
if wanneng and isinstance(wanneng, dict):
|
||||
parse_url = wanneng.get('postUrl', '')
|
||||
type = cp_data.get('TYPE')
|
||||
if type == 'DIRECT':
|
||||
for i in cp_data['V']:
|
||||
for j in i.values():
|
||||
if j.startswith(('http://', 'https://')):
|
||||
play_url = j
|
||||
break
|
||||
if 'play_url' in locals() and play_url:
|
||||
return {'jx': jx, 'parse': '0', 'url': play_url, 'header': play_header}
|
||||
try:
|
||||
if parse_url:
|
||||
parse_data = self.fetch(parse_url, headers={'User-Agent': "okhttp/3.10.0", 'Accept-Encoding': "gzip"}, timeout=self.timeout, verify=False).text
|
||||
except Exception:
|
||||
return {'jx': 0, 'parse': 0, 'url': ''}
|
||||
if not parse_data:
|
||||
return {'jx': 0, 'parse': 0, 'url': ''}
|
||||
timestamp2 = self.timestamp()
|
||||
vid_format = data.get('format', 'high')
|
||||
payload2 = {
|
||||
'pl_id': data.get('pl_id', ''),
|
||||
'is_down': data.get('is_down', '0'),
|
||||
'data': parse_data,
|
||||
'playlink_num': episodes,
|
||||
'ncode': self.init_sign(timestamp),
|
||||
'format': vid_format,
|
||||
'cp_id': data.get('cp_id', ''),
|
||||
'mpl_id': data.get('mpl_id', ''),
|
||||
'url': data['web_url'],
|
||||
'retime': timestamp2,
|
||||
'wn_info': json.dumps(data['cp_data'].get('wanneng', '')),
|
||||
'site': data['site'],
|
||||
'news_type': "null",
|
||||
'web_url': data['web_url'],
|
||||
'mc': "null",
|
||||
'bid': self.bid(vid_format)
|
||||
}
|
||||
response = self.post(f'{self.host}/parse/index/parse', data=payload2, headers=self.headers, timeout=self.timeout, verify=False).json()
|
||||
data2_ = response['data']['data']
|
||||
data2_ = self.decrypt(data2_)
|
||||
data2 = json.loads(data2_)
|
||||
play_url = data2['video'][0]['url']
|
||||
if play_url.startswith('http'):
|
||||
url = play_url
|
||||
return {'jx': jx, 'parse': '0', 'url': url, 'header': play_header}
|
||||
|
||||
def timestamp(self):
|
||||
timestamp = time.time()
|
||||
return str(int(round(timestamp * 1000)))
|
||||
|
||||
def md5(self,str):
|
||||
md5_hash = hashlib.md5()
|
||||
md5_hash.update(str.encode('utf-8'))
|
||||
return md5_hash.hexdigest()
|
||||
|
||||
def init_sign(self, timestamp):
|
||||
first_md5 = self.md5(f"{self.init_sign_salt}{self.token}{self.android_id}{timestamp}")
|
||||
combined = first_md5[:10] + first_md5[22:]
|
||||
return self.md5(combined)
|
||||
|
||||
def get_strings(self, str_param, str2_param):
|
||||
strlist = [
|
||||
"afv", "Cs", "D", "bd", "cs", "h", "i0", "j0", "K", "L", "M8", "N", "O", "P", "Q", "R",
|
||||
"k", "l", "m", "n", "o", "p", "q", "r", "s", "t7", "Z", "A", "B", "E", "F", "G", "H", "I",
|
||||
"J", "S16", "T", "d", "e", "f", "g", "U", "V", "W6", "u", "v", "w", "x", "y", "Xd", "Y",
|
||||
"Za0", "gw", "Us", "Vd", "Wv", "X", "Y", "Zx4", "afv", "Cs", "D", "bd", "cs", "h", "i",
|
||||
"j", "K0", "L", "M", "N", "O0", "P", "Q", "R", "k", "l1", "m", "n", "o", "p", "q", "r",
|
||||
"s1", "t", "Z", "A", "B", "E", "F5", "G", "H", "I0", "J", "S", "T", "d", "e", "f", "g1",
|
||||
"U1", "V", "W3", "u", "v2", "w", "x", "y", "Xd", "Y", "Za", "gw", "Us", "Vd", "Wv", "X",
|
||||
"Y", "Zx"
|
||||
]
|
||||
bytes_data = str_param.encode('utf-8')
|
||||
as_list = strlist.copy()
|
||||
if str2_param and len(str2_param) > 0:
|
||||
for s in str2_param.split(','):
|
||||
as_list.append(s)
|
||||
sb = []
|
||||
for i in range(len(bytes_data)):
|
||||
abs_val = abs(bytes_data[i] - i) % 100
|
||||
if abs_val < len(as_list):
|
||||
sb.append(as_list[abs_val])
|
||||
return ''.join(sb) + "=="
|
||||
|
||||
def send_increment_data(self, str_param, str2_param, str3_param=None):
|
||||
bytes_data = str2_param.encode('utf-8')
|
||||
sb = []
|
||||
loop_count = len(bytes_data) // 2
|
||||
for i in range(loop_count):
|
||||
index = (len(bytes_data) - (i % 8)) - 1
|
||||
b = bytes_data[index]
|
||||
sb.append(str(abs(bytes_data[i] - b) % 100))
|
||||
sb.append("$")
|
||||
sb.append(str((bytes_data[(len(bytes_data) - 1) - i] + b) % 100))
|
||||
sb.append("$")
|
||||
substring = ''.join(sb)[loop_count:]
|
||||
strings = self.get_strings(substring, str3_param)
|
||||
result = str_param + strings
|
||||
return result
|
||||
|
||||
def sign_encrypt(self,text):
|
||||
key = "ZXJsaW5nZXJlcm5pYW5zaXl1ZWVyc2hp".encode('utf-8')
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
text_bytes = text.encode('utf-8')
|
||||
padded_text = pad(text_bytes, AES.block_size)
|
||||
encrypted_bytes = cipher.encrypt(padded_text)
|
||||
encrypted_base64 = base64.b64encode(encrypted_bytes).decode('utf-8')
|
||||
return encrypted_base64
|
||||
|
||||
def decrypt(self, encrypted_data):
|
||||
try:
|
||||
private_key = self.private_key
|
||||
if not private_key.startswith('-----'):
|
||||
private_key = f'-----BEGIN RSA PRIVATE KEY-----\n{private_key}\n-----END RSA PRIVATE KEY-----'
|
||||
private_key = RSA.importKey(private_key)
|
||||
cipher = PKCS1_v1_5.new(private_key)
|
||||
decoded_data = base64.b64decode(encrypted_data)
|
||||
key_size = private_key.size_in_bytes()
|
||||
decrypted_text_parts = []
|
||||
if len(decoded_data) > key_size:
|
||||
i = 0
|
||||
while i < len(decoded_data):
|
||||
chunk = decoded_data[i:i + key_size]
|
||||
decrypted_chunk = cipher.decrypt(chunk, None)
|
||||
if decrypted_chunk is None:
|
||||
raise ValueError("解密失败,可能是数据损坏或密钥不匹配")
|
||||
decrypted_text_parts.append(decrypted_chunk.decode('utf-8'))
|
||||
i += key_size
|
||||
else:
|
||||
decrypted_data = cipher.decrypt(decoded_data, None)
|
||||
if decrypted_data is None:
|
||||
raise ValueError("解密失败,可能是数据损坏或密钥不匹配")
|
||||
decrypted_text_parts.append(decrypted_data.decode('utf-8'))
|
||||
return ''.join(decrypted_text_parts)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def signKey(self,timestamp):
|
||||
return self.send_increment_data(timestamp, f'{self.android_id}{self.md5(self.app_cert_sha1)}{timestamp}')
|
||||
|
||||
def bid(self, str_param):
|
||||
str_to_c2 = {
|
||||
"fluent": 2, "normal": 4, "super2": 15, "4K": 22, "4k": 23, "原画": 13,
|
||||
"标清": 3, "极速": 1, "流畅": 0, "蓝光": 14, "超清": 9, "高清": 5,
|
||||
"360P": 17, "360p": 16, "540P": 8, "540p": 7, "720P": 12, "720p": 11,
|
||||
"high": 6, "1080P": 19, "1080p": 18, "1280P": 21, "1280p": 20, "super": 10
|
||||
}
|
||||
result_mapping = [
|
||||
(0, 2, "200", "fluent"), (3, 4, "300", "normal"), (5, 8, "300", "high"), (9, 10, "500", "super"),
|
||||
(11, 12, "400", "super2"),
|
||||
(13, 15, "600", "super2"), (16, 17, "200", "normal"), (18, 19, "500", "super2"), (20, 21, "600", "super2"),
|
||||
(22, 23, "800", "super2")
|
||||
]
|
||||
if not str_param:
|
||||
return {}
|
||||
c2 = str_to_c2.get(str_param, 65535)
|
||||
for min_val, max_val, bid, fmt in result_mapping:
|
||||
if min_val <= c2 <= max_val:
|
||||
return bid
|
||||
return "300"
|
||||
|
||||
def homeVideoContent(self):
|
||||
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,185 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
import sys,json,base64,urllib3
|
||||
from base.spider import Spider
|
||||
from urllib.parse import urljoin
|
||||
from Crypto.Util.Padding import unpad
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host,site,path,timeout,targets = '','','',5,{b'\xb1\xa1\xa6\xe2'.decode('big5'),b'\xc2\xd7\xc0\xed'.decode('gbk'),b'\xba\xd6\xa7Q'.decode('big5'),b"\xff\xfe\x0cT'`".decode('utf-16')}
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2; rv:107) Gecko/20100101 Firefox/98',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'referer': '',
|
||||
'Cookie': ''
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
if extend:
|
||||
ext = json.loads(extend)
|
||||
else:
|
||||
ext = {'host':'https://api.kaiduan.fun','path':{'search':'/user/movie/cms/v1/search?name={key}&page=1&count=10','detail':'/user/movie/cms/v1/play?id='}}
|
||||
self.host = ext['host'].rstrip('/')
|
||||
self.headers['referer'] = f'{self.host}/'
|
||||
self.headers['Cookie'] = f'{self.host}/'
|
||||
self.site = ext.get('site')
|
||||
self.path = ext.get('path')
|
||||
timeout = ext.get('timeout')
|
||||
if timeout: self.timeout = timeout
|
||||
except Exception:
|
||||
self.host = ''
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
return {'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':'纪录片'}]}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return self.categoryContent('电影,连续剧,动漫,短剧,综艺,纪录片',1,False,{})
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/user/movie/cms/v1/category?count=20&names={tid}&page={pg}',headers=self.headers, verify=False, timeout=self.timeout).text
|
||||
data = json.loads(self.decrypt(response))['datas']
|
||||
videos = self.arr2vods(data)
|
||||
return {'list': videos}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host or str(pg) != '1': return None
|
||||
if self.site:
|
||||
path = f'/other/module?name={self.site}¶ms=search|{key}'
|
||||
else:
|
||||
if '{key}' in self.path['search']:
|
||||
path = self.path['search'].replace('{key}',key)
|
||||
else:
|
||||
path = f"{self.path['search']}{key}"
|
||||
if not path.startswith('http'):
|
||||
path = f'{self.host}{path}'
|
||||
response = self.decrypt(self.fetch(path, headers=self.headers, verify=False, timeout=self.timeout).text)
|
||||
response = json.loads(response)
|
||||
if 'datas' in response or 'data' in response:
|
||||
data = response.get('datas',response.get('data'))
|
||||
else:
|
||||
data = response
|
||||
videos = self.arr2vods(data)
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.host: return None
|
||||
if str(ids[0]).startswith('http'):
|
||||
path = ids[0]
|
||||
else:
|
||||
if '{key}' in ids[0]:
|
||||
path = self.path['detail'].replace('{key}',ids[0])
|
||||
else:
|
||||
path = f"{self.path['detail']}{ids[0]}"
|
||||
if not path.startswith('http'): path = f'{self.host}{path}'
|
||||
data = self.decrypt(self.fetch(path,headers=self.headers, verify=False, timeout=self.timeout).text)
|
||||
raw_data = json.loads(data)
|
||||
show, play_urls = [], []
|
||||
if 'data' in raw_data:
|
||||
data = raw_data['data']
|
||||
else:
|
||||
data = raw_data
|
||||
classify = data.get('classify',data.get('category',data.get('vodClass')))
|
||||
if isinstance(classify, str) and any(i in classify for i in self.targets): return None
|
||||
urls2 = []
|
||||
for i in data.get('episodes',data.get('episodeList',[])):
|
||||
if isinstance(i,dict) and 'episode' in i:
|
||||
urls = []
|
||||
for j in i['episode']:
|
||||
urls.append(f"{j['name']}${j['url']}")
|
||||
show.append(i.get('title', i.get('name')))
|
||||
play_urls.append('#'.join(urls))
|
||||
else:
|
||||
urls2.append(f"{i['name']}${i['url']}")
|
||||
if urls2:
|
||||
show.append('1线')
|
||||
play_urls = ['#'.join(urls2)]
|
||||
if not(show and play_urls) and 'datas' in raw_data:
|
||||
for i in raw_data['datas']:
|
||||
show.append(i['name'])
|
||||
play_urls.append(i['urls'])
|
||||
video = {
|
||||
'vod_id': ids[0],
|
||||
'vod_name': data.get('name',data.get('vodName')),
|
||||
'vod_pic': data.get('pic',data.get('vodPic')),
|
||||
'vod_remarks': data.get('remarks',data.get('vodRemarks')),
|
||||
'vod_year': data.get('year',data.get('vodYear')),
|
||||
'vod_area': data.get('area',data.get('vodArea')),
|
||||
'vod_actor': data.get('actor',data.get('vodActor')),
|
||||
'vod_director': data.get('director',data.get('vodDirector')),
|
||||
'vod_content': data.get('details',data.get('vodContent',data.get('content'))),
|
||||
'vod_play_from': '$$$'.join(show),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': classify
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, url, vip_flags):
|
||||
try:
|
||||
if '|' in url: url = self.raw_url(url)
|
||||
except Exception:
|
||||
pass
|
||||
return { 'jx': 0, 'parse': '0', 'url': url, 'header': {'User-Agent':'ijkplayer/1.0.0 (Linux;Android 11) ExoPlayerLib/2.14.1','Accept-Encoding':'gzip'}}
|
||||
|
||||
def decrypt(self,data):
|
||||
prefix = "AkEdSJx"
|
||||
if not data.startswith(prefix): return data
|
||||
base64_str = data[len(prefix):]
|
||||
decode_bytes = base64.b64decode(base64_str)
|
||||
if len(decode_bytes) < 32:
|
||||
raise ValueError("加密数据长度不足")
|
||||
key = decode_bytes[:16]
|
||||
iv = decode_bytes[16:32]
|
||||
ciphertext = decode_bytes[32:]
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
plaintext_bytes = unpad(cipher.decrypt(ciphertext), AES.block_size)
|
||||
return plaintext_bytes.decode('utf-8')
|
||||
|
||||
def arr2vods(self,arr):
|
||||
videos = []
|
||||
for i in arr:
|
||||
classify = i.get('classify',i.get('category',i.get('vodClass')))
|
||||
if not(isinstance(classify, str) and any(i in classify for i in self.targets)):
|
||||
videos.append({
|
||||
'vod_id': i.get('url',i.get('vodId',i.get('id'))),
|
||||
'vod_name': i.get('name',i.get('vodName')),
|
||||
'vod_pic': i.get('pic',i.get('vodPic')),
|
||||
'vod_remarks': i.get('remarks',i.get('vodRemarks')),
|
||||
'vod_year': i.get('year',i.get('vodYear')),
|
||||
'type_name': classify
|
||||
})
|
||||
return videos
|
||||
|
||||
def raw_url(self,original_url):
|
||||
try:
|
||||
response = self.fetch(original_url,allow_redirects=False,stream=True,timeout=20)
|
||||
if 300 <= response.status_code < 400:
|
||||
redirect_location = response.headers.get('Location')
|
||||
if redirect_location:
|
||||
real_url = urljoin(original_url, redirect_location)
|
||||
return real_url
|
||||
return original_url
|
||||
except Exception:
|
||||
return original_url
|
||||
|
||||
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,135 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
import sys,time,uuid,json,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
device_id,cms_host,parses = '','',{}
|
||||
headers = {
|
||||
'User-Agent': "okhttp/4.9.0",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip",
|
||||
'appId': "522",
|
||||
'requestId': '',
|
||||
'timestamp': '',
|
||||
'version': "2.4.18",
|
||||
'package': "com.lanlanys.app",
|
||||
'deviceId': '',
|
||||
'AppKey': 'dec99f2e3071ece8c7aa5b2fd5800a6f',
|
||||
'AppSecret': 'd686e49b39c996326bfe7b5f28846bb8',
|
||||
'oaid': "",
|
||||
'imei': "",
|
||||
'network': "yd",
|
||||
'appName': "lanlan",
|
||||
'apiVersion': "w2yj3m9b",
|
||||
}
|
||||
headers2 = {
|
||||
'User-Agent': "okhttp/4.9.0",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip"
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
config = json.loads(extend)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
config = {}
|
||||
host = config.get('host', 'http://llsp2.洛阳it商城.com').rstrip('/')
|
||||
self.headers['deviceId'] = config.get('deviceId', 'e718f6482ea3d4ef')
|
||||
self.cms_host = host
|
||||
|
||||
def homeContent(self, filter):
|
||||
self.headers['requestId'] = str(uuid.uuid4())
|
||||
self.headers['timestamp'] = self.timestamp()
|
||||
response = self.fetch(f"{self.cms_host}/api.php/provide/index?tid=0", headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
classes = []
|
||||
videos = data.get('tj',[])
|
||||
for j in data.get('sub_data',[]):
|
||||
if 'data' in j:
|
||||
videos.extend(j['data'])
|
||||
classes.append({'type_id': j['type_id'], 'type_name': j['type_name']})
|
||||
return {'class': classes, 'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
self.headers['requestId'] = str(uuid.uuid4())
|
||||
self.headers['timestamp'] = self.timestamp()
|
||||
response = self.fetch(f"{self.cms_host}/api.php/provide/nav?tid={tid}&type=全部&area=全部&year=全部&orderBy=vod_time&pg={pg}", headers=self.headers, verify=False).json()
|
||||
|
||||
return {'list': response['data']['data'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
self.headers['requestId'] = str(uuid.uuid4())
|
||||
self.headers['timestamp'] = self.timestamp()
|
||||
response = self.fetch(f"{self.cms_host}/api.php/provide/search?wd={key}&tid=0&pg={pg}", headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
for i in data:
|
||||
if 'vod_play_from' in i:
|
||||
i.pop('vod_play_from')
|
||||
return {'list': data, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
self.headers['requestId'] = str(uuid.uuid4())
|
||||
self.headers['timestamp'] = self.timestamp()
|
||||
response = self.fetch(f"{self.cms_host}/api.php/provide/vod?ids={ids[0]}&device_id={self.device_id}", headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
vod_show = []
|
||||
vod_urls = []
|
||||
for i in data['vod_play_url']:
|
||||
vod_from = i['id']
|
||||
show = i['name']
|
||||
if show != vod_from:
|
||||
show += f" ({vod_from})"
|
||||
vod_show.append(show)
|
||||
self.parses[vod_from] = i.get('parse',[])
|
||||
vod_url = []
|
||||
for j in i['data']:
|
||||
vod_url.append(f"{j['name']}${vod_from}@{j['url']}")
|
||||
vod_urls.append('#'.join(vod_url))
|
||||
data['vod_play_from'] = '$$$'.join(vod_show)
|
||||
data['vod_play_url'] = '$$$'.join(vod_urls)
|
||||
return {'list': [data]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
header = {
|
||||
'User-Agent': ' Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/929.36 (KHTML, like Gecko) Chrome/86.0.3347.284 Safari/709.36'}
|
||||
jx, url = 0, ''
|
||||
play_from, raw_url = id.split('@')
|
||||
parses = self.parses.get(play_from, [])
|
||||
for i in parses:
|
||||
try:
|
||||
data = self.fetch(f'{i}{raw_url}', headers=self.headers2, verify=False).json()
|
||||
data = data['data']
|
||||
play_url = data['url']
|
||||
if play_url.startswith('http'):
|
||||
url = play_url
|
||||
header = data.get('header', header)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
return {'jx': jx, 'parse': '0', 'url': url, 'header': header}
|
||||
|
||||
def timestamp(self):
|
||||
return str(int(time.time() * 1000))
|
||||
|
||||
def homeVideoContent(self):
|
||||
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,423 @@
|
||||
"""
|
||||
@header({
|
||||
searchable: 1,
|
||||
filterable: 1,
|
||||
quickSearch: 1,
|
||||
title: '新浪CMS',
|
||||
lang: 'hipy'
|
||||
})
|
||||
"""
|
||||
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
import sys
|
||||
|
||||
sys.path.append('..')
|
||||
try:
|
||||
# from base.spider import Spider as Spider
|
||||
from base.spider import Spider
|
||||
except ImportError:
|
||||
from t4.base.spider import Spider
|
||||
import time
|
||||
import re
|
||||
from urllib import request, parse
|
||||
import urllib
|
||||
import urllib.request
|
||||
from xml.etree.ElementTree import fromstring, ElementTree as et
|
||||
|
||||
"""
|
||||
配置示例:
|
||||
t4的配置里ext节点会自动变成api对应query参数extend,但t4的ext字符串不支持路径格式,比如./开头或者.json结尾
|
||||
api里会自动含有ext参数是base64编码后的选中的筛选条件
|
||||
{
|
||||
"key":"hipy_t4_新浪资源",
|
||||
"name":"新浪资源(hipy_t4)",
|
||||
"type":4,
|
||||
"api":"http://192.168.31.49:5707/api/v1/vod/新浪资源",
|
||||
"searchable":1,
|
||||
"quickSearch":0,
|
||||
"filterable":1,
|
||||
"ext":""
|
||||
},
|
||||
{
|
||||
"key": "hipy_t3_新浪资源",
|
||||
"name": "新浪资源(hipy_t3)",
|
||||
"type": 3,
|
||||
"api": "{{host}}/txt/hipy/新浪资源.py",
|
||||
"searchable": 1,
|
||||
"quickSearch": 0,
|
||||
"filterable": 1,
|
||||
"ext": ""
|
||||
},
|
||||
"""
|
||||
|
||||
|
||||
class Spider(Spider): # 元类 默认的元类 type
|
||||
def getName(self):
|
||||
return "新浪CMS" # 除去少儿不宜的内容
|
||||
|
||||
filterate = False
|
||||
|
||||
def init(self, extend=""):
|
||||
print("============{0}============".format(extend))
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
timeClass = time.localtime(time.time())
|
||||
cateManual = {
|
||||
'动漫': '3',
|
||||
'动漫电影': '17',
|
||||
'综艺': '4',
|
||||
'纪录片': '5',
|
||||
'动作片': '6',
|
||||
'爱情片': '7',
|
||||
'科幻片': '8',
|
||||
'战争片': '9',
|
||||
'剧情片': '10',
|
||||
'恐怖片': '11',
|
||||
'喜剧片': '12',
|
||||
'大陆剧': '13',
|
||||
'港澳剧': '14',
|
||||
'台湾剧': '15',
|
||||
'欧美剧': '16',
|
||||
'韩剧': '18',
|
||||
'日剧': '20',
|
||||
'泰剧': '21',
|
||||
'体育': '23'
|
||||
}
|
||||
# if timeClass.tm_hour>22:
|
||||
# cateManual['伦理片']='22'
|
||||
# self.filterate=False
|
||||
classes = []
|
||||
for k in cateManual:
|
||||
classes.append({
|
||||
'type_name': k,
|
||||
'type_id': cateManual[k]
|
||||
})
|
||||
result['class'] = classes
|
||||
if (filter):
|
||||
result['filters'] = self.config['filter']
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
xmlTxt = self.custom_webReadFile(
|
||||
urlStr='https://api.xinlangapi.com/xinlangapi.php/provide/vod/from/xlyun/at/xml/?ac=list&h=24')
|
||||
tree = et(fromstring(xmlTxt))
|
||||
root = tree.getroot()
|
||||
listXml = root.iter('list')
|
||||
videos = self.custom_list(html=listXml)
|
||||
result = {
|
||||
'list': videos
|
||||
}
|
||||
return result
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
result = {}
|
||||
videos = []
|
||||
pagecount = 1
|
||||
limit = 20
|
||||
total = 9999
|
||||
Url = 'https://api.xinlangapi.com/xinlangapi.php/provide/vod/from/xlyun/at/xml/?ac=list&t={0}&pg={1}'.format(
|
||||
tid, pg)
|
||||
xmlTxt = self.custom_webReadFile(urlStr=Url)
|
||||
tree = et(fromstring(xmlTxt))
|
||||
root = tree.getroot()
|
||||
listXml = root.iter('list')
|
||||
for vod in listXml:
|
||||
pagecount = vod.attrib['pagecount']
|
||||
limit = vod.attrib['pagesize']
|
||||
total = vod.attrib['recordcount']
|
||||
videos = self.custom_list(html=root.iter('list'))
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = pagecount
|
||||
result['limit'] = limit
|
||||
result['total'] = total
|
||||
return result
|
||||
|
||||
def detailContent(self, array):
|
||||
result = {}
|
||||
aid = array[0].split('###')
|
||||
id = aid[1]
|
||||
logo = aid[2]
|
||||
title = aid[0]
|
||||
vod_play_from = ['播放线路', ]
|
||||
vod_year = ''
|
||||
vod_actor = ''
|
||||
vod_content = ''
|
||||
vod_director = ''
|
||||
type_name = ''
|
||||
vod_area = ''
|
||||
vod_lang = ''
|
||||
vodItems = []
|
||||
vod_play_url = []
|
||||
try:
|
||||
url = 'https://api.xinlangapi.com/xinlangapi.php/provide/vod/from/xlyun/at/xml/?ac=detail&ids=' + id
|
||||
xmlTxt = self.custom_webReadFile(urlStr=url)
|
||||
jRoot = et(fromstring(xmlTxt))
|
||||
xmlList = jRoot.iter('list')
|
||||
for vod in xmlList:
|
||||
for x in vod:
|
||||
for v in x:
|
||||
if v.tag == 'actor':
|
||||
vod_actor = v.text
|
||||
if v.tag == 'director':
|
||||
vod_director = v.text
|
||||
if v.tag == 'des':
|
||||
vod_content = v.text
|
||||
if v.tag == 'area':
|
||||
vod_area = v.text
|
||||
if v.tag == 'year':
|
||||
vod_year = v.text
|
||||
if v.tag == 'type':
|
||||
type_name = v.text
|
||||
if v.tag == 'lang':
|
||||
vod_lang = v.text
|
||||
|
||||
temporary = self.custom_RegexGetText(Text=xmlTxt, RegexText=r'<dd flag="xlyun">(.+?)</dd>', Index=1)
|
||||
temporary = temporary.replace('<![CDATA[', '').replace(']]>', '')
|
||||
vodItems = self.custom_EpisodesList(temporary)
|
||||
joinStr = "#".join(vodItems)
|
||||
vod_play_url.append(joinStr)
|
||||
except:
|
||||
pass
|
||||
vod = {
|
||||
"vod_id": array[0],
|
||||
"vod_name": title,
|
||||
"vod_pic": logo,
|
||||
"type_name": type_name,
|
||||
"vod_year": vod_year,
|
||||
"vod_area": vod_area,
|
||||
"vod_remarks": vod_lang,
|
||||
"vod_actor": vod_actor,
|
||||
"vod_director": vod_director,
|
||||
"vod_content": vod_content
|
||||
}
|
||||
vod['vod_play_from'] = "$$$".join(vod_play_from)
|
||||
vod['vod_play_url'] = "$$$".join(vod_play_url)
|
||||
result = {
|
||||
'list': [
|
||||
vod
|
||||
]
|
||||
}
|
||||
if self.filterate == True and self.custom_RegexGetText(Text=type_name, RegexText=r'(伦理|倫理|福利)',
|
||||
Index=1) != '':
|
||||
result = {'list': []}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg=1):
|
||||
Url = 'https://api.xinlangapi.com/xinlangapi.php/provide/vod/from/xlyun/at/xml/?ac=list&wd={0}&pg={1}'.format(
|
||||
urllib.parse.quote(key), '1')
|
||||
xmlTxt = self.custom_webReadFile(urlStr=Url)
|
||||
tree = et(fromstring(xmlTxt))
|
||||
root = tree.getroot()
|
||||
listXml = root.iter('list')
|
||||
videos = self.custom_list(html=listXml)
|
||||
result = {
|
||||
'list': videos
|
||||
}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
result = {}
|
||||
parse = 1
|
||||
url = id
|
||||
htmlTxt = self.custom_webReadFile(urlStr=url, header=self.header)
|
||||
url = self.custom_RegexGetText(Text=htmlTxt, RegexText=r"url\s*:\s*['\"](https?://[^'\"]+?\.m3u8)['\"]", Index=1)
|
||||
if url.find('.m3u8') < 1:
|
||||
url = id
|
||||
parse = 0
|
||||
result["parse"] = parse # 0=直接播放、1=嗅探
|
||||
result["playUrl"] = ''
|
||||
result["url"] = url
|
||||
result['jx'] = 0 # VIP解析,0=不解析、1=解析
|
||||
result["header"] = ''
|
||||
return result
|
||||
|
||||
config = {
|
||||
"player": {},
|
||||
"filter": {}
|
||||
}
|
||||
header = {}
|
||||
|
||||
def localProxy(self, params):
|
||||
return [200, "video/MP2T", ""]
|
||||
|
||||
# -----------------------------------------------自定义函数-----------------------------------------------
|
||||
# 正则取文本
|
||||
def custom_RegexGetText(self, Text, RegexText, Index):
|
||||
returnTxt = ""
|
||||
Regex = re.search(RegexText, Text, re.M | re.S)
|
||||
if Regex is None:
|
||||
returnTxt = ""
|
||||
else:
|
||||
returnTxt = Regex.group(Index)
|
||||
return returnTxt
|
||||
|
||||
# 分类取结果
|
||||
def custom_list(self, html):
|
||||
ListRe = html
|
||||
videos = []
|
||||
temporary = []
|
||||
for vod in ListRe:
|
||||
for value in vod:
|
||||
for x in value:
|
||||
|
||||
if x.tag == 'name':
|
||||
title = x.text
|
||||
if x.tag == 'id':
|
||||
id = x.text
|
||||
if x.tag == 'type':
|
||||
tid = x.text
|
||||
if x.tag == 'last':
|
||||
last = x.text
|
||||
temporary.append({
|
||||
"name": title,
|
||||
"id": id,
|
||||
"last": last
|
||||
})
|
||||
|
||||
if len(temporary) > 0:
|
||||
idTxt = ''
|
||||
for vod in temporary:
|
||||
idTxt = idTxt + vod['id'] + ','
|
||||
if len(idTxt) > 1:
|
||||
idTxt = idTxt[0:-1]
|
||||
url = 'https://api.xinlangapi.com/xinlangapi.php/provide/vod/from/xlyun/at/xml/?ac=detail&ids=' + idTxt
|
||||
xmlTxt = self.custom_webReadFile(urlStr=url)
|
||||
jRoot = et(fromstring(xmlTxt))
|
||||
xmlList = jRoot.iter('list')
|
||||
for vod in xmlList:
|
||||
for x in vod:
|
||||
for v in x:
|
||||
if v.tag == 'name':
|
||||
title = v.text
|
||||
if v.tag == 'id':
|
||||
vod_id = v.text
|
||||
if v.tag == 'pic':
|
||||
img = v.text
|
||||
if v.tag == 'note':
|
||||
remarks = v.text
|
||||
if v.tag == 'year':
|
||||
vod_year = v.text
|
||||
if v.tag == 'type':
|
||||
type_name = v.text
|
||||
if self.filterate == True and self.custom_RegexGetText(Text=type_name,
|
||||
RegexText=r'(伦理|倫理|福利)',
|
||||
Index=1) != '':
|
||||
continue
|
||||
vod_id = '{0}###{1}###{2}'.format(title, vod_id, img)
|
||||
# vod_id='{0}###{1}###{2}###{3}###{4}###{5}###{6}###{7}###{8}###{9}###{10}'.format(title,vod_id,img,vod_actor,vod_director,'/'.join(type_name),'/'.join(vod_time),'/'.join(vod_area),vod_lang,vod_content,vod_play_url)
|
||||
# print(vod_id)
|
||||
videos.append({
|
||||
"vod_id": vod_id,
|
||||
"vod_name": title,
|
||||
"vod_pic": img,
|
||||
"vod_year": vod_year,
|
||||
"vod_remarks": remarks
|
||||
})
|
||||
return videos
|
||||
|
||||
# 访问网页
|
||||
def custom_webReadFile(self, urlStr, header=None, codeName='utf-8'):
|
||||
html = ''
|
||||
if header == None:
|
||||
header = {
|
||||
"Referer": urlStr,
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36',
|
||||
"Host": self.custom_RegexGetText(Text=urlStr, RegexText='https*://(.*?)(/|$)', Index=1)
|
||||
}
|
||||
# import ssl
|
||||
# ssl._create_default_https_context = ssl._create_unverified_context#全局取消证书验证
|
||||
req = urllib.request.Request(url=urlStr, headers=header) # ,headers=header
|
||||
with urllib.request.urlopen(req) as response:
|
||||
html = response.read().decode(codeName)
|
||||
return html
|
||||
|
||||
# 取剧集区
|
||||
def custom_lineList(self, Txt, mark, after):
|
||||
circuit = []
|
||||
origin = Txt.find(mark)
|
||||
while origin > 8:
|
||||
end = Txt.find(after, origin)
|
||||
circuit.append(Txt[origin:end])
|
||||
origin = Txt.find(mark, end)
|
||||
return circuit
|
||||
|
||||
# 正则取文本,返回数组
|
||||
def custom_RegexGetTextLine(self, Text, RegexText, Index):
|
||||
returnTxt = []
|
||||
pattern = re.compile(RegexText, re.M | re.S)
|
||||
ListRe = pattern.findall(Text)
|
||||
if len(ListRe) < 1:
|
||||
return returnTxt
|
||||
for value in ListRe:
|
||||
returnTxt.append(value)
|
||||
return returnTxt
|
||||
|
||||
# 取集数
|
||||
def custom_EpisodesList(self, html):
|
||||
ListRe = html.split('#')
|
||||
videos = []
|
||||
for vod in ListRe:
|
||||
t = vod.split('$')
|
||||
url = t[1]
|
||||
title = t[0]
|
||||
if len(url) == 0:
|
||||
continue
|
||||
videos.append(title + "$" + url)
|
||||
return videos
|
||||
|
||||
# 取分类
|
||||
def custom_classification(self):
|
||||
xmlTxt = self.custom_webReadFile(
|
||||
urlStr='https://api.xinlangapi.com/xinlangapi.php/provide/vod/from/xlyun/at/xml/')
|
||||
tree = et(fromstring(xmlTxt))
|
||||
root = tree.getroot()
|
||||
classXml = root.iter('class')
|
||||
temporaryClass = {}
|
||||
for vod in classXml:
|
||||
for value in vod:
|
||||
if self.custom_RegexGetText(Text=value.text, RegexText=r'(福利|倫理片|伦理片)', Index=1) != '':
|
||||
continue
|
||||
temporaryClass[value.text] = value.attrib['id']
|
||||
print("'{0}':'{1}',".format(value.text, value.attrib['id']))
|
||||
return temporaryClass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from t4.core.loader import t4_spider_init
|
||||
|
||||
spider = Spider()
|
||||
t4_spider_init(spider)
|
||||
print(spider.homeContent(True))
|
||||
print(spider.homeVideoContent())
|
||||
|
||||
# T=Spider()
|
||||
# T. homeContent(filter=False)
|
||||
# T.custom_classification()
|
||||
# l=T.homeVideoContent()
|
||||
# l=T.searchContent(key='柯南',quick='')
|
||||
# l=T.categoryContent(tid='22',pg='1',filter=False,extend={})
|
||||
# for x in l['list']:
|
||||
# print(x['vod_name'])
|
||||
# mubiao= l['list'][2]['vod_id']
|
||||
# # print(mubiao)
|
||||
# playTabulation=T.detailContent(array=[mubiao,])
|
||||
# # print(playTabulation)
|
||||
# vod_play_from=playTabulation['list'][0]['vod_play_from']
|
||||
# vod_play_url=playTabulation['list'][0]['vod_play_url']
|
||||
# url=vod_play_url.split('$$$')
|
||||
# vod_play_from=vod_play_from.split('$$$')[0]
|
||||
# url=url[0].split('$')
|
||||
# url=url[1].split('#')[0]
|
||||
# # print(url)
|
||||
# m3u8=T.playerContent(flag=vod_play_from,id=url,vipFlags=True)
|
||||
# print(m3u8)
|
||||
@@ -0,0 +1,134 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import re,sys,json,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host,player,tares = {
|
||||
'User-Agent': "okhttp/3.10.0",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip"
|
||||
}, 'http://154.219.117.219:8080',{},{b'\xad\xdb\xb2z'.decode('big5'),b"\xff\xfe\x0cT'`".decode('utf-16'),b'\xb1\xa1\xa6\xe2'.decode('big5'),b'\xc2\xd7\xc0\xed'.decode('gbk'),b'\xba\xd6\xa7Q'.decode('big5'),b'\xc0\xed\xc2\xdb'.decode('gbk')}
|
||||
|
||||
def homeContent(self, filter):
|
||||
return {'class':[{'type_id': 1,'type_name':'电影'},{'type_id': 2,'type_name':'电视剧'},{'type_id': 4,'type_name':'动漫'},{'type_id': 3,'type_name':'综艺'},{'type_id': 22,'type_name':'纪录片'},{'type_id': 24,'type_name':'少儿'},{'type_id': 26,'type_name':'短剧'}]}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f'{self.host}/dev_webvip/v4/app/homeListNew?type=0', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
videos = []
|
||||
for i in data:
|
||||
if isinstance(i,dict) and 'dataInfoList' in i:
|
||||
videos.extend(self.arr2vods(i['dataInfoList']))
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(f'{self.host}/dev_webvip/v2/app/getVideoList?pageSize=12¤tPage={pg}&type={tid}', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
return {'list': self.arr2vods(data['list']), 'pagecount': data['pages'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
response = self.post(f'{self.host}/dev_webvip/v2/app/getVideoListType', data={'name':key}, headers=self.headers, verify=False).json()
|
||||
return {'list': self.arr2vods(response['data']), 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
vid, detail = ids[0].split('@',1)
|
||||
details = json.loads(detail)
|
||||
response = self.fetch(f'{self.host}/dev_webvip/v1/typeNameList/totalList?vDetailId={vid}', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
for i in data['vipTypeUrlNames']:
|
||||
self.player[i['vUrlType']] = {
|
||||
'typeUrlName': i['typeUrlName'],
|
||||
'jxApi': i['jxApi'],
|
||||
'ua': i['ua']
|
||||
}
|
||||
show, play_urls = [], []
|
||||
name,urls = '', []
|
||||
for i in data['videoUrlLists']:
|
||||
if not name: name = self.player[i['vUrlType']]['typeUrlName']
|
||||
urls.append(f"{i['vTitle']}${i['vUrlType']}@{i.get('vUrl',i.get('gfUrl',''))}")
|
||||
show.append(name)
|
||||
play_urls.append('#'.join(urls))
|
||||
video = {
|
||||
'vod_id': vid,
|
||||
**details,
|
||||
'vod_play_from': '$$$'.join(show),
|
||||
'vod_play_url': '$$$'.join(play_urls)
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
jx, url, play_ua = 0, '','Lavf/58.12.100'
|
||||
v_url_type, raw_url = vid.split('@',1)
|
||||
v_url_type = self.player[v_url_type]
|
||||
try:
|
||||
if v_url_type['jxApi'] and isinstance(v_url_type['jxApi'],str):
|
||||
response = self.fetch(f"{v_url_type['jxApi']}{raw_url}", headers=self.headers, verify=False).json()
|
||||
play_url = response['url']
|
||||
if play_url.startswith('http') and play_url != raw_url:
|
||||
url = play_url
|
||||
ua = response.get('UA')
|
||||
if ua: play_ua = ua
|
||||
except Exception:
|
||||
pass
|
||||
if v_url_type['ua'] and isinstance(v_url_type['ua'],str):
|
||||
play_ua = v_url_type['ua'].replace('User-Agent=>','')
|
||||
if not url:
|
||||
if raw_url.startswith('http'):
|
||||
jx, url = 0, raw_url
|
||||
elif re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', raw_url):
|
||||
jx, url = 1, raw_url
|
||||
return { 'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent': play_ua}}
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
if isinstance(arr,list):
|
||||
for i in arr:
|
||||
tag2,remake = i.get('vTest2'),''
|
||||
type_name = f"{i['vClass']},{tag2}" if tag2 else i['vClass']
|
||||
if i.get('vTag') and isinstance(i.get('vTag'), str) and any(k in i.get('vTag') for k in self.tares): continue
|
||||
if i.get('vTest1') and isinstance(i.get('vTest1'), str) and any(n in i.get('vTest1') for n in self.tares): continue
|
||||
if not(type_name and isinstance(type_name, str) and any(l in type_name for l in self.tares)):
|
||||
vRemake = json.loads(i['vRemake'])
|
||||
for j in vRemake:
|
||||
if j['remake']: remake = j['remake']
|
||||
detail = json.dumps({
|
||||
'vod_pic': i['vPic'],
|
||||
'vod_remarks': remake,
|
||||
'vod_year': i['vYear'],
|
||||
'vod_area': i['vArea'],
|
||||
'vod_actor': i['vActor'],
|
||||
'vod_director': i['vWriter'],
|
||||
'vod_content': i['vContent'],
|
||||
'type_name': type_name
|
||||
},ensure_ascii=False, separators=(',', ':'))
|
||||
videos.append({
|
||||
'vod_id': f"{i.get('vDetailId',i.get('id'))}@{detail}",
|
||||
'vod_name': i['vName'],
|
||||
'vod_pic': i['vPic'],
|
||||
'vod_remarks': remake,
|
||||
'vod_content': i.get('vBlurb',i.get('vContent'))
|
||||
})
|
||||
return videos
|
||||
|
||||
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,171 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
import sys,time,base64,random,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'Referer': 'https://www.duboku.tv/'
|
||||
}, 'https://api.dbokutv.com'
|
||||
|
||||
def init(self, extend=""):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
return {'class': [{'type_id': 2, 'type_name': '连续剧'}, {'type_id': 3, 'type_name': '综艺'}, {'type_id': 1, 'type_name': '电影'}, {'type_id': 4, 'type_name': '动漫'}]}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(self.sign(f'{self.host}/home'), headers=self.headers, verify=False).json()
|
||||
videos = []
|
||||
for i in response:
|
||||
for j in i.get('VodList',[]):
|
||||
videos.append({
|
||||
'vod_id': self.decode(j.get('DId', j.get('DuId'))),
|
||||
'vod_name': j['Name'],
|
||||
'vod_pic': self.decode(j['TnId']),
|
||||
'vod_remarks': j['Tag']
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(self.sign(f"{self.host}/vodshow/{tid}--------{'' if str(pg) == '1' else pg}---"), headers=self.headers, verify=False).json()
|
||||
videos,pagecount = [], 0
|
||||
for i in response['VodList']:
|
||||
videos.append({
|
||||
'vod_id': self.decode(i.get('DId',i.get('DuId'))),
|
||||
'vod_name': i['Name'],
|
||||
'vod_pic': self.decode(i['TnId']),
|
||||
'vod_remarks': i['Tag']
|
||||
})
|
||||
try:
|
||||
for j in response['PaginationList']:
|
||||
if j['Type'] == 'StartEnd':
|
||||
pgcount = int(self.decode(j.get('PId',j.get('PuId'))).split('-')[8])
|
||||
if pgcount: pagecount = pgcount
|
||||
except Exception:
|
||||
pass
|
||||
if pagecount == 0:
|
||||
try:
|
||||
for j in response['PaginationList']:
|
||||
if j['Type'] == 'ShortPage':
|
||||
pgcount = int(j['Name'].split('/')[1])
|
||||
if pgcount: pagecount = pgcount
|
||||
except Exception:
|
||||
pagecount = 1
|
||||
return {'list': videos,'pagecount': pagecount, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
response = self.fetch(f"{self.sign(f'{self.host}/vodsearch')}&wd={key}", headers=self.headers, verify=False).json()
|
||||
videos = []
|
||||
for i in response:
|
||||
videos.append({
|
||||
'vod_id': self.decode(i.get('DId', i.get('DuId'))),
|
||||
'vod_name': i['Name'],
|
||||
'vod_pic': self.decode(i['TnId']),
|
||||
'vod_remarks': i['Tag'],
|
||||
'vod_actor': i['Actor'],
|
||||
'vod_score': i['Rating']
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
data = self.fetch(self.sign(f"{self.host}{ids[0]}"), headers=self.headers, verify=False).json()
|
||||
play_urls = [f"{i['EpisodeName']}${self.decode(i['VId'])}" for i in data['Playlist']]
|
||||
video = {
|
||||
'vod_id': ids[0],
|
||||
'vod_name': data['Name'],
|
||||
'vod_pic': self.decode(data['TnId']),
|
||||
'vod_remarks': f"评分:{data['Rating']}",
|
||||
'vod_year': data['ReleaseYear'],
|
||||
'vod_area': data['Region'],
|
||||
'vod_actor': ','.join(data['Actor']),
|
||||
'vod_director': data['Director'],
|
||||
'vod_content': data['Description'],
|
||||
'vod_play_from': '独播库',
|
||||
'vod_play_url': '#'.join(play_urls),
|
||||
'type_name': f"{data['Genre']},{data['Scenario']},{data['Language']}"
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, video_id, vip_flags):
|
||||
res = self.fetch(self.sign(f"{self.host}{video_id}"), headers=self.headers, verify=False).json()
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'origin': 'https://w.duboku.io',
|
||||
'referer': 'https://w.duboku.io/',
|
||||
'priority': 'u=1, i'
|
||||
}
|
||||
return { 'jx': 0, 'parse': 0, 'url': self.decode(res['HId']), 'header': headers}
|
||||
|
||||
def decode(self, data):
|
||||
if not data or not isinstance(data, str): return ''
|
||||
stripped_str = data.strip('\'"')
|
||||
if not stripped_str: return ''
|
||||
segment_length = 10
|
||||
try:
|
||||
reversed_segments = (stripped_str[i:i + segment_length][::-1] for i in range(0, len(stripped_str), segment_length))
|
||||
processed_base64 = ''.join(reversed_segments).replace('.', '=')
|
||||
except Exception:
|
||||
return ''
|
||||
try:
|
||||
padding_needed = 4 - len(processed_base64) % 4
|
||||
if padding_needed != 4:
|
||||
processed_base64 += '=' * padding_needed
|
||||
decoded_bytes = base64.b64decode(processed_base64, validate=True)
|
||||
return decoded_bytes.decode('utf-8')
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
def sign(self,raw):
|
||||
random.seed(int(time.time()))
|
||||
random_number = random.randint(0, 800000000)
|
||||
value_a = random_number + 100000000
|
||||
value_b = 900000000 - random_number
|
||||
current_unix_timestamp = int(time.time())
|
||||
unix_timestamp_str = str(current_unix_timestamp)
|
||||
interleaved_str = self.interleave_strings(f'{value_a}{value_b}', unix_timestamp_str)
|
||||
ssid_base64_encoded = base64.b64encode(interleaved_str.encode()).decode().replace('=', '.')
|
||||
random_sign = self.random_string(60)
|
||||
random_token = self.random_string(38)
|
||||
return f"{raw}?sign={random_sign}&token={random_token}&ssid={ssid_base64_encoded}"
|
||||
|
||||
def interleave_strings(self,first_str, second_str):
|
||||
interleaved_chars = []
|
||||
min_length = min(len(first_str), len(second_str))
|
||||
for i in range(min_length):
|
||||
interleaved_chars.append(first_str[i])
|
||||
interleaved_chars.append(second_str[i])
|
||||
interleaved_chars.append(first_str[min_length:])
|
||||
interleaved_chars.append(second_str[min_length:])
|
||||
return ''.join(interleaved_chars)
|
||||
|
||||
|
||||
def random_string(self, length):
|
||||
character = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
random.seed(int(time.time()) + len(character))
|
||||
return ''.join(random.choice(character) for _ in range(length))
|
||||
|
||||
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,334 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import ARC4,AES
|
||||
from urllib.parse import quote_plus
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,time,uuid,json,base64,urllib3,hashlib,secrets,binascii
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers = {
|
||||
'User-Agent': "Dalvik/2.1.0",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip"
|
||||
}
|
||||
headers2 = {**headers}
|
||||
client_mode,max_client,app_id,pg_url,yry_url,client,base_path,pg_key,yry_key,token,user,pwd,markcode,login_mode,app_name,model,cache_key = 0,2,'','','','','','','','','','','','','','',''
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
ext = json.loads(extend)
|
||||
except Exception:
|
||||
ext = {}
|
||||
self.user = ext.get('username')
|
||||
self.pwd = ext.get('password')
|
||||
self.markcode = ext.get('markcode')
|
||||
self.app_name = ext.get('app_name', 'xiaomi')
|
||||
self.model = ext.get('model', 'xiaomi')
|
||||
cos_url = ext.get('CosUrl', 'http://2025-1329689796.cos.ap-guangzhou.myqcloud.com/kanke/app1.json')
|
||||
self.cache_key = hashlib.md5(cos_url.encode('utf-8')).hexdigest()
|
||||
try:
|
||||
number1 = 'No00000'
|
||||
number = self.md5(number1)
|
||||
number_key = self.md5(f'{number1}SmtEk1')
|
||||
payload = {'t': str(int(time.time())), 'key': self.rc4_encrypt(number, number_key)}
|
||||
self.app_id = '10000'
|
||||
res = self.fetch(cos_url, headers=self.headers, verify=False).json()
|
||||
notice = res['msg']['notice']
|
||||
main_host = self.decode(notice['content'])
|
||||
self.headers2['Authorization'] = self.decode(notice['type'])
|
||||
response = self.post(f'{main_host}?app={self.app_id}', data=payload, headers=self.headers2, verify=False).json()
|
||||
data = response['data']
|
||||
maink = self.rc4_decrypt(data['Maink'], number_key)
|
||||
self.pg_key = self.rc4_decrypt(data['pg'], maink)
|
||||
self.yry_key = self.rc4_decrypt(data['yry'], maink)
|
||||
key_time = self.rc4_decrypt(data['MT'], maink)
|
||||
pg_raw_url = data['pgUrl']
|
||||
yry_raw_url = data['yryUrl']
|
||||
aes_key = self.base64_encode(f"{key_time}{pg_raw_url[:14]}")
|
||||
aes_iv = self.base64_encode(f'{key_time}{yry_raw_url[:2]}').ljust(16)
|
||||
pg_url = self.aes_decrypt(self.base64_decode(pg_raw_url[32:]), aes_key, aes_iv)
|
||||
yry_url = self.aes_decrypt(self.base64_decode(yry_raw_url[16:]), aes_key, aes_iv)
|
||||
self.base_path = self.rc4_decrypt(data['HOST'])
|
||||
self.client = self.rc4_decrypt(data['newClient'])
|
||||
self.login_mode = data.get('Login',4)
|
||||
self.pg_url = pg_url
|
||||
self.yry_url = yry_url
|
||||
except Exception:
|
||||
self.pg_url = ''
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.pg_url: return None
|
||||
response = self.fetch(f'{self.pg_url}/api.php/{self.base_path}/Category?{self.sign()}', headers=self.headers2, verify=False).text
|
||||
try:
|
||||
data = self.rc4_decrypt(response)
|
||||
data2 = json.loads(data)
|
||||
except Exception:
|
||||
data2 = json.loads(response)
|
||||
classes = []
|
||||
for i in data2:
|
||||
if isinstance(i,dict) and i['type_status'] == 1:
|
||||
classes.append({'type_id': i['type_en'], 'type_name': i['type_name']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.pg_url: return None
|
||||
response = self.fetch(f'{self.pg_url}/api.php/{self.base_path}/top?{self.sign()}', headers=self.headers2, verify=False).json()
|
||||
videos = self.arr2vods(response['data'])
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.pg_url: return None
|
||||
response = self.fetch(f'{self.pg_url}/api.php/{self.base_path}/vod/?ac=list&class={tid}&page={pg}&{self.sign()}', headers=self.headers2, verify=False).text
|
||||
try:
|
||||
data = self.rc4_decrypt(response)
|
||||
data2 = json.loads(data)
|
||||
except Exception:
|
||||
data2 = json.loads(response)
|
||||
videos = self.arr2vods(data2['data'])
|
||||
return {'list': videos,'pagecount':data2['totalpage'], 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.pg_url: return None
|
||||
response = self.fetch(f'{self.pg_url}/api.php/{self.base_path}/So/?ac=list&zm={key}&page={pg}&{self.sign()}', headers=self.headers2, verify=False).text
|
||||
try:
|
||||
data = self.rc4_decrypt(response)
|
||||
data2 = json.loads(data)
|
||||
except Exception:
|
||||
data2 = json.loads(response)
|
||||
videos = self.arr2vods(data2['data'])
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.pg_url: return None
|
||||
self.client_mode = 0
|
||||
if not self.token: self.auto_logon()
|
||||
req_data = f'token={self.token}&t={int(time.time())}'
|
||||
payload = {'data': self.rc4_encrypt(req_data),'sign': self.md5(f'{req_data}&{self.pg_key}')}
|
||||
res = self.post(f'{self.yry_url}/api.php?app={self.app_id}&act=motion',data=payload, headers=self.headers2, verify=False).json()
|
||||
self.client_mode = int(res['msg']['Clientmode'])
|
||||
if self.client_mode == 0 and res['msg']['Try'] != 1: return None
|
||||
response = self.fetch(f'{self.pg_url}/api.php/{self.base_path}/vod/{ids[0]}&{self.sign()}', headers=self.headers2, verify=False).text
|
||||
try:
|
||||
data2 = json.loads(response)
|
||||
except Exception:
|
||||
data = self.rc4_decrypt(response)
|
||||
data2 = json.loads(data)
|
||||
try:
|
||||
player_ = self.rc4_decrypt(data2['player'])
|
||||
player = json.loads(player_)
|
||||
except Exception:
|
||||
player = json.loads(response['player'])
|
||||
vod_name = data2['title']
|
||||
shows, play_urls = [], []
|
||||
for show, raw_urls in data2['videolist'].items():
|
||||
name = None
|
||||
for i in player:
|
||||
if i['from'] == show and i['from'] != i['show']:
|
||||
name = i['show']
|
||||
break
|
||||
urls = []
|
||||
for j in raw_urls:
|
||||
title = j['title']
|
||||
series = quote_plus(f"{vod_name}-{title}")
|
||||
urls.append(f"{title}${series}@{j['url']}")
|
||||
play_urls.append('#'.join(urls))
|
||||
if name:
|
||||
shows.append(f'{name}\u2005({show})')
|
||||
else:
|
||||
shows.append(show)
|
||||
video = {
|
||||
'vod_id': ids[0],
|
||||
'vod_name': vod_name,
|
||||
'vod_pic': data2['img_url'],
|
||||
'vod_remarks': data2['trunk'],
|
||||
'vod_year': data2['pubtime'],
|
||||
'vod_area': ','.join(data2['area']),
|
||||
'vod_actor': ','.join(data2['actor']),
|
||||
'vod_director': ','.join(data2['director']),
|
||||
'vod_content': data2['intro'],
|
||||
'vod_play_from': '$$$'.join(shows),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': ','.join(data2['type'])
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
jx,url = 0,''
|
||||
series, raw_url = vid.split('@',1)
|
||||
self.client_mode = 1
|
||||
base_path = f'&account={self.user}&password={self.pwd}&series={series}&edition=1.0'
|
||||
cont = 0
|
||||
for i in range(1,4):
|
||||
if cont > self.max_client:
|
||||
break
|
||||
try:
|
||||
if self.client.startswith('http'):
|
||||
if i == 1:
|
||||
path = f'{self.client}/?url={raw_url}'
|
||||
else:
|
||||
path = f'{self.client}{i}/?url={raw_url}'
|
||||
if self.client_mode == 1:
|
||||
payload = {'app': self.app_id,'key': self.rc4_encrypt(base_path),'':''}
|
||||
response = self.post(path, data=payload, headers=self.headers2, verify=False).text
|
||||
else:
|
||||
response = self.post(f'{path}&app={self.app_id}{base_path}', headers=self.headers2, verify=False).text
|
||||
try:
|
||||
data = self.rc4_decrypt(response)
|
||||
data2 = json.loads(data)
|
||||
except Exception:
|
||||
data2 = json.loads(response)
|
||||
self.max_client = int(data2.get('maxClient'))
|
||||
play_url = data2['url']
|
||||
if play_url.startswith('http'):
|
||||
url = play_url
|
||||
break
|
||||
cont += 1
|
||||
except Exception:
|
||||
url = ''
|
||||
cont += 1
|
||||
continue
|
||||
if not url:
|
||||
try:
|
||||
play_url = self.rc4_decrypt(raw_url)
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', play_url):
|
||||
jx, url = 1, play_url
|
||||
except Exception:
|
||||
pass
|
||||
return { 'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent':'Windows'}}
|
||||
|
||||
def auto_logon(self):
|
||||
if not self.pg_url: return
|
||||
if self.user and self.pwd and self.markcode:
|
||||
user, pwd, markcode = self.user, self.pwd, self.markcode
|
||||
else:
|
||||
account_cache_key = f'smtv_account_{self.cache_key}_V73xfmWkXa'
|
||||
try:
|
||||
account = self.getCache(account_cache_key)
|
||||
user, pwd, markcode = account['username'], account['password'], account['markcode']
|
||||
except Exception:
|
||||
user, pwd, markcode = None, None, None
|
||||
if not (user and pwd and markcode):
|
||||
if self.login_mode == 2:
|
||||
random_account = self.android_id()
|
||||
user, pwd, markcode = random_account, random_account, random_account
|
||||
elif self.login_mode == 3:
|
||||
random_account = self.mac()
|
||||
user, pwd, markcode = random_account, random_account, random_account
|
||||
else:
|
||||
random_account = self.uuid_number()
|
||||
user, pwd, markcode = random_account, random_account, random_account
|
||||
self.register(user, pwd, markcode)
|
||||
self.setCache(account_cache_key, {'username': user, 'password': pwd, 'markcode': markcode})
|
||||
self.login(user, pwd, markcode)
|
||||
|
||||
def login(self, user,pwd,markcode):
|
||||
if not self.pg_url: return
|
||||
req_data = f'account={user}&password={pwd}&markcode={markcode}&t={int(time.time())}'
|
||||
payload = {'data': self.rc4_encrypt(req_data),'sign': self.md5(f'{req_data}&{self.pg_key}')}
|
||||
response = self.post(f'{self.yry_url}/api.php?app={self.app_id}&act=user_logon', data=payload, headers=self.headers2, verify=False).json()
|
||||
msg = response['msg']
|
||||
try:
|
||||
data = self.rc4_decrypt(msg, self.yry_key)
|
||||
data2 = json.loads(data)
|
||||
except Exception:
|
||||
data2 = json.loads(msg)
|
||||
self.token = data2['token']
|
||||
self.user = user
|
||||
self.pwd = pwd
|
||||
|
||||
def register(self, user,pwd,markcode):
|
||||
if not self.pg_url: return
|
||||
user_host = self.fetch(f'{self.yry_url}/ip.json', headers=self.headers).text
|
||||
req_data = f'user={user}&password={pwd}&markcode={markcode}&t={int(time.time())}&name={self.app_name}&phone={self.model}'
|
||||
payload = {'data': self.rc4_encrypt(req_data),'sign': self.md5(f'{req_data}&{self.pg_key}')}
|
||||
self.post(f'http://{user_host}/api.php?app={self.app_id}&act=user_reg', data=payload, headers=self.headers2, verify=False)
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
if isinstance(arr,list):
|
||||
for i in arr:
|
||||
videos.append({
|
||||
'vod_id': i.get('tjurl', i.get('nextlink')),
|
||||
'vod_name': i.get('tjinfo', i.get('title')),
|
||||
'vod_pic': i.get('tjpicurl', i.get('pic')),
|
||||
'vod_remarks': i.get('state'),
|
||||
'vod_year': None
|
||||
})
|
||||
return videos
|
||||
|
||||
def rc4_encrypt(self, plaintext, key=None):
|
||||
if not key: key = self.yry_key
|
||||
key_bytes = key.encode('utf-8')
|
||||
plaintext_bytes = plaintext.encode('utf-8')
|
||||
cipher = ARC4.new(key_bytes)
|
||||
ciphertext = cipher.encrypt(plaintext_bytes)
|
||||
return binascii.hexlify(ciphertext).decode('utf-8')
|
||||
|
||||
def rc4_decrypt(self,ciphertext_hex, key=None):
|
||||
if not key: key = self.pg_key
|
||||
key_bytes = key.encode('utf-8')
|
||||
ciphertext_bytes = binascii.unhexlify(ciphertext_hex)
|
||||
cipher = ARC4.new(key_bytes)
|
||||
plaintext_bytes = cipher.decrypt(ciphertext_bytes)
|
||||
return plaintext_bytes.decode('utf-8')
|
||||
|
||||
def aes_decrypt(self, data, key, iv):
|
||||
try:
|
||||
encrypted_data = base64.b64decode(data)
|
||||
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
|
||||
decrypted_padded = cipher.decrypt(encrypted_data)
|
||||
decrypted_data = unpad(decrypted_padded, AES.block_size)
|
||||
return decrypted_data.decode('utf-8')
|
||||
except Exception:
|
||||
raise ValueError
|
||||
|
||||
def uuid_number(self):
|
||||
uuid_bytes = uuid.uuid4().bytes
|
||||
result = []
|
||||
for i in range(9):
|
||||
value = uuid_bytes[i] % 10
|
||||
result.append(str(value))
|
||||
return ''.join(result)
|
||||
|
||||
def mac(self):
|
||||
mac_bytes = [secrets.randbits(8) for _ in range(6)]
|
||||
return ''.join(f"{byte:02X}" for byte in mac_bytes)
|
||||
|
||||
def android_id(self):
|
||||
return ''.join(secrets.choice('0123456789abcdef') for _ in range(16))
|
||||
|
||||
def sign(self):
|
||||
timestamp = str(int(time.time()))
|
||||
return f'key={self.rc4_encrypt(timestamp, timestamp)}&tt={timestamp}'
|
||||
|
||||
def base64_encode(self, data):
|
||||
return base64.b64encode(data.encode('utf-8')).decode('utf-8')
|
||||
|
||||
def base64_decode(self, data):
|
||||
return base64.b64decode(data.encode('utf-8')).decode('utf-8')
|
||||
|
||||
def decode(self, data):
|
||||
return self.base64_decode(self.base64_decode(data[16:]))
|
||||
|
||||
def md5(self, data):
|
||||
return hashlib.md5(data.encode('utf-8')).hexdigest()
|
||||
|
||||
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,202 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import pad
|
||||
import sys,json,time,random,urllib3,binascii
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host,res_api,cache_token_key = {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 12; Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'authorization': '',
|
||||
'uuid': ''
|
||||
}, '','https://speed.hiknz.com/papaya/papaya-file/files/download/','com.sha.fragment_5d091235ac87508f_token'
|
||||
|
||||
def init(self, extend=''):
|
||||
self.host = 'https://free-api.bighotwind.cc'
|
||||
self.login()
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = {}
|
||||
for i in range(2):
|
||||
response = self.fetch(f'{self.host}/papaya/papaya-api/theater/tags', headers=self.headers, verify=False).json()
|
||||
if response['code'] == 401:
|
||||
self.re_login()
|
||||
else:
|
||||
break
|
||||
classes = []
|
||||
for i in response.get('data',[]):
|
||||
if isinstance(i,dict):
|
||||
classes.append({'type_id': i['id'], 'type_name': i['text_val']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host: return None
|
||||
response = {}
|
||||
for i in range(2):
|
||||
response = self.fetch(f'{self.host}/papaya/papaya-api/videos/page?type=5&pageNum=1&pageSize=12',headers=self.headers, verify=False).json()
|
||||
if response['code'] == 401:
|
||||
self.re_login()
|
||||
else:
|
||||
break
|
||||
videos = []
|
||||
for i in response.get('list',[]):
|
||||
videos.append({
|
||||
'vod_id': f"{i['itemId']}@{i['videoCode']}@{i['tags']}",
|
||||
'vod_name': i['title'],
|
||||
'vod_pic': f"{self.res_api}{i['imageKey']}/{i['imageName']}",
|
||||
'vod_remarks': i['tags'],
|
||||
'vod_content': i['content']
|
||||
})
|
||||
return {'list': videos, 'total': response['total']}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
response ={}
|
||||
for i in range(2):
|
||||
response = self.fetch(f'{self.host}/papaya/papaya-api/videos/page?type=5&tagId={tid}&pageNum={pg}&pageSize=12', headers=self.headers, verify=False).json()
|
||||
if response['code'] == 401:
|
||||
self.re_login()
|
||||
else:
|
||||
break
|
||||
videos = []
|
||||
for i in response.get('list',[]):
|
||||
videos.append({
|
||||
'vod_id': f"{i['itemId']}@{i['videoCode']}@{i['tags']}",
|
||||
'vod_name': i['title'],
|
||||
'vod_pic': f"{self.res_api}{i['imageKey']}/{i['imageName']}",
|
||||
'vod_remarks': i['tags'],
|
||||
'vod_content': i['content']
|
||||
})
|
||||
return {'list': videos, 'total': response['total']}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
response = {}
|
||||
for i in range(2):
|
||||
response = self.fetch(f'{self.host}/papaya/papaya-api/videos/page?type=5&search={key}&pageNum={pg}&pageSize=12', headers=self.headers, verify=False).json()
|
||||
if response['code'] == 401:
|
||||
self.re_login()
|
||||
else:
|
||||
break
|
||||
videos = []
|
||||
for i in response.get('list',[]):
|
||||
videos.append({
|
||||
'vod_id': f"{i['itemId']}@{i['videoCode']}@{i['tags']}",
|
||||
'vod_name': i['title'],
|
||||
'vod_pic': f"{self.res_api}{i['imageKey']}/{i['imageName']}",
|
||||
'vod_remarks': i['tags'],
|
||||
'vod_content': i['content']
|
||||
})
|
||||
return {'list': videos, 'total': response['total']}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not self.host: return None
|
||||
item_id, video_code, tags = ids[0].split('@',2)
|
||||
response = {}
|
||||
for i in range(2):
|
||||
response = self.fetch(f'{self.host}/papaya/papaya-api/videos/info?videoCode={video_code}&itemId={item_id}',headers=self.headers, verify=False).json()
|
||||
if response['code'] == 401:
|
||||
self.re_login()
|
||||
else:
|
||||
break
|
||||
data = response.get('data')
|
||||
if not data: return None
|
||||
play_urls = []
|
||||
episodes_list = data.get('episodesList',[])
|
||||
for i in episodes_list:
|
||||
if isinstance(i, dict):
|
||||
resolutionList = i['resolutionList'][0]
|
||||
play_urls.append(f"{i['episodes']}${resolutionList['fileKey']}/{resolutionList['fileName']}")
|
||||
video = {
|
||||
'vod_id': ids[0],
|
||||
'vod_name': data['title'],
|
||||
'vod_pic': f"{self.res_api}{data['imageKey']}/{data['imageName']}",
|
||||
'vod_remarks': f"集数:\u2005{len(episodes_list)} 时长:\u2005{self.convert_minutes(data['duration'])}",
|
||||
'vod_content': data['content'],
|
||||
'type_name': tags,
|
||||
'vod_play_from': '碎片剧场',
|
||||
'vod_play_url': '#'.join(play_urls),
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
if not id.startswith('http'): id = f'{self.res_api}{id}'
|
||||
return { 'jx': 0, 'parse': 0, 'url': id, 'header': {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 12; Build/3480d86.0)','Connection': 'Keep-Alive','Accept-Encoding': 'identity'}}
|
||||
|
||||
def login(self):
|
||||
try:
|
||||
cache_uuid_key = 'com.sha.fragment_5d091235ac87508f_uuid'
|
||||
uuid = self.getCache(cache_uuid_key)
|
||||
if not uuid:
|
||||
uuid = self.generate_android_id()
|
||||
self.setCache(cache_uuid_key,uuid)
|
||||
headers = self.headers.copy()
|
||||
headers['key'] = self.encrypt(str(int(time.time() * 1000)))
|
||||
headers['content-type'] = 'application/json; charset=utf-8'
|
||||
token = self.getCache(self.cache_token_key)
|
||||
if not token:
|
||||
response = self.post(f'{self.host}/papaya/papaya-api/oauth2/uuid', data=json.dumps({"openId":uuid}), headers=headers, verify=False).json()
|
||||
token = response['data']['token']
|
||||
if not token:
|
||||
self.host = None
|
||||
return
|
||||
self.setCache(self.cache_token_key,token)
|
||||
self.headers['authorization'] = token
|
||||
self.headers['uuid'] = uuid
|
||||
print(self.headers)
|
||||
except Exception:
|
||||
self.host = None
|
||||
|
||||
def convert_minutes(self,total_minutes):
|
||||
try:
|
||||
total_minutes = int(total_minutes)
|
||||
except (ValueError, TypeError):
|
||||
return total_minutes
|
||||
if total_minutes == 0:
|
||||
return total_minutes
|
||||
hours = total_minutes // 60
|
||||
minutes = total_minutes % 60
|
||||
if hours == 0:
|
||||
return f"{minutes}分钟"
|
||||
elif minutes == 0:
|
||||
return f"{hours}小时"
|
||||
else:
|
||||
return f"{hours}小时{minutes}分钟"
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
cipher = AES.new("p0sfjw@k&qmewu#w".encode('utf-8'), AES.MODE_ECB)
|
||||
data_bytes = plaintext.encode('utf-8')
|
||||
block_size = AES.block_size
|
||||
padded_data = pad(data_bytes, block_size)
|
||||
encrypted_bytes = cipher.encrypt(padded_data)
|
||||
return binascii.hexlify(encrypted_bytes).decode('utf-8')
|
||||
|
||||
def generate_android_id(self):
|
||||
hex_chars = '0123456789abcdef'
|
||||
return ''.join(random.choice(hex_chars) for _ in range(16))
|
||||
|
||||
def re_login(self):
|
||||
self.delCache(self.cache_token_key)
|
||||
self.login()
|
||||
|
||||
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,264 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,time,json,urllib3,hashlib,binascii
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,host,versionCode = {
|
||||
'User-Agent': "okhttp/3.14.9",
|
||||
'Connection': "Keep-Alive",
|
||||
'Accept-Encoding': "gzip"
|
||||
}, 'http://122.114.11.127:8808','1030'
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
payload = self.payload('App.Vod.Main_type')
|
||||
try:
|
||||
response = self.post(self.host, data=payload, headers=self.headers, verify=False).json()
|
||||
classes = []
|
||||
for i in response.get('data', []):
|
||||
if isinstance(i, dict):
|
||||
classes.append({'type_id': i['list_id'], 'type_name': i['list_name']})
|
||||
except Exception:
|
||||
classes = []
|
||||
filters = {}
|
||||
def fetch_filter(tid):
|
||||
try:
|
||||
f_payload = self.payload({
|
||||
'service': "App.Vod.Videos",
|
||||
'list_id': tid,
|
||||
'type': "全部",
|
||||
'year': "全部",
|
||||
'area': "全部",
|
||||
'language': "全部",
|
||||
'order': "time",
|
||||
'page': "1",
|
||||
'perpage': "24",
|
||||
})
|
||||
res = self.post(self.host, data=f_payload, headers=self.headers, verify=False).json()
|
||||
decrypted_data = self.decrypt(res['data'])
|
||||
data_items = json.loads(decrypted_data)
|
||||
type_filters = []
|
||||
if isinstance(data_items, list):
|
||||
for item in data_items:
|
||||
if isinstance(item, dict) and item.get('type') == 'filter':
|
||||
name = ''
|
||||
tf = item.get('type_filter')
|
||||
if tf == 'type_expansion':
|
||||
key, name = 'type', '类型'
|
||||
elif tf == 'class':
|
||||
key, name = 'type', '类型'
|
||||
elif tf == 'area':
|
||||
key, name = 'area', '地区'
|
||||
elif tf == 'year':
|
||||
key, name = 'year', '年份'
|
||||
elif tf == 'language':
|
||||
key, name = 'language', '语言'
|
||||
elif tf == 'letter':
|
||||
key, name = 'letter', '字母'
|
||||
elif tf == 'order':
|
||||
key, name = 'order', '排序'
|
||||
elif tf == 'type':
|
||||
key = ''
|
||||
else:
|
||||
key, name = tf, tf
|
||||
if key and item.get('filters'):
|
||||
values = []
|
||||
for opt in item['filters']:
|
||||
v = opt.get('order') if key == 'order' and 'order' in opt else opt.get('title')
|
||||
n = opt.get('title')
|
||||
values.append({'n': n, 'v': v})
|
||||
init_value = values[0]['v'] if values else ''
|
||||
type_filters.append({'key': key, 'name': name, 'value': values, 'init': init_value})
|
||||
return str(tid), type_filters
|
||||
except Exception:
|
||||
return str(tid), []
|
||||
if classes:
|
||||
with ThreadPoolExecutor(max_workers=5) as executor:
|
||||
futures = {executor.submit(fetch_filter, cls['type_id']): cls for cls in classes}
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
tid_str, t_filters = future.result()
|
||||
if t_filters:
|
||||
filters[tid_str] = t_filters
|
||||
except Exception:
|
||||
pass
|
||||
return {'class': classes, 'filters': filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
payload = self.payload('App.Vod.HomeVideos')
|
||||
response = self.post(self.host, data=payload, headers=self.headers, verify=False).json()
|
||||
data = self.decrypt(response['data'])
|
||||
videos = self.videos(data)
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
params = {
|
||||
'service': 'App.Vod.Videos',
|
||||
'list_id': tid,
|
||||
'type': '全部',
|
||||
'year': '全部',
|
||||
'area': '全部',
|
||||
'language': '全部',
|
||||
'order': 'time',
|
||||
'page': str(pg),
|
||||
'perpage': '24',
|
||||
}
|
||||
if extend:
|
||||
params.update(extend)
|
||||
payload = self.payload(params)
|
||||
response = self.post(self.host, data=payload, headers=self.headers, verify=False).json()
|
||||
data = self.decrypt(response['data'])
|
||||
videos = self.videos(data)
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
payload = self.payload({
|
||||
'service': "App.Vod.Search",
|
||||
'search': key,
|
||||
'page': pg,
|
||||
'perpage': "24"
|
||||
})
|
||||
response = self.post(self.host, data=payload, headers=self.headers, verify=False).json()
|
||||
data = self.decrypt(response['data'])
|
||||
videos = self.videos(data)
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
payload = self.payload({
|
||||
'service': "App.Vod.Video",
|
||||
'id': ids[0]
|
||||
})
|
||||
response = self.post(self.host, data=payload, headers=self.headers, verify=False).json()
|
||||
player_vod = {}
|
||||
for i in response['data']:
|
||||
if isinstance(i, dict) and i['type'] == 'player':
|
||||
player_vod = i['player_vod']
|
||||
if not player_vod: return None
|
||||
play_from, play_urls = [], []
|
||||
for j in player_vod['vod_play']:
|
||||
play_from.append(j['title'])
|
||||
play_url = []
|
||||
for k in j.get('players', []):
|
||||
play_url.append(f"{k['title']}${k['url']}")
|
||||
play_url.reverse()
|
||||
play_urls.append('#'.join(play_url))
|
||||
video = {
|
||||
'vod_id': player_vod['vod_id'],
|
||||
'vod_name': player_vod['vod_name'],
|
||||
'vod_pic': player_vod['vod_pic'],
|
||||
'vod_remarks': player_vod['vod_title'],
|
||||
'vod_actor': player_vod['vod_actor'],
|
||||
'vod_content': player_vod['vod_content'],
|
||||
'vod_play_from': '$$$'.join(play_from),
|
||||
'vod_play_url': '$$$'.join(play_urls)
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
jx, parse, noparse, ua, url = 0, 0, 0, 'com.jubaotaige.jubaotaigeapp/2.3.2 (Linux;Android 12) ExoPlayerLib/2.14.2', ''
|
||||
if 'nkvod.com' in id:
|
||||
noparse = 1
|
||||
elif 'url=' in id:
|
||||
try:
|
||||
response = self.fetch(id, headers=self.headers, verify=False).json()
|
||||
url = response.get('url', '')
|
||||
if not url.startswith('http'):
|
||||
noparse = 1
|
||||
except Exception as e:
|
||||
print(f"请求异常: {e}")
|
||||
noparse = 1
|
||||
else:
|
||||
url = id
|
||||
if noparse == 1:
|
||||
ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
|
||||
if 'url=' in id:
|
||||
url = id.split('url=', 1)[1]
|
||||
else:
|
||||
url = id
|
||||
if re.match(r'https?:\/\/.*\.(iqiyi|youku|v.qq|mgtv)\.com', url):
|
||||
jx = 1
|
||||
elif 'nkvod.com' in url:
|
||||
parse = 1
|
||||
return {'jx': jx, 'parse': parse, 'url': url, 'header': {'User-Agent': ua}}
|
||||
|
||||
def payload(self, data):
|
||||
timestamp = int(time.time() * 1000)
|
||||
md5 = self.md5(str(8 * timestamp - 12))
|
||||
if isinstance(data, dict):
|
||||
sign_data = self.md5(f"Api_FeiFeiCms{data['service']}{self.versionCode}{timestamp}{md5}")
|
||||
data.update({
|
||||
'versionCode': self.versionCode,
|
||||
'time': timestamp,
|
||||
'md5': md5,
|
||||
'sign': sign_data
|
||||
})
|
||||
payload = data
|
||||
else:
|
||||
sign_data = self.md5(f'Api_FeiFeiCms{data}{self.versionCode}{timestamp}{md5}')
|
||||
payload = {
|
||||
'service': data,
|
||||
'versionCode': self.versionCode,
|
||||
'time': timestamp,
|
||||
'md5': md5,
|
||||
'sign': sign_data
|
||||
}
|
||||
return payload
|
||||
|
||||
def videos(self,data):
|
||||
try:
|
||||
data = json.loads(data)
|
||||
videos = []
|
||||
for i in data:
|
||||
if isinstance(i, dict):
|
||||
for j in i.get('videos', []):
|
||||
videos.append({
|
||||
'vod_id': j['vod_id'],
|
||||
'vod_name': j['vod_name'],
|
||||
'vod_pic': j['vod_pic'],
|
||||
'vod_remarks': j['vod_title']
|
||||
})
|
||||
except Exception:
|
||||
videos = []
|
||||
return videos
|
||||
|
||||
def decrypt(self,data, key='', iv=''):
|
||||
try:
|
||||
if not(key and iv):
|
||||
key, iv = '58ae78ab03bfeefb', '68e3d872b480c14f'
|
||||
encrypted_data = binascii.unhexlify(data)
|
||||
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
|
||||
decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
|
||||
return decrypted_data.decode('utf-8')
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def md5(self, data):
|
||||
md5_hash = hashlib.md5()
|
||||
md5_hash.update(data.encode('utf-8'))
|
||||
return md5_hash.hexdigest()
|
||||
|
||||
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,154 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from base.spider import Spider
|
||||
import re,sys,time,uuid,hmac,urllib3,hashlib
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host,player_host,dkey,player = 'https://app.laodifang.tv','https://www.laodifang.tv',{bs.decode('gbk') for bs in [b'\xc2\xd7\xc0\xed',b'\xb8\xa3\xc0\xfb',b'\xc7\xe9\xc9\xab']},{}
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def headers(self,path,json_type=1):
|
||||
timestamp = str(int(time.time()))
|
||||
random_uuid = str(uuid.uuid4()).replace('-', '')
|
||||
sign = self.hmac_sha256(f'PV-HMAC-SHA256\n{timestamp}\nPOST\n{path}\n\nUNSIGNED-PAYLOAD\n{random_uuid}')
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/4.11.0',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'Content-Type': 'application/json',
|
||||
'access-token': '',
|
||||
'authorization': f"PV-HMAC-SHA256 Credential=5gc4r3ic, SignedHeaders=host;content-type;x-pv-timestamp;x-pv-nonce, Signature={sign}",
|
||||
'x-pv-timestamp': timestamp,
|
||||
'x-pv-nonce': random_uuid
|
||||
}
|
||||
if json_type == 1: headers['content-type'] = 'application/json; charset=UTF-8'
|
||||
return headers
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
path = '/api/v1/type/list'
|
||||
response = self.post(f'{self.host}{path}', data='{}', headers=self.headers(path), verify=False).json()
|
||||
classes = []
|
||||
for item in response['data']:
|
||||
if isinstance(item, dict):
|
||||
type_name = item.get('typeName')
|
||||
if not(isinstance(type_name, str) and any(kw in type_name for kw in self.dkey)):
|
||||
classes.append({'type_id': item['typeId'],'type_name': type_name})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
path = '/api/v1/video/index'
|
||||
response = self.post(f'{self.host}{path}', data='{"isFilterAd":false}', headers=self.headers(path), verify=False).json()
|
||||
videos = []
|
||||
for i in response['data'].values():
|
||||
if isinstance(i,list):
|
||||
videos.extend(self.json2vods(i))
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
path = '/api/v1/video/list'
|
||||
response = self.post(f'{self.host}{path}', data=f'{{"area":"","claxx":"","lang":"","page":{pg},"pageSize":12,"sort":2,"typeId":{tid},"typeTopId":0,"year":"","isFilterAd":false}}', headers=self.headers(path), verify=False).json()
|
||||
return {'list': self.json2vods(response['data']['list']), 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
path = '/api/v1/video/search'
|
||||
response = self.post(f'{self.host}{path}', data=f'{{"keyWord":"{key}","isFilterAd":false}}', headers=self.headers(path), verify=False).json()
|
||||
videos = self.json2vods(response['data'])
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
path = '/api/v1/video/detail'
|
||||
response = self.post(f'{self.host}{path}', data=f'{{"videoId": {ids[0]}}}', headers=self.headers(path), verify=False).json()
|
||||
data = response['data']
|
||||
path2 = '/app/config.json'
|
||||
if not self.player:
|
||||
response2 = self.fetch(f'{self.player_host}{path2}', headers=self.headers(path2), verify=False).json()
|
||||
self.player = {k['code']: (k['name'], k['needParse']) for k in response2['data']['player']}
|
||||
type_name = data['vodClass']
|
||||
if isinstance(type_name, str) and any(kw in type_name for kw in self.dkey): return
|
||||
shows,play_urls = [],[]
|
||||
raw_shows = data['vodPlayFrom'].split('$$$')
|
||||
raw_urls_list = data['vodPlayUrl'].split('$$$')
|
||||
for show_code, urls_str in zip(raw_shows, raw_urls_list):
|
||||
name, need_parse = self.player.get(show_code, ('', 0))
|
||||
if name and name != show_code:
|
||||
formatted_show = f"{name}({show_code})"
|
||||
else:
|
||||
formatted_show = show_code
|
||||
urls = []
|
||||
for url_item in urls_str.split('#'):
|
||||
episode, url = url_item.split('$',1)
|
||||
urls.append(f"{episode}${show_code}@{int(need_parse)}@{url}")
|
||||
play_urls.append('#'.join(urls))
|
||||
shows.append(formatted_show)
|
||||
video = {
|
||||
'vod_id': data['vodId'],
|
||||
'vod_name': data['vodName'],
|
||||
'vod_pic': data['vodPic'],
|
||||
'vod_remarks': data['vodRemarks'],
|
||||
'vod_year': data['vodYear'],
|
||||
'vod_area': data['vodArea'],
|
||||
'vod_actor': data['vodActor'],
|
||||
'vod_director': data['vodDirector'],
|
||||
'vod_content': data['vodContent'],
|
||||
'vod_play_from': '$$$'.join(shows),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': data['vodClass']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, video_id, vip_flags):
|
||||
play_from, need_parse, raw_url = video_id.split('@',2)
|
||||
jx,url = 0,''
|
||||
if need_parse == '1':
|
||||
try:
|
||||
path = '/api/v1/video/realPlayUrl'
|
||||
response = self.post(f'{self.host}{path}', data=f'{{"playerCode":"{play_from}","url":"{raw_url}"}}', headers=self.headers(path), verify=False).json()
|
||||
play_url = response['data']['playUrl']
|
||||
if play_url.startswith('http') and play_url != raw_url: url = play_url
|
||||
except Exception:
|
||||
pass
|
||||
if not url:
|
||||
url = raw_url
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', video_id):
|
||||
jx = 1
|
||||
return { 'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent': 'okhttp/4.11.0','Accept-Encoding': 'gzip'}}
|
||||
|
||||
def json2vods(self,arr):
|
||||
videos = []
|
||||
for i in arr:
|
||||
vod_class = i.get('vodClass')
|
||||
if not(isinstance(vod_class, str) and any(kw in vod_class for kw in self.dkey)):
|
||||
videos.append({
|
||||
'vod_id': i['vodId'],
|
||||
'vod_name': i['vodName'],
|
||||
'vod_pic': i['vodPic'],
|
||||
'vod_remarks': i['vodRemarks'],
|
||||
'vod_year': i['vodYear']
|
||||
})
|
||||
return videos
|
||||
|
||||
def hmac_sha256(self, data):
|
||||
key = "4ssnwkd3qoz4tuqoyeddhe65667qo4oc"
|
||||
return hmac.new(key.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
|
||||
|
||||
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,8 @@
|
||||
class Proxy:
|
||||
@staticmethod
|
||||
def getUrl(local):
|
||||
return f'http://127.0.0.1:{Proxy.getPort()}'
|
||||
|
||||
@staticmethod
|
||||
def getPort():
|
||||
return 9978
|
||||
@@ -0,0 +1,133 @@
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
from lxml import etree
|
||||
from abc import abstractmethod, ABCMeta
|
||||
from importlib.machinery import SourceFileLoader
|
||||
from base.localProxy import Proxy
|
||||
|
||||
class Spider(metaclass=ABCMeta):
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
self.extend = ''
|
||||
self._cache = {}
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance:
|
||||
return cls._instance
|
||||
else:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
@abstractmethod
|
||||
def init(self, extend=""):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
pass
|
||||
|
||||
def detailContent(self, ids):
|
||||
pass
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
pass
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
pass
|
||||
|
||||
def liveContent(self, url):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def getDependence(self):
|
||||
return []
|
||||
|
||||
def loadSpider(self, name):
|
||||
return self.loadModule(name).Spider()
|
||||
|
||||
def loadModule(self, name):
|
||||
path = os.path.join(os.path.join("../plugin"), f'{name}.py')
|
||||
return SourceFileLoader(name, path).load_module()
|
||||
|
||||
def regStr(self, reg, src, group=1):
|
||||
m = re.search(reg, src)
|
||||
src = ''
|
||||
if m:
|
||||
src = m.group(group)
|
||||
return src
|
||||
|
||||
def removeHtmlTags(self, src):
|
||||
clean = re.compile('<.*?>')
|
||||
return re.sub(clean, '', src)
|
||||
|
||||
def cleanText(self, src):
|
||||
clean = re.sub('[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF]', '',
|
||||
src)
|
||||
return clean
|
||||
|
||||
def fetch(self, url, params=None, cookies=None, headers=None, timeout=5, verify=True, stream=False,
|
||||
allow_redirects=True):
|
||||
rsp = requests.get(url, params=params, cookies=cookies, headers=headers, timeout=timeout, verify=verify,
|
||||
stream=stream, allow_redirects=allow_redirects)
|
||||
rsp.encoding = 'utf-8'
|
||||
return rsp
|
||||
|
||||
def post(self, url, params=None, data=None, json=None, cookies=None, headers=None, timeout=5, verify=True,
|
||||
stream=False, allow_redirects=True):
|
||||
rsp = requests.post(url, params=params, data=data, json=json, cookies=cookies, headers=headers, timeout=timeout,
|
||||
verify=verify, stream=stream, allow_redirects=allow_redirects)
|
||||
rsp.encoding = 'utf-8'
|
||||
return rsp
|
||||
|
||||
def html(self, content):
|
||||
return etree.HTML(content)
|
||||
|
||||
def str2json(str):
|
||||
return json.loads(str)
|
||||
|
||||
def json2str(str):
|
||||
return json.dumps(str, ensure_ascii=False)
|
||||
|
||||
def getProxyUrl(self, local=True):
|
||||
return f'{Proxy.getUrl(local)}?do=py'
|
||||
|
||||
def log(self, msg):
|
||||
if isinstance(msg, dict) or isinstance(msg, list):
|
||||
print(json.dumps(msg, ensure_ascii=False))
|
||||
else:
|
||||
print(f'{msg}')
|
||||
|
||||
def getCache(self, key):
|
||||
return self._cache.get(key)
|
||||
|
||||
def setCache(self, key, value):
|
||||
self._cache[key] = value
|
||||
|
||||
def delCache(self, key):
|
||||
self._cache.pop(key)
|
||||
@@ -0,0 +1,126 @@
|
||||
import sys,urllib3
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
class Spider(Spider):
|
||||
host = 'https://film.symx.club'
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'accept-language': 'zh-CN,zh;q=0.9',
|
||||
'cache-control': 'no-cache',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': f'{host}/',
|
||||
'sec-ch-ua': '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'x-platform': 'web'
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
host = extend.strip().rstrip('/')
|
||||
if host.startswith('http'):
|
||||
self.host = host
|
||||
return None
|
||||
return None
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
def homeContent(self, filter):
|
||||
response = self.fetch(f'{self.host}/api/category/top', headers=self.headers, verify=False).json()
|
||||
classes = []
|
||||
for i in response['data']:
|
||||
if isinstance(i,dict):
|
||||
classes.append({'type_id': i['id'], 'type_name': i['name']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f'{self.host}/api/film/category',headers=self.headers, verify=False).json()
|
||||
videos = []
|
||||
for i in response['data']:
|
||||
for j in i.get('filmList',[]):
|
||||
videos.append({
|
||||
'vod_id': j.get('id'),
|
||||
'vod_name': j.get('name'),
|
||||
'vod_pic': j.get('cover'),
|
||||
'vod_remarks': j.get('doubanScore')
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(f'{self.host}/api/film/category/list?area=&categoryId={tid}&language=&pageNum={pg}&pageSize=15&sort=updateTime&year=', headers=self.headers, verify=False).json()
|
||||
videos = []
|
||||
for i in response['data']['list']:
|
||||
videos.append({
|
||||
'vod_id': i.get('id'),
|
||||
'vod_name': i.get('name'),
|
||||
'vod_pic': i.get('cover'),
|
||||
'vod_remarks': i.get('updateStatus')
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
response = self.fetch(f'{self.host}/api/film/search?keyword={key}&pageNum={pg}&pageSize=10', headers=self.headers, verify=False).json()
|
||||
videos = []
|
||||
for i in response['data']['list']:
|
||||
videos.append({
|
||||
'vod_id': i.get('id'),
|
||||
'vod_name': i.get('name'),
|
||||
'vod_pic': i.get('cover'),
|
||||
'vod_remarks': i.get('updateStatus'),
|
||||
'vod_year': i.get('year'),
|
||||
'vod_area': i.get('area'),
|
||||
'vod_director': i.get('director')
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f'{self.host}/api/film/detail?id={ids[0]}',headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
video, show, play_urls, = {}, [], []
|
||||
for i in data['playLineList']:
|
||||
show.append(i['playerName'])
|
||||
play_url = []
|
||||
for j in i['lines']:
|
||||
play_url.append(f"{j['name']}${j['id']}")
|
||||
play_urls.append('#'.join(play_url))
|
||||
video.update({
|
||||
'vod_id': data.get('id'),
|
||||
'vod_name': data.get('name'),
|
||||
'vod_pic': data.get('cover'),
|
||||
'vod_year': data.get('year'),
|
||||
'vod_area': data.get('other'),
|
||||
'vod_actor': data.get('actor'),
|
||||
'vod_director': data.get('director'),
|
||||
'vod_content': data.get('blurb'),
|
||||
'vod_score': data.get('doubanScore'),
|
||||
'vod_play_from': '$$$'.join(show),
|
||||
'vod_play_url': '$$$'.join(play_urls)
|
||||
})
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
response = self.fetch(f'{self.host}/api/line/play/parse?lineId={id}', headers=self.headers).json()
|
||||
return { 'jx': 0, 'parse': 0, 'url': response['data'], 'header': {'User-Agent': self.headers['User-Agent']}}
|
||||
|
||||
|
||||
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,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import sys
|
||||
from base.spider import Spider
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
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",
|
||||
'Accept-Encoding': "gzip"
|
||||
}
|
||||
host = 'https://api.xingzhige.com'
|
||||
|
||||
def init(self, extend=''):
|
||||
ext = extend.strip()
|
||||
if ext.startswith('http'):
|
||||
self.host = 'ext'
|
||||
|
||||
def homeContent(self, filter):
|
||||
return {'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': '权谋身份'}]}
|
||||
|
||||
def search(self,key,pg=1):
|
||||
response = self.fetch(f'{self.host}/API/playlet/?keyword={key}&page={pg}', headers=self.headers).json()
|
||||
data = response['data']
|
||||
videos = []
|
||||
for i in data:
|
||||
videos.append({
|
||||
'vod_id': i.get('book_id'),
|
||||
'vod_name': i.get('title'),
|
||||
'vod_pic': i.get('cover'),
|
||||
'vod_remarks': i.get('type')
|
||||
})
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return self.search('热门')
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f'{self.host}/API/playlet/?book_id={ids[0]}', headers=self.headers).json()
|
||||
data = response['data']
|
||||
detail = data['detail']
|
||||
vod_play_url = ''
|
||||
for i in data['video_list']:
|
||||
vod_play_url += f"{i.get('title','')}${i.get('video_id','')}#"
|
||||
vod_play_url.rstrip('#')
|
||||
videos = []
|
||||
|
||||
videos.append({
|
||||
'vod_id': detail.get('book_id'),
|
||||
'vod_name': detail.get('title'),
|
||||
'vod_content': detail.get('desc'),
|
||||
'vod_remarks': '集数:' + str(detail.get('total')),
|
||||
'vod_area': detail.get('vod_area'),
|
||||
'vod_play_from': '星之阁',
|
||||
'vod_play_url': vod_play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def searchContent(self, key, quick, pg=1):
|
||||
return self.search(key,pg)
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
return self.search(tid,pg)
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
response = self.fetch(f'{self.host}/API/playlet/?video_id={id}&quality=1080p', headers=self.headers).json()
|
||||
data = response['data']
|
||||
return {'jx': 0, 'parse': 0, 'url': data['video']['url'],'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'}}
|
||||
|
||||
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,135 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import re,sys,json,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
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, verify=False).json()
|
||||
videos = self.json2vods(response)
|
||||
return {'list': videos}
|
||||
|
||||
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, verify=False).json()
|
||||
videos = self.json2vods(response)
|
||||
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, verify=False).json()
|
||||
videos = self.json2vods(response)
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.post(f'{self.api_host}/api/detail/{ids[0]}', data='{}', headers=self.headers, verify=False).json()
|
||||
data = response['data']
|
||||
play_urls = []
|
||||
for name,url in data['player'].items():
|
||||
play_urls.append(f'{name}${url}&auto=1')
|
||||
videos = {
|
||||
'vod_id': data.get('vod_id'),
|
||||
'vod_name': data.get('vod_name'),
|
||||
'vod_content': data.get('vod_blurb'),
|
||||
'vod_remarks': f"集数:{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': '#'.join(play_urls),
|
||||
'type_name': f"{data.get('vod_class')},{data.get('vod_tag')}",
|
||||
}
|
||||
return {'list': [videos]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
parse = 0
|
||||
try:
|
||||
response = self.fetch(vid, headers=self.headers, verify=False).text
|
||||
match = re.search(r'let\s+data\s*=\s*(\{[^}]*http[^}]*\});', response, re.IGNORECASE)
|
||||
data = match.group(1)
|
||||
data2 = json.loads(data)
|
||||
play_url = data2['url']
|
||||
if play_url.startswith('http'):
|
||||
url = play_url
|
||||
except Exception:
|
||||
url, parse, header = vid, 1, self.headers
|
||||
return {'parse': parse, 'jx': 0, 'url': url,'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'}}
|
||||
|
||||
def json2vods(self,arr):
|
||||
videos = []
|
||||
for i in arr['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_area': i.get('vod_area'),
|
||||
"vod_remarks": f"{i.get('vod_class','')} {i['vod_total']}集",
|
||||
'type_name': f"{i.get('vod_class')},{i.get('vod_tag')}"
|
||||
})
|
||||
return videos
|
||||
|
||||
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,221 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from urllib.parse import quote_plus
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,json,base64,urllib3,hashlib
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
main_headers,key,host = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36',
|
||||
'accept-language': 'zh-CN,zh;q=0.9',
|
||||
'sec-ch-ua': '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
|
||||
'cache-control': 'no-cache',
|
||||
'pragma': 'no-cache',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-site': 'same-origin'
|
||||
},'Mcxos@mucho!nmme','https://www.mqtv.cc'
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
return {'class': [{'type_id': '/type/movie', 'type_name': '电影'},{'type_id': '/type/tv', 'type_name': '电视剧'},{'type_id': '/type/va', 'type_name': '综艺'},{'type_id': '/type/ct', 'type_name': '动漫'}]}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f"{self.host}/libs/VodList.api.php?home=index&token={self.token('/')}", headers=self.headers2('/'), verify=False).json()
|
||||
videos = []
|
||||
for i in response['data']['movie']:
|
||||
videos.extend(self.arr2vods(i.get('show',[])))
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
url = f"{self.host}/libs/VodList.api.php?type={tid.split('/',2)[2]}&rank=rankhot&cat=&year=&area=&page={pg}&token={self.token(tid,'/')}"
|
||||
response = self.fetch(url, headers=self.headers2(tid), verify=False).json()
|
||||
videos = self.arr2vods(response['data'])
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
path = f'/search/{quote_plus(key)}'
|
||||
url = f"{self.host}/libs/VodList.api.php?search={key}&token={self.token(path, '/')}"
|
||||
response = self.fetch(url, headers=self.headers2(path), verify=False).json()
|
||||
data = self.decode(response['data'])
|
||||
videos = []
|
||||
for i in data['vod_all']:
|
||||
if isinstance(i,dict):
|
||||
videos.extend(self.arr2vods(i.get('show',[])))
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
url = f"{self.host}/libs/VodInfo.api.php?type=ct&id={ids[0].split('/',3)[3]}&token={self.token(ids[0], '/')}"
|
||||
response = self.fetch(url, headers=self.headers2(ids[0]), verify=False).json()
|
||||
data = response['data']
|
||||
parses_arr = []
|
||||
for i in data.get('playapi',[]):
|
||||
if isinstance(i, dict) and i.get('url') and isinstance(i['url'], str):
|
||||
if i['url'].startswith('//'):
|
||||
parses_arr.append(f"https:{i['url']}")
|
||||
else:
|
||||
parses_arr.append(i['url'])
|
||||
parses = ','.join(parses_arr)
|
||||
shows, play_urls = [], []
|
||||
for j in data['playinfo']:
|
||||
urls = []
|
||||
for k in j['player']:
|
||||
urls.append(f"{k['no']}${k['url']}@{parses}")
|
||||
if urls:
|
||||
play_urls.append('#'.join(urls))
|
||||
shows.append(j['cnsite'])
|
||||
video = {
|
||||
'vod_id': ids[0],
|
||||
'vod_name': data['title'],
|
||||
'vod_pic': data['img'],
|
||||
'vod_remarks': data['remark'],
|
||||
'vod_year': data['year'],
|
||||
'vod_area': data['area'],
|
||||
'vod_actor': data['actor'],
|
||||
'vod_director': data['director'],
|
||||
'vod_content': None,
|
||||
'vod_play_from': '$$$'.join(shows),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': None
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
raw_url, parses_str = vid.split('@',1)
|
||||
jx,sniff,url = 0,0,''
|
||||
parses = parses_str.split(',')
|
||||
for i in parses:
|
||||
try:
|
||||
response = self.fetch(f'{i}{raw_url}', headers=self.main_headers, verify=False, timeout=10).text
|
||||
charset_meta_id = re.search(r'<(?:\s+)?meta(?:\s+)?charset="UTF-8"(?:\s+)?id(?:\s+)?=(?:\s+)?"now_(.*?)"(?:\s+)?>', response, re.I).groups()[0]
|
||||
viewport_meta_id = re.search(r'<(?:\s+)?meta(?:\s+)?name(?:\s+)?=(?:\s+)?"viewport".*?id(?:\s+)?=(?:\s+)?"now_(.*?)">', response, re.I).groups()[0]
|
||||
jm_url = re.search(r'"url"(?:\s+)?:\s+?"(.*?)",', response, re.I).groups()[0]
|
||||
play_url = self.decrypt(jm_url,viewport_meta_id,charset_meta_id)
|
||||
if play_url.startswith('http'): url = play_url
|
||||
except Exception:
|
||||
continue
|
||||
if not url:
|
||||
if raw_url.startswith('http') and re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', raw_url):
|
||||
url,jx = raw_url,1
|
||||
else:
|
||||
for j in parses:
|
||||
if j.startswith('http'):
|
||||
url,sniff = f'{j}{raw_url}',1
|
||||
break
|
||||
return { 'jx': jx, 'parse': sniff, 'url': url, 'header': {'User-Agent': self.main_headers['User-Agent']}}
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
for i in arr:
|
||||
videos.append({
|
||||
'vod_id': i['url'],
|
||||
'vod_name': i['title'],
|
||||
'vod_pic': i['img'],
|
||||
'vod_remarks': i['remark'],
|
||||
'vod_year': None
|
||||
})
|
||||
return videos
|
||||
|
||||
def token(self,path,ref_path=''):
|
||||
headers = {
|
||||
**self.main_headers,
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'priority': 'u=0, i',
|
||||
'sec-fetch-dest': 'document',
|
||||
'sec-fetch-mode': 'navigate',
|
||||
'sec-fetch-user': '?1',
|
||||
'upgrade-insecure-requests': '1'
|
||||
}
|
||||
if ref_path: headers['referer']= f'{self.host}{ref_path}'
|
||||
response = self.fetch(f'{self.host}{path}', headers=headers, verify=False).text
|
||||
page_id = m.group(1) if (m := re.search(r'window\.pageid\s?=\s?\'(.*?)\';', response, re.I)) else None
|
||||
return self.encode(page_id)
|
||||
|
||||
def headers2(self,ref_path=''):
|
||||
headers = {
|
||||
**self.main_headers,
|
||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'priority': 'u=1, i',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'x-requested-with': 'XMLHttpRequest'
|
||||
}
|
||||
if ref_path: headers['referer']= f'{self.host}{ref_path}'
|
||||
return headers
|
||||
|
||||
def encode(self,data):
|
||||
key = self.key
|
||||
key_len = len(key)
|
||||
json_str = json.dumps(data, ensure_ascii=False)
|
||||
b64_1 = base64.b64encode(json_str.encode('utf-8')).decode('utf-8')
|
||||
xor_result = []
|
||||
for i, char in enumerate(b64_1):
|
||||
key_idx = i % key_len
|
||||
xor_code = ord(char) ^ ord(key[key_idx])
|
||||
xor_result.append(chr(xor_code))
|
||||
xor_str = ''.join(xor_result)
|
||||
return quote_plus(base64.b64encode(xor_str.encode('utf-8')).decode('utf-8'))
|
||||
|
||||
def decode(self,encoded_str):
|
||||
key = self.key
|
||||
key_length = len(key)
|
||||
try:
|
||||
decoded_step1 = base64.b64decode(encoded_str + '=' * (4 - len(encoded_str) % 4), altchars=None)
|
||||
decoded_step1_str = decoded_step1.decode('utf-8')
|
||||
except Exception:
|
||||
raise ValueError()
|
||||
xor_result = []
|
||||
for i, char in enumerate(decoded_step1_str):
|
||||
key_index = i % key_length
|
||||
char_code = ord(char) ^ ord(key[key_index])
|
||||
xor_result.append(chr(char_code))
|
||||
xor_str = ''.join(xor_result)
|
||||
try:
|
||||
decoded_step2 = base64.b64decode(xor_str + '=' * (4 - len(xor_str) % 4), altchars=None)
|
||||
decoded_step2_str = decoded_step2.decode('utf-8')
|
||||
except Exception:
|
||||
raise ValueError()
|
||||
try:
|
||||
result = json.loads(decoded_step2_str)
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError()
|
||||
return result
|
||||
|
||||
def decrypt(self, encrypted_str, viewport_meta_id, charset_meta_id):
|
||||
id_text_list = []
|
||||
for idx in range(len(charset_meta_id)):
|
||||
id_char = charset_meta_id[idx]
|
||||
text_char = viewport_meta_id[idx] if idx < len(viewport_meta_id) else ''
|
||||
id_text_list.append({'id':id_char, 'text':text_char})
|
||||
id_text_list_sorted = sorted(id_text_list, key=lambda x: int(x['id']))
|
||||
seed = ''.join([item['text'] for item in id_text_list_sorted])
|
||||
md5_result = hashlib.md5(f'{seed}lemon'.encode('utf-8')).hexdigest()
|
||||
cipher_data = base64.b64decode(encrypted_str)
|
||||
cipher = AES.new(md5_result[16:].encode('utf-8'),AES.MODE_CBC,md5_result[:16].encode('utf-8'))
|
||||
decrypted_data = cipher.decrypt(cipher_data)
|
||||
plain_data = unpad(decrypted_data, AES.block_size, style='pkcs7')
|
||||
return plain_data.decode('utf-8')
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user