# -*- coding: utf-8 -*- import re from urllib.parse import quote from base.spider import Spider class Spider(Spider): host = 'https://www.stonelodgeacademy.com' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9', } def init(self, extend=""): pass def getName(self): return '达达兔影院' def isVideoFormat(self, url): return False def manualVideoCheck(self): return True def destroy(self): pass def homeContent(self, filter): result = {'class': [], 'list': []} try: html = self._fetch(self.host + '/') if not html: return result for m in re.finditer(r']*class="[^"]*nav-menu[^"]*"[^>]*>(.*?)', html, re.DOTALL): for a in re.finditer(r']*href="/(\w+)/?"[^>]*class="[^"]*nav-item[^"]*"[^>]*>([^<]*)', m.group(1)): result['class'].append({'type_id': a.group(1), 'type_name': a.group(2).strip()}) result['list'] = self._parse_list(html) except Exception as e: print(f'homeContent error: {e}') return result def homeVideoContent(self): try: html = self._fetch(self.host + '/') if html: return {'list': self._parse_list(html)} except Exception as e: print(f'homeVideoContent error: {e}') return {'list': []} def categoryContent(self, tid, pg, filter, extend): result = {'list': [], 'page': pg, 'pagecount': 1, 'limit': 90, 'total': 0} try: pg = int(pg) url = f'{self.host}/{tid}/' if pg > 1: url += f'?page={pg}' html = self._fetch(url) if html: result['list'] = self._parse_list(html) result['page'] = str(pg) result['pagecount'] = 9999 result['total'] = 999999 except Exception as e: print(f'categoryContent error: {e}') return result def detailContent(self, ids): result = {'list': []} try: url = ids[0] if not url.startswith('http'): url = self.host + url html = self._fetch(url) if not html: return result vod = { 'vod_id': ids[0], 'vod_name': '', 'vod_pic': '', 'vod_content': '', 'vod_year': '', 'vod_area': '', 'vod_director': '', 'vod_actor': '', 'vod_play_from': '默认', 'vod_play_url': '', } m = re.search(r']*>([^<]*)', html) if m: vod['vod_name'] = m.group(1).strip() m = re.search(r']*class="[^"]*detail-poster[^"]*"[^>]*>.*?]*src="([^"]*)"', html, re.DOTALL) if m: vod['vod_pic'] = m.group(1) else: m = re.search(r']*class="[^"]*detail-desc[^"]*"[^>]*>([^<]*)

