上传文件至「py」
This commit is contained in:
@@ -0,0 +1,470 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
sys.path.append('..')
|
||||||
|
from base.spider import Spider
|
||||||
|
|
||||||
|
|
||||||
|
class Spider(Spider):
|
||||||
|
|
||||||
|
def init(self, extend=""):
|
||||||
|
self.extend = extend
|
||||||
|
self.cookie_cache = ""
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return "抖音直播"
|
||||||
|
|
||||||
|
def isVideoFormat(self, url):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def manualVideoCheck(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def localProxy(self, param):
|
||||||
|
return None
|
||||||
|
|
||||||
|
host = "https://live.douyin.com"
|
||||||
|
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"User-Agent": ua,
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||||
|
}
|
||||||
|
|
||||||
|
classes_config = [
|
||||||
|
{"type_id": "10000$3", "type_name": "娱乐天地"},
|
||||||
|
{"type_id": "10001$3", "type_name": "科技文化"},
|
||||||
|
{"type_id": "102$4", "type_name": "音乐"},
|
||||||
|
{"type_id": "103$4", "type_name": "游戏"},
|
||||||
|
{"type_id": "105$4", "type_name": "舞蹈"},
|
||||||
|
{"type_id": "101$4", "type_name": "聊天"},
|
||||||
|
{"type_id": "108$4", "type_name": "运动"},
|
||||||
|
{"type_id": "107$4", "type_name": "生活"},
|
||||||
|
{"type_id": "106$4", "type_name": "文化"},
|
||||||
|
{"type_id": "104$4", "type_name": "二次元"},
|
||||||
|
]
|
||||||
|
|
||||||
|
# ==================== 工具函数 ====================
|
||||||
|
def _generate_device_id(self):
|
||||||
|
timestamp = self._base36_encode(int(time.time() * 1000))
|
||||||
|
random_part = ''.join(random.choices(string.ascii_lowercase + string.digits, k=13))
|
||||||
|
return f"{timestamp}{random_part}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _base36_encode(num):
|
||||||
|
alphabet = '0123456789abcdefghijklmnopqrstuvwxyz'
|
||||||
|
if num == 0:
|
||||||
|
return '0'
|
||||||
|
res = []
|
||||||
|
while num > 0:
|
||||||
|
num, rem = divmod(num, 36)
|
||||||
|
res.append(alphabet[rem])
|
||||||
|
return ''.join(reversed(res))
|
||||||
|
|
||||||
|
def _get_cookie(self):
|
||||||
|
if self.cookie_cache:
|
||||||
|
return self.cookie_cache
|
||||||
|
try:
|
||||||
|
resp = self.fetch(self.host, headers=self.headers, verify=False)
|
||||||
|
cookies = resp.headers.get('set-cookie', '')
|
||||||
|
if cookies:
|
||||||
|
match = re.search(r'ttwid=([^;]+)', cookies)
|
||||||
|
if match:
|
||||||
|
self.cookie_cache = f"ttwid={match.group(1)}"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return self.cookie_cache
|
||||||
|
|
||||||
|
def _get_headers(self):
|
||||||
|
cookie = self._get_cookie()
|
||||||
|
hd = self.headers.copy()
|
||||||
|
hd["Referer"] = self.host
|
||||||
|
if cookie:
|
||||||
|
hd["Cookie"] = cookie
|
||||||
|
return hd
|
||||||
|
|
||||||
|
def _first_non_empty(self, *values):
|
||||||
|
for v in values:
|
||||||
|
if v is not None and v != '':
|
||||||
|
return v
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _parse_raw_live_data(self, item):
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
return None
|
||||||
|
candidates = [
|
||||||
|
item.get('lives', {}).get('rawdata'),
|
||||||
|
item.get('lives', {}).get('raw_data'),
|
||||||
|
item.get('live', {}).get('rawdata'),
|
||||||
|
item.get('live_info', {}).get('rawdata'),
|
||||||
|
item.get('aweme_info', {}).get('live_info', {}).get('rawdata'),
|
||||||
|
item.get('data', {}).get('rawdata'),
|
||||||
|
item.get('rawdata'),
|
||||||
|
item.get('lives'),
|
||||||
|
item.get('live'),
|
||||||
|
item.get('live_info'),
|
||||||
|
item.get('aweme_info', {}).get('live_info'),
|
||||||
|
item.get('aweme_info'),
|
||||||
|
item.get('data'),
|
||||||
|
item
|
||||||
|
]
|
||||||
|
for c in candidates:
|
||||||
|
if isinstance(c, str):
|
||||||
|
try:
|
||||||
|
parsed = json.loads(c)
|
||||||
|
if isinstance(parsed, dict):
|
||||||
|
return parsed
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
elif isinstance(c, dict):
|
||||||
|
return c
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _normalize_search_item(self, raw, fallback=None):
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
return None
|
||||||
|
fallback = fallback or {}
|
||||||
|
|
||||||
|
room_id = self._first_non_empty(
|
||||||
|
raw.get('id_str'),
|
||||||
|
raw.get('room_id_str'),
|
||||||
|
raw.get('room', {}).get('id_str'),
|
||||||
|
raw.get('room', {}).get('id'),
|
||||||
|
raw.get('room_id'),
|
||||||
|
raw.get('roomId')
|
||||||
|
)
|
||||||
|
if not room_id:
|
||||||
|
return None
|
||||||
|
|
||||||
|
web_rid = self._first_non_empty(
|
||||||
|
raw.get('owner', {}).get('web_rid'),
|
||||||
|
raw.get('web_rid'),
|
||||||
|
raw.get('room', {}).get('owner', {}).get('web_rid')
|
||||||
|
) or self._generate_device_id()
|
||||||
|
|
||||||
|
nickname = self._first_non_empty(
|
||||||
|
raw.get('owner', {}).get('nickname'),
|
||||||
|
raw.get('nickname'),
|
||||||
|
raw.get('room', {}).get('owner', {}).get('nickname'),
|
||||||
|
fallback.get('nickname')
|
||||||
|
) or '抖音直播'
|
||||||
|
|
||||||
|
title = self._first_non_empty(
|
||||||
|
raw.get('title'),
|
||||||
|
raw.get('room', {}).get('title'),
|
||||||
|
fallback.get('title'),
|
||||||
|
nickname
|
||||||
|
)
|
||||||
|
|
||||||
|
pic = self._first_non_empty(
|
||||||
|
raw.get('owner', {}).get('avatar_large', {}).get('url_list', [None])[0],
|
||||||
|
raw.get('room', {}).get('cover', {}).get('url_list', [None])[0],
|
||||||
|
raw.get('cover', {}).get('url_list', [None])[0],
|
||||||
|
raw.get('cover_url')
|
||||||
|
) or ''
|
||||||
|
|
||||||
|
online_text = self._first_non_empty(
|
||||||
|
raw.get('room', {}).get('stats', {}).get('user_count_str'),
|
||||||
|
raw.get('user_count_str'),
|
||||||
|
raw.get('room', {}).get('user_count_str'),
|
||||||
|
raw.get('user_count')
|
||||||
|
)
|
||||||
|
tag_text = self._first_non_empty(
|
||||||
|
raw.get('video_feed_tag'),
|
||||||
|
raw.get('room', {}).get('partition_road_map', [{}])[0].get('title'),
|
||||||
|
raw.get('partition', {}).get('title'),
|
||||||
|
fallback.get('tag')
|
||||||
|
)
|
||||||
|
remark = ' '.join(filter(None, [tag_text, online_text]))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"vod_id": f"{web_rid}@@{room_id}",
|
||||||
|
"vod_name": nickname,
|
||||||
|
"vod_pic": pic,
|
||||||
|
"vod_remarks": remark,
|
||||||
|
"vod_content": title
|
||||||
|
}
|
||||||
|
|
||||||
|
def _extract_search_videos(self, payload):
|
||||||
|
if not isinstance(payload, list):
|
||||||
|
return []
|
||||||
|
results = []
|
||||||
|
seen = set()
|
||||||
|
for item in payload:
|
||||||
|
raw = self._parse_raw_live_data(item)
|
||||||
|
norm = self._normalize_search_item(raw, {
|
||||||
|
"nickname": item.get('nickname'),
|
||||||
|
"title": item.get('title') or item.get('desc'),
|
||||||
|
"tag": item.get('search_keyword')
|
||||||
|
})
|
||||||
|
if not norm:
|
||||||
|
continue
|
||||||
|
if norm['vod_id'] in seen:
|
||||||
|
continue
|
||||||
|
seen.add(norm['vod_id'])
|
||||||
|
results.append(norm)
|
||||||
|
return results
|
||||||
|
|
||||||
|
# ==================== 框架标准方法 ====================
|
||||||
|
def homeContent(self, filter):
|
||||||
|
classes = self.classes_config
|
||||||
|
return {
|
||||||
|
"class": classes,
|
||||||
|
"list": []
|
||||||
|
}
|
||||||
|
|
||||||
|
def homeVideoContent(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def categoryContent(self, tid, pg, filter, extend):
|
||||||
|
category_id = str(tid)
|
||||||
|
page = int(pg or 1)
|
||||||
|
offset = 15 * (page - 1)
|
||||||
|
parts = category_id.split('$')
|
||||||
|
if len(parts) < 2:
|
||||||
|
return {"list": [], "page": page, "pagecount": 0, "limit": 15, "total": 9999}
|
||||||
|
partition, ptype = parts[0], parts[1]
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"aid": "6383",
|
||||||
|
"app_name": "douyin_web",
|
||||||
|
"live_id": "1",
|
||||||
|
"device_platform": "web",
|
||||||
|
"language": "zh-CN",
|
||||||
|
"browser_language": "zh-CN",
|
||||||
|
"browser_platform": "Win32",
|
||||||
|
"browser_name": "Chrome",
|
||||||
|
"browser_version": "120.0.0.0",
|
||||||
|
"partition": partition,
|
||||||
|
"partition_type": ptype,
|
||||||
|
"count": "15",
|
||||||
|
"offset": str(offset),
|
||||||
|
"web_rid": self._generate_device_id(),
|
||||||
|
"cookie_enabled": "true",
|
||||||
|
"screen_width": "1920",
|
||||||
|
"screen_height": "1080"
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = self._get_headers()
|
||||||
|
urls = [
|
||||||
|
"https://live.douyin.com/webcast/web/partition/detail/room/v2/",
|
||||||
|
"https://webcast.amemv.com/webcast/web/partition/detail/room/v2/",
|
||||||
|
]
|
||||||
|
list_ = []
|
||||||
|
for url in urls:
|
||||||
|
try:
|
||||||
|
resp = self.fetch(url, headers=headers, params=params, verify=False)
|
||||||
|
data = resp.json()
|
||||||
|
if data.get('status_code') != 0:
|
||||||
|
continue
|
||||||
|
if not data.get('data', {}).get('data'):
|
||||||
|
break
|
||||||
|
items = data['data']['data']
|
||||||
|
for it in items:
|
||||||
|
web_rid = it.get('web_rid') or self._generate_device_id()
|
||||||
|
room = it['room']
|
||||||
|
list_.append({
|
||||||
|
"vod_id": f"{web_rid}@@{room['id_str']}",
|
||||||
|
"vod_name": room['title'],
|
||||||
|
"vod_pic": room['cover']['url_list'][0],
|
||||||
|
"vod_remarks": f"{room['owner']['nickname']} (🔥{room['stats']['user_count_str']})"
|
||||||
|
})
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return {
|
||||||
|
"list": list_,
|
||||||
|
"page": page,
|
||||||
|
"pagecount": 9999,
|
||||||
|
"limit": 15,
|
||||||
|
"total": 999999
|
||||||
|
}
|
||||||
|
|
||||||
|
def searchContent(self, key, quick, pg="1"):
|
||||||
|
kw = key.strip()
|
||||||
|
if not kw:
|
||||||
|
return {"list": [], "page": 1}
|
||||||
|
page = 1
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
headers = self._get_headers()
|
||||||
|
|
||||||
|
# 策略1 专用直播搜索
|
||||||
|
try:
|
||||||
|
params1 = {
|
||||||
|
"device_platform": "webapp",
|
||||||
|
"aid": "6383",
|
||||||
|
"channel": "channel_pc_web",
|
||||||
|
"search_channel": "aweme_live",
|
||||||
|
"search_source": "switch_tab",
|
||||||
|
"query_correct_type": "1",
|
||||||
|
"need_filter_settings": "1",
|
||||||
|
"list_type": "single",
|
||||||
|
"keyword": kw,
|
||||||
|
"offset": str(offset),
|
||||||
|
"count": "20",
|
||||||
|
"os_version": "10"
|
||||||
|
}
|
||||||
|
r = self.fetch("https://www.douyin.com/aweme/v1/web/live/search/",
|
||||||
|
params=params1, headers=headers, verify=False)
|
||||||
|
data = r.json()
|
||||||
|
list_ = self._extract_search_videos(data.get('data'))
|
||||||
|
if list_:
|
||||||
|
return {"list": list_, "page": 1}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 策略2 通用搜索
|
||||||
|
try:
|
||||||
|
params2 = {
|
||||||
|
"device_platform": "webapp",
|
||||||
|
"aid": "6383",
|
||||||
|
"channel": "channel_pc_web",
|
||||||
|
"search_channel": "aweme_live",
|
||||||
|
"keyword": kw,
|
||||||
|
"offset": str(offset),
|
||||||
|
"count": "20",
|
||||||
|
"os_version": "10"
|
||||||
|
}
|
||||||
|
r = self.fetch("https://www.douyin.com/aweme/v1/web/general/search/stream/",
|
||||||
|
params=params2, headers=headers, verify=False)
|
||||||
|
data = r.json()
|
||||||
|
list_ = self._extract_search_videos(data.get('data'))
|
||||||
|
if list_:
|
||||||
|
return {"list": list_, "page": 1}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 降级分区搜索
|
||||||
|
try:
|
||||||
|
part_url = f"https://live.douyin.com/webcast/web/partition/search/?keyword={kw}&aid=6383"
|
||||||
|
r = self.fetch(part_url, headers=self._get_headers(), verify=False)
|
||||||
|
data = r.json()
|
||||||
|
partitions = data.get('data', {}).get('SearchResult', [])
|
||||||
|
if not partitions:
|
||||||
|
return {"list": [], "page": 1}
|
||||||
|
|
||||||
|
merged = []
|
||||||
|
seen = set()
|
||||||
|
for i in range(min(3, len(partitions))):
|
||||||
|
part = partitions[i].get('partition', {})
|
||||||
|
p_id = part.get('id_str')
|
||||||
|
p_type = part.get('type')
|
||||||
|
if not p_id or p_type is None:
|
||||||
|
continue
|
||||||
|
cate_ret = self.categoryContent(f"{p_id}${p_type}", 1, None, None)
|
||||||
|
for item in cate_ret.get('list', []):
|
||||||
|
if item['vod_id'] in seen:
|
||||||
|
continue
|
||||||
|
seen.add(item['vod_id'])
|
||||||
|
item['vod_remarks'] = item['vod_remarks'] or part.get('title', kw)
|
||||||
|
merged.append(item)
|
||||||
|
if len(merged) >= 20:
|
||||||
|
break
|
||||||
|
if len(merged) >= 20:
|
||||||
|
break
|
||||||
|
return {"list": merged, "page": 1}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return {"list": [], "page": 1}
|
||||||
|
|
||||||
|
def detailContent(self, ids):
|
||||||
|
if not ids:
|
||||||
|
return {"list": []}
|
||||||
|
raw_id = ids[0]
|
||||||
|
parts = raw_id.split('@@')
|
||||||
|
if len(parts) != 2:
|
||||||
|
return {"list": []}
|
||||||
|
web_rid, room_id = parts[0], parts[1]
|
||||||
|
|
||||||
|
url = "https://live.douyin.com/webcast/room/web/enter/"
|
||||||
|
params = {
|
||||||
|
"aid": "6383",
|
||||||
|
"app_name": "douyin_web",
|
||||||
|
"live_id": "1",
|
||||||
|
"device_platform": "web",
|
||||||
|
"enter_from": "web_live",
|
||||||
|
"browser_language": "zh-CN",
|
||||||
|
"browser_platform": "Win32",
|
||||||
|
"browser_name": "Chrome",
|
||||||
|
"browser_version": "120.0.0.0",
|
||||||
|
"web_rid": web_rid,
|
||||||
|
"room_id_str": room_id,
|
||||||
|
"enter_source": "",
|
||||||
|
"is_need_double_stream": "false"
|
||||||
|
}
|
||||||
|
headers = self._get_headers()
|
||||||
|
try:
|
||||||
|
r = self.fetch(url, params=params, headers=headers, verify=False)
|
||||||
|
data = r.json()
|
||||||
|
if not data.get('data', {}).get('data'):
|
||||||
|
return {"list": []}
|
||||||
|
info = data['data']['data'][0]
|
||||||
|
|
||||||
|
resolution_map = {
|
||||||
|
"FULL_HD1": "蓝光",
|
||||||
|
"HD1": "超清",
|
||||||
|
"ORIGION": "原画",
|
||||||
|
"SD1": "标清",
|
||||||
|
"SD2": "高清"
|
||||||
|
}
|
||||||
|
|
||||||
|
flv_pull = info.get('stream_url', {}).get('flv_pull_url', {})
|
||||||
|
flv_episodes = []
|
||||||
|
for k, v in flv_pull.items():
|
||||||
|
name = resolution_map.get(k, k)
|
||||||
|
flv_episodes.append(f"{name}${v}")
|
||||||
|
|
||||||
|
hls_pull = info.get('stream_url', {}).get('hls_pull_url_map', {})
|
||||||
|
hls_episodes = []
|
||||||
|
for k, v in hls_pull.items():
|
||||||
|
name = resolution_map.get(k, k)
|
||||||
|
hls_episodes.append(f"{name}${v}")
|
||||||
|
|
||||||
|
vod_play_from = ""
|
||||||
|
vod_play_url = ""
|
||||||
|
if flv_episodes:
|
||||||
|
vod_play_from += "FLV$$$"
|
||||||
|
vod_play_url += "#".join(flv_episodes) + "$$$"
|
||||||
|
if hls_episodes:
|
||||||
|
vod_play_from += "HLS"
|
||||||
|
vod_play_url += "#".join(hls_episodes)
|
||||||
|
|
||||||
|
vod_play_from = vod_play_from.rstrip("$$$")
|
||||||
|
vod_play_url = vod_play_url.rstrip("$$$")
|
||||||
|
|
||||||
|
vod = {
|
||||||
|
"vod_id": raw_id,
|
||||||
|
"vod_name": info['title'],
|
||||||
|
"vod_pic": info['cover']['url_list'][0],
|
||||||
|
"vod_actor": info['owner']['nickname'],
|
||||||
|
"vod_content": "拾光请你看:"+info['title'],
|
||||||
|
"vod_play_from": vod_play_from,
|
||||||
|
"vod_play_url": vod_play_url
|
||||||
|
}
|
||||||
|
return {"list": [vod]}
|
||||||
|
except Exception:
|
||||||
|
return {"list": []}
|
||||||
|
|
||||||
|
def playerContent(self, flag, id, vipFlags):
|
||||||
|
if not id:
|
||||||
|
return {"parse": 0, "url": "", "header": self.headers}
|
||||||
|
return {
|
||||||
|
"parse": 0,
|
||||||
|
"url": id,
|
||||||
|
"header": {
|
||||||
|
"User-Agent": self.ua,
|
||||||
|
"Referer": self.host
|
||||||
|
}
|
||||||
|
}
|
||||||
+392
@@ -0,0 +1,392 @@
|
|||||||
|
# -*- 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
|
||||||
+392
@@ -0,0 +1,392 @@
|
|||||||
|
# -*- 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://www.shzchc.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": "2", "type_name": "电视剧"},
|
||||||
|
{"type_id": "1", "type_name": "电影"},
|
||||||
|
{"type_id": "4", "type_name": "动漫"},
|
||||||
|
{"type_id": "3", "type_name": "综艺"},
|
||||||
|
{"type_id": "5", "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,250 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# by @嗷呜
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pyquery import PyQuery as pq
|
||||||
|
|
||||||
|
sys.path.append('..')
|
||||||
|
from base.spider import Spider
|
||||||
|
|
||||||
|
|
||||||
|
class Spider(Spider):
|
||||||
|
|
||||||
|
def init(self, extend=""):
|
||||||
|
self.extend = extend
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return "毒舌影视"
|
||||||
|
|
||||||
|
def isVideoFormat(self, url):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def manualVideoCheck(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
host = 'https://www.xnhrsb.com/'
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; Mobile) '
|
||||||
|
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||||
|
'Chrome/114.0.0.0 Mobile Safari/537.36',
|
||||||
|
'Referer': host,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 对应 js 里的 class_name / class_url
|
||||||
|
classes_config = [
|
||||||
|
("电影", "1"),
|
||||||
|
("电视剧", "2"),
|
||||||
|
("综艺", "3"),
|
||||||
|
("动漫", "4"),
|
||||||
|
("短剧", "5"),
|
||||||
|
("豆瓣", "duoban"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def homeContent(self, filter):
|
||||||
|
result = {}
|
||||||
|
classes = []
|
||||||
|
vlist = []
|
||||||
|
|
||||||
|
# 静态分类
|
||||||
|
for name, tid in self.classes_config:
|
||||||
|
classes.append({
|
||||||
|
"type_name": name,
|
||||||
|
"type_id": tid
|
||||||
|
})
|
||||||
|
|
||||||
|
# 首页推荐,沿用一级规则里的 .mrb ul li
|
||||||
|
rsp = self.fetch(self.host, headers=self.headers)
|
||||||
|
data = pq(rsp.text)
|
||||||
|
|
||||||
|
# 一级: '.mrb&&ul li;.dytit&&Text;.lazy&&data-original;.hdinfo&&Text;a&&href'
|
||||||
|
vlist.extend(self.getlist(data('.mrb ul li')))
|
||||||
|
|
||||||
|
result['class'] = classes
|
||||||
|
result['list'] = vlist
|
||||||
|
return result
|
||||||
|
|
||||||
|
def homeVideoContent(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def categoryContent(self, tid, pg, filter, extend):
|
||||||
|
# js: url: '/dsshiyisw/fyclass--------fypage---.html'
|
||||||
|
url = f'{self.host}dsshiyisw/{tid}--------{pg}---.html'
|
||||||
|
rsp = self.fetch(url, headers=self.headers)
|
||||||
|
data = pq(rsp.text)
|
||||||
|
|
||||||
|
videos = self.getlist(data('.mrb ul li'))
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'list': videos,
|
||||||
|
'page': pg,
|
||||||
|
'pagecount': 9999,
|
||||||
|
'limit': 90,
|
||||||
|
'total': 999999
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
def detailContent(self, ids):
|
||||||
|
vid = ids[0]
|
||||||
|
if not vid.startswith('http'):
|
||||||
|
if vid.startswith('/'):
|
||||||
|
url = self.host.rstrip('/') + vid
|
||||||
|
else:
|
||||||
|
url = self.host.rstrip('/') + '/' + vid
|
||||||
|
else:
|
||||||
|
url = vid
|
||||||
|
|
||||||
|
rsp = self.fetch(url, headers=self.headers)
|
||||||
|
data = pq(rsp.text)
|
||||||
|
|
||||||
|
# 二级: title/img/desc/content/tabs/lists
|
||||||
|
# title: 'h1&&Text;.moviedteail_list li&&a&&Text'
|
||||||
|
name = data('h1').text()
|
||||||
|
|
||||||
|
info_list = data('.moviedteail_list li')
|
||||||
|
|
||||||
|
def li_text(idx):
|
||||||
|
return info_list.eq(idx).text() if idx < len(info_list) else ''
|
||||||
|
|
||||||
|
# 参考 desc 顺序,大致映射
|
||||||
|
# desc: '.moviedteail_list li:eq(3)&&Text;
|
||||||
|
# .moviedteail_list li:eq(2)&&Text;
|
||||||
|
# .moviedteail_list li:eq(1)&&Text;
|
||||||
|
# .moviedteail_list li:eq(6)&&Text;
|
||||||
|
# .moviedteail_list li:eq(4)&&Text'
|
||||||
|
type_name = li_text(3)
|
||||||
|
director = li_text(2)
|
||||||
|
actor = li_text(1)
|
||||||
|
remarks = li_text(6)
|
||||||
|
year_or_area = li_text(4)
|
||||||
|
|
||||||
|
pic = data('div.dyimg img').attr('src') or ''
|
||||||
|
if pic and not pic.startswith('http'):
|
||||||
|
if pic.startswith('/'):
|
||||||
|
pic = self.host.rstrip('/') + pic
|
||||||
|
else:
|
||||||
|
pic = self.host.rstrip('/') + '/' + pic
|
||||||
|
|
||||||
|
content = data('.yp_context').text()
|
||||||
|
|
||||||
|
vod = {
|
||||||
|
'vod_id': vid,
|
||||||
|
'vod_name': name,
|
||||||
|
'vod_pic': pic,
|
||||||
|
'type_name': type_name,
|
||||||
|
'vod_year': year_or_area,
|
||||||
|
'vod_area': '',
|
||||||
|
'vod_remarks': remarks,
|
||||||
|
'vod_actor': actor,
|
||||||
|
'vod_director': director,
|
||||||
|
'vod_content': content,
|
||||||
|
'vod_play_from': '',
|
||||||
|
'vod_play_url': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
# tabs: '.mi_paly_box .ypxingq_t'
|
||||||
|
tabs = [i.text() for i in data('.mi_paly_box .ypxingq_t').items()]
|
||||||
|
|
||||||
|
# lists: '.paly_list_btn:eq(#id) a:gt(0)'
|
||||||
|
# 对应每个 tab 一组播放列表
|
||||||
|
play_lists = []
|
||||||
|
play_uls = list(data('.paly_list_btn').items())
|
||||||
|
|
||||||
|
for idx, ul in enumerate(play_uls):
|
||||||
|
items = []
|
||||||
|
for i, a in enumerate(ul('a').items()):
|
||||||
|
# a:gt(0) => 跳过第一个
|
||||||
|
if i == 0:
|
||||||
|
continue
|
||||||
|
title = a.text()
|
||||||
|
href = a.attr('href') or ''
|
||||||
|
if href and not href.startswith('http'):
|
||||||
|
if href.startswith('/'):
|
||||||
|
href = self.host.rstrip('/') + href
|
||||||
|
else:
|
||||||
|
href = self.host.rstrip('/') + '/' + href
|
||||||
|
items.append(f'{title}${href}')
|
||||||
|
play_lists.append('#'.join(items))
|
||||||
|
|
||||||
|
vod['vod_play_from'] = '$$$'.join(tabs)
|
||||||
|
vod['vod_play_url'] = '$$$'.join(play_lists)
|
||||||
|
|
||||||
|
return {'list': [vod]}
|
||||||
|
|
||||||
|
def searchContent(self, key, quick, pg="1"):
|
||||||
|
# js: searchUrl: '/dsshiyisc/**----------fypage---.html'
|
||||||
|
url = f'{self.host}dsshiyisc/{key}----------{pg}---.html'
|
||||||
|
rsp = self.fetch(url, headers=self.headers)
|
||||||
|
data = pq(rsp.text)
|
||||||
|
|
||||||
|
videos = self.getlist(data('.mrb ul li'))
|
||||||
|
return {
|
||||||
|
'list': videos,
|
||||||
|
'page': pg
|
||||||
|
}
|
||||||
|
|
||||||
|
def playerContent(self, flag, id, vipFlags):
|
||||||
|
"""
|
||||||
|
对应 js 里的 lazy 解析:
|
||||||
|
- 如果页面中存在 player_aaaa 且脚本里有 "url":"xxx.m3u8" 就直接取 m3u8
|
||||||
|
- 否则返回原始地址
|
||||||
|
"""
|
||||||
|
if id.startswith('http'):
|
||||||
|
play_url = id
|
||||||
|
else:
|
||||||
|
if id.startswith('/'):
|
||||||
|
play_url = self.host.rstrip('/') + id
|
||||||
|
else:
|
||||||
|
play_url = self.host.rstrip('/') + '/' + id
|
||||||
|
|
||||||
|
p = 0 # 直接返回真实地址,不再二级解析
|
||||||
|
|
||||||
|
# 如果本身就是 m3u8
|
||||||
|
if '.m3u8' in play_url:
|
||||||
|
return {'parse': p, 'url': play_url, 'header': self.headers}
|
||||||
|
|
||||||
|
rsp = self.fetch(play_url, headers=self.headers)
|
||||||
|
html = rsp.text
|
||||||
|
|
||||||
|
# 查找包含 player_aaaa 的脚本并提取 m3u8
|
||||||
|
# js 里正则: /\"url\"\\s*:\\s*\"([^\"]+\\.m3u8[^\"]*)\"/
|
||||||
|
m = re.search(r'"url"\s*:\s*"([^"]+\.m3u8[^"]*)"', html)
|
||||||
|
if m:
|
||||||
|
real = m.group(1).replace('\\/', '/')
|
||||||
|
return {'parse': p, 'url': real, 'header': self.headers}
|
||||||
|
|
||||||
|
# 兜底,返回原地址
|
||||||
|
return {'parse': p, 'url': play_url, 'header': self.headers}
|
||||||
|
|
||||||
|
def localProxy(self, param):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getlist(self, data):
|
||||||
|
"""
|
||||||
|
对应 js 一级/搜索 规则:
|
||||||
|
'.mrb&&ul li;.dytit&&Text;.lazy&&data-original;.hdinfo&&Text;a&&href'
|
||||||
|
"""
|
||||||
|
vlist = []
|
||||||
|
for j in data.items():
|
||||||
|
name = j('.dytit').text()
|
||||||
|
pic = j('.lazy').attr('data-original') or ''
|
||||||
|
remark = j('.hdinfo').text()
|
||||||
|
href = j('a').attr('href') or ''
|
||||||
|
|
||||||
|
if pic and not pic.startswith('http'):
|
||||||
|
if pic.startswith('/'):
|
||||||
|
pic = self.host.rstrip('/') + pic
|
||||||
|
else:
|
||||||
|
pic = self.host.rstrip('/') + '/' + pic
|
||||||
|
|
||||||
|
vod_id = href
|
||||||
|
vlist.append({
|
||||||
|
'vod_id': vod_id,
|
||||||
|
'vod_name': name,
|
||||||
|
'vod_pic': pic,
|
||||||
|
'vod_remarks': remark
|
||||||
|
})
|
||||||
|
return vlist
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
"""
|
||||||
|
@header({
|
||||||
|
searchable: 1,
|
||||||
|
filterable: 1,
|
||||||
|
quickSearch: 1,
|
||||||
|
title: '独播库',
|
||||||
|
lang: 'hipy'
|
||||||
|
})
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||||
|
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||||
|
|
||||||
|
from base.spider import Spider
|
||||||
|
import sys,time,json,base64,random,urllib3
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
sys.path.append('..')
|
||||||
|
|
||||||
|
class Spider(Spider):
|
||||||
|
headers,host = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
|
||||||
|
'Connection': 'Keep-Alive',
|
||||||
|
'Accept-Encoding': 'gzip',
|
||||||
|
'Referer': 'https://www.dbku.tv/'
|
||||||
|
}, 'https://api.dbokutv.com'
|
||||||
|
|
||||||
|
def init(self, extend=""):
|
||||||
|
pass
|
||||||
|
|
||||||
|
filters = {
|
||||||
|
1: [
|
||||||
|
{'key': 'area', 'name': '地区', 'value': [
|
||||||
|
{'n': '全部', 'v': ''}, {'n': '大陆', 'v': '大陆'}, {'n': '香港', 'v': '香港'},
|
||||||
|
{'n': '台湾', 'v': '台湾'}, {'n': '韩国', 'v': '韩国'}, {'n': '日本', 'v': '日本'},
|
||||||
|
{'n': '欧美', 'v': '欧美'}, {'n': '泰国', 'v': '泰国'}]},
|
||||||
|
{'key': 'order', 'name': '排序', 'value': [
|
||||||
|
{'n': '时间', 'v': ''}, {'n': '人气', 'v': '人气'}, {'n': '评分', 'v': '评分'}]},
|
||||||
|
{'key': 'class', 'name': '剧情', 'value': [
|
||||||
|
{'n': '全部', 'v': ''}, {'n': '喜剧', 'v': '喜剧'}, {'n': '爱情', 'v': '爱情'},
|
||||||
|
{'n': '动作', 'v': '动作'}, {'n': '科幻', 'v': '科幻'}, {'n': '悬疑', 'v': '悬疑'},
|
||||||
|
{'n': '惊悚', 'v': '惊悚'}, {'n': '恐怖', 'v': '恐怖'}, {'n': '战争', 'v': '战争'},
|
||||||
|
{'n': '犯罪', 'v': '犯罪'}, {'n': '纪录片', 'v': '纪录片'}, {'n': '剧情', 'v': '剧情'},
|
||||||
|
{'n': '歌舞', 'v': '歌舞'}, {'n': '家庭', 'v': '家庭'}, {'n': '历史', 'v': '历史'},
|
||||||
|
{'n': '奇幻', 'v': '奇幻'}, {'n': '冒险', 'v': '冒险'}, {'n': '传记', 'v': '传记'},
|
||||||
|
{'n': '动画', 'v': '动画'}, {'n': '短片', 'v': '短片'}]},
|
||||||
|
{'key': 'lang', 'name': '语言', 'value': [
|
||||||
|
{'n': '全部', 'v': ''}, {'n': '国语', 'v': '国语'}, {'n': '粤语', 'v': '粤语'},
|
||||||
|
{'n': '英语', 'v': '英语'}, {'n': '韩语', 'v': '韩语'}, {'n': '日语', 'v': '日语'},
|
||||||
|
{'n': '泰语', 'v': '泰语'}]},
|
||||||
|
{'key': 'year', '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': '更早', 'v': '更早'}]},
|
||||||
|
],
|
||||||
|
2: [
|
||||||
|
{'key': 'area', 'name': '地区', 'value': [
|
||||||
|
{'n': '全部', 'v': ''}, {'n': '大陆', 'v': '大陆'}, {'n': '香港', 'v': '香港'},
|
||||||
|
{'n': '台湾', 'v': '台湾'}, {'n': '韩国', 'v': '韩国'}, {'n': '日本', 'v': '日本'},
|
||||||
|
{'n': '新加坡', 'v': '新加坡'}, {'n': '泰国', 'v': '泰国'}]},
|
||||||
|
{'key': 'order', 'name': '排序', 'value': [
|
||||||
|
{'n': '时间', 'v': ''}, {'n': '人气', 'v': '人气'}, {'n': '评分', 'v': '评分'}]},
|
||||||
|
{'key': 'class', 'name': '剧情', 'value': [
|
||||||
|
{'n': '全部', 'v': ''}, {'n': '悬疑', 'v': '悬疑'}, {'n': '武侠', 'v': '武侠'},
|
||||||
|
{'n': '科幻', 'v': '科幻'}, {'n': '都市', 'v': '都市'}, {'n': '爱情', 'v': '爱情'},
|
||||||
|
{'n': '古装', 'v': '古装'}, {'n': '战争', 'v': '战争'}, {'n': '青春', 'v': '青春'},
|
||||||
|
{'n': '偶像', 'v': '偶像'}, {'n': '喜剧', 'v': '喜剧'}, {'n': '家庭', 'v': '家庭'},
|
||||||
|
{'n': '奇幻', 'v': '奇幻'}, {'n': '剧情', 'v': '剧情'}, {'n': '乡村', 'v': '乡村'},
|
||||||
|
{'n': '年代', 'v': '年代'}, {'n': '警匪', 'v': '警匪'}, {'n': '谍战', 'v': '谍战'},
|
||||||
|
{'n': '历险', 'v': '历险'}, {'n': '罪案', 'v': '罪案'}, {'n': '宫廷', 'v': '宫廷'},
|
||||||
|
{'n': '经典', 'v': '经典'}, {'n': '动作', 'v': '动作'}, {'n': '惊悚', 'v': '惊悚'},
|
||||||
|
{'n': '历史', 'v': '历史'}, {'n': '穿越', 'v': '穿越'}, {'n': '同性', 'v': '同性'}]},
|
||||||
|
{'key': 'lang', 'name': '语言', 'value': [
|
||||||
|
{'n': '全部', 'v': ''}, {'n': '国语', 'v': '国语'}, {'n': '粤语', 'v': '粤语'},
|
||||||
|
{'n': '韩语', 'v': '韩语'}, {'n': '泰语', 'v': '泰语'}, {'n': '日语', 'v': '日语'}]},
|
||||||
|
{'key': 'year', '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': '更早', 'v': '更早'}]},
|
||||||
|
],
|
||||||
|
3: [
|
||||||
|
{'key': 'area', 'name': '地区', 'value': [
|
||||||
|
{'n': '全部', 'v': ''}, {'n': '大陆', 'v': '大陆'}, {'n': '香港', 'v': '香港'},
|
||||||
|
{'n': '台湾', 'v': '台湾'}, {'n': '韩国', 'v': '韩国'}, {'n': '日本', 'v': '日本'},
|
||||||
|
{'n': '欧美', 'v': '欧美'}]},
|
||||||
|
{'key': 'order', 'name': '排序', 'value': [
|
||||||
|
{'n': '时间', 'v': ''}, {'n': '人气', 'v': '人气'}, {'n': '评分', 'v': '评分'}]},
|
||||||
|
{'key': 'year', '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': '更早', 'v': '更早'}]},
|
||||||
|
],
|
||||||
|
4: [
|
||||||
|
{'key': 'area', 'name': '地区', 'value': [
|
||||||
|
{'n': '全部', 'v': ''}, {'n': '大陆', 'v': '大陆'}, {'n': '日本', 'v': '日本'},
|
||||||
|
{'n': '欧美', 'v': '欧美'}, {'n': '韩国', 'v': '韩国'}]},
|
||||||
|
{'key': 'order', 'name': '排序', 'value': [
|
||||||
|
{'n': '时间', 'v': ''}, {'n': '人气', 'v': '人气'}, {'n': '评分', 'v': '评分'}]},
|
||||||
|
{'key': 'year', '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': '更早', 'v': '更早'}]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
def homeContent(self, filter):
|
||||||
|
if not self.host: return None
|
||||||
|
result = {'class': [
|
||||||
|
{'type_id': 1, 'type_name': '电影'},
|
||||||
|
{'type_id': 2, 'type_name': '连续剧'},
|
||||||
|
{'type_id': 3, 'type_name': '综艺'},
|
||||||
|
{'type_id': 4, 'type_name': '动漫'}]}
|
||||||
|
if filter:
|
||||||
|
result['filters'] = self.filters
|
||||||
|
return result
|
||||||
|
|
||||||
|
def homeVideoContent(self):
|
||||||
|
response = self.fetch(self.sign(f'{self.host}/home'), headers=self.headers, verify=False).json()
|
||||||
|
videos = []
|
||||||
|
for i in response:
|
||||||
|
for j in i.get('VodList',[]):
|
||||||
|
videos.append({
|
||||||
|
'vod_id': self.decode(j.get('DId', j.get('DuId'))),
|
||||||
|
'vod_name': j['Name'],
|
||||||
|
'vod_pic': self.decode(j['TnId']),
|
||||||
|
'vod_remarks': j['Tag']
|
||||||
|
})
|
||||||
|
return {'list': videos}
|
||||||
|
|
||||||
|
def categoryContent(self, tid, pg, filter, extend):
|
||||||
|
area = extend.get('area', '')
|
||||||
|
order = extend.get('order', '')
|
||||||
|
cls = extend.get('class', '')
|
||||||
|
lang = extend.get('lang', '')
|
||||||
|
year = extend.get('year', '')
|
||||||
|
pg_str = '' if str(pg) == '1' else str(pg)
|
||||||
|
# URL格式: /vodshow/{tid}-{area}-{order}-{class}-{lang}-{letter}-{?}-{?}-{pg}-{?}-{year}
|
||||||
|
path = f"{self.host}/vodshow/{tid}-{area}-{order}-{cls}-{lang}----{pg_str}---{year}"
|
||||||
|
response = self.fetch(self.sign(path), headers=self.headers, verify=False).json()
|
||||||
|
videos,pagecount = [], 0
|
||||||
|
for i in response['VodList']:
|
||||||
|
videos.append({
|
||||||
|
'vod_id': self.decode(i.get('DId',i.get('DuId'))),
|
||||||
|
'vod_name': i['Name'],
|
||||||
|
'vod_pic': self.decode(i['TnId']),
|
||||||
|
'vod_remarks': i['Tag']
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
for j in response['PaginationList']:
|
||||||
|
if j['Type'] == 'StartEnd':
|
||||||
|
pgcount = int(self.decode(j.get('PId',j.get('PuId'))).split('-')[8])
|
||||||
|
if pgcount: pagecount = pgcount
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if pagecount == 0:
|
||||||
|
try:
|
||||||
|
for j in response['PaginationList']:
|
||||||
|
if j['Type'] == 'ShortPage':
|
||||||
|
pgcount = int(j['Name'].split('/')[1])
|
||||||
|
if pgcount: pagecount = pgcount
|
||||||
|
except Exception:
|
||||||
|
pagecount = 1
|
||||||
|
return {'list': videos,'pagecount': pagecount, 'page': pg}
|
||||||
|
|
||||||
|
def searchContent(self, key, quick, pg='1'):
|
||||||
|
response = self.fetch(f"{self.sign(f'{self.host}/vodsearch')}&wd={key}", headers=self.headers, verify=False).json()
|
||||||
|
videos = []
|
||||||
|
for i in response:
|
||||||
|
videos.append({
|
||||||
|
'vod_id': self.decode(i.get('DId', i.get('DuId'))),
|
||||||
|
'vod_name': i['Name'],
|
||||||
|
'vod_pic': self.decode(i['TnId']),
|
||||||
|
'vod_remarks': i['Tag'],
|
||||||
|
'vod_actor': i['Actor'],
|
||||||
|
'vod_score': i['Rating']
|
||||||
|
})
|
||||||
|
return {'list': videos, 'page': pg}
|
||||||
|
|
||||||
|
def detailContent(self, ids):
|
||||||
|
data = self.fetch(self.sign(f"{self.host}{ids[0]}"), headers=self.headers, verify=False).json()
|
||||||
|
play_urls = [f"{i['EpisodeName']}${self.decode(i['VId'])}" for i in data['Playlist']]
|
||||||
|
video = {
|
||||||
|
'vod_id': ids[0],
|
||||||
|
'vod_name': data['Name'],
|
||||||
|
'vod_pic': self.decode(data['TnId']),
|
||||||
|
'vod_remarks': f"评分:{data['Rating']}",
|
||||||
|
'vod_year': data['ReleaseYear'],
|
||||||
|
'vod_area': data['Region'],
|
||||||
|
'vod_actor': ','.join(data['Actor']),
|
||||||
|
'vod_director': data['Director'],
|
||||||
|
'vod_content': data['Description'],
|
||||||
|
'vod_play_from': '独播库',
|
||||||
|
'vod_play_url': '#'.join(play_urls),
|
||||||
|
'type_name': f"{data['Genre']},{data['Scenario']},{data['Language']}"
|
||||||
|
}
|
||||||
|
return {'list': [video]}
|
||||||
|
|
||||||
|
def playerContent(self, flag, video_id, vip_flags):
|
||||||
|
res = self.fetch(self.sign(f"{self.host}{video_id}"), headers=self.headers, verify=False).json()
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||||
|
'Accept-Encoding': 'gzip, deflate',
|
||||||
|
'origin': 'https://w.duboku.io',
|
||||||
|
'referer': 'https://w.duboku.io/',
|
||||||
|
'priority': 'u=1, i'
|
||||||
|
}
|
||||||
|
return { 'jx': 0, 'parse': 0, 'url': self.decode(res['HId']), 'header': headers}
|
||||||
|
|
||||||
|
def decode(self, data):
|
||||||
|
if not data or not isinstance(data, str): return ''
|
||||||
|
stripped_str = data.strip('\'"')
|
||||||
|
if not stripped_str: return ''
|
||||||
|
segment_length = 10
|
||||||
|
try:
|
||||||
|
reversed_segments = (stripped_str[i:i + segment_length][::-1] for i in range(0, len(stripped_str), segment_length))
|
||||||
|
processed_base64 = ''.join(reversed_segments).replace('.', '=')
|
||||||
|
except Exception:
|
||||||
|
return ''
|
||||||
|
try:
|
||||||
|
padding_needed = 4 - len(processed_base64) % 4
|
||||||
|
if padding_needed != 4:
|
||||||
|
processed_base64 += '=' * padding_needed
|
||||||
|
decoded_bytes = base64.b64decode(processed_base64, validate=True)
|
||||||
|
return decoded_bytes.decode('utf-8')
|
||||||
|
except Exception:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def sign(self,raw):
|
||||||
|
random.seed(int(time.time()))
|
||||||
|
random_number = random.randint(0, 800000000)
|
||||||
|
value_a = random_number + 100000000
|
||||||
|
value_b = 900000000 - random_number
|
||||||
|
current_unix_timestamp = int(time.time())
|
||||||
|
unix_timestamp_str = str(current_unix_timestamp)
|
||||||
|
interleaved_str = self.interleave_strings(f'{value_a}{value_b}', unix_timestamp_str)
|
||||||
|
ssid_base64_encoded = base64.b64encode(interleaved_str.encode()).decode().replace('=', '.')
|
||||||
|
random_sign = self.random_string(60)
|
||||||
|
random_token = self.random_string(38)
|
||||||
|
return f"{raw}?sign={random_sign}&token={random_token}&ssid={ssid_base64_encoded}"
|
||||||
|
|
||||||
|
def interleave_strings(self,first_str, second_str):
|
||||||
|
interleaved_chars = []
|
||||||
|
min_length = min(len(first_str), len(second_str))
|
||||||
|
for i in range(min_length):
|
||||||
|
interleaved_chars.append(first_str[i])
|
||||||
|
interleaved_chars.append(second_str[i])
|
||||||
|
interleaved_chars.append(first_str[min_length:])
|
||||||
|
interleaved_chars.append(second_str[min_length:])
|
||||||
|
return ''.join(interleaved_chars)
|
||||||
|
|
||||||
|
|
||||||
|
def random_string(self, length):
|
||||||
|
character = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
|
random.seed(int(time.time()) + len(character))
|
||||||
|
return ''.join(random.choice(character) for _ in range(length))
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def isVideoFormat(self, url):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def manualVideoCheck(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def localProxy(self, param):
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user