Sync all projects

This commit is contained in:
github-actions[bot]
2026-07-30 13:13:43 +00:00
parent f75f772d13
commit d98caa5150
19 changed files with 5998 additions and 3174 deletions
+2 -2
View File
@@ -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
};
};
+240
View File
@@ -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&currentPage={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
+240
View File
@@ -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&currentPage={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
}
+278
View File
@@ -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
+428
View File
@@ -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}
+255
View File
@@ -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
+208
View File
@@ -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("&nbsp;", " ")).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
+311
View File
@@ -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
+133
View File
@@ -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, ""]
+68
View File
@@ -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}