From bd4e8d688f508c415600453d511da594498d780f Mon Sep 17 00:00:00 2001 From: qist <87984115@qq.com> Date: Tue, 27 Jan 2026 19:02:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/厂长资源.js | 2 +- py/TVB云播.py | 426 +++++++++++++++++++++++++++++++++++++++++++++ py/闪雷.py | 331 +++++++++++++++++++++++++++++++++++ 3 files changed, 758 insertions(+), 1 deletion(-) create mode 100644 py/TVB云播.py create mode 100644 py/闪雷.py diff --git a/js/厂长资源.js b/js/厂长资源.js index 4a431ca7..486bbd0b 100644 --- a/js/厂长资源.js +++ b/js/厂长资源.js @@ -2,7 +2,7 @@ // 地址发布页 https://cz01.vip var rule = { title: '厂长资源', - host: 'https://www.cz0101.com', + host: 'https://www.czzymovie.com', // host:'https://www.czzy.site', // host:'https://cz01.vip', // hostJs:'print(HOST);let html=request(HOST,{headers:{"User-Agent":PC_UA}});HOST = html.match(/推荐访问 " + HOST)', diff --git a/py/TVB云播.py b/py/TVB云播.py new file mode 100644 index 00000000..5b8d4076 --- /dev/null +++ b/py/TVB云播.py @@ -0,0 +1,426 @@ +# -*- coding: utf-8 -*- +# by @Qist +""" +TVB云播 +""" +import re +import json +import requests +from pyquery import PyQuery as pq +from bs4 import BeautifulSoup +from base.spider import Spider # 继承基础Spider类 + + +class Spider(Spider): # 直接继承Spider基类 + def getName(self): + return 'TVB云播' + + def init(self, extend=""): + pass + + def __init__(self): + self.name = 'TVB云播' + self.host = 'http://www.tvyb03.com' + self.timeout = 25 + self.header = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + } + + # 分类映射 + self.class_names = '电影&电视剧&综艺&动漫&日韩剧&国产剧&欧美剧&港台剧'.split('&') + self.class_urls = '1&2&3&4&16&13&15&14'.split('&') + + # 过滤条件映射 + self.filter_map = { + "1": [ + {"key": "cateId", "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"}]}, + {"key": "class", "name": "剧情", "value": [{"n": "全部", "v": ""}, {"n": "喜剧", "v": "/class/喜剧"}, {"n": "爱情", "v": "/class/爱情"}, {"n": "恐怖", "v": "/class/恐怖"}, {"n": "动作", "v": "/class/动作"}, {"n": "科幻", "v": "/class/科幻"}, {"n": "剧情", "v": "/class/剧情"}, {"n": "战争", "v": "/class/战争"}, {"n": "警匪", "v": "/class/警匪"}, {"n": "犯罪", "v": "/class/犯罪"}, {"n": "动画", "v": "/class/动画"}, {"n": "奇幻", "v": "/class/奇幻"}, {"n": "武侠", "v": "/class/武侠"}, {"n": "冒险", "v": "/class/冒险"}, {"n": "枪战", "v": "/class/枪战"}, {"n": "悬疑", "v": "/class/悬疑"}, {"n": "惊悚", "v": "/class/惊悚"}, {"n": "经典", "v": "/class/经典"}, {"n": "青春", "v": "/class/青春"}, {"n": "文艺", "v": "/class/文艺"}, {"n": "微电影", "v": "/class/微电影"}, {"n": "古装", "v": "/class/古装"}, {"n": "历史", "v": "/class/历史"}, {"n": "运动", "v": "/class/运动"}, {"n": "农村", "v": "/class/农村"}, {"n": "儿童", "v": "/class/儿童"}, {"n": "网络电影", "v": "/class/网络电影"}]}, + {"key": "area", "name": "地区", "value": [{"n": "全部", "v": ""}, {"n": "大陆", "v": "/area/大陆"}, {"n": "香港", "v": "/area/香港"}, {"n": "台湾", "v": "/area/台湾"}, {"n": "美国", "v": "/area/美国"}, {"n": "法国", "v": "/area/法国"}, {"n": "英国", "v": "/area/英国"}, {"n": "日本", "v": "/area/日本"}, {"n": "韩国", "v": "/area/韩国"}, {"n": "德国", "v": "/area/Germany"}, {"n": "泰国", "v": "/area/Thailand"}, {"n": "India", "v": "/area/India"}, {"n": "Italy", "v": "/area/Italy"}, {"n": "Spain", "v": "/area/Spain"}, {"n": "Canada", "v": "/area/Canada"}, {"n": "Other", "v": "/area/Other"}]}, + {"key": "lang", "name": "语言", "value": [{"n": "全部", "v": ""}, {"n": "国语", "v": "/lang/国语"}, {"n": "英语", "v": "/lang/英语"}, {"n": "粤语", "v": "/lang/粤语"}, {"n": "韩语", "v": "/lang/韩语"}, {"n": "日语", "v": "/lang/日语"}, {"n": "法语", "v": "/lang/法语"}, {"n": "德语", "v": "/lang/德语"}, {"n": "其它", "v": "/lang/其它"}]}, + {"key": "year", "name": "年份", "value": [{"n": "全部", "v": ""}, {"n": "2026", "v": "/year/2026"}, {"n": "2025", "v": "/year/2025"}, {"n": "2024", "v": "/year/2024"}, {"n": "2023", "v": "/year/2023"}, {"n": "2022", "v": "/year/2022"}, {"n": "2021", "v": "/year/2021"}, {"n": "2020", "v": "/year/2020"}, {"n": "2019", "v": "/year/2019"}, {"n": "2018", "v": "/year/2018"}, {"n": "2017", "v": "/year/2017"}, {"n": "2016", "v": "/year/2016"}, {"n": "2015", "v": "/year/2015"}, {"n": "2014", "v": "/year/2014"}, {"n": "2013", "v": "/year/2013"}, {"n": "2012", "v": "/year/2012"}, {"n": "2011", "v": "/year/2011"}, {"n": "2010", "v": "/year/2010"}, {"n": "2009", "v": "/year/2009"}, {"n": "2008", "v": "/year/2008"}, {"n": "2007", "v": "/year/2007"}, {"n": "2006", "v": "/year/2006"}, {"n": "2005", "v": "/year/2005"}, {"n": "2004", "v": "/year/2004"}]}, + {"key": "by", "name": "排序", "value": [{"n": "全部", "v": ""}, {"n": "时间", "v": "/by/time"}, {"n": "人气", "v": "/by/hits"}, {"n": "评分", "v": "/by/score"}]} + ], + "2": [ + {"key": "cateId", "name": "类型", "value": [{"n": "全部", "v": "2"}, {"n": "国产剧", "v": "13"}, {"n": "港台剧", "v": "14"}, {"n": "日韩剧", "v": "15"}, {"n": "欧美剧", "v": "16"}]}, + {"key": "class", "name": "剧情", "value": [{"n": "全部", "v": ""}, {"n": "古装", "v": "/class/古装"}, {"n": "青春", "v": "/class/青春"}, {"n": "偶像", "v": "/class/偶像"}, {"n": "喜剧", "v": "/class/喜剧"}, {"n": "家庭", "v": "/class/家庭"}, {"n": "犯罪", "v": "/class/犯罪"}, {"n": "动作", "v": "/class/动作"}, {"n": "奇幻", "v": "/class/奇幻"}, {"n": "剧情", "v": "/class/剧情"}, {"n": "历史", "v": "/class/历史"}, {"n": "经典", "v": "/class/经典"}, {"n": "乡村", "v": "/class/乡村"}, {"n": "情景", "v": "/class/情景"}, {"n": "商战", "v": "/class/商战"}, {"n": "网剧", "v": "/class/网剧"}, {"n": "其他", "v": "/class/其他"}]}, + {"key": "area", "name": "地区", "value": [{"n": "全部", "v": ""}, {"n": "内地", "v": "/area/内地"}, {"n": "韩国", "v": "/area/韩国"}, {"n": "香港", "v": "/area/香港"}, {"n": "台湾", "v": "/area/台湾"}, {"n": "日本", "v": "/area/日本"}, {"n": "美国", "v": "/area/美国"}, {"n": "泰国", "v": "/area/泰国"}, {"n": "英国", "v": "/area/英国"}, {"n": "Singapore", "v": "/area/Singapore"}, {"n": "Other", "v": "/area/Other"}]}, + {"key": "year", "name": "年份", "value": [{"n": "全部", "v": ""}, {"n": "2026", "v": "/year/2026"}, {"n": "2025", "v": "/year/2025"}, {"n": "2024", "v": "/year/2024"}, {"n": "2023", "v": "/year/2023"}, {"n": "2022", "v": "/year/2022"}, {"n": "2021", "v": "/year/2021"}, {"n": "2020", "v": "/year/2020"}, {"n": "2019", "v": "/year/2019"}, {"n": "2018", "v": "/year/2018"}, {"n": "2017", "v": "/year/2017"}, {"n": "2016", "v": "/year/2016"}, {"n": "2015", "v": "/year/2015"}, {"n": "2014", "v": "/year/2014"}, {"n": "2013", "v": "/year/2013"}, {"n": "2012", "v": "/year/2012"}, {"n": "2011", "v": "/year/2011"}, {"n": "2010", "v": "/year/2010"}, {"n": "2009", "v": "/year/2009"}, {"n": "2008", "v": "/year/2008"}, {"n": "2007", "v": "/year/2007"}, {"n": "2006", "v": "/year/2006"}, {"n": "2005", "v": "/year/2005"}, {"n": "2004", "v": "/year/2004"}]}, + {"key": "lang", "name": "语言", "value": [{"n": "全部", "v": ""}, {"n": "国语", "v": "/lang/国语"}, {"n": "英语", "v": "/lang/英语"}, {"n": "粤语", "v": "/lang/粤语"}, {"n": "韩语", "v": "/lang/韩语"}, {"n": "日语", "v": "/lang/日语"}, {"n": "其它", "v": "/lang/其它"}]}, + {"key": "by", "name": "排序", "value": [{"n": "全部", "v": ""}, {"n": "时间", "v": "/by/time"}, {"n": "人气", "v": "/by/hits"}, {"n": "评分", "v": "/by/score"}]} + ], + "3": [ + {"key": "class", "name": "剧情", "value": [{"n": "全部", "v": ""}, {"n": "选秀", "v": "/class/选秀"}, {"n": "情感", "v": "/class/情感"}, {"n": "访谈", "v": "/class/访谈"}, {"n": "播报", "v": "/class/播报"}, {"n": "旅游", "v": "/class/旅游"}, {"n": "音乐", "v": "/class/音乐"}, {"n": "美食", "v": "/class/美食"}, {"n": "纪实", "v": "/class/纪实"}, {"n": "曲艺", "v": "/class/曲艺"}, {"n": "生活", "v": "/class/生活"}, {"n": "游戏互动", "v": "/class/游戏互动"}, {"n": "财经", "v": "/class/财经"}, {"n": "求职", "v": "/class/求职"}]}, + {"key": "area", "name": "地区", "value": [{"n": "全部", "v": ""}, {"n": "内地", "v": "/area/内地"}, {"n": "港台", "v": "/area/港台"}, {"n": "日韩", "v": "/area/日韩"}, {"n": "欧美", "v": "/area/欧美"}]}, + {"key": "lang", "name": "语言", "value": [{"n": "全部", "v": ""}, {"n": "国语", "v": "/lang/国语"}, {"n": "英语", "v": "/lang/英语"}, {"n": "粤语", "v": "/lang/粤语"}, {"n": "韩语", "v": "/lang/韩语"}, {"n": "日语", "v": "/lang/日语"}, {"n": "其它", "v": "/lang/其它"}]}, + {"key": "year", "name": "年份", "value": [{"n": "全部", "v": ""}, {"n": "2026", "v": "/year/2026"}, {"n": "2025", "v": "/year/2025"}, {"n": "2024", "v": "/year/2024"}, {"n": "2023", "v": "/year/2023"}, {"n": "2022", "v": "/year/2022"}, {"n": "2021", "v": "/year/2021"}, {"n": "2020", "v": "/year/2020"}, {"n": "2019", "v": "/year/2019"}, {"n": "2018", "v": "/year/2018"}, {"n": "2017", "v": "/year/2017"}, {"n": "2016", "v": "/year/2016"}, {"n": "2015", "v": "/year/2015"}, {"n": "2014", "v": "/year/2014"}, {"n": "2013", "v": "/year/2013"}, {"n": "2012", "v": "/year/2012"}, {"n": "2011", "v": "/year/2011"}, {"n": "2010", "v": "/year/2010"}, {"n": "2009", "v": "/year/2009"}, {"n": "2008", "v": "/year/2008"}, {"n": "2007", "v": "/year/2007"}, {"n": "2006", "v": "/year/2006"}, {"n": "2005", "v": "/year/2005"}, {"n": "2004", "v": "/year/2004"}]}, + {"key": "by", "name": "排序", "value": [{"n": "全部", "v": ""}, {"n": "时间", "v": "/by/time"}, {"n": "人气", "v": "/by/hits"}, {"n": "评分", "v": "/by/score"}]} + ], + "4": [ + {"key": "class", "name": "剧情", "value": [{"n": "全部", "v": ""}, {"n": "情感", "v": "/class/情感"}, {"n": "科幻", "v": "/class/科幻"}, {"n": "热血", "v": "/class/热血"}, {"n": "推理", "v": "/class/推理"}, {"n": "搞笑", "v": "/class/搞笑"}, {"n": "冒险", "v": "/class/冒险"}, {"n": "萝莉", "v": "/class/萝莉"}, {"n": "校园", "v": "/class/校园"}, {"n": "动作", "v": "/class/动作"}, {"n": "机战", "v": "/class/机战"}, {"n": "运动", "v": "/class/运动"}, {"n": "战争", "v": "/class/战争"}, {"n": "少年", "v": "/class/少年"}, {"n": "少女", "v": "/class/少女"}, {"n": "社会", "v": "/class/社会"}, {"n": "原创", "v": "/class/原创"}, {"n": "亲子", "v": "/class/亲子"}, {"n": "益智", "v": "/class/益智"}, {"n": "励志", "v": "/class/励志"}, {"n": "其它", "v": "/class/其他"}]}, + {"key": "area", "name": "地区", "value": [{"n": "全部", "v": ""}, {"n": "国产", "v": "/area/国产"}, {"n": "日本", "v": "/area/日本"}, {"n": "欧美", "v": "/area/欧美"}, {"n": "Other", "v": "/area/Other"}]}, + {"key": "lang", "name": "语言", "value": [{"n": "全部", "v": ""}, {"n": "国语", "v": "/lang/国语"}, {"n": "英语", "v": "/lang/英语"}, {"n": "粤语", "v": "/lang/粤语"}, {"n": "韩语", "v": "/lang/韩语"}, {"n": "日语", "v": "/lang/日语"}, {"n": "其它", "v": "/lang/其它"}]}, + {"key": "year", "name": "年份", "value": [{"n": "全部", "v": ""}, {"n": "2026", "v": "/year/2026"}, {"n": "2025", "v": "/year/2025"}, {"n": "2024", "v": "/year/2024"}, {"n": "2023", "v": "/year/2023"}, {"n": "2022", "v": "/year/2022"}, {"n": "2021", "v": "/year/2021"}, {"n": "2020", "v": "/year/2020"}, {"n": "2019", "v": "/year/2019"}, {"n": "2018", "v": "/year/2018"}, {"n": "2017", "v": "/year/2017"}, {"n": "2016", "v": "/year/2016"}, {"n": "2015", "v": "/year/2015"}, {"n": "2014", "v": "/year/2014"}, {"n": "2013", "v": "/year/2013"}, {"n": "2012", "v": "/year/2012"}, {"n": "2011", "v": "/year/2011"}, {"n": "2010", "v": "/year/2010"}, {"n": "2009", "v": "/year/2009"}, {"n": "2008", "v": "/year/2008"}, {"n": "2007", "v": "/year/2007"}, {"n": "2006", "v": "/year/2006"}, {"n": "2005", "v": "/year/2005"}, {"n": "2004", "v": "/year/2004"}]}, + {"key": "by", "name": "排序", "value": [{"n": "全部", "v": ""}, {"n": "时间", "v": "/by/time"}, {"n": "人气", "v": "/by/hits"}, {"n": "评分", "v": "/by/score"}]} + ] + } + + def homeContent(self, filter): + """ + 获取首页内容 + """ + try: + result = {'class': [], 'list': []} + + # 添加分类 + for name, cid in zip(self.class_names, self.class_urls): + result['class'].append({ + 'type_name': name, + 'type_id': cid + }) + + # 如果启用了筛选功能,添加筛选条件 + if filter: + result['filters'] = {} + for cid in self.class_urls: + result['filters'][cid] = self.get_filter_data(cid) + + # 获取推荐内容 + url = f"{self.host}/index.php/vod/show/id/1.html" + response = self.fetch(url) + if response: + data = pq(response) + + # 使用推荐选择器: ul.myui-vodlist;li;*;*;*;* + ul_items = data('ul.myui-vodlist li') + for item in ul_items.items(): + title = item('a:first').attr('title') or '' + pic = item('.lazyload').attr('data-original') or '' + if pic and not pic.startswith('http'): + pic = self.host + pic + href = item('a:first').attr('href') or '' + if href and not href.startswith('http'): + href = self.host + href + remarks = item('.tag').text() or '' + + if title and href: + result['list'].append({ + 'vod_id': self.get_id_from_href(href), + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': remarks + }) + + return result + except Exception as e: + print(f"Error in homeContent: {str(e)}") + import traceback + traceback.print_exc() + return {'class': [], 'list': []} + + def get_filter_data(self, tid): + """ + 获取筛选数据,与JS规则一致,动态从页面获取筛选条件 + """ + try: + # 根据JS中的filter_map直接返回对应的筛选条件 + # 因为JS规则中是预定义的筛选条件,所以我们根据分类ID返回对应的预定义条件 + if tid in self.filter_map: + return self.filter_map[tid] + elif tid == '13': # 国产剧 + return self.filter_map["2"] + elif tid == '14': # 港台剧 + return self.filter_map["2"] + elif tid == '15': # 日韩剧 + return self.filter_map["2"] + elif tid == '16': # 欧美剧 + return self.filter_map["2"] + else: + # 默认返回电影的筛选条件 + return self.filter_map["1"] + except Exception as e: + print(f"Error in get_filter_data: {str(e)}") + return [] + + def categoryContent(self, tid, pg, filter, extend): + """ + 获取分类内容 + 对应JS中的url: 'vod/show/id/fyfilter.html' + """ + try: + # 构建过滤URL + filter_str = "" + if extend: + for key, value in extend.items(): + if value: + filter_str += value + + url = f"{self.host}/index.php/vod/show/id/{tid}{filter_str}/page/{pg}.html" + response = self.fetch(url) + + if not response: + return {'list': [], 'page': pg, 'pagecount': 0, 'limit': 0, 'total': 0} + + data = pq(response) + videos = [] + + # 使用一级选择器: .myui-vodlist__box;a&&title;.lazyload&&data-original;.tag&&Text;a&&href + items = data('.myui-vodlist__box, ul.myui-vodlist li') + for item in items.items(): + title = item('a').attr('title') or item('a').text() + pic = item('.lazyload').attr('data-original') + if pic and not pic.startswith('http'): + pic = self.host + pic + remarks = item('.tag').text() or item('.pic-text').text() or '' + href = item('a').attr('href') + if href and not href.startswith('http'): + href = self.host + href + + if title and href: + # 检查是否已有相同ID的视频,避免重复 + vid = self.get_id_from_href(href) + if not any(v['vod_id'] == vid for v in videos): + videos.append({ + 'vod_id': vid, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': remarks + }) + + return { + 'list': videos, + 'page': int(pg), + 'pagecount': 999, # 假设很多页 + 'limit': len(videos), + 'total': 999999 + } + except Exception as e: + print(f"Error in categoryContent: {str(e)}") + import traceback + traceback.print_exc() + return {'list': [], 'page': pg, 'pagecount': 0, 'limit': 0, 'total': 0} + + def detailContent(self, ids): + """ + 获取详情内容 + 对应JS中的二级规则 + """ + try: + if not ids or not ids[0]: + return {'list': []} + + vid = ids[0] + url = f"{self.host}/index.php/vod/detail/id/{vid}.html" + response = self.fetch(url) + + if not response: + return {'list': []} + + data = pq(response) + + # 解析详情页 + vod = { + 'vod_id': vid, + 'vod_name': data('h1:first').text() or data('.data a:first').text(), + 'vod_pic': '', + 'vod_remarks': '', + 'vod_year': '', + 'vod_area': '', + 'vod_director': '', + 'vod_actor': '', + 'vod_content': '' + } + + # 图片 + img = data('.lazyload').attr('data-original') + if img: + if not img.startswith('http'): + img = self.host + img + vod['vod_pic'] = img + + # 描述信息 + data_items = data('.data').items() + data_list = list(data_items) + if len(data_list) >= 1: + # 提取各种信息 + data_text = data('.data').text() + # 年份、地区、导演、演员等信息通常在.data内 + # 根据JS规则: ";.data:eq(0) a:eq(2)&&Text;.data:eq(0) a:eq(1)&&Text;.data:eq(2)&&Text;.data:eq(3)&&Text" + if len(data_list) > 0: + links = data_list[0]('a') + if len(links) > 2: + # 假设第3个链接是年代,第2个是地区,第1个是演员(反向对应) + if len(links) > 2: + vod['vod_year'] = links.eq(2).text() + if len(links) > 1: + vod['vod_area'] = links.eq(1).text() + if len(links) > 0: + vod['vod_actor'] = links.eq(0).text() + + # 内容描述 + content_elem = data('.text-collapse span') + if content_elem: + vod['vod_content'] = content_elem.text() + + + # 处理播放线路和播放列表,确保每个线路分开 + play_from = [] + play_urls = [] + + # 查找所有播放源标签 + tabs = data('.myui-panel__head h3') + for tab in tabs.items(): + tab_text = tab.text().strip() + if tab_text and any(keyword in tab_text.lower() for keyword in ['播放', '线路', '云播', '在线', '资源', '高清', '极速', '备用', '专享', '秒播']): + play_from.append(tab_text) + + # 获取所有播放列表 + all_lists = data('.myui-content__list') + for idx in range(len(play_from)): + urls = [] + list_items = all_lists.eq(idx)('li') if idx < len(all_lists) else [] + for item in list_items.items(): + a = item('a') + href = a.attr('href') + if href and not href.startswith('http'): + href = self.host + href + title = a.text().strip() + if title and href: + urls.append(f"{title}${href}") + play_urls.append('#'.join(urls)) + + # 如果没有找到任何播放线路,尝试获取所有列表作为一个播放线路 + if not play_from or not any(play_urls): + urls = [] + list_items = data('.myui-content__list:first li') + for item in list_items.items(): + a = item('a') + href = a.attr('href') + if href and not href.startswith('http'): + href = self.host + href + title = a.text().strip() + if title and href: + urls.append(f"{title}${href}") + if urls: + play_from = ['播放源'] + play_urls = ['#'.join(urls)] + + # 补齐长度,确保一一对应 + if len(play_urls) < len(play_from): + while len(play_urls) < len(play_from): + play_urls.append('') + elif len(play_from) < len(play_urls): + play_urls = play_urls[:len(play_from)] + + # 过滤掉空线路和空地址,确保前端能正确识别独立线路 + play_from_clean = [] + play_urls_clean = [] + for f, u in zip(play_from, play_urls): + if f.strip() and u.strip(): + play_from_clean.append(f.strip()) + play_urls_clean.append(u.strip()) + if play_from_clean and play_urls_clean: + vod['vod_play_from'] = '$$$'.join(play_from_clean) + vod['vod_play_url'] = '$$$'.join(play_urls_clean) + else: + vod['vod_play_from'] = 'TVB云播' + vod['vod_play_url'] = '播放$' + url + + return {"list": [vod]} + except Exception as e: + print(f"Error in detailContent: {str(e)}") + import traceback + traceback.print_exc() + return {"list": []} + + def searchContent(self, key, quick, pg="1"): + """ + 搜索内容 + """ + try: + url = f"{self.host}/index.php/vod/search.html?wd={key}&submit=" + response = self.fetch(url) + + if not response: + return {'list': []} + + data = pq(response) + videos = [] + + # 使用搜索选择器: 'ul.myui-vodlist__media li;*;*;*;*' + items = data('ul.myui-vodlist__media li, ul.myui-vodlist li') + for item in items.items(): + title = item('a:first').attr('title') or item('a:first').text() + pic = item('.lazyload').attr('data-original') + if pic and not pic.startswith('http'): + pic = self.host + pic + href = item('a:first').attr('href') + if href and not href.startswith('http'): + href = self.host + href + remarks = item('.tag').text() or item('.pic-text').text() or '' + + if title and href: + # 检查是否已有相同ID的视频,避免重复 + vid = self.get_id_from_href(href) + if not any(v['vod_id'] == vid for v in videos): + videos.append({ + 'vod_id': vid, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': remarks + }) + + return {'list': videos} + except Exception as e: + print(f"Error in searchContent: {str(e)}") + import traceback + traceback.print_exc() + return {'list': []} + + def playerContent(self, flag, id, vipFlags): + """ + 获取播放URL + """ + try: + # 直接返回播放页URL,让播放器自行处理 + return { + 'parse': 1, + 'url': id, + 'header': self.header, + 'playUrl': '' + } + except Exception as e: + print(f"Error in playerContent: {str(e)}") + return { + 'parse': 0, + 'url': '', + 'header': {}, + 'playUrl': '' + } + + def fetch(self, url): + """ + 发送HTTP请求 + """ + try: + response = requests.get(url, headers=self.header, timeout=self.timeout) + response.encoding = 'utf-8' + if response.status_code == 200: + return response.text + return None + except Exception as e: + print(f"Error fetching {url}: {str(e)}") + return None + + def get_id_from_href(self, href): + """ + 从链接中提取ID + """ + # 例如从 /index.php/vod/detail/id/123.html 提取 123 + match = re.search(r'/id/(\d+)', href) + if match: + return match.group(1) + # 或者从 /vod-detail-id-123.html 提取 123 + match = re.search(r'-id-(\d+)', href) + if match: + return match.group(1) + return href \ No newline at end of file diff --git a/py/闪雷.py b/py/闪雷.py new file mode 100644 index 00000000..15930995 --- /dev/null +++ b/py/闪雷.py @@ -0,0 +1,331 @@ +# -*- coding: utf-8 -*- +# by @Qist +import re +import sys +import json +import time +from pyquery import PyQuery as pq +from base.spider import Spider +import requests +from bs4 import BeautifulSoup + + +class Spider(Spider): + def getName(self): + return "闪雷影视" + + def init(self, extend=""): + pass + + host = 'http://60.6.229.145:88' + ip = '60.6.229.145' + header = { + 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; SM-G960F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36', + } + encoding = 'gb2312' + + class_names = '电视剧&大陆地区&港台地区&日韩地区&欧美地区&其他地区&动作片&喜剧片&恐怖片&科幻片&战争片&动画片&爱情片&综艺片&剧情片&MTV'.split('&') + class_urls = '10&20&21&22&23&24&1&2&3&4&5&6&7&8&9&12'.split('&') + + def homeContent(self, filter): + """ + 获取首页内容 + """ + try: + result = {'class': [], 'list': []} + for name, cid in zip(self.class_names, self.class_urls): + result['class'].append({'type_name': name, 'type_id': cid}) + + # 获取首页推荐影片 + url = f"{self.host}/jdl/List.asp?ClassId=10" + resp = self.fetch(url, headers=self.header) + data = self.getpq(resp.text) + + videos = [] + # 使用更兼容的选择器 - 先选所有的dl,然后过滤有classid=h4的dd的dl + all_dls = data('dl') + for dl in all_dls.items(): + h4_dd = dl('dd[classid="h4"]') + if h4_dd.length > 0: + title = h4_dd('a').text() + pic = dl('dt img').attr('src') + if pic and pic.startswith('../'): + pic = self.host + '/' + pic.replace('../', '') + elif pic and not pic.startswith('http'): + pic = self.host + '/' + pic.lstrip('/') + + href = h4_dd('a').attr('href') + if href: + # 从href中提取ClassId + vid_match = re.search(r'[Cc]lass[Ii][Dd]=(\d+)', href) + vid = vid_match.group(1) if vid_match else href + videos.append({ + 'vod_id': vid, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': '' + }) + + result['list'] = videos[:10] # 只取前10个 + return result + except Exception as e: + print(f"Error in homeContent: {str(e)}") + import traceback + traceback.print_exc() + return {"class": [], "list": []} + + def categoryContent(self, tid, pg, filter, extend): + """ + 获取分类内容 + """ + try: + url = f"{self.host}/jdl/List.asp?ClassId={tid}&searchword=&page={pg}" + resp = self.fetch(url, headers=self.header) + data = self.getpq(resp.text) + + result = {} + videos = [] + + # 使用相同的选择器逻辑 + all_dls = data('dl') + for dl in all_dls.items(): + h4_dd = dl('dd[classid="h4"]') + if h4_dd.length > 0: + title = h4_dd('a').text() + pic = dl('dt img').attr('src') + if pic and pic.startswith('../'): + pic = self.host + '/' + pic.replace('../', '') + elif pic and not pic.startswith('http'): + pic = self.host + '/' + pic.lstrip('/') + + href = h4_dd('a').attr('href') + if href: + # 从href中提取ClassId + vid_match = re.search(r'[Cc]lass[Ii][Dd]=(\d+)', href) + vid = vid_match.group(1) if vid_match else href + videos.append({ + 'vod_id': vid, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': '' + }) + + result['list'] = videos + result['page'] = int(pg) + result['pagecount'] = 999 # 假设有很多页 + result['limit'] = len(videos) + result['total'] = 999999 + return result + except Exception as e: + print(f"Error in categoryContent: {str(e)}") + import traceback + traceback.print_exc() + return {"list": [], "page": 1, "pagecount": 1, "limit": 0, "total": 0} + + def detailContent(self, ids): + """ + 获取影片详情 + """ + try: + url = f"{self.host}/jdl/movie.asp?ClassId={ids[0]}" + resp = self.fetch(url, headers=self.header) + data = self.getpq(resp.text) + + vod = { + 'vod_id': ids[0], + 'vod_name': data('li[classid="h4"]').text(), + 'vod_pic': '', + 'vod_remarks': '', + 'vod_year': '', + 'vod_area': '', + 'vod_director': '', + 'vod_actor': '', + 'vod_content': '' + } + + # 获取封面 + cover_img = data('.intro .img img').attr('src') + if cover_img and cover_img.startswith('../'): + vod['vod_pic'] = f"{self.host}/{cover_img[3:]}" + elif cover_img and not cover_img.startswith('http'): + vod['vod_pic'] = f"{self.host}/{cover_img}" + else: + vod['vod_pic'] = cover_img + + # 获取演员信息 + actor_info = data('li:contains("主  演")').text() + if actor_info: + vod['vod_actor'] = actor_info.replace('主  演:', '').strip() + + # 获取内容信息 + content_parts = [] + selectors = ['li:contains("状  态")', 'li:contains("类  型")', 'li:contains("拍摄地区")', 'li:contains("更新时间")', 'li:contains("单集时长")'] + for sel in selectors: + part = data(sel).text() + if part: + content_parts.append(part) + vod['vod_content'] = '\n'.join(content_parts) + + # 获取播放列表 + play_urls = [] + # 根据j.js中的规则: div.listt a + for a in data('div.listt a').items(): + title = a.text() + href = a.attr('href') + if title and href: + # 从href构造播放地址 + play_link = f"{self.host}{href}" + play_urls.append(f"{title}${play_link}") + + vod['vod_play_from'] = '闪雷影视' + vod['vod_play_url'] = '#'.join(play_urls) if play_urls else '无播放源' + + result = {"list": [vod]} + return result + except Exception as e: + print(f"Error in detailContent: {str(e)}") + import traceback + traceback.print_exc() + return {"list": []} + + def searchContent(self, key, quick, pg="1"): + """ + 搜索内容 + """ + try: + url = f"{self.host}/jdl/List.asp?ClassId=30&type=&searchword={key}&page={pg}" + resp = self.fetch(url, headers=self.header) + data = self.getpq(resp.text) + + videos = [] + # 使用相同的选择器逻辑 + all_dls = data('dl') + for dl in all_dls.items(): + h4_dd = dl('dd[classid="h4"]') + if h4_dd.length > 0: + title = h4_dd('a').text() + pic = dl('dt img').attr('src') + if pic and pic.startswith('../'): + pic = self.host + '/' + pic.replace('../', '') + elif pic and not pic.startswith('http'): + pic = self.host + '/' + pic.lstrip('/') + + href = h4_dd('a').attr('href') + if href: + # 从href中提取ClassId + vid_match = re.search(r'[Cc]lass[Ii][Dd]=(\d+)', href) + vid = vid_match.group(1) if vid_match else href + videos.append({ + 'vod_id': vid, + 'vod_name': title, + 'vod_pic': pic, + 'vod_remarks': '' + }) + + return {'list': videos, 'page': pg} + except Exception as e: + print(f"Error in searchContent: {str(e)}") + import traceback + traceback.print_exc() + return {'list': [], 'page': pg} + + def playerContent(self, flag, id, vipFlags): + """ + 播放内容 + """ + try: + # id 现在是播放页面的完整URL,格式类似: ClassId,xx,xx,movNo + if ',' in id: + parts = id.split(',') + classid = parts[2] + movno = parts[3] + play_url = f"{self.host}/PlayMov.asp?ClassId={classid}&video=2&exe=0&down=0&movNo={movno}&vgver=undefined&ClientIP={self.ip}" + else: + play_url = id # 已经是完整的播放页面URL + + # 获取播放页面内容 + resp = self.fetch(play_url, headers=self.header) + html = resp.text + + # 根据j.js中的lazy规则进行解析 + # var url = request(html).match(/videoarr\.push\('(.*?)'/)[1] + video_match = re.search(r"videoarr\.push\(['\"](.*?)['\"]\)", html) + if video_match: + video_url = video_match.group(1) + # 处理URL,替换域名部分 + # url = url.replace(/https?:\/\/(?:[\d.]+|[\w\-]+)(?::\d+)?\//, rule.host + '/') + video_url = re.sub(r'https?://(?:[\d.]+|[\w\-]+)(?::\d+)?/', f'{self.host}/', video_url) + + result = { + "parse": 0, + "url": video_url, + "header": self.header, + "playUrl": "" + } + return result + else: + print(f"Warning: Could not extract video URL from {play_url}") + # 尝试寻找其他可能的视频URL + # 匹配 player.open("...") 调用 + js_open_matches = re.findall(r'player\.open\s*\(\s*["\']([^"\']+)["\']\s*\)', html) + if js_open_matches: + video_url = js_open_matches[0] + if not video_url.startswith('http'): + video_url = self.host + '/' + video_url.lstrip('/') + result = { + "parse": 0, + "url": video_url, + "header": self.header, + "playUrl": "" + } + return result + + # 如果还是找不到,返回错误 + return { + "parse": 1, + "url": play_url + } + except Exception as e: + print(f"Error in playerContent: {str(e)}") + import traceback + traceback.print_exc() + return { + "parse": 1, + "url": id + } + + def getpq(self, data): + try: + return pq(data) + except Exception as e: + print(f"Error parsing data: {str(e)}") + return pq(data.encode('utf-8')) + + def fetch(self, url, headers=None): + """ + 发送HTTP请求 + """ + session = requests.Session() + if headers: + session.headers.update(headers) + else: + session.headers.update(self.header) + + # 增加超时时间以适应响应较慢的服务 + response = session.get(url, timeout=25) + response.encoding = self.encoding + return response + + def post(self, url, headers=None, data=None): + """ + 发送POST请求 + """ + session = requests.Session() + if headers: + session.headers.update(headers) + else: + session.headers.update(self.header) + + response = session.post(url, data=data, timeout=25) + response.encoding = self.encoding + return response \ No newline at end of file