# -*- coding: utf-8 -*- # 123AV短视频 - Fongmi影视App适配爬虫 # 优化为短视频模式,支持滑动切换 import sys import re import json import urllib.parse sys.path.append('..') from base.spider import Spider class Spider(Spider): def getName(self): return "123AV" def init(self, extend=''): self.home_url = 'https://123av.fun' self.ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" def getDependence(self): return [] def isVideoFormat(self, url): return False def manualVideoCheck(self): return False def homeContent(self, filter): return { 'class': [ {'type_id': 'publish-time/sort-desc', 'type_name': '最新发布'}, {'type_id': 'view-count/sort-desc', 'type_name': '最多播放'}, {'type_id': 'comment-count/sort-desc', 'type_name': '最多评论'}, {'type_id': 'favorite-count/sort-desc', 'type_name': '最多收藏'}, {'type_id': 'explore', 'type_name': '探索发现'}, {'type_id': 'list', 'type_name': '排行榜'}, ], 'filters': {} } def homeVideoContent(self): return self.categoryContent('publish-time/sort-desc', 1, {}, {}) def _fetch_html(self, url): try: rsp = self.fetch(url, headers={ "User-Agent": self.ua, "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9", }, timeout=15) if rsp and hasattr(rsp, 'text') and rsp.text: return rsp.text except Exception as e: print(f'fetch error: {e}') return '' def _extract_video_list(self, html): videos = [] if not html: return videos # 匹配视频卡片 card_pattern = re.compile( r']*data-src="https://static\.123av\.fun/[^"]+\.m3u8"[^>]*)>(.*?)', re.S ) cards = card_pattern.findall(html) for attrs, content in cards: try: src_match = re.search(r'data-src="(https://static\.123av\.fun/[^"]+\.m3u8)"', attrs) poster_match = re.search(r'data-poster="([^"]*)"', attrs) id_match = re.search(r'data-id="(\d+)"', attrs) dur_match = re.search(r'data-duration="(\d+)"', attrs) title_match = re.search(r']*alt="([^"]*)"', content) if src_match and id_match: m3u8_url = src_match.group(1) vid = id_match.group(1) poster = poster_match.group(1) if poster_match else '' duration = dur_match.group(1) if dur_match else '0' title = title_match.group(1).strip() if title_match else f'视频{vid}' dur = int(duration) if dur >= 3600: duration_str = f'{dur // 3600}:{(dur % 3600) // 60:02d}:{dur % 60:02d}' else: duration_str = f'{dur // 60:02d}:{dur % 60:02d}' videos.append({ 'vod_id': vid, 'vod_name': title, 'vod_pic': poster, 'vod_remarks': duration_str, }) except Exception as e: continue return videos def categoryContent(self, tid, page, filter, ext): video_list = [] if tid in ('explore', 'list', 'subscribe'): url = f'{self.home_url}/{tid}/page-{page}' else: url = f'{self.home_url}/{tid}/page-{page}' html = self._fetch_html(url) video_list = self._extract_video_list(html) return { 'list': video_list, 'page': int(page), 'pagecount': 999, 'limit': 20, 'total': 999 * 20 } def detailContent(self, did): """视频详情 - 关键修改:返回播放URL让playerContent处理""" video_list = [] try: vid = did[0] detail_url = f'{self.home_url}/detail/{vid}' html = self._fetch_html(detail_url) if html: src_match = re.search(r'data-src="(https://static\.123av\.fun/[^"]+\.m3u8)"', html) poster_match = re.search(r'data-poster="([^"]*)"', html) title_match = re.search(r']*>([^<]+)', html) if not title_match: title_match = re.search(r'property="og:title"\s+content="([^"]*)"', html) if not title_match: title_match = re.search(r']*alt="([^"]*)"', html) desc_match = re.search(r'property="og:description"\s+content="([^"]*)"', html) dur_match = re.search(r'data-duration="(\d+)"', html) m3u8_url = src_match.group(1) if src_match else '' vod_pic = poster_match.group(1) if poster_match else '' vod_name = title_match.group(1).strip() if title_match else '' vod_content = desc_match.group(1) if desc_match else '' duration_str = '' if dur_match: dur = int(dur_match.group(1)) if dur >= 3600: duration_str = f'{dur // 3600}:{(dur % 3600) // 60:02d}:{dur % 60:02d}' else: duration_str = f'{dur // 60:02d}:{dur % 60:02d}' else: m3u8_url = '' vod_pic = '' vod_name = '' vod_content = '' duration_str = '' # 关键修改:如果直接有m3u8,放入播放URL # 使用特殊格式让Fongmi识别为短视频 if m3u8_url: # 格式: 集数名称$url#集数名称$url vod_play_url = f'正片${m3u8_url}' else: vod_play_url = '' video_list.append({ 'vod_id': vid, 'vod_name': vod_name, 'vod_pic': vod_pic, 'vod_remarks': duration_str, 'vod_content': vod_content, 'vod_play_from': '短视频', # 改为短视频,可能触发滑动模式 'vod_play_url': vod_play_url, 'type_name': '短视频', 'vod_year': '', 'vod_area': '', 'vod_director': '', 'vod_actor': '', }) except Exception as e: print(f'detailContent error: {e}') return { 'list': video_list, 'parse': 0, 'jx': 0 } def searchContent(self, key, quick, page='1'): video_list = [] try: encoded_key = urllib.parse.quote(key) url = f'{self.home_url}/search/{encoded_key}/page-{page}' html = self._fetch_html(url) video_list = self._extract_video_list(html) except Exception as e: print(f'searchContent error: {e}') return { 'list': video_list, 'page': int(page), 'pagecount': 99, 'limit': 20, 'total': 99 * 20 } def playerContent(self, flag, pid, vipFlags): """播放器内容 - 关键修改""" # 如果pid已经是m3u8地址,直接返回 if pid.startswith('http') and '.m3u8' in pid: return { 'parse': 0, # 直接播放 'url': pid, 'header': { 'User-Agent': self.ua, 'Referer': self.home_url + '/' } } # 如果是详情页URL,获取m3u8 if '/detail/' in pid: html = self._fetch_html(pid) if html: src_match = re.search(r'data-src="(https://static\.123av\.fun/[^"]+\.m3u8)"', html) if src_match: return { 'parse': 0, 'url': src_match.group(1), 'header': { 'User-Agent': self.ua, 'Referer': self.home_url + '/' } } # 默认返回,让外部解析 return { 'parse': 1, 'url': pid, 'header': { 'User-Agent': self.ua, 'Referer': self.home_url + '/' } } def localProxy(self, params): return {} def destroy(self): return '正在Destroy' if __name__ == '__main__': pass