# coding: utf-8 import sys import re import json from urllib.parse import quote, unquote from lxml import etree sys.path.append('..') from base.spider import Spider class Spider(Spider): def getName(self): return "有声小说" def init(self, extend): print("============有声小说============") def homeContent(self, filter): # 只保留有声小说分类 cateManual = { "有声小说": "audio" } classes = [{'type_name': k, 'type_id': v} for k, v in cateManual.items()] return {'class': classes} def homeVideoContent(self): return {'list': []} def categoryContent(self, tid, pg, filter, extend): result = {} # 使用 per-page=100 提高每页数量 url = f'https://www.book18.me/audio/index?page={pg}&per-page=100' try: 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", "Referer": "https://www.book18.me/", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } rsp = self.fetch(url, headers=headers) text = rsp.text videos = [] # 方法1: 使用正则提取列表项数据属性(更准确) list_pattern = r'
  • ]*data-id="(\d+)"[^>]*data-title="([^"]*)"[^>]*data-author="([^"]*)"' list_matches = re.findall(list_pattern, text) for data_id, data_title, data_author in list_matches: # 在同一个列表项中查找播放量 play_pattern = rf']*data-id="{data_id}"[^>]*>.*?\s*(\d+(?:\.\d+)?)\s*万' play_match = re.search(play_pattern, text, re.DOTALL) play_count = play_match.group(1) if play_match else "0" videos.append({ "vod_id": data_id, "vod_name": data_title, "vod_pic": "", "vod_remarks": f"声优: {data_author} 人气: {play_count}万" }) # 方法2: 如果方法1没找到,使用原来的正则提取 if not videos: # 新的正则模式匹配标题、声优和播放量 pattern = r']*>([^<]+).*?声优:\s*]*>([^<]+).*?\s*(\d+(?:\.\d+)?)\s*万' matches = re.findall(pattern, text, re.DOTALL) for title, actor, play_count in matches: title = title.strip() actor = actor.strip() if actor else "未知" play_count = play_count.strip() # 提取 aid(从链接中) href_match = re.search(rf'href="(/audio/(\d+))"[^>]*>{re.escape(title)}', text) aid = href_match.group(2) if href_match else str(abs(hash(title)) % 100000) videos.append({ "vod_id": aid, "vod_name": title, "vod_pic": "", "vod_remarks": f"声优: {actor} 人气: {play_count}万" }) result['list'] = videos result['page'] = int(pg) result['pagecount'] = 9999 result['limit'] = 100 result['total'] = 999999 except Exception as e: print(f"【有声小说】分类内容获取失败: {e}") result = { 'list': [], 'page': int(pg), 'pagecount': 1, 'limit': 100, 'total': 0 } return result def detailContent(self, array): aid = array[0] url = f'https://www.book18.me/audio/{aid}' try: headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://www.book18.me/" } rsp = self.fetch(url, headers=headers) html = rsp.text vod = { "vod_id": aid, "vod_name": "未知标题", "vod_pic": "", "type_name": "有声小说", "vod_year": "", "vod_area": "", "vod_remarks": "", "vod_actor": "", "vod_director": "", "vod_content": "", "vod_play_from": "书生玩剣ⁱ·*₁'", "vod_play_url": "" } # 方法1: 从页面中的标题元素提取 title_match = re.search(r']*>(.*?)', html) if title_match: vod['vod_name'] = self.clean_html(title_match.group(1)) # 方法2: 从页面标题中提取(去除网站后缀) if vod['vod_name'] == "未知标题": title_match = re.search(r'(.*?)', html) if title_match: raw_title = title_match.group(1) # 去除各种可能的网站后缀 clean_title = re.sub(r'\s*-?\s*(有声小说|色情小说|成人小说|情色小说|book18\.me).*$', '', raw_title).strip() vod['vod_name'] = clean_title if clean_title else raw_title # 方法3: 从面包屑导航或列表项中提取 if vod['vod_name'] == "未知标题": # 尝试从列表项的数据属性中提取 data_title_match = re.search(r'data-title="([^"]+)"', html) if data_title_match: vod['vod_name'] = data_title_match.group(1) # 方法4: 最后兜底,使用 aid 作为标题 if vod['vod_name'] == "未知标题": vod['vod_name'] = f"有声小说 {aid}" # 提取声优信息 actors = [] # 从页面中提取声优信息 actor_matches = re.findall(r'声优[::\s]*([^<>\n]+)', html) if actor_matches: actors.extend(actor_matches) # 从列表项的数据属性中提取 data_author_match = re.search(r'data-author="([^"]+)"', html) if data_author_match: actors.append(data_author_match.group(1)) vod['vod_actor'] = ', '.join(set(actors)) if actors else "未知声优" # 提取播放量 play_match = re.search(r'(\d+(?:\.\d+)?)\s*万\s*播放', html) or \ re.search(r'播放[::\s]*(\d+(?:\.\d+)?)\s*万', html) or \ re.search(r'\s*(\d+(?:\.\d+)?)\s*万', html) if play_match: vod['vod_remarks'] = f"人气: {play_match.group(1)}万" else: vod['vod_remarks'] = "有声小说" # 提取描述内容 desc_match = re.search(r']*data-id="(\d+)"[^>]*data-title="([^"]*)"[^>]*data-author="([^"]*)"' list_matches = re.findall(list_pattern, text) for data_id, data_title, data_author in list_matches: # 在同一个列表项中查找播放量 play_pattern = rf']*data-id="{data_id}"[^>]*>.*?\s*(\d+(?:\.\d+)?)\s*万' play_match = re.search(play_pattern, text, re.DOTALL) play_count = play_match.group(1) if play_match else "0" videos.append({ "vod_id": data_id, "vod_name": data_title, "vod_pic": "", "vod_remarks": f"声优: {data_author} 人气: {play_count}万" }) # 方法2: 如果方法1没找到,使用原来的正则提取 if not videos: # 新的正则模式匹配标题、声优和播放量 pattern = r']*>([^<]+).*?声优:\s*]*>([^<]+).*?\s*(\d+(?:\.\d+)?)\s*万' matches = re.findall(pattern, text, re.DOTALL) for title, actor, play_count in matches: title = title.strip() actor = actor.strip() if actor else "未知" play_count = play_count.strip() # 提取 aid href_match = re.search(rf'href="(/audio/(\d+))"[^>]*>{re.escape(title)}', text) aid = href_match.group(2) if href_match else str(abs(hash(title)) % 100000) videos.append({ "vod_id": aid, "vod_name": title, "vod_pic": "", "vod_remarks": f"声优: {actor} 人气: {play_count}万" }) result['list'] = videos except Exception as e: print(f"【有声小说】搜索失败: {e}") return result def playerContent(self, flag, id, vipFlags): # 直接播放 .webm 音频 if id.startswith('http') and ('.webm' in id or '.m3u8' in id): return { "parse": 0, "playUrl": "", "url": id, "header": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://www.book18.me/" } } else: # 可能是页面链接,交给 detail 处理 return { "parse": 0, "playUrl": "", "url": id, "header": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://www.book18.me/" } } def isVideoFormat(self, url): return False def manualVideoCheck(self): return False def localProxy(self, param): return {} def clean_html(self, text): """清除HTML标签""" if not text: return text return re.sub(r'<[^>]+>', '', text).strip()