上传文件至「py」
This commit is contained in:
@@ -0,0 +1,249 @@
|
|||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import urllib.parse
|
||||||
|
from base.spider import Spider
|
||||||
|
from urllib.parse import quote, urljoin
|
||||||
|
|
||||||
|
sys.path.append('..')
|
||||||
|
|
||||||
|
xurl = "http://www.dgpengcheng.com"
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Linux; Android 13; M2102J2SC Build/TKQ1.221114.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/144.0.7559.31 Mobile Safari/537.36',
|
||||||
|
'Referer': xurl,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Spider(Spider):
|
||||||
|
global xurl, headers
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return "星辰影院"
|
||||||
|
|
||||||
|
def init(self, extend):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def isVideoFormat(self, url):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def manualVideoCheck(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def homeContent(self, filter):
|
||||||
|
classes = [
|
||||||
|
{"type_id": "1", "name": "电影"},
|
||||||
|
{"type_id": "2", "name": "电视剧"},
|
||||||
|
{"type_id": "3", "name": "综艺"},
|
||||||
|
{"type_id": "4", "name": "动漫"},
|
||||||
|
{"type_id": "28", "name": "纪录片"}
|
||||||
|
]
|
||||||
|
return {'class': classes}
|
||||||
|
|
||||||
|
def _parse_video_items(self, soup):
|
||||||
|
videos = []
|
||||||
|
seen_ids = set()
|
||||||
|
|
||||||
|
for li in soup.select('li'):
|
||||||
|
a_tag = li.select_one('a.stui-vodlist__thumb')
|
||||||
|
if not a_tag:
|
||||||
|
continue
|
||||||
|
|
||||||
|
href = a_tag.get('href', '')
|
||||||
|
if not href:
|
||||||
|
continue
|
||||||
|
vod_id = href if 'http' in href else xurl + href
|
||||||
|
if vod_id in seen_ids:
|
||||||
|
continue
|
||||||
|
seen_ids.add(vod_id)
|
||||||
|
|
||||||
|
img = a_tag.select_one('img')
|
||||||
|
title = a_tag.get('title', '')
|
||||||
|
if not title and img:
|
||||||
|
title = img.get('alt', '')
|
||||||
|
if not title:
|
||||||
|
title = a_tag.get_text(strip=True)
|
||||||
|
if not title:
|
||||||
|
continue
|
||||||
|
|
||||||
|
pic = a_tag.get('data-original', '')
|
||||||
|
if not pic and img:
|
||||||
|
pic = img.get('data-original') or img.get('src', '')
|
||||||
|
if pic and not pic.startswith('http'):
|
||||||
|
pic = xurl + ('' if pic.startswith('/') else '/') + pic
|
||||||
|
|
||||||
|
remark = ''
|
||||||
|
remark_span = a_tag.select_one('span.pic-text')
|
||||||
|
if remark_span:
|
||||||
|
remark = remark_span.get_text(strip=True)
|
||||||
|
|
||||||
|
videos.append({
|
||||||
|
"vod_id": vod_id,
|
||||||
|
"vod_name": title,
|
||||||
|
"vod_pic": pic,
|
||||||
|
"vod_remarks": remark
|
||||||
|
})
|
||||||
|
return videos
|
||||||
|
|
||||||
|
def homeVideoContent(self):
|
||||||
|
return {'list': self._parse_video_items(BeautifulSoup(requests.get(xurl, headers=headers).text, 'lxml'))}
|
||||||
|
|
||||||
|
def categoryContent(self, cid, pg, filter, ext):
|
||||||
|
page = int(pg) if pg else 1
|
||||||
|
url = f"{xurl}/vtype/{cid}-{page}.html"
|
||||||
|
videos = self._parse_video_items(BeautifulSoup(requests.get(url, headers=headers).text, 'lxml'))
|
||||||
|
return {
|
||||||
|
'list': videos,
|
||||||
|
'page': page,
|
||||||
|
'pagecount': 999,
|
||||||
|
'limit': 90,
|
||||||
|
'total': 9999
|
||||||
|
}
|
||||||
|
|
||||||
|
def detailContent(self, ids):
|
||||||
|
did = ids[0]
|
||||||
|
if not did.startswith('http'):
|
||||||
|
did = urljoin(xurl, did)
|
||||||
|
resp = requests.get(did, headers=headers, timeout=10)
|
||||||
|
soup = BeautifulSoup(resp.text, 'lxml')
|
||||||
|
info = {'vod_id': did}
|
||||||
|
|
||||||
|
thumb = soup.select_one('.stui-content__thumb img')
|
||||||
|
if thumb:
|
||||||
|
pic = thumb.get('data-original') or thumb.get('src', '')
|
||||||
|
if pic and pic.startswith('//'):
|
||||||
|
pic = 'https:' + pic
|
||||||
|
elif pic and not pic.startswith('http'):
|
||||||
|
pic = urljoin(xurl, pic)
|
||||||
|
info['vod_pic'] = pic
|
||||||
|
|
||||||
|
detail_div = soup.select_one('.stui-content')
|
||||||
|
if detail_div:
|
||||||
|
title_h1 = detail_div.select_one('h3.title')
|
||||||
|
if title_h1:
|
||||||
|
raw_title = title_h1.get_text(strip=True).replace('\n', '')
|
||||||
|
info['vod_name'] = raw_title
|
||||||
|
|
||||||
|
for p in detail_div.select('p.data'):
|
||||||
|
text = p.get_text(strip=True)
|
||||||
|
if '主演:' in text:
|
||||||
|
info['vod_actor'] = text.split('主演:')[-1].strip()
|
||||||
|
if '类型:' in text:
|
||||||
|
info['type_name'] = text.split('类型:')[-1].strip()
|
||||||
|
if '导演:' in text:
|
||||||
|
info['vod_director'] = text.split('导演:')[-1].strip()
|
||||||
|
if '状态:' in text:
|
||||||
|
info['vod_remarks'] = text.split('状态:')[-1].strip()
|
||||||
|
if '年代:' in text:
|
||||||
|
info['vod_year'] = text.split('年代:')[-1].strip()
|
||||||
|
if '地区:' in text:
|
||||||
|
info['vod_area'] = text.split('地区:')[-1].strip()
|
||||||
|
|
||||||
|
content_div = soup.select_one('.detail-content')
|
||||||
|
if content_div:
|
||||||
|
p = content_div.find('p')
|
||||||
|
if p:
|
||||||
|
text = p.get_text(strip=True)
|
||||||
|
else:
|
||||||
|
text = content_div.get_text(strip=True)
|
||||||
|
text = re.sub(r'^简介[::]\s*', '', text)
|
||||||
|
info['vod_content'] = text
|
||||||
|
else:
|
||||||
|
info['vod_content'] = ''
|
||||||
|
|
||||||
|
filter_lines = ['猜您喜欢', '同类型']
|
||||||
|
filter_titles = ['1080P', 'дрр滈凊']
|
||||||
|
ktabs = []
|
||||||
|
klists = []
|
||||||
|
seen_lines = set()
|
||||||
|
|
||||||
|
line_items = soup.select('.stui-pannel__head h3')
|
||||||
|
playlist_containers = soup.select('.stui-content__playlist')
|
||||||
|
|
||||||
|
for idx, tab in enumerate(line_items):
|
||||||
|
if idx >= len(playlist_containers):
|
||||||
|
break
|
||||||
|
line_name = tab.get_text(strip=True)
|
||||||
|
if not line_name or line_name in filter_lines:
|
||||||
|
continue
|
||||||
|
if line_name in seen_lines:
|
||||||
|
continue
|
||||||
|
container = playlist_containers[idx]
|
||||||
|
episode_links = container.select('li a')
|
||||||
|
if not episode_links:
|
||||||
|
continue
|
||||||
|
klist = []
|
||||||
|
for ep in episode_links:
|
||||||
|
ep_name = ep.get_text(strip=True)
|
||||||
|
ep_link = ep.get('href', '')
|
||||||
|
if ep_name in filter_titles:
|
||||||
|
continue
|
||||||
|
if ep_name and ep_link:
|
||||||
|
klist.append(f'{ep_name}${ep_link}')
|
||||||
|
if klist:
|
||||||
|
seen_lines.add(line_name)
|
||||||
|
ktabs.append(line_name)
|
||||||
|
klists.append('#'.join(klist))
|
||||||
|
|
||||||
|
info["vod_play_from"] = '$$$'.join(ktabs)
|
||||||
|
info["vod_play_url"] = '$$$'.join(klists)
|
||||||
|
|
||||||
|
return {'list': [info]}
|
||||||
|
|
||||||
|
def playerContent(self, flag, id, vipFlags):
|
||||||
|
try:
|
||||||
|
play_url = id if id.startswith(('http://', 'https://')) else xurl + id
|
||||||
|
html = requests.get(play_url, headers=headers, timeout=10).text
|
||||||
|
|
||||||
|
pattern = r'var\s+player_\w+\s*=\s*(\{[^;]+?\})\s*(?:;|</script)'
|
||||||
|
match = re.search(pattern, html, re.DOTALL)
|
||||||
|
video_url = ''
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
json_str = match.group(1).strip()
|
||||||
|
json_str = re.sub(r',\s*}', '}', json_str)
|
||||||
|
data = json.loads(json_str)
|
||||||
|
video_url = data.get('url', '')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not video_url:
|
||||||
|
m3u8_match = re.search(r'["\']url["\']\s*:\s*["\'](https?://[^"\']+\.m3u8[^"\']*)["\']', html)
|
||||||
|
if m3u8_match:
|
||||||
|
video_url = m3u8_match.group(1)
|
||||||
|
|
||||||
|
if video_url:
|
||||||
|
parse = 0 if re.search(r'\.(m3u8|mp4|mkv|flv|ts)$', video_url, re.I) else 1
|
||||||
|
if parse:
|
||||||
|
video_url = play_url
|
||||||
|
else:
|
||||||
|
parse = 1
|
||||||
|
video_url = play_url
|
||||||
|
|
||||||
|
return {"parse": parse, "playUrl": "", "url": video_url, "header": headers}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"player error: {e}")
|
||||||
|
return {"parse": 1, "playUrl": "", "url": xurl + id, "header": headers}
|
||||||
|
|
||||||
|
def searchContent(self, key, quick, page='1'):
|
||||||
|
page = int(page) if page else 1
|
||||||
|
encoded_key = urllib.parse.quote(key, safe='')
|
||||||
|
url = f"{xurl}/vodsearch/{encoded_key}----------{page}---.html"
|
||||||
|
soup = BeautifulSoup(requests.get(url, headers=headers).text, 'lxml')
|
||||||
|
videos = self._parse_video_items(soup)
|
||||||
|
return {
|
||||||
|
'list': videos,
|
||||||
|
'page': page,
|
||||||
|
'pagecount': 60,
|
||||||
|
'limit': 30,
|
||||||
|
'total': 999999
|
||||||
|
}
|
||||||
|
|
||||||
|
def localProxy(self, params):
|
||||||
|
if params['type'] == "m3u8":
|
||||||
|
return self.proxyM3u8(params)
|
||||||
|
elif params['type'] == "media":
|
||||||
|
return self.proxyMedia(params)
|
||||||
|
elif params['type'] == "ts":
|
||||||
|
return self.proxyTs(params)
|
||||||
|
return None
|
||||||
@@ -0,0 +1,546 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
# !/usr/bin/python
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
作者 精彩一瞬间 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除
|
||||||
|
====================Diudiumiao====================
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from Crypto.Util.Padding import unpad
|
||||||
|
from Crypto.Util.Padding import pad
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from urllib.parse import unquote
|
||||||
|
from Crypto.Cipher import ARC4
|
||||||
|
from urllib.parse import quote
|
||||||
|
from base.spider import Spider
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from datetime import datetime
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from base64 import b64decode
|
||||||
|
import concurrent.futures
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
import datetime
|
||||||
|
import binascii
|
||||||
|
import requests
|
||||||
|
import hashlib
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import hmac
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
sys.path.append('..')
|
||||||
|
|
||||||
|
xurl = "https://www.wasu.cn"
|
||||||
|
|
||||||
|
headerx = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
|
||||||
|
}
|
||||||
|
|
||||||
|
headerz = {
|
||||||
|
'accept': '*/*',
|
||||||
|
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||||
|
'cache-control': 'no-cache',
|
||||||
|
'origin': xurl,
|
||||||
|
'pragma': 'no-cache',
|
||||||
|
'priority': 'u=1, i',
|
||||||
|
'referer': xurl,
|
||||||
|
'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'sec-ch-ua-platform': '"Windows"',
|
||||||
|
'sec-fetch-dest': 'empty',
|
||||||
|
'sec-fetch-mode': 'cors',
|
||||||
|
'sec-fetch-site': 'cross-site',
|
||||||
|
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',
|
||||||
|
}
|
||||||
|
|
||||||
|
class Spider(Spider):
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return "精彩"
|
||||||
|
|
||||||
|
def init(self, extend):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def isVideoFormat(self, url):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def manualVideoCheck(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def homeVideoContent(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def homeContent(self, filter):
|
||||||
|
result = {}
|
||||||
|
result = {"class": [{"type_id": "961", "type_name": "电影"},
|
||||||
|
{"type_id": "962", "type_name": "剧集"},
|
||||||
|
{"type_id": "963", "type_name": "少儿"},
|
||||||
|
{"type_id": "965", "type_name": "栏目"},
|
||||||
|
{"type_id": "966", "type_name": "新闻"}],
|
||||||
|
"list": [],
|
||||||
|
"filters": {"961": [{"key": "地区",
|
||||||
|
"name": "地区",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "内地", "v": "内地"},
|
||||||
|
{"n": "港台", "v": "港台"},
|
||||||
|
{"n": "欧美", "v": "欧美"},
|
||||||
|
{"n": "日韩", "v": "日韩"},
|
||||||
|
{"n": "泰国", "v": "泰国"},
|
||||||
|
{"n": "其他", "v": "其他"}]},
|
||||||
|
{"key": "类型",
|
||||||
|
"name": "类型",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "动作", "v": "动作"},
|
||||||
|
{"n": "科幻", "v": "科幻"},
|
||||||
|
{"n": "惊悚", "v": "惊悚"},
|
||||||
|
{"n": "冒险", "v": "冒险"},
|
||||||
|
{"n": "剧情", "v": "剧情"},
|
||||||
|
{"n": "励志", "v": "励志"},
|
||||||
|
{"n": "爱情", "v": "爱情"},
|
||||||
|
{"n": "喜剧", "v": "喜剧"},
|
||||||
|
{"n": "家庭", "v": "家庭"},
|
||||||
|
{"n": "历史", "v": "历史"},
|
||||||
|
{"n": "魔幻", "v": "魔幻"},
|
||||||
|
{"n": "恐怖", "v": "恐怖"},
|
||||||
|
{"n": "战争", "v": "战争"},
|
||||||
|
{"n": "武侠", "v": "武侠"}]},
|
||||||
|
{"key": "年代",
|
||||||
|
"name": "年代",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "2025", "v": "2025"},
|
||||||
|
{"n": "2024", "v": "2024"},
|
||||||
|
{"n": "2023", "v": "2023"},
|
||||||
|
{"n": "2022", "v": "2022"},
|
||||||
|
{"n": "2021", "v": "2021"},
|
||||||
|
{"n": "2020", "v": "2020"},
|
||||||
|
{"n": "2019", "v": "2019"},
|
||||||
|
{"n": "2018", "v": "2018"},
|
||||||
|
{"n": "2017", "v": "2017"},
|
||||||
|
{"n": "2016", "v": "2016"},
|
||||||
|
{"n": "2015", "v": "2015"},
|
||||||
|
{"n": "2014", "v": "2014"},
|
||||||
|
{"n": "2013", "v": "2013"},
|
||||||
|
{"n": "2012", "v": "2012"},
|
||||||
|
{"n": "2011", "v": "2011"},
|
||||||
|
{"n": "2010", "v": "2010"}]}],
|
||||||
|
"962": [{"key": "地区",
|
||||||
|
"name": "地区",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "内地", "v": "内地"},
|
||||||
|
{"n": "港台", "v": "港台"},
|
||||||
|
{"n": "日韩", "v": "日韩"},
|
||||||
|
{"n": "欧美", "v": "欧美"},
|
||||||
|
{"n": "泰国", "v": "泰国"},
|
||||||
|
{"n": "其他", "v": "其他"}]},
|
||||||
|
{"key": "类型",
|
||||||
|
"name": "类型",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "都市", "v": "都市"},
|
||||||
|
{"n": "爱情", "v": "爱情"},
|
||||||
|
{"n": "战争", "v": "战争"},
|
||||||
|
{"n": "家庭", "v": "家庭"},
|
||||||
|
{"n": "悬疑", "v": "悬疑"},
|
||||||
|
{"n": "古装", "v": "古装"},
|
||||||
|
{"n": "短剧", "v": "短剧"},
|
||||||
|
{"n": "谍战", "v": "谍战"},
|
||||||
|
{"n": "喜剧", "v": "喜剧"},
|
||||||
|
{"n": "农村", "v": "农村"},
|
||||||
|
{"n": "刑侦", "v": "刑侦"},
|
||||||
|
{"n": "武侠", "v": "武侠"},
|
||||||
|
{"n": "历史", "v": "历史"}]},
|
||||||
|
{"key": "年代",
|
||||||
|
"name": "年代",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "2025", "v": "2025"},
|
||||||
|
{"n": "2024", "v": "2024"},
|
||||||
|
{"n": "2023", "v": "2023"},
|
||||||
|
{"n": "2022", "v": "2022"},
|
||||||
|
{"n": "2021", "v": "2021"},
|
||||||
|
{"n": "2020", "v": "2020"},
|
||||||
|
{"n": "2019", "v": "2019"},
|
||||||
|
{"n": "2018", "v": "2018"},
|
||||||
|
{"n": "2017", "v": "2017"},
|
||||||
|
{"n": "2016", "v": "2016"},
|
||||||
|
{"n": "2015", "v": "2015"},
|
||||||
|
{"n": "2014", "v": "2014"},
|
||||||
|
{"n": "2013", "v": "2013"},
|
||||||
|
{"n": "2012", "v": "2012"},
|
||||||
|
{"n": "2011", "v": "2011"},
|
||||||
|
{"n": "2010", "v": "2010"}]}],
|
||||||
|
"963": [{"key": "地区",
|
||||||
|
"name": "地区",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "内地", "v": "内地"},
|
||||||
|
{"n": "日韩", "v": "日韩"},
|
||||||
|
{"n": "欧美", "v": "欧美"},
|
||||||
|
{"n": "港台", "v": "港台"},
|
||||||
|
{"n": "其他", "v": "其他"}]},
|
||||||
|
{"key": "类型",
|
||||||
|
"name": "类型",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "动作", "v": "动作"},
|
||||||
|
{"n": "冒险", "v": "冒险"},
|
||||||
|
{"n": "益智", "v": "益智"},
|
||||||
|
{"n": "亲子", "v": "亲子"},
|
||||||
|
{"n": "热血", "v": "热血"},
|
||||||
|
{"n": "剧情", "v": "剧情"},
|
||||||
|
{"n": "魔幻", "v": "魔幻"},
|
||||||
|
{"n": "励志", "v": "励志"},
|
||||||
|
{"n": "机战", "v": "机战"},
|
||||||
|
{"n": "搞笑", "v": "搞笑"},
|
||||||
|
{"n": "科幻", "v": "科幻"},
|
||||||
|
{"n": "治愈", "v": "治愈"},
|
||||||
|
{"n": "儿歌", "v": "儿歌"},
|
||||||
|
{"n": "教育", "v": "教育"},
|
||||||
|
{"n": "校园", "v": "校园"},
|
||||||
|
{"n": "童话", "v": "童话"},
|
||||||
|
{"n": "推理", "v": "推理"},
|
||||||
|
{"n": "怀旧", "v": "怀旧"},
|
||||||
|
{"n": "宠物", "v": "宠物"},
|
||||||
|
{"n": "舞蹈", "v": "舞蹈"}]},
|
||||||
|
{"key": "年代",
|
||||||
|
"name": "年代",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "2025", "v": "2025"},
|
||||||
|
{"n": "2024", "v": "2024"},
|
||||||
|
{"n": "2023", "v": "2023"},
|
||||||
|
{"n": "2022", "v": "2022"},
|
||||||
|
{"n": "2021", "v": "2021"},
|
||||||
|
{"n": "2020", "v": "2020"},
|
||||||
|
{"n": "2019", "v": "2019"},
|
||||||
|
{"n": "2018", "v": "2018"},
|
||||||
|
{"n": "2017", "v": "2017"},
|
||||||
|
{"n": "2016", "v": "2016"},
|
||||||
|
{"n": "2015", "v": "2015"},
|
||||||
|
{"n": "2014", "v": "2014"},
|
||||||
|
{"n": "2013", "v": "2013"},
|
||||||
|
{"n": "2012", "v": "2012"},
|
||||||
|
{"n": "2011", "v": "2011"},
|
||||||
|
{"n": "2010", "v": "2010"}]}],
|
||||||
|
"965": [{"key": "地区",
|
||||||
|
"name": "地区",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "内地", "v": "内地"},
|
||||||
|
{"n": "欧美", "v": "欧美"}]},
|
||||||
|
{"key": "类型",
|
||||||
|
"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": "年代",
|
||||||
|
"name": "年代",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "2025", "v": "2025"},
|
||||||
|
{"n": "2024", "v": "2024"},
|
||||||
|
{"n": "2023", "v": "2023"},
|
||||||
|
{"n": "2022", "v": "2022"},
|
||||||
|
{"n": "2021", "v": "2021"},
|
||||||
|
{"n": "2020", "v": "2020"},
|
||||||
|
{"n": "2019", "v": "2019"},
|
||||||
|
{"n": "2018", "v": "2018"},
|
||||||
|
{"n": "2017", "v": "2017"},
|
||||||
|
{"n": "2016", "v": "2016"},
|
||||||
|
{"n": "2015", "v": "2015"},
|
||||||
|
{"n": "2014", "v": "2014"},
|
||||||
|
{"n": "2013", "v": "2013"},
|
||||||
|
{"n": "2012", "v": "2012"},
|
||||||
|
{"n": "2011", "v": "2011"},
|
||||||
|
{"n": "2010", "v": "2010"}]}],
|
||||||
|
"966": [{"key": "类型",
|
||||||
|
"name": "类型",
|
||||||
|
"value": [{"n": "全部", "v": ""},
|
||||||
|
{"n": "国内视野", "v": "国内视野"},
|
||||||
|
{"n": "国际纵览", "v": "国际纵览"},
|
||||||
|
{"n": "军事话题", "v": "军事话题"},
|
||||||
|
{"n": "社会百态", "v": "社会百态"},
|
||||||
|
{"n": "央视频", "v": "央视频"}]}]}}
|
||||||
|
return result
|
||||||
|
|
||||||
|
def generate_x_sign(self, secret_b64):
|
||||||
|
secret_key = self.decode_secret_key(secret_b64)
|
||||||
|
data = self.get_data_string()
|
||||||
|
signature = self.compute_signature(secret_key, data)
|
||||||
|
x_sign = self.encode_signature(signature)
|
||||||
|
return x_sign
|
||||||
|
|
||||||
|
def decode_secret_key(self, secret_b64):
|
||||||
|
return base64.b64decode(secret_b64)
|
||||||
|
|
||||||
|
def get_data_string(self):
|
||||||
|
return "{}"
|
||||||
|
|
||||||
|
def compute_signature(self, secret_key, data):
|
||||||
|
return hmac.new(secret_key, data.encode('utf-8'), hashlib.sha256).digest()
|
||||||
|
|
||||||
|
def encode_signature(self, signature):
|
||||||
|
return base64.b64encode(signature).decode('utf-8')
|
||||||
|
|
||||||
|
def get_current_app_key(self, xurl, headerx):
|
||||||
|
resp = self.fetch_index_page(xurl, headerx)
|
||||||
|
js_path = self.extract_js_path(resp.text)
|
||||||
|
js_url = f"{xurl}{js_path}"
|
||||||
|
js_content = self.fetch_js_content(js_url, headerx)
|
||||||
|
target_key_b64 = self.extract_target_key(js_content)
|
||||||
|
return target_key_b64
|
||||||
|
|
||||||
|
def fetch_index_page(self, xurl, headerx):
|
||||||
|
resp = requests.get(xurl, headers=headerx)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def extract_js_path(self, html_content):
|
||||||
|
js_path_pattern = r'src="(/[\d\.]+/assets/js/index-[\w\.-]+\.js)"'
|
||||||
|
match = re.search(js_path_pattern, html_content)
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def fetch_js_content(self, js_url, headerx):
|
||||||
|
js_resp = requests.get(js_url, headers=headerx)
|
||||||
|
js_resp.raise_for_status()
|
||||||
|
return js_resp.text
|
||||||
|
|
||||||
|
def extract_target_key(self, js_content):
|
||||||
|
key_pattern = r'const \w+="([^"]+)",\w+="([^"]+)",\w+="([^"]+)";'
|
||||||
|
key_match = re.search(key_pattern, js_content)
|
||||||
|
return key_match.group(2)
|
||||||
|
|
||||||
|
def get_headers(self, sign1, xurl):
|
||||||
|
return {
|
||||||
|
'accept': 'application/json, text/plain, */*',
|
||||||
|
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||||
|
'cache-control': 'no-cache',
|
||||||
|
'launchchannel': 'web_channel',
|
||||||
|
'origin': xurl,
|
||||||
|
'pragma': 'no-cache',
|
||||||
|
'priority': 'u=1, i',
|
||||||
|
'referer': xurl,
|
||||||
|
'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'sec-ch-ua-platform': '"Windows"',
|
||||||
|
'sec-fetch-dest': 'empty',
|
||||||
|
'sec-fetch-mode': 'cors',
|
||||||
|
'sec-fetch-site': 'cross-site',
|
||||||
|
'siteid': '1000101',
|
||||||
|
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',
|
||||||
|
'x-sign': sign1,
|
||||||
|
}
|
||||||
|
|
||||||
|
def categoryContent(self, cid, pg, filter, ext):
|
||||||
|
secret_b64 = self.get_current_app_key(xurl, headerx)
|
||||||
|
sign1 = self.generate_x_sign(secret_b64)
|
||||||
|
page = self.get_page_number(pg)
|
||||||
|
NdType, DqType, LxType = self.extract_filter_types(ext)
|
||||||
|
headers = self.get_headers(sign1, xurl)
|
||||||
|
params = self.build_category_params(cid, page, NdType, DqType, LxType)
|
||||||
|
data = self.fetch_category_data(params, headers)
|
||||||
|
videos = self.parse_category_videos(data)
|
||||||
|
return self.build_category_result(videos, pg)
|
||||||
|
|
||||||
|
def get_page_number(self, pg):
|
||||||
|
return int(pg) if pg else 1
|
||||||
|
|
||||||
|
def extract_filter_types(self, ext):
|
||||||
|
NdType = ext.get('年代', '全部')
|
||||||
|
DqType = ext.get('地区', '全部')
|
||||||
|
LxType = ext.get('类型', '全部')
|
||||||
|
return NdType, DqType, LxType
|
||||||
|
|
||||||
|
def build_category_params(self, cid, page, NdType, DqType, LxType):
|
||||||
|
return {'functionName': 'getNewsSearchedByCondition','nodeId': cid,'nodeTag': LxType,'yearTag': NdType,'countryTag': DqType,'orderType': '0','pageSize': '40','page': page,'keyword': '','siteId': '1000101',}
|
||||||
|
|
||||||
|
def fetch_category_data(self, params, headers):
|
||||||
|
detail = requests.get('https://ups.5g.wasu.tv/rmp-user-suggest/1000101/hzhs/searchServlet', params=params,headers=headers)
|
||||||
|
detail.encoding = "utf-8"
|
||||||
|
return detail.json()
|
||||||
|
|
||||||
|
def parse_category_videos(self, data):
|
||||||
|
videos = []
|
||||||
|
for vod in data['data']:
|
||||||
|
videos.append(self.parse_category_video(vod))
|
||||||
|
return videos
|
||||||
|
|
||||||
|
def parse_category_video(self, vod):
|
||||||
|
return {"vod_id": f"{vod['nodeId']}@{vod['newsId']}","vod_name": vod['title'],"vod_pic": vod['hPic'],"vod_year": vod.get('pubTime', '暂无日期'),"vod_remarks": vod.get('episodeDesc', '暂无备注')}
|
||||||
|
|
||||||
|
def build_category_result(self, videos, pg):
|
||||||
|
result = {'list': videos}
|
||||||
|
result['page'] = pg
|
||||||
|
result['pagecount'] = 9999
|
||||||
|
result['limit'] = 90
|
||||||
|
result['total'] = 999999
|
||||||
|
return result
|
||||||
|
|
||||||
|
def detailContent(self, ids):
|
||||||
|
did = ids[0]
|
||||||
|
secret_b64 = self.get_current_app_key(xurl, headerx)
|
||||||
|
sign1 = self.generate_x_sign(secret_b64)
|
||||||
|
fenge = did.split("@")
|
||||||
|
headers = self.get_headers(sign1, xurl)
|
||||||
|
params = self.build_detail_params(fenge)
|
||||||
|
data = self.fetch_detail_data(params, headers)
|
||||||
|
content = self.build_content(data)
|
||||||
|
director = self.extract_detail_field(data, 'director')
|
||||||
|
actor = self.extract_detail_field(data, 'actor')
|
||||||
|
remarks = self.extract_detail_field(data, 'episodeDesc')
|
||||||
|
year = self.extract_detail_field(data, 'pubTime')
|
||||||
|
area = self.extract_detail_field(data, 'countryTag')
|
||||||
|
bofang = self.build_play_url(data)
|
||||||
|
videos = [self.build_video_data(did, director, actor, remarks, year, area, content, bofang)]
|
||||||
|
return self.build_result(videos)
|
||||||
|
|
||||||
|
def build_detail_params(self, fenge):
|
||||||
|
return {'siteId': '1000101', 'functionName': 'getCurrentNews', 'nodeId': fenge[0], 'newsId': fenge[1],'platform': 'web',}
|
||||||
|
|
||||||
|
def fetch_detail_data(self, params, headers):
|
||||||
|
detail = requests.get('https://mcspapp.5g.wasu.tv/bvradio_app/hzhs/newsServlet', params=params, headers=headers)
|
||||||
|
detail.encoding = "utf-8"
|
||||||
|
return detail.json()
|
||||||
|
|
||||||
|
def build_content(self, data):
|
||||||
|
return '介绍剧情📢' + data.get('data', {}).get('newsAbstract', '')
|
||||||
|
|
||||||
|
def extract_detail_field(self, data, field_name):
|
||||||
|
return data.get('data', {}).get(field_name, '')
|
||||||
|
|
||||||
|
def build_play_url(self, data):
|
||||||
|
bofang = ''
|
||||||
|
for vod in data['data']['vodList']:
|
||||||
|
name = vod['title']
|
||||||
|
if len(vod['fileList']) > 1:
|
||||||
|
id = vod['fileList'][1]['playUrl']
|
||||||
|
else:
|
||||||
|
id = vod['fileList'][0]['playUrl']
|
||||||
|
bofang += name + '$' + id + '#'
|
||||||
|
return bofang[:-1]
|
||||||
|
|
||||||
|
def build_video_data(self, did, director, actor, remarks, year, area, content, bofang):
|
||||||
|
return {"vod_id": did, "vod_director": director, "vod_actor": actor, "vod_remarks": remarks, "vod_year": year, "vod_area": area, "vod_content": content, "vod_play_from": "华数专线", "vod_play_url": bofang}
|
||||||
|
|
||||||
|
def build_result(self, videos):
|
||||||
|
result = {}
|
||||||
|
result['list'] = videos
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_x_sign_for_post(self, secret_b64, playUrl):
|
||||||
|
secret_key = self.decode_secret_key(secret_b64)
|
||||||
|
payload_dict = self.build_payload_dict(playUrl)
|
||||||
|
data_string = self.serialize_payload(payload_dict)
|
||||||
|
signature = self.compute_hmac_signature(secret_key, data_string)
|
||||||
|
x_sign = self.encode_signature(signature)
|
||||||
|
return x_sign
|
||||||
|
|
||||||
|
def decode_secret_key(self, secret_b64):
|
||||||
|
return base64.b64decode(secret_b64)
|
||||||
|
|
||||||
|
def build_payload_dict(self, playUrl):
|
||||||
|
return {"playUrl": playUrl, "platform": "web"}
|
||||||
|
|
||||||
|
def serialize_payload(self, payload_dict):
|
||||||
|
return json.dumps(payload_dict, separators=(',', ':'), ensure_ascii=False)
|
||||||
|
|
||||||
|
def compute_hmac_signature(self, secret_key, data_string):
|
||||||
|
return hmac.new(secret_key, data_string.encode('utf-8'), hashlib.sha256).digest()
|
||||||
|
|
||||||
|
def encode_signature(self, signature):
|
||||||
|
return base64.b64encode(signature).decode('utf-8')
|
||||||
|
|
||||||
|
def playerContent(self, flag, id, vipFlags):
|
||||||
|
secret_b64 = self.get_current_app_key(xurl, headerx)
|
||||||
|
id = self.normalize_play_url(id)
|
||||||
|
sign2 = self.get_x_sign_for_post(secret_b64, id)
|
||||||
|
headers = self.get_headers(sign2, xurl)
|
||||||
|
json_data = self.build_request_json(id)
|
||||||
|
result = self.post_play_url_request(headers, json_data)
|
||||||
|
play_url = result['data']['playUrl']
|
||||||
|
return self.build_player_result(play_url)
|
||||||
|
|
||||||
|
def normalize_play_url(self, id):
|
||||||
|
return id.replace('.mp4', '/playlist.m3u8')
|
||||||
|
|
||||||
|
def build_request_json(self, playUrl):
|
||||||
|
return {'playUrl': playUrl, 'platform': 'web', }
|
||||||
|
|
||||||
|
def post_play_url_request(self, headers, json_data):
|
||||||
|
response = requests.post('https://mcspapp.5g.wasu.tv/thirdApiFile/file/getPlayUrl', headers=headers, json=json_data)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def build_player_result(self, play_url):
|
||||||
|
result = {}
|
||||||
|
result["parse"] = 0
|
||||||
|
result["playUrl"] = ''
|
||||||
|
result["url"] = play_url
|
||||||
|
result["header"] = headerz
|
||||||
|
return result
|
||||||
|
|
||||||
|
def searchContentPage(self, key, quick, pg):
|
||||||
|
page = self.get_page_number(pg)
|
||||||
|
secret_b64 = self.get_current_app_key(xurl, headerx)
|
||||||
|
sign1 = self.generate_x_sign(secret_b64)
|
||||||
|
headers = self.get_headers(sign1, xurl)
|
||||||
|
params = self.build_search_params(key, page)
|
||||||
|
data = self.fetch_search_data(params, headers)
|
||||||
|
videos = self.parse_search_videos(data)
|
||||||
|
return self.build_search_result(videos, pg)
|
||||||
|
|
||||||
|
def get_page_number(self, pg):
|
||||||
|
return int(pg) if pg else 1
|
||||||
|
|
||||||
|
def build_search_params(self, key, page):
|
||||||
|
return {'functionName': 'getNewsSearched', 'searchNewsType': '3,4,5', 'keyword': key, 'pageSize': 10, 'page': page, 'siteId': 1000101}
|
||||||
|
|
||||||
|
def fetch_search_data(self, params, headers):
|
||||||
|
detail = requests.get("https://ups.5g.wasu.tv/rmp-user-suggest/1000101/hzhs/searchServlet", params=params, headers=headers)
|
||||||
|
detail.encoding = "utf-8"
|
||||||
|
return detail.json()
|
||||||
|
|
||||||
|
def parse_search_videos(self, data):
|
||||||
|
videos = []
|
||||||
|
for vod in data['data']:
|
||||||
|
videos.append(self.parse_search_video(vod))
|
||||||
|
return videos
|
||||||
|
|
||||||
|
def parse_search_video(self, vod):
|
||||||
|
return {"vod_id": f"{vod['nodeId']}@{vod['newsId']}", "vod_name": vod['title'], "vod_pic": vod['hPic'], "vod_year": vod.get('pubTime', '暂无日期'), "vod_remarks": vod.get('episodeDesc', '暂无备注')}
|
||||||
|
|
||||||
|
def build_search_result(self, videos, pg):
|
||||||
|
result = {'list': videos}
|
||||||
|
result['page'] = pg
|
||||||
|
result['pagecount'] = 9999
|
||||||
|
result['limit'] = 90
|
||||||
|
result['total'] = 999999
|
||||||
|
return result
|
||||||
|
|
||||||
|
def searchContent(self, key, quick, pg="1"):
|
||||||
|
return self.searchContentPage(key, quick, '1')
|
||||||
|
|
||||||
|
def localProxy(self, params):
|
||||||
|
if params['type'] == "m3u8":
|
||||||
|
return self.proxyM3u8(params)
|
||||||
|
elif params['type'] == "media":
|
||||||
|
return self.proxyMedia(params)
|
||||||
|
elif params['type'] == "ts":
|
||||||
|
return self.proxyTs(params)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re, json, base64
|
||||||
|
import urllib.parse
|
||||||
|
from base.spider import Spider
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
|
class Spider(Spider):
|
||||||
|
headers = {
|
||||||
|
"User-Agent": "Mozilla/5.0 (Linux; Android 15; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Mobile Safari/537.36",
|
||||||
|
"Referer": "https://www.jkan.app/"
|
||||||
|
}
|
||||||
|
|
||||||
|
def init(self, extend=""):
|
||||||
|
self.host = "https://www.jkan.app"
|
||||||
|
self._timeout = 15
|
||||||
|
|
||||||
|
def _soup(self, html):
|
||||||
|
return BeautifulSoup(html, "html.parser") if html else None
|
||||||
|
|
||||||
|
def _fetch(self, url):
|
||||||
|
return self.fetch(url, headers=self.headers, timeout=self._timeout, verify=False)
|
||||||
|
|
||||||
|
def _fix(self, u):
|
||||||
|
if not u: return ""
|
||||||
|
if u.startswith("//"): return "https:" + u
|
||||||
|
if u.startswith("/"): return self.host + u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def _parse_list(self, html):
|
||||||
|
videos, seen = [], set()
|
||||||
|
soup = self._soup(html)
|
||||||
|
if soup is None:
|
||||||
|
return videos
|
||||||
|
for a in soup.select('a[href*="/video/"]'):
|
||||||
|
href = a.get("href", "")
|
||||||
|
if not href or href in seen:
|
||||||
|
continue
|
||||||
|
if a.get("class") and "text_muted" in a.get("class"):
|
||||||
|
continue
|
||||||
|
seen.add(href)
|
||||||
|
title = a.get("title", "") or a.get_text(strip=True)[:100]
|
||||||
|
pic = a.get("data-original", "") or a.get("data-background-image", "")
|
||||||
|
if not pic:
|
||||||
|
bg = re.search(r"background(?:-image)?\s*:\s*url\([\"']?([^\"')]+)[\"']?\)", a.get("style", "") or "")
|
||||||
|
if bg:
|
||||||
|
pic = bg.group(1)
|
||||||
|
remark = ""
|
||||||
|
rt = a.select_one("span.xszxj, span.pic_text")
|
||||||
|
if rt:
|
||||||
|
remark = rt.get_text(strip=True)
|
||||||
|
videos.append({"vod_id": href, "vod_name": title, "vod_pic": self._fix(pic), "vod_remarks": remark})
|
||||||
|
if len(videos) >= 50:
|
||||||
|
break
|
||||||
|
return videos
|
||||||
|
|
||||||
|
def _get_filters(self, tid):
|
||||||
|
return [
|
||||||
|
{"key": "class", "name": "类型", "value": [
|
||||||
|
{"n": "全部", "v": ""}, {"n": "喜剧", "v": "喜剧"}, {"n": "爱情", "v": "爱情"},
|
||||||
|
{"n": "恐怖", "v": "恐怖"}, {"n": "动作", "v": "动作"}, {"n": "科幻", "v": "科幻"},
|
||||||
|
{"n": "剧情", "v": "剧情"}, {"n": "战争", "v": "战争"}, {"n": "悬疑", "v": "悬疑"},
|
||||||
|
{"n": "惊悚", "v": "惊悚"}, {"n": "冒险", "v": "冒险"}, {"n": "犯罪", "v": "犯罪"},
|
||||||
|
{"n": "动画", "v": "动画"}, {"n": "奇幻", "v": "奇幻"}, {"n": "武侠", "v": "武侠"},
|
||||||
|
{"n": "古装", "v": "古装"}, {"n": "历史", "v": "历史"}, {"n": "记录", "v": "记录"},
|
||||||
|
]},
|
||||||
|
{"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": "德国"}, {"n": "印度", "v": "印度"},
|
||||||
|
]},
|
||||||
|
{"key": "year", "name": "年份", "value":
|
||||||
|
[{"n": "全部", "v": ""}] + [{"n": str(y), "v": str(y)} for y in range(2026, 1999, -1)]},
|
||||||
|
{"key": "sort", "name": "排序", "value": [
|
||||||
|
{"n": "按时间", "v": "time"}, {"n": "按人气", "v": "hits"}, {"n": "按评分", "v": "score"},
|
||||||
|
]},
|
||||||
|
]
|
||||||
|
|
||||||
|
def homeContent(self, filter):
|
||||||
|
return {
|
||||||
|
"class": [
|
||||||
|
{"type_id": "1", "type_name": "电影"},
|
||||||
|
{"type_id": "2", "type_name": "电视剧"},
|
||||||
|
{"type_id": "3", "type_name": "动漫"},
|
||||||
|
{"type_id": "4", "type_name": "综艺"},
|
||||||
|
],
|
||||||
|
"filters": {tid: self._get_filters(tid) for tid in ["1", "2", "3", "4"]}
|
||||||
|
}
|
||||||
|
|
||||||
|
def homeVideoContent(self):
|
||||||
|
try:
|
||||||
|
rsp = self._fetch(self.host)
|
||||||
|
return {"list": self._parse_list(rsp.text)}
|
||||||
|
except:
|
||||||
|
return {"list": []}
|
||||||
|
|
||||||
|
def _build_vodshow_url(self, tid, pg, extend):
|
||||||
|
segs = [""] * 12
|
||||||
|
segs[0] = tid
|
||||||
|
if extend.get("area"): segs[1] = extend["area"]
|
||||||
|
if extend.get("sort"): segs[2] = extend["sort"]
|
||||||
|
if extend.get("class"): segs[3] = extend["class"]
|
||||||
|
if extend.get("year"): segs[11] = extend["year"]
|
||||||
|
url = f"{self.host}/show/{'-'.join(segs)}.html"
|
||||||
|
if pg != "1":
|
||||||
|
url = f"{self.host}/show/{'-'.join(segs)}-{pg}.html"
|
||||||
|
return url
|
||||||
|
|
||||||
|
def categoryContent(self, tid, pg, filter, extend):
|
||||||
|
try:
|
||||||
|
if filter and any(v for v in extend.values()):
|
||||||
|
url = self._build_vodshow_url(tid, pg, extend)
|
||||||
|
else:
|
||||||
|
url = f"{self.host}/show/{tid}-----------{pg}.html"
|
||||||
|
rsp = self._fetch(url)
|
||||||
|
return {"list": self._parse_list(rsp.text), "page": str(pg), "pagecount": 999}
|
||||||
|
except:
|
||||||
|
return {"list": [], "page": str(pg), "pagecount": 0}
|
||||||
|
|
||||||
|
def _get_detail(self, vid):
|
||||||
|
if vid.startswith("/"):
|
||||||
|
url = self._fix(vid)
|
||||||
|
elif vid.startswith("http"):
|
||||||
|
url = vid
|
||||||
|
else:
|
||||||
|
url = f"{self.host}/video/{vid}.html"
|
||||||
|
try:
|
||||||
|
rsp = self._fetch(url)
|
||||||
|
except:
|
||||||
|
return {"vod_id": vid}
|
||||||
|
html = rsp.text
|
||||||
|
if re.search(r"(模板文件不存在|系统安全验证)", html, re.I):
|
||||||
|
return {"vod_id": "0", "vod_name": ""}
|
||||||
|
vod = {"vod_id": vid}
|
||||||
|
soup = self._soup(html)
|
||||||
|
if soup is None:
|
||||||
|
return vod
|
||||||
|
vod["vod_name"] = self._og(soup, "og:title")
|
||||||
|
vod["vod_pic"] = self._og(soup, "og:image")
|
||||||
|
vod["vod_content"] = self._og(soup, "og:description")
|
||||||
|
vod["vod_year"] = self._og(soup, "og:video:release_date")
|
||||||
|
vod["vod_area"] = self._og(soup, "og:video:area")
|
||||||
|
vod["vod_director"] = self._og(soup, "og:video:director")
|
||||||
|
vod["vod_actor"] = self._og(soup, "og:video:actor")
|
||||||
|
vod["type_name"] = self._og(soup, "og:video:class")
|
||||||
|
score = self._og(soup, "og:video:score")
|
||||||
|
if score: vod["vod_score"] = score
|
||||||
|
# extract play sources and episodes
|
||||||
|
src_tabs = soup.select_one(".play_source_tab")
|
||||||
|
src_names = [a.get("alt", "").strip() or a.get_text(strip=True) for a in src_tabs.select("a")] if src_tabs else []
|
||||||
|
for i, ul in enumerate(soup.select("ul.content_playlist, ul.playlist")):
|
||||||
|
source = src_names[i] if i < len(src_names) else "默认"
|
||||||
|
episodes = []
|
||||||
|
seen_urls = set()
|
||||||
|
for a in ul.select("a[href]"):
|
||||||
|
ep_url = a.get("href", "")
|
||||||
|
if ep_url in seen_urls:
|
||||||
|
continue
|
||||||
|
seen_urls.add(ep_url)
|
||||||
|
ep_name = a.get_text(strip=True) or "播放"
|
||||||
|
episodes.append(f"{ep_name}${self._fix(ep_url)}")
|
||||||
|
if episodes:
|
||||||
|
vod.setdefault("vod_play_from", []).append(source)
|
||||||
|
vod.setdefault("vod_play_url", []).append("#".join(episodes))
|
||||||
|
if "vod_play_from" in vod:
|
||||||
|
vod["vod_play_from"] = "$$$".join(vod.pop("vod_play_from"))
|
||||||
|
vod["vod_play_url"] = "$$$".join(vod.pop("vod_play_url"))
|
||||||
|
else:
|
||||||
|
vod["vod_play_from"] = "默认线路"
|
||||||
|
vod["vod_play_url"] = ""
|
||||||
|
return vod
|
||||||
|
|
||||||
|
def _og(self, soup, prop):
|
||||||
|
m = soup.select_one(f'meta[property="{prop}"]')
|
||||||
|
if m: return m.get("content", "")
|
||||||
|
m = soup.select_one(f'meta[name="{prop}"]')
|
||||||
|
if m: return m.get("content", "")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def detailContent(self, ids):
|
||||||
|
return {"list": [self._get_detail(ids[0])]}
|
||||||
|
|
||||||
|
def playerContent(self, flag, id, vipFlags):
|
||||||
|
play_url = self._fix(id)
|
||||||
|
if self.isVideoFormat(play_url):
|
||||||
|
return {"parse": 0, "url": play_url, "header": self.headers}
|
||||||
|
try:
|
||||||
|
rsp = self._fetch(play_url)
|
||||||
|
return self._parse_player(rsp.text, play_url)
|
||||||
|
except:
|
||||||
|
return {"parse": 0, "url": play_url, "header": self.headers}
|
||||||
|
|
||||||
|
def _parse_player(self, html, play_url):
|
||||||
|
match = re.search(r'player_aaaa\s*=\s*(\{[\s\S]*?\});?\s*</script>', html, re.I)
|
||||||
|
if not match:
|
||||||
|
return {"parse": 1, "url": play_url, "header": self.headers}
|
||||||
|
try:
|
||||||
|
data = json.loads(match.group(1))
|
||||||
|
url_enc = data.get("url", "")
|
||||||
|
enc_type = str(data.get("encrypt", "0"))
|
||||||
|
if enc_type == "1":
|
||||||
|
url_enc = urllib.parse.unquote(url_enc)
|
||||||
|
elif enc_type == "2":
|
||||||
|
url_enc = urllib.parse.unquote(base64.b64decode(url_enc).decode())
|
||||||
|
real_url = self._unescape(url_enc)
|
||||||
|
return {"parse": 0, "url": real_url or play_url, "header": self.headers}
|
||||||
|
except:
|
||||||
|
return {"parse": 0, "url": play_url, "header": self.headers}
|
||||||
|
|
||||||
|
def _unescape(self, s):
|
||||||
|
result, i, n = [], 0, len(s)
|
||||||
|
while i < n:
|
||||||
|
if s[i] == '%' and i + 5 < n and s[i + 1] == 'u':
|
||||||
|
result.append(chr(int(s[i + 2:i + 6], 16)))
|
||||||
|
i += 6
|
||||||
|
elif s[i] == '%' and i + 2 < n:
|
||||||
|
result.append(chr(int(s[i + 1:i + 3], 16)))
|
||||||
|
i += 3
|
||||||
|
else:
|
||||||
|
result.append(s[i])
|
||||||
|
i += 1
|
||||||
|
return ''.join(result)
|
||||||
|
|
||||||
|
def searchContent(self, key, quick, pg="1"):
|
||||||
|
# 搜索翻页格式: ---------{N}--- (page1: 13 dashes, pageN: 10 dashes + N + 3 dashes)
|
||||||
|
try:
|
||||||
|
kw = urllib.parse.quote(key)
|
||||||
|
if pg == "1":
|
||||||
|
url = f"{self.host}/search/{kw}-------------.html"
|
||||||
|
else:
|
||||||
|
url = f"{self.host}/search/{kw}----------{pg}---.html"
|
||||||
|
rsp = self._fetch(url)
|
||||||
|
videos = self._parse_list(rsp.text)
|
||||||
|
return {"list": videos, "page": str(pg), "pagecount": 99}
|
||||||
|
except:
|
||||||
|
return {"list": [], "pagecount": 0}
|
||||||
|
|
||||||
|
def isVideoFormat(self, url):
|
||||||
|
return bool(re.search(r'\.(mp4|m3u8|flv|avi|mkv|ts|webm|mpg|mpeg)\b', url, re.I))
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
from base.spider import Spider
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
host = "https://www.hongguoguo.tv"
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36',
|
||||||
|
'Referer': host
|
||||||
|
}
|
||||||
|
timeout = 10
|
||||||
|
|
||||||
|
# TVBox 兼容的 header 字符串格式
|
||||||
|
def _header_str():
|
||||||
|
return f"User-Agent={headers['User-Agent']}&Referer={headers['Referer']}"
|
||||||
|
|
||||||
|
|
||||||
|
class Spider(Spider):
|
||||||
|
def getName(self):
|
||||||
|
return "红果果短剧"
|
||||||
|
|
||||||
|
def init(self, extend):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def isVideoFormat(self, url):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def manualVideoCheck(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def homeContent(self, filter):
|
||||||
|
# 福利短剧(fuliduanju)需要会员权限,已移除
|
||||||
|
classes = [
|
||||||
|
{"type_id": "jingxuanduanju", "type_name": "精选短剧"}
|
||||||
|
]
|
||||||
|
return {"class": classes}
|
||||||
|
|
||||||
|
def homeVideoContent(self):
|
||||||
|
try:
|
||||||
|
r = requests.get(host, headers=headers, timeout=timeout)
|
||||||
|
return {'list': self._parse_list(r.text)}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[homeVideoContent] error: {e}")
|
||||||
|
return {'list': []}
|
||||||
|
|
||||||
|
def categoryContent(self, cid, pg, filter, ext):
|
||||||
|
page = int(pg) if pg else 1
|
||||||
|
if page == 1:
|
||||||
|
url = f"{host}/vod/show/id/{cid}.html"
|
||||||
|
else:
|
||||||
|
url = f"{host}/vod/show/id/{cid}/page/{page}.html"
|
||||||
|
try:
|
||||||
|
r = requests.get(url, headers=headers, timeout=timeout)
|
||||||
|
videos = self._parse_list(r.text)
|
||||||
|
return {
|
||||||
|
'list': videos,
|
||||||
|
'page': pg,
|
||||||
|
'pagecount': 9999,
|
||||||
|
'limit': 24,
|
||||||
|
'total': 999999
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[categoryContent] error: {e}")
|
||||||
|
return {'list': []}
|
||||||
|
|
||||||
|
def detailContent(self, ids):
|
||||||
|
did = str(ids[0])
|
||||||
|
try:
|
||||||
|
play_page = f"{host}/vod/play/id/{did}/sid/1/nid/1.html"
|
||||||
|
r = requests.get(play_page, headers=headers, timeout=timeout)
|
||||||
|
text = r.text
|
||||||
|
|
||||||
|
# 权限检查
|
||||||
|
if '没有权限' in text or '升级会员' in text:
|
||||||
|
print(f"[detailContent] ID={did} 需要会员权限")
|
||||||
|
return {'list': []}
|
||||||
|
|
||||||
|
# 提取 player_aaaa(支持多种闭合标签)
|
||||||
|
player = None
|
||||||
|
for pattern in [
|
||||||
|
r'var player_aaaa=(\{.*?\})</script>',
|
||||||
|
r'var player_aaaa=(\{.*?\});',
|
||||||
|
r'player_aaaa=(\{.*?\})</script>',
|
||||||
|
]:
|
||||||
|
m = re.search(pattern, text, re.DOTALL)
|
||||||
|
if m:
|
||||||
|
try:
|
||||||
|
player = json.loads(m.group(1))
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not player:
|
||||||
|
print(f"[detailContent] ID={did} 未找到 player_aaaa")
|
||||||
|
return {'list': []}
|
||||||
|
|
||||||
|
vod_data = player.get('vod_data', {})
|
||||||
|
play_url = player.get('url', '').replace('\\/', '/')
|
||||||
|
play_from = player.get('from', 'm3u8')
|
||||||
|
|
||||||
|
if not play_url:
|
||||||
|
print(f"[detailContent] ID={did} 无播放地址")
|
||||||
|
return {'list': []}
|
||||||
|
|
||||||
|
# 提取封面
|
||||||
|
pic = ''
|
||||||
|
pic_m = re.search(r'"thumbnailUrl":"([^"]+)"', text)
|
||||||
|
if pic_m:
|
||||||
|
pic = pic_m.group(1).replace('\\/', '/')
|
||||||
|
|
||||||
|
# 提取简介
|
||||||
|
content = ''
|
||||||
|
desc_m = re.search(r'<p[^>]*class="[^"]*desc[^"]*"[^>]*>(.*?)</p>', text, re.DOTALL)
|
||||||
|
if desc_m:
|
||||||
|
content = re.sub(r'<[^>]+>', '', desc_m.group(1)).strip()
|
||||||
|
|
||||||
|
# 提取集数按钮
|
||||||
|
ep_pattern = r'href="/vod/play/id/' + did + r'/sid/(\d+)/nid/(\d+)\.html"[^>]*>([^<]*)</a>'
|
||||||
|
episodes = re.findall(ep_pattern, text)
|
||||||
|
|
||||||
|
play_from_list = []
|
||||||
|
play_url_list = []
|
||||||
|
|
||||||
|
if not episodes:
|
||||||
|
# 单集短剧
|
||||||
|
play_from_list.append(play_from)
|
||||||
|
vod_name = vod_data.get('vod_name', '第1集')
|
||||||
|
play_url_list.append(f"{vod_name}${play_url}")
|
||||||
|
else:
|
||||||
|
# 按线路分组
|
||||||
|
lines = {}
|
||||||
|
for sid, nid, name in episodes:
|
||||||
|
sid = int(sid)
|
||||||
|
if sid not in lines:
|
||||||
|
lines[sid] = []
|
||||||
|
lines[sid].append((int(nid), name.strip()))
|
||||||
|
|
||||||
|
for sid in sorted(lines.keys()):
|
||||||
|
line_eps = sorted(lines[sid], key=lambda x: x[0])
|
||||||
|
urls = []
|
||||||
|
line_name = None
|
||||||
|
|
||||||
|
for idx, (nid, name) in enumerate(line_eps):
|
||||||
|
if sid == 1 and nid == 1:
|
||||||
|
# 当前页面(第一集)
|
||||||
|
line_name = play_from
|
||||||
|
urls.append(f"{name}${play_url}")
|
||||||
|
else:
|
||||||
|
# 请求其他集页面
|
||||||
|
ep_url = f"{host}/vod/play/id/{did}/sid/{sid}/nid/{nid}.html"
|
||||||
|
try:
|
||||||
|
rr = requests.get(ep_url, headers=headers, timeout=8)
|
||||||
|
if '没有权限' in rr.text:
|
||||||
|
continue
|
||||||
|
for pattern in [
|
||||||
|
r'var player_aaaa=(\{.*?\})</script>',
|
||||||
|
r'var player_aaaa=(\{.*?\});',
|
||||||
|
r'player_aaaa=(\{.*?\})</script>',
|
||||||
|
]:
|
||||||
|
mm = re.search(pattern, rr.text, re.DOTALL)
|
||||||
|
if mm:
|
||||||
|
try:
|
||||||
|
ep_player = json.loads(mm.group(1))
|
||||||
|
ep_url_real = ep_player.get('url', '').replace('\\/', '/')
|
||||||
|
if ep_url_real:
|
||||||
|
urls.append(f"{name}${ep_url_real}")
|
||||||
|
if line_name is None:
|
||||||
|
line_name = ep_player.get('from', f'线路{sid}')
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[detailContent] 获取集 {sid}/{nid} 失败: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if urls and line_name:
|
||||||
|
play_from_list.append(line_name)
|
||||||
|
play_url_list.append("#".join(urls))
|
||||||
|
|
||||||
|
# 如果多线路处理失败,回退到单集
|
||||||
|
if not play_from_list:
|
||||||
|
play_from_list.append(play_from)
|
||||||
|
vod_name = vod_data.get('vod_name', '第1集')
|
||||||
|
play_url_list.append(f"{vod_name}${play_url}")
|
||||||
|
|
||||||
|
VOD = {
|
||||||
|
"vod_id": did,
|
||||||
|
"vod_name": vod_data.get('vod_name', ''),
|
||||||
|
"vod_pic": pic,
|
||||||
|
"vod_actor": vod_data.get('vod_actor', ''),
|
||||||
|
"vod_director": vod_data.get('vod_director', ''),
|
||||||
|
"type_name": vod_data.get('vod_class', ''),
|
||||||
|
"vod_remarks": "短剧",
|
||||||
|
"vod_content": content,
|
||||||
|
"vod_play_from": "$$$".join(play_from_list),
|
||||||
|
"vod_play_url": "$$$".join(play_url_list)
|
||||||
|
}
|
||||||
|
return {'list': [VOD]}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[detailContent] 获取详情失败: {e}")
|
||||||
|
return {'list': []}
|
||||||
|
|
||||||
|
def playerContent(self, flag, id, vipFlags):
|
||||||
|
print(f"[playerContent] flag={flag}, url={id[:80]}...")
|
||||||
|
return {
|
||||||
|
"parse": 0,
|
||||||
|
"playUrl": '',
|
||||||
|
"url": id,
|
||||||
|
"header": _header_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
def searchContent(self, key, quick, pg=1):
|
||||||
|
page = int(pg) if pg else 1
|
||||||
|
keyword = urllib.parse.quote(key)
|
||||||
|
if page == 1:
|
||||||
|
url = f"{host}/vod/search.html?wd={keyword}"
|
||||||
|
else:
|
||||||
|
url = f"{host}/vod/search.html?wd={keyword}&page={page}"
|
||||||
|
try:
|
||||||
|
r = requests.get(url, headers=headers, timeout=timeout)
|
||||||
|
videos = self._parse_list(r.text)
|
||||||
|
return {
|
||||||
|
'list': videos,
|
||||||
|
'page': page,
|
||||||
|
'pagecount': 9999,
|
||||||
|
'limit': 24,
|
||||||
|
'total': 999999
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[searchContent] error: {e}")
|
||||||
|
return {'list': []}
|
||||||
|
|
||||||
|
def _parse_list(self, text):
|
||||||
|
videos = []
|
||||||
|
pattern = (
|
||||||
|
r'<a[^>]*href="/vod/play/id/(\d+)/sid/1/nid/1\.html"[^>]*>'
|
||||||
|
r'.*?<div class="Image">.*?'
|
||||||
|
r'<div class="lazy" data-original="([^"]+)"[^>]*>.*?</div>'
|
||||||
|
r'.*?<span class="f">([^<]*)</span>'
|
||||||
|
r'.*?<span class="b">([^<]*)</span>'
|
||||||
|
r'.*?</div>.*?'
|
||||||
|
r'<h2 class="tim-title">([^<]*)</h2>'
|
||||||
|
r'.*?</a>'
|
||||||
|
)
|
||||||
|
matches = re.findall(pattern, text, re.DOTALL)
|
||||||
|
for m in matches:
|
||||||
|
vid, pic, score, status, name = m
|
||||||
|
videos.append({
|
||||||
|
"vod_id": vid,
|
||||||
|
"vod_name": name.strip(),
|
||||||
|
"vod_pic": pic.strip(),
|
||||||
|
"vod_remarks": f"{score.strip()} {status.strip()}",
|
||||||
|
"vod_content": ""
|
||||||
|
})
|
||||||
|
return videos
|
||||||
+225
@@ -0,0 +1,225 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# by @嗷呜
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import uuid
|
||||||
|
import requests
|
||||||
|
sys.path.append('..')
|
||||||
|
from base.spider import Spider
|
||||||
|
import time
|
||||||
|
from Crypto.Hash import MD5, SHA1
|
||||||
|
|
||||||
|
class Spider(Spider):
|
||||||
|
'''
|
||||||
|
配置示例:
|
||||||
|
{
|
||||||
|
"key": "xxxx",
|
||||||
|
"name": "xxxx",
|
||||||
|
"type": 3,
|
||||||
|
"api": ".所在路径/金牌.py",
|
||||||
|
"searchable": 1,
|
||||||
|
"quickSearch": 1,
|
||||||
|
"filterable": 1,
|
||||||
|
"changeable": 1,
|
||||||
|
"ext": {
|
||||||
|
"site": "https://www.jiabaide.cn,域名2,域名3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'''
|
||||||
|
def init(self, extend=""):
|
||||||
|
if extend:
|
||||||
|
hosts=json.loads(extend)['site']
|
||||||
|
self.host = self.host_late(hosts)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def isVideoFormat(self, url):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def manualVideoCheck(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def homeContent(self, filter):
|
||||||
|
cdata = self.fetch(f"{self.host}/api/mw-movie/anonymous/get/filer/type", headers=self.getheaders()).json()
|
||||||
|
fdata = self.fetch(f"{self.host}/api/mw-movie/anonymous/v1/get/filer/list", headers=self.getheaders()).json()
|
||||||
|
result = {}
|
||||||
|
classes = []
|
||||||
|
filters={}
|
||||||
|
for k in cdata['data']:
|
||||||
|
classes.append({
|
||||||
|
'type_name': k['typeName'],
|
||||||
|
'type_id': str(k['typeId']),
|
||||||
|
})
|
||||||
|
sort_values = [{"n": "最近更新", "v": "2"},{"n": "人气高低", "v": "3"}, {"n": "评分高低", "v": "4"}]
|
||||||
|
for tid, d in fdata['data'].items():
|
||||||
|
current_sort_values = sort_values.copy()
|
||||||
|
if tid == '1':
|
||||||
|
del current_sort_values[0]
|
||||||
|
filters[tid] = [
|
||||||
|
{"key": "type", "name": "类型",
|
||||||
|
"value": [{"n": i["itemText"], "v": i["itemValue"]} for i in d["typeList"]]},
|
||||||
|
|
||||||
|
*([] if not d["plotList"] else [{"key": "v_class", "name": "剧情",
|
||||||
|
"value": [{"n": i["itemText"], "v": i["itemText"]}
|
||||||
|
for i in d["plotList"]]}]),
|
||||||
|
|
||||||
|
{"key": "area", "name": "地区",
|
||||||
|
"value": [{"n": i["itemText"], "v": i["itemText"]} for i in d["districtList"]]},
|
||||||
|
|
||||||
|
{"key": "year", "name": "年份",
|
||||||
|
"value": [{"n": i["itemText"], "v": i["itemText"]} for i in d["yearList"]]},
|
||||||
|
|
||||||
|
{"key": "lang", "name": "语言",
|
||||||
|
"value": [{"n": i["itemText"], "v": i["itemText"]} for i in d["languageList"]]},
|
||||||
|
|
||||||
|
{"key": "sort", "name": "排序", "value": current_sort_values}
|
||||||
|
]
|
||||||
|
result['class'] = classes
|
||||||
|
result['filters'] = filters
|
||||||
|
return result
|
||||||
|
|
||||||
|
def homeVideoContent(self):
|
||||||
|
data1 = self.fetch(f"{self.host}/api/mw-movie/anonymous/v1/home/all/list", headers=self.getheaders()).json()
|
||||||
|
data2=self.fetch(f"{self.host}/api/mw-movie/anonymous/home/hotSearch",headers=self.getheaders()).json()
|
||||||
|
data=[]
|
||||||
|
for i in data1['data'].values():
|
||||||
|
data.extend(i['list'])
|
||||||
|
data.extend(data2['data'])
|
||||||
|
vods=self.getvod(data)
|
||||||
|
return {'list':vods}
|
||||||
|
|
||||||
|
def categoryContent(self, tid, pg, filter, extend):
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"area": extend.get('area', ''),
|
||||||
|
"filterStatus": "1",
|
||||||
|
"lang": extend.get('lang', ''),
|
||||||
|
"pageNum": pg,
|
||||||
|
"pageSize": "30",
|
||||||
|
"sort": extend.get('sort', '1'),
|
||||||
|
"sortBy": "1",
|
||||||
|
"type": extend.get('type', ''),
|
||||||
|
"type1": tid,
|
||||||
|
"v_class": extend.get('v_class', ''),
|
||||||
|
"year": extend.get('year', '')
|
||||||
|
}
|
||||||
|
data = self.fetch(f"{self.host}/api/mw-movie/anonymous/video/list?{self.js(params)}", headers=self.getheaders(params)).json()
|
||||||
|
result = {}
|
||||||
|
result['list'] = self.getvod(data['data']['list'])
|
||||||
|
result['page'] = pg
|
||||||
|
result['pagecount'] = 9999
|
||||||
|
result['limit'] = 90
|
||||||
|
result['total'] = 999999
|
||||||
|
return result
|
||||||
|
|
||||||
|
def detailContent(self, ids):
|
||||||
|
data=self.fetch(f"{self.host}/api/mw-movie/anonymous/video/detail?id={ids[0]}",headers=self.getheaders({'id':ids[0]})).json()
|
||||||
|
vod=self.getvod([data['data']])[0]
|
||||||
|
vod['vod_play_from']='金牌'
|
||||||
|
vod['vod_play_url'] = '#'.join(
|
||||||
|
f"{i['name'] if len(vod['episodelist']) > 1 else vod['vod_name']}${ids[0]}@@{i['nid']}" for i in
|
||||||
|
vod['episodelist'])
|
||||||
|
vod.pop('episodelist', None)
|
||||||
|
return {'list':[vod]}
|
||||||
|
|
||||||
|
def searchContent(self, key, quick, pg="1"):
|
||||||
|
params = {
|
||||||
|
"keyword": key,
|
||||||
|
"pageNum": pg,
|
||||||
|
"pageSize": "8",
|
||||||
|
"sourceCode": "1"
|
||||||
|
}
|
||||||
|
data=self.fetch(f"{self.host}/api/mw-movie/anonymous/video/searchByWord?{self.js(params)}",headers=self.getheaders(params)).json()
|
||||||
|
vods=self.getvod(data['data']['result']['list'])
|
||||||
|
return {'list':vods,'page':pg}
|
||||||
|
|
||||||
|
def playerContent(self, flag, id, vipFlags):
|
||||||
|
self.header = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.61 Chrome/126.0.6478.61 Not/A)Brand/8 Safari/537.36',
|
||||||
|
'sec-ch-ua-platform': '"Windows"',
|
||||||
|
'DNT': '1',
|
||||||
|
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'Origin': self.host,
|
||||||
|
'Referer': f'{self.host}/'
|
||||||
|
}
|
||||||
|
ids=id.split('@@')
|
||||||
|
pdata = self.fetch(f"{self.host}/api/mw-movie/anonymous/v2/video/episode/url?clientType=1&id={ids[0]}&nid={ids[1]}",headers=self.getheaders({'clientType':'1','id': ids[0], 'nid': ids[1]})).json()
|
||||||
|
vlist=[]
|
||||||
|
for i in pdata['data']['list']:vlist.extend([i['resolutionName'],i['url']])
|
||||||
|
return {'parse':0,'url':vlist,'header':self.header}
|
||||||
|
|
||||||
|
def localProxy(self, param):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def host_late(self, url_list):
|
||||||
|
if isinstance(url_list, str):
|
||||||
|
urls = [u.strip() for u in url_list.split(',')]
|
||||||
|
else:
|
||||||
|
urls = url_list
|
||||||
|
if len(urls) <= 1:
|
||||||
|
return urls[0] if urls else ''
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
threads = []
|
||||||
|
|
||||||
|
def test_host(url):
|
||||||
|
try:
|
||||||
|
start_time = time.time()
|
||||||
|
response = requests.head(url, timeout=1.0, allow_redirects=False)
|
||||||
|
delay = (time.time() - start_time) * 1000
|
||||||
|
results[url] = delay
|
||||||
|
except Exception as e:
|
||||||
|
results[url] = float('inf')
|
||||||
|
for url in urls:
|
||||||
|
t = threading.Thread(target=test_host, args=(url,))
|
||||||
|
threads.append(t)
|
||||||
|
t.start()
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
return min(results.items(), key=lambda x: x[1])[0]
|
||||||
|
|
||||||
|
def md5(self, sign_key):
|
||||||
|
md5_hash = MD5.new()
|
||||||
|
md5_hash.update(sign_key.encode('utf-8'))
|
||||||
|
md5_result = md5_hash.hexdigest()
|
||||||
|
return md5_result
|
||||||
|
|
||||||
|
def js(self, param):
|
||||||
|
return '&'.join(f"{k}={v}" for k, v in param.items())
|
||||||
|
|
||||||
|
def getheaders(self, param=None):
|
||||||
|
if param is None:param = {}
|
||||||
|
t=str(int(time.time()*1000))
|
||||||
|
param['key']='cb808529bae6b6be45ecfab29a4889bc'
|
||||||
|
param['t']=t
|
||||||
|
sha1_hash = SHA1.new()
|
||||||
|
sha1_hash.update(self.md5(self.js(param)).encode('utf-8'))
|
||||||
|
sign = sha1_hash.hexdigest()
|
||||||
|
deviceid = str(uuid.uuid4())
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.61 Chrome/126.0.6478.61 Not/A)Brand/8 Safari/537.36',
|
||||||
|
'Accept': 'application/json, text/plain, */*',
|
||||||
|
'sign': sign,
|
||||||
|
't': t,
|
||||||
|
'deviceid':deviceid
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def convert_field_name(self, field):
|
||||||
|
field = field.lower()
|
||||||
|
if field.startswith('vod') and len(field) > 3:
|
||||||
|
field = field.replace('vod', 'vod_')
|
||||||
|
if field.startswith('type') and len(field) > 4:
|
||||||
|
field = field.replace('type', 'type_')
|
||||||
|
return field
|
||||||
|
|
||||||
|
def getvod(self, array):
|
||||||
|
return [{self.convert_field_name(k): v for k, v in item.items()} for item in array]
|
||||||
|
|
||||||
Reference in New Issue
Block a user