Files
tvbox_collect/recha18/py/py_nivod_cc.py
T
2026-07-11 04:45:19 +00:00

304 lines
15 KiB
Python

# -*- coding: utf-8 -*-
# @Author : 老王叔叔 for 泥視頻.CC with Multi-Source Support
# @Time : 2025/04/06
import sys
import requests
from lxml import etree
import json
import re
from urllib.parse import urlencode
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def __init__(self):
self.home_url = 'https://www.nivod.cc'
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
"Referer": "https://www.nivod.cc/",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "keep-alive",
}
self.placeholder_pic = 'https://image.tmdb.org/t/p/w600_and_h900_bestv2/placeholder.jpg'
def init(self, extend=""):
pass
def getName(self):
return "泥視頻.CC"
def getDependence(self):
return []
def isVideoFormat(self, url):
return url.endswith('.m3u8') or url.endswith('.mp4')
def manualVideoCheck(self):
return False
def homeContent(self, filter):
categories = "電影$movie#電視劇$tv#綜藝$show#動漫$anime"
class_list = [{'type_id': v.split('$')[1], 'type_name': v.split('$')[0]} for v in categories.split('#')]
filters = {
'movie': [
{'key': 'class', 'name': '剧情', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "冒险$mao-xian#剧情$ju-qing#动作$dong-zuo#同性$tong-xing#喜剧$xi-ju#奇幻$qi-huan#恐怖$kong-bu#悬疑$xuan-yi#惊悚$jing-song#战争$zhan-zheng#歌舞$ge-wu#灾难$zai-nan#爱情$ai-qing#犯罪$fan-zui#科幻$ke-huan".split('#')]},
{'key': 'area', 'name': '地区', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "大陆$cn#香港$hk#台湾$tw#欧美$west#泰国$th#新马$sg-my#其他$other".split('#')]},
{'key': 'year', 'name': '年份', 'value': [{'n': v, 'v': v} for v in ["2025", "2024", "2023", "2022", "2021", "2020", "2019-2010", "2009-2000", "90年代", "80年代", "更早"]]}
],
'tv': [
{'key': 'class', 'name': '剧情', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "剧情$ju-qing#动作$dong-zuo#历史$li-shi#历险$mao-xian#古装$gu-zhuang#同性$tong-xing#喜剧$xi-ju#奇幻$qi-huan#家庭$jia-ting#悬疑$xuan-yi#惊悚$jing-song#战争$zhan-zheng#武侠$wu-xia#爱情$ai-qing#科幻$ke-huan#罪案$zui-an".split('#')]},
{'key': 'area', 'name': '地区', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "大陆$cn#香港$hk#台湾$tw#日本$jp#韩国$kr#欧美$west#泰国$th#新马$sg-my".split('#')]},
{'key': 'year', 'name': '年份', 'value': [{'n': v, 'v': v} for v in ["2025", "2024", "2023", "2022", "2021", "2020", "2019-2015", "2014-2010", "2009-2000", "90年代", "80年代", "更早"]]}
],
'show': [
{'key': 'class', 'name': '剧情', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "搞笑$gao-xiao#音乐$yin-yue#真人秀$zhen-ren-xiu#脱口秀$tuo-kou-xiu".split('#')]},
{'key': 'area', 'name': '地区', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "大陆$cn#韩国$kr#欧美$west#其它$other".split('#')]},
{'key': 'year', 'name': '年份', 'value': [{'n': v, 'v': v} for v in ["2025", "2024", "2023", "2022", "2021", "2020", "2019-2015", "2014-2010", "2009-2000", "90年代", "80年代", "更早"]]}
],
'anime': [
{'key': 'class', 'name': '剧情', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "冒险$mao-xian#动画电影$movie#推理$tui-li#校园$xiao-yuan#治愈$zhi-yu#泡面$pao-mian#热血$re-xue#科幻$ke-huan#魔幻$mo-huan".split('#')]},
{'key': 'area', 'name': '地区', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "大陆$cn#日本$jp#欧美$west".split('#')]},
{'key': 'year', 'name': '年份', 'value': [{'n': v, 'v': v} for v in ["2025", "2024", "2023", "2022", "2021", "2020", "2019-2015", "2014-2010", "2009-2000", "90年代", "80年代", "更早"]]}
]
}
return {'class': class_list, 'filters': filters if filter else {}}
def homeVideoContent(self):
result = {'list': []}
try:
res = requests.get(self.home_url, headers=self.headers)
res.encoding = 'utf-8'
root = etree.HTML(res.text)
data_list = root.xpath('//div[contains(@class, "qy-mod-link-wrap")]/a')
for i in data_list:
name_nodes = i.xpath('.//picture[@class="video-item-preview-img"]/img/@alt')
vod_name = name_nodes[0].strip() if name_nodes else None
if not vod_name:
vod_name = i.xpath('./@title')
vod_name = vod_name[0].strip() if vod_name else "未知"
vod_id = i.get('href', '')
pic_nodes = i.xpath('.//picture[@class="video-item-preview-img"]/img/@src')
vod_pic = pic_nodes[0] if pic_nodes else self.placeholder_pic
if vod_pic.startswith('/'):
vod_pic = self.home_url + vod_pic
remark_nodes = i.xpath('.//span[contains(@class, "qy-mod-label")]/text()')
vod_remarks = remark_nodes[0].strip() if remark_nodes else ''
result['list'].append({
'vod_id': vod_id,
'vod_name': vod_name,
'vod_pic': vod_pic,
'vod_remarks': vod_remarks
})
result['list'] = result['list'][:10]
except Exception as e:
print(f"Error in homeVideoContent: {e}")
return result
def categoryContent(self, tid, pg, filter, ext):
result = {'list': []}
_year = ext.get('year', '')
_class = ext.get('class', '')
_area = ext.get('area', '')
params = {
'channel': tid,
'region': _area,
'class': _class,
'year': _year,
'page': pg
}
url = f"{self.home_url}/filter.html?{urlencode(params)}"
# 如果頁碼超過 5,直接返回空結果
if int(pg) > 5:
result['page'] = int(pg)
result['pagecount'] = 5
result['limit'] = 0
result['total'] = 240
return result
try:
res = requests.get(url, headers=self.headers)
res.encoding = 'utf-8'
print(f"categoryContent URL: {url}")
print(f"categoryContent Response Status: {res.status_code}")
print(f"categoryContent HTML length: {len(res.text)}")
root = etree.HTML(res.text)
data_list = root.xpath('//li[contains(@class, "qy-mod-li")]')
for i in data_list:
vod_id = i.xpath('.//a/@href')[0] if i.xpath('.//a/@href') else ''
name_nodes = i.xpath('.//a/@title')
vod_name = name_nodes[0].strip() if name_nodes else "未知"
pic_nodes = i.xpath('.//div[@class="qy-mod-cover"]/@style')
vod_pic = None
if pic_nodes:
style = pic_nodes[0]
match = re.search(r'url\((.*?)\)', style)
vod_pic = match.group(1).strip() if match else None
if not vod_pic:
vod_pic = self.placeholder_pic
if vod_pic.startswith('/'):
vod_pic = self.home_url + vod_pic
remark_nodes = i.xpath('.//span[@class="qy-mod-label"]/text()')
vod_remarks = remark_nodes[0].strip() if remark_nodes else ''
result['list'].append({
'vod_id': vod_id,
'vod_name': vod_name,
'vod_pic': vod_pic,
'vod_remarks': vod_remarks
})
# 假設每頁最多 48 個項目,網站分頁上限為 5 頁
current_items = len(data_list)
total_pages = 5 # 網站分頁上限為 5
if current_items < 48: # 如果當前頁項目少於 48,假設是最後一頁
total_pages = int(pg)
total_items = (int(pg) - 1) * 48 + current_items if total_pages == int(pg) else total_pages * 48
result['page'] = int(pg)
result['pagecount'] = total_pages
result['limit'] = current_items
result['total'] = total_items
except Exception as e:
print(f"Error in categoryContent: {e}")
return result
def detailContent(self, array):
result = {'list': []}
ids = array[0]
detail_url = f"{self.home_url}{ids}"
try:
res = requests.get(detail_url, headers=self.headers)
res.encoding = 'utf-8'
root = etree.HTML(res.text)
vod_name = root.xpath('//div[@class="right-title"]/text()')[0].strip() if root.xpath('//div[@class="right-title"]') else "未知"
vod_year = root.xpath('//div[@id="postYear"]/text()')[0].strip() if root.xpath('//div[@id="postYear"]') else ""
vod_area = root.xpath('//div[@id="region"]/text()')[0].strip() if root.xpath('//div[@id="region"]') else ""
vod_content = root.xpath('//div[@id="show-desc"]/text()')[0].strip() if root.xpath('//div[@id="show-desc"]') else ""
vod_remarks = root.xpath('//div[@id="updateTxt"]/text()')[0].strip() if root.xpath('//div[@id="updateTxt"]') else ""
vod_actor = root.xpath('//div[@id="actors"]/text()')[0].strip() if root.xpath('//div[@id="actors"]') else ""
vod_director = root.xpath('//div[@id="director"]/text()')[0].strip() if root.xpath('//div[@id="director"]') else ""
vod_pic = root.xpath('//img[@class="left-img"]/@src')[0] if root.xpath('//img[@class="left-img"]') else self.placeholder_pic
if vod_pic.startswith('/'):
vod_pic = self.home_url + vod_pic
episodes = root.xpath('//div[@id="list-jj"]/a')
if not episodes:
vod = {
'vod_id': ids,
'vod_name': vod_name,
'vod_pic': vod_pic,
'type_name': '',
'vod_year': vod_year,
'vod_area': vod_area,
'vod_remarks': vod_remarks,
'vod_actor': vod_actor,
'vod_director': vod_director,
'vod_content': vod_content,
'vod_play_from': '泥視頻',
'vod_play_url': '第1集$https://www.nivod.cc/vodplay/202552243/ep1'
}
else:
play_from = set()
play_urls = {}
for ep in episodes[::-1]:
ep_name = ep.xpath('.//div[@class="item"]/text()')[0].strip() if ep.xpath('.//div[@class="item"]') else "未知"
ep_url = self.home_url + ep.get('href', '')
vod_id = ids.split('/')[2]
ep_id = ep_url.split('/')[-1]
xhr_url = f"{self.home_url}/xhr_playinfo/{vod_id}-{ep_id}"
res = requests.get(xhr_url, headers=self.headers)
res.encoding = 'utf-8'
data = res.json()
if 'pdatas' in data and data['pdatas']:
for source in data['pdatas']:
source_name = source['from']
play_from.add(source_name)
if source_name not in play_urls:
play_urls[source_name] = []
play_urls[source_name].append(f"{ep_name}${source['playurl']}")
vod_play_from = '$$$'.join(play_from)
vod_play_url = '$$$'.join(['#'.join(play_urls[source]) for source in play_from])
vod = {
'vod_id': ids,
'vod_name': vod_name,
'vod_pic': vod_pic,
'type_name': '',
'vod_year': vod_year,
'vod_area': vod_area,
'vod_remarks': vod_remarks,
'vod_actor': vod_actor,
'vod_director': vod_director,
'vod_content': vod_content,
'vod_play_from': vod_play_from,
'vod_play_url': vod_play_url
}
result['list'].append(vod)
except Exception as e:
print(f"Error in detailContent: {e}")
result['list'].append({
'vod_id': ids,
'vod_name': '未知',
'vod_pic': self.placeholder_pic,
'vod_play_from': '泥視頻',
'vod_play_url': ''
})
return result
def searchContent(self, key, quick, pg='1'):
result = {'list': []}
try:
search_url = f"{self.home_url}/search_x.html?keyword={key}&page={pg}"
res = requests.get(search_url, headers=self.headers)
res.encoding = 'utf-8'
root = etree.HTML(res.text)
data_list = root.xpath('//a[contains(@class, "qy-mod-link")]')
for item in data_list:
name_nodes = item.xpath('.//picture[@class="video-item-preview-img"]/img/@alt')
vod_name = name_nodes[0].strip() if name_nodes else None
if not vod_name:
vod_name = item.xpath('./@title')
vod_name = vod_name[0].strip() if vod_name else "未知"
vod_id = item.get('href', '')
pic_nodes = item.xpath('.//picture[@class="video-item-preview-img"]/img/@src')
vod_pic = pic_nodes[0] if pic_nodes else self.placeholder_pic
if vod_pic.startswith('/'):
vod_pic = self.home_url + vod_pic
vod_remarks = ''
result['list'].append({
'vod_id': vod_id,
'vod_name': vod_name,
'vod_pic': vod_pic,
'vod_remarks': vod_remarks
})
except Exception as e:
print(f"Error in searchContent: {e}")
return result
def playerContent(self, flag, id, vipFlags):
result = {}
try:
play_url = id.split('$')[1] if '$' in id else id
result = {
'url': play_url,
'header': json.dumps(self.headers),
'parse': 0,
'playUrl': ''
}
except Exception as e:
print(f"Error in playerContent: {e}")
result = {'url': '', 'parse': 0}
return result
def localProxy(self, param):
return [200, "video/MP2T", {}, b""]
def destroy(self):
pass