From 9747d453e6495bd67e0be4b6f205f5f3070cf79f Mon Sep 17 00:00:00 2001 From: qist <87984115@qq.com> Date: Thu, 21 May 2026 17:07:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0py=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- py/libvio.py | 513 +++++++++++++++++++++++++++++++++++++++++++++ tools/dianshi.json | 3 +- tools/jsm.json | 3 +- 3 files changed, 517 insertions(+), 2 deletions(-) create mode 100644 py/libvio.py diff --git a/py/libvio.py b/py/libvio.py new file mode 100644 index 00000000..b2537f04 --- /dev/null +++ b/py/libvio.py @@ -0,0 +1,513 @@ +# -*- coding: utf-8 -*- +# by @Qist +""" +LibVio (libvio.run) - 免费在线观影 +""" +import re +import json +import requests +from urllib.parse import quote +from base.spider import Spider + + +class Spider(Spider): + def getName(self): + return "LibVio" + + def init(self, extend=""): + self.host = 'https://www.libvio.run' + self.header = { + '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': self.host + '/', + 'Origin': self.host, + } + self.timeout = 20 + self.session = requests.Session() + self.session.headers.update(self.header) + + def _safe_get(self, url, max_retries=3): + for attempt in range(max_retries): + try: + resp = self.session.get(url, timeout=self.timeout, verify=False) + resp.encoding = 'utf-8' + return resp + except: + import time + time.sleep(1) + return None + + def _parse_videos(self, html): + videos = [] + seen = set() + pattern = re.compile( + r'href="/detail/(\d+)\.html"[^>]+title="([^"]*)"[^>]+data-original="([^"]*)"[^>]*>.*?' + r'([^<]*).*?' + r'([^<]*)', + re.DOTALL + ) + for m in pattern.finditer(html): + vid = m.group(1) + if vid in seen: + continue + seen.add(vid) + vod_name = m.group(2) + vod_pic = m.group(3) + vod_remarks = m.group(4).strip() + score = m.group(5).strip() + if score and score != '0.0': + vod_remarks = vod_remarks + '/' + score if vod_remarks else score + videos.append({ + 'vod_id': vid, + 'vod_name': vod_name, + 'vod_pic': vod_pic, + 'vod_remarks': vod_remarks, + }) + return videos + + def isVideoFormat(self, url): + pass + + def manualVideoCheck(self): + pass + + def destroy(self): + pass + + def homeContent(self, filter): + try: + result = {'class': [], 'list': []} + classes = [ + {'type_name': '电影', 'type_id': '1'}, + {'type_name': '剧集', 'type_id': '2'}, + {'type_name': '国剧', 'type_id': '13'}, + {'type_name': '动作片', 'type_id': '6'}, + {'type_name': '喜剧片', 'type_id': '7'}, + {'type_name': '爱情片', 'type_id': '8'}, + {'type_name': '科幻片', 'type_id': '9'}, + {'type_name': '恐怖片', 'type_id': '10'}, + {'type_name': '剧情片', 'type_id': '11'}, + {'type_name': '战争片', 'type_id': '12'}, + {'type_name': '动画片', 'type_id': '23'}, + {'type_name': '纪录片', 'type_id': '21'}, + {'type_name': '港台剧', 'type_id': '14'}, + {'type_name': '日韩剧', 'type_id': '15'}, + {'type_name': '欧美剧', 'type_id': '16'}, + {'type_name': '泰国剧', 'type_id': '24'}, + {'type_name': '综艺', 'type_id': '3'}, + {'type_name': '番剧', 'type_id': '4'}, + ] + result['class'] = classes + + # 获取首页推荐视频 + resp = self._safe_get(self.host + '/') + if resp and resp.status_code == 200: + result['list'] = self._parse_videos(resp.text)[:10] + + filter_cfg = { + '1': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, + {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "泰国", "v": "泰国"}, + {"n": "英国", "v": "英国"}, {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '6': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, + {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '7': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, + {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '8': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, + {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '9': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, + {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '10': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, + {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '11': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, + {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '12': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, + {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '23': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, + {"n": "美国", "v": "美国"}, {"n": "日本", "v": "日本"}, + {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '21': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, + {"n": "美国", "v": "美国"}, {"n": "日本", "v": "日本"}, + {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '2': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国台湾", "v": "中国台湾"}, {"n": "中国香港", "v": "中国香港"}, + {"n": "韩国", "v": "韩国"}, {"n": "日本", "v": "日本"}, + {"n": "美国", "v": "美国"}, {"n": "泰国", "v": "泰国"}, + {"n": "英国", "v": "英国"}, {"n": "新加坡", "v": "新加坡"}, + {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '13': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '14': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国香港", "v": "中国香港"}, + {"n": "中国台湾", "v": "中国台湾"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '15': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '16': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "美国", "v": "美国"}, + {"n": "英国", "v": "英国"}, {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '24': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "泰国", "v": "泰国"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '3': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "中国香港", "v": "中国香港"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "美国", "v": "美国"}, + {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + '4': [ + {"key": "area", "name": "地区", "value": [ + {"n": "全部", "v": ""}, {"n": "中国大陆", "v": "中国大陆"}, + {"n": "日本", "v": "日本"}, {"n": "美国", "v": "美国"}, + {"n": "其他", "v": "其他"}, + ]}, + {"key": "by", "name": "排序", "value": [ + {"n": "最新", "v": "time"}, {"n": "人气", "v": "hits"}, + {"n": "评分", "v": "score"}, + ]}, + ], + } + result['filters'] = filter_cfg + return result + except Exception as e: + print(f"Error in homeContent: {str(e)}") + return {"class": [], "list": []} + + def homeVideoContent(self): + try: + resp = self._safe_get(self.host + '/') + if not resp or resp.status_code != 200: + return {} + videos = self._parse_videos(resp.text) + return {'list': videos[:30]} + except Exception as e: + print(f"Error in homeVideoContent: {str(e)}") + return {} + + def categoryContent(self, tid, pg, filter, extend): + try: + url = f"{self.host}/show/{tid}--------{pg}---.html" + resp = self._safe_get(url) + if not resp or resp.status_code != 200: + return {'list': [], 'page': int(pg), 'pagecount': 1, 'limit': 0, 'total': 0} + html = resp.text + videos = self._parse_videos(html) + pagecount_match = re.search(r'(\d+)/(\d+)', html) + pagecount = int(pagecount_match.group(2)) if pagecount_match else 999 + last_page_match = re.search(r'class="page-link"[^>]*href="[^"]*-(\d+)---\.html"[^>]*>尾页', html) + if last_page_match: + pagecount = int(last_page_match.group(1)) + return { + 'list': videos, + 'page': int(pg), + 'pagecount': pagecount, + 'limit': len(videos), + 'total': pagecount * len(videos) if videos else 0, + } + except Exception as e: + print(f"Error in categoryContent: {str(e)}") + return {'list': [], 'page': int(pg), 'pagecount': 1, 'limit': 0, 'total': 0} + + def detailContent(self, ids): + try: + vid = ids[0] + url = f"{self.host}/detail/{vid}.html" + resp = self._safe_get(url) + if not resp or resp.status_code != 200: + return {'list': []} + html = resp.text + + vod_name = '' + h1_match = re.search(r'