diff --git a/初见影视.py b/初见影视.py new file mode 100644 index 0000000..4413015 --- /dev/null +++ b/初见影视.py @@ -0,0 +1,622 @@ +# -*- coding: utf-8 -*- +# 初见影视爬虫 - 爱豆儿解析版 +import re +import json +import urllib.parse +import sys + +sys.path.append('..') +from base.spider import Spider + + +class Spider(Spider): + + def init(self, extend=""): + self.extend = extend + + def getName(self): + return "初见影视" + + def isVideoFormat(self, url): + return False + + def manualVideoCheck(self): + return False + + def destroy(self): + pass + + host = 'https://cjysw.cc' + + headers = { + 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; Mobile) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/114.0.0.0 Mobile Safari/537.36', + 'Referer': host, + } + + classes_config = [ + ("电影", "1"), + ("剧集", "15"), + ("动漫", "30"), + ("综艺", "24"), + ("纪录片", "63"), + ("短剧", "47"), + ("Netflix", "60"), + ] + + # ====== 只使用爱豆儿解析接口 ====== + PARSE_API = "https://jx.aidouer.net/?url=" + + def homeContent(self, filter): + result = {} + classes = [] + + for name, tid in self.classes_config: + classes.append({ + "type_name": name, + "type_id": tid + }) + + filters = {} + + filters["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": "历史"}, + ]}, + {"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": "其他"}, + ]}, + {"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"}, + ]}, + ] + + filters["15"] = [ + {"key": "class", "name": "类型", "value": [ + {"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": "英国"}, + ]}, + {"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"}, + ]}, + ] + + filters["30"] = [ + {"key": "area", "name": "地区", "value": [ + {"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"}, + ]}, + ] + + filters["24"] = [ + {"key": "year", "name": "年份", "value": [ + {"n": "全部", "v": ""}, + {"n": "2026", "v": "2026"}, + {"n": "2025", "v": "2025"}, + {"n": "2024", "v": "2024"}, + ]}, + ] + + filters["63"] = [ + {"key": "year", "name": "年份", "value": [ + {"n": "全部", "v": ""}, + {"n": "2026", "v": "2026"}, + {"n": "2025", "v": "2025"}, + {"n": "2024", "v": "2024"}, + ]}, + ] + + filters["47"] = [ + {"key": "year", "name": "年份", "value": [ + {"n": "全部", "v": ""}, + {"n": "2026", "v": "2026"}, + {"n": "2025", "v": "2025"}, + ]}, + ] + + filters["60"] = [ + {"key": "year", "name": "年份", "value": [ + {"n": "全部", "v": ""}, + {"n": "2026", "v": "2026"}, + {"n": "2025", "v": "2025"}, + ]}, + ] + + result['filters'] = filters + + rsp = self.fetch(self.host, headers=self.headers) + html = rsp.text + + vlist = [] + + pattern = r']*href="([^"]+)"[^>]*class="[^"]*module-poster-item[^"]*"[^>]*>(.*?)' + items = re.findall(pattern, html, re.DOTALL) + + for href, item_html in items: + title_match = re.search(r']*class="[^"]*module-poster-item-title[^"]*"[^>]*>([^<]*)', item_html) + title = title_match.group(1).strip() if title_match else '' + + note_match = re.search(r']*class="[^"]*module-item-note[^"]*"[^>]*>([^<]*)', item_html) + note = note_match.group(1).strip() if note_match else '' + + pic_match = re.search(r']*data-original="([^"]+)"[^>]*>', item_html) + pic = pic_match.group(1) if pic_match else '' + if not pic: + pic_match = re.search(r']*src="([^"]+)"[^>]*>', item_html) + pic = pic_match.group(1) if pic_match else '' + + if pic and not pic.startswith('http'): + pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic + if href and not href.startswith('http'): + href = self.host + href if href.startswith('/') else self.host + '/' + href + + if title: + vlist.append({ + 'vod_id': href, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': note + }) + + result['class'] = classes + result['list'] = vlist[:30] + return result + + def homeVideoContent(self): + return {} + + def categoryContent(self, tid, pg, filter, extend): + page = int(pg) if pg else 1 + + class_val = extend.get('class', '') if extend else '' + area_val = extend.get('area', '') if extend else '' + year_val = extend.get('year', '') if extend else '' + + if page == 1: + if class_val and not area_val and not year_val: + url = f"{self.host}/index.php/vod/show/id/{tid}/class/{class_val}.html" + elif area_val and not class_val and not year_val: + url = f"{self.host}/index.php/vod/show/id/{tid}/area/{area_val}.html" + elif year_val and not class_val and not area_val: + url = f"{self.host}/index.php/vod/show/id/{tid}/year/{year_val}.html" + elif class_val and area_val and not year_val: + url = f"{self.host}/index.php/vod/show/id/{tid}/class/{class_val}/area/{area_val}.html" + elif class_val and year_val and not area_val: + url = f"{self.host}/index.php/vod/show/id/{tid}/class/{class_val}/year/{year_val}.html" + elif area_val and year_val and not class_val: + url = f"{self.host}/index.php/vod/show/id/{tid}/area/{area_val}/year/{year_val}.html" + else: + url = f"{self.host}/index.php/vod/show/id/{tid}.html" + else: + if class_val and not area_val and not year_val: + url = f"{self.host}/index.php/vod/show/id/{tid}/class/{class_val}/page/{page}.html" + elif area_val and not class_val and not year_val: + url = f"{self.host}/index.php/vod/show/id/{tid}/area/{area_val}/page/{page}.html" + elif year_val and not class_val and not area_val: + url = f"{self.host}/index.php/vod/show/id/{tid}/year/{year_val}/page/{page}.html" + elif class_val and area_val and not year_val: + url = f"{self.host}/index.php/vod/show/id/{tid}/class/{class_val}/area/{area_val}/page/{page}.html" + else: + url = f"{self.host}/index.php/vod/show/id/{tid}/page/{page}.html" + + rsp = self.fetch(url, headers=self.headers) + html = rsp.text + + videos = [] + + pattern = r']*href="([^"]+)"[^>]*class="[^"]*module-poster-item[^"]*"[^>]*>(.*?)' + items = re.findall(pattern, html, re.DOTALL) + + for href, item_html in items: + title_match = re.search(r']*class="[^"]*module-poster-item-title[^"]*"[^>]*>([^<]*)', item_html) + title = title_match.group(1).strip() if title_match else '' + + note_match = re.search(r']*class="[^"]*module-item-note[^"]*"[^>]*>([^<]*)', item_html) + note = note_match.group(1).strip() if note_match else '' + + pic_match = re.search(r']*data-original="([^"]+)"[^>]*>', item_html) + pic = pic_match.group(1) if pic_match else '' + if not pic: + pic_match = re.search(r']*src="([^"]+)"[^>]*>', item_html) + pic = pic_match.group(1) if pic_match else '' + + if pic and not pic.startswith('http'): + pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic + if href and not href.startswith('http'): + href = self.host + href if href.startswith('/') else self.host + '/' + href + + if title: + videos.append({ + 'vod_id': href, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': note + }) + + pagecount = 1 + page_pattern = r']*class="[^"]*page-link[^"]*"[^>]*>(\d+)' + page_matches = re.findall(page_pattern, html) + for p in page_matches: + if p.isdigit() and int(p) > pagecount: + pagecount = int(p) + + result = { + 'list': videos, + 'page': page, + 'pagecount': pagecount, + 'limit': 90, + 'total': 999999 + } + return result + + def detailContent(self, ids): + vid = ids[0] + if not vid.startswith('http'): + url = self.host + vid if vid.startswith('/') else self.host + '/' + vid + else: + url = vid + + rsp = self.fetch(url, headers=self.headers) + html = rsp.text + + info = {'vod_id': vid} + + title_match = re.search(r']*>([^<]+)', html) + if title_match: + info['vod_name'] = title_match.group(1).strip() + else: + title_match = re.search(r'([^<]+)', html) + if title_match: + info['vod_name'] = title_match.group(1).split('-')[0].strip() + + pic_match = re.search(r']*data-original="([^"]+)"[^>]*class="[^"]*lazy[^"]*"[^>]*alt="[^"]*"[^>]*>', html) + if not pic_match: + pic_match = re.search(r']*src="([^"]+)"[^>]*class="[^"]*lazy[^"]*"[^>]*alt="[^"]*"[^>]*>', html) + if not pic_match: + pic_match = re.search(r']*class="[^"]*module-item-pic[^"]*"[^>]*>.*?]*src="([^"]+)"', html, re.DOTALL) + if pic_match: + pic = pic_match.group(1) + if pic and not pic.startswith('http'): + pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic + info['vod_pic'] = pic + + desc_match = re.search(r']*class="[^"]*module-info-introduction-content[^"]*"[^>]*>(.*?)', html, re.DOTALL) + if desc_match: + info['vod_content'] = re.sub(r'<[^>]+>', '', desc_match.group(1)).strip() + + info_items = re.findall(r']*class="[^"]*module-info-item[^"]*"[^>]*>.*?]*class="[^"]*module-info-item-title[^"]*"[^>]*>([^<]*).*?]*class="[^"]*module-info-item-content[^"]*"[^>]*>(.*?)', html, re.DOTALL) + + for title, value in info_items: + title = title.strip().replace(':', '').replace(':', '') + value = re.sub(r'<[^>]+>', '', value).strip() + if '导演' in title: + info['vod_director'] = value + elif '主演' in title: + info['vod_actor'] = value + elif '集数' in title or '更新' in title: + info['vod_remarks'] = value + elif '年份' in title or '上映' in title: + info['vod_year'] = value + + vod_id = None + player_match = re.search(r'var player_aaaa\s*=\s*({[^;]+})', html) + if player_match: + try: + player_data = json.loads(player_match.group(1)) + if player_data.get('id'): + vod_id = player_data['id'] + except: + pass + + if not vod_id: + vod_match = re.search(r'/detail/(\d+)\.html', url) + if vod_match: + vod_id = vod_match.group(1) + else: + vod_match = re.search(r'/play/(\d+)-\d+-\d+\.html', url) + if vod_match: + vod_id = vod_match.group(1) + + tabs = [] + + tab_pattern1 = r'<(?:div|a)[^>]*class="[^"]*module-tab-item[^"]*"[^>]*>.*?([^<]+)' + tabs = re.findall(tab_pattern1, html, re.DOTALL) + tabs = list(dict.fromkeys(tabs)) + + if not tabs: + tab_pattern2 = r']*class="[^"]*module-tab-value[^"]*"[^>]*>([^<]*)' + tabs = re.findall(tab_pattern2, html) + tabs = list(dict.fromkeys(tabs)) + + if not tabs: + tab_pattern3 = r'选择播放源\s*([^<]+)' + tabs = re.findall(tab_pattern3, html) + tabs = list(dict.fromkeys(tabs)) + + if not tabs: + tabs = ['高清'] + + play_items = [] + + container_pattern = r']*class="[^"]*module-play-list[^"]*"[^>]*>(.*?)' + containers = re.findall(container_pattern, html, re.DOTALL) + + for container in containers: + link_pattern = r']*href="([^"]+)"[^>]*>(.*?)' + links = re.findall(link_pattern, container, re.DOTALL) + for href, title in links: + title = re.sub(r'<[^>]+>', '', title).strip() + if not title: + title = '播放' + if href.startswith('/'): + href = self.host + href + if href not in [item.split('$')[-1] for item in play_items]: + play_items.append(f'{title}${href}') + + if not play_items and vod_id: + for idx, tab_name in enumerate(tabs): + sid = idx + 1 + total = 40 + total_match = re.search(r'(\d+)', html) + if total_match: + total = int(total_match.group(1)) + episodes = [] + for nid in range(1, total + 1): + ep_name = f"第{nid}集" + ep_url = f"{self.host}/play/{vod_id}-{sid}-{nid}.html" + episodes.append(f'{ep_name}${ep_url}') + if episodes: + play_items.extend(episodes) + + ktabs = [] + klists = [] + + if play_items: + if len(tabs) == 1: + ktabs = tabs + klists = ['#'.join(play_items)] + else: + items_per_tab = max(1, len(play_items) // len(tabs)) + for i in range(len(tabs)): + start = i * items_per_tab + end = start + items_per_tab if i < len(tabs) - 1 else len(play_items) + if start < len(play_items): + ktabs.append(tabs[i]) + klists.append('#'.join(play_items[start:end])) + + if not ktabs: + link_pattern = r']*class="[^"]*module-play-list-link[^"]*"[^>]*href="([^"]+)"[^>]*>([^<]+)' + links = re.findall(link_pattern, html) + if links: + episodes = [] + for href, title in links: + if href and not href.startswith('#'): + if not href.startswith('http'): + href = self.host + href if href.startswith('/') else self.host + '/' + href + episodes.append(f'{title}${href}') + if episodes: + ktabs.append("播放") + klists.append('#'.join(episodes)) + + info["vod_play_from"] = '$$$'.join(ktabs) if ktabs else "初见影视" + info["vod_play_url"] = '$$$'.join(klists) if klists else "" + + return {'list': [info]} + + def searchContent(self, key, quick, pg="1"): + page = int(pg) if pg else 1 + encoded_key = urllib.parse.quote(key, safe='') + url = f"{self.host}/index.php/vod/search.html?wd={encoded_key}&page={page}" + + rsp = self.fetch(url, headers=self.headers) + html = rsp.text + + videos = [] + + card_pattern = r'
]*>(.*?)
\s*\s*' + cards = re.findall(card_pattern, html, re.DOTALL) + + if not cards: + card_pattern = r'
]*>(.*?)
' + temp_cards = re.findall(card_pattern, html, re.DOTALL) + cards = [c for c in temp_cards if len(c) > 100] + + for card_html in cards: + href_match = re.search(r']*href="([^"]+)"[^>]*class="[^"]*module-card-item-poster[^"]*"[^>]*>', card_html) + if not href_match: + href_match = re.search(r']*href="([^"]+)"[^>]*>.*?
]*>', card_html) + href = href_match.group(1) if href_match else '' + + title_match = re.search(r'
.*?]*>.*?([^<]+)', card_html, re.DOTALL) + if not title_match: + title_match = re.search(r'([^<]+)', card_html) + if not title_match: + title_match = re.search(r'
.*?]*>([^<]+)', card_html, re.DOTALL) + title = title_match.group(1).strip() if title_match else '' + + note_match = re.search(r'
([^<]*)
', card_html) + note = note_match.group(1).strip() if note_match else '' + + pic_match = re.search(r']*data-original="([^"]+)"[^>]*>', card_html) + if not pic_match: + pic_match = re.search(r']*src="([^"]+)"[^>]*>', card_html) + pic = pic_match.group(1) if pic_match else '' + + if pic and not pic.startswith('http'): + pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic + if href and not href.startswith('http'): + href = self.host + href if href.startswith('/') else self.host + '/' + href + + if title: + videos.append({ + 'vod_id': href, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': note + }) + + if not videos: + try: + from pyquery import PyQuery as pq + data = pq(html) + for item in data('.module-card-item').items(): + href = item('a.module-card-item-poster').attr('href') or '' + title = item('.module-card-item-title strong').text() or '' + note = item('.module-item-note').text() or '' + pic = item('img').attr('data-original') or '' + + if title: + if pic and not pic.startswith('http'): + pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic + if href and not href.startswith('http'): + href = self.host + href if href.startswith('/') else self.host + '/' + href + videos.append({ + 'vod_id': href, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': note + }) + except: + pass + + return { + 'list': videos, + 'page': page, + 'pagecount': 9999, + 'limit': 90, + 'total': 999999 + } + + # ============================================================ + # 使用爱豆儿解析接口 + # ============================================================ + def playerContent(self, flag, id, vipFlags): + if id.startswith('http'): + play_url = id + else: + play_url = self.host + id if id.startswith('/') else self.host + '/' + id + + rsp = self.fetch(play_url, headers=self.headers) + html = rsp.text + + real_url = play_url + + # ====== 从 player_aaaa 提取 ====== + player_match = re.search(r'var player_aaaa\s*=\s*({[^;]+})', html) + if player_match: + try: + player_data = json.loads(player_match.group(1)) + if player_data.get('url'): + real_url = player_data['url'].replace('\\/', '/') + if real_url.startswith('//'): + real_url = 'https:' + real_url + except: + pass + + # ====== 从 iframe 提取 ====== + iframe_match = re.search(r']*src="([^"]+)"[^>]*>', html) + if iframe_match: + real_url = iframe_match.group(1) + if real_url.startswith('//'): + real_url = 'https:' + real_url + if real_url.startswith('/'): + real_url = self.host + real_url + + # ====== 判断URL类型 ====== + # 如果是m3u8/mp4,直接播放 + if '.m3u8' in real_url or '.mp4' in real_url: + return {'parse': 0, 'url': real_url, 'header': self.headers} + + # ====== 如果是第三方网站,使用爱豆儿解析 ====== + need_parse = False + parse_keywords = [ + 'iqiyi.com', 'bilibili.com', 'v.qq.com', 'youku.com', + 'qq.com', 'mgtv.com', 'sohu.com', 'le.com', 'pptv.com', + '爱奇艺', '腾讯', '优酷', '芒果', '乐视', '哔哩' + ] + for keyword in parse_keywords: + if keyword in real_url: + need_parse = True + break + + if need_parse: + parse_url = self.PARSE_API + urllib.parse.quote(real_url) + parse_headers = self.headers.copy() + parse_headers['Referer'] = 'https://jx.aidouer.net' + return { + 'parse': 0, + 'url': parse_url, + 'header': parse_headers + } + + return {'parse': 0, 'url': real_url, 'header': self.headers} + + def localProxy(self, param): + return None \ No newline at end of file diff --git a/圣魂.py b/圣魂.py new file mode 100644 index 0000000..89bb2a5 --- /dev/null +++ b/圣魂.py @@ -0,0 +1,443 @@ +# -*- coding: utf-8 -*- +# 少货影视 - TVbox标准版 + +import re +import sys +import json +import urllib.parse +import urllib.request +import ssl +import http.cookiejar + +sys.path.append('..') +from base.spider import Spider + +ssl._create_default_https_context = ssl._create_unverified_context + + +class Spider(Spider): + + def init(self, extend=""): + self.extend = extend + self.host = 'https://shdy3.com' + self.cookie_jar = http.cookiejar.CookieJar() + self.cookie_handler = urllib.request.HTTPCookieProcessor(self.cookie_jar) + self.opener = urllib.request.build_opener(self.cookie_handler) + self.fetch(self.host) + + def getName(self): + return "少货影视" + + def isVideoFormat(self, url): + return False + + def manualVideoCheck(self): + return False + + def destroy(self): + pass + + headers = { + 'User-Agent': 'Mozilla/5.0 (Linux; Android 9; ALN-AL00 Build/PQ3B.190801.05281406; wv) AppleWebKit/537.36', + 'accept-language': 'zh-CN,zh;q=0.9', + 'Referer': 'https://shdy3.com/', + } + + classes_config = [ + ("电影", "1"), + ("电视剧", "2"), + ("动漫", "4"), + ] + + def fetch(self, url): + try: + req = urllib.request.Request(url, headers=self.headers) + with self.opener.open(req, timeout=15) as response: + content = response.read() + try: + return content.decode('utf-8') + except: + return content.decode('gbk', errors='ignore') + except Exception as e: + print(f"请求失败: {e}") + return '' + + def clean_html(self, text): + if not text: + return '' + text = re.sub(r'<[^>]+>', '', text) + text = re.sub(r'\s+', ' ', text) + return text.strip() + + # ==================== 首页 ==================== + def homeContent(self, filter): + result = {} + classes = [] + for name, tid in self.classes_config: + classes.append({"type_name": name, "type_id": tid}) + + filters = { + "1": [{"key": "cateId", "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": "13"}, + {"n": "奇幻", "v": "14"}, + {"n": "剧情", "v": "15"}, + {"n": "冒险", "v": "16"}, + {"n": "悬疑", "v": "17"}, + {"n": "惊悚", "v": "18"}, + {"n": "其他", "v": "20"}, + ]}], + "2": [{"key": "cateId", "name": "类型", "value": [ + {"n": "全部", "v": "2"}, + {"n": "国产剧", "v": "20"}, + {"n": "TVB", "v": "21"}, + {"n": "韩剧", "v": "22"}, + {"n": "美剧", "v": "23"}, + {"n": "日剧", "v": "24"}, + {"n": "英剧", "v": "25"}, + {"n": "台剧", "v": "26"}, + {"n": "其他", "v": "27"}, + ]}], + } + result['filters'] = filters + + html = self.fetch(self.host) + vlist = [] + if html: + items = re.findall( + r']*class="myui-vodlist__thumb[^"]*"[^>]*href="([^"]+)"[^>]*title="([^"]+)"[^>]*data-original="([^"]+)"[^>]*>.*?]*class="pic-text[^"]*"[^>]*>(.*?)', + html, re.DOTALL + ) + for href, title, pic, remark in items[:12]: + if href and title: + if not href.startswith('http'): + href = self.host + href if href.startswith('/') else self.host + '/' + href + if pic and not pic.startswith('http'): + pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic + if pic and pic.startswith('//'): + pic = 'https:' + pic + vlist.append({ + 'vod_id': href, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': self.clean_html(remark) + }) + + result['class'] = classes + result['list'] = vlist + return result + + def homeVideoContent(self): + return {} + + # ==================== 分类 ==================== + def categoryContent(self, tid, pg, filter, extend): + page = int(pg) if pg else 1 + + if extend and 'cateId' in extend: + cateId = extend.get('cateId') + else: + cateId = tid + + if page <= 1: + url = f"{self.host}/list/{cateId}.html" + else: + url = f"{self.host}/list/{cateId}-{page}.html" + + html = self.fetch(url) + if not html: + return {'list': [], 'page': page, 'pagecount': 1} + + videos = [] + + items = re.findall( + r'
.*?.*?]*data-original="([^"]+)".*?
(.*?)
', + html, re.DOTALL + ) + + for href, title, pic, remark in items: + if href and title: + if not href.startswith('http'): + href = self.host + href if href.startswith('/') else self.host + '/' + href + if pic and not pic.startswith('http'): + pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic + if pic and pic.startswith('//'): + pic = 'https:' + pic + videos.append({ + 'vod_id': href, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': self.clean_html(remark) + }) + + if not videos: + items2 = re.findall( + r'
.*?]*data-original="([^"]+)"', + html, re.DOTALL + ) + for href, title, pic in items2: + if href and title: + href = self.host + href if href.startswith('/') else self.host + '/' + href + if pic and not pic.startswith('http'): + pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic + if pic and pic.startswith('//'): + pic = 'https:' + pic + videos.append({ + 'vod_id': href, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': '' + }) + + pagecount = page + page_match = re.search(r'\d+/(\d+)', html) + if page_match: + pagecount = int(page_match.group(1)) + else: + if re.search(r']*href="[^"]*-\d+\.html"[^>]*>下一页', html): + pagecount = page + 1 + else: + pagecount = page + + return { + 'list': videos, + 'page': page, + 'pagecount': pagecount, + 'limit': len(videos), + 'total': pagecount * len(videos) if videos else 999999 + } + + # ==================== 详情 ==================== + def detailContent(self, ids): + vid = ids[0] if ids else '' + if not vid: + return {'list': []} + + if not vid.startswith('http'): + vid = self.host + vid if vid.startswith('/') else self.host + '/' + vid + + html = self.fetch(vid) + if not html: + return {'list': []} + + info = {'vod_id': vid} + + # ====== 标题 ====== + title_match = re.search(r']*class="v_title[^"]*"[^>]*>(.*?)', html, re.DOTALL) + info['vod_name'] = self.clean_html(title_match.group(1)) if title_match else '未知' + + # ====== 海报 ====== + pic_match = re.search(r']*class="lazyload"[^>]*data-original="([^"]+)"', html) + if pic_match: + pic = pic_match.group(1) + if pic and not pic.startswith('http'): + pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic + if pic and pic.startswith('//'): + pic = 'https:' + pic + info['vod_pic'] = pic + else: + info['vod_pic'] = '' + + # ====== 简介 ====== + desc_match = re.search(r']*class="p_txt[^"]*"[^>]*>(.*?)

', html, re.DOTALL) + info['vod_content'] = self.clean_html(desc_match.group(1)) if desc_match else '' + + # ====== 导演、演员、年份、类型、地区 ====== + info['vod_director'] = '' + info['vod_actor'] = '' + info['vod_year'] = '' + info['type_name'] = '' + info['vod_area'] = '' + + info_p = re.search(r'

(.*?)

', html, re.DOTALL) + if info_p: + text = info_p.group(1) + # 去除标签 + text = re.sub(r']*>.*?', '', text) + text = self.clean_html(text) + + # 用 / 分割 + parts = text.split('/') + for part in parts: + part = part.strip() + if not part: + continue + + # 导演 + if '导演:' in part: + info['vod_director'] = part.replace('导演:', '').strip() + # 主演 + elif '主演:' in part: + info['vod_actor'] = part.replace('主演:', '').strip() + # 如果只有纯文本且没有冒号 + elif ':' not in part: + if re.match(r'^\d{4}$', part): + info['vod_year'] = part + elif part in ['大陆', '美国', '日本', '韩国', '英国', '法国', '德国', '意大利', '西班牙', '加拿大', '澳大利亚', '台湾', '香港', '泰国', '印度', '其他']: + info['vod_area'] = part + else: + info['type_name'] = part + + # 如果没提取到年份,从其他地方匹配 + if not info['vod_year']: + year_match = re.search(r'(\d{4})', html) + if year_match: + info['vod_year'] = year_match.group(1) + + # ====== 播放列表 ====== + play_from = [] + play_url = [] + + from_match = re.search(r']*class="from_list"[^>]*>(.*?)', html, re.DOTALL) + link_match = re.search(r']*class="play_list"[^>]*id="play_link"[^>]*>(.*?)', html, re.DOTALL) + + if from_match and link_match: + names = re.findall(r']*>(.*?)', from_match.group(1), re.DOTALL) + names = [self.clean_html(n) for n in names if n.strip()] + link_blocks = re.findall(r']*>(.*?)', link_match.group(1), re.DOTALL) + + if names and link_blocks and len(names) == len(link_blocks): + for idx, block in enumerate(link_blocks): + eps = re.findall(r']*href="([^"]+)"[^>]*>(.*?)', block, re.DOTALL) + source_name = names[idx] if idx < len(names) else f"线路{idx+1}" + episodes = [] + for ep_href, ep_name in eps: + ep_name = ep_name.strip() + if ep_name and ep_href: + if not ep_href.startswith('/'): + ep_href = '/' + ep_href + num = re.sub(r'[^0-9]', '', ep_name) + if num: + num = int(num) + else: + num = len(episodes) + 1 + episodes.append((num, f'{ep_name}${ep_href}')) + if episodes: + episodes.sort(key=lambda x: x[0]) + play_from.append(source_name) + play_url.append('#'.join([ep[1] for ep in episodes])) + + if not play_from: + panels = re.findall( + r']*class="myui-panel_bd[^"]*"[^>]*>.*?]*class="myui-content__list[^"]*"[^>]*>(.*?)', + html, re.DOTALL + ) + titles = re.findall(r']*class="title"[^>]*>(.*?)', html) + if panels: + for idx, panel in enumerate(panels): + eps = re.findall(r']*href="([^"]+)"[^>]*>(.*?)', panel, re.DOTALL) + source_name = titles[idx] if idx < len(titles) else f"线路{idx+1}" + episodes = [] + for ep_href, ep_name in eps: + ep_name = ep_name.strip() + if ep_name and ep_href: + if not ep_href.startswith('/'): + ep_href = '/' + ep_href + num = re.sub(r'[^0-9]', '', ep_name) + if num: + num = int(num) + else: + num = len(episodes) + 1 + episodes.append((num, f'{ep_name}${ep_href}')) + if episodes: + episodes.sort(key=lambda x: x[0]) + play_from.append(source_name) + play_url.append('#'.join([ep[1] for ep in episodes])) + + if not play_from: + all_eps = re.findall(r']*href="(/play/[^"]+)"[^>]*>([^<]+)', html, re.DOTALL) + if all_eps: + episodes = [] + for ep_href, ep_name in all_eps: + ep_name = ep_name.strip() + if ep_name and ep_href: + if not ep_href.startswith('/'): + ep_href = '/' + ep_href + num = re.sub(r'[^0-9]', '', ep_name) + if num: + num = int(num) + else: + num = len(episodes) + 1 + episodes.append((num, f'{ep_name}${ep_href}')) + if episodes: + episodes.sort(key=lambda x: x[0]) + play_from.append("骚火影视") + play_url.append('#'.join([ep[1] for ep in episodes])) + + info["vod_play_from"] = '$$$'.join(play_from) if play_from else "骚火影视" + info["vod_play_url"] = '$$$'.join(play_url) if play_url else "" + + return {'list': [info]} + + # ==================== 搜索 ==================== + def searchContent(self, key, quick, pg="1"): + page = int(pg) if pg else 1 + encoded_key = urllib.parse.quote(key, safe='') + url = f"{self.host}/s----------.html?wd={encoded_key}" + + html = self.fetch(url) + if not html: + return {'list': [], 'page': page, 'pagecount': 0, 'limit': 90, 'total': 0} + + videos = [] + li_pattern = r']*class="clearfix"[^>]*>(.*?)' + items = re.findall(li_pattern, html, re.DOTALL) + + for item in items: + pic = '' + pic_match = re.search(r'data-original="([^"]+)"', item) + if pic_match: + pic = pic_match.group(1) + + title_match = re.search(r']*href="([^"]+)"[^>]*title="([^"]+)"', item) + if not title_match: + continue + + href = title_match.group(1) + title = title_match.group(2).strip() + + remark = '' + remark_match = re.search(r']*class="pic-text[^"]*"[^>]*>(.*?)', item) + if remark_match: + remark = self.clean_html(remark_match.group(1)) + + if href and title: + if not href.startswith('http'): + href = self.host + href if href.startswith('/') else self.host + '/' + href + if pic and not pic.startswith('http'): + pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic + if pic and pic.startswith('//'): + pic = 'https:' + pic + videos.append({ + 'vod_id': href, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': remark + }) + + return {'list': videos, 'page': page, 'pagecount': 999, 'limit': 90, 'total': 999999} + + # ==================== 播放 ==================== + def playerContent(self, flag, id, vipFlags): + """ + 播放接口 - 完全模仿JS版本的play函数 + """ + if id.startswith('http'): + final_url = id + else: + if id.startswith('/'): + final_url = self.host.rstrip('/') + id + else: + final_url = self.host.rstrip('/') + '/' + id + + return {"parse": 1, "url": final_url, "jx": 0} + + def localProxy(self, param): + return None \ No newline at end of file