#!/usr/bin/python # -*- coding: utf-8 -*- from base.spider import Spider import requests, re, json class Spider(Spider): def getName(self): return "JableTV" def init(self, extend=""): self.name = "JableTV" self.host = "https://jable.tv" self.header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"} def destroy(self): pass def localProxy(self, param): return [False, ""] def fix_url(self, url): if not url: return "" if url.startswith("//"): return "https:" + url if url.startswith("/"): return self.host + url return url def clean_text(self, text): return re.sub(r'\s+', ' ', text).strip() if text else "" def _fetch(self, url, method="GET", data=None): try: if method == "POST": resp = requests.post(url, data=data, headers=self.header, timeout=15) else: resp = requests.get(url, headers=self.header, timeout=15) resp.encoding = 'utf-8' return resp.text except Exception as e: print(f"[{self.name}] request fail: {url} - {e}") return "" def _extract_video_list(self, html): items = [] seen_ids = set() img_urls = re.findall(r'data-src="(https://[^"]*assets-cdn\.jable\.tv[^"]*preview\.jpg[^"]*)"', html) if not img_urls: img_urls = re.findall(r'data-src="(https://[^"]*assets-cdn\.jable\.tv[^"]*320x180[^"]*)"', html) if not img_urls: img_urls = re.findall(r'data-src="(https://[^"]*assets-cdn\.jable\.tv[^"]*)"', html) video_data = [] h6_matches = re.findall(r']*class="[^"]*title[^"]*"[^>]*>(.*?)', html, re.DOTALL) for h6c in h6_matches: if '/videos/' not in h6c: continue m = re.search(r'href="(https?://[^"]*videos/[^"]+)"', h6c) if not m: m = re.search(r'href="(/videos/[^"]+)"', h6c) if m: link = m.group(1) if link.startswith('/'): link = self.host + link title = re.sub(r'<[^>]+>', '', h6c).strip() vid = re.search(r'/videos/([^/]+)', link) vod_id = vid.group(1) if vid else "" if vod_id and vod_id not in seen_ids: seen_ids.add(vod_id) video_data.append((vod_id, title, link)) if not video_data: all_links = re.findall(r']*href="([^"]*videos/[^"]+)"[^>]*>(.*?)', html, re.DOTALL) for href, content in all_links: title = re.sub(r'<[^>]+>', '', content).strip() if not title: continue link = href if href.startswith('http') else self.host + href vid = re.search(r'/videos/([^/]+)', link) vod_id = vid.group(1) if vid else "" if vod_id and vod_id not in seen_ids: seen_ids.add(vod_id) video_data.append((vod_id, title, link)) if not video_data: hrefs = re.findall(r'href="(https?://[^"]*videos/[^"]+)"', html) for href in hrefs: vid = re.search(r'/videos/([^/]+)', href) vod_id = vid.group(1) if vid else "" if vod_id and vod_id not in seen_ids: seen_ids.add(vod_id) video_data.append((vod_id, vod_id.upper(), href)) for i, (vid, title, link) in enumerate(video_data): pic = img_urls[i] if i < len(img_urls) else "" items.append({"vod_name": self.clean_text(title), "vod_id": vid, "vod_pic": pic}) return items def homeContent(self, filter): print(f"[{self.name}] homeContent") try: html = self._fetch(self.host) items = self._extract_video_list(html) class_list = [ {"type_name": "最新更新", "type_id": "latest-updates"}, {"type_name": "所有热门", "type_id": "hot"}, {"type_name": "本月热门", "type_id": "hot-monthly"}, {"type_name": "本周热门", "type_id": "hot-weekly"}, {"type_name": "今日热门", "type_id": "hot-daily"}, {"type_name": "新片速递", "type_id": "new-release"}, {"type_name": "蓝光无码", "type_id": "uncensored"}, ] return {"class": class_list, "filters": {}, "list": items, "page": 1, "pagecount": 1, "limit": len(items), "total": len(items)} except Exception as e: print(f"[{self.name}] homeContent error: {e}") return {"class": [], "filters": {}, "list": [], "page": 1, "pagecount": 1, "limit": 0, "total": 0} def categoryContent(self, tid, pg, filter, extend): print(f"[{self.name}] category: {tid} page {pg}") try: pg = int(pg) if pg else 1 tmap = {"hot-monthly":"hot","hot-weekly":"hot","hot-daily":"hot"} sort_map = {"hot":"video_viewed","hot-monthly":"video_viewed_month","hot-weekly":"video_viewed_week","hot-daily":"video_viewed_today"} rpath = tmap.get(tid, tid) params = f"?sort_by={sort_map[tid]}" if tid in sort_map else "" url = f"{self.host}/{rpath}/{params}" if pg == 1 else f"{self.host}/{rpath}/{pg}/{params}" html = self._fetch(url) items = self._extract_video_list(html) pagecount = 99 nums = re.findall(r'/(\d+)/"', html) if nums: ints = [int(x) for x in nums if x.isdigit() and 1 <= int(x) <= 200] if ints: pagecount = max(ints) total = len(items) * pagecount return {"list": items, "page": pg, "pagecount": pagecount, "limit": len(items), "total": total} except Exception as e: print(f"[{self.name}] category error: {e}") return {"list": [], "page": pg, "pagecount": 1, "limit": 0, "total": 0} def detailContent(self, ids): vid = ids[0] if isinstance(ids, list) else ids print(f"[{self.name}] detail: {vid}") try: html = self._fetch(f"{self.host}/videos/{vid}/") title = "" m = re.search(r']*>(.*?)', html) if m: title = re.sub(r'<[^>]+>', '', m.group(1)).strip() if not title: m = re.search(r'(.*?)', html) if m: title = re.sub(r'\s*-\s*Jable\.TV.*', '', m.group(1), flags=re.IGNORECASE).strip() if not title: m = re.search(r']*class="[^"]*title[^"]*"[^>]*>(.*?)', html, re.DOTALL) if m: title = re.sub(r'<[^>]+>', '', m.group(1)).strip() pic = "" m = re.search(r'poster="([^"]+)"', html) if m: pic = self.fix_url(m.group(1)) if not pic: m = re.search(r']*property="og:image"[^>]*content="([^"]+)"', html) if m: pic = m.group(1) if not pic: m = re.search(r'data-src="([^"]*preview\.jpg[^"]*)"', html) if m: pic = m.group(1) hls_url = "" m = re.search(r"var\s+hlsUrl\s*=\s*'([^']+)'", html) if m: hls_url = m.group(1) if not hls_url: m = re.search(r'var\s+hlsUrl\s*=\s*"([^"]+)"', html) if m: hls_url = m.group(1) vod_play_url = f"默认线路${hls_url}" if hls_url else "" vod_play_from = "默认线路" if hls_url else "" print(f"[{self.name}] detail ok: {title[:30] if title else 'N/A'}, has_play={bool(hls_url)}") return {"list": [{"vod_id": vid, "vod_name": self.clean_text(title), "vod_pic": pic, "vod_play_from": vod_play_from, "vod_play_url": vod_play_url}]} except Exception as e: print(f"[{self.name}] detail error: {e}") return {"list": []} def searchContent(self, key, quick, pg): pg = int(pg) if pg else 1 print(f"[{self.name}] search: {key}") try: html = self._fetch(f"{self.host}/search/", method="POST", data={"searchword": key}) items = self._extract_video_list(html) if not items: html = self._fetch(f"{self.host}/search/{key}/") items = self._extract_video_list(html) print(f"[{self.name}] search results: {len(items)}") return {"list": items, "page": pg, "pagecount": 1, "limit": len(items), "total": len(items)} except Exception as e: print(f"[{self.name}] search error: {e}") return {"list": [], "page": pg, "pagecount": 1, "limit": 0, "total": 0} def playerContent(self, flag, id, vip_flag): print(f"[{self.name}] player: {flag}") try: if ".m3u8" in (id or "") or ".mp4" in (id or ""): print(f"[{self.name}] direct: {id[:50] if id else 'N/A'}...") return {"parse": 0, "url": id, "header": json.dumps(self.header)} html = self._fetch(f"{self.host}/videos/{id}/") hls_url = "" m = re.search(r"var\s+hlsUrl\s*=\s*'([^']+)'", html) if m: hls_url = m.group(1) if not hls_url: m = re.search(r'var\s+hlsUrl\s*=\s*"([^"]+)"', html) if m: hls_url = m.group(1) if hls_url: print(f"[{self.name}] play: {hls_url[:50]}...") return {"parse": 0, "url": hls_url, "header": json.dumps(self.header)} print(f"[{self.name}] play url not found") return {"parse": 0, "url": "", "header": ""} except Exception as e: print(f"[{self.name}] player error: {e}") return {"parse": 0, "url": "", "header": ""}