上传文件至「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