Sync all projects
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -22,6 +22,13 @@
|
||||
"bz": "1",
|
||||
"paichu": "1,2,3,4"
|
||||
},
|
||||
{
|
||||
"name": "TV-悠悠资源站",
|
||||
"api": "https://uuzy.me/api.php/provide/vod/",
|
||||
"detail": "https://uuzy.me",
|
||||
"bz": "1",
|
||||
"paichu": "1,2,3,4"
|
||||
},
|
||||
{
|
||||
"name": "TV-量子资源",
|
||||
"api": "https://cj.lziapi.com/api.php/provide/vod",
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
"detail": "https://98zy.vip",
|
||||
"bz": "1",
|
||||
"paichu": "1,2,3,4"
|
||||
},
|
||||
{
|
||||
"name": "TV-悠悠资源站",
|
||||
"api": "https://uuzy.me/api.php/provide/vod/",
|
||||
"detail": "https://uuzy.me",
|
||||
"bz": "1",
|
||||
"paichu": "1,2,3,4"
|
||||
},
|
||||
{
|
||||
"name": "TV-大众资源站",
|
||||
|
||||
@@ -46,7 +46,8 @@ class Spider(Spider):
|
||||
's33': {'name': '🐾淘片', 'api': 'https://www.taopianzy.com/cjapi/mc/vod/json.html'},
|
||||
's34': {'name': '🐾98', 'api': 'https://98zy.vip/api.php/provide/vod/'},
|
||||
's35': {'name': '🐾大众', 'api': 'https://cdn.dzzyapi.com/api.php/provide/vod/'},
|
||||
's36': {'name': '📺魔都', 'api': 'https://www.mdzyapi.com/api.php/provide/vod'},
|
||||
's36': {'name': '🐾悠悠2', 'api': 'https://uuzy.me/api.php/provide/vod/'},
|
||||
's37': {'name': '📺魔都', 'api': 'https://www.mdzyapi.com/api.php/provide/vod'},
|
||||
}
|
||||
|
||||
headers = {
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from base.spider import Spider
|
||||
import re, sys, time, random, secrets, hashlib, urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host, device_id = 'https://bubutv.top/', 'com.sunshine.tv_3qys_B7k7Dt56Rn'
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host: return None
|
||||
response = self.fetch(f'{self.host}/api.php/app/index/home', headers=self.headers(), verify=False).json()
|
||||
categories = response['data']['categories']
|
||||
videos, classes = [], []
|
||||
for i in categories:
|
||||
classes.append({'type_id': i['type_name'], 'type_name': i['type_name']})
|
||||
videos.extend(self.arr2vods(i['videos']))
|
||||
return {'class': classes, 'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
limit = 15
|
||||
response = self.fetch(f'{self.host}/api.php/app/filter/vod?type_name={tid}&page={pg}&sort=hits&limit={limit}', headers=self.headers(), verify=False).json()
|
||||
videos = self.arr2vods(response['data'])
|
||||
page = int(pg)
|
||||
# 接口返回的 pageCount/total 固定为 1/15,不可靠,按实际返回条数判断是否有下一页
|
||||
pagecount = page + 1 if len(videos) == limit else page
|
||||
return {
|
||||
'page': page,
|
||||
'pagecount': pagecount,
|
||||
'limit': limit,
|
||||
'total': pagecount * limit,
|
||||
'list': videos
|
||||
}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
limit = 15
|
||||
response = self.fetch(f'{self.host}/api.php/app/search/index?wd={key}&page={pg}&limit={limit}', headers=self.headers(), verify=False).json()
|
||||
videos = self.arr2vods(response['data'])
|
||||
page = int(pg)
|
||||
# 接口返回的 pageCount/total 固定为 1/15,不可靠,按实际返回条数判断是否有下一页
|
||||
pagecount = page + 1 if len(videos) == limit else page
|
||||
return {
|
||||
'page': page,
|
||||
'pagecount': pagecount,
|
||||
'limit': limit,
|
||||
'total': pagecount * limit,
|
||||
'list': videos
|
||||
}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f'{self.host}/api.php/app/vod/get_detail?vod_id={ids[0]}', headers=self.headers(), verify=False).json()
|
||||
data = response['data'][0]
|
||||
shows, play_urls = [], []
|
||||
raw_shows = data['vod_play_from'].split('$$$')
|
||||
raw_urls_list = data['vod_play_url'].split('$$$')
|
||||
for show_code, urls_str in zip(raw_shows, raw_urls_list):
|
||||
need_parse, is_show, name, urls = 0, 0, show_code, []
|
||||
for i in response['vodplayer']:
|
||||
if i['from'] == show_code:
|
||||
is_show = 1
|
||||
need_parse = i['decode_status']
|
||||
if show_code.casefold() != i['show'].casefold():
|
||||
name = f"{i['show']}\u2005({show_code})"
|
||||
break
|
||||
if is_show == 1:
|
||||
for url_item in urls_str.split('#'):
|
||||
if '$' in url_item:
|
||||
episode, url = url_item.split('$', 1)
|
||||
urls.append(f"{episode}${show_code}@{int(need_parse)}@{url}")
|
||||
if urls:
|
||||
play_urls.append('#'.join(urls))
|
||||
shows.append(name)
|
||||
video = {
|
||||
'vod_id': data['vod_id'],
|
||||
'vod_name': data['vod_name'],
|
||||
'vod_pic': data['vod_pic'],
|
||||
'vod_remarks': data['vod_remarks'],
|
||||
'vod_year': data['vod_year'],
|
||||
'vod_area': data['vod_area'],
|
||||
'vod_actor': data['vod_actor'],
|
||||
'vod_director': data['vod_director'],
|
||||
'vod_content': data['vod_content'],
|
||||
'vod_play_from': '$$$'.join(shows),
|
||||
'vod_play_url': '$$$'.join(play_urls),
|
||||
'type_name': data['vod_class']
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, vid, vip_flags):
|
||||
play_from, need_parse, raw_url = vid.split('@', 2)
|
||||
jx, url = 0, ''
|
||||
if need_parse == '1':
|
||||
try:
|
||||
response = self.fetch(f'{self.host}/api.php/app/decode/url/?url={raw_url}&vodFrom={play_from}', headers=self.headers(), timeout=30, verify=False).json()
|
||||
play_url = response['data']
|
||||
if play_url.startswith('http'): url = play_url
|
||||
except Exception:
|
||||
pass
|
||||
if not url:
|
||||
url = raw_url
|
||||
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', raw_url):
|
||||
jx = 1
|
||||
return {'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent': 'com.sunshine.tv/1.2.0 (Linux;Android 15) AndroidXMedia3/1.4.1'}}
|
||||
|
||||
def arr2vods(self, arr):
|
||||
videos = []
|
||||
for i in arr:
|
||||
type_name = i.get('type_name')
|
||||
if i.get('vod_class'):
|
||||
type_name = f"{type_name},{i.get('vod_class', '')}"
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i['vod_name'],
|
||||
'vod_pic': i['vod_pic'],
|
||||
'vod_remarks': i['vod_remarks'],
|
||||
'type_name': type_name,
|
||||
'vod_year': i.get('vod_year')
|
||||
})
|
||||
return videos
|
||||
|
||||
def headers(self):
|
||||
timestamp = str(int(time.time()))
|
||||
nonce = ''.join(random.choice('0123456789') for _ in range(3))
|
||||
ver, pkg = '3', 'com.sunshine.tv'
|
||||
sign_str = f"finger=SF-C3B2B41F6EFFFF9869176CF68F6790E8F07506FC88632C94B4F5F0430D5498CA&id={pkg}&nonce={nonce}&sk=SK-thanks&time={timestamp}&v={ver}"
|
||||
sign = hashlib.sha256(sign_str.encode('utf-8')).hexdigest().upper()
|
||||
if not self.device_id:
|
||||
device_id_cache_key = 'com.sunshine.tv_3qys_B7k7Dt56Rn'
|
||||
self.device_id = self.getCache(device_id_cache_key)
|
||||
if not (isinstance(self.device_id, str) and len(self.device_id) == 16):
|
||||
self.device_id = ''.join(secrets.choice('0123456789abcdef') for _ in range(16))
|
||||
self.setCache(device_id_cache_key, self.device_id)
|
||||
return {
|
||||
'User-Agent': 'okhttp/4.12.0',
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'x-aid': pkg,
|
||||
'x-ave': ver,
|
||||
'x-time': timestamp,
|
||||
'x-nonc': nonce,
|
||||
'x-sign': sign,
|
||||
'x-device-id': self.device_id,
|
||||
'x-device-brand': 'vivo',
|
||||
'x-device-model': 'V2309A',
|
||||
'x-update-id': '0245861b-2ebf-5524-389d-f983830651ec'
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,119 @@
|
||||
# coding=utf-8
|
||||
#!/usr/bin/python
|
||||
# ========== 固定头部(所有TVBox爬虫通用,不可修改)==========
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
import json
|
||||
import urllib.parse
|
||||
import re
|
||||
from lxml import etree
|
||||
from urllib.parse import urljoin
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def __init__(self):
|
||||
# 唯一自定义:站点域名
|
||||
self.host = "https://www.china-eae.com"
|
||||
self.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
# ==================== 固定函数:首页分类 ====================
|
||||
def homeContent(self, filter):
|
||||
res = self.fetch(self.host, headers=self.headers)
|
||||
html = etree.HTML(res.text)
|
||||
class_list = []
|
||||
# 提取顶部导航分类
|
||||
class_items = html.xpath('//ul[@class="stui-header__menu"]//div[@class="flickity-slider"]/li/a')
|
||||
for item in class_items:
|
||||
tid_name = item.xpath('./text()')[0].strip()
|
||||
tid_url = urljoin(self.host, item.xpath('./@href')[0])
|
||||
class_list.append({
|
||||
"type_id": tid_url,
|
||||
"type_name": tid_name
|
||||
})
|
||||
return {
|
||||
'class': class_list,
|
||||
'list': []
|
||||
}
|
||||
|
||||
# ==================== 固定函数:分类分页 ====================
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
# tid是分类完整url,pg页码
|
||||
page_url = f"{tid}?page={pg}"
|
||||
res = self.fetch(page_url, headers=self.headers)
|
||||
html = etree.HTML(res.text)
|
||||
vod_items = html.xpath('//ul[@class="stui-vodlist clearfix"]/li')
|
||||
vod_list = []
|
||||
for li in vod_items:
|
||||
vod = self.parse_vod_item(li)
|
||||
vod_list.append(vod)
|
||||
return {
|
||||
'list': vod_list,
|
||||
'page': int(pg),
|
||||
'pagecount': 10
|
||||
}
|
||||
|
||||
# ==================== 固定函数:搜索 ====================
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
wd = urllib.parse.quote(key)
|
||||
search_url = f"{self.host}/msfw/search/-------------.html?wd={wd}&page={pg}"
|
||||
res = self.fetch(search_url, headers=self.headers)
|
||||
html = etree.HTML(res.text)
|
||||
vod_items = html.xpath('//ul[@class="stui-vodlist clearfix"]/li')
|
||||
vod_list = []
|
||||
for li in vod_items:
|
||||
vod = self.parse_vod_item(li)
|
||||
vod_list.append(vod)
|
||||
return {
|
||||
'list': vod_list,
|
||||
'page': int(pg),
|
||||
'pagecount': 8,
|
||||
'limit': 24,
|
||||
'total': int(pg)*24
|
||||
}
|
||||
|
||||
# ==================== 自定义工具:解析单条影片列表数据 ====================
|
||||
def parse_vod_item(self, li_xpath):
|
||||
vod_href = li_xpath.xpath('.//a[@class="stui-vodlist__thumb lazyload"]/@href')[0]
|
||||
vod_id = vod_href.replace("/about/","").replace(".html","")
|
||||
vod_pic = li_xpath.xpath('.//a[@class="stui-vodlist__thumb lazyload"]/@data-original')[0]
|
||||
vod_name = li_xpath.xpath('.//h4[@class="title text-overflow"]/a/@title')[0]
|
||||
vod_remarks = li_xpath.xpath('.//span[@class="pic-text text-right"]/text()')[0].strip()
|
||||
try:
|
||||
vod_actor = li_xpath.xpath('.//p[@class="text text-overflow text-muted hidden-xs"]/text()')[0].strip()
|
||||
except:
|
||||
vod_actor = ""
|
||||
return {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_name,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_remarks": vod_remarks,
|
||||
"vod_actor": vod_actor
|
||||
}
|
||||
|
||||
# ==================== 固定函数:详情页(播放线路,需补全播放地址解析) ====================
|
||||
def detailContent(self, ids):
|
||||
vod_list = []
|
||||
for vid in ids:
|
||||
detail_url = f"{self.host}/about/{vid}.html"
|
||||
res = self.fetch(detail_url, headers=self.headers)
|
||||
html = etree.HTML(res.text)
|
||||
vod_name = html.xpath('//h1/@title')[0]
|
||||
# 此处需自行解析页面内m3u8/mp4播放线路,示例占位
|
||||
play_url = "需解析详情页script内播放地址"
|
||||
vod_list.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": vod_name,
|
||||
"vod_play_url": play_url
|
||||
})
|
||||
return {"list": vod_list}
|
||||
|
||||
# ==================== 固定函数:播放器解析 ====================
|
||||
def playerContent(self, flag, url, headers):
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': url,
|
||||
'header': headers,
|
||||
'playType': 1
|
||||
}
|
||||
Reference in New Issue
Block a user