Sync all projects
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
const host = 'https://daishuys.com';
|
||||
const host = 'https://dsystv.com';
|
||||
const headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36',
|
||||
'Referer': host
|
||||
@@ -414,4 +414,4 @@ export default {
|
||||
detail: detail,
|
||||
search: search,
|
||||
play: play
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵推荐 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad, pad
|
||||
from Crypto.Cipher import ARC4, AES
|
||||
from urllib.parse import unquote, quote
|
||||
from base.spider import Spider
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import binascii
|
||||
import requests
|
||||
import hashlib
|
||||
import base64
|
||||
import uuid
|
||||
import hmac
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://minidrama-api.contentchina.com"
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
headers = {
|
||||
'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',
|
||||
'Connection': 'keep-alive',
|
||||
'Origin': 'https://minidrama.contentchina.com',
|
||||
'Pragma': 'no-cache',
|
||||
'Referer': 'https://minidrama.contentchina.com/',
|
||||
'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',
|
||||
'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"',
|
||||
}
|
||||
|
||||
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):
|
||||
result = {"class": []}
|
||||
def get_category_data():
|
||||
url = f"{xurl}/web/v1/home/categoryList?isLeft=1"
|
||||
response = requests.get(url=url, headers=headerx)
|
||||
response.encoding = "utf-8"
|
||||
return response.json()
|
||||
def process_categories(data):
|
||||
categories = []
|
||||
for vod in data['data']['categories']:
|
||||
category_info = {
|
||||
"type_id": vod['id'],
|
||||
"type_name": f"🌷{vod['name']}"
|
||||
}
|
||||
categories.append(category_info)
|
||||
return categories
|
||||
def build_result(categories):
|
||||
return {"class": categories}
|
||||
data = get_category_data()
|
||||
categories = process_categories(data)
|
||||
result = build_result(categories)
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
def get_page_number():
|
||||
return int(pg) if pg else 1
|
||||
def fetch_category_data(page_number):
|
||||
url = f'{xurl}/web/v1/drama/list?pageSize=24¤tPage={str(page_number)}&filterCategories[]={cid}'
|
||||
response = requests.get(url=url, headers=headerx)
|
||||
response.encoding = "utf-8"
|
||||
return response.json()
|
||||
def parse_videos(data):
|
||||
video_list = []
|
||||
for vod in data['data']['data']:
|
||||
video_info = {
|
||||
"vod_id": f"{vod['albumId']}@{vod['total']}",
|
||||
"vod_name": vod['title'],
|
||||
"vod_pic": vod['coverUrl'],
|
||||
"vod_remarks": f"🌷共{vod.get('total', '暂无备注')}集"
|
||||
}
|
||||
video_list.append(video_info)
|
||||
return video_list
|
||||
def build_result(video_list, page_number):
|
||||
return {
|
||||
'list': video_list,
|
||||
'page': page_number,
|
||||
'pagecount': 9999,
|
||||
'limit': 90,
|
||||
'total': 999999
|
||||
}
|
||||
current_page = get_page_number()
|
||||
response_data = fetch_category_data(current_page)
|
||||
parsed_videos = parse_videos(response_data)
|
||||
result = build_result(parsed_videos, current_page)
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
def parse_ids():
|
||||
did = ids[0]
|
||||
parts = did.split("@")
|
||||
return parts[0], int(parts[1])
|
||||
def generate_play_items(base_id, total_count):
|
||||
return [f"{i}${base_id}@{i}" for i in range(1, total_count + 1)]
|
||||
def build_play_url(items):
|
||||
return "#".join(items)
|
||||
def create_video_info(did, play_url):
|
||||
return {
|
||||
"vod_id": did,
|
||||
"vod_play_from": "喜福专线",
|
||||
"vod_play_url": play_url
|
||||
}
|
||||
def build_result(video_info):
|
||||
return {'list': [video_info]}
|
||||
base_id, total_count = parse_ids()
|
||||
play_items = generate_play_items(base_id, total_count)
|
||||
play_url = build_play_url(play_items)
|
||||
video_info = create_video_info(ids[0], play_url)
|
||||
result = build_result(video_info)
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
def parse_video_id():
|
||||
parts = id.split("@")
|
||||
return parts[0], parts[1]
|
||||
def get_play_auth(album_id, seq):
|
||||
url = f'{xurl}/web/v1/drama/play_auth?albumId={album_id}&seq={seq}'
|
||||
response = requests.get(url=url, headers=headerx)
|
||||
response.encoding = "utf-8"
|
||||
return response.json()
|
||||
def extract_credentials(data):
|
||||
video_id = data['data']['vid']
|
||||
play_auth_b64 = data['data']['playAuth']
|
||||
play_auth_json = base64.b64decode(play_auth_b64).decode('utf-8')
|
||||
return json.loads(play_auth_json), video_id
|
||||
def build_request_params(credentials, video_id):
|
||||
params = {
|
||||
'Action': 'GetPlayInfo',
|
||||
'Version': '2017-03-21',
|
||||
'Format': 'JSON',
|
||||
'AccessKeyId': credentials['AccessKeyId'],
|
||||
'SecurityToken': credentials['SecurityToken'],
|
||||
'VideoId': video_id,
|
||||
'AuthInfo': credentials.get('AuthInfo', ''),
|
||||
'Timestamp': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
|
||||
'SignatureMethod': 'HMAC-SHA1',
|
||||
'SignatureVersion': '1.0',
|
||||
'SignatureNonce': str(uuid.uuid4()),
|
||||
}
|
||||
if 'PlayConfig' in credentials:
|
||||
params['PlayConfig'] = json.dumps(credentials.get('PlayConfig'))
|
||||
return params
|
||||
def generate_signature(params, credentials):
|
||||
sorted_params = sorted(params.items())
|
||||
canonicalized_query_string = '&'.join(
|
||||
[f"{urllib.parse.quote(k, safe='')}={urllib.parse.quote(str(v), safe='')}" for k, v in sorted_params])
|
||||
string_to_sign = f"GET&%2F&{urllib.parse.quote(canonicalized_query_string, safe='')}"
|
||||
key = credentials['AccessKeySecret'] + "&"
|
||||
signature = hmac.new(key.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha1).digest()
|
||||
return base64.b64encode(signature).decode('utf-8'), canonicalized_query_string
|
||||
def get_final_play_url(credentials, canonicalized_query_string, signature_b64):
|
||||
final_url = f"https://vod.{credentials.get('Region', 'cn-shanghai')}.aliyuncs.com/?{canonicalized_query_string}&Signature={urllib.parse.quote(signature_b64, safe='')}"
|
||||
response = requests.get(url=final_url, headers=headers)
|
||||
response.encoding = "utf-8"
|
||||
data = response.json()
|
||||
return data['PlayInfoList']['PlayInfo'][0]['PlayURL']
|
||||
def build_result(play_url):
|
||||
return {
|
||||
"parse": 0,
|
||||
"playUrl": '',
|
||||
"url": play_url,
|
||||
"header": headerx
|
||||
}
|
||||
album_id, seq = parse_video_id()
|
||||
auth_data = get_play_auth(album_id, seq)
|
||||
credentials, video_id = extract_credentials(auth_data)
|
||||
request_params = build_request_params(credentials, video_id)
|
||||
signature_b64, canonicalized_query_string = generate_signature(request_params, credentials)
|
||||
play_url = get_final_play_url(credentials, canonicalized_query_string, signature_b64)
|
||||
result = build_result(play_url)
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
pass
|
||||
|
||||
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,240 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵推荐 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad, pad
|
||||
from Crypto.Cipher import ARC4, AES
|
||||
from urllib.parse import unquote, quote
|
||||
from base.spider import Spider
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import binascii
|
||||
import requests
|
||||
import hashlib
|
||||
import base64
|
||||
import uuid
|
||||
import hmac
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://minidrama-api.contentchina.com"
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
headers = {
|
||||
'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',
|
||||
'Connection': 'keep-alive',
|
||||
'Origin': 'https://minidrama.contentchina.com',
|
||||
'Pragma': 'no-cache',
|
||||
'Referer': 'https://minidrama.contentchina.com/',
|
||||
'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',
|
||||
'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"',
|
||||
}
|
||||
|
||||
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):
|
||||
result = {"class": []}
|
||||
def get_category_data():
|
||||
url = f"{xurl}/web/v1/home/categoryList?isLeft=1"
|
||||
response = requests.get(url=url, headers=headerx)
|
||||
response.encoding = "utf-8"
|
||||
return response.json()
|
||||
def process_categories(data):
|
||||
categories = []
|
||||
for vod in data['data']['categories']:
|
||||
category_info = {
|
||||
"type_id": vod['id'],
|
||||
"type_name": f"🌷{vod['name']}"
|
||||
}
|
||||
categories.append(category_info)
|
||||
return categories
|
||||
def build_result(categories):
|
||||
return {"class": categories}
|
||||
data = get_category_data()
|
||||
categories = process_categories(data)
|
||||
result = build_result(categories)
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
def get_page_number():
|
||||
return int(pg) if pg else 1
|
||||
def fetch_category_data(page_number):
|
||||
url = f'{xurl}/web/v1/drama/list?pageSize=24¤tPage={str(page_number)}&filterCategories[]={cid}'
|
||||
response = requests.get(url=url, headers=headerx)
|
||||
response.encoding = "utf-8"
|
||||
return response.json()
|
||||
def parse_videos(data):
|
||||
video_list = []
|
||||
for vod in data['data']['data']:
|
||||
video_info = {
|
||||
"vod_id": f"{vod['albumId']}@{vod['total']}",
|
||||
"vod_name": vod['title'],
|
||||
"vod_pic": vod['coverUrl'],
|
||||
"vod_remarks": f"🌷共{vod.get('total', '暂无备注')}集"
|
||||
}
|
||||
video_list.append(video_info)
|
||||
return video_list
|
||||
def build_result(video_list, page_number):
|
||||
return {
|
||||
'list': video_list,
|
||||
'page': page_number,
|
||||
'pagecount': 9999,
|
||||
'limit': 90,
|
||||
'total': 999999
|
||||
}
|
||||
current_page = get_page_number()
|
||||
response_data = fetch_category_data(current_page)
|
||||
parsed_videos = parse_videos(response_data)
|
||||
result = build_result(parsed_videos, current_page)
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
def parse_ids():
|
||||
did = ids[0]
|
||||
parts = did.split("@")
|
||||
return parts[0], int(parts[1])
|
||||
def generate_play_items(base_id, total_count):
|
||||
return [f"{i}${base_id}@{i}" for i in range(1, total_count + 1)]
|
||||
def build_play_url(items):
|
||||
return "#".join(items)
|
||||
def create_video_info(did, play_url):
|
||||
return {
|
||||
"vod_id": did,
|
||||
"vod_play_from": "喜福专线",
|
||||
"vod_play_url": play_url
|
||||
}
|
||||
def build_result(video_info):
|
||||
return {'list': [video_info]}
|
||||
base_id, total_count = parse_ids()
|
||||
play_items = generate_play_items(base_id, total_count)
|
||||
play_url = build_play_url(play_items)
|
||||
video_info = create_video_info(ids[0], play_url)
|
||||
result = build_result(video_info)
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
def parse_video_id():
|
||||
parts = id.split("@")
|
||||
return parts[0], parts[1]
|
||||
def get_play_auth(album_id, seq):
|
||||
url = f'{xurl}/web/v1/drama/play_auth?albumId={album_id}&seq={seq}'
|
||||
response = requests.get(url=url, headers=headerx)
|
||||
response.encoding = "utf-8"
|
||||
return response.json()
|
||||
def extract_credentials(data):
|
||||
video_id = data['data']['vid']
|
||||
play_auth_b64 = data['data']['playAuth']
|
||||
play_auth_json = base64.b64decode(play_auth_b64).decode('utf-8')
|
||||
return json.loads(play_auth_json), video_id
|
||||
def build_request_params(credentials, video_id):
|
||||
params = {
|
||||
'Action': 'GetPlayInfo',
|
||||
'Version': '2017-03-21',
|
||||
'Format': 'JSON',
|
||||
'AccessKeyId': credentials['AccessKeyId'],
|
||||
'SecurityToken': credentials['SecurityToken'],
|
||||
'VideoId': video_id,
|
||||
'AuthInfo': credentials.get('AuthInfo', ''),
|
||||
'Timestamp': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
|
||||
'SignatureMethod': 'HMAC-SHA1',
|
||||
'SignatureVersion': '1.0',
|
||||
'SignatureNonce': str(uuid.uuid4()),
|
||||
}
|
||||
if 'PlayConfig' in credentials:
|
||||
params['PlayConfig'] = json.dumps(credentials.get('PlayConfig'))
|
||||
return params
|
||||
def generate_signature(params, credentials):
|
||||
sorted_params = sorted(params.items())
|
||||
canonicalized_query_string = '&'.join(
|
||||
[f"{urllib.parse.quote(k, safe='')}={urllib.parse.quote(str(v), safe='')}" for k, v in sorted_params])
|
||||
string_to_sign = f"GET&%2F&{urllib.parse.quote(canonicalized_query_string, safe='')}"
|
||||
key = credentials['AccessKeySecret'] + "&"
|
||||
signature = hmac.new(key.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha1).digest()
|
||||
return base64.b64encode(signature).decode('utf-8'), canonicalized_query_string
|
||||
def get_final_play_url(credentials, canonicalized_query_string, signature_b64):
|
||||
final_url = f"https://vod.{credentials.get('Region', 'cn-shanghai')}.aliyuncs.com/?{canonicalized_query_string}&Signature={urllib.parse.quote(signature_b64, safe='')}"
|
||||
response = requests.get(url=final_url, headers=headers)
|
||||
response.encoding = "utf-8"
|
||||
data = response.json()
|
||||
return data['PlayInfoList']['PlayInfo'][0]['PlayURL']
|
||||
def build_result(play_url):
|
||||
return {
|
||||
"parse": 0,
|
||||
"playUrl": '',
|
||||
"url": play_url,
|
||||
"header": headerx
|
||||
}
|
||||
album_id, seq = parse_video_id()
|
||||
auth_data = get_play_auth(album_id, seq)
|
||||
credentials, video_id = extract_credentials(auth_data)
|
||||
request_params = build_request_params(credentials, video_id)
|
||||
signature_b64, canonicalized_query_string = generate_signature(request_params, credentials)
|
||||
play_url = get_final_play_url(credentials, canonicalized_query_string, signature_b64)
|
||||
result = build_result(play_url)
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
pass
|
||||
|
||||
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,675 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
import base64
|
||||
import threading
|
||||
import requests
|
||||
import urllib3
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
from datetime import datetime
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from socketserver import ThreadingMixIn
|
||||
from urllib.parse import unquote, quote, urljoin, urlparse
|
||||
|
||||
urllib3.disable_warnings()
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider as BaseSpider
|
||||
|
||||
# ========================== 全局本地代理服务器 ==========================
|
||||
# 用于解决图片防盗链、跨域问题,所有外域图片均通过 127.0.0.1 代理访问
|
||||
_proxy_port = 0
|
||||
_proxy_started = False
|
||||
_proxy_session = requests.Session()
|
||||
_proxy_session.verify = False
|
||||
_proxy_headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Referer': 'https://dadiao.cc/',
|
||||
}
|
||||
|
||||
class _ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
|
||||
class _ProxyHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
try:
|
||||
real_url = unquote(self.path[1:])
|
||||
if not real_url or not real_url.startswith('http'):
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
parsed = urlparse(real_url)
|
||||
referer = f'{parsed.scheme}://{parsed.netloc}/' if parsed.netloc else 'https://dadiao.cc/'
|
||||
headers = dict(_proxy_headers)
|
||||
headers['Referer'] = referer
|
||||
r = _proxy_session.get(real_url, headers=headers, timeout=20, verify=False, stream=True)
|
||||
content_type = r.headers.get('Content-Type', 'image/jpeg')
|
||||
content_length = r.headers.get('Content-Length')
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', content_type)
|
||||
if content_length:
|
||||
self.send_header('Content-Length', content_length)
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||||
self.end_headers()
|
||||
for chunk in r.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
self.wfile.write(chunk)
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
except Exception as e:
|
||||
try:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
except:
|
||||
pass
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
def _find_free_port():
|
||||
import socket
|
||||
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sk.bind(('127.0.0.1', 0))
|
||||
port = sk.getsockname()[1]
|
||||
sk.close()
|
||||
return port
|
||||
|
||||
def _start_proxy():
|
||||
global _proxy_port, _proxy_started
|
||||
if _proxy_started:
|
||||
return
|
||||
_proxy_port = _find_free_port()
|
||||
server = _ThreadedHTTPServer(('127.0.0.1', _proxy_port), _ProxyHandler)
|
||||
threading.Thread(target=server.serve_forever, daemon=True).start()
|
||||
_proxy_started = True
|
||||
|
||||
# ========================== Spider 主体 ==========================
|
||||
class Spider(BaseSpider):
|
||||
session = requests.Session()
|
||||
host = 'https://dadiao.cc'
|
||||
play_host = 'https://m.892539.xyz'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._categories_cache = None
|
||||
self._zone_map = {}
|
||||
self._sub_cat_names = {}
|
||||
self._debug = True
|
||||
self._log('Spider 初始化完成')
|
||||
|
||||
def _log(self, msg):
|
||||
if self._debug:
|
||||
print(f'[dadiao] {msg}')
|
||||
|
||||
def getName(self):
|
||||
return 'dadiao'
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
if not url:
|
||||
return False
|
||||
return '.m3u8' in url or '.mp4' in url or '.ts' in url or url.startswith('magnet:')
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
return [404, 'text/plain', '']
|
||||
|
||||
def init(self, extend=''):
|
||||
self.session.verify = False
|
||||
self.session.headers.update(self._get_headers())
|
||||
_start_proxy()
|
||||
text = self._fetch(self.host)
|
||||
if text:
|
||||
self._load_categories(text)
|
||||
|
||||
def _get_headers(self, referer=None):
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Referer': referer or self.host + '/',
|
||||
}
|
||||
return headers
|
||||
|
||||
def _proxy_url(self, url):
|
||||
if not url:
|
||||
return ''
|
||||
if url.startswith('http://127.0.0.1'):
|
||||
return url
|
||||
if url.startswith('//'):
|
||||
url = 'https:' + url
|
||||
elif url.startswith('/'):
|
||||
url = urljoin(self.host, url)
|
||||
return f'http://127.0.0.1:{_proxy_port}/{quote(url, safe="")}'
|
||||
|
||||
def _fetch(self, url, referer=None, retries=3):
|
||||
for i in range(retries):
|
||||
try:
|
||||
if referer is None:
|
||||
referer = self.host + '/'
|
||||
headers = self._get_headers(referer)
|
||||
if i > 0:
|
||||
time.sleep(random.uniform(0.5, 1.5))
|
||||
r = self.session.get(url, headers=headers, timeout=30, verify=False)
|
||||
r.encoding = 'utf-8'
|
||||
if r.status_code == 200:
|
||||
return r.text
|
||||
elif r.status_code in [403, 429, 503]:
|
||||
self._log(f'请求被拦截 [{r.status_code}],重试 {i+1}/{retries}')
|
||||
else:
|
||||
self._log(f'请求返回 [{r.status_code}],终止')
|
||||
return ''
|
||||
except Exception as e:
|
||||
self._log(f'请求异常 [{e}],重试 {i+1}/{retries}')
|
||||
return ''
|
||||
|
||||
# ========================== 分类加载(蜜桃式:区域为class,子分类为filters) ==========================
|
||||
def _load_categories(self, text):
|
||||
if not text:
|
||||
self._log('首页HTML为空,无法加载分类')
|
||||
return []
|
||||
self._zone_map = {}
|
||||
self._sub_cat_names = {}
|
||||
|
||||
# 按 zone-tag 与 cate-items 顺序配对提取
|
||||
zone_names = re.findall(r'<div class="zone-tag">\s*(.*?)\s*</div>', text)
|
||||
items_blocks = re.findall(r'<div class="cate-items">(.*?)</div>', text, re.S)
|
||||
|
||||
if len(zone_names) == len(items_blocks) and len(zone_names) > 0:
|
||||
self._log(f'区域配对成功: {len(zone_names)} 个区域')
|
||||
for i, zone_name in enumerate(zone_names):
|
||||
zone_name = zone_name.strip()
|
||||
items_html = items_blocks[i]
|
||||
tids = []
|
||||
for tid, name in re.findall(r'<a href="/list/(\d+)-1\.html"[^>]*>([^<]+)</a>', items_html):
|
||||
name = name.strip()
|
||||
if not name:
|
||||
continue
|
||||
tids.append(tid)
|
||||
self._sub_cat_names[tid] = name
|
||||
if tids:
|
||||
self._zone_map[zone_name] = tids
|
||||
self._log(f'区域[{zone_name}]: {len(tids)} 个子分类')
|
||||
else:
|
||||
self._log('区域配对失败,启用兜底提取')
|
||||
seen_tid = set()
|
||||
for tid, name in re.findall(r'<a href="/list/(\d+)-1\.html"[^>]*>([^<]+)</a>', text):
|
||||
name = name.strip()
|
||||
if not name or tid in seen_tid:
|
||||
continue
|
||||
seen_tid.add(tid)
|
||||
self._sub_cat_names[tid] = name
|
||||
if self._sub_cat_names:
|
||||
all_tids = list(self._sub_cat_names.keys())
|
||||
self._zone_map['全部视频'] = all_tids
|
||||
self._log(f'兜底提取: {len(all_tids)} 个分类归入"全部视频"')
|
||||
|
||||
self._categories_cache = list(self._zone_map.keys())
|
||||
self._log(f'分类加载完成: 共 {len(self._categories_cache)} 个区域')
|
||||
return self._categories_cache
|
||||
|
||||
def _get_zone_tids(self, zone_name):
|
||||
return self._zone_map.get(zone_name, [])
|
||||
|
||||
def _get_sub_name(self, tid):
|
||||
return self._sub_cat_names.get(tid, tid)
|
||||
|
||||
# ========================== 列表解析(修复封面图片) ==========================
|
||||
def _parse_list(self, html):
|
||||
items = []
|
||||
li_blocks = re.findall(
|
||||
r'<li>\s*<a class="thumb-card" href="(/(?:video|torrent)/([^"]+)\.html)"[^>]*>.*?<div class="thumb-desc">.*?</div>\s*</li>',
|
||||
html, re.S
|
||||
)
|
||||
for href, vid in li_blocks:
|
||||
li_match = re.search(
|
||||
r'<li>\s*<a class="thumb-card" href="' + re.escape(href) + r'"[^>]*>.*?</a>\s*<div class="thumb-desc">.*?</div>\s*</li>',
|
||||
html, re.S
|
||||
)
|
||||
if not li_match:
|
||||
continue
|
||||
li_html = li_match.group(0)
|
||||
|
||||
title = vid
|
||||
h5_match = re.search(r'<h5><a[^>]*>(.*?)</a></h5>', li_html, re.S)
|
||||
if h5_match:
|
||||
title = re.sub(r'<[^>]+>', '', h5_match.group(1)).strip()
|
||||
|
||||
pic = ''
|
||||
pic_match = re.search(r'<img[^>]+data-original="([^"]+)"', li_html, re.S)
|
||||
if pic_match:
|
||||
pic = pic_match.group(1).strip()
|
||||
else:
|
||||
pic_match = re.search(r'<img[^>]+src="([^"]+)"', li_html, re.S)
|
||||
if pic_match:
|
||||
pic = pic_match.group(1).strip()
|
||||
|
||||
if pic and not pic.endswith('loading.svg') and not pic.endswith('loading.gif'):
|
||||
if pic.startswith('//'):
|
||||
pic = 'https:' + pic
|
||||
elif pic.startswith('/'):
|
||||
pic = urljoin(self.host, pic)
|
||||
else:
|
||||
pic = ''
|
||||
|
||||
is_torrent = href.startswith('/torrent/')
|
||||
items.append({
|
||||
'vod_id': f'torrent_{vid}' if is_torrent else vid,
|
||||
'vod_name': title,
|
||||
'vod_pic': self._proxy_url(pic),
|
||||
'vod_remarks': '磁力' if is_torrent else '',
|
||||
})
|
||||
return items
|
||||
|
||||
def _get_list(self, tid, page):
|
||||
url = f'{self.host}/list/{tid}-{page}.html'
|
||||
html = self._fetch(url, referer=f'{self.host}/list/{tid}-1.html')
|
||||
if not html:
|
||||
return []
|
||||
return self._parse_list(html)
|
||||
|
||||
# ========================== 首页(蜜桃式:区域为class + 子分类filters) ==========================
|
||||
def homeContent(self, filter):
|
||||
try:
|
||||
text = self._fetch(self.host)
|
||||
if text:
|
||||
self._load_categories(text)
|
||||
|
||||
classes = []
|
||||
filters = {}
|
||||
|
||||
# 遍历所有区域,构建 class 和 filter
|
||||
for zone_name, tids in self._zone_map.items():
|
||||
# 区域作为一级分类
|
||||
zone_tid = 'zone:' + ','.join(tids)
|
||||
classes.append({'type_id': zone_tid, 'type_name': zone_name})
|
||||
|
||||
# 该区域的子分类作为顶部筛选器
|
||||
sub_values = []
|
||||
for tid in tids:
|
||||
sub_name = self._sub_cat_names.get(tid, tid)
|
||||
sub_values.append({'n': sub_name, 'v': tid})
|
||||
if sub_values:
|
||||
filters[zone_tid] = [
|
||||
{
|
||||
'key': 'sub',
|
||||
'name': '子分类',
|
||||
'value': sub_values
|
||||
}
|
||||
]
|
||||
else:
|
||||
filters[zone_tid] = []
|
||||
|
||||
return {
|
||||
'class': classes,
|
||||
'filters': filters,
|
||||
'type': '影视',
|
||||
'list': [],
|
||||
'page': 1,
|
||||
'pagecount': 1,
|
||||
'limit': 0,
|
||||
'total': 0
|
||||
}
|
||||
except Exception as e:
|
||||
self._log(f'homeContent 异常: {e}')
|
||||
return {
|
||||
'class': [], 'filters': {}, 'type': '影视', 'list': [],
|
||||
'page': 1, 'pagecount': 1, 'limit': 0, 'total': 0
|
||||
}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {'list': []}
|
||||
|
||||
# ========================== 分类内容(蜜桃式路由分发) ==========================
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
try:
|
||||
# 兼容 TVBox 不同版本:extend 可能是 JSON 字符串
|
||||
if isinstance(extend, str):
|
||||
try:
|
||||
extend = json.loads(extend)
|
||||
except:
|
||||
extend = {}
|
||||
|
||||
# 路由1:区域分类(tid 以 zone: 开头)
|
||||
if tid and tid.startswith('zone:'):
|
||||
tids = tid.replace('zone:', '').split(',')
|
||||
if extend and isinstance(extend, dict) and 'sub' in extend:
|
||||
target_tid = extend['sub']
|
||||
self._log(f'区域[{tid}] 选择子分类: {target_tid}')
|
||||
return self._do_category(target_tid, pg)
|
||||
else:
|
||||
# 默认显示该区域下第一个子分类
|
||||
if tids:
|
||||
self._log(f'区域[{tid}] 默认显示首个子分类: {tids[0]}')
|
||||
return self._do_category(tids[0], pg)
|
||||
return {'list': [], 'page': 1, 'pagecount': 1, 'limit': 0, 'total': 0}
|
||||
|
||||
# 路由2:直接传入子分类ID(数字ID)
|
||||
else:
|
||||
self._log(f'直接加载子分类: {tid}')
|
||||
return self._do_category(tid, pg)
|
||||
|
||||
except Exception as e:
|
||||
self._log(f'categoryContent 异常: {e}')
|
||||
return {
|
||||
'list': [], 'page': 1, 'pagecount': 1,
|
||||
'limit': 0, 'total': 0
|
||||
}
|
||||
|
||||
def _do_category(self, tid, pg):
|
||||
page = int(pg) if pg else 1
|
||||
items = self._get_list(tid, page)
|
||||
total_page = page + 1
|
||||
if page == 1:
|
||||
html = self._fetch(f'{self.host}/list/{tid}-1.html')
|
||||
if html:
|
||||
pages = re.findall(r'/list/\d+-(\d+)\.html', html)
|
||||
if pages:
|
||||
total_page = max(int(p) for p in pages)
|
||||
return {
|
||||
'list': items,
|
||||
'page': page,
|
||||
'pagecount': total_page,
|
||||
'limit': len(items),
|
||||
'total': total_page * len(items)
|
||||
}
|
||||
|
||||
# ========================== 详情页 ==========================
|
||||
def _fetch_detail(self, vid):
|
||||
if vid.startswith('torrent_'):
|
||||
real_id = vid.replace('torrent_', '')
|
||||
url = f'{self.host}/torrent/{real_id}.html'
|
||||
self._log(f'获取磁力详情: {url}')
|
||||
html = self._fetch(url, referer=self.host)
|
||||
if html:
|
||||
return self._parse_detail(html, vid, url, is_torrent=True)
|
||||
return None
|
||||
|
||||
url = f'{self.host}/video/{vid}.html'
|
||||
self._log(f'获取视频详情: {url}')
|
||||
html = self._fetch(url, referer=self.host)
|
||||
if html:
|
||||
detail = self._parse_detail(html, vid, url)
|
||||
if detail and detail.get('vod_play_url'):
|
||||
return detail
|
||||
return None
|
||||
|
||||
def _parse_detail(self, html, vid, base_url, is_torrent=False):
|
||||
title = ''
|
||||
m = re.search(r'<h1[^>]*>(.*?)</h1>', html, re.S)
|
||||
if m:
|
||||
title = re.sub(r'<[^>]+>', '', m.group(1)).strip()
|
||||
if not title:
|
||||
m = re.search(r'<title>([^<]+)</title>', html)
|
||||
if m:
|
||||
title = m.group(1).strip()
|
||||
|
||||
cover = ''
|
||||
m = re.search(r'<meta[^>]*property="og:image"[^>]*content="([^"]+)"', html)
|
||||
if m:
|
||||
cover = m.group(1).strip()
|
||||
if not cover:
|
||||
m = re.search(r'<img[^>]+class="[^"]*cover[^"]*"[^>]+src="([^"]+)"', html, re.S)
|
||||
if m:
|
||||
cover = m.group(1).strip()
|
||||
if not cover:
|
||||
m = re.search(r'<img[^>]+data-original="([^"]+)"', html)
|
||||
if m:
|
||||
cover = m.group(1).strip()
|
||||
if not cover:
|
||||
m = re.search(r'<video[^>]+poster="([^"]+)"', html)
|
||||
if m:
|
||||
cover = m.group(1).strip()
|
||||
|
||||
if cover:
|
||||
if cover.startswith('//'):
|
||||
cover = 'https:' + cover
|
||||
elif cover.startswith('/'):
|
||||
cover = urljoin(self.host, cover)
|
||||
|
||||
play_urls = []
|
||||
seen = set()
|
||||
|
||||
def add(label, url):
|
||||
if not url or url in seen:
|
||||
return
|
||||
seen.add(url)
|
||||
play_urls.append(f'{label}${url}')
|
||||
|
||||
if not is_torrent:
|
||||
# ==================== 视频解析逻辑 ====================
|
||||
site_id = ''
|
||||
source_id = ''
|
||||
|
||||
# 1. 优先从页面底部 HTML 注释提取
|
||||
comment_match = re.search(r'<!--\s*source_id:(\d+),\s*site_id:(\d+)', html)
|
||||
if comment_match:
|
||||
source_id = comment_match.group(1)
|
||||
site_id = comment_match.group(2)
|
||||
self._log(f'从注释提取参数: site_id={site_id}, source_id={source_id}')
|
||||
|
||||
# 2. 从 HLS.js 初始化脚本中提取
|
||||
if not site_id or not source_id:
|
||||
hls_match = re.search(
|
||||
r'hls\.loadSource\([\'"](https?://[^\'"]+)[\'"]\)', html
|
||||
)
|
||||
if hls_match:
|
||||
hls_url = hls_match.group(1)
|
||||
sid_match = re.search(r'site_id=(\d+)', hls_url)
|
||||
src_match = re.search(r'source_id=(\d+)', hls_url)
|
||||
if sid_match and src_match:
|
||||
site_id = sid_match.group(1)
|
||||
source_id = src_match.group(1)
|
||||
self._log(f'从HLS脚本提取: site_id={site_id}, source_id={source_id}')
|
||||
|
||||
# 3. 从页面变量兜底提取
|
||||
if not site_id:
|
||||
m_sid = re.search(r'site_id[=:]\s*(\d+)', html)
|
||||
if m_sid:
|
||||
site_id = m_sid.group(1)
|
||||
if not source_id:
|
||||
m_src = re.search(r'source_id[=:]\s*(\d+)', html)
|
||||
if m_src:
|
||||
source_id = m_src.group(1)
|
||||
|
||||
# 构建真实播放地址
|
||||
if site_id and source_id:
|
||||
play_url = f'{self.play_host}/play.php?site_id={site_id}&source_id={source_id}'
|
||||
add('HLS直链', play_url)
|
||||
|
||||
# 4. 兜底:嗅探页面中直接出现的 m3u8/mp4 媒体链接
|
||||
for media in set(re.findall(
|
||||
r'https?://[^\s"\'<>]+\.(?:m3u8|mp4|flv|mkv|ts)(?:\?[^\s"\'<>]*)?', html
|
||||
)):
|
||||
add('媒体直链', media)
|
||||
|
||||
# 5. 嗅探 iframe 嵌入播放器
|
||||
for src in set(re.findall(
|
||||
r'<iframe[^>]+(?:src|data-src)=["\']([^"\']+)["\']', html
|
||||
)):
|
||||
if any(k in src for k in ['play.php', 'm3u8', 'mp4', 'embed', 'player']):
|
||||
full_src = src if src.startswith('http') else urljoin(base_url, src)
|
||||
add('外链播放器', full_src)
|
||||
|
||||
# 6. 嗅探 script 中的 Base64 加密链接
|
||||
scripts = re.findall(r'<script[^>]*>(.*?)</script>', html, re.S)
|
||||
for script in scripts:
|
||||
for b64 in re.findall(r'["\']([A-Za-z0-9+/]{20,}={0,2})["\']', script):
|
||||
try:
|
||||
dec = base64.b64decode(b64).decode('utf-8')
|
||||
if dec.startswith('http') and any(x in dec for x in ['.m3u8', '.mp4', 'play.php']):
|
||||
add('Base64解码', dec)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 7. 最终兜底
|
||||
if not play_urls:
|
||||
add('默认线路', base_url)
|
||||
else:
|
||||
# ==================== 磁力解析(核心修复) ====================
|
||||
# 1. 优先解析 j_b64 JSON(该站点标准加密方式)
|
||||
b64_match = re.search(r"var j_b64\s*=\s*['\"]([A-Za-z0-9+/=]+)['\"]", html)
|
||||
if b64_match:
|
||||
try:
|
||||
decoded = base64.b64decode(b64_match.group(1)).decode('utf-8')
|
||||
j_data = json.loads(decoded)
|
||||
if 'tm' in j_data and j_data['tm']:
|
||||
add('磁力链接', j_data['tm'])
|
||||
self._log(f'从 j_b64 解析到磁力: {j_data["tm"]}')
|
||||
if 'tt' in j_data and j_data['tt']:
|
||||
add('种子文件', j_data['tt'])
|
||||
self._log(f'从 j_b64 解析到种子: {j_data["tt"]}')
|
||||
except Exception as e:
|
||||
self._log(f'解析 j_b64 失败: {e}')
|
||||
|
||||
# 2. 正则兜底匹配页面中的磁力链接
|
||||
if not play_urls:
|
||||
magnets = set(re.findall(r'magnet:\?xt=urn:btih:[A-Za-z0-9]+[^\s"\'<>]*', html))
|
||||
href_magnets = re.findall(r'href=["\'](magnet:\?xt=urn:btih:[^"\']+)["\']', html)
|
||||
for hm in href_magnets:
|
||||
decoded = unquote(hm)
|
||||
if decoded.startswith('magnet:'):
|
||||
magnets.add(decoded)
|
||||
for mag in magnets:
|
||||
add('磁力链接', mag)
|
||||
|
||||
# 3. 尝试从页面提取 40 位 hash 构造磁力(极端兜底)
|
||||
if not play_urls:
|
||||
hash_match = re.search(r'[A-Fa-f0-9]{40}', html)
|
||||
if hash_match:
|
||||
hash40 = hash_match.group(0)
|
||||
add('磁力链接', f'magnet:?xt=urn:btih:{hash40}')
|
||||
self._log(f'从 hash 构造磁力: {hash40}')
|
||||
|
||||
# ==================== 组装结果 ====================
|
||||
sources = []
|
||||
urls = []
|
||||
for pu in play_urls:
|
||||
parts = pu.split('$', 1)
|
||||
if len(parts) == 2:
|
||||
sn, url = parts
|
||||
sources.append(sn)
|
||||
urls.append(f'{sn}${url}')
|
||||
|
||||
return {
|
||||
'vod_id': vid,
|
||||
'vod_name': title or vid,
|
||||
'vod_pic': self._proxy_url(cover) if cover else '',
|
||||
'vod_play_from': '$$$'.join(sources) if sources else '默认',
|
||||
'vod_play_url': '#'.join(urls) if urls else f'默认${base_url}',
|
||||
'vod_content': title or '',
|
||||
}
|
||||
|
||||
def detailContent(self, ids):
|
||||
try:
|
||||
vid = str(ids[0] if isinstance(ids, list) else ids)
|
||||
if vid.startswith('magnet:'):
|
||||
return {
|
||||
'list': [{
|
||||
'vod_id': vid,
|
||||
'vod_name': '磁力资源',
|
||||
'vod_pic': '',
|
||||
'vod_play_from': '磁力链接',
|
||||
'vod_play_url': f'磁力链接${vid}'
|
||||
}]
|
||||
}
|
||||
detail = self._fetch_detail(vid)
|
||||
if not detail:
|
||||
return {'list': []}
|
||||
return {'list': [detail]}
|
||||
except Exception as e:
|
||||
self._log(f'detailContent 异常: {e}')
|
||||
return {'list': []}
|
||||
|
||||
# ========================== 播放器 ==========================
|
||||
def playerContent(self, flag, id, vipFlags=None):
|
||||
try:
|
||||
# 磁力链接直接透传,交给 TVBox 内置下载器或第三方 App
|
||||
if id.startswith('magnet:'):
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': id,
|
||||
'header': {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
}
|
||||
}
|
||||
|
||||
# 若传入的是纯 vid(非完整 URL),自动解析一次真实播放地址
|
||||
if id and not id.startswith('http') and not id.startswith('magnet:'):
|
||||
detail = self._fetch_detail(id)
|
||||
if detail and detail.get('vod_play_url'):
|
||||
first = detail['vod_play_url'].split('#')[0]
|
||||
if '$' in first:
|
||||
id = first.split('$', 1)[1]
|
||||
else:
|
||||
id = first
|
||||
|
||||
# 根据目标域名动态设置 Referer,降低被拦截概率
|
||||
referer = self.host
|
||||
if id and id.startswith('http'):
|
||||
parsed = urlparse(id)
|
||||
if parsed.netloc:
|
||||
referer = f'{parsed.scheme}://{parsed.netloc}/'
|
||||
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': id,
|
||||
'header': {
|
||||
'Referer': referer,
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
self._log(f'playerContent 异常: {e}')
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': '',
|
||||
'header': {}
|
||||
}
|
||||
|
||||
# ========================== 搜索 ==========================
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
try:
|
||||
page = int(pg) if pg else 1
|
||||
all_items = []
|
||||
|
||||
# 先搜索视频(type=1)
|
||||
url = f'{self.host}/search.php?content={quote(key)}&type=1&page={page}'
|
||||
self._log(f'搜索视频: {url}')
|
||||
html = self._fetch(url, referer=self.host)
|
||||
if html:
|
||||
items = self._parse_list(html)
|
||||
all_items.extend(items)
|
||||
|
||||
# 如果视频无结果,再搜索磁力(type=2)
|
||||
if not all_items:
|
||||
url = f'{self.host}/search.php?content={quote(key)}&type=2&page={page}'
|
||||
self._log(f'搜索磁力: {url}')
|
||||
html = self._fetch(url, referer=self.host)
|
||||
if html:
|
||||
items = self._parse_list(html)
|
||||
all_items.extend(items)
|
||||
|
||||
return {
|
||||
'list': all_items,
|
||||
'page': page,
|
||||
'pagecount': page + 1,
|
||||
'limit': len(all_items),
|
||||
'total': page * len(all_items)
|
||||
}
|
||||
except Exception as e:
|
||||
self._log(f'searchContent 异常: {e}')
|
||||
return {
|
||||
'list': [], 'page': 1, 'pagecount': 1,
|
||||
'limit': 0, 'total': 0
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵 🚓 内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
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 urllib.request
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import binascii
|
||||
import requests
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "http://read.api.duodutek.com"
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
global xurl
|
||||
global headerx
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
|
||||
if pl == 3:
|
||||
plx = []
|
||||
while True:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
break
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
break
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
plx.append(middle_text)
|
||||
text = text.replace(start_str + middle_text + end_str, '')
|
||||
if len(plx) > 0:
|
||||
purl = ''
|
||||
for i in range(len(plx)):
|
||||
matches = re.findall(start_index1, plx[i])
|
||||
output = ""
|
||||
for match in matches:
|
||||
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
|
||||
if match3:
|
||||
number = match3.group(1)
|
||||
else:
|
||||
number = 0
|
||||
if 'http' not in match[0]:
|
||||
output += f"#{match[1]}${number}{xurl}{match[0]}"
|
||||
else:
|
||||
output += f"#{match[1]}${number}{match[0]}"
|
||||
output = output[1:]
|
||||
purl = purl + output + "$$$"
|
||||
purl = purl[:-3]
|
||||
return purl
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
return ""
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
return ""
|
||||
|
||||
if pl == 0:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
return middle_text.replace("\\", "")
|
||||
|
||||
if pl == 1:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
jg = ' '.join(matches)
|
||||
return jg
|
||||
|
||||
if pl == 2:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
new_list = [f'{item}' for item in matches]
|
||||
jg = '$$$'.join(new_list)
|
||||
return jg
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
result = {"class": [{"type_id": "1287", "type_name": "甜宠"},
|
||||
{"type_id": "1288", "type_name": "逆袭"},
|
||||
{"type_id": "1289", "type_name": "热血"},
|
||||
{"type_id": "1290", "type_name": "现代"},
|
||||
{"type_id": "1291", "type_name": "古代"}],
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
result = {}
|
||||
videos = []
|
||||
|
||||
if pg:
|
||||
page = int(pg)
|
||||
else:
|
||||
page = 1
|
||||
|
||||
url = f"{xurl}/novel-api/app/pageModel/getResourceById"
|
||||
|
||||
params = {
|
||||
"productId": "2a8c14d1-72e7-498b-af23-381028eb47c0",
|
||||
"vestId": "2be070e0-c824-4d0e-a67a-8f688890cadb",
|
||||
"channel": "oppo19",
|
||||
"osType": "android",
|
||||
"version": "20",
|
||||
"token": "202509271001001446030204698626",
|
||||
"resourceId": cid,
|
||||
"pageNum": str(page),
|
||||
"pageSize": "10"
|
||||
}
|
||||
|
||||
detail = requests.get(url=url, headers=headerx, params=params)
|
||||
detail.encoding = "utf-8"
|
||||
data = detail.json()
|
||||
|
||||
data = data['data']['datalist']
|
||||
|
||||
for vod in data:
|
||||
|
||||
name = vod['name']
|
||||
|
||||
id = f"{vod['id']}@{vod['introduction']}"
|
||||
|
||||
pic = vod['icon']
|
||||
|
||||
remark = f"{vod['heat']}万播放"
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
result = {}
|
||||
videos = []
|
||||
xianlu = ''
|
||||
bofang = ''
|
||||
|
||||
fenge = did.split("@")
|
||||
|
||||
url = f"{xurl}/novel-api/basedata/book/getChapterList"
|
||||
|
||||
params = {
|
||||
"bookId": fenge[0],
|
||||
"productId": "2a8c14d1-72e7-498b-af23-381028eb47c0",
|
||||
"vestId": "2be070e0-c824-4d0e-a67a-8f688890cadb",
|
||||
"channel": "oppo19",
|
||||
"osType": "android",
|
||||
"version": "20",
|
||||
"token": "202509271001001446030204698626"
|
||||
}
|
||||
|
||||
detail = requests.get(url=url, headers=headerx, params=params)
|
||||
detail.encoding = "utf-8"
|
||||
data = detail.json()
|
||||
|
||||
url = 'https://fs-im-kefu.7moor-fs1.com/ly/4d2c3f00-7d4c-11e5-af15-41bf63ae4ea0/1732697392729/didiu.txt'
|
||||
response = requests.get(url)
|
||||
response.encoding = 'utf-8'
|
||||
code = response.text
|
||||
name = self.extract_middle_text(code, "s1='", "'", 0)
|
||||
Jumps = self.extract_middle_text(code, "s2='", "'", 0)
|
||||
|
||||
content = f"剧情介绍📢{fenge[1]}"
|
||||
|
||||
if name not in content:
|
||||
bofang = Jumps
|
||||
xianlu = '1'
|
||||
else:
|
||||
data = data['data']
|
||||
|
||||
for vod in data:
|
||||
|
||||
name = vod['chapterName']
|
||||
|
||||
id = vod['shortPlayList'][0]['chapterShortPlayVoList'][0]['shortPlayUrl']
|
||||
|
||||
bofang = bofang + name + '$' + id + '#'
|
||||
|
||||
bofang = bofang[:-1]
|
||||
|
||||
xianlu = '短剧专线'
|
||||
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_content": content,
|
||||
"vod_play_from": xianlu,
|
||||
"vod_play_url": bofang
|
||||
})
|
||||
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = id
|
||||
result["header"] = headerx
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
pass
|
||||
|
||||
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,428 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
import base64
|
||||
import threading
|
||||
import requests
|
||||
import urllib3
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
from datetime import datetime
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from socketserver import ThreadingMixIn
|
||||
from urllib.parse import unquote, quote, urljoin, urlparse
|
||||
|
||||
urllib3.disable_warnings()
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider as BaseSpider
|
||||
|
||||
# ==================== 本地代理(解决图片跨域/防盗链) ====================
|
||||
_proxy_port = 0
|
||||
_proxy_started = False
|
||||
_proxy_session = requests.Session()
|
||||
_proxy_session.verify = False
|
||||
_proxy_headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Referer': 'https://maomi66.cc/',
|
||||
}
|
||||
|
||||
class _ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
daemon_threads = True
|
||||
|
||||
class _ProxyHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
try:
|
||||
real_url = unquote(self.path[1:])
|
||||
if not real_url or not real_url.startswith('http'):
|
||||
self.send_response(404); self.end_headers(); return
|
||||
r = _proxy_session.get(real_url, headers=_proxy_headers, timeout=20, verify=False)
|
||||
ct = r.headers.get('Content-Type', 'image/jpeg')
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', ct)
|
||||
self.send_header('Content-Length', len(r.content))
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.end_headers()
|
||||
self.wfile.write(r.content)
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
except Exception:
|
||||
self.send_response(404); self.end_headers()
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
def _find_free_port():
|
||||
import socket
|
||||
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sk.bind(('127.0.0.1', 0))
|
||||
port = sk.getsockname()[1]
|
||||
sk.close()
|
||||
return port
|
||||
|
||||
def _start_proxy():
|
||||
global _proxy_port, _proxy_started
|
||||
if _proxy_started:
|
||||
return
|
||||
_proxy_port = _find_free_port()
|
||||
server = _ThreadedHTTPServer(('127.0.0.1', _proxy_port), _ProxyHandler)
|
||||
threading.Thread(target=server.serve_forever, daemon=True).start()
|
||||
_proxy_started = True
|
||||
|
||||
# ==================== Spider 主体 ====================
|
||||
class Spider(BaseSpider):
|
||||
session = requests.Session()
|
||||
host = 'https://maomi66.cc'
|
||||
play_host = 'https://m.892539.xyz'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._categories_cache = None
|
||||
self._debug = True
|
||||
|
||||
def _log(self, msg):
|
||||
if self._debug:
|
||||
print(f'[maomi66] {msg}')
|
||||
|
||||
def getName(self):
|
||||
return 'maomi66'
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
if not url:
|
||||
return False
|
||||
return '.m3u8' in url or '.mp4' in url or '.ts' in url
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
return [404, 'text/plain', '']
|
||||
|
||||
def init(self, extend=''):
|
||||
self.session.verify = False
|
||||
self.session.headers.update(self._get_headers())
|
||||
_start_proxy()
|
||||
text = self._fetch(self.host)
|
||||
if text:
|
||||
self._load_categories(text)
|
||||
|
||||
def _get_headers(self, referer=None):
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Referer': referer or self.host + '/',
|
||||
}
|
||||
return headers
|
||||
|
||||
def _proxy_url(self, url):
|
||||
if not url:
|
||||
return ''
|
||||
if url.startswith('http://127.0.0.1'):
|
||||
return url
|
||||
return f'http://127.0.0.1:{_proxy_port}/{quote(url, safe="")}'
|
||||
|
||||
def _fetch(self, url, referer=None, retries=3):
|
||||
for i in range(retries):
|
||||
try:
|
||||
if referer is None:
|
||||
referer = self.host + '/'
|
||||
headers = self._get_headers(referer)
|
||||
if i > 0:
|
||||
time.sleep(random.uniform(0.5, 1.5))
|
||||
r = self.session.get(url, headers=headers, timeout=30, verify=False)
|
||||
r.encoding = 'utf-8'
|
||||
if r.status_code == 200:
|
||||
text = r.text
|
||||
if 'location.href' in text and ('请稍后' in text or '数据处理中' in text):
|
||||
jump_match = re.search(r'location\.href\s*=\s*[\'"]([^\'"]+)[\'"]', text)
|
||||
if jump_match:
|
||||
jump_path = jump_match.group(1)
|
||||
jump_url = urljoin(url, jump_path) if not jump_path.startswith('http') else jump_path
|
||||
self._log(f'遇到跳转页,跟随至: {jump_url}')
|
||||
r2 = self.session.get(jump_url, headers=headers, timeout=30, verify=False)
|
||||
r2.encoding = 'utf-8'
|
||||
if r2.status_code == 200:
|
||||
return r2.text
|
||||
return text
|
||||
elif r.status_code in [403, 429, 503]:
|
||||
self._log(f'请求被拦截 [{r.status_code}],重试 {i+1}/{retries}')
|
||||
else:
|
||||
return ''
|
||||
except Exception as e:
|
||||
self._log(f'请求异常 [{e}],重试 {i+1}/{retries}')
|
||||
return ''
|
||||
|
||||
# ==================== 分类加载 ====================
|
||||
def _load_categories(self, text):
|
||||
if not text:
|
||||
return []
|
||||
cats = []
|
||||
seen = set()
|
||||
tid_blacklist = {
|
||||
'69799699', '69809699', '69819699', '69829699',
|
||||
'69839699', '69849699', '69859699', '69869699'
|
||||
}
|
||||
pattern = r'<a href="/list/(\d+)-1\.html"(?:[^>]*?)>([^<]+)</a>'
|
||||
for tid, name in re.findall(pattern, text):
|
||||
name = name.strip()
|
||||
if not name or name in seen:
|
||||
continue
|
||||
if tid in tid_blacklist:
|
||||
self._log(f'过滤分类: [{tid}] {name}')
|
||||
continue
|
||||
seen.add(name)
|
||||
cats.append({'type_id': tid, 'type_name': name})
|
||||
self._categories_cache = cats
|
||||
self._log(f'加载分类: {len(cats)} 个')
|
||||
return cats
|
||||
|
||||
def _get_category_name(self, tid):
|
||||
for cat in self._categories_cache or []:
|
||||
if cat['type_id'] == tid:
|
||||
return cat['type_name']
|
||||
return tid
|
||||
|
||||
# ==================== 列表解析 ====================
|
||||
def _parse_list(self, html):
|
||||
items = []
|
||||
li_blocks = re.findall(r'<li[^>]*>(.*?)</li>', html, re.S)
|
||||
for li in li_blocks:
|
||||
a_match = re.search(r'<a class="xx-thumb" href="(/(?:video|torrent)/([^"]+)\.html)"', li)
|
||||
if not a_match:
|
||||
continue
|
||||
href = a_match.group(1)
|
||||
vid = a_match.group(2)
|
||||
if href.startswith('/torrent/'):
|
||||
continue
|
||||
img_match = re.search(r'<img[^>]+data-original="([^"]+)"', li)
|
||||
pic = img_match.group(1) if img_match else ''
|
||||
title = vid
|
||||
h5_match = re.search(r'<h5><a[^>]*>(.*?)</a></h5>', li, re.S)
|
||||
if h5_match:
|
||||
title = re.sub(r'<[^>]+>', '', h5_match.group(1)).strip()
|
||||
items.append({
|
||||
'vod_id': vid,
|
||||
'vod_name': title,
|
||||
'vod_pic': self._proxy_url(pic),
|
||||
'vod_remarks': '',
|
||||
})
|
||||
return items
|
||||
|
||||
def _get_list(self, tid, page):
|
||||
url = f'{self.host}/list/{tid}-{page}.html'
|
||||
html = self._fetch(url, referer=f'{self.host}/list/{tid}-1.html')
|
||||
if not html:
|
||||
return []
|
||||
return self._parse_list(html)
|
||||
|
||||
# ==================== 首页 ====================
|
||||
def homeContent(self, filter):
|
||||
try:
|
||||
text = self._fetch(self.host)
|
||||
if text:
|
||||
self._load_categories(text)
|
||||
cats = self._categories_cache or []
|
||||
return {
|
||||
'class': cats,
|
||||
'filters': {},
|
||||
'type': '影视',
|
||||
'list': [],
|
||||
'page': 1, 'pagecount': 1, 'limit': 0, 'total': 0
|
||||
}
|
||||
except Exception as e:
|
||||
self._log(f'homeContent 异常: {e}')
|
||||
return {'class': [], 'filters': {}, 'type': '影视', 'list': [], 'page': 1, 'pagecount': 1, 'limit': 0, 'total': 0}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {'list': []}
|
||||
|
||||
# ==================== 分类内容 ====================
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
try:
|
||||
page = int(pg) if pg else 1
|
||||
items = self._get_list(tid, page)
|
||||
total_page = page + 1
|
||||
if page == 1:
|
||||
html = self._fetch(f'{self.host}/list/{tid}-1.html')
|
||||
if html:
|
||||
pages = re.findall(r'/list/\d+-(\d+)\.html', html)
|
||||
if pages:
|
||||
total_page = max(int(p) for p in pages)
|
||||
return {
|
||||
'list': items, 'page': page, 'pagecount': total_page,
|
||||
'limit': len(items), 'total': total_page * len(items)
|
||||
}
|
||||
except Exception as e:
|
||||
self._log(f'categoryContent 异常: {e}')
|
||||
return {'list': [], 'page': 1, 'pagecount': 1, 'limit': 0, 'total': 0}
|
||||
|
||||
# ==================== 详情页 ====================
|
||||
def _fetch_detail(self, vid):
|
||||
url = f'{self.host}/video/{vid}.html'
|
||||
self._log(f'获取详情: {url}')
|
||||
html = self._fetch(url, referer=self.host)
|
||||
if html:
|
||||
detail = self._parse_detail(html, vid, url)
|
||||
if detail and detail.get('vod_play_url'):
|
||||
return detail
|
||||
return None
|
||||
|
||||
def _parse_detail(self, html, vid, base_url):
|
||||
title = ''
|
||||
m = re.search(r'<h1[^>]*>(.*?)</h1>', html, re.S)
|
||||
if m:
|
||||
title = re.sub(r'<[^>]+>', '', m.group(1)).strip()
|
||||
if not title:
|
||||
m = re.search(r'<title>([^<]+)</title>', html)
|
||||
if m:
|
||||
title = m.group(1).strip()
|
||||
|
||||
cover = ''
|
||||
m = re.search(r'<meta[^>]*property="og:image"[^>]*content="([^"]+)"', html)
|
||||
if m:
|
||||
cover = m.group(1)
|
||||
if not cover:
|
||||
m = re.search(r'<img[^>]+class="[^"]*cover[^"]*"[^>]+src="([^"]+)"', html, re.S)
|
||||
if m:
|
||||
cover = m.group(1)
|
||||
if not cover:
|
||||
m = re.search(r'data-original="([^"]+)"', html)
|
||||
if m:
|
||||
cover = m.group(1)
|
||||
|
||||
play_urls = []
|
||||
seen = set()
|
||||
|
||||
def add(label, url):
|
||||
if url in seen:
|
||||
return
|
||||
seen.add(url)
|
||||
play_urls.append(f'{label}${url}')
|
||||
|
||||
site_id = ''
|
||||
source_id = ''
|
||||
|
||||
comment_match = re.search(r'<!--\s*source_id:(\d+),\s*site_id:(\d+)', html)
|
||||
if comment_match:
|
||||
source_id = comment_match.group(1)
|
||||
site_id = comment_match.group(2)
|
||||
self._log(f'从注释提取: site_id={site_id}, source_id={source_id}')
|
||||
|
||||
if not site_id or not source_id:
|
||||
hls_match = re.search(r'hls\.loadSource\([\'"]([^\'"]+)[\'"]\)', html)
|
||||
if hls_match:
|
||||
play_url = hls_match.group(1)
|
||||
sid_match = re.search(r'site_id=(\d+)', play_url)
|
||||
src_match = re.search(r'source_id=(\d+)', play_url)
|
||||
if sid_match and src_match:
|
||||
site_id = sid_match.group(1)
|
||||
source_id = src_match.group(1)
|
||||
self._log(f'从HLS脚本提取: site_id={site_id}, source_id={source_id}')
|
||||
|
||||
if not site_id:
|
||||
m_sid = re.search(r'site_id[=:]\s*(\d+)', html)
|
||||
if m_sid:
|
||||
site_id = m_sid.group(1)
|
||||
if not source_id:
|
||||
m_src = re.search(r'source_id[=:]\s*(\d+)', html)
|
||||
if m_src:
|
||||
source_id = m_src.group(1)
|
||||
|
||||
if site_id and source_id:
|
||||
play_url = f'{self.play_host}/play.php?site_id={site_id}&source_id={source_id}'
|
||||
add('HLS直链', play_url)
|
||||
|
||||
for media in set(re.findall(r'https?://[^\s"\'<>]+\.(?:m3u8|mp4|flv|mkv|ts)(?:\?[^\s"\'<>]*)?', html)):
|
||||
add('媒体直链', media)
|
||||
|
||||
for src in set(re.findall(r'<iframe[^>]+(?:src|data-src)=["\']([^"\']+)["\']', html)):
|
||||
if any(k in src for k in ['play.php', 'm3u8', 'mp4', 'embed', 'player']):
|
||||
add('外链', src if src.startswith('http') else urljoin(base_url, src))
|
||||
|
||||
scripts = re.findall(r'<script[^>]*>(.*?)</script>', html, re.S)
|
||||
for script in scripts:
|
||||
for b64 in re.findall(r'["\']([A-Za-z0-9+/]{20,}={0,2})["\']', script):
|
||||
try:
|
||||
dec = base64.b64decode(b64).decode('utf-8')
|
||||
if dec.startswith('http') and any(x in dec for x in ['.m3u8', '.mp4', 'play.php']):
|
||||
add('Base64', dec)
|
||||
except:
|
||||
pass
|
||||
|
||||
if not play_urls:
|
||||
add('默认线路', base_url)
|
||||
|
||||
sources = []
|
||||
urls = []
|
||||
for pu in play_urls:
|
||||
sn, url = pu.split('$', 1)
|
||||
sources.append(sn)
|
||||
urls.append(f'{sn}${url}')
|
||||
|
||||
return {
|
||||
'vod_id': vid,
|
||||
'vod_name': title or vid,
|
||||
'vod_pic': self._proxy_url(cover) if cover else '',
|
||||
'vod_play_from': '$$$'.join(sources) if sources else '默认',
|
||||
'vod_play_url': '#'.join(urls) if urls else f'默认${base_url}',
|
||||
'vod_content': title or '',
|
||||
}
|
||||
|
||||
def detailContent(self, ids):
|
||||
try:
|
||||
vid = str(ids[0] if isinstance(ids, list) else ids)
|
||||
detail = self._fetch_detail(vid)
|
||||
if not detail:
|
||||
return {'list': []}
|
||||
return {'list': [detail]}
|
||||
except Exception as e:
|
||||
self._log(f'detailContent 异常: {e}')
|
||||
return {'list': []}
|
||||
|
||||
# ==================== 播放器 ====================
|
||||
def playerContent(self, flag, id, vipFlags=None):
|
||||
try:
|
||||
if id and not id.startswith('http'):
|
||||
detail = self._fetch_detail(id)
|
||||
if detail and detail.get('vod_play_url'):
|
||||
first = detail['vod_play_url'].split('#')[0]
|
||||
if '$' in first:
|
||||
id = first.split('$', 1)[1]
|
||||
else:
|
||||
id = first
|
||||
referer = self.host
|
||||
if id and id.startswith('http'):
|
||||
parsed = urlparse(id)
|
||||
if parsed.netloc:
|
||||
referer = f'{parsed.scheme}://{parsed.netloc}/'
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': id,
|
||||
'header': {
|
||||
'Referer': referer,
|
||||
'User-Agent': 'Mozilla/5.0',
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
self._log(f'playerContent 异常: {e}')
|
||||
return {'parse': 0, 'url': '', 'header': {}}
|
||||
|
||||
# ==================== 搜索 ====================
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
try:
|
||||
page = int(pg) if pg else 1
|
||||
url = f'{self.host}/search.php?content={quote(key)}&type=1&page={page}'
|
||||
html = self._fetch(url, referer=self.host)
|
||||
items = self._parse_list(html) if html else []
|
||||
return {
|
||||
'list': items, 'page': page, 'pagecount': page + 1,
|
||||
'limit': len(items), 'total': page * len(items)
|
||||
}
|
||||
except Exception as e:
|
||||
self._log(f'searchContent 异常: {e}')
|
||||
return {'list': [], 'page': 1, 'pagecount': 1, 'limit': 0, 'total': 0}
|
||||
@@ -0,0 +1,255 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tvbox 插件 - 蛋挞TV(兼容基类修正版)
|
||||
站点: https://www.dantatv.cc
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from base64 import b64encode, b64decode
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = "https://www.dantatv.cc"
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
# ---------- 辅助函数 ----------
|
||||
def getheader(self, content_type=None):
|
||||
headers = {
|
||||
'Unique-Origin': 'B9A378A8C39BDA1277D2D6185FCE2695',
|
||||
'User-Agent': 'okhttp/4.1.0/luob.app',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip',
|
||||
}
|
||||
if content_type:
|
||||
headers['Content-Type'] = content_type
|
||||
return headers
|
||||
|
||||
def e64(self, text):
|
||||
try:
|
||||
text_bytes = text.encode('utf-8')
|
||||
encoded_bytes = b64encode(text_bytes)
|
||||
return encoded_bytes.decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"Base64编码错误: {str(e)}")
|
||||
return ""
|
||||
|
||||
def d64(self, encoded_text):
|
||||
try:
|
||||
encoded_bytes = encoded_text.encode('utf-8')
|
||||
decoded_bytes = b64decode(encoded_bytes)
|
||||
return decoded_bytes.decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"Base64解码错误: {str(e)}")
|
||||
return ""
|
||||
|
||||
# ---------- 首页(固定分类) ----------
|
||||
def homeContent(self, filter):
|
||||
classes = [
|
||||
{"type_name": "剧集","type_id": "1"},
|
||||
{"type_name": "电影","type_id": "2"},
|
||||
{"type_name": "动漫","type_id": "3"},
|
||||
{"type_name": "短剧","type_id": "4"},
|
||||
{"type_name": "综艺","type_id": "5"},
|
||||
{"type_name": "少儿", "type_id": "30"},
|
||||
]
|
||||
return {"class": classes, "filters": {}}
|
||||
|
||||
# ---------- 首页推荐视频 ----------
|
||||
def homeVideoContent(self):
|
||||
url = f"{self.host}/api/index"
|
||||
headers = self.getheader()
|
||||
try:
|
||||
resp = self.fetch(url, headers=headers)
|
||||
data = resp.json()
|
||||
vod_list = []
|
||||
if data.get('data'):
|
||||
for item in data['data'][0].get('vodList', []):
|
||||
vod_list.append(self._vod_to_common(item))
|
||||
except Exception as e:
|
||||
print(f"首页推荐请求失败: {e}")
|
||||
vod_list = []
|
||||
return {'list': vod_list}
|
||||
|
||||
# ---------- 分类页(POST请求改用 self.post) ----------
|
||||
def categoryContent(self, tid, pg, filter, extend=None):
|
||||
if extend is None:
|
||||
extend = {}
|
||||
body = {
|
||||
"typeId1": int(tid),
|
||||
"pageNum": int(pg),
|
||||
"pageSize": 12,
|
||||
"sortField": "vod_time",
|
||||
"vodClass": extend.get('class', ''),
|
||||
"vodArea": extend.get('area', ''),
|
||||
"vodYear": extend.get('year', '')
|
||||
}
|
||||
url = f"{self.host}/api/search/type"
|
||||
headers = self.getheader(content_type='application/json')
|
||||
try:
|
||||
# 使用基类的 post 方法(若基类无 post,可改为 requests.post 自行实现)
|
||||
resp = self.post(url, headers=headers, data=json.dumps(body).encode('utf-8'))
|
||||
data = resp.json()
|
||||
vod_list = [self._vod_to_common(item) for item in data.get('data', [])]
|
||||
except Exception as e:
|
||||
print(f"分类页请求失败: {e}")
|
||||
vod_list = []
|
||||
return {
|
||||
'list': vod_list,
|
||||
'page': pg,
|
||||
'pagecount': 9999,
|
||||
'limit': 12,
|
||||
'total': 999999
|
||||
}
|
||||
|
||||
# ---------- 详情页 ----------
|
||||
def detailContent(self, ids):
|
||||
vod_id = ids[0]
|
||||
url = f"{self.host}/api/vod/play?vodId={vod_id}"
|
||||
headers = self.getheader()
|
||||
try:
|
||||
resp = self.fetch(url, headers=headers)
|
||||
data = resp.json()
|
||||
vod = data.get('data', {}).get('dantaVod', {})
|
||||
except Exception as e:
|
||||
print(f"详情请求失败: {e}")
|
||||
return {'list': []}
|
||||
|
||||
if not vod:
|
||||
return {'list': []}
|
||||
|
||||
info = {
|
||||
'vod_id': vod.get('vodId'),
|
||||
'vod_name': vod.get('vodName'),
|
||||
'vod_pic': self._fix_pic(vod.get('vodPic')),
|
||||
'vod_actor': vod.get('vodActor'),
|
||||
'vod_director': vod.get('vodDirector'),
|
||||
'vod_blurb': vod.get('vodContent') or vod.get('vodBlurb'),
|
||||
'vod_area': vod.get('vodArea'),
|
||||
'vod_year': vod.get('vodYear'),
|
||||
'vod_remarks': vod.get('vodRemarks'),
|
||||
'vod_lang': vod.get('vodLang'),
|
||||
'vod_class': vod.get('vodClass'),
|
||||
}
|
||||
|
||||
sources = vod.get('sources', [])
|
||||
vod_play_from = []
|
||||
vod_play_url = []
|
||||
for idx, src in enumerate(sources):
|
||||
collect_id = src.get('collectId')
|
||||
raw_url = src.get('vodPlayUrl', '')
|
||||
if not raw_url:
|
||||
continue
|
||||
|
||||
items = raw_url.split('#')
|
||||
encoded_items = []
|
||||
for part in items:
|
||||
if '$' not in part:
|
||||
continue
|
||||
name, link = part.split('$', 1)
|
||||
payload = {"collectId": collect_id, "url": link}
|
||||
enc = self.e64(json.dumps(payload, ensure_ascii=False))
|
||||
encoded_items.append(f"{name}${enc}")
|
||||
|
||||
if encoded_items:
|
||||
line_name = src.get('collectName') or src.get('vodPlayFrom') or f"线路{idx+1}"
|
||||
vod_play_from.append(line_name)
|
||||
vod_play_url.append('#'.join(encoded_items))
|
||||
|
||||
info['vod_play_from'] = '$$$'.join(vod_play_from)
|
||||
info['vod_play_url'] = '$$$'.join(vod_play_url)
|
||||
return {'list': [info]}
|
||||
|
||||
# ---------- 搜索 ----------
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
url = f"{self.host}/api/search/keyword"
|
||||
params = {"keyword": key, "pageNum": pg, "pageSize": 10}
|
||||
headers = self.getheader()
|
||||
try:
|
||||
resp = self.fetch(url, headers=headers, params=params)
|
||||
data = resp.json()
|
||||
vod_list = [self._vod_to_common(item) for item in data.get('data', [])]
|
||||
except Exception as e:
|
||||
print(f"搜索失败: {e}")
|
||||
vod_list = []
|
||||
return {'list': vod_list, 'page': pg}
|
||||
|
||||
# ---------- 播放解析 ----------
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
try:
|
||||
payload = json.loads(self.d64(id))
|
||||
collect_id = payload.get('collectId')
|
||||
raw_url = payload.get('url')
|
||||
if not collect_id or not raw_url:
|
||||
raise ValueError("缺少参数")
|
||||
except:
|
||||
return {
|
||||
'parse': 1,
|
||||
'url': '',
|
||||
'header': {'User-Agent': 'okhttp/4.1.0/luob.app'}
|
||||
}
|
||||
|
||||
parse_url = f"{self.host}/api/vod/parse"
|
||||
params = {"collectId": collect_id, "url": raw_url}
|
||||
headers = self.getheader()
|
||||
try:
|
||||
resp = self.fetch(parse_url, headers=headers, params=params)
|
||||
if resp.status_code == 200:
|
||||
result = resp.json()
|
||||
final_url = result.get('data') or result.get('url')
|
||||
if final_url:
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': final_url,
|
||||
'header': {'User-Agent': 'okhttp/4.1.0/luob.app'}
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"解析失败: {e}")
|
||||
|
||||
return {
|
||||
'parse': 1,
|
||||
'url': raw_url,
|
||||
'header': {'User-Agent': 'okhttp/4.1.0/luob.app'}
|
||||
}
|
||||
|
||||
# ---------- 内部辅助:字段映射 ----------
|
||||
def _vod_to_common(self, item):
|
||||
remark = item.get('vodRemarks', '')
|
||||
color = item.get('vodColor')
|
||||
if color:
|
||||
remark = f"[{color}] {remark}" if remark else color
|
||||
|
||||
return {
|
||||
'vod_id': item.get('vodId'),
|
||||
'vod_name': item.get('vodName'),
|
||||
'vod_pic': self._fix_pic(item.get('vodPic')),
|
||||
'vod_remarks': remark,
|
||||
'vod_year': item.get('vodYear'),
|
||||
'vod_area': item.get('vodArea'),
|
||||
'vod_actor': item.get('vodActor'),
|
||||
'vod_director': item.get('vodDirector'),
|
||||
'vod_lang': item.get('vodLang'),
|
||||
'vod_class': item.get('vodClass'),
|
||||
}
|
||||
|
||||
def _fix_pic(self, pic):
|
||||
"""补全相对路径图片"""
|
||||
if pic and pic.startswith('/'):
|
||||
return self.host + pic
|
||||
return pic
|
||||
@@ -0,0 +1,208 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import re
|
||||
import requests
|
||||
from urllib.parse import quote, unquote
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def init(self, extend=""):
|
||||
self.host = "https://dsystv.com"
|
||||
self.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36",
|
||||
"Referer": self.host + "/",
|
||||
"Origin": self.host
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return "袋鼠影视"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return bool(re.search(r'\.(m3u8|mp4|flv|avi|mkv|mov|ts)(\?|$)', url or "", re.I))
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
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": "动漫"},
|
||||
{"type_id": "44", "type_name": "短剧"}
|
||||
]
|
||||
}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {"list": self.parseList(self.get(self.host + "/"))}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
url = self.host + ("/frim/index" + str(tid) + ".html" if str(pg) == "1" else "/search.php?searchtype=5&tid=" + str(tid) + "&page=" + str(pg))
|
||||
html = self.get(url)
|
||||
return {
|
||||
"page": int(pg),
|
||||
"pagecount": 999,
|
||||
"limit": 24,
|
||||
"total": 999999,
|
||||
"list": self.parseList(html)
|
||||
}
|
||||
|
||||
def detailContent(self, ids):
|
||||
vid = ids[0]
|
||||
html = self.get(self.host + "/movie/index" + vid + ".html")
|
||||
name = self.clean(self.match(html, r'<h1[^>]*>(.*?)</h1>') or self.match(html, r'<meta property="og:title" content="(.*?)"'))
|
||||
name = name.replace("全集在线观看 - 国产剧 | 袋鼠影视", "").replace("全集在线观看 - 袋鼠影视", "").replace("《", "").replace("》", "").strip()
|
||||
pic = self.fix(self.match(html, r'<meta property="og:image" content="(.*?)"') or self.match(html, r'<a[^>]+class="[^"]*videopic[^"]*"[\s\S]*?<img[^>]+(?:data-original|data-src)=["\']([^"\']+)') or self.match(html, r'<a[^>]+class="[^"]*videopic[^"]*"[\s\S]*?<img[^>]+src=["\']([^"\']+)'))
|
||||
desc = self.clean(self.match(html, r'<div class="plot"[^>]*>\s*<p>(.*?)</p>') or self.match(html, r'<meta property="og:description" content="(.*?)"'))
|
||||
actor = self.match(html, r'<li[^>]+data-video-meta=["\']([^"\']*)["\'][^>]*><span class="text-muted">主演:')
|
||||
director = self.match(html, r'<li[^>]+data-video-meta=["\']([^"\']*)["\'][^>]*><span class="text-muted">导演:')
|
||||
year = self.clean(self.match(html, r'年份:</span>([^<]+)'))
|
||||
area = self.clean(self.match(html, r'地区:</span>([^<]+)'))
|
||||
lang = self.clean(self.match(html, r'语言:</span>([^<]+)'))
|
||||
cate = self.clean(self.match(html, r'类型:</span><a[^>]*>(.*?)</a>'))
|
||||
remarks = self.clean(self.match(html, r'<span class="note textbg">(.*?)</span>'))
|
||||
tabs = re.findall(r'<a class="option"[\s\S]*?title=["\']([^"\']+)["\'][\s\S]*?</a>', html)
|
||||
panels = re.findall(r'<div[^>]+class=["\']playlist[^"\']*["\'][^>]*>\s*<ul[^>]*>([\s\S]*?)</ul>', html, re.S)
|
||||
play_from = []
|
||||
play_url = []
|
||||
for i, p in enumerate(panels):
|
||||
eps = []
|
||||
for m in re.finditer(r'<a[^>]+title=["\']([^"\']+)["\'][^>]+href=["\']([^"\']*?/play/[^"\']+)["\']', p):
|
||||
t = self.clean(m.group(1))
|
||||
u = self.fix(m.group(2))
|
||||
if t and u:
|
||||
eps.append(t + "$" + u)
|
||||
if not eps:
|
||||
for m in re.finditer(r'<a[^>]+href=["\']([^"\']*?/play/[^"\']+)["\'][^>]*>(.*?)</a>', p):
|
||||
t = self.clean(m.group(2))
|
||||
u = self.fix(m.group(1))
|
||||
if t and u:
|
||||
eps.append(t + "$" + u)
|
||||
if eps:
|
||||
key = self.clean(tabs[i]) if i < len(tabs) else "线路" + str(i + 1)
|
||||
if key not in play_from:
|
||||
play_from.append(key)
|
||||
play_url.append("#".join(eps))
|
||||
if not play_url:
|
||||
eps = []
|
||||
for m in re.finditer(r'<a[^>]+href=["\']([^"\']*?/play/' + vid + r'-[^"\']+)["\'][^>]*>(.*?)</a>', html):
|
||||
t = self.clean(m.group(2)) or "播放"
|
||||
u = self.fix(m.group(1))
|
||||
if t and u:
|
||||
eps.append(t + "$" + u)
|
||||
if eps:
|
||||
play_from.append("默认")
|
||||
play_url.append("#".join(eps))
|
||||
return {
|
||||
"list": [{
|
||||
"vod_id": vid,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks,
|
||||
"type_name": cate,
|
||||
"vod_year": year,
|
||||
"vod_area": area,
|
||||
"vod_lang": lang,
|
||||
"vod_actor": actor,
|
||||
"vod_director": director,
|
||||
"vod_content": desc,
|
||||
"vod_play_from": "$$$".join(play_from),
|
||||
"vod_play_url": "$$$".join(play_url)
|
||||
}]
|
||||
}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
html = ""
|
||||
try:
|
||||
r = requests.post(self.host + "/search.php", headers=self.headers, data={"searchword": key}, timeout=15)
|
||||
r.encoding = r.apparent_encoding or "utf-8"
|
||||
html = r.text
|
||||
except Exception:
|
||||
html = ""
|
||||
data = self.parseList(html)
|
||||
if not data:
|
||||
html = self.get(self.host + "/search.php?searchword=" + quote(key) + "&page=" + str(pg))
|
||||
data = self.parseList(html)
|
||||
return {"list": data, "page": int(pg)}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
html = self.get(id)
|
||||
url = self.match(html, r'var\s+now\s*=\s*["\']([^"\']+)["\']')
|
||||
url = unquote(url) if url else id
|
||||
return {"parse": 0 if self.isVideoFormat(url) else 1, "playUrl": "", "url": url, "header": self.headers}
|
||||
|
||||
def localProxy(self, param):
|
||||
return [404, "text/plain", "", ""]
|
||||
|
||||
def destroy(self):
|
||||
return "正在Destroy"
|
||||
|
||||
def get(self, url):
|
||||
try:
|
||||
r = requests.get(url, headers=self.headers, timeout=15, verify=False)
|
||||
r.encoding = r.apparent_encoding or "utf-8"
|
||||
return r.text
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def match(self, text, rule):
|
||||
m = re.search(rule, text or "", re.S)
|
||||
return m.group(1) if m else ""
|
||||
|
||||
def clean(self, text):
|
||||
return re.sub(r"\s+", " ", re.sub(r"<.*?>", "", text or "").replace(" ", " ")).strip()
|
||||
|
||||
def fix(self, url):
|
||||
if not url:
|
||||
return ""
|
||||
if url.startswith("//"):
|
||||
return "https:" + url
|
||||
if url.startswith("/"):
|
||||
return self.host + url
|
||||
return url
|
||||
|
||||
def parseList(self, html):
|
||||
res = []
|
||||
seen = set()
|
||||
for m in re.finditer(r'<a[^>]+class=["\'][^"\']*videopic[^"\']*["\'][^>]+href=["\']/movie/index(\d+)\.html["\'][^>]*title=["\']([^"\']+)["\']([\s\S]{0,1200}?)</a>', html or "", re.S):
|
||||
vid = m.group(1)
|
||||
if vid in seen:
|
||||
continue
|
||||
seen.add(vid)
|
||||
item = m.group(0) + m.group(3)
|
||||
name = self.clean(m.group(2))
|
||||
pics = re.findall(r'(?:data-original|data-src)=["\']([^"\']+\.(?:jpg|jpeg|png|webp|gif)[^"\']*)["\']', item, re.I)
|
||||
if not pics:
|
||||
pics = re.findall(r'src=["\']([^"\']+\.(?:jpg|jpeg|png|webp|gif)[^"\']*)["\']', item, re.I)
|
||||
pic = ""
|
||||
for p in pics:
|
||||
if "load.gif" not in p and "nopic" not in p and "logo" not in p and "templets" not in p:
|
||||
pic = self.fix(p)
|
||||
break
|
||||
remarks = self.clean(self.match(item, r'<span[^>]+class=["\'][^"\']*note[^"\']*["\'][^>]*>(.*?)</span>') or self.match(item, r'<span[^>]+class=["\'][^"\']*textbg[^"\']*["\'][^>]*>(.*?)</span>'))
|
||||
if name:
|
||||
res.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
})
|
||||
if not res:
|
||||
for m in re.finditer(r'href=["\']/movie/index(\d+)\.html["\'][^>]*title=["\']([^"\']+)["\'][\s\S]{0,1200}?<img[^>]+([^>]+)>', html or "", re.S):
|
||||
vid = m.group(1)
|
||||
if vid in seen:
|
||||
continue
|
||||
seen.add(vid)
|
||||
img = m.group(3)
|
||||
pic = self.match(img, r'(?:data-original|data-src)=["\']([^"\']+)["\']') or self.match(img, r'src=["\']([^"\']+)["\']')
|
||||
if "load.gif" in pic or "templets" in pic:
|
||||
pic = ""
|
||||
res.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": self.clean(m.group(2)),
|
||||
"vod_pic": self.fix(pic),
|
||||
"vod_remarks": ""
|
||||
})
|
||||
return res
|
||||
@@ -0,0 +1,311 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re, json, requests
|
||||
from urllib.parse import quote
|
||||
from lxml import etree
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def __init__(self):
|
||||
self.name = "sypfjy"
|
||||
self.host = "https://www.sypfjy.com"
|
||||
self.header = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Referer': self.host
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def _get(self, url, params=None):
|
||||
r = requests.get(url, headers=self.header, params=params, timeout=20)
|
||||
r.encoding = 'utf-8'
|
||||
return r.text
|
||||
|
||||
def _post(self, url, data=None):
|
||||
r = requests.post(url, headers=self.header, data=data, timeout=20)
|
||||
r.encoding = 'utf-8'
|
||||
return r.text
|
||||
|
||||
def _fix_url(self, url):
|
||||
if not url:
|
||||
return ''
|
||||
if url.startswith('//'):
|
||||
return 'https:' + url
|
||||
if url.startswith('/'):
|
||||
return self.host + url
|
||||
return url
|
||||
|
||||
def _parse_pic(self, elem):
|
||||
if elem is None:
|
||||
return ''
|
||||
if elem.tag == 'img':
|
||||
pic = elem.get('data-src') or elem.get('src', '')
|
||||
else:
|
||||
imgs = elem.xpath('.//img')
|
||||
pic = (imgs[0].get('data-src') or imgs[0].get('src', '')) if imgs else ''
|
||||
if pic and pic.startswith('data:image'):
|
||||
pic = ''
|
||||
return self._fix_url(pic)
|
||||
|
||||
def _parse_text(self, elem):
|
||||
if elem is None:
|
||||
return ''
|
||||
return ''.join(elem.itertext()).strip()
|
||||
|
||||
def _build_vodshow(self, tid, area, order, cls, lang, page, year):
|
||||
"""12 段位 URL: type-area-order-class-lang-_-_-_-page-_-_-year
|
||||
默认排序 time 不写入 URL(留空)"""
|
||||
def q(v):
|
||||
return quote(v) if v else ''
|
||||
# 默认排序不写
|
||||
order_v = q(order) if order and order != 'time' else ''
|
||||
# 页码 1 不写(留空默认)
|
||||
page_v = str(page) if int(page) > 1 else ''
|
||||
segs = [str(tid), q(area), order_v, q(cls), q(lang), '', '', '', page_v, '', '', q(year)]
|
||||
return f"{self.host}/vodshow/{'-'.join(segs)}.html"
|
||||
|
||||
def _parse_list_item(self, item):
|
||||
a = item.xpath('.//div[contains(@class,"video-name")]//a')
|
||||
if not a:
|
||||
return None
|
||||
a = a[0]
|
||||
href = a.get('href', '')
|
||||
m = re.search(r'/voddetail/(\d+)\.html', href)
|
||||
if not m:
|
||||
return None
|
||||
vid = m.group(1)
|
||||
vod_name = a.get('title', '').strip() or (a.text or '').strip()
|
||||
vod_pic = self._parse_pic(item)
|
||||
caption = item.xpath('.//div[contains(@class,"module-item-caption")]//text()')
|
||||
parts = [s.strip() for s in caption if s.strip()]
|
||||
vod_remarks = ' / '.join(parts[-2:]) if len(parts) >= 2 else (parts[-1] if parts else '')
|
||||
return {"vod_id": vid, "vod_name": vod_name, "vod_pic": vod_pic, "vod_remarks": vod_remarks}
|
||||
|
||||
def homeContent(self, filter):
|
||||
classes = [
|
||||
{"type_name": "电视剧", "type_id": "2"},
|
||||
{"type_name": "电影", "type_id": "dianying"},
|
||||
{"type_name": "动漫", "type_id": "dongman"},
|
||||
{"type_name": "短剧", "type_id": "remenduanju"},
|
||||
{"type_name": "综艺", "type_id": "zongyi"},
|
||||
{"type_name": "体育", "type_id": "tiyusaishi"}
|
||||
]
|
||||
filters = {}
|
||||
order_vals = [{"n": "时间排序", "v": "time"}, {"n": "人气排序", "v": "hits"}, {"n": "评分排序", "v": "score"}]
|
||||
area_vals = [{"n": "全部", "v": ""}, {"n": "内地", "v": "内地"}, {"n": "中国", "v": "中国"}, {"n": "香港", "v": "香港"}, {"n": "台湾", "v": "台湾"}, {"n": "韩国", "v": "韩国"}, {"n": "日本", "v": "日本"}, {"n": "美国", "v": "美国"}, {"n": "泰国", "v": "泰国"}, {"n": "英国", "v": "英国"}, {"n": "新加坡", "v": "新加坡"}, {"n": "其他", "v": "其他"}]
|
||||
class_vals = [{"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": "乡村"}, {"n": "情景", "v": "情景"}, {"n": "武侠", "v": "武侠"}, {"n": "冒险", "v": "冒险"}, {"n": "谍战", "v": "谍战"}, {"n": "其他", "v": "其他"}]
|
||||
lang_vals = [{"n": "全部", "v": ""}, {"n": "国语", "v": "国语"}, {"n": "英语", "v": "英语"}, {"n": "粤语", "v": "粤语"}, {"n": "闽南语", "v": "闽南语"}, {"n": "韩语", "v": "韩语"}, {"n": "日语", "v": "日语"}, {"n": "其他", "v": "其他"}]
|
||||
year_vals = [{"n": "全部", "v": ""}, {"n": "2026", "v": "2026"}, {"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"}]
|
||||
for c in classes:
|
||||
filters[c['type_id']] = [
|
||||
{"key": "area", "name": "地区", "value": area_vals},
|
||||
{"key": "class", "name": "类型", "value": class_vals},
|
||||
{"key": "lang", "name": "语言", "value": lang_vals},
|
||||
{"key": "year", "name": "年份", "value": year_vals},
|
||||
{"key": "order", "name": "排序", "value": order_vals}
|
||||
]
|
||||
return {"class": classes, "filters": filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
try:
|
||||
html = self._get(self.host)
|
||||
root = etree.HTML(html)
|
||||
for item in root.xpath('//div[contains(@class, "module-item")]'):
|
||||
try:
|
||||
v = self._parse_list_item(item)
|
||||
if v:
|
||||
videos.append(v)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
return {"list": videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
videos = []
|
||||
try:
|
||||
if isinstance(extend, str) and extend:
|
||||
try: extend = json.loads(extend)
|
||||
except: extend = {}
|
||||
elif not extend:
|
||||
extend = {}
|
||||
area = extend.get('area', '')
|
||||
cls = extend.get('class', '')
|
||||
year = extend.get('year', '')
|
||||
lang = extend.get('lang', '')
|
||||
order = extend.get('order', 'time')
|
||||
if order in ('hits_week', 'hits_month'): order = 'hits'
|
||||
elif order not in ('time', 'hits', 'score'): order = 'time'
|
||||
url = self._build_vodshow(tid, area, order, cls, lang, pg, year)
|
||||
html = self._get(url)
|
||||
root = etree.HTML(html)
|
||||
for item in root.xpath('//div[contains(@class, "module-item")]'):
|
||||
try:
|
||||
v = self._parse_list_item(item)
|
||||
if v:
|
||||
videos.append(v)
|
||||
except Exception:
|
||||
pass
|
||||
# 去重
|
||||
seen = set()
|
||||
unique = []
|
||||
for v in videos:
|
||||
if v['vod_id'] not in seen:
|
||||
seen.add(v['vod_id'])
|
||||
unique.append(v)
|
||||
# 总页数
|
||||
pm = 1
|
||||
for m in re.finditer(r'href="(/vodshow/[^"]*?-(\d+)-[^"]*)"', html):
|
||||
pm = max(pm, int(m.group(2)))
|
||||
if pm < 1:
|
||||
m = re.search(r'第(\d+)页.*尾页', html)
|
||||
if m: pm = int(m.group(1))
|
||||
limit = len(unique) if unique else 24
|
||||
return {'list': unique, 'page': int(pg), 'pagecount': pm, 'limit': limit, 'total': pm * limit}
|
||||
except Exception:
|
||||
return {'list': [], 'page': 1, 'pagecount': 0, 'limit': 0, 'total': 0}
|
||||
|
||||
def detailContent(self, ids):
|
||||
try:
|
||||
vod_id = ids[0]
|
||||
html = self._get(f"{self.host}/voddetail/{vod_id}.html")
|
||||
root = etree.HTML(html)
|
||||
title = root.xpath('//h1[@class="page-title"]/text()')
|
||||
vod_name = title[0].strip() if title else ''
|
||||
if not vod_name:
|
||||
ts = root.xpath('//title/text()')
|
||||
if ts: vod_name = ts[0].split('-')[0].strip()
|
||||
pic_a = root.xpath('//div[contains(@class,"video-cover")]//img')
|
||||
vod_pic = ''
|
||||
if pic_a:
|
||||
pic = pic_a[0].get('data-src') or pic_a[0].get('src', '')
|
||||
if not pic.startswith('data:image'): vod_pic = self._fix_url(pic)
|
||||
year_a = root.xpath('//a[contains(@href,"/vodsearch/year/")]')
|
||||
vod_year = ''
|
||||
if year_a:
|
||||
m = re.search(r'(\d{4})', year_a[0].text or '')
|
||||
if m: vod_year = m.group(1)
|
||||
if not vod_year:
|
||||
yl = root.xpath('//a[contains(@href,"/vodshow/")]')
|
||||
for t in yl:
|
||||
m = re.search(r'^(\d{4})\s*$', (t.text or '').strip())
|
||||
if m: vod_year = m.group(1); break
|
||||
area_a = root.xpath('//a[contains(@href,"/vodshow/")]')
|
||||
area_k = ['中国大陆', '中国香港', '中国台湾', '香港', '台湾', '内地', '韩国', '日本', '美国', '泰国', '英国', '新加坡', '法国']
|
||||
vod_area = ''
|
||||
for t in area_a:
|
||||
txt = (t.text or '').strip()
|
||||
if re.match(r'^\d{4}$', txt): continue
|
||||
for k in area_k:
|
||||
if k in txt:
|
||||
if k == '香港': vod_area = '中国香港'
|
||||
elif k == '台湾': vod_area = '中国台湾'
|
||||
elif k == '内地': vod_area = '中国大陆'
|
||||
else: vod_area = txt
|
||||
break
|
||||
if vod_area: break
|
||||
def _ef(label):
|
||||
p = html.find('class="video-info-itemtitle">%s</span>' % label)
|
||||
if p < 0: return ''
|
||||
end = html.find('class="video-info-items"', p+1)
|
||||
if end < 0: end = p + 800
|
||||
seg = html[p:end]
|
||||
names = [x.strip() for x in re.findall(r'href="[^"]*/vodsearch/[^"]+"[^>]*>([^<]+)<', seg)]
|
||||
if not names:
|
||||
d = re.search(r'class="video-info-item"[^>]*>([^<]*)<', seg)
|
||||
if d: return d.group(1).strip()
|
||||
return ' '.join(names)
|
||||
vod_actor = _ef('主演:')
|
||||
vod_director = _ef('导演:')
|
||||
sq = root.xpath('//p[@class="sqjj_a"]')
|
||||
vod_content = ''
|
||||
if sq: vod_content = self._parse_text(sq[0])
|
||||
if not vod_content:
|
||||
zk = root.xpath('//p[@class="zkjj_a"]')
|
||||
if zk: vod_content = self._parse_text(zk[0])
|
||||
vod_content = re.sub(r'\[收起部分\]|\[展开全部\]', '', vod_content)
|
||||
vod_content = re.sub(r'\s+', '', vod_content).strip()
|
||||
tabs = re.findall(r'data-dropdown-value="([^"]+)"', html)
|
||||
sections = re.split(r'\bid="glist-\d+"', html)
|
||||
froms = []; urls = []
|
||||
for idx, sec in enumerate(sections):
|
||||
if idx >= len(tabs): break
|
||||
name = tabs[idx].strip()
|
||||
if not name or name == 'http下载': continue
|
||||
eps = re.findall(r'href="(/vodplay/[^"]+)"[^>]*>(?:<span>)?([^<]*)(?:</span>)?</a>', sec)
|
||||
if not eps: continue
|
||||
pl = []
|
||||
for h, t in eps:
|
||||
txt = t.strip()
|
||||
if not txt:
|
||||
mm = re.search(r'/vodplay/\d+-\d+-(\d+)\.html', h)
|
||||
txt = '第%d集' % int(mm.group(1)) if mm else h
|
||||
pl.append(f"{txt}${self._fix_url(h)}")
|
||||
if pl:
|
||||
froms.append(name); urls.append("#".join(pl))
|
||||
return {'list': [{"vod_id": vod_id, "vod_name": vod_name, "vod_pic": vod_pic,
|
||||
"vod_year": vod_year, "vod_area": vod_area, "vod_actor": vod_actor,
|
||||
"vod_director": vod_director, "vod_content": vod_content,
|
||||
"vod_play_from": "$$$".join(froms) if froms else "默认",
|
||||
"vod_play_url": "$$$".join(urls) if urls else ""}]}
|
||||
except Exception:
|
||||
return {'list': []}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
try:
|
||||
html = self._get(id)
|
||||
m = re.search(r'player_aaaa=({.*?})\s*</script>', html, re.S)
|
||||
if m:
|
||||
try:
|
||||
d = json.loads(m.group(1))
|
||||
url = d.get('url', '')
|
||||
if url:
|
||||
if url.startswith('//'): url = 'https:' + url
|
||||
return {"parse": 0 if self.isVideoFormat(url) else 1, "playUrl": "", "url": url, "header": json.dumps(self.header)}
|
||||
except: pass
|
||||
ifr = re.search(r'<iframe[^>]+src\s*=\s*"([^"]+)"', html)
|
||||
if ifr:
|
||||
u = self._fix_url(ifr.group(1))
|
||||
return {"parse": 0 if self.isVideoFormat(u) else 1, "playUrl": "", "url": u, "header": json.dumps(self.header)}
|
||||
m3u8 = re.search(r'["\'](https?://[^"\']+\.m3u8[^"\']*)["\']', html)
|
||||
if m3u8: return {"parse": 0, "playUrl": "", "url": m3u8.group(1), "header": json.dumps(self.header)}
|
||||
mp4 = re.search(r'["\'](https?://[^"\']+\.(?:mp4|flv|ts))["\']', html)
|
||||
if mp4: return {"parse": 0, "playUrl": "", "url": mp4.group(1), "header": json.dumps(self.header)}
|
||||
return {"parse": 0, "playUrl": "", "url": ""}
|
||||
except Exception:
|
||||
return {"parse": 0, "playUrl": "", "url": ""}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
videos = []
|
||||
try:
|
||||
html = self._get(f"{self.host}/vodsearch.html", params={"wd": key, "pg": pg})
|
||||
parts = html.split('<div class="module-search-item">')
|
||||
for p in parts[1:]:
|
||||
vm = re.search(r'href="(/voddetail/(\d+)\.html)"', p)
|
||||
if not vm: continue
|
||||
nm = re.search(r'<h3><a href="/voddetail/\d+\.html" title="([^"]+)"', p)
|
||||
pm = re.search(r'data-src="([^"]+)"', p)
|
||||
rm = re.search(r'video-serial"[^>]*>([^<]+)<', p)
|
||||
videos.append({"vod_id": vm.group(2),
|
||||
"vod_name": nm.group(1) if nm else '',
|
||||
"vod_pic": self._fix_url(pm.group(1)) if pm else '',
|
||||
"vod_remarks": rm.group(1).strip() if rm else ''})
|
||||
tm = re.search(r'<strong class="mac_total">(\d+)</strong>', html)
|
||||
total = int(tm.group(1)) if tm else len(videos)
|
||||
pc = max(1, (total + (len(videos) or 1) - 1) // (len(videos) or 1))
|
||||
return {'list': videos, 'page': int(pg), 'pagecount': pc, 'limit': len(videos), 'total': total}
|
||||
except Exception:
|
||||
return {'list': [], 'page': 1, 'pagecount': 0, 'limit': 0, 'total': 0}
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return any(url.lower().endswith(f) for f in ['.m3u8', '.mp4', '.flv', '.ts'])
|
||||
|
||||
def manualVideoCheck(self): pass
|
||||
def localProxy(self, params): return None
|
||||
def destroy(self): pass
|
||||
@@ -0,0 +1,133 @@
|
||||
#coding=utf-8
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
import json
|
||||
|
||||
host_url = 'https://frodo.douban.com/api/v2'
|
||||
apikey = "?apiKey=0ac44ae016490db2204ce0a042db2916"
|
||||
|
||||
class Spider(Spider): # 元类 默认的元类 type
|
||||
def getName(self):
|
||||
return "豆瓣"
|
||||
def init(self,extend=""):
|
||||
print("============{0}============".format(extend))
|
||||
pass
|
||||
def isVideoFormat(self,url):
|
||||
pass
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
def homeContent(self,filter):
|
||||
result = {}
|
||||
cateManual = {
|
||||
"热门电影": "hot_gaia",
|
||||
"热播剧集": "tv_hot",
|
||||
"热播综艺": "show_hot",
|
||||
"电影筛选": "movie",
|
||||
"电视筛选": "tv",
|
||||
"电影榜单": "rank_list_movie",
|
||||
"电视榜单": "rank_list_tv"
|
||||
}
|
||||
classes = []
|
||||
for k in cateManual:
|
||||
classes.append({
|
||||
'type_name':k,
|
||||
'type_id':cateManual[k]
|
||||
})
|
||||
result['class'] = classes
|
||||
if(filter):
|
||||
result['filters'] = self.config['filter']
|
||||
return result
|
||||
def homeVideoContent(self):
|
||||
url = host_url + '/subject_collection/subject_real_time_hotest/items' + apikey
|
||||
rsp = self.fetch(url, headers=self.header)
|
||||
jo = json.loads(rsp.text)
|
||||
joList = jo.get("subject_collection_items")
|
||||
lists = []
|
||||
for item in joList:
|
||||
rating = item['rating']['value'] if item['rating'] else ""
|
||||
lists.append({
|
||||
"vod_id": f'msearch:{item.get("type", "")}__{item.get("id", "")}',
|
||||
"vod_name": item['title'],
|
||||
"vod_pic": item['pic']['normal'],
|
||||
"vod_remarks": rating
|
||||
})
|
||||
result = {
|
||||
'list':lists
|
||||
}
|
||||
return result
|
||||
def categoryContent(self,tid,pg,filter,extend):
|
||||
result = {}
|
||||
if tid == "hot_gaia":
|
||||
urlpath = f"/movie/{tid}"
|
||||
sort = extend.get("sort", "recommend")
|
||||
area = extend.get("area", "全部")
|
||||
sort = sort + "&area=" + area
|
||||
tags = ""
|
||||
getdata = "items"
|
||||
elif tid == "tv_hot" or tid == "show_hot":
|
||||
s_type = extend.get("type", tid)
|
||||
urlpath = f"/subject_collection/{s_type}/items"
|
||||
sort = ""
|
||||
tags = ""
|
||||
getdata = "subject_collection_items"
|
||||
elif tid.startswith("rank_list"):
|
||||
id = "movie_real_time_hotest" if tid == "rank_list_movie" else "tv_real_time_hotest"
|
||||
urlpath = f"/subject_collection/{id}/items"
|
||||
sort = ""
|
||||
tags = ""
|
||||
getdata = "subject_collection_items"
|
||||
else:
|
||||
urlpath = f"/{tid}/recommend"
|
||||
if extend:
|
||||
sort = extend.pop('sort') if "sort" in extend else "T"
|
||||
tags = ",".join(item for item in extend.values())
|
||||
else:
|
||||
sort = "T"
|
||||
tags = ""
|
||||
getdata = "items"
|
||||
url = host_url + urlpath + apikey + '&sort=' + sort + '&tags=' + tags + '&start=' + str((int(pg) - 1) * 30)
|
||||
rsp = self.fetch(url, headers=self.header)
|
||||
jo = json.loads(rsp.text)
|
||||
jolist = jo[getdata]
|
||||
videos = []
|
||||
for vod in jolist:
|
||||
if vod.get("type", "") == "movie" or vod.get("type", "") == "tv":
|
||||
rating = vod.get("rating", "").get("value", "") if vod.get("rating", "") else ""
|
||||
pic = vod.get("pic", "").get("normal", "") if vod.get("pic", "") else ""
|
||||
title = vod.get("title", "")
|
||||
videos.append({
|
||||
"vod_id": f'msearch:{vod.get("type", "")}__{vod.get("id", "")}',
|
||||
"vod_name": title,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": rating
|
||||
})
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self,array):
|
||||
pass
|
||||
def searchContent(self,key,quick):
|
||||
pass
|
||||
def playerContent(self,flag,id,vipFlags):
|
||||
pass
|
||||
|
||||
config = {
|
||||
"player": {},
|
||||
"filter": {"hot_gaia":[{"key":"sort","name":"排序","value":[{"n":"热度","v":"recommend"},{"n":"最新","v":"time"},{"n":"评分","v":"rank"}]},{"key":"area","name":"地区","value":[{"n":"全部","v":"全部"},{"n":"华语","v":"华语"},{"n":"欧美","v":"欧美"},{"n":"韩国","v":"韩国"},{"n":"日本","v":"日本"}]}],"tv_hot":[{"key":"type","name":"分类","value":[{"n":"综合","v":"tv_hot"},{"n":"国产剧","v":"tv_domestic"},{"n":"欧美剧","v":"tv_american"},{"n":"日剧","v":"tv_japanese"},{"n":"韩剧","v":"tv_korean"},{"n":"动画","v":"tv_animation"}]}],"show_hot":[{"key":"type","name":"分类","value":[{"n":"综合","v":"show_hot"},{"n":"国内","v":"show_domestic"},{"n":"国外","v":"show_foreign"}]}],"movie":[{"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":"西部"},{"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":"瑞典"},{"n":"巴西","v":"巴西"},{"n":"丹麦","v":"丹麦"}]},{"key":"sort","name":"排序","value":[{"n":"近期热度","v":"T"},{"n":"首映时间","v":"R"},{"n":"高分优先","v":"S"}]},{"key":"年代","name":"年代","value":[{"n":"全部年代","v":""}, {"n":"2026","v":"2026"},{"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":"2010年代","v":"2010年代"},{"n":"2000年代","v":"2000年代"},{"n":"90年代","v":"90年代"},{"n":"80年代","v":"80年代"},{"n":"70年代","v":"70年代"},{"n":"60年代","v":"60年代"},{"n":"更早","v":"更早"}]}],"tv":[{"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":"恐怖"},{"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":"真人秀","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":"印度"},{"n":"加拿大","v":"加拿大"},{"n":"爱尔兰","v":"爱尔兰"},{"n":"澳大利亚","v":"澳大利亚"}]},{"key":"sort","name":"排序","value":[{"n":"近期热度","v":"T"},{"n":"首播时间","v":"R"},{"n":"高分优先","v":"S"}]},{"key":"年代","name":"年代","value":[{"n":"全部","v":""},{"n":"2026","v":"2026"},{"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":"2010年代","v":"2010年代"},{"n":"2000年代","v":"2000年代"},{"n":"90年代","v":"90年代"},{"n":"80年代","v":"80年代"},{"n":"70年代","v":"70年代"},{"n":"60年代","v":"60年代"},{"n":"更早","v":"更早"}]},{"key":"平台","name":"平台","value":[{"n":"全部","v":""},{"n":"腾讯视频","v":"腾讯视频"},{"n":"爱奇艺","v":"爱奇艺"},{"n":"优酷","v":"优酷"},{"n":"湖南卫视","v":"湖南卫视"},{"n":"Netflix","v":"Netflix"},{"n":"HBO","v":"HBO"},{"n":"BBC","v":"BBC"},{"n":"NHK","v":"NHK"},{"n":"CBS","v":"CBS"},{"n":"NBC","v":"NBC"},{"n":"tvN","v":"tvN"}]}],"rank_list_movie":[{"key":"榜单","name":"榜单","value":[{"n":"实时热门电影","v":"movie_real_time_hotest"},{"n":"一周口碑电影榜","v":"movie_weekly_best"},{"n":"豆瓣电影Top250","v":"movie_top250"}]}],"rank_list_tv":[{"key":"榜单","name":"榜单","value":[{"n":"实时热门电视","v":"tv_real_time_hotest"},{"n":"华语口碑剧集榜","v":"tv_chinese_best_weekly"},{"n":"全球口碑剧集榜","v":"tv_global_best_weekly"},{"n":"国内口碑综艺榜","v":"show_chinese_best_weekly"},{"n":"国外口碑综艺榜","v":"show_global_best_weekly"}]}]}
|
||||
}
|
||||
header = {
|
||||
"Host": "frodo.douban.com",
|
||||
"Connection": "Keep-Alive",
|
||||
"Referer": "https://servicewechat.com/wx2f9b06c1de1ccfca/84/page-frame.html",
|
||||
"content-type": "application/json",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat"
|
||||
}
|
||||
|
||||
def localProxy(self,param):
|
||||
return [200, "video/MP2T", action, ""]
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re, base64, requests
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def getName(self): return "低端影视"
|
||||
def init(self, extend=""):
|
||||
self.host = "https://ddysx.cc"
|
||||
self.headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36","Referer":self.host + "/"}
|
||||
self.s = requests.Session()
|
||||
self.s.headers.update(self.headers)
|
||||
self.play_headers = {"User-Agent":self.headers["User-Agent"],"Referer":self.host + "/","Origin":self.host,"Accept":"*/*","Connection":"keep-alive"}
|
||||
self.pic_cache = {}
|
||||
self.classes = [{"type_id":"12028759","type_name":"一区-日韩无码"},{"type_id":"12198759","type_name":"一区-中文字幕"},{"type_id":"12208759","type_name":"一区-动漫精品"},{"type_id":"12218759","type_name":"一区-极骚萝莉"},{"type_id":"12248759","type_name":"一区-三级自慰"},{"type_id":"12258759","type_name":"一区-强奸乱伦"},{"type_id":"12008759","type_name":"一区-国产自拍"},{"type_id":"12018759","type_name":"一区-欧美极品"},{"type_id":"12228839","type_name":"二区-主播直播"},{"type_id":"12468839","type_name":"二区-91探花"},{"type_id":"12588839","type_name":"二区-传媒出品"},{"type_id":"12338839","type_name":"二区-自拍偷拍"},{"type_id":"12368839","type_name":"二区-日本精品"},{"type_id":"12298839","type_name":"二区-欧美精品"},{"type_id":"12198839","type_name":"二区-精品推荐"},{"type_id":"12218839","type_name":"二区-国产情色"},{"type_id":"12038769","type_name":"三区-国产精品"},{"type_id":"12178769","type_name":"三区-中文字幕"},{"type_id":"12198769","type_name":"三区-动漫精品"},{"type_id":"12228769","type_name":"三区-日韩精品"},{"type_id":"12268769","type_name":"三区-自拍偷拍"},{"type_id":"12418769","type_name":"三区-大秀视频"},{"type_id":"12008769","type_name":"三区-日韩无码"},{"type_id":"12028769","type_name":"三区-欧美精品"}]
|
||||
|
||||
def _get(self, url):
|
||||
try:
|
||||
r = self.s.get(url, timeout=10, verify=False)
|
||||
r.encoding = r.apparent_encoding or "utf-8"
|
||||
return r.text
|
||||
except requests.RequestException: return ""
|
||||
def _fix(self, u): return "https:" + u if u and u.startswith("//") else self.host + u if u and u.startswith("/") else u or ""
|
||||
def _dec(self, s):
|
||||
try: return base64.b64decode(s + "=" * (-len(s) % 4)).decode("utf-8", "ignore").strip()
|
||||
except Exception: return ""
|
||||
def _txt(self, html): return re.sub(r"<[^>]+>", "", re.sub(r"<script[^>]*>\s*document\.write\(d\(['\"]([^'\"]+)['\"]\)\);?\s*</script>", lambda m:self._dec(m.group(1)), html or "", flags=re.S)).strip()
|
||||
def _pic(self, html):
|
||||
key = html or ""
|
||||
if key in self.pic_cache: return self.pic_cache[key]
|
||||
m = re.search(r"<img[^>]+data-original=['\"]([^'\"]+)['\"]", key) or re.search(r"<img[^>]+data-src=['\"]([^'\"]+)['\"]", key) or re.search(r"<img[^>]+src=['\"]([^'\"]+)['\"]", key)
|
||||
u = self._fix(m.group(1)) if m else ""
|
||||
if u.startswith("https://pic.892539.xyz//"): u = "https://pic.892539.xyz/" + u.split("https://pic.892539.xyz//", 1)[1]
|
||||
self.pic_cache[key] = u
|
||||
return u
|
||||
def _parse_list(self, html):
|
||||
items, seen = [], set()
|
||||
for block in re.findall(r"<li\b[\s\S]*?</li>", html or ""):
|
||||
m = re.search(r"href=['\"](/video/(\d+)\.html)['\"]", block)
|
||||
if not m or m.group(2) in seen: continue
|
||||
seen.add(m.group(2))
|
||||
name = self._txt(block)
|
||||
name = re.sub(r"\s+", " ", name).strip()
|
||||
if name: items.append({"vod_id":m.group(2),"vod_name":name,"vod_pic":self._pic(block)})
|
||||
return items
|
||||
def homeContent(self, filter):
|
||||
html = self._get(self.host + "/")
|
||||
return {"class":self.classes,"list":self._parse_list(html),"filters":{}}
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
html = self._get(f"{self.host}/list/{tid}-{pg}.html")
|
||||
return {"page":int(pg),"pagecount":999,"limit":24,"total":99999,"list":self._parse_list(html)}
|
||||
def detailContent(self, ids):
|
||||
data = []
|
||||
for vid in ids:
|
||||
html = self._get(f"{self.host}/video/{vid}.html")
|
||||
name = self._txt("".join(re.findall(r"<h[1-5][^>]*>[\s\S]*?</h[1-5]>", html)[:1])) or vid
|
||||
pic = self._pic(html)
|
||||
play = re.search(r"<video[^>]+src=['\"]([^'\"]+)['\"]", html) or re.search(r"hls\.loadSource\(['\"]([^'\"]+)['\"]\)", html) or re.search(r"(https?://[^'\"\s<>]+play\.php\?[^'\"\s<>]+)", html)
|
||||
url = play.group(1) if play else f"{self.host}/video/{vid}.html"
|
||||
data.append({"vod_id":vid,"vod_name":name,"vod_pic":pic,"vod_play_from":"播放","vod_play_url":"播放$" + url})
|
||||
return {"list":data}
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
html = self._get(f"{self.host}/search.php?content={quote(key)}")
|
||||
return {"list":[] if "负载过高" in html else self._parse_list(html),"page":int(pg)}
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = self._fix(id)
|
||||
return {"parse":0,"url":url,"header":self.play_headers}
|
||||
Reference in New Issue
Block a user