From 03ddf98142a138853f9a3e1b2a5eae5fd4defb4f Mon Sep 17 00:00:00 2001 From: XC-Pro <19840+xc-pro@noreply.example.org> Date: Sat, 15 Aug 2026 10:30:30 +0200 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8Cpy=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- py/drpy2.min.js | 1 + py/jieyingshi.py | 180 +++++++++++++++++ py/枫叶影院.py | 392 ++++++++++++++++++++++++++++++++++++ py/独播库.py | 270 +++++++++++++++++++++++++ py/瓜娃子.py | 510 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1353 insertions(+) create mode 100644 py/drpy2.min.js create mode 100644 py/jieyingshi.py create mode 100644 py/枫叶影院.py create mode 100644 py/独播库.py create mode 100644 py/瓜娃子.py diff --git a/py/drpy2.min.js b/py/drpy2.min.js new file mode 100644 index 0000000..437c853 --- /dev/null +++ b/py/drpy2.min.js @@ -0,0 +1 @@ +import cheerio from"assets://js/lib/cheerio.min.js";import"assets://js/lib/crypto-js.js";import 模板 from"./模板.js";import{gbkTool}from"./gbk.js";function init_test(){console.log("init_test_start");console.log("当前版本号:"+VERSION);console.log(RKEY);console.log(JSON.stringify(rule));console.log("init_test_end")}function pre(){if(typeof rule.预处理==="string"&&rule.预处理&&rule.预处理.trim()){let code=rule.预处理.trim();console.log("执行预处理代码:"+code);if(code.startsWith("js:")){code=code.replace("js:","")}try{eval(code)}catch(e){console.log("预处理执行失败:"+e.message)}}}let rule={};let vercode=typeof pdfl==="function"?"drpy2.1":"drpy2";const VERSION=vercode+" 3.9.49beta2 20231122";const MOBILE_UA="Mozilla/5.0 (Linux; Android 11; M2007J3SC Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045714 Mobile Safari/537.36";const PC_UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36";const UA="Mozilla/5.0";const UC_UA="Mozilla/5.0 (Linux; U; Android 9; zh-CN; MI 9 Build/PKQ1.181121.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.5.5.1035 Mobile Safari/537.36";const IOS_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";const RULE_CK="cookie";const CATE_EXCLUDE="首页|留言|APP|下载|资讯|新闻|动态";const TAB_EXCLUDE="猜你|喜欢|下载|剧情|热播";const OCR_RETRY=3;const OCR_API="http://drpy.nokia.press:8028/ocr/drpy/text";if(typeof MY_URL==="undefined"){var MY_URL}var HOST;var RKEY;var fetch;var print;var log;var rule_fetch_params;var fetch_params;var oheaders;var _pdfh;var _pdfa;var _pd;const DOM_CHECK_ATTR=/(url|src|href|-original|-src|-play|-url|style)$/;const SPECIAL_URL=/^(ftp|magnet|thunder|ws):/;const NOADD_INDEX=/:eq|:lt|:gt|:first|:last|^body$|^#/;const URLJOIN_ATTR=/(url|src|href|-original|-src|-play|-url|style)$/;const SELECT_REGEX=/:eq|:lt|:gt|#/g;const SELECT_REGEX_A=/:eq|:lt|:gt/g;if(typeof Object.assign!="function"){Object.assign=function(){var target=arguments[0];for(var i=1;ithis.length){return false}else{return this.indexOf(search,start)!==-1}}}if(!Array.prototype.includes){Object.defineProperty(Array.prototype,"includes",{value:function(searchElement,fromIndex){if(this==null){throw new TypeError('"this" is null or not defined')}var o=Object(this);var len=o.length>>>0;if(len===0){return false}var n=fromIndex|0;var k=Math.max(n>=0?n:len-Math.abs(n),0);while(k=end){return lists}let first=lists[start];let second=lists[end];if(key){try{first=first[key];second=second[key]}catch(e){}}if(option&&typeof option==="function"){try{first=option(first);second=option(second)}catch(e){}}first+="";second+="";if(first.match(/(\d+)/)&&second.match(/(\d+)/)){let num1=Number(first.match(/(\d+)/)[1]);let num2=Number(second.match(/(\d+)/)[1]);if(num1>num2){lists.reverse()}}return lists}let VODS=[];let VOD={};let TABS=[];let LISTS=[];globalThis.encodeUrl=urlencode;globalThis.urlencode=urlencode;function urljoin(fromPath,nowPath){fromPath=fromPath||"";nowPath=nowPath||"";return joinUrl(fromPath,nowPath)}var urljoin2=urljoin;const defaultParser={pdfh:pdfh,pdfa:pdfa,pd:pd};function pdfh2(html,parse){let html2=html;try{if(typeof html!=="string"){html2=html.rr(html.ele).toString()}}catch(e){print("html对象转文本发生了错误:"+e.message)}let result=defaultParser.pdfh(html2,parse);let option=parse.includes("&&")?parse.split("&&").slice(-1)[0]:parse.split(" ").slice(-1)[0];if(/style/.test(option.toLowerCase())&&/url\(/.test(result)){try{result=result.match(/url\((.*?)\)/)[1];result=result.replace(/^['|"](.*)['|"]$/,"$1")}catch(e){}}return result}function pdfa2(html,parse){let html2=html;try{if(typeof html!=="string"){html2=html.rr(html.ele).toString()}}catch(e){print("html对象转文本发生了错误:"+e.message)}return defaultParser.pdfa(html2,parse)}function pd2(html,parse,uri){let ret=pdfh2(html,parse);if(typeof uri==="undefined"||!uri){uri=""}if(DOM_CHECK_ATTR.test(parse)&&!SPECIAL_URL.test(ret)){if(/http/.test(ret)){ret=ret.substr(ret.indexOf("http"))}else{ret=urljoin(MY_URL,ret)}}return ret}const parseTags={jsp:{pdfh:pdfh2,pdfa:pdfa2,pd:pd2},json:{pdfh(html,parse){if(!parse||!parse.trim()){return""}if(typeof html==="string"){html=JSON.parse(html)}parse=parse.trim();if(!parse.startsWith("$.")){parse="$."+parse}parse=parse.split("||");for(let ps of parse){let ret=cheerio.jp(ps,html);if(Array.isArray(ret)){ret=ret[0]||""}else{ret=ret||""}if(ret&&typeof ret!=="string"){ret=ret.toString()}if(ret){return ret}}return""},pdfa(html,parse){if(!parse||!parse.trim()){return""}if(typeof html==="string"){html=JSON.parse(html)}parse=parse.trim();if(!parse.startsWith("$.")){parse="$."+parse}let ret=cheerio.jp(parse,html);if(Array.isArray(ret)&&Array.isArray(ret[0])&&ret.length===1){return ret[0]||[]}return ret||[]},pd(html,parse){let ret=parseTags.json.pdfh(html,parse);if(ret){return urljoin(MY_URL,ret)}return ret}},jq:{pdfh(html,parse){if(!html||!parse||!parse.trim()){return""}parse=parse.trim();let result=defaultParser.pdfh(html,parse);return result},pdfa(html,parse){if(!html||!parse||!parse.trim()){return[]}parse=parse.trim();let result=defaultParser.pdfa(html,parse);print(`pdfa解析${parse}=>${result.length}`);return result},pd(html,parse,base_url){if(!html||!parse||!parse.trim()){return""}parse=parse.trim();base_url=base_url||MY_URL;return defaultParser.pd(html,parse,base_url)}},getParse(p0){if(p0.startsWith("jsp:")){return this.jsp}else if(p0.startsWith("json:")){return this.json}else if(p0.startsWith("jq:")){return this.jq}else{return this.jq}}};const stringify=JSON.stringify;const jsp=parseTags.jsp;const jq=parseTags.jq;function readFile(filePath){filePath=filePath||"./uri.min.js";var fd=os.open(filePath);var buffer=new ArrayBuffer(1024);var len=os.read(fd,buffer,0,1024);console.log(len);let text=String.fromCharCode.apply(null,new Uint8Array(buffer));console.log(text);return text}function dealJson(html){try{html=html.trim();if(!(html.startsWith("{")&&html.endsWith("}")||html.startsWith("[")&&html.endsWith("]"))){html="{"+html.match(/.*?\{(.*)\}/m)[1]+"}"}}catch(e){}try{html=JSON.parse(html)}catch(e){}return html}var OcrApi={api:OCR_API,classification:function(img){let code="";try{log("通过drpy_ocr验证码接口过验证...");let html=request(OCR_API,{data:{img:img},headers:{"User-Agent":PC_UA},method:"POST"},true);code=html||""}catch(e){log(`OCR识别验证码发生错误:${e.message}`)}return code}};function verifyCode(url){let cnt=0;let host=getHome(url);let cookie="";while(cntit.toLowerCase()==="set-cookie");cookie=setCk?json[setCk].split(";")[0]:""}console.log("cookie:"+cookie);let img=json.body;let code=OcrApi.classification(img);console.log(`第${cnt+1}次验证码识别结果:${code}`);let submit_url=`${host}/index.php/ajax/verify_check?type=search&verify=${code}`;console.log(submit_url);let html=request(submit_url,{headers:{Cookie:cookie,"User-Agent":MOBILE_UA},method:"POST"});html=JSON.parse(html);if(html.msg==="ok"){console.log(`第${cnt+1}次验证码提交成功`);return cookie}else if(html.msg!=="ok"&&cnt+1>=OCR_RETRY){cookie=""}}catch(e){console.log(`第${cnt+1}次验证码提交失败:${e.message}`);if(cnt+1>=OCR_RETRY){cookie=""}}cnt+=1}return cookie}function setItem(k,v){local.set(RKEY,k,v);console.log(`规则${RKEY}设置${k} => ${v}`)}function getItem(k,v){return local.get(RKEY,k)||v}function clearItem(k){local.delete(RKEY,k)}function getHome(url){if(!url){return""}let tmp=url.split("//");url=tmp[0]+"//"+tmp[1].split("/")[0];try{url=decodeURIComponent(url)}catch(e){}return url}function buildUrl(url,obj){obj=obj||{};if(url.indexOf("?")<0){url+="?"}let param_list=[];let keys=Object.keys(obj);keys.forEach(it=>{param_list.push(it+"="+obj[it])});let prs=param_list.join("&");if(keys.length>0&&!url.endsWith("?")){url+="&"}url+=prs;return url}function require(url){eval(request(url))}function request(url,obj,ocr_flag){ocr_flag=ocr_flag||false;if(typeof obj==="undefined"||!obj||obj==={}){if(!fetch_params||!fetch_params.headers){let headers={"User-Agent":MOBILE_UA};if(rule.headers){Object.assign(headers,rule.headers)}if(!fetch_params){fetch_params={}}fetch_params.headers=headers}if(!fetch_params.headers.Referer){fetch_params.headers.Referer=getHome(url)}obj=fetch_params}else{let headers=obj.headers||{};let keys=Object.keys(headers).map(it=>it.toLowerCase());if(!keys.includes("user-agent")){headers["User-Agent"]=MOBILE_UA}if(!keys.includes("referer")){headers["Referer"]=getHome(url)}obj.headers=headers}if(rule.encoding&&rule.encoding!=="utf-8"&&!ocr_flag){if(!obj.headers.hasOwnProperty("Content-Type")&&!obj.headers.hasOwnProperty("content-type")){obj.headers["Content-Type"]="text/html; charset="+rule.encoding}}if(typeof obj.body!="undefined"&&obj.body&&typeof obj.body==="string"){if(!obj.headers.hasOwnProperty("Content-Type")&&!obj.headers.hasOwnProperty("content-type")){obj.headers["Content-Type"]="application/x-www-form-urlencoded; charset="+rule.encoding}}else if(typeof obj.body!="undefined"&&obj.body&&typeof obj.body==="object"){obj.data=obj.body;delete obj.body}if(!url){return obj.withHeaders?"{}":""}if(obj.toBase64){obj.buffer=2;delete obj.toBase64}console.log(JSON.stringify(obj.headers));console.log("request:"+url+`|method:${obj.method||"GET"}|body:${obj.body||""}`);let res=req(url,obj);let html=res.content||"";if(obj.withHeaders){let htmlWithHeaders=res.headers;htmlWithHeaders.body=html;return JSON.stringify(htmlWithHeaders)}else{return html}}function post(url,obj){obj.method="POST";return request(url,obj)}fetch=request;print=function(data){data=data||"";if(typeof data=="object"&&Object.keys(data).length>0){try{data=JSON.stringify(data);console.log(data)}catch(e){console.log(typeof data+":"+data.length);return}}else if(typeof data=="object"&&Object.keys(data).length<1){console.log("null object")}else{console.log(data)}};log=print;function checkHtml(html,url,obj){if(/\?btwaf=/.test(html)){let btwaf=html.match(/btwaf(.*?)"/)[1];url=url.split("#")[0]+"?btwaf"+btwaf;print("宝塔验证访问链接:"+url);html=request(url,obj)}return html}function getCode(url,obj){let html=request(url,obj);html=checkHtml(html,url,obj);return html}function getHtml(url){let obj={};if(rule.headers){obj.headers=rule.headers}let cookie=getItem(RULE_CK,"");if(cookie){if(obj.headers&&!Object.keys(obj.headers).map(it=>it.toLowerCase()).includes("cookie")){log("历史无cookie,新增过验证后的cookie");obj.headers["Cookie"]=cookie}else if(obj.headers&&obj.headers.cookie&&obj.headers.cookie!==cookie){obj.headers["Cookie"]=cookie;log("历史有小写过期的cookie,更新过验证后的cookie")}else if(obj.headers&&obj.headers.Cookie&&obj.headers.Cookie!==cookie){obj.headers["Cookie"]=cookie;log("历史有大写过期的cookie,更新过验证后的cookie")}else if(!obj.headers){obj.headers={Cookie:cookie};log("历史无headers,更新过验证后的含cookie的headers")}}let html=getCode(url,obj);return html}function homeParse(homeObj){fetch_params=JSON.parse(JSON.stringify(rule_fetch_params));let classes=[];if(homeObj.class_name&&homeObj.class_url){let names=homeObj.class_name.split("&");let urls=homeObj.class_url.split("&");let cnt=Math.min(names.length,urls.length);for(let i=0;i=3){try{let html=getHtml(homeObj.MY_URL);if(html){homeHtmlCache=html;let list=_pdfa(html,p[0]);if(list&&list.length>0){list.forEach((it,idex)=>{try{let name=_pdfh(it,p[1]);if(homeObj.cate_exclude&&new RegExp(homeObj.cate_exclude).test(name)){return}let url=_pd(it,p[2]);if(p.length>3&&p[3]){let exp=new RegExp(p[3]);url=url.match(exp)[1]}classes.push({type_id:url.trim(),type_name:name.trim()})}catch(e){console.log(`分类列表定位第${idex}个元素正常报错:${e.message}`)}})}}}catch(e){console.log(e.message)}}}classes=classes.filter(it=>!homeObj.cate_exclude||!new RegExp(homeObj.cate_exclude).test(it.type_name));let resp={class:classes};if(homeObj.filter){resp.filters=homeObj.filter}console.log(JSON.stringify(resp));return JSON.stringify(resp)}function getPP(p,pn,pp,ppn){try{let ps=p[pn]==="*"&&pp.length>ppn?pp[ppn]:p[pn];return ps}catch(e){return""}}function homeVodParse(homeVodObj){fetch_params=JSON.parse(JSON.stringify(rule_fetch_params));let d=[];MY_URL=homeVodObj.homeUrl;console.log(MY_URL);let t1=(new Date).getTime();let p=homeVodObj.推荐;print("p:"+p);if(p==="*"&&rule.一级){p=rule.一级;homeVodObj.double=false}if(!p||typeof p!=="string"){return"{}"}p=p.trim();let pp=rule.一级.split(";");if(p.startsWith("js:")){const TYPE="home";var input=MY_URL;HOST=rule.host;eval(p.replace("js:",""));d=VODS}else{p=p.split(";");if(!homeVodObj.double&&p.length<5){return"{}"}else if(homeVodObj.double&&p.length<6){return"{}"}let p0=getPP(p,0,pp,0);let _ps=parseTags.getParse(p0);_pdfa=_ps.pdfa;_pdfh=_ps.pdfh;_pd=_ps.pd;let is_json=p0.startsWith("json:");p0=p0.replace(/^(jsp:|json:|jq:)/,"");let html=homeHtmlCache||getHtml(MY_URL);homeHtmlCache=undefined;if(is_json){html=dealJson(html)}try{console.log("double:"+homeVodObj.double);if(homeVodObj.double){let items=_pdfa(html,p0);let p1=getPP(p,1,pp,0);let p2=getPP(p,2,pp,1);let p3=getPP(p,3,pp,2);let p4=getPP(p,4,pp,3);let p5=getPP(p,5,pp,4);let p6=getPP(p,6,pp,5);for(let item of items){let items2=_pdfa(item,p1);for(let item2 of items2){try{let title=_pdfh(item2,p2);let img="";try{img=_pd(item2,p3)}catch(e){}let desc="";try{desc=_pdfh(item2,p4)}catch(e){}let links=[];for(let _p5 of p5.split("+")){let link=!homeVodObj.detailUrl?_pd(item2,_p5,MY_URL):_pdfh(item2,_p5);links.push(link)}let content;if(p.length>6&&p[6]){content=_pdfh(item2,p6)}else{content=""}let vid=links.join("$");if(rule.二级==="*"){vid=vid+"@@"+title+"@@"+img}let vod={vod_name:title,vod_pic:img,vod_remarks:desc,vod_content:content,vod_id:vid};d.push(vod)}catch(e){console.log("首页列表双层定位处理发生错误:"+e.message)}}}}else{let items=_pdfa(html,p0);let p1=getPP(p,1,pp,1);let p2=getPP(p,2,pp,2);let p3=getPP(p,3,pp,3);let p4=getPP(p,4,pp,4);let p5=getPP(p,5,pp,5);for(let item of items){try{let title=_pdfh(item,p1);let img="";try{img=_pd(item,p2,MY_URL)}catch(e){}let desc="";try{desc=_pdfh(item,p3)}catch(e){}let links=[];for(let _p5 of p4.split("+")){let link=!homeVodObj.detailUrl?_pd(item,_p5,MY_URL):_pdfh(item,_p5);links.push(link)}let content;if(p.length>5&&p[5]){content=_pdfh(item,p5)}else{content=""}let vid=links.join("$");if(rule.二级==="*"){vid=vid+"@@"+title+"@@"+img}let vod={vod_name:title,vod_pic:img,vod_remarks:desc,vod_content:content,vod_id:vid};d.push(vod)}catch(e){console.log("首页列表单层定位处理发生错误:"+e.message)}}}}catch(e){}}let t2=(new Date).getTime();console.log("加载首页推荐耗时:"+(t2-t1)+"毫秒");if(rule.图片来源){d.forEach(it=>{if(it.vod_pic&&it.vod_pic.startsWith("http")){it.vod_pic=it.vod_pic+rule.图片来源}})}if(d.length>0){print(d.slice(0,2))}return JSON.stringify({list:d})}function categoryParse(cateObj){fetch_params=JSON.parse(JSON.stringify(rule_fetch_params));let p=cateObj.一级;if(!p||typeof p!=="string"){return"{}"}let d=[];let url=cateObj.url.replaceAll("fyclass",cateObj.tid);if(cateObj.pg===1&&url.includes("[")&&url.includes("]")){url=url.split("[")[1].split("]")[0]}else if(cateObj.pg>1&&url.includes("[")&&url.includes("]")){url=url.split("[")[0]}if(rule.filter_url){if(!/fyfilter/.test(url)){if(!url.endsWith("&")&&!rule.filter_url.startsWith("&")){url+="&"}url+=rule.filter_url}else{url=url.replace("fyfilter",rule.filter_url)}let fl=cateObj.filter?cateObj.extend:{};if(rule.filter_def&&typeof rule.filter_def==="object"){try{if(Object.keys(rule.filter_def).length>0&&rule.filter_def.hasOwnProperty(cateObj.tid)){let self_fl_def=rule.filter_def[cateObj.tid];if(self_fl_def&&typeof self_fl_def==="object"){let fl_def=JSON.parse(JSON.stringify(self_fl_def));fl=Object.assign(fl_def,fl)}}}catch(e){print("合并不同分类对应的默认筛选出错:"+e.message)}}let new_url;new_url=cheerio.jinja2(url,{fl:fl});url=new_url}if(/fypage/.test(url)){if(url.includes("(")&&url.includes(")")){let url_rep=url.match(/.*?\((.*)\)/)[1];let cnt_page=url_rep.replaceAll("fypage",cateObj.pg);let cnt_pg=eval(cnt_page);url=url.replaceAll(url_rep,cnt_pg).replaceAll("(","").replaceAll(")","")}else{url=url.replaceAll("fypage",cateObj.pg)}}MY_URL=url;console.log(MY_URL);p=p.trim();const MY_CATE=cateObj.tid;if(p.startsWith("js:")){var MY_FL=cateObj.extend;const TYPE="cate";var input=MY_URL;const MY_PAGE=cateObj.pg;var desc="";eval(p.trim().replace("js:",""));d=VODS}else{p=p.split(";");if(p.length<5){return"{}"}let _ps=parseTags.getParse(p[0]);_pdfa=_ps.pdfa;_pdfh=_ps.pdfh;_pd=_ps.pd;let is_json=p[0].startsWith("json:");p[0]=p[0].replace(/^(jsp:|json:|jq:)/,"");try{let html=getHtml(MY_URL);if(html){if(is_json){html=dealJson(html)}let list=_pdfa(html,p[0]);list.forEach(it=>{let links=p[4].split("+").map(p4=>{return!rule.detailUrl?_pd(it,p4,MY_URL):_pdfh(it,p4)});let link=links.join("$");let vod_id=rule.detailUrl?MY_CATE+"$"+link:link;let vod_name=_pdfh(it,p[1]).replace(/\n|\t/g,"").trim();let vod_pic=_pd(it,p[2],MY_URL);if(rule.二级==="*"){vod_id=vod_id+"@@"+vod_name+"@@"+vod_pic}d.push({vod_id:vod_id,vod_name:vod_name,vod_pic:vod_pic,vod_remarks:_pdfh(it,p[3]).replace(/\n|\t/g,"").trim()})})}}catch(e){console.log(e.message)}}if(rule.图片来源){d.forEach(it=>{if(it.vod_pic&&it.vod_pic.startsWith("http")){it.vod_pic=it.vod_pic+rule.图片来源}})}if(d.length>0){print(d.slice(0,2))}let pagecount=0;if(rule.pagecount&&typeof rule.pagecount==="object"&&rule.pagecount.hasOwnProperty(MY_CATE)){print(`MY_CATE:${MY_CATE},pagecount:${JSON.stringify(rule.pagecount)}`);pagecount=parseInt(rule.pagecount[MY_CATE])}let nodata={list:[{vod_name:"无数据,防无限请求",vod_id:"no_data",vod_remarks:"不要点,会崩的",vod_pic:"https://ghproxy.net/https://raw.githubusercontent.com/hjdhnx/dr_py/main/404.jpg"}],total:1,pagecount:1,page:1,limit:1};let vod=d.length<1?JSON.stringify(nodata):JSON.stringify({page:parseInt(cateObj.pg),pagecount:pagecount||999,limit:20,total:999,list:d});return vod}function searchParse(searchObj){fetch_params=JSON.parse(JSON.stringify(rule_fetch_params));let d=[];if(!searchObj.searchUrl){return"{}"}let p=searchObj.搜索==="*"&&rule.一级?rule.一级:searchObj.搜索;if(!p||typeof p!=="string"){return"{}"}p=p.trim();let pp=rule.一级.split(";");let url=searchObj.searchUrl.replaceAll("**",searchObj.wd);if(searchObj.pg===1&&url.includes("[")&&url.includes("]")&&!url.includes("#")){url=url.split("[")[1].split("]")[0]}else if(searchObj.pg>1&&url.includes("[")&&url.includes("]")&&!url.includes("#")){url=url.split("[")[0]}if(/fypage/.test(url)){if(url.includes("(")&&url.includes(")")){let url_rep=url.match(/.*?\((.*)\)/)[1];let cnt_page=url_rep.replaceAll("fypage",searchObj.pg);let cnt_pg=eval(cnt_page);url=url.replaceAll(url_rep,cnt_pg).replaceAll("(","").replaceAll(")","")}else{url=url.replaceAll("fypage",searchObj.pg)}}MY_URL=url;console.log(MY_URL);if(p.startsWith("js:")){const TYPE="search";const MY_PAGE=searchObj.pg;const KEY=searchObj.wd;var input=MY_URL;var detailUrl=rule.detailUrl||"";eval(p.trim().replace("js:",""));d=VODS}else{p=p.split(";");if(p.length<5){return"{}"}let p0=getPP(p,0,pp,0);let _ps=parseTags.getParse(p0);_pdfa=_ps.pdfa;_pdfh=_ps.pdfh;_pd=_ps.pd;let is_json=p0.startsWith("json:");p0=p0.replace(/^(jsp:|json:|jq:)/,"");try{let req_method=MY_URL.split(";").length>1?MY_URL.split(";")[1].toLowerCase():"get";let html;if(req_method==="post"){let rurls=MY_URL.split(";")[0].split("#");let rurl=rurls[0];let params=rurls.length>1?rurls[1]:"";print(`post=》rurl:${rurl},params:${params}`);let _fetch_params=JSON.parse(JSON.stringify(rule_fetch_params));let postData={body:params};Object.assign(_fetch_params,postData);html=post(rurl,_fetch_params)}else if(req_method==="postjson"){let rurls=MY_URL.split(";")[0].split("#");let rurl=rurls[0];let params=rurls.length>1?rurls[1]:"";print(`postjson-》rurl:${rurl},params:${params}`);try{params=JSON.parse(params)}catch(e){params="{}"}let _fetch_params=JSON.parse(JSON.stringify(rule_fetch_params));let postData={body:params};Object.assign(_fetch_params,postData);html=post(rurl,_fetch_params)}else{html=getHtml(MY_URL)}if(html){if(/系统安全验证|输入验证码/.test(html)){let cookie=verifyCode(MY_URL);if(cookie){console.log(`本次成功过验证,cookie:${cookie}`);setItem(RULE_CK,cookie)}else{console.log(`本次自动过搜索验证失败,cookie:${cookie}`)}html=getHtml(MY_URL)}if(!html.includes(searchObj.wd)){console.log("搜索结果源码未包含关键字,疑似搜索失败,正为您打印结果源码");console.log(html)}if(is_json){html=dealJson(html)}let list=_pdfa(html,p0);let p1=getPP(p,1,pp,1);let p2=getPP(p,2,pp,2);let p3=getPP(p,3,pp,3);let p4=getPP(p,4,pp,4);let p5=getPP(p,5,pp,5);list.forEach(it=>{let links=p4.split("+").map(_p4=>{return!rule.detailUrl?_pd(it,_p4,MY_URL):_pdfh(it,_p4)});let link=links.join("$");let content;if(p.length>5&&p[5]){content=_pdfh(it,p5)}else{content=""}let vod_id=link;let vod_name=_pdfh(it,p1).replace(/\n|\t/g,"").trim();let vod_pic=_pd(it,p2,MY_URL);if(rule.二级==="*"){vod_id=vod_id+"@@"+vod_name+"@@"+vod_pic}let ob={vod_id:vod_id,vod_name:vod_name,vod_pic:vod_pic,vod_remarks:_pdfh(it,p3).replace(/\n|\t/g,"").trim(),vod_content:content.replace(/\n|\t/g,"").trim()};d.push(ob)})}}catch(e){print("搜索发生错误:"+e.message);return"{}"}}if(rule.图片来源){d.forEach(it=>{if(it.vod_pic&&it.vod_pic.startsWith("http")){it.vod_pic=it.vod_pic+rule.图片来源}})}return JSON.stringify({page:parseInt(searchObj.pg),pagecount:10,limit:20,total:100,list:d})}function detailParse(detailObj){let t1=(new Date).getTime();fetch_params=JSON.parse(JSON.stringify(rule_fetch_params));let orId=detailObj.orId;let vod_name="片名";let vod_pic="";let vod_id=orId;if(rule.二级==="*"){let extra=orId.split("@@");vod_name=extra.length>1?extra[1]:vod_name;vod_pic=extra.length>2?extra[2]:vod_pic}let vod={vod_id:vod_id,vod_name:vod_name,vod_pic:vod_pic,type_name:"类型",vod_year:"年份",vod_area:"地区",vod_remarks:"更新信息",vod_actor:"主演",vod_director:"导演",vod_content:"简介"};let p=detailObj.二级;let url=detailObj.url;let detailUrl=detailObj.detailUrl;let fyclass=detailObj.fyclass;let tab_exclude=detailObj.tab_exclude;let html=detailObj.html||"";MY_URL=url;if(detailObj.二级访问前){try{print(`尝试在二级访问前执行代码:${detailObj.二级访问前}`);eval(detailObj.二级访问前.trim().replace("js:",""))}catch(e){print(`二级访问前执行代码出现错误:${e.message}`)}}if(p==="*"){vod.vod_play_from="道长在线";vod.vod_remarks=detailUrl;vod.vod_actor="没有二级,只有一级链接直接嗅探播放";vod.vod_content=MY_URL;vod.vod_play_url="嗅探播放$"+MY_URL.split("@@")[0]}else if(typeof p==="string"&&p.trim().startsWith("js:")){const TYPE="detail";var input=MY_URL;var play_url="";eval(p.trim().replace("js:",""));vod=VOD;console.log(JSON.stringify(vod))}else if(p&&typeof p==="object"){let tt1=(new Date).getTime();if(!html){html=getHtml(MY_URL)}print(`二级${MY_URL}仅获取源码耗时:${(new Date).getTime()-tt1}毫秒`);let _ps;if(p.is_json){print("二级是json");_ps=parseTags.json;html=dealJson(html)}else if(p.is_jsp){print("二级是jsp");_ps=parseTags.jsp}else if(p.is_jq){print("二级是jq");_ps=parseTags.jq}else{print("二级默认jq");_ps=parseTags.jq}let tt2=(new Date).getTime();print(`二级${MY_URL}获取并装载源码耗时:${tt2-tt1}毫秒`);_pdfa=_ps.pdfa;_pdfh=_ps.pdfh;_pd=_ps.pd;if(p.title){let p1=p.title.split(";");vod.vod_name=_pdfh(html,p1[0]).replace(/\n|\t/g,"").trim();let type_name=p1.length>1?_pdfh(html,p1[1]).replace(/\n|\t/g,"").replace(/ /g,"").trim():"";vod.type_name=type_name||vod.type_name}if(p.desc){try{let p1=p.desc.split(";");vod.vod_remarks=_pdfh(html,p1[0]).replace(/\n|\t/g,"").trim();vod.vod_year=p1.length>1?_pdfh(html,p1[1]).replace(/\n|\t/g,"").trim():"";vod.vod_area=p1.length>2?_pdfh(html,p1[2]).replace(/\n|\t/g,"").trim():"";vod.vod_actor=p1.length>3?_pdfh(html,p1[3]).replace(/\n|\t/g,"").trim():"";vod.vod_director=p1.length>4?_pdfh(html,p1[4]).replace(/\n|\t/g,"").trim():""}catch(e){}}if(p.content){try{let p1=p.content.split(";");vod.vod_content=_pdfh(html,p1[0]).replace(/\n|\t/g,"").trim()}catch(e){}}if(p.img){try{let p1=p.img.split(";");vod.vod_pic=_pd(html,p1[0],MY_URL)}catch(e){}}let vod_play_from="$$$";let playFrom=[];if(p.重定向&&p.重定向.startsWith("js:")){print("开始执行重定向代码:"+p.重定向);html=eval(p.重定向.replace("js:",""))}if(p.tabs){if(p.tabs.startsWith("js:")){print("开始执行tabs代码:"+p.tabs);var input=MY_URL;eval(p.tabs.replace("js:",""));playFrom=TABS}else{let p_tab=p.tabs.split(";")[0];let vHeader=_pdfa(html,p_tab);console.log(vHeader.length);let tab_text=p.tab_text||"body&&Text";let new_map={};for(let v of vHeader){let v_title=_pdfh(v,tab_text).trim();console.log(v_title);if(tab_exclude&&new RegExp(tab_exclude).test(v_title)){continue}if(!new_map.hasOwnProperty(v_title)){new_map[v_title]=1}else{new_map[v_title]+=1}if(new_map[v_title]>1){v_title+=Number(new_map[v_title]-1)}playFrom.push(v_title)}}console.log(JSON.stringify(playFrom))}else{playFrom=["道长在线"]}vod.vod_play_from=playFrom.join(vod_play_from);let vod_play_url="$$$";let vod_tab_list=[];if(p.lists){if(p.lists.startsWith("js:")){print("开始执行lists代码:"+p.lists);try{var input=MY_URL;var play_url="";eval(p.lists.replace("js:",""));for(let i in LISTS){if(LISTS.hasOwnProperty(i)){try{LISTS[i]=LISTS[i].map(it=>it.split("$").slice(0,2).join("$"))}catch(e){print("格式化LISTS发生错误:"+e.message)}}}vod_play_url=LISTS.map(it=>it.join("#")).join(vod_play_url)}catch(e){print("js执行lists: 发生错误:"+e.message)}}else{let list_text=p.list_text||"body&&Text";let list_url=p.list_url||"a&&href";let is_tab_js=p.tabs.trim().startsWith("js:");for(let i=0;i1&&!is_tab_js?p.tabs.split(";")[1]:"";let p1=p.lists.replaceAll("#idv",tab_name).replaceAll("#id",i);tab_ext=tab_ext.replaceAll("#idv",tab_name).replaceAll("#id",i);let tabName=tab_ext?_pdfh(html,tab_ext):tab_name;console.log(tabName);let new_vod_list=[];let tt1=(new Date).getTime();if(typeof pdfl==="function"){new_vod_list=pdfl(html,p1,list_text,list_url,MY_URL)}else{let vodList=[];try{vodList=_pdfa(html,p1);console.log("len(vodList):"+vodList.length)}catch(e){}for(let i=0;i0){new_vod_list=forceOrder(new_vod_list,"",x=>x.split("$")[0]);console.log(`drpy影响性能代码共计列表数循环次数:${new_vod_list.length},耗时:${(new Date).getTime()-tt1}毫秒`)}let vlist=new_vod_list.join("#");vod_tab_list.push(vlist)}vod_play_url=vod_tab_list.join(vod_play_url)}}vod.vod_play_url=vod_play_url}if(rule.图片来源&&vod.vod_pic&&vod.vod_pic.startsWith("http")){vod.vod_pic=vod.vod_pic+rule.图片来源}if(!vod.vod_id||vod_id.includes("$")&&vod.vod_id!==vod_id){vod.vod_id=vod_id}let t2=(new Date).getTime();console.log(`加载二级界面${MY_URL}耗时:${t2-t1}毫秒`);vod=vodDeal(vod);return JSON.stringify({list:[vod]})}function get_tab_index(vod){let obj={};vod.vod_play_from.split("$$$").forEach((it,index)=>{obj[it]=index});return obj}function vodDeal(vod){let vod_play_from=vod.vod_play_from.split("$$$");let vod_play_url=vod.vod_play_url.split("$$$");let tab_removed_list=vod_play_from;let tab_ordered_list=vod_play_from;let tab_renamed_list=vod_play_from;let tab_list=vod_play_from;let play_ordered_list=vod_play_url;if(rule.tab_remove&&rule.tab_remove.length>0||rule.tab_order&&rule.tab_order.length>0){let tab_index_dict=get_tab_index(vod);if(rule.tab_remove&&rule.tab_remove.length>0){tab_removed_list=vod_play_from.filter(it=>!rule.tab_remove.includes(it));tab_list=tab_removed_list}if(rule.tab_order&&rule.tab_order.length>0){let tab_order=rule.tab_order;tab_ordered_list=tab_removed_list.sort((a,b)=>{return(tab_order.indexOf(a)===-1?9999:tab_order.indexOf(a))-(tab_order.indexOf(b)===-1?9999:tab_order.indexOf(b))});tab_list=tab_ordered_list}play_ordered_list=tab_list.map(it=>vod_play_url[tab_index_dict[it]])}if(rule.tab_rename&&typeof rule.tab_rename==="object"&Object.keys(rule.tab_rename).length>0){tab_renamed_list=tab_list.map(it=>rule.tab_rename[it]||it);tab_list=tab_renamed_list}vod.vod_play_from=tab_list.join("$$$");vod.vod_play_url=play_ordered_list.join("$$$");return vod}function tellIsJx(url){try{let is_vip=!/\.(m3u8|mp4|m4a)$/.test(url.split("?")[0])&&是否正版(url);return is_vip?1:0}catch(e){return 1}}function playParse(playObj){fetch_params=JSON.parse(JSON.stringify(rule_fetch_params));MY_URL=playObj.url;if(!/http/.test(MY_URL)){try{MY_URL=base64Decode(MY_URL)}catch(e){}}MY_URL=decodeURIComponent(MY_URL);var input=MY_URL;let common_play={parse:1,url:input,jx:tellIsJx(input)};let lazy_play;if(!rule.play_parse||!rule.lazy){lazy_play=common_play}else if(rule.play_parse&&rule.lazy&&typeof rule.lazy==="string"){try{let lazy_code=rule.lazy.replace("js:","").trim();print("开始执行js免嗅=>"+lazy_code);eval(lazy_code);lazy_play=typeof input==="object"?input:{parse:1,jx:tellIsJx(input),url:input}}catch(e){print("js免嗅错误:"+e.message);lazy_play=common_play}}else{lazy_play=common_play}if(Array.isArray(rule.play_json)&&rule.play_json.length>0){let web_url=lazy_play.url;for(let pjson of rule.play_json){if(pjson.re&&(pjson.re==="*"||web_url.match(new RegExp(pjson.re)))){if(pjson.json&&typeof pjson.json==="object"){let base_json=pjson.json;lazy_play=Object.assign(lazy_play,base_json);break}}}}else if(rule.play_json&&!Array.isArray(rule.play_json)){let base_json={jx:1,parse:1};lazy_play=Object.assign(lazy_play,base_json)}else if(!rule.play_json){let base_json={jx:0,parse:1};lazy_play=Object.assign(lazy_play,base_json)}console.log(JSON.stringify(lazy_play));return JSON.stringify(lazy_play)}function proxyParse(proxyObj){var input=proxyObj.params;if(proxyObj.proxy_rule){log("准备执行本地代理规则:\n"+proxyObj.proxy_rule);try{eval(proxyObj.proxy_rule);if(input&&input!==proxyObj.params&&Array.isArray(input)&&input.length===3){return input}else{return[404,"text/plain","Not Found"]}}catch(e){return[500,"text/plain","代理规则错误:"+e.message]}}else{return[404,"text/plain","Not Found"]}}function isVideoParse(isVideoObj){var input=isVideoObj.url;if(!isVideoObj.t){let re_matcher=new RegExp(isVideoObj.isVideo,"i");return re_matcher.test(input)}else{try{eval(isVideoObj.isVideo);if(typeof input==="boolean"){return input}else{return false}}catch(e){log("执行嗅探规则发生错误:"+e.message);return false}}}function init(ext){console.log("init");try{let muban=模板.getMubans();if(typeof ext=="object"){rule=ext}else if(typeof ext=="string"){if(ext.startsWith("http")){let js=request(ext,{method:"GET"});if(js){eval(js.replace("var rule","rule"))}}else{eval(ext.replace("var rule","rule"))}}if(rule.模板&&muban.hasOwnProperty(rule.模板)){print("继承模板:"+rule.模板);rule=Object.assign(muban[rule.模板],rule)}let rule_cate_excludes=(rule.cate_exclude||"").split("|").filter(it=>it.trim());let rule_tab_excludes=(rule.tab_exclude||"").split("|").filter(it=>it.trim());rule_cate_excludes=rule_cate_excludes.concat(CATE_EXCLUDE.split("|").filter(it=>it.trim()));rule_tab_excludes=rule_tab_excludes.concat(TAB_EXCLUDE.split("|").filter(it=>it.trim()));rule.cate_exclude=rule_cate_excludes.join("|");rule.tab_exclude=rule_tab_excludes.join("|");rule.host=(rule.host||"").rstrip("/");HOST=rule.host;if(rule.hostJs){console.log(`检测到hostJs,准备执行...`);try{eval(rule.hostJs);rule.host=HOST.rstrip("/")}catch(e){console.log(`执行${rule.hostJs}获取host发生错误:`+e.message)}}rule.url=rule.url||"";rule.double=rule.double||false;rule.homeUrl=rule.homeUrl||"";rule.detailUrl=rule.detailUrl||"";rule.searchUrl=rule.searchUrl||"";rule.homeUrl=rule.host&&rule.homeUrl?urljoin(rule.host,rule.homeUrl):rule.homeUrl||rule.host;rule.detailUrl=rule.host&&rule.detailUrl?urljoin(rule.host,rule.detailUrl):rule.detailUrl;rule.二级访问前=rule.二级访问前||"";if(rule.url.includes("[")&&rule.url.includes("]")){let u1=rule.url.split("[")[0];let u2=rule.url.split("[")[1].split("]")[0];rule.url=rule.host&&rule.url?urljoin(rule.host,u1)+"["+urljoin(rule.host,u2)+"]":rule.url}else{rule.url=rule.host&&rule.url?urljoin(rule.host,rule.url):rule.url}if(rule.searchUrl.includes("[")&&rule.searchUrl.includes("]")&&!rule.searchUrl.includes("#")){let u1=rule.searchUrl.split("[")[0];let u2=rule.searchUrl.split("[")[1].split("]")[0];rule.searchUrl=rule.host&&rule.searchUrl?urljoin(rule.host,u1)+"["+urljoin(rule.host,u2)+"]":rule.searchUrl}else{rule.searchUrl=rule.host&&rule.searchUrl?urljoin(rule.host,rule.searchUrl):rule.searchUrl}rule.timeout=rule.timeout||5e3;rule.encoding=rule.编码||rule.encoding||"utf-8";rule.search_encoding=rule.搜索编码||rule.search_encoding||"";rule.图片来源=rule.图片来源||"";rule.play_json=rule.hasOwnProperty("play_json")?rule.play_json:[];rule.pagecount=rule.hasOwnProperty("pagecount")?rule.pagecount:{};rule.proxy_rule=rule.hasOwnProperty("proxy_rule")?rule.proxy_rule:"";rule.sniffer=rule.hasOwnProperty("sniffer")?rule.sniffer:"";rule.sniffer=!!(rule.sniffer&&rule.sniffer!=="0"&&rule.sniffer!=="false");rule.isVideo=rule.hasOwnProperty("isVideo")?rule.isVideo:"";rule.tab_remove=rule.hasOwnProperty("tab_remove")?rule.tab_remove:[];rule.tab_order=rule.hasOwnProperty("tab_order")?rule.tab_order:[];rule.tab_rename=rule.hasOwnProperty("tab_rename")?rule.tab_rename:{};if(rule.headers&&typeof rule.headers==="object"){try{let header_keys=Object.keys(rule.headers);for(let k of header_keys){if(k.toLowerCase()==="user-agent"){let v=rule.headers[k];console.log(v);if(["MOBILE_UA","PC_UA","UC_UA","IOS_UA","UA"].includes(v)){rule.headers[k]=eval(v)}}else if(k.toLowerCase()==="cookie"){let v=rule.headers[k];if(v&&v.startsWith("http")){console.log(v);try{v=fetch(v);console.log(v);rule.headers[k]=v}catch(e){console.log(`从${v}获取cookie发生错误:`+e.message)}}}}}catch(e){console.log("处理headers发生错误:"+e.message)}}rule_fetch_params={headers:rule.headers||false,timeout:rule.timeout,encoding:rule.encoding};oheaders=rule.headers||{};RKEY=typeof key!=="undefined"&&key?key:"drpy_"+(rule.title||rule.host);pre();init_test()}catch(e){console.log("init_test发生错误:"+e.message)}}let homeHtmlCache=undefined;function home(filter){console.log("home");let homeObj={filter:rule.filter||false,MY_URL:rule.homeUrl,class_name:rule.class_name||"",class_url:rule.class_url||"",class_parse:rule.class_parse||"",cate_exclude:rule.cate_exclude};return homeParse(homeObj)}function homeVod(params){console.log("homeVod");let homeVodObj={"推荐":rule.推荐,double:rule.double,homeUrl:rule.homeUrl,detailUrl:rule.detailUrl};return homeVodParse(homeVodObj)}function category(tid,pg,filter,extend){let cateObj={url:rule.url,"一级":rule.一级,tid:tid,pg:parseInt(pg),filter:filter,extend:extend};return categoryParse(cateObj)}function detail(vod_url){let orId=vod_url;let fyclass="";log("orId:"+orId);if(vod_url.indexOf("$")>-1){let tmp=vod_url.split("$");fyclass=tmp[0];vod_url=tmp[1]}let detailUrl=vod_url.split("@@")[0];let url;if(!detailUrl.startsWith("http")&&!detailUrl.includes("/")){url=rule.detailUrl.replaceAll("fyid",detailUrl).replaceAll("fyclass",fyclass)}else if(detailUrl.includes("/")){url=urljoin(rule.homeUrl,detailUrl)}else{url=detailUrl}let detailObj={orId:orId,url:url,"二级":rule.二级,"二级访问前":rule.二级访问前,detailUrl:detailUrl,fyclass:fyclass,tab_exclude:rule.tab_exclude};return detailParse(detailObj)}function play(flag,id,flags){let playObj={url:id,flag:flag,flags:flags};return playParse(playObj)}function search(wd,quick,pg){if(rule.search_encoding){if(rule.search_encoding.toLowerCase()!=="utf-8"){wd=encodeStr(wd,rule.search_encoding)}}else if(rule.encoding&&rule.encoding.toLowerCase()!=="utf-8"){wd=encodeStr(wd,rule.encoding)}let searchObj={searchUrl:rule.searchUrl,"搜索":rule.搜索,wd:wd,pg:pg||1,quick:quick};return searchParse(searchObj)}function proxy(params){if(rule.proxy_rule&&rule.proxy_rule.trim()){rule.proxy_rule=rule.proxy_rule.trim()}if(rule.proxy_rule.startsWith("js:")){rule.proxy_rule=rule.proxy_rule.replace("js:","")}let proxyObj={params:params,proxy_rule:rule.proxy_rule};return proxyParse(proxyObj)}function sniffer(){let enable_sniffer=rule.sniffer||false;if(enable_sniffer){log("开始执行辅助嗅探代理规则...")}return enable_sniffer}function isVideo(url){let t=0;let is_video;if(rule.isVideo&&rule.isVideo.trim()){is_video=rule.isVideo.trim()}if(is_video.startsWith("js:")){is_video=is_video.replace("js:","");t=1}let isVideoObj={url:url,isVideo:is_video,t:t};let result=isVideoParse(isVideoObj);if(result){log("成功执行辅助嗅探规则并检测到视频地址:\n"+rule.isVideo)}return result}function DRPY(){return{init:init,home:home,homeVod:homeVod,category:category,detail:detail,play:play,search:search,proxy:proxy,sniffer:sniffer,isVideo:isVideo}}export default{init:init,home:home,homeVod:homeVod,category:category,detail:detail,play:play,search:search,proxy:proxy,sniffer:sniffer,isVideo:isVideo,DRPY:DRPY}; \ No newline at end of file diff --git a/py/jieyingshi.py b/py/jieyingshi.py new file mode 100644 index 0000000..892e829 --- /dev/null +++ b/py/jieyingshi.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +# @Author : Doubebly +# @Time : 2025/1/21 23:07 + +import hashlib +import re +import sys +import time +import requests +sys.path.append('..') +from base.spider import Spider + + +class Spider(Spider): + def getName(self): + return "JieYingShi" + + def init(self, extend): + self.home_url = 'https://www.hkybqufgh.com' + self.error_url = 'https://json.doube.eu.org/error/4gtv/index.m3u8' + self.headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", + } + + def getDependence(self): + return [] + + def isVideoFormat(self, url): + pass + + def manualVideoCheck(self): + pass + + def homeContent(self, filter): + + return {'class': [ + { + 'type_id': '1', + 'type_name': '电影' + }, + { + 'type_id': '2', + 'type_name': '电视剧' + }, + { + 'type_id': '4', + 'type_name': '动漫' + }, + { + 'type_id': '3', + 'type_name': '综艺' + } + ]} + + def homeVideoContent(self): + a = self.get_data(self.home_url) + return {'list': a, 'parse': 0, 'jx': 0} + + def categoryContent(self, cid, page, filter, ext): + url = self.home_url + f'/vod/show/id/{cid}/page/{page}' + data = self.get_data(url) + return {'list': data, 'parse': 0, 'jx': 0} + + + def detailContent(self, did): + ids = did[0] + data = self.get_detail_data(ids) + return {"list": data, 'parse': 0, 'jx': 0} + + def searchContent(self, key, quick, page='1'): + if int(page) > 1: + return {'list': [], 'parse': 0, 'jx': 0} + url = self.home_url + f'/vod/search/{key}' + data = self.get_data(url) + return {'list': data, 'parse': 0, 'jx': 0} + + def playerContent(self, flag, pid, vipFlags): + url = self.get_play_data(pid) + return {"url": url, "header": self.headers, "parse": 1, "jx": 0} + + def localProxy(self, params): + pass + + def destroy(self): + return '正在Destroy' + + + def get_data(self, url): + data = [] + try: + res = requests.get(url, headers=self.headers) + if res.status_code != 200: + return data + vod_id_s = re.findall(r'\\"vodId\\":(.*?),', res.text) + vod_name_s = re.findall(r'\\"vodName\\":\\"(.*?)\\"', res.text) + vod_pic_s = re.findall(r'\\"vodPic\\":\\"(.*?)\\"', res.text) + vod_remarks_s = re.findall(r'\\"vodRemarks\\":\\"(.*?)\\"', res.text) + + for i in range(len(vod_id_s)): + data.append( + { + 'vod_id': vod_id_s[i], + 'vod_name': vod_name_s[i], + 'vod_pic': vod_pic_s[i], + 'vod_remarks': vod_remarks_s[i], + } + ) + except requests.RequestException as e: + print(e) + return data + + def get_detail_data(self, ids): + url = self.home_url + f'/api/mw-movie/anonymous/video/detail?id={ids}' + t = str(int(time.time() * 1000)) + headers = self.get_headers(t, f'id={ids}&key=cb808529bae6b6be45ecfab29a4889bc&t={t}') + try: + res = requests.get(url, headers=headers) + if res.status_code != 200: + return [] + i = res.json()['data'] + urls = [] + for ii in res.json()['data']['episodeList']: + name = ii['name'] + url = ii['nid'] + urls.append(f'{name}${ids}-{url}') + data = { + 'type_name': i['vodClass'], + 'vod_id': i['vodId'], + 'vod_name': i['vodName'], + 'vod_remarks': i['vodRemarks'], + 'vod_year': i['vodYear'], + 'vod_area': i['vodArea'], + 'vod_actor': i['vodActor'], + 'vod_director': i['vodDirector'], + 'vod_content': i['vodContent'], + 'vod_play_from': '🌈七星专享', + 'vod_play_url': '#'.join(urls), + + } + return [data] + + except requests.RequestException as e: + print(e) + return [] + + def get_play_data(self, play): + info = play.split('-') + _id = info[0] + _pid = info[1] + url = self.home_url + f'/api/mw-movie/anonymous/v2/video/episode/url?id={_id}&nid={_pid}' + t = str(int(time.time() * 1000)) + headers = self.get_headers(t, f'id={_id}&nid={_pid}&key=cb808529bae6b6be45ecfab29a4889bc&t={t}') + try: + res = requests.get(url, headers=headers) + if res.status_code != 200: + return self.error_url + return res.json()['data']['list'][0]['url'] + except requests.RequestException as e: + print(e) + return self.error_url + + @staticmethod + def get_headers(t, e): + sign = hashlib.sha1(hashlib.md5(e.encode()).hexdigest().encode()).hexdigest() + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + 'Accept': 'application/json, text/plain, */*', + 'sign': sign, + 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', + 't': t, + 'referer': 'https://www.hkybqufgh.com/', + } + return headers + +if __name__ == '__main__': + pass + + + + diff --git a/py/枫叶影院.py b/py/枫叶影院.py new file mode 100644 index 0000000..81130bf --- /dev/null +++ b/py/枫叶影院.py @@ -0,0 +1,392 @@ +# -*- coding: utf-8 -*- +import re, urllib.parse +import json +from bs4 import BeautifulSoup +import requests +from base.spider import Spider as BaseSpider + + +class Spider(BaseSpider): + def init(self, extend=""): + self.host = "https://maihaolian.com" + self.headers = { + "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "zh-CN,zh;q=0.9", + } + + def getName(self): + return '枫叶影院' + + def homeContent(self, filter): + return {"class": [ + {'type_id': "/label/qq", 'type_name': "腾讯VIP精选"}, + {'type_id': "/label/bli", 'type_name': "B站VIP精选"}, + {'type_id': "/label/youku", 'type_name': "优酷VIP精选"}, + {"type_id": "5", "type_name": "红果短剧"}, + {"type_id": "2", "type_name": "电视剧"}, + {"type_id": "1", "type_name": "电影"}, + {"type_id": "4", "type_name": "动漫"}, + {"type_id": "3", "type_name": "综艺"}, + ], "filters": self._build_filters()} + + def _build_filters(self): + area = [{"n": "全部", "v": ""}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"}, + {"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "泰国", "v": "泰国"}, {"n": "新加坡", "v": "新加坡"}, + {"n": "马来西亚", "v": "马来西亚"}, {"n": "印度", "v": "印度"}, {"n": "英国", "v": "英国"}, + {"n": "法国", "v": "法国"}, {"n": "加拿大", "v": "加拿大"}, {"n": "西班牙", "v": "西班牙"}, + {"n": "俄罗斯", "v": "俄罗斯"}, {"n": "其它", "v": "其它"}] + year = [{"n": "全部", "v": ""}, {"n": "2026", "v": "2026"}, {"n": "2025", "v": "2025"}, + {"n": "2024", "v": "2024"}, {"n": "2023", "v": "2023"}, {"n": "2022", "v": "2022"}, + {"n": "2021", "v": "2021"}, {"n": "2020", "v": "2020"}, {"n": "2019", "v": "2019"}, + {"n": "2018", "v": "2018"}, {"n": "2017", "v": "2017"}, {"n": "2016", "v": "2016"}, + {"n": "2015", "v": "2015"}, {"n": "2014", "v": "2014"}, {"n": "2013", "v": "2013"}, + {"n": "2012", "v": "2012"}, {"n": "2011", "v": "2011"}, {"n": "2010", "v": "2010"}, + {"n": "2009", "v": "2009"}, {"n": "2008", "v": "2008"}, {"n": "2007", "v": "2007"}, + {"n": "2006", "v": "2006"}, {"n": "2005", "v": "2005"}, {"n": "2004", "v": "2004"}] + lang = [{"n": "全部", "v": ""}, {"n": "国语", "v": "国语"}, {"n": "英语", "v": "英语"}, + {"n": "粤语", "v": "粤语"}, {"n": "闽南语", "v": "闽南语"}, {"n": "韩语", "v": "韩语"}, + {"n": "日语", "v": "日语"}, {"n": "法语", "v": "法语"}, {"n": "德语", "v": "德语"}, + {"n": "其它", "v": "其它"}] + sort = [{"n": "时间", "v": "time"}, {"n": "人气", "v": "hits"}, {"n": "评分", "v": "score"}] + letter = [{"n": "全部", "v": ""}, {"n": "A", "v": "A"}, {"n": "B", "v": "B"}, {"n": "C", "v": "C"}, + {"n": "D", "v": "D"}, {"n": "E", "v": "E"}, {"n": "F", "v": "F"}, {"n": "G", "v": "G"}, + {"n": "H", "v": "H"}, {"n": "I", "v": "I"}, {"n": "J", "v": "J"}, {"n": "K", "v": "K"}, + {"n": "L", "v": "L"}, {"n": "M", "v": "M"}, {"n": "N", "v": "N"}, {"n": "O", "v": "O"}, + {"n": "P", "v": "P"}, {"n": "Q", "v": "Q"}, {"n": "R", "v": "R"}, {"n": "S", "v": "S"}, + {"n": "T", "v": "T"}, {"n": "U", "v": "U"}, {"n": "V", "v": "V"}, {"n": "W", "v": "W"}, + {"n": "X", "v": "X"}, {"n": "Y", "v": "Y"}, {"n": "Z", "v": "Z"}, {"n": "0-9", "v": "0-9"}] + return { + "2": [ + {"key": "class", "name": "类型", + "value": [{"n": "全部", "v": "2"}, {"n": "国产剧", "v": "13"}, {"n": "日韩剧", "v": "15"}, + {"n": "海外剧", "v": "16"}]}, + {"key": "area", "name": "地区", "value": area}, + {"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in + [("全部", ""), ("古装", "古装"), ("战争", "战争"), + ("青春偶像", "青春偶像"), ("喜剧", "喜剧"), + ("家庭", "家庭"), ("犯罪", "犯罪"), ("动作", "动作"), + ("奇幻", "奇幻"), ("剧情", "剧情"), ("历史", "历史"), + ("经典", "经典"), ("乡村", "乡村"), ("情景", "情景"), + ("商战", "商战"), ("网剧", "网剧"), ("其他", "其他")]]}, + {"key": "year", "name": "年份", "value": year}, + {"key": "lang", "name": "语言", "value": lang}, + {"key": "letter", "name": "字母", "value": letter}, + {"key": "sort", "name": "排序", "value": sort}, + ], + "1": [ + {"key": "class", "name": "类型", + "value": [{"n": "全部", "v": "1"}, {"n": "动作片", "v": "6"}, {"n": "喜剧片", "v": "7"}, + {"n": "恐怖片", "v": "8"}, {"n": "科幻片", "v": "9"}, {"n": "爱情片", "v": "10"}, + {"n": "剧情片", "v": "11"}, {"n": "战争片", "v": "12"}, {"n": "纪录片", "v": "20"}]}, + {"key": "area", "name": "地区", "value": area}, + {"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in + [("全部", ""), ("喜剧", "喜剧"), ("爱情", "爱情"), + ("恐怖", "恐怖"), ("动作", "动作"), ("科幻", "科幻"), + ("剧情", "剧情"), ("战争", "战争"), ("警匪", "警匪"), + ("犯罪", "犯罪"), ("动画", "动画"), ("奇幻", "奇幻"), + ("武侠", "武侠"), ("冒险", "冒险"), ("枪战", "枪战"), + ("悬疑", "悬疑"), ("惊悚", "惊悚"), ("经典", "经典"), + ("青春", "青春"), ("文艺", "文艺"), ("微电影", "微电影"), + ("古装", "古装"), ("历史", "历史"), ("运动", "运动"), + ("农村", "农村"), ("儿童", "儿童"), + ("网络电影", "网络电影")]]}, + {"key": "year", "name": "年份", "value": year}, + {"key": "lang", "name": "语言", "value": lang}, + {"key": "letter", "name": "字母", "value": letter}, + {"key": "sort", "name": "排序", "value": sort}, + ], + "4": [ + {"key": "class", "name": "类型", + "value": [{"n": "全部", "v": "4"}, {"n": "国产动漫", "v": "25"}, {"n": "日韩动漫", "v": "26"}]}, + {"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in + [("全部", ""), ("情感", "情感"), ("科幻", "科幻"), + ("热血", "热血"), ("推理", "推理"), ("搞笑", "搞笑"), + ("冒险", "冒险"), ("奇幻", "奇幻"), ("战斗", "战斗"), + ("校园", "校园"), ("萝莉", "萝莉"), ("治愈", "治愈"), + ("原创", "原创"), ("亲子", "亲子"), ("益智", "益智"), + ("励志", "励志"), ("其他", "其他")]]}, + {"key": "area", "name": "地区", + "value": [{"n": "全部", "v": ""}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"}, + {"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "法国", "v": "法国"}, {"n": "英国", "v": "英国"}, + {"n": "其它", "v": "其它"}]}, + {"key": "year", "name": "年份", "value": year}, + {"key": "lang", "name": "语言", "value": lang}, + {"key": "letter", "name": "字母", "value": letter}, + {"key": "sort", "name": "排序", "value": sort}, + ], + "3": [ + {"key": "class", "name": "类型", + "value": [{"n": "全部", "v": "3"}, {"n": "大陆综艺", "v": "21"}, {"n": "日韩综艺", "v": "22"}]}, + {"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in + [("全部", ""), ("选秀", "选秀"), ("情感", "情感"), + ("访谈", "访谈"), ("播报", "播报"), ("音乐", "音乐"), + ("美食", "美食"), ("旅游", "旅游"), ("搞笑", "搞笑"), + ("游戏", "游戏"), ("亲子", "亲子"), ("其它", "其它")]]}, + {"key": "area", "name": "地区", + "value": [{"n": "全部", "v": ""}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"}, + {"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "英国", "v": "英国"}, {"n": "其它", "v": "其它"}]}, + {"key": "year", "name": "年份", "value": year}, + {"key": "lang", "name": "语言", "value": lang}, + {"key": "letter", "name": "字母", "value": letter}, + {"key": "sort", "name": "排序", "value": sort}, + ], + } + + def homeVideoContent(self): + html = self._fetch('/') + return {"list": self._parse_video_list(html)} + + def categoryContent(self, tid, pg, filter, extend): + # 构建筛选参数:参照歪比巴卜,直接取extend里的值,fallback到filter + if tid.startswith('/label'): + url = f'{tid}/page/{pg}.html' + html = self._fetch(url) + items = self._parse_video_list(html) + page = int(pg) + page_count = page if len(items) < 24 else page + 2 + return {"list": items, "page": page, "pagecount": page_count, "limit": 24, "total": page_count * 24} + + args = {} + if extend and isinstance(extend, dict): + for k, v in extend.items(): + if v: + args[k] = str(v) + if isinstance(filter, dict): + for k, v in filter.items(): + if v and k not in args: + args[k] = str(v) + route_tid = args.get('class', args.get('tid', str(tid))) + area = args.get('area', '') + genre = args.get('genre', '') + year = args.get('year', '') + lang = args.get('lang', '') + letter = args.get('letter', '') + sort = args.get('sort', '') + # 无筛选走正常分页 + if not area and not genre and not year and not lang and not letter and not sort: + url = f'/cupfox-list/{route_tid}--------{pg}---.html' + html = self._fetch(url) + items = self._parse_video_list(html) + page = int(pg) + soup = BeautifulSoup(html, 'html.parser') + pagecount = page + for a in soup.select('a.page-link'): + if a.text == '尾页': + m = re.search(r'---(\d+)---', a.get('href', '')) + if m: + pagecount = int(m.group(1)) + break + if not items: + pagecount = 0 + return {"list": items, "page": page, "pagecount": pagecount, "limit": 36, "total": 9999} + # 有筛选:{tid}-{area}-{sort}-{genre}-{lang}-{letter}------{year}.html + segs = [route_tid, area, sort, genre, lang, letter, '', '', year] + url = '/cupfox-list/' + '-'.join(segs) + '.html' + html = self._fetch(url) + items = self._parse_video_list(html) + return {"list": items, "page": 1, "pagecount": 1, "limit": 36, "total": 9999} + + def detailContent(self, ids): + result = {"list": []} + vid = ids[0].split(',')[0].strip() + try: + html = self._fetch(f'/detail/{vid}.html') + if not html: return result + soup = BeautifulSoup(html, 'html.parser') + vod_name = soup.select_one('h3.slide-info-title') + vod_name = vod_name.text.strip() if vod_name else '' + vod_pic = soup.select_one('img.lazy') + vod_pic = self._fix_pic(vod_pic.get('data-src', '')) if vod_pic else '' + vod_director = '' + vod_actor = '' + for el in soup.select('.slide-info'): + text = el.get_text(' ').strip() + if text.startswith('导演:'): + vod_director = text.replace('导演:', '').strip() + elif text.startswith('演员:'): + vod_actor = text.replace('演员:', '').strip() + vod_content = soup.select_one('#height_limit') + vod_content = vod_content.get_text(' ', strip=True) if vod_content else '' + play_from, play_url = [], [] + for tab in soup.select('.anthology-tab a.swiper-slide'): + src_name = re.sub(r'<[^>]+>', '', str(tab)).strip() or tab.get_text(' ', strip=True).strip() + if src_name: + play_from.append(src_name) + tab_blocks = soup.select('.anthology-list-box') + for i, block in enumerate(tab_blocks): + ep_list = [] + for a in block.select('li a'): + href = a.get('href', '') + m = re.search(r'/play/(.*?)\.html', href) + if m: + ep_list.append(f'{a.text.strip()}${vid}-{m.group(1)}') + ep_list.reverse() + if ep_list and i < len(play_from): + play_url.append('#'.join(ep_list)) + valid_from = [pf for i, pf in enumerate(play_from) if i < len(play_url)] + result["list"].append({ + "vod_id": vid, "vod_name": vod_name, "vod_pic": vod_pic, + "vod_director": vod_director, "vod_actor": vod_actor, + "vod_content": vod_content, + "vod_play_from": "$$$".join(valid_from), + "vod_play_url": "$$$".join(play_url), + }) + except: + pass + return result + + def searchContent(self, key, quick, pg="1"): + try: + decoded = urllib.parse.unquote(key) + except: + decoded = key + html = self._fetch(f'/cupfox-search/{urllib.parse.quote(decoded)}----------{pg}---.html') + items = self._parse_search_list(html) + return {"list": items, "page": int(pg), "pagecount": 1, "limit": 36, "total": len(items)} + + def playerContent(self, flag, id, vipFlags): + url = '' + try: + url = id if id.startswith('http') else f'{self.host}/play/{id}.html' + html = self._fetch(url) + if html: + m = re.search(r'player_aaaa=(.*?)', html, re.S) + if m: + + try: + pd = json.loads(m.group(1)) + except Exception as e: + print(e) + pd = {} + # print('pd:', pd) + play_url = pd.get('url') + play_id = pd.get('from') + + api_map = { + 'YYNB': 'https://zzrs.mfdyvip.com/player/mplayer.php', + 'JD4K': 'https://fgsrg.hzqingshan.com/player/mplayer.php', + } + if not play_url: + return {"parse": 0, "url": 'https://php.doube.eu.org/error.m3u8', + "header": {'User-Agent': 'Mozilla/5.0'}} + if play_url.startswith('http') and (play_url.endswith('.m3u8') or play_url.endswith('.mp4')): + return {"parse": 0, "url": play_url, "header": {'User-Agent': 'Mozilla/5.0'}} + + else: + headers = { + 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", + 'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + 'accept-language': "zh-CN,zh;q=0.9", + 'cache-control': "no-cache", + 'pragma': "no-cache", + 'priority': "u=0, i", + 'referer': "https://www.ht10010.com/", + 'Content-Type': 'application/x-www-form-urlencoded', + } + response = requests.get(f"https://fgsrg.hzqingshan.com/player/?url={play_url}", headers=headers) + token = re.search(r'data-te="(.*?)"', response.text) + if token: + token = token.group(1) + payload = { + 'url': play_url, + 'token': token + } + # print('payload', payload) + try: + response = self.post(api_map[play_id], data=payload, headers=headers) + + response.raise_for_status() + result = response.json() + # print('result:', result) + if result['code'] == 200 and 'url' in result: + play_url = result['url'] + return {"parse": 0, "url": play_url, "header": { + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'}} + except Exception as e: + print(e) + except Exception as e: + print(e) + return {"parse": 1, "url": url} + + def localProxy(self, param=''): + return {} + + def isVideoFormat(self, url): + return False + + def manualVideoCheck(self): + return False + + def _fetch(self, url): + try: + if not url.startswith('http'): + url = self.host + url + rsp = self.fetch(url, headers=self.headers) + return rsp.text if rsp else '' + except: + return '' + + def _fix_pic(self, u): + if not u: return '' + if u.startswith('//'): return 'https:' + u + return u.replace('&', '&') + + def _parse_video_list(self, html): + videos, seen = [], set() + soup = BeautifulSoup(html, 'html.parser') + cards = soup.select('a.public-list-exp') + for a in cards: + href = a.get('href', '') + m = re.search(r'/detail/(\d+)\.html', href) + if not m: continue + vod_id = m.group(1) + if vod_id in seen: continue + seen.add(vod_id) + span = ','.join([span.text for span in a.select('span.public-prt')]) + # print('span', span) + vod_name = a.get('title', '') or (a.select_one('img') and a.select_one('img').get('alt', '')) or '' + pic_el = a.select_one('img') + vod_pic = self._fix_pic(pic_el.get('data-src', '')) if pic_el else '' + remark_el = a.select_one('.ft2') or a.select_one('.public-list-prb') + vod_remarks = remark_el.text.strip() if remark_el else '' + videos.append( + {"vod_id": vod_id, "vod_name": vod_name.strip(), "vod_pic": vod_pic, "vod_remarks": vod_remarks, "vod_year": span}) + return videos + + def _parse_search_list(self, html): + videos, seen = [], set() + soup = BeautifulSoup(html, 'html.parser') + cards = soup.select('a.public-list-exp') + for a in cards: + href = a.get('href', '') + m = re.search(r'/detail/(\d+)\.html', href) + if not m: continue + vod_id = m.group(1) + if vod_id in seen: continue + seen.add(vod_id) + pic_el = a.select_one('img') + vod_pic = self._fix_pic(pic_el.get('data-src', '')) if pic_el else '' + title_el = soup.select_one(f'a.thumb-txt[href="/detail/{vod_id}.html"]') + if title_el: + vod_name = title_el.text.strip() + else: + vod_name = a.select_one('img') and a.select_one('img').get('alt', '') or '' + remark_el = a.select_one('.public-list-prb') or a.select_one('.ft2') + vod_remarks = remark_el.text.strip() if remark_el else '' + videos.append( + {"vod_id": vod_id, "vod_name": vod_name.strip(), "vod_pic": vod_pic, "vod_remarks": vod_remarks}) + return videos + + +if __name__ == '__main__': + sp = Spider() + sp.init() + # 20067-5-189 + print(sp.categoryContent('/label/qq','1',True, {})) + # print(sp.playerContent('', '20067-6-189', [])) + # print(sp.playerContent('', '20067-5-189', [])) + pass diff --git a/py/独播库.py b/py/独播库.py new file mode 100644 index 0000000..7822c1c --- /dev/null +++ b/py/独播库.py @@ -0,0 +1,270 @@ +""" +@header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '独播库', + lang: 'hipy' +}) +""" + +# -*- coding: utf-8 -*- +# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。 +# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。 + +from base.spider import Spider +import sys,time,json,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.dbku.tv/' + }, 'https://api.dbokutv.com' + + def init(self, extend=""): + pass + + filters = { + 1: [ + {'key': 'area', 'name': '地区', 'value': [ + {'n': '全部', 'v': ''}, {'n': '大陆', 'v': '大陆'}, {'n': '香港', 'v': '香港'}, + {'n': '台湾', 'v': '台湾'}, {'n': '韩国', 'v': '韩国'}, {'n': '日本', 'v': '日本'}, + {'n': '欧美', 'v': '欧美'}, {'n': '泰国', 'v': '泰国'}]}, + {'key': 'order', 'name': '排序', 'value': [ + {'n': '时间', 'v': ''}, {'n': '人气', 'v': '人气'}, {'n': '评分', 'v': '评分'}]}, + {'key': 'class', 'name': '剧情', 'value': [ + {'n': '全部', 'v': ''}, {'n': '喜剧', 'v': '喜剧'}, {'n': '爱情', 'v': '爱情'}, + {'n': '动作', 'v': '动作'}, {'n': '科幻', 'v': '科幻'}, {'n': '悬疑', 'v': '悬疑'}, + {'n': '惊悚', 'v': '惊悚'}, {'n': '恐怖', 'v': '恐怖'}, {'n': '战争', 'v': '战争'}, + {'n': '犯罪', 'v': '犯罪'}, {'n': '纪录片', 'v': '纪录片'}, {'n': '剧情', 'v': '剧情'}, + {'n': '歌舞', 'v': '歌舞'}, {'n': '家庭', 'v': '家庭'}, {'n': '历史', 'v': '历史'}, + {'n': '奇幻', 'v': '奇幻'}, {'n': '冒险', 'v': '冒险'}, {'n': '传记', 'v': '传记'}, + {'n': '动画', 'v': '动画'}, {'n': '短片', 'v': '短片'}]}, + {'key': 'lang', 'name': '语言', 'value': [ + {'n': '全部', 'v': ''}, {'n': '国语', 'v': '国语'}, {'n': '粤语', 'v': '粤语'}, + {'n': '英语', 'v': '英语'}, {'n': '韩语', 'v': '韩语'}, {'n': '日语', 'v': '日语'}, + {'n': '泰语', 'v': '泰语'}]}, + {'key': 'year', 'name': '年份', 'value': [ + {'n': '全部', 'v': ''}, {'n': '2026', 'v': '2026'}, {'n': '2025', 'v': '2025'}, + {'n': '2024', 'v': '2024'}, {'n': '2023', 'v': '2023'}, {'n': '2022', 'v': '2022'}, + {'n': '2021', 'v': '2021'}, {'n': '2020', 'v': '2020'}, {'n': '更早', 'v': '更早'}]}, + ], + 2: [ + {'key': 'area', 'name': '地区', 'value': [ + {'n': '全部', 'v': ''}, {'n': '大陆', 'v': '大陆'}, {'n': '香港', 'v': '香港'}, + {'n': '台湾', 'v': '台湾'}, {'n': '韩国', 'v': '韩国'}, {'n': '日本', 'v': '日本'}, + {'n': '新加坡', 'v': '新加坡'}, {'n': '泰国', 'v': '泰国'}]}, + {'key': 'order', 'name': '排序', 'value': [ + {'n': '时间', 'v': ''}, {'n': '人气', 'v': '人气'}, {'n': '评分', 'v': '评分'}]}, + {'key': 'class', 'name': '剧情', 'value': [ + {'n': '全部', 'v': ''}, {'n': '悬疑', 'v': '悬疑'}, {'n': '武侠', 'v': '武侠'}, + {'n': '科幻', 'v': '科幻'}, {'n': '都市', 'v': '都市'}, {'n': '爱情', 'v': '爱情'}, + {'n': '古装', 'v': '古装'}, {'n': '战争', 'v': '战争'}, {'n': '青春', 'v': '青春'}, + {'n': '偶像', 'v': '偶像'}, {'n': '喜剧', 'v': '喜剧'}, {'n': '家庭', 'v': '家庭'}, + {'n': '奇幻', 'v': '奇幻'}, {'n': '剧情', 'v': '剧情'}, {'n': '乡村', 'v': '乡村'}, + {'n': '年代', 'v': '年代'}, {'n': '警匪', 'v': '警匪'}, {'n': '谍战', 'v': '谍战'}, + {'n': '历险', 'v': '历险'}, {'n': '罪案', 'v': '罪案'}, {'n': '宫廷', 'v': '宫廷'}, + {'n': '经典', 'v': '经典'}, {'n': '动作', 'v': '动作'}, {'n': '惊悚', 'v': '惊悚'}, + {'n': '历史', 'v': '历史'}, {'n': '穿越', 'v': '穿越'}, {'n': '同性', 'v': '同性'}]}, + {'key': 'lang', 'name': '语言', 'value': [ + {'n': '全部', 'v': ''}, {'n': '国语', 'v': '国语'}, {'n': '粤语', 'v': '粤语'}, + {'n': '韩语', 'v': '韩语'}, {'n': '泰语', 'v': '泰语'}, {'n': '日语', 'v': '日语'}]}, + {'key': 'year', 'name': '年份', 'value': [ + {'n': '全部', 'v': ''}, {'n': '2026', 'v': '2026'}, {'n': '2025', 'v': '2025'}, + {'n': '2024', 'v': '2024'}, {'n': '2023', 'v': '2023'}, {'n': '2022', 'v': '2022'}, + {'n': '2021', 'v': '2021'}, {'n': '2020', 'v': '2020'}, {'n': '更早', 'v': '更早'}]}, + ], + 3: [ + {'key': 'area', 'name': '地区', 'value': [ + {'n': '全部', 'v': ''}, {'n': '大陆', 'v': '大陆'}, {'n': '香港', 'v': '香港'}, + {'n': '台湾', 'v': '台湾'}, {'n': '韩国', 'v': '韩国'}, {'n': '日本', 'v': '日本'}, + {'n': '欧美', 'v': '欧美'}]}, + {'key': 'order', 'name': '排序', 'value': [ + {'n': '时间', 'v': ''}, {'n': '人气', 'v': '人气'}, {'n': '评分', 'v': '评分'}]}, + {'key': 'year', 'name': '年份', 'value': [ + {'n': '全部', 'v': ''}, {'n': '2026', 'v': '2026'}, {'n': '2025', 'v': '2025'}, + {'n': '2024', 'v': '2024'}, {'n': '2023', 'v': '2023'}, {'n': '2022', 'v': '2022'}, + {'n': '2021', 'v': '2021'}, {'n': '更早', 'v': '更早'}]}, + ], + 4: [ + {'key': 'area', 'name': '地区', 'value': [ + {'n': '全部', 'v': ''}, {'n': '大陆', 'v': '大陆'}, {'n': '日本', 'v': '日本'}, + {'n': '欧美', 'v': '欧美'}, {'n': '韩国', 'v': '韩国'}]}, + {'key': 'order', 'name': '排序', 'value': [ + {'n': '时间', 'v': ''}, {'n': '人气', 'v': '人气'}, {'n': '评分', 'v': '评分'}]}, + {'key': 'year', 'name': '年份', 'value': [ + {'n': '全部', 'v': ''}, {'n': '2026', 'v': '2026'}, {'n': '2025', 'v': '2025'}, + {'n': '2024', 'v': '2024'}, {'n': '2023', 'v': '2023'}, {'n': '2022', 'v': '2022'}, + {'n': '2021', 'v': '2021'}, {'n': '更早', 'v': '更早'}]}, + ], + } + + def homeContent(self, filter): + if not self.host: return None + result = {'class': [ + {'type_id': 1, 'type_name': '电影'}, + {'type_id': 2, 'type_name': '连续剧'}, + {'type_id': 3, 'type_name': '综艺'}, + {'type_id': 4, 'type_name': '动漫'}]} + if filter: + result['filters'] = self.filters + return result + + 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): + area = extend.get('area', '') + order = extend.get('order', '') + cls = extend.get('class', '') + lang = extend.get('lang', '') + year = extend.get('year', '') + pg_str = '' if str(pg) == '1' else str(pg) + # URL格式: /vodshow/{tid}-{area}-{order}-{class}-{lang}-{letter}-{?}-{?}-{pg}-{?}-{year} + path = f"{self.host}/vodshow/{tid}-{area}-{order}-{cls}-{lang}----{pg_str}---{year}" + response = self.fetch(self.sign(path), 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 diff --git a/py/瓜娃子.py b/py/瓜娃子.py new file mode 100644 index 0000000..da9c0ad --- /dev/null +++ b/py/瓜娃子.py @@ -0,0 +1,510 @@ +# coding = utf-8 +#!/usr/bin/python +import re +import sys +import json +import time +import base64 +import requests +import hashlib +import random +import string +import urllib.parse +from Crypto.Cipher import AES +from Crypto.Util.Padding import pad, unpad +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 +from base.spider import Spider + +sys.path.append('..') + +class Spider(Spider): + def __init__(self): + self.name = "瓜子" + self.hosts = [ + 'https://apinew.uozvr.com', + 'https://api.w32z7vtd.com', + 'https://api.6a7nnf7.com', + 'https://api.umygrx3.com', + 'https://api.rmedphk.com' + ] + self.host_index = 0 + self.host = self.hosts[self.host_index] + + # AES 固定密钥(与Java版一致) + self.AES_KEY = 'OITxa5OqAYjhswxx' + self.AES_IV = 'rCMNwZASNBKZ8mXV' + + # RSA 公钥/私钥 + self.RSA_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUM5+/y8sPsWkd1/RQS64X259EUwxFXFE5HlA65MqrxnPs0JqoSRojSDy5QhwvROlaD6TwRQHKMY2OAZ6SnQeUJsChTEFIR9qUkwrs3/MVUMxjsv6JS6Oe/juclyJGTgVmDhB55EafXsD0SQYVj/QXXsxR6ewR5E2kL52yAAD4yQIDAQAB" + self.RSA_PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGAe6hKrWLi1zQmjTT1 +ozbE4QdFeJGNxubxld6GrFGximxfMsMB6BpJhpcTouAqywAFppiKetUBBbXwYsYU +1wNr648XVmPmCMCy4rY8vdliFnbMUj086DU6Z+/oXBdWU3/b1G0DN3E9wULRSwcK +ZT3wj/cCI1vsCm3gj2R5SqkA9Y0CAwEAAQKBgAJH+4CxV0/zBVcLiBCHvSANm0l7 +HetybTh/j2p0Y1sTXro4ALwAaCTUeqdBjWiLSo9lNwDHFyq8zX90+gNxa7c5EqcW +V9FmlVXr8VhfBzcZo1nXeNdXFT7tQ2yah/odtdcx+vRMSGJd1t/5k5bDd9wAvYdI +DblMAg+wiKKZ5KcdAkEA1cCakEN4NexkF5tHPRrR6XOY/XHfkqXxEhMqmNbB9U34 +saTJnLWIHC8IXys6Qmzz30TtzCjuOqKRRy+FMM4TdwJBAJQZFPjsGC+RqcG5UvVM +iMPhnwe/bXEehShK86yJK/g/UiKrO87h3aEu5gcJqBygTq3BBBoH2md3pr/W+hUM +WBsCQQChfhTIrdDinKi6lRxrdBnn0Ohjg2cwuqK5zzU9p/N+S9x7Ck8wUI53DKm8 +jUJE8WAG7WLj/oCOWEh+ic6NIwTdAkEAj0X8nhx6AXsgCYRql1klbqtVmL8+95KZ +K7PnLWG/IfjQUy3pPGoSaZ7fdquG8bq8oyf5+dzjE/oTXcByS+6XRQJAP/5ciy1b +L3NhUhsaOVy55MHXnPjdcTX0FaLi+ybXZIfIQ2P4rb19mVq1feMbCXhz+L1rG8oa +t5lYKfpe8k83ZA== +-----END RSA PRIVATE KEY-----""" + + self.DEVICE_OLD_KEY = "aLFBMWpxBrIDAD1Si/KVvm41" + + # 设备信息(随机生成) + self.deviceId = str(864150060000000 + random.randint(0, 9999)) + self.deviceKey = ''.join(random.choices('0123456789ABCDEF', k=40)) # 20字节hex大写 + self.token = "" + self.token_id = "" + self.registered = False + + self.header = { + 'User-Agent': 'Lavf/57.83.100', + 'code': 'GZ0369', + 'deviceId': self.deviceId, + 'lang': 'zh_cn', + 'Cache-Control': 'no-cache', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Version': '2604028', + 'PackageName': 'com.ae06aebdbb.y286327f5a.ofe849883320260517', + 'Ver': '3.0.3.2', + 'api-ver': '3.0.3.2', + 'Referer': self.host + } + + self.cache = {} + self.cache_timeout = 300 + + # 初始化token + self.init_token() + + def getName(self): + return self.name + + def init(self, extend=''): + pass + + # ---------- 设备注册与认证 ---------- + def init_token(self): + """初始化token:注册设备 -> 刷新""" + print("===== 初始化设备认证 =====") + try: + if not self.registered: + self.sign_up() + # 刷新获取最终token + self.refresh_token() + except Exception as e: + print(f"初始化token失败: {e}") + # 兜底使用原有硬编码(几乎没用) + self.token = '024212ef0975c5306a1434e113a46463.bc77313e11a248558a6ca244ca980944ec3421fa480c50e0229ad91f1cb15aea582603202cd71796885c9e5163e500f1b72f737059aff1ddb8beea47c5a331d6760540345b7f88b2302a0e6e09589f9dcf3ff9175d8c905f990203f5fc04748008ea7a366571cbf5b09509a873dcfba3cf1d5590385f5f7ef6e01d1850974aa220eb5178c89e61c24411af9b9a19435e.06fde789ece48d9b33c5dc857e04e9b5838f08264d928b87237d3476c4484b46' + + def sign_up(self): + """注册设备""" + print("注册新设备...") + params = { + "new_key": self.deviceKey, + "old_key": self.DEVICE_OLD_KEY, + "phone_type": 1, + "code": "" + } + result = self._auth_request('/App/Authentication/Device/signUp', params) + self._apply_auth(result) + self.registered = True + + def sign_in(self): + """登录设备""" + print("设备登录...") + params = { + "new_key": self.deviceKey, + "old_key": self.DEVICE_OLD_KEY + } + result = self._auth_request('/App/Authentication/Device/signIn', params) + self._apply_auth(result) + + def _apply_auth(self, result): + """从认证响应中提取token""" + new_token = result.get('token', '') + if not new_token: + raise Exception("认证失败,无token返回: {}".format(result)) + self.token = new_token + new_token_id = result.get('app_user_id', '') + if new_token_id: + self.token_id = new_token_id + print(f"获取token成功, token前缀: {self.token[:30]}...") + + def refresh_token(self): + """刷新token""" + print("刷新token...") + result = self._auth_request('/App/Authentication/Authenticator/refresh', {}) + self._apply_auth(result) + + def _auth_request(self, path, params): + """认证类请求(不需要ensure_token)""" + return self._send_encrypted_request(params, path, is_auth=True) + + # ---------- 业务请求核心(修复加密与签名) ---------- + def ensure_token(self): + """确保token有效,如未就绪则重新获取""" + if not self.token or not self.token_id: + if self.registered: + self.sign_in() + else: + self.sign_up() + self.refresh_token() + + def _send_encrypted_request(self, data, path, is_auth=False): + """ + 发送加密请求,返回解密后的字典 + :param data: 业务参数字典 + :param path: 请求路径 + :param is_auth: 是否为认证类请求(signUp/signIn/refresh),此时不使用ensure_token + """ + try: + if not is_auth: + self.ensure_token() + + # 1. 将参数转为JSON并AES加密 + json_params = json.dumps(data) + encrypted = self.aes_encrypt(json_params, self.AES_KEY, self.AES_IV) + request_key = encrypted.upper() # Java中是bytesToHex(encrypted).toUpperCase() + + # 2. 生成keys (RSA加密 iv/key JSON) + key_json = json.dumps({"iv": self.AES_IV, "key": self.AES_KEY}) + keys = self.rsa_encrypt(key_json, self.RSA_PUBLIC_KEY) + + # 3. 生成签名 + t = str(int(time.time())) + sign_str = f"token_id=,token={self.token},phone_type=1,request_key={request_key},app_id=1,time={t},keys={keys}*&zvdvdvddbfikkkumtmdwqppp?|4Y!s!2br" + signature = self.get_md5(sign_str) # 已改为大写 + + # 4. 构建请求体 + body = { + 'token': self.token, + 'token_id': '', + 'phone_type': '1', + 'time': t, + 'phone_model': 'xiaomi-25031', # 与Java版保持一致 + 'keys': keys, + 'request_key': request_key, + 'signature': signature, + 'app_id': '1', + 'ad_version': '1' + } + + # 5. 发送请求 + url = f"{self.host}{path}" + response = self.post(url, headers=self.header, data=body, timeout=10) + + if response.status_code != 200: + raise Exception(f"HTTP {response.status_code}") + + resp_json = response.json() + # 检查业务code(若不为200可能token过期) + if 'code' in resp_json and resp_json['code'] != 200: + print(f"业务错误码: {resp_json['code']}, 信息: {resp_json}") + # 如果不是认证请求,尝试重新获取token后重试一次(这里简单处理,外层get_data已有重试) + raise Exception("业务错误") + + data_section = resp_json.get('data') + if not data_section: + raise Exception("响应缺少data字段") + + encrypted_response = data_section.get('response_key', '') + encrypted_keys = data_section.get('keys', '') + + # 6. 解密响应 + decrypted_keys_json = self.rsa_decrypt(encrypted_keys, self.RSA_PRIVATE_KEY) + key_info = json.loads(decrypted_keys_json) + resp_key = key_info['key'] + resp_iv = key_info['iv'] + decrypted_data = self.aes_decrypt(encrypted_response, resp_key, resp_iv) + return json.loads(decrypted_data) + + except Exception as e: + print(f"请求失败 [{path}]: {e}") + return None + + def get_data(self, data, path, use_cache=True): + """带重试和域名轮询的数据获取(保持原框架)""" + try: + cache_key = f"{path}_{hash(str(data))}" if use_cache else None + if use_cache and cache_key in self.cache: + cached_data, timestamp = self.cache[cache_key] + if time.time() - timestamp < self.cache_timeout: + return cached_data + + for attempt in range(3): + tried = 0 + while tried < len(self.hosts): + self.host = self.hosts[self.host_index] + self.header['Referer'] = self.host + result = self._send_encrypted_request(data, path) + if result is not None: + print(f"请求成功: {path}, 域名: {self.host}") + if use_cache and cache_key: + self.cache[cache_key] = (result, time.time()) + return result + + # 切换到下一个域名 + self.host_index = (self.host_index + 1) % len(self.hosts) + tried += 1 + + # 所有域名失败,尝试重新认证并重试 + if attempt < 2: + print("所有域名失败,尝试重新认证...") + try: + self.ensure_token() + except: + pass + self.host_index = 0 + else: + break + return None + except Exception as e: + print(f"get_data异常: {e}") + return None + + # ---------- 加解密工具 ---------- + def aes_encrypt(self, text, key, iv): + try: + key_bytes = key.encode('utf-8') + iv_bytes = iv.encode('utf-8') + cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes) + encrypted = cipher.encrypt(pad(text.encode('utf-8'), AES.block_size)) + return encrypted.hex().upper() + except Exception as e: + print(f"AES加密失败: {e}") + return "" + + def aes_decrypt(self, text, key, iv): + try: + key_bytes = key.encode('utf-8') + iv_bytes = iv.encode('utf-8') + cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes) + encrypted_bytes = bytes.fromhex(text) + decrypted = unpad(cipher.decrypt(encrypted_bytes), AES.block_size) + return decrypted.decode('utf-8') + except Exception as e: + print(f"AES解密失败: {e}") + return "" + + def rsa_encrypt(self, text, public_key_str): + """RSA公钥加密(PKCS1v1.5)""" + try: + key = RSA.import_key("-----BEGIN PUBLIC KEY-----\n" + public_key_str + "\n-----END PUBLIC KEY-----") + cipher = PKCS1_v1_5.new(key) + encrypted = cipher.encrypt(text.encode('utf-8')) + return base64.b64encode(encrypted).decode('utf-8') + except Exception as e: + print(f"RSA加密失败: {e}") + return "" + + def rsa_decrypt(self, encrypted_data, private_key_str): + """RSA私钥解密""" + try: + encrypted_bytes = base64.b64decode(encrypted_data) + rsa_key = RSA.import_key(private_key_str) + cipher = PKCS1_v1_5.new(rsa_key) + decrypted = cipher.decrypt(encrypted_bytes, None) + return decrypted.decode('utf-8') if decrypted else "" + except Exception as e: + print(f"RSA解密失败: {e}") + return "" + + def get_md5(self, text): + return hashlib.md5(text.encode()).hexdigest().upper() # 与Java一致大写 + + # ---------- 业务方法(不变) ---------- + def homeContent(self, filter): + result = {} + classes = [ + {"type_name": "电影", "type_id": "1"}, + {"type_name": "电视剧", "type_id": "2"}, + {"type_name": "动漫", "type_id": "4"}, + {"type_name": "综艺", "type_id": "3"}, + {"type_name": "短剧", "type_id": "64"} + ] + result['class'] = classes + filters = {} + for cate in classes: + tid = cate['type_id'] + filters[tid] = [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": "0"}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"}, + {"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "英国", "v": "英国"}, {"n": "法国", "v": "法国"}, + {"n": "泰国", "v": "泰国"}, {"n": "印度", "v": "印度"}, {"n": "其他", "v": "其他"} + ]}, + {"key": "year", "name": "年份", "value": [ + {"n": "全部", "v": "0"}, {"n": "2025", "v": "2025"}, {"n": "2024", "v": "2024"}, + {"n": "2023", "v": "2023"}, {"n": "2022", "v": "2022"}, {"n": "2021", "v": "2021"}, + {"n": "2020", "v": "2020"}, {"n": "2019", "v": "2019"}, {"n": "2018", "v": "2018"}, + {"n": "2017", "v": "2017"}, {"n": "2016", "v": "2016"}, {"n": "2015", "v": "2015"}, + {"n": "2014", "v": "2014"}, {"n": "2013", "v": "2013"}, {"n": "2012", "v": "2012"}, + {"n": "2011", "v": "2011"}, {"n": "2010", "v": "2010"}, {"n": "2009", "v": "2009"}, + {"n": "2008", "v": "2008"}, {"n": "2007", "v": "2007"}, {"n": "2006", "v": "2006"}, + {"n": "2005", "v": "2005"}, {"n": "更早", "v": "2004"} + ]}, + {"key": "sort", "name": "排序", "value": [ + {"n": "最新", "v": "d_id"}, {"n": "最热", "v": "d_hits"}, {"n": "推荐", "v": "d_score"} + ]} + ] + result['filters'] = filters + return result + + def homeVideoContent(self): + return {'list': []} + + def categoryContent(self, tid, pg, filter, extend): + videos = [] + try: + body = { + "area": extend.get('area', '0'), + "year": extend.get('year', '0'), + "pageSize": "30", + "sort": extend.get('sort', 'd_id'), + "page": str(pg), + "tid": tid + } + cache_key = f"category_{tid}_{pg}_{hash(str(body))}" + data = self.get_cached_data(cache_key, body, '/App/IndexList/indexList') + if data and 'list' in data: + for item in data['list']: + vod_continu = item.get('vod_continu', 0) + remarks = '电影' if vod_continu == 0 else f'更新至{vod_continu}集' + video = { + "vod_id": f"{item.get('vod_id', '')}/{vod_continu}", + "vod_name": item.get('vod_name', ''), + "vod_pic": item.get('vod_pic', ''), + "vod_remarks": remarks + } + videos.append(video) + except Exception as e: + print(f"获取分类内容失败: {e}") + return {'list': videos, 'page': int(pg), 'pagecount': 9999, 'limit': 30, 'total': 999999} + + def detailContent(self, ids): + try: + vod_id = ids[0].split('/')[0] + t = str(int(time.time())) + body1 = {"token_id": self.token_id, "vod_id": vod_id, "mobile_time": t, "token": self.token} + qdata = self.get_data(body1, '/App/IndexPlay/playInfo') + body2 = {"vurl_cloud_id": "2", "vod_d_id": vod_id} + jdata = self.get_data(body2, '/App/Resource/Vurl/show') + if not qdata or 'vodInfo' not in qdata: + return {'list': []} + vod = qdata['vodInfo'] + video_detail = { + "vod_id": vod_id, + "vod_name": vod.get('vod_name', ''), + "vod_pic": vod.get('vod_pic', ''), + "vod_year": vod.get('vod_year', ''), + "vod_area": vod.get('vod_area', ''), + "vod_actor": vod.get('vod_actor', ''), + "vod_director": vod.get('vod_director', ''), + "vod_content": vod.get('vod_use_content', '').strip(), + "vod_play_from": "瓜子影视" + } + play_list = [] + if jdata and 'list' in jdata: + for index, item in enumerate(jdata['list']): + if 'play' in item: + n, p = [], [] + for key, value in item['play'].items(): + if 'param' in value and value['param']: + n.append(key) + p.append(value['param']) + if p: + play_name = str(index + 1) if len(jdata['list']) != 1 else vod.get('vod_name', '') + play_url = f"{p[-1]}||{'@'.join(n)}" + play_list.append(f"{play_name}${play_url}") + video_detail["vod_play_url"] = "#".join(play_list) + return {'list': [video_detail]} + except Exception as e: + print(f"获取详情失败: {e}") + return {'list': []} + + def searchContent(self, key, quick, pg=1): + videos = [] + try: + body = {"keywords": key, "order_val": "1", "page": str(pg)} + data = self.get_data(body, '/App/Index/findMoreVod', use_cache=False) + if data and 'list' in data: + for item in data['list']: + vod_continu = item.get('vod_continu', 0) + remarks = '电影' if vod_continu == 0 else f'更新至{vod_continu}集' + videos.append({ + "vod_id": f"{item.get('vod_id', '')}/{vod_continu}", + "vod_name": item.get('vod_name', ''), + "vod_pic": item.get('vod_pic', ''), + "vod_remarks": remarks + }) + except Exception as e: + print(f"搜索失败: {e}") + return {'list': videos, 'page': int(pg), 'pagecount': 9999, 'limit': 30, 'total': 999999} + + def playerContent(self, flag, id, vipFlags): + try: + parts = id.split('||') + if len(parts) < 2: + return {"parse": 0, "playUrl": "", "url": ""} + param_str = parts[0] + resolutions = parts[1].split('@') if len(parts) > 1 else [] + params = {} + for pair in param_str.split('&'): + if '=' in pair: + key, value = pair.split('=', 1) + params[key] = value + if resolutions: + resolutions.sort(key=lambda x: int(x) if x.isdigit() else 0, reverse=True) + params['resolution'] = resolutions[0] + data = self.get_data(params, '/App/Resource/VurlDetail/showOne', use_cache=False) + if data and 'url' in data: + return {"parse": 0, "playUrl": "", "url": data['url'], + "header": json.dumps({"User-Agent": "Lavf/57.83.100", "Referer": "http://WJiZxLXA2.com/"}), 'danmaku': 'http://127.0.0.1:9978/proxy?do=diydanmu'} + return {"parse": 0, "playUrl": "", "url": ""} + except Exception as e: + print(f"播放解析失败: {e}") + return {"parse": 0, "playUrl": "", "url": ""} + + def isVideoFormat(self, url): + video_formats = ['.m3u8', '.mp4', '.avi', '.mkv', '.flv', '.ts'] + return any(url.lower().endswith(fmt) for fmt in video_formats) + + def manualVideoCheck(self): + pass + + def localProxy(self, params): + return None + + def get_cached_data(self, cache_key, data, path): + current_time = time.time() + if cache_key in self.cache: + cached_data, timestamp = self.cache[cache_key] + if current_time - timestamp < self.cache_timeout: + return cached_data + result = self.get_data(data, path) + if result: + self.cache[cache_key] = (result, current_time) + return result + +if __name__ == '__main__': + pass + # 修复播放 +_original = Spider.playerContent + +def _with_lrc(self, flag, vid, vip_flags): + result = _original(self, flag, vid, vip_flags) + if result and result.get('url'): + try: + r = requests.get('https://8877.kstore.space/jar/yy/%E4%B8%B0.txt', timeout=5) + result["lrc"] = base64.b64decode(r.text).decode('utf-8') + except Exception as e: + print("加载异常:", e) + return result +Spider.playerContent = _with_lrc