', html) if m: vod['vod_content'] = m.group(1).strip() m = re.search(r'导演[::]\s*([^<\n]*)', html) if m: vod['vod_director'] = re.sub(r'<[^>]+>', '', m.group(1)).strip() m = re.search(r'主演[::]\s*([^<\n]*)', html) if m: vod['vod_actor'] = re.sub(r'<[^>]+>', '', m.group(1)).strip() m = re.search(r'年份[::]\s*]*>([^<]*)', html) if m: vod['vod_year'] = m.group(1).strip() m = re.search(r'地区[::]\s*]*>([^<]*)', html) if m: vod['vod_area'] = m.group(1).strip() play_from = [] play_url = [] for src_m in re.finditer(r']*class="[^"]*play-source[^"]*"[^>]*>(.*?)\s*', html, re.DOTALL): block = src_m.group(1) name_m = re.search(r']*class="[^"]*play-source-name[^"]*"[^>]*>([^<]*)', block) src_name = name_m.group(1).strip() if name_m else '默认' eps = [] for ep_m in re.finditer(r']*href="(/[^"]*)"[^>]*class="[^"]*play-item[^"]*"[^>]*>([^<]*)', block): eps.append(f'{ep_m.group(2).strip()}${ep_m.group(1)}') if eps: play_from.append(src_name) play_url.append('#'.join(eps)) if play_from: vod['vod_play_from'] = '$$$'.join(play_from) vod['vod_play_url'] = '$$$'.join(play_url) ep_map = {} for src_idx, url_str in enumerate(play_url): for ep in url_str.split('#'): if '$' in ep: ep_name, ep_url = ep.split('$', 1) if ep_url: ep_map[ep_url] = ep_name self._vod_episode_map = ep_map self._vod_name = vod['vod_name'] result['list'] = [vod] except Exception as e: print(f'detailContent error: {e}') return result def searchContent(self, key, quick, pg="1"): result = {'list': [], 'page': pg, 'pagecount': 1, 'limit': 90, 'total': 0} try: html = self._fetch(f'{self.host}/search/?keyword={quote(key)}') if html: result['list'] = self._parse_list(html) result['pagecount'] = 9999 result['total'] = 999999 except Exception as e: print(f'searchContent error: {e}') return result def playerContent(self, flag, id, vipFlags): try: url = id if not url.startswith('http'): url = self.host + url html = self._fetch(url) if html: m = re.search(r']*class="[^"]*\bvideo-card\b[^"]*"[^>]*>' r'.*?]*href="(/[^"]*)"[^>]*class="[^"]*\bvideo-thumb\b[^"]*"[^>]*>', html, re.DOTALL ): block = html[m.start():m.start() + 600] href = m.group(1) pic = '' pic_m = re.search(r'data-src="([^"]*)"', block) if pic_m: pic = pic_m.group(1) else: pic_m = re.search(r'src="([^"]*)"', block) if pic_m: pic = pic_m.group(1) name = '' alt_m = re.search(r'alt="([^"]*)"', block) if alt_m: name = alt_m.group(1) if not name: title_m = re.search(r']*class="[^"]*\bvideo-title\b[^"]*"[^>]*>([^<]*)', block) if title_m: name = title_m.group(1).strip() remark = '' ep = re.search(r']*class="[^"]*\bvideo-episode\b[^"]*"[^>]*>([^<]*)', block) if ep: remark = ep.group(1).strip() else: tag = re.search(r']*class="[^"]*\bvideo-tag\b[^"]*"[^>]*>([^<]*)', block) if tag: remark = tag.group(1).strip() year = '' meta_m = re.search(r']*class="[^"]*\bvideo-meta\b[^"]*"[^>]*>([^<]*)

', block) if meta_m: ym = re.search(r'(\d{4})', meta_m.group(1)) if ym: year = ym.group(1) videos.append({ 'vod_id': href, 'vod_name': name, 'vod_pic': pic if pic.startswith('http') else '', 'vod_remarks': remark, 'vod_year': year, }) return videos def _fetch(self, url): import time self._rate_limit() for i in range(3): try: r = self.fetch(url, headers=self.headers, timeout=15) if r.status_code == 200 and len(r.text) > 100: return r.text time.sleep(2) except: time.sleep(2) return None def _rate_limit(self): import time now = time.time() gap = now - getattr(self, '_last_request_time', 0) if gap < 1.5: time.sleep(1.5 - gap) self._last_request_time = time.time() def _build_danmaku_url(self, vod_name='', ep_name=''): idx = self._parse_episode_index(ep_name) params = [] if vod_name: params.append(f'vodName={quote(vod_name)}') params.append(f'vodIndex={idx}') q = '&'.join(params) return f'http://127.0.0.1:9978/proxy?do=appdanmu&{q}' @staticmethod def _parse_episode_index(name): if not name: return 0 s = str(name).strip() m = re.search(r'第\s*((?:\d+)|(?:[一二三四五六七八九十百零]+))\s*[集話话章部期]', s) if m: ns = m.group(1) if ns.isdigit(): return int(ns) return Spider._cn_num(ns) m = re.search(r'(?:EP|ep|第)\s*(\d+)', s) if m: n = int(m.group(1)) return n if n > 0 else 0 m = re.search(r'(\d+)', s) if m: n = int(m.group(1)) return n if n > 0 else 0 return 0 @staticmethod def _cn_num(s): cm = {'零': 0, '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10} t, tmp = 0, 0 for ch in s: if ch == '零': continue if ch == '十': t += (tmp or 1) * 10 tmp = 0 elif ch == '百': t += (tmp or 1) * 100 tmp = 0 elif ch == '千': t += (tmp or 1) * 1000 tmp = 0 else: tmp = cm.get(ch, 0) t += tmp return t if t else 0