From 9e90ad0c975fd246439414f4d38e4597566efe35 Mon Sep 17 00:00:00 2001 From: XC-Pro <19840+xc-pro@noreply.example.org> Date: Fri, 14 Aug 2026 11:24:54 +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/抖音.py | 470 ++++++++++++++++++++++++++++++++++++++++++++++++ py/枫叶影院.py | 392 ++++++++++++++++++++++++++++++++++++++++ py/枫叶影院1.py | 392 ++++++++++++++++++++++++++++++++++++++++ py/毒蛇.py | 250 ++++++++++++++++++++++++++ py/独播库.py | 270 ++++++++++++++++++++++++++++ 5 files changed, 1774 insertions(+) create mode 100644 py/抖音.py create mode 100644 py/枫叶影院.py create mode 100644 py/枫叶影院1.py create mode 100644 py/毒蛇.py create mode 100644 py/独播库.py diff --git a/py/抖音.py b/py/抖音.py new file mode 100644 index 0000000..77a74d0 --- /dev/null +++ b/py/抖音.py @@ -0,0 +1,470 @@ +# -*- coding: utf-8 -*- +import re +import sys +import json +import time +import random +import string + +sys.path.append('..') +from base.spider import Spider + + +class Spider(Spider): + + def init(self, extend=""): + self.extend = extend + self.cookie_cache = "" + + def getName(self): + return "抖音直播" + + def isVideoFormat(self, url): + return False + + def manualVideoCheck(self): + return False + + def destroy(self): + pass + + def localProxy(self, param): + return None + + host = "https://live.douyin.com" + ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + + headers = { + "User-Agent": ua, + "Accept": "application/json, text/plain, */*", + "Accept-Language": "zh-CN,zh;q=0.9", + } + + classes_config = [ + {"type_id": "10000$3", "type_name": "娱乐天地"}, + {"type_id": "10001$3", "type_name": "科技文化"}, + {"type_id": "102$4", "type_name": "音乐"}, + {"type_id": "103$4", "type_name": "游戏"}, + {"type_id": "105$4", "type_name": "舞蹈"}, + {"type_id": "101$4", "type_name": "聊天"}, + {"type_id": "108$4", "type_name": "运动"}, + {"type_id": "107$4", "type_name": "生活"}, + {"type_id": "106$4", "type_name": "文化"}, + {"type_id": "104$4", "type_name": "二次元"}, + ] + + # ==================== 工具函数 ==================== + def _generate_device_id(self): + timestamp = self._base36_encode(int(time.time() * 1000)) + random_part = ''.join(random.choices(string.ascii_lowercase + string.digits, k=13)) + return f"{timestamp}{random_part}" + + @staticmethod + def _base36_encode(num): + alphabet = '0123456789abcdefghijklmnopqrstuvwxyz' + if num == 0: + return '0' + res = [] + while num > 0: + num, rem = divmod(num, 36) + res.append(alphabet[rem]) + return ''.join(reversed(res)) + + def _get_cookie(self): + if self.cookie_cache: + return self.cookie_cache + try: + resp = self.fetch(self.host, headers=self.headers, verify=False) + cookies = resp.headers.get('set-cookie', '') + if cookies: + match = re.search(r'ttwid=([^;]+)', cookies) + if match: + self.cookie_cache = f"ttwid={match.group(1)}" + except Exception: + pass + return self.cookie_cache + + def _get_headers(self): + cookie = self._get_cookie() + hd = self.headers.copy() + hd["Referer"] = self.host + if cookie: + hd["Cookie"] = cookie + return hd + + def _first_non_empty(self, *values): + for v in values: + if v is not None and v != '': + return v + return None + + def _parse_raw_live_data(self, item): + if not isinstance(item, dict): + return None + candidates = [ + item.get('lives', {}).get('rawdata'), + item.get('lives', {}).get('raw_data'), + item.get('live', {}).get('rawdata'), + item.get('live_info', {}).get('rawdata'), + item.get('aweme_info', {}).get('live_info', {}).get('rawdata'), + item.get('data', {}).get('rawdata'), + item.get('rawdata'), + item.get('lives'), + item.get('live'), + item.get('live_info'), + item.get('aweme_info', {}).get('live_info'), + item.get('aweme_info'), + item.get('data'), + item + ] + for c in candidates: + if isinstance(c, str): + try: + parsed = json.loads(c) + if isinstance(parsed, dict): + return parsed + except Exception: + continue + elif isinstance(c, dict): + return c + return None + + def _normalize_search_item(self, raw, fallback=None): + if not isinstance(raw, dict): + return None + fallback = fallback or {} + + room_id = self._first_non_empty( + raw.get('id_str'), + raw.get('room_id_str'), + raw.get('room', {}).get('id_str'), + raw.get('room', {}).get('id'), + raw.get('room_id'), + raw.get('roomId') + ) + if not room_id: + return None + + web_rid = self._first_non_empty( + raw.get('owner', {}).get('web_rid'), + raw.get('web_rid'), + raw.get('room', {}).get('owner', {}).get('web_rid') + ) or self._generate_device_id() + + nickname = self._first_non_empty( + raw.get('owner', {}).get('nickname'), + raw.get('nickname'), + raw.get('room', {}).get('owner', {}).get('nickname'), + fallback.get('nickname') + ) or '抖音直播' + + title = self._first_non_empty( + raw.get('title'), + raw.get('room', {}).get('title'), + fallback.get('title'), + nickname + ) + + pic = self._first_non_empty( + raw.get('owner', {}).get('avatar_large', {}).get('url_list', [None])[0], + raw.get('room', {}).get('cover', {}).get('url_list', [None])[0], + raw.get('cover', {}).get('url_list', [None])[0], + raw.get('cover_url') + ) or '' + + online_text = self._first_non_empty( + raw.get('room', {}).get('stats', {}).get('user_count_str'), + raw.get('user_count_str'), + raw.get('room', {}).get('user_count_str'), + raw.get('user_count') + ) + tag_text = self._first_non_empty( + raw.get('video_feed_tag'), + raw.get('room', {}).get('partition_road_map', [{}])[0].get('title'), + raw.get('partition', {}).get('title'), + fallback.get('tag') + ) + remark = ' '.join(filter(None, [tag_text, online_text])) + + return { + "vod_id": f"{web_rid}@@{room_id}", + "vod_name": nickname, + "vod_pic": pic, + "vod_remarks": remark, + "vod_content": title + } + + def _extract_search_videos(self, payload): + if not isinstance(payload, list): + return [] + results = [] + seen = set() + for item in payload: + raw = self._parse_raw_live_data(item) + norm = self._normalize_search_item(raw, { + "nickname": item.get('nickname'), + "title": item.get('title') or item.get('desc'), + "tag": item.get('search_keyword') + }) + if not norm: + continue + if norm['vod_id'] in seen: + continue + seen.add(norm['vod_id']) + results.append(norm) + return results + + # ==================== 框架标准方法 ==================== + def homeContent(self, filter): + classes = self.classes_config + return { + "class": classes, + "list": [] + } + + def homeVideoContent(self): + return {} + + def categoryContent(self, tid, pg, filter, extend): + category_id = str(tid) + page = int(pg or 1) + offset = 15 * (page - 1) + parts = category_id.split('$') + if len(parts) < 2: + return {"list": [], "page": page, "pagecount": 0, "limit": 15, "total": 9999} + partition, ptype = parts[0], parts[1] + + params = { + "aid": "6383", + "app_name": "douyin_web", + "live_id": "1", + "device_platform": "web", + "language": "zh-CN", + "browser_language": "zh-CN", + "browser_platform": "Win32", + "browser_name": "Chrome", + "browser_version": "120.0.0.0", + "partition": partition, + "partition_type": ptype, + "count": "15", + "offset": str(offset), + "web_rid": self._generate_device_id(), + "cookie_enabled": "true", + "screen_width": "1920", + "screen_height": "1080" + } + + headers = self._get_headers() + urls = [ + "https://live.douyin.com/webcast/web/partition/detail/room/v2/", + "https://webcast.amemv.com/webcast/web/partition/detail/room/v2/", + ] + list_ = [] + for url in urls: + try: + resp = self.fetch(url, headers=headers, params=params, verify=False) + data = resp.json() + if data.get('status_code') != 0: + continue + if not data.get('data', {}).get('data'): + break + items = data['data']['data'] + for it in items: + web_rid = it.get('web_rid') or self._generate_device_id() + room = it['room'] + list_.append({ + "vod_id": f"{web_rid}@@{room['id_str']}", + "vod_name": room['title'], + "vod_pic": room['cover']['url_list'][0], + "vod_remarks": f"{room['owner']['nickname']} (🔥{room['stats']['user_count_str']})" + }) + break + except Exception: + continue + + return { + "list": list_, + "page": page, + "pagecount": 9999, + "limit": 15, + "total": 999999 + } + + def searchContent(self, key, quick, pg="1"): + kw = key.strip() + if not kw: + return {"list": [], "page": 1} + page = 1 + offset = 0 + + headers = self._get_headers() + + # 策略1 专用直播搜索 + try: + params1 = { + "device_platform": "webapp", + "aid": "6383", + "channel": "channel_pc_web", + "search_channel": "aweme_live", + "search_source": "switch_tab", + "query_correct_type": "1", + "need_filter_settings": "1", + "list_type": "single", + "keyword": kw, + "offset": str(offset), + "count": "20", + "os_version": "10" + } + r = self.fetch("https://www.douyin.com/aweme/v1/web/live/search/", + params=params1, headers=headers, verify=False) + data = r.json() + list_ = self._extract_search_videos(data.get('data')) + if list_: + return {"list": list_, "page": 1} + except Exception: + pass + + # 策略2 通用搜索 + try: + params2 = { + "device_platform": "webapp", + "aid": "6383", + "channel": "channel_pc_web", + "search_channel": "aweme_live", + "keyword": kw, + "offset": str(offset), + "count": "20", + "os_version": "10" + } + r = self.fetch("https://www.douyin.com/aweme/v1/web/general/search/stream/", + params=params2, headers=headers, verify=False) + data = r.json() + list_ = self._extract_search_videos(data.get('data')) + if list_: + return {"list": list_, "page": 1} + except Exception: + pass + + # 降级分区搜索 + try: + part_url = f"https://live.douyin.com/webcast/web/partition/search/?keyword={kw}&aid=6383" + r = self.fetch(part_url, headers=self._get_headers(), verify=False) + data = r.json() + partitions = data.get('data', {}).get('SearchResult', []) + if not partitions: + return {"list": [], "page": 1} + + merged = [] + seen = set() + for i in range(min(3, len(partitions))): + part = partitions[i].get('partition', {}) + p_id = part.get('id_str') + p_type = part.get('type') + if not p_id or p_type is None: + continue + cate_ret = self.categoryContent(f"{p_id}${p_type}", 1, None, None) + for item in cate_ret.get('list', []): + if item['vod_id'] in seen: + continue + seen.add(item['vod_id']) + item['vod_remarks'] = item['vod_remarks'] or part.get('title', kw) + merged.append(item) + if len(merged) >= 20: + break + if len(merged) >= 20: + break + return {"list": merged, "page": 1} + except Exception: + pass + + return {"list": [], "page": 1} + + def detailContent(self, ids): + if not ids: + return {"list": []} + raw_id = ids[0] + parts = raw_id.split('@@') + if len(parts) != 2: + return {"list": []} + web_rid, room_id = parts[0], parts[1] + + url = "https://live.douyin.com/webcast/room/web/enter/" + params = { + "aid": "6383", + "app_name": "douyin_web", + "live_id": "1", + "device_platform": "web", + "enter_from": "web_live", + "browser_language": "zh-CN", + "browser_platform": "Win32", + "browser_name": "Chrome", + "browser_version": "120.0.0.0", + "web_rid": web_rid, + "room_id_str": room_id, + "enter_source": "", + "is_need_double_stream": "false" + } + headers = self._get_headers() + try: + r = self.fetch(url, params=params, headers=headers, verify=False) + data = r.json() + if not data.get('data', {}).get('data'): + return {"list": []} + info = data['data']['data'][0] + + resolution_map = { + "FULL_HD1": "蓝光", + "HD1": "超清", + "ORIGION": "原画", + "SD1": "标清", + "SD2": "高清" + } + + flv_pull = info.get('stream_url', {}).get('flv_pull_url', {}) + flv_episodes = [] + for k, v in flv_pull.items(): + name = resolution_map.get(k, k) + flv_episodes.append(f"{name}${v}") + + hls_pull = info.get('stream_url', {}).get('hls_pull_url_map', {}) + hls_episodes = [] + for k, v in hls_pull.items(): + name = resolution_map.get(k, k) + hls_episodes.append(f"{name}${v}") + + vod_play_from = "" + vod_play_url = "" + if flv_episodes: + vod_play_from += "FLV$$$" + vod_play_url += "#".join(flv_episodes) + "$$$" + if hls_episodes: + vod_play_from += "HLS" + vod_play_url += "#".join(hls_episodes) + + vod_play_from = vod_play_from.rstrip("$$$") + vod_play_url = vod_play_url.rstrip("$$$") + + vod = { + "vod_id": raw_id, + "vod_name": info['title'], + "vod_pic": info['cover']['url_list'][0], + "vod_actor": info['owner']['nickname'], + "vod_content": "拾光请你看:"+info['title'], + "vod_play_from": vod_play_from, + "vod_play_url": vod_play_url + } + return {"list": [vod]} + except Exception: + return {"list": []} + + def playerContent(self, flag, id, vipFlags): + if not id: + return {"parse": 0, "url": "", "header": self.headers} + return { + "parse": 0, + "url": id, + "header": { + "User-Agent": self.ua, + "Referer": self.host + } + } 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/枫叶影院1.py b/py/枫叶影院1.py new file mode 100644 index 0000000..08379b8 --- /dev/null +++ b/py/枫叶影院1.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://www.shzchc.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": "2", "type_name": "电视剧"}, + {"type_id": "1", "type_name": "电影"}, + {"type_id": "4", "type_name": "动漫"}, + {"type_id": "3", "type_name": "综艺"}, + {"type_id": "5", "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..e75df2f --- /dev/null +++ b/py/毒蛇.py @@ -0,0 +1,250 @@ +# -*- coding: utf-8 -*- +# by @嗷呜 +import re +import sys +from pyquery import PyQuery as pq + +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://www.xnhrsb.com/' + + 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, + } + + # 对应 js 里的 class_name / class_url + classes_config = [ + ("电影", "1"), + ("电视剧", "2"), + ("综艺", "3"), + ("动漫", "4"), + ("短剧", "5"), + ("豆瓣", "duoban"), + ] + + def homeContent(self, filter): + result = {} + classes = [] + vlist = [] + + # 静态分类 + for name, tid in self.classes_config: + classes.append({ + "type_name": name, + "type_id": tid + }) + + # 首页推荐,沿用一级规则里的 .mrb ul li + rsp = self.fetch(self.host, headers=self.headers) + data = pq(rsp.text) + + # 一级: '.mrb&&ul li;.dytit&&Text;.lazy&&data-original;.hdinfo&&Text;a&&href' + vlist.extend(self.getlist(data('.mrb ul li'))) + + result['class'] = classes + result['list'] = vlist + return result + + def homeVideoContent(self): + return {} + + def categoryContent(self, tid, pg, filter, extend): + # js: url: '/dsshiyisw/fyclass--------fypage---.html' + url = f'{self.host}dsshiyisw/{tid}--------{pg}---.html' + rsp = self.fetch(url, headers=self.headers) + data = pq(rsp.text) + + videos = self.getlist(data('.mrb ul li')) + + result = { + 'list': videos, + 'page': pg, + 'pagecount': 9999, + 'limit': 90, + 'total': 999999 + } + return result + + def detailContent(self, ids): + vid = ids[0] + if not vid.startswith('http'): + if vid.startswith('/'): + url = self.host.rstrip('/') + vid + else: + url = self.host.rstrip('/') + '/' + vid + else: + url = vid + + rsp = self.fetch(url, headers=self.headers) + data = pq(rsp.text) + + # 二级: title/img/desc/content/tabs/lists + # title: 'h1&&Text;.moviedteail_list li&&a&&Text' + name = data('h1').text() + + info_list = data('.moviedteail_list li') + + def li_text(idx): + return info_list.eq(idx).text() if idx < len(info_list) else '' + + # 参考 desc 顺序,大致映射 + # desc: '.moviedteail_list li:eq(3)&&Text; + # .moviedteail_list li:eq(2)&&Text; + # .moviedteail_list li:eq(1)&&Text; + # .moviedteail_list li:eq(6)&&Text; + # .moviedteail_list li:eq(4)&&Text' + type_name = li_text(3) + director = li_text(2) + actor = li_text(1) + remarks = li_text(6) + year_or_area = li_text(4) + + pic = data('div.dyimg img').attr('src') or '' + if pic and not pic.startswith('http'): + if pic.startswith('/'): + pic = self.host.rstrip('/') + pic + else: + pic = self.host.rstrip('/') + '/' + pic + + content = data('.yp_context').text() + + vod = { + 'vod_id': vid, + 'vod_name': name, + 'vod_pic': pic, + 'type_name': type_name, + 'vod_year': year_or_area, + 'vod_area': '', + 'vod_remarks': remarks, + 'vod_actor': actor, + 'vod_director': director, + 'vod_content': content, + 'vod_play_from': '', + 'vod_play_url': '' + } + + # tabs: '.mi_paly_box .ypxingq_t' + tabs = [i.text() for i in data('.mi_paly_box .ypxingq_t').items()] + + # lists: '.paly_list_btn:eq(#id) a:gt(0)' + # 对应每个 tab 一组播放列表 + play_lists = [] + play_uls = list(data('.paly_list_btn').items()) + + for idx, ul in enumerate(play_uls): + items = [] + for i, a in enumerate(ul('a').items()): + # a:gt(0) => 跳过第一个 + if i == 0: + continue + title = a.text() + href = a.attr('href') or '' + if href and not href.startswith('http'): + if href.startswith('/'): + href = self.host.rstrip('/') + href + else: + href = self.host.rstrip('/') + '/' + href + items.append(f'{title}${href}') + play_lists.append('#'.join(items)) + + vod['vod_play_from'] = '$$$'.join(tabs) + vod['vod_play_url'] = '$$$'.join(play_lists) + + return {'list': [vod]} + + def searchContent(self, key, quick, pg="1"): + # js: searchUrl: '/dsshiyisc/**----------fypage---.html' + url = f'{self.host}dsshiyisc/{key}----------{pg}---.html' + rsp = self.fetch(url, headers=self.headers) + data = pq(rsp.text) + + videos = self.getlist(data('.mrb ul li')) + return { + 'list': videos, + 'page': pg + } + + def playerContent(self, flag, id, vipFlags): + """ + 对应 js 里的 lazy 解析: + - 如果页面中存在 player_aaaa 且脚本里有 "url":"xxx.m3u8" 就直接取 m3u8 + - 否则返回原始地址 + """ + if id.startswith('http'): + play_url = id + else: + if id.startswith('/'): + play_url = self.host.rstrip('/') + id + else: + play_url = self.host.rstrip('/') + '/' + id + + p = 0 # 直接返回真实地址,不再二级解析 + + # 如果本身就是 m3u8 + if '.m3u8' in play_url: + return {'parse': p, 'url': play_url, 'header': self.headers} + + rsp = self.fetch(play_url, headers=self.headers) + html = rsp.text + + # 查找包含 player_aaaa 的脚本并提取 m3u8 + # js 里正则: /\"url\"\\s*:\\s*\"([^\"]+\\.m3u8[^\"]*)\"/ + m = re.search(r'"url"\s*:\s*"([^"]+\.m3u8[^"]*)"', html) + if m: + real = m.group(1).replace('\\/', '/') + return {'parse': p, 'url': real, 'header': self.headers} + + # 兜底,返回原地址 + return {'parse': p, 'url': play_url, 'header': self.headers} + + def localProxy(self, param): + return None + + def getlist(self, data): + """ + 对应 js 一级/搜索 规则: + '.mrb&&ul li;.dytit&&Text;.lazy&&data-original;.hdinfo&&Text;a&&href' + """ + vlist = [] + for j in data.items(): + name = j('.dytit').text() + pic = j('.lazy').attr('data-original') or '' + remark = j('.hdinfo').text() + href = j('a').attr('href') or '' + + if pic and not pic.startswith('http'): + if pic.startswith('/'): + pic = self.host.rstrip('/') + pic + else: + pic = self.host.rstrip('/') + '/' + pic + + vod_id = href + vlist.append({ + 'vod_id': vod_id, + 'vod_name': name, + 'vod_pic': pic, + 'vod_remarks': remark + }) + return vlist 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