Sync all projects
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,392 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re, urllib.parse
|
||||
import json
|
||||
from bs4 import BeautifulSoup
|
||||
import requests
|
||||
from base.spider import Spider as BaseSpider
|
||||
|
||||
|
||||
class Spider(BaseSpider):
|
||||
def init(self, extend=""):
|
||||
self.host = "https://maihaolian.com"
|
||||
self.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return '枫叶影院'
|
||||
|
||||
def homeContent(self, filter):
|
||||
return {"class": [
|
||||
{'type_id': "/label/qq", 'type_name': "腾讯VIP精选"},
|
||||
{'type_id': "/label/bli", 'type_name': "B站VIP精选"},
|
||||
{'type_id': "/label/youku", 'type_name': "优酷VIP精选"},
|
||||
{"type_id": "5", "type_name": "红果短剧"},
|
||||
{"type_id": "2", "type_name": "电视剧"},
|
||||
{"type_id": "1", "type_name": "电影"},
|
||||
{"type_id": "4", "type_name": "动漫"},
|
||||
{"type_id": "3", "type_name": "综艺"},
|
||||
], "filters": self._build_filters()}
|
||||
|
||||
def _build_filters(self):
|
||||
area = [{"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": "其它"}]
|
||||
year = [{"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"}, {"n": "2014", "v": "2014"}, {"n": "2013", "v": "2013"},
|
||||
{"n": "2012", "v": "2012"}, {"n": "2011", "v": "2011"}, {"n": "2010", "v": "2010"},
|
||||
{"n": "2009", "v": "2009"}, {"n": "2008", "v": "2008"}, {"n": "2007", "v": "2007"},
|
||||
{"n": "2006", "v": "2006"}, {"n": "2005", "v": "2005"}, {"n": "2004", "v": "2004"}]
|
||||
lang = [{"n": "全部", "v": ""}, {"n": "国语", "v": "国语"}, {"n": "英语", "v": "英语"},
|
||||
{"n": "粤语", "v": "粤语"}, {"n": "闽南语", "v": "闽南语"}, {"n": "韩语", "v": "韩语"},
|
||||
{"n": "日语", "v": "日语"}, {"n": "法语", "v": "法语"}, {"n": "德语", "v": "德语"},
|
||||
{"n": "其它", "v": "其它"}]
|
||||
sort = [{"n": "时间", "v": "time"}, {"n": "人气", "v": "hits"}, {"n": "评分", "v": "score"}]
|
||||
letter = [{"n": "全部", "v": ""}, {"n": "A", "v": "A"}, {"n": "B", "v": "B"}, {"n": "C", "v": "C"},
|
||||
{"n": "D", "v": "D"}, {"n": "E", "v": "E"}, {"n": "F", "v": "F"}, {"n": "G", "v": "G"},
|
||||
{"n": "H", "v": "H"}, {"n": "I", "v": "I"}, {"n": "J", "v": "J"}, {"n": "K", "v": "K"},
|
||||
{"n": "L", "v": "L"}, {"n": "M", "v": "M"}, {"n": "N", "v": "N"}, {"n": "O", "v": "O"},
|
||||
{"n": "P", "v": "P"}, {"n": "Q", "v": "Q"}, {"n": "R", "v": "R"}, {"n": "S", "v": "S"},
|
||||
{"n": "T", "v": "T"}, {"n": "U", "v": "U"}, {"n": "V", "v": "V"}, {"n": "W", "v": "W"},
|
||||
{"n": "X", "v": "X"}, {"n": "Y", "v": "Y"}, {"n": "Z", "v": "Z"}, {"n": "0-9", "v": "0-9"}]
|
||||
return {
|
||||
"2": [
|
||||
{"key": "class", "name": "类型",
|
||||
"value": [{"n": "全部", "v": "2"}, {"n": "国产剧", "v": "13"}, {"n": "日韩剧", "v": "15"},
|
||||
{"n": "海外剧", "v": "16"}]},
|
||||
{"key": "area", "name": "地区", "value": area},
|
||||
{"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in
|
||||
[("全部", ""), ("古装", "古装"), ("战争", "战争"),
|
||||
("青春偶像", "青春偶像"), ("喜剧", "喜剧"),
|
||||
("家庭", "家庭"), ("犯罪", "犯罪"), ("动作", "动作"),
|
||||
("奇幻", "奇幻"), ("剧情", "剧情"), ("历史", "历史"),
|
||||
("经典", "经典"), ("乡村", "乡村"), ("情景", "情景"),
|
||||
("商战", "商战"), ("网剧", "网剧"), ("其他", "其他")]]},
|
||||
{"key": "year", "name": "年份", "value": year},
|
||||
{"key": "lang", "name": "语言", "value": lang},
|
||||
{"key": "letter", "name": "字母", "value": letter},
|
||||
{"key": "sort", "name": "排序", "value": sort},
|
||||
],
|
||||
"1": [
|
||||
{"key": "class", "name": "类型",
|
||||
"value": [{"n": "全部", "v": "1"}, {"n": "动作片", "v": "6"}, {"n": "喜剧片", "v": "7"},
|
||||
{"n": "恐怖片", "v": "8"}, {"n": "科幻片", "v": "9"}, {"n": "爱情片", "v": "10"},
|
||||
{"n": "剧情片", "v": "11"}, {"n": "战争片", "v": "12"}, {"n": "纪录片", "v": "20"}]},
|
||||
{"key": "area", "name": "地区", "value": area},
|
||||
{"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in
|
||||
[("全部", ""), ("喜剧", "喜剧"), ("爱情", "爱情"),
|
||||
("恐怖", "恐怖"), ("动作", "动作"), ("科幻", "科幻"),
|
||||
("剧情", "剧情"), ("战争", "战争"), ("警匪", "警匪"),
|
||||
("犯罪", "犯罪"), ("动画", "动画"), ("奇幻", "奇幻"),
|
||||
("武侠", "武侠"), ("冒险", "冒险"), ("枪战", "枪战"),
|
||||
("悬疑", "悬疑"), ("惊悚", "惊悚"), ("经典", "经典"),
|
||||
("青春", "青春"), ("文艺", "文艺"), ("微电影", "微电影"),
|
||||
("古装", "古装"), ("历史", "历史"), ("运动", "运动"),
|
||||
("农村", "农村"), ("儿童", "儿童"),
|
||||
("网络电影", "网络电影")]]},
|
||||
{"key": "year", "name": "年份", "value": year},
|
||||
{"key": "lang", "name": "语言", "value": lang},
|
||||
{"key": "letter", "name": "字母", "value": letter},
|
||||
{"key": "sort", "name": "排序", "value": sort},
|
||||
],
|
||||
"4": [
|
||||
{"key": "class", "name": "类型",
|
||||
"value": [{"n": "全部", "v": "4"}, {"n": "国产动漫", "v": "25"}, {"n": "日韩动漫", "v": "26"}]},
|
||||
{"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in
|
||||
[("全部", ""), ("情感", "情感"), ("科幻", "科幻"),
|
||||
("热血", "热血"), ("推理", "推理"), ("搞笑", "搞笑"),
|
||||
("冒险", "冒险"), ("奇幻", "奇幻"), ("战斗", "战斗"),
|
||||
("校园", "校园"), ("萝莉", "萝莉"), ("治愈", "治愈"),
|
||||
("原创", "原创"), ("亲子", "亲子"), ("益智", "益智"),
|
||||
("励志", "励志"), ("其他", "其他")]]},
|
||||
{"key": "area", "name": "地区",
|
||||
"value": [{"n": "全部", "v": ""}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"},
|
||||
{"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"},
|
||||
{"n": "日本", "v": "日本"}, {"n": "法国", "v": "法国"}, {"n": "英国", "v": "英国"},
|
||||
{"n": "其它", "v": "其它"}]},
|
||||
{"key": "year", "name": "年份", "value": year},
|
||||
{"key": "lang", "name": "语言", "value": lang},
|
||||
{"key": "letter", "name": "字母", "value": letter},
|
||||
{"key": "sort", "name": "排序", "value": sort},
|
||||
],
|
||||
"3": [
|
||||
{"key": "class", "name": "类型",
|
||||
"value": [{"n": "全部", "v": "3"}, {"n": "大陆综艺", "v": "21"}, {"n": "日韩综艺", "v": "22"}]},
|
||||
{"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in
|
||||
[("全部", ""), ("选秀", "选秀"), ("情感", "情感"),
|
||||
("访谈", "访谈"), ("播报", "播报"), ("音乐", "音乐"),
|
||||
("美食", "美食"), ("旅游", "旅游"), ("搞笑", "搞笑"),
|
||||
("游戏", "游戏"), ("亲子", "亲子"), ("其它", "其它")]]},
|
||||
{"key": "area", "name": "地区",
|
||||
"value": [{"n": "全部", "v": ""}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"},
|
||||
{"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"},
|
||||
{"n": "日本", "v": "日本"}, {"n": "英国", "v": "英国"}, {"n": "其它", "v": "其它"}]},
|
||||
{"key": "year", "name": "年份", "value": year},
|
||||
{"key": "lang", "name": "语言", "value": lang},
|
||||
{"key": "letter", "name": "字母", "value": letter},
|
||||
{"key": "sort", "name": "排序", "value": sort},
|
||||
],
|
||||
}
|
||||
|
||||
def homeVideoContent(self):
|
||||
html = self._fetch('/')
|
||||
return {"list": self._parse_video_list(html)}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
# 构建筛选参数:参照歪比巴卜,直接取extend里的值,fallback到filter
|
||||
if tid.startswith('/label'):
|
||||
url = f'{tid}/page/{pg}.html'
|
||||
html = self._fetch(url)
|
||||
items = self._parse_video_list(html)
|
||||
page = int(pg)
|
||||
page_count = page if len(items) < 24 else page + 2
|
||||
return {"list": items, "page": page, "pagecount": page_count, "limit": 24, "total": page_count * 24}
|
||||
|
||||
args = {}
|
||||
if extend and isinstance(extend, dict):
|
||||
for k, v in extend.items():
|
||||
if v:
|
||||
args[k] = str(v)
|
||||
if isinstance(filter, dict):
|
||||
for k, v in filter.items():
|
||||
if v and k not in args:
|
||||
args[k] = str(v)
|
||||
route_tid = args.get('class', args.get('tid', str(tid)))
|
||||
area = args.get('area', '')
|
||||
genre = args.get('genre', '')
|
||||
year = args.get('year', '')
|
||||
lang = args.get('lang', '')
|
||||
letter = args.get('letter', '')
|
||||
sort = args.get('sort', '')
|
||||
# 无筛选走正常分页
|
||||
if not area and not genre and not year and not lang and not letter and not sort:
|
||||
url = f'/cupfox-list/{route_tid}--------{pg}---.html'
|
||||
html = self._fetch(url)
|
||||
items = self._parse_video_list(html)
|
||||
page = int(pg)
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
pagecount = page
|
||||
for a in soup.select('a.page-link'):
|
||||
if a.text == '尾页':
|
||||
m = re.search(r'---(\d+)---', a.get('href', ''))
|
||||
if m:
|
||||
pagecount = int(m.group(1))
|
||||
break
|
||||
if not items:
|
||||
pagecount = 0
|
||||
return {"list": items, "page": page, "pagecount": pagecount, "limit": 36, "total": 9999}
|
||||
# 有筛选:{tid}-{area}-{sort}-{genre}-{lang}-{letter}------{year}.html
|
||||
segs = [route_tid, area, sort, genre, lang, letter, '', '', year]
|
||||
url = '/cupfox-list/' + '-'.join(segs) + '.html'
|
||||
html = self._fetch(url)
|
||||
items = self._parse_video_list(html)
|
||||
return {"list": items, "page": 1, "pagecount": 1, "limit": 36, "total": 9999}
|
||||
|
||||
def detailContent(self, ids):
|
||||
result = {"list": []}
|
||||
vid = ids[0].split(',')[0].strip()
|
||||
try:
|
||||
html = self._fetch(f'/detail/{vid}.html')
|
||||
if not html: return result
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
vod_name = soup.select_one('h3.slide-info-title')
|
||||
vod_name = vod_name.text.strip() if vod_name else ''
|
||||
vod_pic = soup.select_one('img.lazy')
|
||||
vod_pic = self._fix_pic(vod_pic.get('data-src', '')) if vod_pic else ''
|
||||
vod_director = ''
|
||||
vod_actor = ''
|
||||
for el in soup.select('.slide-info'):
|
||||
text = el.get_text(' ').strip()
|
||||
if text.startswith('导演:'):
|
||||
vod_director = text.replace('导演:', '').strip()
|
||||
elif text.startswith('演员:'):
|
||||
vod_actor = text.replace('演员:', '').strip()
|
||||
vod_content = soup.select_one('#height_limit')
|
||||
vod_content = vod_content.get_text(' ', strip=True) if vod_content else ''
|
||||
play_from, play_url = [], []
|
||||
for tab in soup.select('.anthology-tab a.swiper-slide'):
|
||||
src_name = re.sub(r'<[^>]+>', '', str(tab)).strip() or tab.get_text(' ', strip=True).strip()
|
||||
if src_name:
|
||||
play_from.append(src_name)
|
||||
tab_blocks = soup.select('.anthology-list-box')
|
||||
for i, block in enumerate(tab_blocks):
|
||||
ep_list = []
|
||||
for a in block.select('li a'):
|
||||
href = a.get('href', '')
|
||||
m = re.search(r'/play/(.*?)\.html', href)
|
||||
if m:
|
||||
ep_list.append(f'{a.text.strip()}${vid}-{m.group(1)}')
|
||||
ep_list.reverse()
|
||||
if ep_list and i < len(play_from):
|
||||
play_url.append('#'.join(ep_list))
|
||||
valid_from = [pf for i, pf in enumerate(play_from) if i < len(play_url)]
|
||||
result["list"].append({
|
||||
"vod_id": vid, "vod_name": vod_name, "vod_pic": vod_pic,
|
||||
"vod_director": vod_director, "vod_actor": vod_actor,
|
||||
"vod_content": vod_content,
|
||||
"vod_play_from": "$$$".join(valid_from),
|
||||
"vod_play_url": "$$$".join(play_url),
|
||||
})
|
||||
except:
|
||||
pass
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
try:
|
||||
decoded = urllib.parse.unquote(key)
|
||||
except:
|
||||
decoded = key
|
||||
html = self._fetch(f'/cupfox-search/{urllib.parse.quote(decoded)}----------{pg}---.html')
|
||||
items = self._parse_search_list(html)
|
||||
return {"list": items, "page": int(pg), "pagecount": 1, "limit": 36, "total": len(items)}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = ''
|
||||
try:
|
||||
url = id if id.startswith('http') else f'{self.host}/play/{id}.html'
|
||||
html = self._fetch(url)
|
||||
if html:
|
||||
m = re.search(r'player_aaaa=(.*?)</script>', html, re.S)
|
||||
if m:
|
||||
|
||||
try:
|
||||
pd = json.loads(m.group(1))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pd = {}
|
||||
# print('pd:', pd)
|
||||
play_url = pd.get('url')
|
||||
play_id = pd.get('from')
|
||||
|
||||
api_map = {
|
||||
'YYNB': 'https://zzrs.mfdyvip.com/player/mplayer.php',
|
||||
'JD4K': 'https://fgsrg.hzqingshan.com/player/mplayer.php',
|
||||
}
|
||||
if not play_url:
|
||||
return {"parse": 0, "url": 'https://php.doube.eu.org/error.m3u8',
|
||||
"header": {'User-Agent': 'Mozilla/5.0'}}
|
||||
if play_url.startswith('http') and (play_url.endswith('.m3u8') or play_url.endswith('.mp4')):
|
||||
return {"parse": 0, "url": play_url, "header": {'User-Agent': 'Mozilla/5.0'}}
|
||||
|
||||
else:
|
||||
headers = {
|
||||
'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
||||
'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
'accept-language': "zh-CN,zh;q=0.9",
|
||||
'cache-control': "no-cache",
|
||||
'pragma': "no-cache",
|
||||
'priority': "u=0, i",
|
||||
'referer': "https://www.ht10010.com/",
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
response = requests.get(f"https://fgsrg.hzqingshan.com/player/?url={play_url}", headers=headers)
|
||||
token = re.search(r'data-te="(.*?)"', response.text)
|
||||
if token:
|
||||
token = token.group(1)
|
||||
payload = {
|
||||
'url': play_url,
|
||||
'token': token
|
||||
}
|
||||
# print('payload', payload)
|
||||
try:
|
||||
response = self.post(api_map[play_id], data=payload, headers=headers)
|
||||
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
# print('result:', result)
|
||||
if result['code'] == 200 and 'url' in result:
|
||||
play_url = result['url']
|
||||
return {"parse": 0, "url": play_url, "header": {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'}}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {"parse": 1, "url": url}
|
||||
|
||||
def localProxy(self, param=''):
|
||||
return {}
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return False
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def _fetch(self, url):
|
||||
try:
|
||||
if not url.startswith('http'):
|
||||
url = self.host + url
|
||||
rsp = self.fetch(url, headers=self.headers)
|
||||
return rsp.text if rsp else ''
|
||||
except:
|
||||
return ''
|
||||
|
||||
def _fix_pic(self, u):
|
||||
if not u: return ''
|
||||
if u.startswith('//'): return 'https:' + u
|
||||
return u.replace('&', '&')
|
||||
|
||||
def _parse_video_list(self, html):
|
||||
videos, seen = [], set()
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
cards = soup.select('a.public-list-exp')
|
||||
for a in cards:
|
||||
href = a.get('href', '')
|
||||
m = re.search(r'/detail/(\d+)\.html', href)
|
||||
if not m: continue
|
||||
vod_id = m.group(1)
|
||||
if vod_id in seen: continue
|
||||
seen.add(vod_id)
|
||||
span = ','.join([span.text for span in a.select('span.public-prt')])
|
||||
# print('span', span)
|
||||
vod_name = a.get('title', '') or (a.select_one('img') and a.select_one('img').get('alt', '')) or ''
|
||||
pic_el = a.select_one('img')
|
||||
vod_pic = self._fix_pic(pic_el.get('data-src', '')) if pic_el else ''
|
||||
remark_el = a.select_one('.ft2') or a.select_one('.public-list-prb')
|
||||
vod_remarks = remark_el.text.strip() if remark_el else ''
|
||||
videos.append(
|
||||
{"vod_id": vod_id, "vod_name": vod_name.strip(), "vod_pic": vod_pic, "vod_remarks": vod_remarks, "vod_year": span})
|
||||
return videos
|
||||
|
||||
def _parse_search_list(self, html):
|
||||
videos, seen = [], set()
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
cards = soup.select('a.public-list-exp')
|
||||
for a in cards:
|
||||
href = a.get('href', '')
|
||||
m = re.search(r'/detail/(\d+)\.html', href)
|
||||
if not m: continue
|
||||
vod_id = m.group(1)
|
||||
if vod_id in seen: continue
|
||||
seen.add(vod_id)
|
||||
pic_el = a.select_one('img')
|
||||
vod_pic = self._fix_pic(pic_el.get('data-src', '')) if pic_el else ''
|
||||
title_el = soup.select_one(f'a.thumb-txt[href="/detail/{vod_id}.html"]')
|
||||
if title_el:
|
||||
vod_name = title_el.text.strip()
|
||||
else:
|
||||
vod_name = a.select_one('img') and a.select_one('img').get('alt', '') or ''
|
||||
remark_el = a.select_one('.public-list-prb') or a.select_one('.ft2')
|
||||
vod_remarks = remark_el.text.strip() if remark_el else ''
|
||||
videos.append(
|
||||
{"vod_id": vod_id, "vod_name": vod_name.strip(), "vod_pic": vod_pic, "vod_remarks": vod_remarks})
|
||||
return videos
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sp = Spider()
|
||||
sp.init()
|
||||
# 20067-5-189
|
||||
print(sp.categoryContent('/label/qq','1',True, {}))
|
||||
# print(sp.playerContent('', '20067-6-189', []))
|
||||
# print(sp.playerContent('', '20067-5-189', []))
|
||||
pass
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user