潇洒更新 202507040345

pull/57/head
github-actions[bot] 8 months ago
parent 88cdae3d2b
commit 4df1e8da89

@ -314,20 +314,6 @@
"version": "200" "version": "200"
} }
}, },
{
"key": "剧咖",
"name": "剧咖APP",
"type": 3,
"quickSearch": 1,
"api": "csp_AppGet",
"ext": {
"url": "https://ys.xiaoqi0.cn",
"dataKey": "jukayingshi88888",
"dataIv": "jukayingshi88888",
"deviceId": "20d9b2c01d130342a99fdc8d693bfc0cd",
"version": "112"
}
},
{ {
"key": "瓜萌", "key": "瓜萌",
"name": "瓜萌APP", "name": "瓜萌APP",
@ -510,20 +496,6 @@
"version": "" "version": ""
} }
}, },
{
"key": "晚枫",
"name": "晚枫APP",
"type": 3,
"quickSearch": 1,
"api": "csp_AppGet",
"ext": {
"url": "http://111.180.202.216:999",
"dataKey": "fjhndgbjidfhgjsd",
"dataIv": "fjhndgbjidfhgjsd",
"deviceId": "2a7baa6ca6f46308cbf1d939bab0586f3",
"version": "120"
}
},
{ {
"key": "诺映", "key": "诺映",
"name": "诺映APP", "name": "诺映APP",
@ -957,6 +929,17 @@
"quickSearch": 0, "quickSearch": 0,
"filterable": 0 "filterable": 0
}, },
{
"key": "河马短剧",
"name": "河马|短剧",
"type": 3,
"api": "./py/河马短剧.py",
"searchable": 1,
"changeable": 1,
"quickSearch": 1,
"filterable": 1,
"playerType": 2
},
{ {
"key": "偷乐短剧", "key": "偷乐短剧",
"name": "偷乐|短剧", "name": "偷乐|短剧",
@ -1565,16 +1548,6 @@
"filterable": 1, "filterable": 1,
"ext": "./XBPQ/狐狸君.json" "ext": "./XBPQ/狐狸君.json"
}, },
{
"key": "布谷TV",
"name": "布谷TV磁力",
"type": 3,
"api": "csp_HBPQ",
"searchable": 1,
"quickSearch": 1,
"filterable": 1,
"ext": "./XBPQ/布谷TV.json"
},
{ {
"key": "BT天堂", "key": "BT天堂",
"name": "BT天堂磁力", "name": "BT天堂磁力",
@ -1594,15 +1567,6 @@
"quickSearch": 1, "quickSearch": 1,
"filterable": 1 "filterable": 1
}, },
{
"key": "酷吧电影",
"name": "酷吧电影|磁力",
"type": 3,
"api": "csp_KubaCL",
"searchable": 1,
"quickSearch": 1,
"filterable": 1
},
{ {
"key": "美剧天堂", "key": "美剧天堂",
"name": "美剧天堂|磁力", "name": "美剧天堂|磁力",
@ -1828,6 +1792,74 @@
"ratio": 1.597 "ratio": 1.597
} }
}, },
{
"key": "少儿教育",
"name": "少儿|教育",
"type": 3,
"api": "csp_Bili",
"searchable": 0,
"quickSearch": 0,
"filterable": 1,
"ext": {
"json": "./json/少儿教育.json",
"cookie": "http://127.0.0.1:9978/file/TVBox/bili_cookie.txt"
},
"style": {
"type": "rect",
"ratio": 1.597
}
},
{
"key": "小学课堂",
"name": "小学|课堂",
"type": 3,
"api": "csp_Bili",
"searchable": 0,
"quickSearch": 0,
"filterable": 1,
"ext": {
"json": "./json/小学课堂.json",
"cookie": "http://127.0.0.1:9978/file/TVBox/bili_cookie.txt"
},
"style": {
"type": "rect",
"ratio": 1.597
}
},
{
"key": "初中课堂",
"name": "初中|课堂",
"type": 3,
"api": "csp_Bili",
"searchable": 0,
"quickSearch": 0,
"filterable": 1,
"ext": {
"json": "./json/初中课堂.json",
"cookie": "http://127.0.0.1:9978/file/TVBox/bili_cookie.txt"
},
"style": {
"type": "rect",
"ratio": 1.597
}
},
{
"key": "高中教育",
"name": "高中|课堂",
"type": 3,
"api": "csp_Bili",
"searchable": 0,
"quickSearch": 0,
"filterable": 1,
"ext": {
"json": "./json/高中课堂.json",
"cookie": "http://127.0.0.1:9978/file/TVBox/bili_cookie.txt"
},
"style": {
"type": "rect",
"ratio": 1.597
}
},
{ {
"key": "急救教学", "key": "急救教学",
"name": "急救|教学", "name": "急救|教学",

@ -1,6 +1,7 @@
{ {
"SiteUrl": "https://wogg.banye.tech:7086", "SiteUrl": "https://wogg.banye.tech:7086",
"Domains": [ "Domains": [
"http://woggpan.wogg.lol",
"https://wogg.xxooo.cf", "https://wogg.xxooo.cf",
"https://wogg.333232.xyz", "https://wogg.333232.xyz",
"https://woggpan.333232.xyz", "https://woggpan.333232.xyz",
@ -570,8 +571,8 @@
"name": "时间", "name": "时间",
"value": [ "value": [
{ {
"v": "2025", "v": "2025",
"n": "2025" "n": "2025"
}, },
{ {
"v": "2024", "v": "2024",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,454 @@
{
"class": [
{
"type_name": "儿童早教",
"type_id": "儿童早教"
},
{
"type_name": "儿童启蒙故事",
"type_id": "儿童启蒙故事"
},
{
"type_name": "儿童英语启蒙",
"type_id": "儿童英语启蒙"
},
{
"type_name": "儿童歌曲",
"type_id": "儿童歌曲"
},
{
"type_name": "儿童绘画",
"type_id": "儿童绘画"
},
{
"type_name": "睡前故事",
"type_id": "睡前故事"
},
{
"type_name": "儿童动画",
"type_id": "儿童动画"
},
{
"type_name": "儿童音乐",
"type_id": "儿童音乐"
},
{
"type_name": "儿童安全教育",
"type_id": "儿童安全教育"
},
{
"type_name": "贝瓦儿歌",
"type_id": "贝瓦儿歌"
},
{
"type_name": "悟空识字",
"type_id": "悟空识字"
},
{
"type_name": "宝宝巴士",
"type_id": "宝宝巴士"
},
{
"type_name": "儿歌多多",
"type_id": "儿歌多多"
},
{
"type_name": "学而思",
"type_id": "学而思"
}
],
"filters": {
"儿童早教": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"儿童启蒙故事": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"儿童英语启蒙": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"儿童歌曲": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"儿童绘画": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"睡前故事": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"儿童动画": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"儿童音乐": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"儿童安全教育": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"贝瓦儿歌": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"悟空识字": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"宝宝巴士": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"儿歌多多": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
],
"学而思": [
{
"key": "duration",
"name": "时长",
"value": [
{
"n": "全部",
"v": "0"
},
{
"n": "60分钟以上",
"v": "4"
},
{
"n": "30~60分钟",
"v": "3"
},
{
"n": "10~30分钟",
"v": "2"
},
{
"n": "10分钟以下",
"v": "1"
}
]
}
]
}
}

File diff suppressed because it is too large Load Diff

@ -4,6 +4,7 @@ import re
import json import json
import traceback import traceback
import sys import sys
from urllib.parse import quote
sys.path.append('../../') sys.path.append('../../')
try: try:
@ -17,7 +18,6 @@ except ImportError:
class Spider(Spider): class Spider(Spider):
def __init__(self): def __init__(self):
self.siteUrl = "https://www.kuaikaw.cn" self.siteUrl = "https://www.kuaikaw.cn"
self.nextData = None # 缓存NEXT_DATA数据
self.cateManual = { self.cateManual = {
"甜宠": "462", "甜宠": "462",
"古装仙侠": "1102", "古装仙侠": "1102",
@ -30,552 +30,351 @@ class Spider(Spider):
"总裁": "1147", "总裁": "1147",
"职场商战": "943" "职场商战": "943"
} }
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
"Referer": self.siteUrl,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
}
def getName(self): def getName(self):
# 返回爬虫名称
return "河马短剧" return "河马短剧"
def init(self, extend=""): def init(self, extend=""):
return return
def fetch(self, url, headers=None): def fetch(self, url, headers=None, retry=2):
"""统一的网络请求接口""" """统一的网络请求接口"""
if headers is None: if headers is None:
headers = { headers = self.headers
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
"Referer": self.siteUrl,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
}
try: for i in range(retry + 1):
response = requests.get(url, headers=headers, timeout=10, allow_redirects=True) try:
response.raise_for_status() response = requests.get(url, headers=headers, timeout=10, allow_redirects=True)
return response response.raise_for_status()
except Exception as e: return response
print(f"请求异常: {url}, 错误: {str(e)}") except Exception as e:
return None if i == retry:
print(f"请求异常: {url}, 错误: {str(e)}")
return None
continue
def isVideoFormat(self, url): def isVideoFormat(self, url):
# 检查是否为视频格式
video_formats = ['.mp4', '.mkv', '.avi', '.wmv', '.m3u8', '.flv', '.rmvb'] video_formats = ['.mp4', '.mkv', '.avi', '.wmv', '.m3u8', '.flv', '.rmvb']
for format in video_formats: return any(format in url.lower() for format in video_formats)
if format in url.lower():
return True
return False
def manualVideoCheck(self): def manualVideoCheck(self):
# 不需要手动检查
return False return False
def homeContent(self, filter): def homeContent(self, filter):
"""获取首页分类及筛选"""
result = {} result = {}
# 分类列表使用已初始化的cateManual classes = [{'type_name': k, 'type_id': v} for k, v in self.cateManual.items()]
classes = []
for k in self.cateManual:
classes.append({
'type_name': k,
'type_id': self.cateManual[k]
})
result['class'] = classes result['class'] = classes
# 获取首页推荐视频
try: try:
result['list'] = self.homeVideoContent()['list'] result['list'] = self.homeVideoContent()['list']
except: except:
result['list'] = [] result['list'] = []
return result return result
def homeVideoContent(self): def homeVideoContent(self):
"""获取首页推荐视频内容"""
videos = [] videos = []
try: try:
response = self.fetch(self.siteUrl) response = self.fetch(self.siteUrl)
if not response:
return {'list': []}
html_content = response.text html_content = response.text
# 提取NEXT_DATA JSON数据
next_data_pattern = r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>' next_data_pattern = r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>'
next_data_match = re.search(next_data_pattern, html_content, re.DOTALL) next_data_match = re.search(next_data_pattern, html_content, re.DOTALL)
if next_data_match: if not next_data_match:
next_data_json = json.loads(next_data_match.group(1)) return {'list': []}
page_props = next_data_json.get("props", {}).get("pageProps", {})
# 获取轮播图数据 - 这些通常是推荐内容 next_data_json = json.loads(next_data_match.group(1))
if "bannerList" in page_props and isinstance(page_props["bannerList"], list): page_props = next_data_json.get("props", {}).get("pageProps", {})
banner_list = page_props["bannerList"]
for banner in banner_list: # 处理轮播图数据
book_id = banner.get("bookId", "") if "bannerList" in page_props:
book_name = banner.get("bookName", "") for banner in page_props["bannerList"]:
cover_url = banner.get("coverWap", banner.get("wapUrl", "")) if banner.get("bookId"):
# 获取状态和章节数 videos.append({
status = banner.get("statusDesc", "") "vod_id": f"/drama/{banner['bookId']}",
total_chapters = banner.get("totalChapterNum", "") "vod_name": banner.get("bookName", ""),
if book_id and book_name: "vod_pic": banner.get("coverWap", ""),
"vod_remarks": f"{banner.get('statusDesc', '')} {banner.get('totalChapterNum', '')}".strip()
})
# 处理SEO分类推荐
if "seoColumnVos" in page_props:
for column in page_props["seoColumnVos"]:
for book in column.get("bookInfos", []):
if book.get("bookId"):
videos.append({ videos.append({
"vod_id": f"/drama/{book_id}", "vod_id": f"/drama/{book['bookId']}",
"vod_name": book_name, "vod_name": book.get("bookName", ""),
"vod_pic": cover_url, "vod_pic": book.get("coverWap", ""),
"vod_remarks": f"{status} {total_chapters}" if total_chapters else status "vod_remarks": f"{book.get('statusDesc', '')} {book.get('totalChapterNum', '')}".strip()
}) })
# SEO分类下的推荐 # 去重处理
if "seoColumnVos" in page_props and isinstance(page_props["seoColumnVos"], list): seen = set()
for column in page_props["seoColumnVos"]: unique_videos = []
book_infos = column.get("bookInfos", []) for video in videos:
for book in book_infos: key = (video["vod_id"], video["vod_name"])
book_id = book.get("bookId", "") if key not in seen:
book_name = book.get("bookName", "") seen.add(key)
cover_url = book.get("coverWap", "") unique_videos.append(video)
status = book.get("statusDesc", "")
total_chapters = book.get("totalChapterNum", "")
if book_id and book_name:
videos.append({
"vod_id": f"/drama/{book_id}",
"vod_name": book_name,
"vod_pic": cover_url,
"vod_remarks": f"{status} {total_chapters}" if total_chapters else status
})
# # 去重
# seen = set()
# unique_videos = []
# for video in videos:
# if video["vod_id"] not in seen:
# seen.add(video["vod_id"])
# unique_videos.append(video)
# videos = unique_videos
except Exception as e: except Exception as e:
print(f"获取首页推荐内容出错: {e}") print(f"获取首页推荐内容出错: {e}")
unique_videos = []
result = { return {'list': unique_videos}
"list": videos
}
return result
def categoryContent(self, tid, pg, filter, extend): def categoryContent(self, tid, pg, filter, extend):
"""获取分类内容""" result = {'list': [], 'page': pg, 'pagecount': 1, 'limit': 20, 'total': 0}
result = {}
videos = []
url = f"{self.siteUrl}/browse/{tid}/{pg}" url = f"{self.siteUrl}/browse/{tid}/{pg}"
response = self.fetch(url) response = self.fetch(url)
if not response:
return result
html_content = response.text html_content = response.text
# 提取NEXT_DATA JSON数据 next_data_match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html_content, re.DOTALL)
next_data_pattern = r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>' if not next_data_match:
next_data_match = re.search(next_data_pattern, html_content, re.DOTALL) return result
if next_data_match:
try:
next_data_json = json.loads(next_data_match.group(1)) next_data_json = json.loads(next_data_match.group(1))
page_props = next_data_json.get("props", {}).get("pageProps", {}) page_props = next_data_json.get("props", {}).get("pageProps", {})
# 获取总页数和当前页
current_page = page_props.get("page", 1) current_page = page_props.get("page", 1)
total_pages = page_props.get("pages", 1) total_pages = page_props.get("pages", 1)
# 获取书籍列表
book_list = page_props.get("bookList", []) book_list = page_props.get("bookList", [])
# 转换为通用格式
videos = []
for book in book_list: for book in book_list:
book_id = book.get("bookId", "") if book.get("bookId"):
book_name = book.get("bookName", "")
cover_url = book.get("coverWap", "")
status_desc = book.get("statusDesc", "")
total_chapters = book.get("totalChapterNum", "")
if book_id and book_name:
videos.append({ videos.append({
"vod_id": f"/drama/{book_id}", "vod_id": f"/drama/{book['bookId']}",
"vod_name": book_name, "vod_name": book.get("bookName", ""),
"vod_pic": cover_url, "vod_pic": book.get("coverWap", ""),
"vod_remarks": f"{status_desc} {total_chapters}" if total_chapters else status_desc "vod_remarks": f"{book.get('statusDesc', '')} {book.get('totalChapterNum', '')}".strip()
}) })
# 构建返回结果
result = { result.update({
"list": videos, 'list': videos,
"page": int(current_page), 'page': int(current_page),
"pagecount": total_pages, 'pagecount': total_pages,
"limit": len(videos), 'limit': len(videos),
"total": total_pages * len(videos) if videos else 0 'total': len(videos) * total_pages if videos else 0
} })
except Exception as e:
print(f"分类内容获取出错: {e}")
return result return result
def switch(self, key, pg): def searchContent(self, key, quick, pg=1):
# 搜索功能 return self.searchContentPage(key, quick, pg)
search_results = []
# 获取第一页结果,并检查总页数 def searchContentPage(self, key, quick, pg=1):
url = f"{self.siteUrl}/search?searchValue={key}&page={pg}" result = {'list': [], 'page': pg, 'pagecount': 1, 'limit': 20, 'total': 0}
response = self.fetch(url) search_url = f"{self.siteUrl}/search?searchValue={quote(key)}&page={pg}"
response = self.fetch(search_url)
if not response:
return result
html_content = response.text html_content = response.text
# 提取NEXT_DATA JSON数据 next_data_match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html_content, re.DOTALL)
next_data_pattern = r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>' if not next_data_match:
next_data_match = re.search(next_data_pattern, html_content, re.DOTALL) return result
if next_data_match:
try:
next_data_json = json.loads(next_data_match.group(1)) next_data_json = json.loads(next_data_match.group(1))
page_props = next_data_json.get("props", {}).get("pageProps", {}) page_props = next_data_json.get("props", {}).get("pageProps", {})
# 获取总页数
total_pages = page_props.get("pages", 1) total_pages = page_props.get("pages", 1)
# 处理所有页的数据
all_book_list = []
# 添加第一页的书籍列表
book_list = page_props.get("bookList", []) book_list = page_props.get("bookList", [])
all_book_list.extend(book_list)
# 如果有多页,获取其他页的数据 videos = []
if total_pages > 1 : # quick模式只获取第一页 for book in book_list:
for page in range(2, total_pages + 1): if book.get("bookId"):
next_page_url = f"{self.siteUrl}/search?searchValue={key}&page={page}" videos.append({
next_page_response = self.fetch(next_page_url) "vod_id": f"/drama/{book['bookId']}",
next_page_html = next_page_response.text "vod_name": book.get("bookName", ""),
next_page_match = re.search(next_data_pattern, next_page_html, re.DOTALL) "vod_pic": book.get("coverWap", ""),
if next_page_match: "vod_remarks": f"{book.get('statusDesc', '')} {book.get('totalChapterNum', '')}".strip()
next_page_json = json.loads(next_page_match.group(1)) })
next_page_props = next_page_json.get("props", {}).get("pageProps", {})
next_page_books = next_page_props.get("bookList", []) result.update({
all_book_list.extend(next_page_books) 'list': videos,
# 转换为统一的搜索结果格式 'pagecount': total_pages,
for book in all_book_list: 'total': len(videos) * total_pages if videos else 0
book_id = book.get("bookId", "") })
book_name = book.get("bookName", "")
cover_url = book.get("coverWap", "") except Exception as e:
total_chapters = book.get("totalChapterNum", "0") print(f"搜索内容出错: {e}")
status_desc = book.get("statusDesc", "")
# 构建视频项
vod = {
"vod_id": f"/drama/{book_id}",
"vod_name": book_name,
"vod_pic": cover_url,
"vod_remarks": f"{status_desc} {total_chapters}"
}
search_results.append(vod)
result = {
"list": search_results,
"page": pg
}
return result
def searchContent(self, key, quick, pg=1):
result = self.switch(key, pg=pg)
result['page'] = pg
return result return result
def searchContentPage(self, key, quick, pg=1):
return self.searchContent(key, quick, pg)
def detailContent(self, ids): def detailContent(self, ids):
# 获取剧集信息 result = {'list': []}
if not ids:
return result
vod_id = ids[0] vod_id = ids[0]
episode_id = None
chapter_id = None
if not vod_id.startswith('/drama/'): if not vod_id.startswith('/drama/'):
if vod_id.startswith('/episode/'): vod_id = f'/drama/{vod_id}'
episode_info = vod_id.replace('/episode/', '').split('/')
if len(episode_info) >= 2:
episode_id = episode_info[0]
chapter_id = episode_info[1]
vod_id = f'/drama/{episode_id}'
else:
vod_id = '/drama/' + vod_id
drama_url = self.siteUrl + vod_id
print(f"请求URL: {drama_url}")
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
"Referer": self.siteUrl,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
}
rsp = self.fetch(drama_url, headers=headers)
if not rsp or rsp.status_code != 200:
print(f"请求失败,状态码: {getattr(rsp, 'status_code', 'N/A')}")
return {}
html = rsp.text drama_url = f"{self.siteUrl}{vod_id}"
response = self.fetch(drama_url)
if not response:
return result
html = response.text
next_data_match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html, re.DOTALL) next_data_match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html, re.DOTALL)
if not next_data_match: if not next_data_match:
print("未找到NEXT_DATA内容") return result
return {}
try: try:
next_data = json.loads(next_data_match.group(1)) next_data = json.loads(next_data_match.group(1))
page_props = next_data.get("props", {}).get("pageProps", {}) page_props = next_data.get("props", {}).get("pageProps", {})
print(f"找到页面属性,包含 {len(page_props.keys())} 个键")
book_info = page_props.get("bookInfoVo", {}) book_info = page_props.get("bookInfoVo", {})
chapter_list = page_props.get("chapterList", []) chapter_list = page_props.get("chapterList", [])
title = book_info.get("title", "") if not book_info.get("bookId"):
sub_title = f"{book_info.get('totalChapterNum', '')}" return result
categories = []
for category in book_info.get("categoryList", []):
categories.append(category.get("name", ""))
vod_content = book_info.get("introduction", "") # 基本信息
categories = [c.get("name", "") for c in book_info.get("categoryList", [])]
performers = [p.get("name", "") for p in book_info.get("performerList", [])]
vod = { vod = {
"vod_id": vod_id, "vod_id": vod_id,
"vod_name": title, "vod_name": book_info.get("title", ""),
"vod_pic": book_info.get("coverWap", ""), "vod_pic": book_info.get("coverWap", ""),
"type_name": ",".join(categories), "type_name": ",".join(categories),
"vod_year": "", "vod_year": "",
"vod_area": book_info.get("countryName", ""), "vod_area": book_info.get("countryName", ""),
"vod_remarks": sub_title, "vod_remarks": f"{book_info.get('statusDesc', '')} {book_info.get('totalChapterNum', '')}".strip(),
"vod_actor": ", ".join([p.get("name", "") for p in book_info.get("performerList", [])]), "vod_actor": ", ".join(performers),
"vod_director": "", "vod_director": "",
"vod_content": vod_content "vod_content": book_info.get("introduction", "")
} }
# 处理播放列表 # 处理剧集
play_url_list = [] play_urls = self.processEpisodes(vod_id, chapter_list)
episodes = [] if play_urls:
vod['vod_play_from'] = '河马剧场'
vod['vod_play_url'] = '$$$'.join(play_urls)
if chapter_list: result['list'] = [vod]
print(f"找到 {len(chapter_list)} 个章节")
except Exception as e:
# 先检查是否有可以直接使用的MP4链接作为模板 print(f"详情页解析出错: {e}")
mp4_template = None traceback.print_exc()
first_mp4_chapter_id = None
return result
# 先搜索第一个章节的MP4链接
# 为提高成功率,尝试直接请求第一个章节的播放页 def processEpisodes(self, vod_id, chapter_list):
if chapter_list and len(chapter_list) > 0: play_urls = []
first_chapter = chapter_list[0] episodes = []
first_chapter_id = first_chapter.get("chapterId", "")
drama_id_clean = vod_id.replace('/drama/', '') for chapter in chapter_list:
chapter_id = chapter.get("chapterId", "")
if first_chapter_id and drama_id_clean: chapter_name = chapter.get("chapterName", "")
first_episode_url = f"{self.siteUrl}/episode/{drama_id_clean}/{first_chapter_id}"
print(f"请求第一集播放页: {first_episode_url}") if not chapter_id or not chapter_name:
continue
first_rsp = self.fetch(first_episode_url, headers=headers)
if first_rsp and first_rsp.status_code == 200:
first_html = first_rsp.text
# 直接从HTML提取MP4链接
mp4_pattern = r'(https?://[^"\']+\.mp4)'
mp4_matches = re.findall(mp4_pattern, first_html)
if mp4_matches:
mp4_template = mp4_matches[0]
first_mp4_chapter_id = first_chapter_id
print(f"找到MP4链接模板: {mp4_template}")
print(f"模板对应的章节ID: {first_mp4_chapter_id}")
# 如果未找到模板再检查章节对象中是否有MP4链接 # 尝试获取直接视频链接
if not mp4_template: video_url = self.getDirectVideoUrl(chapter)
for chapter in chapter_list[:5]: # 只检查前5个章节以提高效率 if video_url:
if "chapterVideoVo" in chapter and chapter["chapterVideoVo"]: episodes.append(f"{chapter_name}${video_url}")
chapter_video = chapter["chapterVideoVo"] continue
mp4_url = chapter_video.get("mp4", "") or chapter_video.get("mp4720p", "") or chapter_video.get("vodMp4Url", "")
if mp4_url and ".mp4" in mp4_url:
mp4_template = mp4_url
first_mp4_chapter_id = chapter.get("chapterId", "")
print(f"从chapterVideoVo找到MP4链接模板: {mp4_template}")
print(f"模板对应的章节ID: {first_mp4_chapter_id}")
break
# 遍历所有章节处理播放信息 # 回退方案
for chapter in chapter_list: episodes.append(f"{chapter_name}${vod_id}${chapter_id}${chapter_name}")
chapter_id = chapter.get("chapterId", "")
chapter_name = chapter.get("chapterName", "") if episodes:
play_urls.append("#".join(episodes))
# 1. 如果章节自身有MP4链接直接使用
if "chapterVideoVo" in chapter and chapter["chapterVideoVo"]: return play_urls
chapter_video = chapter["chapterVideoVo"]
mp4_url = chapter_video.get("mp4", "") or chapter_video.get("mp4720p", "") or chapter_video.get("vodMp4Url", "") def getDirectVideoUrl(self, chapter):
if mp4_url and ".mp4" in mp4_url: if "chapterVideoVo" not in chapter or not chapter["chapterVideoVo"]:
episodes.append(f"{chapter_name}${mp4_url}") return None
continue
# 2. 如果有MP4模板尝试替换章节ID构建MP4链接
if mp4_template and first_mp4_chapter_id and chapter_id:
# 替换模板中的章节ID部分
if first_mp4_chapter_id in mp4_template:
new_mp4_url = mp4_template.replace(first_mp4_chapter_id, chapter_id)
episodes.append(f"{chapter_name}${new_mp4_url}")
continue
# 3. 如果上述方法都不可行回退到使用chapter_id构建中间URL
if chapter_id and chapter_name:
url = f"{vod_id}${chapter_id}${chapter_name}"
episodes.append(f"{chapter_name}${url}")
if not episodes and vod_id:
# 尝试构造默认的集数
total_chapters = int(book_info.get("totalChapterNum", "0"))
if total_chapters > 0:
print(f"尝试构造 {total_chapters} 个默认集数")
# 如果知道章节ID的模式可以构造
if chapter_id and episode_id:
for i in range(1, total_chapters + 1):
chapter_name = f"{i}"
url = f"{vod_id}${chapter_id}${chapter_name}"
episodes.append(f"{chapter_name}${url}")
else:
# 使用普通的构造方式
for i in range(1, total_chapters + 1):
chapter_name = f"{i}"
url = f"{vod_id}${chapter_name}"
episodes.append(f"{chapter_name}${url}")
if episodes:
play_url_list.append("#".join(episodes))
vod['vod_play_from'] = '河马剧场'
vod['vod_play_url'] = '$$$'.join(play_url_list)
result = { video_info = chapter["chapterVideoVo"]
'list': [vod] for key in ["mp4", "mp4720p", "vodMp4Url"]:
} if key in video_info and video_info[key] and ".mp4" in video_info[key].lower():
return result return video_info[key]
except Exception as e: return None
print(f"解析详情页失败: {str(e)}")
print(traceback.format_exc())
return {}
def playerContent(self, flag, id, vipFlags): def playerContent(self, flag, id, vipFlags):
result = {} result = {
print(f"调用playerContent: flag={flag}, id={id}") "parse": 0,
"url": id,
headers = { "header": json.dumps(self.headers)
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
"Referer": self.siteUrl,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
} }
# 解析id参数 # 如果已经是视频链接直接返回
parts = id.split('$') if 'http' in id and ('.mp4' in id or '.m3u8' in id):
drama_id = None
chapter_id = None
if len(parts) >= 2:
drama_id = parts[0]
chapter_id = parts[1]
chapter_name = parts[2] if len(parts) > 2 else "第一集"
print(f"解析参数: drama_id={drama_id}, chapter_id={chapter_id}")
else:
# 处理旧数据格式
print(f"使用原始URL格式: {id}")
result["parse"] = 0
result["url"] = id
result["header"] = json.dumps(headers)
return result return result
# 直接检查chapter_id是否包含http可能已经是视频链接 # 解析参数
if 'http' in chapter_id and '.mp4' in chapter_id: parts = id.split('$')
print(f"已经是MP4链接: {chapter_id}") if len(parts) < 2:
result["parse"] = 0
result["url"] = chapter_id
result["header"] = json.dumps(headers)
return result return result
drama_id = parts[0].replace('/drama/', '')
chapter_id = parts[1]
# 构建episode页面URL # 尝试获取视频链接
drama_id_clean = drama_id.replace('/drama/', '') video_url = self.getEpisodeVideoUrl(drama_id, chapter_id)
episode_url = f"{self.siteUrl}/episode/{drama_id_clean}/{chapter_id}" if video_url:
print(f"请求episode页面: {episode_url}") result["url"] = video_url
try:
rsp = self.fetch(episode_url, headers=headers)
if not rsp or rsp.status_code != 200:
print(f"请求失败,状态码: {getattr(rsp, 'status_code', 'N/A')}")
result["parse"] = 0
result["url"] = id
result["header"] = json.dumps(headers)
return result
html = rsp.text return result
print(f"获取页面大小: {len(html)} 字节")
# 尝试从NEXT_DATA提取视频链接
mp4_url = None
# 方法1: 从NEXT_DATA提取
next_data_match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html, re.DOTALL)
if next_data_match:
try:
print("找到NEXT_DATA")
next_data = json.loads(next_data_match.group(1))
page_props = next_data.get("props", {}).get("pageProps", {})
# 从chapterList中查找当前章节
chapter_list = page_props.get("chapterList", [])
print(f"找到章节列表,长度: {len(chapter_list)}")
for chapter in chapter_list:
if chapter.get("chapterId") == chapter_id:
print(f"找到匹配的章节: {chapter.get('chapterName')}")
chapter_video = chapter.get("chapterVideoVo", {})
mp4_url = chapter_video.get("mp4", "") or chapter_video.get("mp4720p", "") or chapter_video.get("vodMp4Url", "")
if mp4_url:
print(f"从chapterList找到MP4链接: {mp4_url}")
break
# 如果未找到,尝试从当前章节获取
if not mp4_url:
current_chapter = page_props.get("chapterInfo", {})
if current_chapter:
print("找到当前章节信息")
chapter_video = current_chapter.get("chapterVideoVo", {})
mp4_url = chapter_video.get("mp4", "") or chapter_video.get("mp4720p", "") or chapter_video.get("vodMp4Url", "")
if mp4_url:
print(f"从chapterInfo找到MP4链接: {mp4_url}")
except Exception as e:
print(f"解析NEXT_DATA失败: {str(e)}")
print(traceback.format_exc())
# 方法2: 直接从HTML中提取MP4链接
if not mp4_url:
mp4_pattern = r'(https?://[^"\']+\.mp4)'
mp4_matches = re.findall(mp4_pattern, html)
if mp4_matches:
# 查找含有chapter_id的链接
matched_mp4 = False
for url in mp4_matches:
if chapter_id in url:
mp4_url = url
matched_mp4 = True
print(f"从HTML直接提取章节MP4链接: {mp4_url}")
break
# 如果没找到包含chapter_id的链接使用第一个
if not matched_mp4 and mp4_matches:
mp4_url = mp4_matches[0]
print(f"从HTML直接提取MP4链接: {mp4_url}")
if mp4_url and ".mp4" in mp4_url:
print(f"最终找到的MP4链接: {mp4_url}")
result["parse"] = 0
result["url"] = mp4_url
result["header"] = json.dumps(headers)
return result
else:
print(f"未找到有效的MP4链接尝试再次解析页面内容")
# 再尝试一次从HTML中广泛搜索所有可能的MP4链接
all_mp4_pattern = r'(https?://[^"\']+\.mp4)'
all_mp4_matches = re.findall(all_mp4_pattern, html)
if all_mp4_matches:
mp4_url = all_mp4_matches[0]
print(f"从HTML广泛搜索找到MP4链接: {mp4_url}")
result["parse"] = 0
result["url"] = mp4_url
result["header"] = json.dumps(headers)
return result
print(f"未找到视频链接返回原episode URL: {episode_url}")
result["parse"] = 0
result["url"] = episode_url
result["header"] = json.dumps(headers)
return result
except Exception as e:
print(f"请求或解析失败: {str(e)}")
print(traceback.format_exc())
result["parse"] = 0
result["url"] = id
result["header"] = json.dumps(headers)
return result
def getEpisodeVideoUrl(self, drama_id, chapter_id):
episode_url = f"{self.siteUrl}/episode/{drama_id}/{chapter_id}"
response = self.fetch(episode_url)
if not response:
return None
html = response.text
# 方法1: 从NEXT_DATA提取
next_data_match = re.search(r'<script id="__NEXT_DATA__".*?>(.*?)</script>', html, re.DOTALL)
if next_data_match:
try:
next_data = json.loads(next_data_match.group(1))
page_props = next_data.get("props", {}).get("pageProps", {})
chapter_info = page_props.get("chapterInfo", {})
if chapter_info and "chapterVideoVo" in chapter_info:
video_info = chapter_info["chapterVideoVo"]
for key in ["mp4", "mp4720p", "vodMp4Url"]:
if key in video_info and video_info[key] and ".mp4" in video_info[key].lower():
return video_info[key]
except:
pass
# 方法2: 直接从HTML提取
mp4_matches = re.findall(r'(https?://[^"\']+\.mp4)', html)
if mp4_matches:
for url in mp4_matches:
if chapter_id in url or drama_id in url:
return url
return mp4_matches[0]
return None
def localProxy(self, param): def localProxy(self, param):
# 本地代理处理,此处简单返回传入的参数
return [200, "video/MP2T", {}, param] return [200, "video/MP2T", {}, param]
def destroy(self): def destroy(self):
# 资源回收 pass
pass

Binary file not shown.
Loading…
Cancel
Save