From 5e9d8678417733ce26e4995cc664c84b6a263e22 Mon Sep 17 00:00:00 2001 From: ziyongdaima <153406854+ziyongdaima@users.noreply.github.com> Date: Sun, 13 Jul 2025 14:41:40 +0800 Subject: [PATCH] =?UTF-8?q?Create=20=E5=89=A7=E9=9C=B8.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- py/剧霸.py | 315 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 py/剧霸.py diff --git a/py/剧霸.py b/py/剧霸.py new file mode 100644 index 0000000..fa69760 --- /dev/null +++ b/py/剧霸.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- +# by @嗷呜 +import json +import random +import re +import sys +import time +from base64 import b64decode, b64encode +import concurrent.futures +import requests +from Crypto.Hash import MD5 +from pyquery import PyQuery as pq +sys.path.append('..') +from base.spider import Spider + +class Spider(Spider): + + def init(self, extend=""): + self.host=self.gethost() + self.headers.update({ + 'referer': f'{self.host}/', + 'origin': self.host, + }) + self.session = requests.Session() + self.session.headers.update(self.headers) + self.session.get(self.host) + pass + + def getName(self): + pass + + def isVideoFormat(self, url): + pass + + def manualVideoCheck(self): + pass + + def destroy(self): + pass + + headers = { + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36', + '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', + 'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="134", "Google Chrome";v="134"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-fetch-site': 'same-origin', + 'sec-fetch-mode': 'navigate', + 'sec-fetch-user': '?1', + 'sec-fetch-dest': 'document', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8', + } + + config={ + "1":[{"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":"儿童"},{"n":"网络电影","v":"网络电影"}]},{"key":"area","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":"其他"}]},{"key":"year","name":"年份","value":[{"n":"全部","v":""},{"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"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"}]},{"key":"by","name":"排序","value":[{"n":"时间","v":"time"},{"n":"人气","v":"hits"},{"n":"评分","v":"score"}]}], + "2":[{"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":"其他"}]},{"key":"area","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":"其他"}]},{"key":"year","name":"年份","value":[{"n":"全部","v":""},{"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"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"}]},{"key":"by","name":"排序","value":[{"n":"时间","v":"time"},{"n":"人气","v":"hits"},{"n":"评分","v":"score"}]}], + "3":[{"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":"求职"}]},{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"内地","v":"内地"},{"n":"港台","v":"港台"},{"n":"欧美","v":"欧美"},{"n":"日韩","v":"日韩"},{"n":"其他","v":"其他"}]},{"key":"year","name":"年份","value":[{"n":"全部","v":""},{"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"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"}]},{"key":"by","name":"排序","value":[{"n":"时间","v":"time"},{"n":"人气","v":"hits"},{"n":"评分","v":"score"}]}], + "4":[{"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":"其他"}]},{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"国产","v":"国产"},{"n":"欧美","v":"欧美"},{"n":"日本","v":"日本"},{"n":"其他","v":"其他"}]},{"key":"year","name":"年份","value":[{"n":"全部","v":""},{"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"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"}]},{"key":"by","name":"排序","value":[{"n":"时间","v":"time"},{"n":"人气","v":"hits"},{"n":"评分","v":"score"}]}], + } + + def homeContent(self, filter): + data=self.getpq() + result = {} + classes = [] + for k in data('ul.swiper-wrapper').eq(0)('li').items(): + i=k('a').attr('href') + if i and 'type' in i: + classes.append({ + 'type_name': k.text(), + 'type_id': re.findall(r'\d+', i)[0], + }) + result['class'] = classes + result['list'] = self.getlist(data('.tab-content.ewave-pannel_bd li')) + result['filters'] = self.config + return result + + def homeVideoContent(self): + pass + + def categoryContent(self, tid, pg, filter, extend): + path=f"/vodshow/{tid}-{extend.get('area','')}-{extend.get('by','')}-{extend.get('class','')}-----{pg}---{extend.get('year','')}.html" + data=self.getpq(path) + result = {} + result['list'] = self.getlist(data('ul.ewave-vodlist.clearfix li')) + result['page'] = pg + result['pagecount'] = 9999 + result['limit'] = 90 + result['total'] = 999999 + return result + + def detailContent(self, ids): + data=self.getpq(f"/voddetail/{ids[0]}.html") + v=data('.ewave-content__detail') + c=data('p') + vod = { + 'type_name':c.eq(0)('a').text(), + 'vod_year': v('.data.hidden-sm').text(), + 'vod_remarks': v('h1').text(), + 'vod_actor': c.eq(1)('a').text(), + 'vod_director': c.eq(2)('a').text(), + 'vod_content': c.eq(-1).text(), + 'vod_play_from': '', + 'vod_play_url': '' + } + nd=list(data('ul.nav-tabs.swiper-wrapper li').items()) + pd=list(data('ul.ewave-content__playlist').items()) + n,p=[],[] + for i,x in enumerate(nd): + n.append(x.text()) + p.append('#'.join([f"{j.text()}${j('a').attr('href')}" for j in pd[i]('li').items()])) + vod['vod_play_url']='$$$'.join(p) + vod['vod_play_from']='$$$'.join(n) + return {'list':[vod]} + + def searchContent(self, key, quick, pg="1"): + if pg=="1": + p=f"-------------.html?wd={key}" + else: + p=f"{key}----------{pg}---.html" + data=self.getpq(f"/vodsearch/{p}") + return {'list':self.getlist(data('ul.ewave-vodlist__media.clearfix li')),'page':pg} + + def playerContent(self, flag, id, vipFlags): + try: + data=self.getpq(id) + jstr = json.loads(data('.ewave-player__video script').eq(0).text().split('=', 1)[-1]) + jxpath='/bbplayer/api.php' + data=self.session.post(f"{self.host}{jxpath}",data={'vid':jstr['url']}).json()['data'] + if re.search(r'\.m3u8|\.mp4',data['url']): + url=data['url'] + elif data['urlmode'] == 1: + url=self.decode1(data['url']) + elif data['urlmode'] == 2: + url=self.decode2(data['url']) + elif re.search(r'\.m3u8|\.mp4',jstr['url']): + url=jstr['url'] + else: + url=None + if not url:raise Exception('未找到播放地址') + p,c=0,'' + except Exception as e: + self.log(f"解析失败: {e}") + p,url,c=1,f"{self.host}{id}",'document.querySelector("#playleft iframe").contentWindow.document.querySelector("#start").click()' + return {'parse': p, 'url': url, 'header': {'User-Agent':'okhttp/3.12.1'},'click': c} + + def localProxy(self, param): + wdict=json.loads(self.d64(param['wdict'])) + url=f"{wdict['jx']}{wdict['id']}" + data=pq(self.fetch(url,headers=self.headers).text) + html=data('script').eq(-1).text() + url = re.search(r'src="(.*?)"', html).group(1) + return [302,'text/html',None,{'Location':url}] + + def liveContent(self, url): + pass + + def gethost(self): + data=pq(self.fetch('https://13826.kstore.space/jubba.py/jbb1.html',headers=self.headers).text) + hlist=list(data('.content-top ul li').items())[:2] + hsots=[j('a').attr('href') for i in hlist for j in i('a').items()] + return self.host_late(hsots) + + def host_late(self, urls): + with concurrent.futures.ThreadPoolExecutor() as executor: + future_to_url = { + executor.submit(self.test_host, url): url + for url in urls + } + results = {} + for future in concurrent.futures.as_completed(future_to_url): + url = future_to_url[future] + try: + results[url] = future.result() + except Exception as e: + results[url] = float('inf') + min_url = min(results.items(), key=lambda x: x[1])[0] if results else None + if all(delay == float('inf') for delay in results.values()) or not min_url: + return urls[0] + return min_url + + def test_host(self, url): + try: + start_time = time.monotonic() + response = requests.head( + url, + timeout=1.0, + allow_redirects=False, + headers=self.headers + ) + response.raise_for_status() + return (time.monotonic() - start_time) * 1000 + except Exception as e: + print(f"测试{url}失败: {str(e)}") + return float('inf') + + def getpq(self, path='',min=0,max=3): + data = self.session.get(f"{self.host}{path}") + data=data.text + try: + if '人机验证' in data: + print(f"第{min}次尝试人机验证") + jstr=pq(data)('script').eq(-1).html() + token,tpath,stt=self.extract(jstr) + body={'value':self.encrypt(self.host,stt),'token':self.encrypt(token,stt)} + cd=self.session.post(f"{self.host}{tpath}",data=body) + if min>max:raise Exception('人机验证失败') + return self.getpq(path,min+1,max) + return pq(data) + except: + return pq(data.encode('utf-8')) + + def encrypt(self, input_str,staticchars): + encodechars = "" + for char in input_str: + num0 = staticchars.find(char) + if num0 == -1: + code = char + else: + code = staticchars[(num0 + 3) % 62] + num1 = random.randint(0, 61) + num2 = random.randint(0, 61) + encodechars += staticchars[num1] + code + staticchars[num2] + return self.e64(encodechars) + + def extract(self, js_code): + token_match = re.search(r'var token = encrypt\("([^"]+)"\);', js_code) + token_value = token_match.group(1) if token_match else None + url_match = re.search(r'var url = \'([^\']+)\';', js_code) + url_value = url_match.group(1) if url_match else None + staticchars_match = re.search(r'var\s+staticchars\s*=\s*["\']([^"\']+)["\'];', js_code) + staticchars = staticchars_match.group(1) if staticchars_match else None + return token_value, url_value,staticchars + + def decode1(self, val): + url = self._custom_str_decode(val) + parts = url.split("/") + result = "/".join(parts[2:]) + key1 = json.loads(self.d64(parts[1])) + key2 = json.loads(self.d64(parts[0])) + decoded = self.d64(result) + return self._de_string(key1, key2, decoded) + + def _custom_str_decode(self, val): + decoded = self.d64(val) + key = self.md5("test") + result = "" + for i in range(len(decoded)): + result += chr(ord(decoded[i]) ^ ord(key[i % len(key)])) + return self.d64(result) + + def _de_string(self, key_array, value_array, input_str): + result = "" + for char in input_str: + if re.match(r'^[a-zA-Z]$', char): + if char in key_array: + index = key_array.index(char) + result += value_array[index] + continue + result += char + return result + + def decode2(self, url): + key = "PXhw7UT1B0a9kQDKZsjIASmOezxYG4CHo5Jyfg2b8FLpEvRr3WtVnlqMidu6cN" + url=self.d64(url) + result = "" + i = 1 + while i < len(url): + try: + index = key.find(url[i]) + if index == -1: + char = url[i] + else: + char = key[(index + 59) % 62] + result += char + except IndexError: + break + i += 3 + return result + + def getlist(self, data): + videos = [] + for k in data.items(): + j = k('.ewave-vodlist__thumb') + h=k('.text-overflow a') + if not h.attr('href'):h=j + videos.append({ + 'vod_id': re.findall(r'\d+', h.attr('href'))[0], + 'vod_name': j.attr('title'), + 'vod_pic': j.attr('data-original'), + 'vod_remarks': k('.pic-text').text(), + }) + return videos + + def e64(self, text): + try: + text_bytes = text.encode('utf-8') + encoded_bytes = b64encode(text_bytes) + return encoded_bytes.decode('utf-8') + except Exception as e: + print(f"Base64编码错误: {str(e)}") + return "" + + def d64(self,encoded_text): + try: + encoded_bytes = encoded_text.encode('utf-8') + decoded_bytes = b64decode(encoded_bytes) + return decoded_bytes.decode('utf-8') + except Exception as e: + print(f"Base64解码错误: {str(e)}") + return "" + + def md5(self, text): + h = MD5.new() + h.update(text.encode('utf-8')) + return h.hexdigest()