# -*- 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://www.jubaba.vip',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()