Sync all projects
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,451 @@
|
||||
# coding=utf-8
|
||||
import re, json, requests
|
||||
from urllib.parse import quote
|
||||
from lxml import etree
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def __init__(self):
|
||||
self.name = "webstar"
|
||||
self.host = "https://www.webstar.cn"
|
||||
self.header = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Referer': self.host
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def _get(self, url, params=None):
|
||||
r = requests.get(url, headers=self.header, params=params, timeout=15)
|
||||
r.encoding = 'utf-8'
|
||||
return r.text
|
||||
|
||||
def _post(self, url, data=None):
|
||||
r = requests.post(url, headers=self.header, data=data, timeout=15)
|
||||
r.encoding = 'utf-8'
|
||||
return r.text
|
||||
|
||||
def _fix_url(self, url):
|
||||
if not url:
|
||||
return ''
|
||||
if url.startswith('//'):
|
||||
return 'https:' + url
|
||||
if url.startswith('/'):
|
||||
return self.host + url
|
||||
return url
|
||||
|
||||
def _parse_text(self, elem):
|
||||
if elem is None:
|
||||
return ''
|
||||
return ''.join(elem.itertext()).strip()
|
||||
|
||||
def _parse_list_item(self, item):
|
||||
a = item.xpath('.//a[contains(@class, "myui-vodlist__thumb")]')
|
||||
if not a:
|
||||
return None
|
||||
a = a[0]
|
||||
href = a.get('href', '')
|
||||
m = re.search(r'/voddetail/(\d+)\.html', href)
|
||||
if not m:
|
||||
return None
|
||||
vod_id = m.group(1)
|
||||
vod_name = a.get('title', '').strip()
|
||||
if not vod_name:
|
||||
t = item.xpath('.//h4[contains(@class, "title")]//a/text()')
|
||||
vod_name = t[0].strip() if t else ''
|
||||
vod_pic = a.get('data-original', '')
|
||||
if not vod_pic:
|
||||
vod_pic = a.get('data-src', '')
|
||||
if not vod_pic:
|
||||
img = a.xpath('.//img')
|
||||
if img:
|
||||
vod_pic = img[0].get('data-original', '') or img[0].get('data-src', '') or img[0].get('src', '')
|
||||
vod_pic = self._fix_url(vod_pic)
|
||||
if vod_pic and vod_pic.startswith('data:image'):
|
||||
vod_pic = ''
|
||||
remark = item.xpath('.//span[contains(@class, "pic-text")]/text()')
|
||||
vod_remarks = remark[0].strip() if remark else ''
|
||||
if not vod_remarks:
|
||||
score = item.xpath('.//span[contains(@class, "pic-tag-top")]/text()')
|
||||
vod_remarks = score[0].strip() if score else ''
|
||||
return {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_name,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_remarks": vod_remarks
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {"class": []}
|
||||
classes = [
|
||||
{"type_name": "电影", "type_id": "1"},
|
||||
{"type_name": "电视剧", "type_id": "2"},
|
||||
{"type_name": "综艺", "type_id": "3"},
|
||||
{"type_name": "动漫", "type_id": "4"},
|
||||
{"type_name": "短剧", "type_id": "20"},
|
||||
{"type_name": "动画", "type_id": "35"}
|
||||
]
|
||||
result["class"] = classes
|
||||
filters = {}
|
||||
area_vals = [
|
||||
{"n": "全部", "v": ""}, {"n": "大陆", "v": "大陆"},
|
||||
{"n": "香港", "v": "香港"}, {"n": "台湾", "v": "台湾"},
|
||||
{"n": "美国", "v": "美国"}, {"n": "法国", "v": "法国"},
|
||||
{"n": "英国", "v": "英国"}, {"n": "日本", "v": "日本"},
|
||||
{"n": "韩国", "v": "韩国"}, {"n": "德国", "v": "德国"},
|
||||
{"n": "泰国", "v": "泰国"}, {"n": "印度", "v": "印度"},
|
||||
{"n": "意大利", "v": "意大利"}, {"n": "西班牙", "v": "西班牙"},
|
||||
{"n": "加拿大", "v": "加拿大"}, {"n": "其他", "v": "其他"}
|
||||
]
|
||||
class_vals = [
|
||||
{"n": "全部", "v": ""}, {"n": "喜剧", "v": "喜剧"},
|
||||
{"n": "爱情", "v": "爱情"}, {"n": "恐怖", "v": "恐怖"},
|
||||
{"n": "动作", "v": "动作"}, {"n": "科幻", "v": "科幻"},
|
||||
{"n": "剧情", "v": "剧情"}, {"n": "战争", "v": "战争"},
|
||||
{"n": "警匪", "v": "警匪"}, {"n": "犯罪", "v": "犯罪"},
|
||||
{"n": "动画", "v": "动画"}, {"n": "奇幻", "v": "奇幻"},
|
||||
{"n": "武侠", "v": "武侠"}, {"n": "冒险", "v": "冒险"},
|
||||
{"n": "枪战", "v": "枪战"}, {"n": "悬疑", "v": "悬疑"},
|
||||
{"n": "惊悚", "v": "惊悚"}, {"n": "经典", "v": "经典"},
|
||||
{"n": "青春", "v": "青春"}, {"n": "文艺", "v": "文艺"},
|
||||
{"n": "微电影", "v": "微电影"}, {"n": "古装", "v": "古装"},
|
||||
{"n": "历史", "v": "历史"}, {"n": "运动", "v": "运动"},
|
||||
{"n": "农村", "v": "农村"}, {"n": "儿童", "v": "儿童"},
|
||||
{"n": "网络电影", "v": "网络电影"}
|
||||
]
|
||||
year_vals = [
|
||||
{"n": "全部", "v": ""}, {"n": "2026", "v": "2026"},
|
||||
{"n": "2025", "v": "2025"}, {"n": "2024", "v": "2024"},
|
||||
{"n": "2023", "v": "2023"}, {"n": "2022", "v": "2022"},
|
||||
{"n": "2021", "v": "2021"}, {"n": "2020", "v": "2020"},
|
||||
{"n": "2019", "v": "2019"}, {"n": "2018", "v": "2018"},
|
||||
{"n": "2017", "v": "2017"}, {"n": "2016", "v": "2016"},
|
||||
{"n": "2015", "v": "2015"}, {"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"},
|
||||
{"n": "2003", "v": "2003"}, {"n": "2002", "v": "2002"},
|
||||
{"n": "2001", "v": "2001"}, {"n": "2000", "v": "2000"}
|
||||
]
|
||||
lang_vals = [
|
||||
{"n": "全部", "v": ""}, {"n": "国语", "v": "国语"},
|
||||
{"n": "英语", "v": "英语"}, {"n": "粤语", "v": "粤语"},
|
||||
{"n": "闽南语", "v": "闽南语"}, {"n": "韩语", "v": "韩语"},
|
||||
{"n": "日语", "v": "日语"}, {"n": "法语", "v": "法语"},
|
||||
{"n": "德语", "v": "德语"}, {"n": "其它", "v": "其它"}
|
||||
]
|
||||
order_vals = [
|
||||
{"n": "时间", "v": "time"}, {"n": "人气", "v": "hits"},
|
||||
{"n": "评分", "v": "score"}
|
||||
]
|
||||
for c in classes:
|
||||
filters[c['type_id']] = [
|
||||
{"key": "area", "name": "地区", "value": area_vals},
|
||||
{"key": "class", "name": "类型", "value": class_vals},
|
||||
{"key": "year", "name": "年份", "value": year_vals},
|
||||
{"key": "lang", "name": "语言", "value": lang_vals},
|
||||
{"key": "order", "name": "排序", "value": order_vals}
|
||||
]
|
||||
result["filters"] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
try:
|
||||
html = self._get(self.host)
|
||||
root = etree.HTML(html)
|
||||
items = root.xpath('//div[contains(@class, "myui-vodlist__box")]')
|
||||
for item in items:
|
||||
try:
|
||||
video = self._parse_list_item(item)
|
||||
if video:
|
||||
videos.append(video)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
return {"list": videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
videos = []
|
||||
try:
|
||||
if isinstance(extend, str) and extend:
|
||||
try:
|
||||
extend = json.loads(extend)
|
||||
except Exception:
|
||||
extend = {}
|
||||
elif not extend:
|
||||
extend = {}
|
||||
area = extend.get('area', '')
|
||||
cls = extend.get('class', '')
|
||||
year = extend.get('year', '')
|
||||
lang = extend.get('lang', '')
|
||||
order = extend.get('order', 'time')
|
||||
area = quote(area) if area else ''
|
||||
cls = quote(cls) if cls else ''
|
||||
year = quote(year) if year else ''
|
||||
lang = quote(lang) if lang else ''
|
||||
order = quote(order) if order else 'time'
|
||||
# 456电影网 vodshow URL 12 段:0类型 1地区 2排序 3类型 4语言 5字母 6-7空 8页码 9-10空 11年份
|
||||
segments = [str(tid), area, order, cls, lang, '', '', '', str(pg), '', '', year]
|
||||
url = f"{self.host}/vodshow/{'-'.join(segments)}.html"
|
||||
html = self._get(url)
|
||||
root = etree.HTML(html)
|
||||
items = root.xpath('//div[contains(@class, "myui-vodlist__box")]')
|
||||
for item in items:
|
||||
try:
|
||||
video = self._parse_list_item(item)
|
||||
if video:
|
||||
videos.append(video)
|
||||
except Exception:
|
||||
pass
|
||||
total_pages = 1
|
||||
page_links = root.xpath('//ul[contains(@class,"myui-page")]//a/@href')
|
||||
for href in page_links:
|
||||
m = re.search(r'/(?:vodshow|vodsearch)/[^-]*(?:-[^-]*)*-(\d+)---\.html', href)
|
||||
if m:
|
||||
total_pages = max(total_pages, int(m.group(1)))
|
||||
if not page_links:
|
||||
# 无分页时默认只一页
|
||||
total_pages = 1
|
||||
return {
|
||||
'list': videos,
|
||||
'page': int(pg),
|
||||
'pagecount': total_pages,
|
||||
'limit': len(videos),
|
||||
'total': total_pages * len(videos) if videos else total_pages * 36
|
||||
}
|
||||
except Exception:
|
||||
return {'list': [], 'page': 1, 'pagecount': 0, 'limit': 0, 'total': 0}
|
||||
|
||||
def detailContent(self, ids):
|
||||
try:
|
||||
vod_id = ids[0]
|
||||
detail_url = f"{self.host}/voddetail/{vod_id}.html"
|
||||
html = self._get(detail_url)
|
||||
root = etree.HTML(html)
|
||||
|
||||
vod_name = ''
|
||||
title_elem = root.xpath('//div[contains(@class,"myui-content__detail")]//h1[@class="title"]')
|
||||
if title_elem:
|
||||
vod_name = self._parse_text(title_elem[0])
|
||||
if not vod_name:
|
||||
title = root.xpath('//title/text()')
|
||||
if title:
|
||||
vod_name = title[0].split('_')[0].strip()
|
||||
|
||||
vod_pic = ''
|
||||
thumb = root.xpath('//div[contains(@class,"myui-content__thumb")]//img')
|
||||
if thumb:
|
||||
vod_pic = thumb[0].get('data-original', '') or thumb[0].get('data-src', '') or thumb[0].get('src', '')
|
||||
vod_pic = self._fix_url(vod_pic)
|
||||
|
||||
vod_year = ''
|
||||
vod_area = ''
|
||||
data_ps = root.xpath('//div[contains(@class,"myui-content__detail")]//p[contains(@class,"data")]')
|
||||
for p in data_ps:
|
||||
txt = self._parse_text(p)
|
||||
if '年份:' in txt and not vod_year:
|
||||
year_links = p.xpath('.//a/text()')
|
||||
for y in year_links:
|
||||
y = y.strip()
|
||||
if re.match(r'^\d{4}$', y):
|
||||
vod_year = y
|
||||
break
|
||||
if '地区:' in txt and not vod_area:
|
||||
area_links = p.xpath('.//a/text()')
|
||||
for a in area_links:
|
||||
a = a.strip()
|
||||
if a:
|
||||
vod_area = a
|
||||
break
|
||||
|
||||
vod_actor = ''
|
||||
actor_elem = root.xpath('//div[contains(@class,"myui-content__detail")]//p[contains(@class,"data") and contains(.,"主演:")]')
|
||||
if actor_elem:
|
||||
actors = actor_elem[0].xpath('.//a/text()')
|
||||
vod_actor = ' '.join([a.strip() for a in actors if a.strip()])
|
||||
|
||||
vod_director = ''
|
||||
director_elem = root.xpath('//div[contains(@class,"myui-content__detail")]//p[contains(@class,"data") and contains(.,"导演:")]')
|
||||
if director_elem:
|
||||
directors = director_elem[0].xpath('.//a/text()')
|
||||
vod_director = ' '.join([d.strip() for d in directors if d.strip()])
|
||||
|
||||
vod_content = ''
|
||||
desc_elem = root.xpath('//div[contains(@class,"text-collapse")]//span[contains(@class,"data")]')
|
||||
if desc_elem:
|
||||
vod_content = self._parse_text(desc_elem[0])
|
||||
if not vod_content:
|
||||
desc_elem = root.xpath('//div[contains(@class,"text-collapse")]')
|
||||
if desc_elem:
|
||||
vod_content = self._parse_text(desc_elem[0])
|
||||
vod_content = re.sub(r'想要看更多的.*$', '', vod_content).strip()
|
||||
|
||||
vod_play_from = []
|
||||
vod_play_url = []
|
||||
# 先尝试带 tab 的播放列表结构
|
||||
source_tabs = root.xpath('//div[contains(@class,"myui-panel__head")]//ul[contains(@class,"nav-tabs")]//a')
|
||||
tab_panes = root.xpath('//div[contains(@class,"tab-content")]//div[contains(@class,"tab-pane")]')
|
||||
if source_tabs and tab_panes:
|
||||
for idx, tab in enumerate(source_tabs):
|
||||
source_name = self._parse_text(tab)
|
||||
source_name = re.sub(r'\s+', ' ', source_name).strip()
|
||||
if not source_name:
|
||||
source_name = f"线路{idx + 1}"
|
||||
if idx < len(tab_panes):
|
||||
pane = tab_panes[idx]
|
||||
else:
|
||||
continue
|
||||
links = pane.xpath('.//ul[contains(@class,"myui-content__list")]//a')
|
||||
play_list = []
|
||||
for a in links:
|
||||
ep_name = a.text or a.get('title', '')
|
||||
ep_name = ep_name.strip()
|
||||
href = a.get('href', '')
|
||||
if not ep_name or not href:
|
||||
continue
|
||||
play_url = self._fix_url(href)
|
||||
play_list.append(f"{ep_name}${play_url}")
|
||||
if play_list:
|
||||
vod_play_from.append(source_name)
|
||||
vod_play_url.append("#".join(play_list))
|
||||
else:
|
||||
# 兜底:直接取播放地址列表
|
||||
links = root.xpath('//ul[contains(@class,"myui-content__list")]//a')
|
||||
play_list = []
|
||||
for a in links:
|
||||
ep_name = a.text or a.get('title', '')
|
||||
ep_name = ep_name.strip()
|
||||
href = a.get('href', '')
|
||||
if not ep_name or not href:
|
||||
continue
|
||||
play_url = self._fix_url(href)
|
||||
play_list.append(f"{ep_name}${play_url}")
|
||||
if play_list:
|
||||
vod_play_from.append("默认")
|
||||
vod_play_url.append("#".join(play_list))
|
||||
|
||||
if vod_play_from:
|
||||
vod_play_from_str = "$$$".join(vod_play_from)
|
||||
vod_play_url_str = "$$$".join(vod_play_url)
|
||||
else:
|
||||
vod_play_from_str = "默认"
|
||||
vod_play_url_str = ""
|
||||
|
||||
detail = {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_name,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_year": vod_year,
|
||||
"vod_area": vod_area,
|
||||
"vod_actor": vod_actor,
|
||||
"vod_director": vod_director,
|
||||
"vod_content": vod_content,
|
||||
"vod_play_from": vod_play_from_str,
|
||||
"vod_play_url": vod_play_url_str
|
||||
}
|
||||
return {'list': [detail]}
|
||||
except Exception:
|
||||
return {'list': []}
|
||||
|
||||
def _extract_player_url(self, html):
|
||||
try:
|
||||
m = re.search(r'var\s+player_aaaa\s*=\s*\{', html)
|
||||
if not m:
|
||||
return None
|
||||
start = m.end() - 1
|
||||
depth = 1
|
||||
i = start + 1
|
||||
while i < len(html) and depth > 0:
|
||||
if html[i] == '{':
|
||||
depth += 1
|
||||
elif html[i] == '}':
|
||||
depth -= 1
|
||||
i += 1
|
||||
player_data = json.loads(html[start:i])
|
||||
return player_data.get('url', '')
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
try:
|
||||
html = self._get(id)
|
||||
real_url = self._extract_player_url(html)
|
||||
if real_url:
|
||||
real_url = real_url.replace('\\/', '/')
|
||||
if real_url.startswith('//'):
|
||||
real_url = 'https:' + real_url
|
||||
parse_flag = 0 if self.isVideoFormat(real_url) else 1
|
||||
return {"parse": parse_flag, "playUrl": "", "url": real_url, "header": json.dumps(self.header)}
|
||||
iframe_match = re.search(r'<iframe[^>]+src\s*=\s*"([^"]+)"', html)
|
||||
if iframe_match:
|
||||
real_url = iframe_match.group(1)
|
||||
real_url = self._fix_url(real_url)
|
||||
parse_flag = 0 if self.isVideoFormat(real_url) else 1
|
||||
return {"parse": parse_flag, "playUrl": "", "url": real_url, "header": json.dumps(self.header)}
|
||||
m3u8_match = re.search(r'["\'](https?://[^"\']+\.m3u8[^"\']*)["\']', html)
|
||||
if m3u8_match:
|
||||
real_url = m3u8_match.group(1)
|
||||
return {"parse": 0, "playUrl": "", "url": real_url, "header": json.dumps(self.header)}
|
||||
mp4_match = re.search(r'["\'](https?://[^"\']+\.(?:mp4|flv|ts))["\']', html)
|
||||
if mp4_match:
|
||||
real_url = mp4_match.group(1)
|
||||
return {"parse": 0, "playUrl": "", "url": real_url, "header": json.dumps(self.header)}
|
||||
return {"parse": 1, "playUrl": "", "url": id, "header": json.dumps(self.header)}
|
||||
except Exception:
|
||||
return {"parse": 0, "playUrl": "", "url": ""}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
videos = []
|
||||
try:
|
||||
qkey = quote(key)
|
||||
if str(pg) == '1':
|
||||
url = f"{self.host}/vodsearch/{qkey}-------------.html"
|
||||
else:
|
||||
url = f"{self.host}/vodsearch/{qkey}--------{pg}---.html"
|
||||
html = self._get(url)
|
||||
root = etree.HTML(html)
|
||||
items = root.xpath('//div[contains(@class, "myui-vodlist__box")]')
|
||||
for item in items:
|
||||
try:
|
||||
video = self._parse_list_item(item)
|
||||
if video:
|
||||
videos.append(video)
|
||||
except Exception:
|
||||
pass
|
||||
total_pages = 1
|
||||
page_links = root.xpath('//ul[contains(@class,"myui-page")]//a/@href')
|
||||
for href in page_links:
|
||||
m = re.search(r'/vodsearch/[^/]+-(\d+)---\.html', href)
|
||||
if m:
|
||||
total_pages = max(total_pages, int(m.group(1)))
|
||||
return {
|
||||
'list': videos,
|
||||
'page': int(pg),
|
||||
'pagecount': total_pages,
|
||||
'limit': len(videos),
|
||||
'total': total_pages * len(videos) if videos else total_pages * 36
|
||||
}
|
||||
except Exception:
|
||||
return {'list': [], 'page': 1, 'pagecount': 0, 'limit': 0, 'total': 0}
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return any(url.lower().endswith(fmt) for fmt in ['.m3u8', '.mp4', '.flv', '.ts'])
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, params):
|
||||
return None
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,207 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import re
|
||||
import requests
|
||||
from urllib.parse import quote
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def init(self, extend=""):
|
||||
self.host = "https://www.ddys24.com"
|
||||
self.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36",
|
||||
"Referer": self.host + "/",
|
||||
"Origin": self.host
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return "低端影视"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return bool(re.search(r'\.(m3u8|mp4|flv|avi|mkv|mov|ts)(\?|$)', url or "", re.I))
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def homeContent(self, filter):
|
||||
return {
|
||||
"class": [
|
||||
{"type_id": "1", "type_name": "电影"},
|
||||
{"type_id": "2", "type_name": "电视剧"},
|
||||
{"type_id": "3", "type_name": "综艺"},
|
||||
{"type_id": "4", "type_name": "动漫"},
|
||||
{"type_id": "42", "type_name": "豆瓣Top250"}
|
||||
]
|
||||
}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {"list": self.parseList(self.get(self.host + "/"))}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
url = self.host + "/ddvodtype/" + str(tid) + ".html" if str(pg) == "1" else self.host + "/ddvodtype/" + str(tid) + "-" + str(pg) + ".html"
|
||||
html = self.get(url)
|
||||
if not html and str(pg) != "1":
|
||||
html = self.get(self.host + "/ddvodshow/" + str(tid) + "--------" + str(pg) + "---.html")
|
||||
return {
|
||||
"page": int(pg),
|
||||
"pagecount": 999,
|
||||
"limit": 24,
|
||||
"total": 999999,
|
||||
"list": self.parseList(html)
|
||||
}
|
||||
|
||||
def detailContent(self, ids):
|
||||
vid = ids[0]
|
||||
url = vid if str(vid).startswith("http") else self.host + "/ddvoddetail/" + str(vid) + ".html"
|
||||
html = self.get(url)
|
||||
name = self.clean(self.match(html, r'<h1[^>]*class=["\']title["\'][^>]*>(.*?)</h1>') or self.match(html, r'<title>《?([^《》_]+?)》?'))
|
||||
pic = self.fix(self.match(html, r'<img[^>]+data-original=["\']([^"\']+)') or self.match(html, r'<a[^>]+class=["\'][^"\']*v-thumb[^"\']*["\'][^>]+data-original=["\']([^"\']+)'))
|
||||
remarks = self.clean(self.match(html, r'<span class=["\']pic-text[^"\']*["\'][^>]*>(.*?)</span>'))
|
||||
desc = self.clean(self.match(html, r'<span class=["\']detail-content["\'][^>]*>(.*?)</span>') or self.match(html, r'<span class=["\']detail-sketch["\'][^>]*>(.*?)</span>') or self.match(html, r'<meta name=["\']description["\'] content=["\']([^"\']+)'))
|
||||
actor = self.clean(self.match(html, r'主演:</span>([\s\S]*?)</p>'))
|
||||
director = self.clean(self.match(html, r'导演:</span>([\s\S]*?)</p>'))
|
||||
area = self.clean(self.match(html, r'地区:</span>([\s\S]*?)<span'))
|
||||
year = self.clean(self.match(html, r'年份:</span>([\s\S]*?)</p>'))
|
||||
play_from = []
|
||||
play_url = []
|
||||
blocks = re.findall(r'<div class="stui-pannel-ddy1102-cbox b playlist mb">([\s\S]*?)</div>\s*</div>\s*</div>', html)
|
||||
if not blocks:
|
||||
blocks = re.findall(r'<ul class="stui-content_ddy1102-cplaylist clearfix">([\s\S]*?)</ul>', html)
|
||||
for i, block in enumerate(blocks):
|
||||
eps = []
|
||||
for m in re.finditer(r'<a[^>]+href=["\']([^"\']*?/ddvodplay/[^"\']+)["\'][^>]*>(.*?)</a>', block):
|
||||
u = self.fix(m.group(1))
|
||||
t = self.clean(m.group(2)) or "播放"
|
||||
if u:
|
||||
eps.append(t + "$" + u)
|
||||
if eps:
|
||||
line = self.clean(self.match(block, r'<h3 class=["\']title["\'][^>]*>(.*?)</h3>')) or "线路" + str(i + 1)
|
||||
play_from.append(line)
|
||||
play_url.append("#".join(eps))
|
||||
if not play_url:
|
||||
eps = []
|
||||
for m in re.finditer(r'<a[^>]+href=["\']([^"\']*?/ddvodplay/' + str(vid) + r'-[^"\']+)["\'][^>]*>(.*?)</a>', html):
|
||||
u = self.fix(m.group(1))
|
||||
t = self.clean(m.group(2)) or "播放"
|
||||
if u:
|
||||
eps.append(t + "$" + u)
|
||||
if eps:
|
||||
play_from.append("线路W")
|
||||
play_url.append("#".join(eps))
|
||||
if not play_url:
|
||||
u = self.fix(self.match(html, r'href=["\']([^"\']*?/ddvodplay/' + str(vid) + r'-1-1\.html)["\']'))
|
||||
if u:
|
||||
play_from.append("线路W")
|
||||
play_url.append("播放$" + u)
|
||||
return {
|
||||
"list": [{
|
||||
"vod_id": vid,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks,
|
||||
"vod_year": year,
|
||||
"vod_area": area,
|
||||
"vod_actor": actor,
|
||||
"vod_director": director,
|
||||
"vod_content": desc,
|
||||
"vod_play_from": "$$$".join(play_from),
|
||||
"vod_play_url": "$$$".join(play_url)
|
||||
}]
|
||||
}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
k = quote(key)
|
||||
urls = [
|
||||
self.host + "/ddvodsearch/" + k + "-------------.html",
|
||||
self.host + "/ddvodsearch/-------------.html?wd=" + k,
|
||||
self.host + "/index.php/vod/search.html?wd=" + k,
|
||||
self.host + "/vodsearch/" + k + "-------------.html"
|
||||
]
|
||||
html = ""
|
||||
for u in urls:
|
||||
html = self.get(u)
|
||||
if "ddvoddetail" in html:
|
||||
break
|
||||
return {"list": self.parseList(html), "page": int(pg)}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
return {"parse": 1, "url": id, "header": self.headers}
|
||||
|
||||
def localProxy(self, param):
|
||||
return [404, "text/plain", "", ""]
|
||||
|
||||
def destroy(self):
|
||||
return "正在Destroy"
|
||||
|
||||
def get(self, url):
|
||||
try:
|
||||
r = requests.get(url, headers=self.headers, timeout=15)
|
||||
r.encoding = r.apparent_encoding or "utf-8"
|
||||
return r.text
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def match(self, text, rule):
|
||||
m = re.search(rule, text or "", re.S)
|
||||
return m.group(1) if m else ""
|
||||
|
||||
def clean(self, text):
|
||||
return re.sub(r"\s+", " ", re.sub(r"<.*?>", "", text or "")).replace(" ", " ").replace("&", "&").strip()
|
||||
|
||||
def fix(self, url):
|
||||
if not url:
|
||||
return ""
|
||||
if url.startswith("//"):
|
||||
return "https:" + url
|
||||
if url.startswith("/"):
|
||||
return self.host + url
|
||||
return url
|
||||
|
||||
def parseList(self, html):
|
||||
res = []
|
||||
seen = set()
|
||||
for m in re.finditer(r'<a[^>]+class=["\'][^"\']*stui-vodlist_ddy1102-cthumb[^"\']*["\'][^>]+href=["\']([^"\']*?/ddvoddetail/(\d+)\.html)["\'][^>]*title=["\']([^"\']+)["\'][^>]*(?:data-original=["\']([^"\']+)["\'])?[\s\S]*?</a>', html or "", re.S):
|
||||
vid = m.group(2)
|
||||
if vid in seen:
|
||||
continue
|
||||
seen.add(vid)
|
||||
block = m.group(0)
|
||||
pic = m.group(4) or self.match(block, r'data-original=["\']([^"\']+)') or self.match(block, r'src=["\']([^"\']+\.(?:jpg|jpeg|png|webp|gif)[^"\']*)')
|
||||
remarks = self.clean(self.match(block, r'<span class=["\']pic-text[^"\']*["\'][^>]*>(.*?)</span>'))
|
||||
name = self.clean(m.group(3))
|
||||
if name:
|
||||
res.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": name,
|
||||
"vod_pic": self.fix(pic),
|
||||
"vod_remarks": remarks
|
||||
})
|
||||
if not res:
|
||||
for m in re.finditer(r'href=["\']([^"\']*?/ddvoddetail/(\d+)\.html)["\'][^>]*title=["\']([^"\']+)["\'][\s\S]{0,500}?(?:data-original|src)=["\']([^"\']+)["\']', html or "", re.S):
|
||||
vid = m.group(2)
|
||||
if vid in seen:
|
||||
continue
|
||||
seen.add(vid)
|
||||
res.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": self.clean(m.group(3)),
|
||||
"vod_pic": self.fix(m.group(4)),
|
||||
"vod_remarks": ""
|
||||
})
|
||||
if not res:
|
||||
for m in re.finditer(r'href=["\']([^"\']*?/ddvoddetail/(\d+)\.html)["\'][^>]*>([\s\S]{0,120}?)</a>', html or "", re.S):
|
||||
vid = m.group(2)
|
||||
if vid in seen:
|
||||
continue
|
||||
name = self.clean(m.group(3))
|
||||
if not name:
|
||||
continue
|
||||
seen.add(vid)
|
||||
res.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": name,
|
||||
"vod_pic": "",
|
||||
"vod_remarks": ""
|
||||
})
|
||||
return res
|
||||
@@ -0,0 +1,474 @@
|
||||
# coding=utf-8
|
||||
import re
|
||||
import json
|
||||
import requests
|
||||
from urllib.parse import quote
|
||||
from lxml import etree
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
def __init__(self):
|
||||
self.name = "btdy"
|
||||
self.host = "https://dy.8ttv.cn"
|
||||
self.header = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Referer': self.host
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def init(self, extend=''):
|
||||
pass
|
||||
|
||||
def _get(self, url, params=None):
|
||||
r = requests.get(url, headers=self.header, params=params, timeout=15)
|
||||
r.encoding = 'utf-8'
|
||||
return r.text
|
||||
|
||||
def _post(self, url, data=None):
|
||||
r = requests.post(url, headers=self.header, data=data, timeout=15)
|
||||
r.encoding = 'utf-8'
|
||||
return r.text
|
||||
|
||||
def _fix_url(self, url):
|
||||
if not url:
|
||||
return ''
|
||||
if url.startswith('//'):
|
||||
return 'https:' + url
|
||||
if url.startswith('/'):
|
||||
return self.host + url
|
||||
return url
|
||||
|
||||
@staticmethod
|
||||
def _parse_text(elem):
|
||||
if elem is None:
|
||||
return ''
|
||||
return ''.join(elem.itertext()).strip()
|
||||
|
||||
def _parse_pic(self, elem):
|
||||
pic = ''
|
||||
if elem is None:
|
||||
return pic
|
||||
if elem.tag == 'img':
|
||||
pic = elem.get('data-original') or elem.get('data-src') or elem.get('src', '')
|
||||
else:
|
||||
imgs = elem.xpath('.//img')
|
||||
if imgs:
|
||||
pic = imgs[0].get('data-original') or imgs[0].get('data-src') or imgs[0].get('src', '')
|
||||
if not pic:
|
||||
return ''
|
||||
if pic.startswith('data:image') or 'alicdn.com' in pic:
|
||||
return ''
|
||||
return self._fix_url(pic)
|
||||
|
||||
def _parse_poster_item(self, a):
|
||||
href = a.get('href', '')
|
||||
m = re.search(r'/detail/id/(\d+)\.html', href)
|
||||
if not m:
|
||||
return None
|
||||
vod_id = m.group(1)
|
||||
vod_name = a.get('title', '').strip()
|
||||
if not vod_name:
|
||||
t = a.xpath('.//div[contains(@class,"module-poster-item-title")]/text()')
|
||||
vod_name = t[0].strip() if t else ''
|
||||
vod_pic = self._parse_pic(a)
|
||||
remark = a.xpath('.//div[contains(@class,"module-item-note")]/text()')
|
||||
vod_remarks = remark[0].strip() if remark else ''
|
||||
return {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_name,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_remarks": vod_remarks
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {"class": []}
|
||||
classes = [
|
||||
{"type_name": "电影", "type_id": "1"},
|
||||
{"type_name": "电视剧", "type_id": "2"},
|
||||
{"type_name": "综艺", "type_id": "3"},
|
||||
{"type_name": "动漫", "type_id": "4"},
|
||||
{"type_name": "短剧", "type_id": "5"},
|
||||
{"type_name": "纪录片", "type_id": "20"}
|
||||
]
|
||||
result["class"] = classes
|
||||
|
||||
area_vals = [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "中国大陆", "v": "中国大陆"},
|
||||
{"n": "中国香港", "v": "中国香港"},
|
||||
{"n": "中国台湾", "v": "中国台湾"},
|
||||
{"n": "美国", "v": "美国"},
|
||||
{"n": "日本", "v": "日本"},
|
||||
{"n": "韩国", "v": "韩国"},
|
||||
{"n": "泰国", "v": "泰国"},
|
||||
{"n": "英国", "v": "英国"},
|
||||
{"n": "法国", "v": "法国"},
|
||||
{"n": "德国", "v": "德国"},
|
||||
{"n": "意大利", "v": "意大利"},
|
||||
{"n": "西班牙", "v": "西班牙"},
|
||||
{"n": "加拿大", "v": "加拿大"},
|
||||
{"n": "印度", "v": "印度"},
|
||||
{"n": "其他", "v": "其他"}
|
||||
]
|
||||
class_vals = [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "喜剧", "v": "喜剧"},
|
||||
{"n": "爱情", "v": "爱情"},
|
||||
{"n": "恐怖", "v": "恐怖"},
|
||||
{"n": "动作", "v": "动作"},
|
||||
{"n": "科幻", "v": "科幻"},
|
||||
{"n": "剧情", "v": "剧情"},
|
||||
{"n": "战争", "v": "战争"},
|
||||
{"n": "警匪", "v": "警匪"},
|
||||
{"n": "犯罪", "v": "犯罪"},
|
||||
{"n": "动画", "v": "动画"},
|
||||
{"n": "奇幻", "v": "奇幻"},
|
||||
{"n": "武侠", "v": "武侠"},
|
||||
{"n": "冒险", "v": "冒险"},
|
||||
{"n": "枪战", "v": "枪战"},
|
||||
{"n": "悬疑", "v": "悬疑"},
|
||||
{"n": "惊悚", "v": "惊悚"},
|
||||
{"n": "经典", "v": "经典"},
|
||||
{"n": "青春", "v": "青春"},
|
||||
{"n": "文艺", "v": "文艺"},
|
||||
{"n": "微电影", "v": "微电影"},
|
||||
{"n": "古装", "v": "古装"},
|
||||
{"n": "历史", "v": "历史"},
|
||||
{"n": "运动", "v": "运动"},
|
||||
{"n": "农村", "v": "农村"},
|
||||
{"n": "儿童", "v": "儿童"},
|
||||
{"n": "网络电影", "v": "网络电影"},
|
||||
{"n": "短剧", "v": "短剧"},
|
||||
{"n": "纪录片", "v": "纪录片"},
|
||||
{"n": "综艺", "v": "综艺"},
|
||||
{"n": "动漫", "v": "动漫"}
|
||||
]
|
||||
year_vals = [{"n": "全部", "v": ""}]
|
||||
for y in range(2026, 1989, -1):
|
||||
year_vals.append({"n": str(y), "v": str(y)})
|
||||
year_vals.append({"n": "其他", "v": "其他"})
|
||||
by_vals = [
|
||||
{"n": "最新", "v": "time"},
|
||||
{"n": "最热", "v": "hits"},
|
||||
{"n": "高分", "v": "score"}
|
||||
]
|
||||
|
||||
filters = {}
|
||||
for c in classes:
|
||||
filters[c['type_id']] = [
|
||||
{"key": "class", "name": "类型", "value": class_vals},
|
||||
{"key": "area", "name": "地区", "value": area_vals},
|
||||
{"key": "year", "name": "年份", "value": year_vals},
|
||||
{"key": "by", "name": "排序", "value": by_vals}
|
||||
]
|
||||
result["filters"] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
try:
|
||||
html = self._get(self.host)
|
||||
root = etree.HTML(html)
|
||||
items = root.xpath('//a[contains(@class,"module-poster-item") and contains(@class,"module-item")]')
|
||||
seen = set()
|
||||
for a in items:
|
||||
try:
|
||||
video = self._parse_poster_item(a)
|
||||
if video and video['vod_id'] not in seen:
|
||||
videos.append(video)
|
||||
seen.add(video['vod_id'])
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
return {"list": videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
videos = []
|
||||
try:
|
||||
if isinstance(extend, str) and extend:
|
||||
try:
|
||||
extend = json.loads(extend)
|
||||
except Exception:
|
||||
extend = {}
|
||||
elif not extend:
|
||||
extend = {}
|
||||
|
||||
cls = extend.get('class', '')
|
||||
area = extend.get('area', '')
|
||||
year = extend.get('year', '')
|
||||
by = extend.get('by', 'time')
|
||||
|
||||
segments = []
|
||||
if cls:
|
||||
segments.append(f"class/{quote(cls)}")
|
||||
if area:
|
||||
segments.append(f"area/{quote(area)}")
|
||||
if year:
|
||||
segments.append(f"year/{year}")
|
||||
if by:
|
||||
segments.append(f"by/{quote(by)}")
|
||||
|
||||
url = f"{self.host}/index.php/vod/show/"
|
||||
if segments:
|
||||
url += '/'.join(segments) + '/'
|
||||
url += f"id/{tid}"
|
||||
if int(pg) > 1:
|
||||
url += f"/page/{pg}"
|
||||
url += ".html"
|
||||
|
||||
html = self._get(url)
|
||||
root = etree.HTML(html)
|
||||
items = root.xpath('//a[contains(@class,"module-poster-item") and contains(@class,"module-item")]')
|
||||
for a in items:
|
||||
try:
|
||||
video = self._parse_poster_item(a)
|
||||
if video:
|
||||
videos.append(video)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
total_pages = 1
|
||||
page_hrefs = root.xpath('//a[contains(@class,"page-link")]/@href')
|
||||
for href in page_hrefs:
|
||||
m = re.search(r'/page/(\d+)\.html', href)
|
||||
if m:
|
||||
total_pages = max(total_pages, int(m.group(1)))
|
||||
|
||||
return {
|
||||
'list': videos,
|
||||
'page': int(pg),
|
||||
'pagecount': total_pages,
|
||||
'limit': len(videos),
|
||||
'total': total_pages * len(videos) if videos else total_pages * 36
|
||||
}
|
||||
except Exception:
|
||||
return {'list': [], 'page': 1, 'pagecount': 0, 'limit': 0, 'total': 0}
|
||||
|
||||
def detailContent(self, ids):
|
||||
try:
|
||||
vod_id = ids[0]
|
||||
detail_url = f"{self.host}/index.php/vod/detail/id/{vod_id}.html"
|
||||
html = self._get(detail_url)
|
||||
root = etree.HTML(html)
|
||||
|
||||
vod_name = ''
|
||||
h1 = root.xpath('//div[contains(@class,"module-info-heading")]//h1/text()')
|
||||
if h1:
|
||||
vod_name = h1[0].strip()
|
||||
if not vod_name:
|
||||
title = root.xpath('//title/text()')
|
||||
if title:
|
||||
vod_name = title[0].split('-')[0].strip()
|
||||
|
||||
vod_pic = ''
|
||||
poster = root.xpath('//div[contains(@class,"module-info-poster")]')
|
||||
if poster:
|
||||
vod_pic = self._parse_pic(poster[0])
|
||||
|
||||
vod_year = ''
|
||||
vod_area = ''
|
||||
area_set = {'中国大陆', '中国香港', '中国台湾', '美国', '日本', '韩国', '泰国',
|
||||
'英国', '法国', '德国', '意大利', '西班牙', '加拿大', '印度', '其他'}
|
||||
for a in root.xpath('//div[contains(@class,"module-info-tag")]//a'):
|
||||
txt = self._parse_text(a)
|
||||
title = a.get('title', '')
|
||||
if not vod_year and (re.match(r'^\d{4}$', txt) or re.match(r'^\d{4}$', title)):
|
||||
vod_year = txt or title
|
||||
if not vod_area and txt in area_set:
|
||||
vod_area = txt
|
||||
|
||||
vod_actor = ''
|
||||
vod_director = ''
|
||||
info_items = root.xpath('//div[contains(@class,"module-info-item")]')
|
||||
for item in info_items:
|
||||
label = item.xpath('.//span[contains(@class,"module-info-item-title")]/text()')
|
||||
if not label:
|
||||
continue
|
||||
label_text = label[0].strip()
|
||||
content_elems = item.xpath('.//div[contains(@class,"module-info-item-content")]')
|
||||
if not content_elems:
|
||||
continue
|
||||
content = content_elems[0]
|
||||
if '导演' in label_text:
|
||||
directors = content.xpath('.//a/text()')
|
||||
vod_director = ' '.join([d.strip() for d in directors if d.strip()])
|
||||
elif '主演' in label_text:
|
||||
actors = content.xpath('.//a/text()')
|
||||
vod_actor = ' '.join([a.strip() for a in actors if a.strip()])
|
||||
|
||||
vod_content = ''
|
||||
intro = root.xpath('//div[contains(@class,"module-info-introduction-content")]//p')
|
||||
if intro:
|
||||
vod_content = self._parse_text(intro[0])
|
||||
vod_content = re.sub(r'\[.*?\]', '', vod_content).strip()
|
||||
|
||||
vod_play_from = []
|
||||
vod_play_url = []
|
||||
source_tabs = root.xpath('//div[@id="y-playList"]//div[contains(@class,"module-tab-item")]')
|
||||
list_boxes = root.xpath('//div[contains(@class,"module-list") and contains(@class,"tab-list")]')
|
||||
|
||||
for idx, tab in enumerate(source_tabs):
|
||||
source_name = tab.get('data-dropdown-value', '').strip()
|
||||
if not source_name:
|
||||
spans = tab.xpath('.//span')
|
||||
source_name = self._parse_text(spans[0]) if spans else ''
|
||||
source_name = re.sub(r'\s+', ' ', source_name).strip()
|
||||
if not source_name:
|
||||
continue
|
||||
if idx >= len(list_boxes):
|
||||
continue
|
||||
box = list_boxes[idx]
|
||||
links = box.xpath('.//a[contains(@class,"module-play-list-link")]')
|
||||
play_list = []
|
||||
for a in links:
|
||||
ep_name = ''
|
||||
spans = a.xpath('.//span/text()')
|
||||
if spans:
|
||||
ep_name = spans[0].strip()
|
||||
if not ep_name:
|
||||
ep_name = a.get('title', '').strip()
|
||||
href = a.get('href', '')
|
||||
if not ep_name or not href:
|
||||
continue
|
||||
play_list.append(f"{ep_name}${self._fix_url(href)}")
|
||||
if play_list:
|
||||
vod_play_from.append(source_name)
|
||||
vod_play_url.append('#'.join(play_list))
|
||||
|
||||
if not vod_play_from:
|
||||
links = root.xpath('//div[contains(@class,"module-play-list")]//a[contains(@class,"module-play-list-link")]')
|
||||
play_list = []
|
||||
for a in links:
|
||||
ep_name = ''
|
||||
spans = a.xpath('.//span/text()')
|
||||
if spans:
|
||||
ep_name = spans[0].strip()
|
||||
if not ep_name:
|
||||
ep_name = a.get('title', '').strip()
|
||||
href = a.get('href', '')
|
||||
if ep_name and href:
|
||||
play_list.append(f"{ep_name}${self._fix_url(href)}")
|
||||
if play_list:
|
||||
vod_play_from.append("默认")
|
||||
vod_play_url.append('#'.join(play_list))
|
||||
|
||||
detail = {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_name,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_year": vod_year,
|
||||
"vod_area": vod_area,
|
||||
"vod_actor": vod_actor,
|
||||
"vod_director": vod_director,
|
||||
"vod_content": vod_content,
|
||||
"vod_play_from": "$$$".join(vod_play_from),
|
||||
"vod_play_url": "$$$".join(vod_play_url)
|
||||
}
|
||||
return {'list': [detail]}
|
||||
except Exception:
|
||||
return {'list': []}
|
||||
|
||||
def _extract_player_data(self, html):
|
||||
try:
|
||||
m = re.search(r'var\s+player_aaaa\s*=\s*\{', html)
|
||||
if not m:
|
||||
return None
|
||||
start = m.end() - 1
|
||||
depth = 1
|
||||
i = start + 1
|
||||
while i < len(html) and depth > 0:
|
||||
if html[i] == '{':
|
||||
depth += 1
|
||||
elif html[i] == '}':
|
||||
depth -= 1
|
||||
i += 1
|
||||
data = json.loads(html[start:i])
|
||||
return data
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
try:
|
||||
html = self._get(id)
|
||||
data = self._extract_player_data(html)
|
||||
if data and data.get('url'):
|
||||
real_url = data['url']
|
||||
if real_url.startswith('//'):
|
||||
real_url = 'https:' + real_url
|
||||
parse_flag = 0 if self.isVideoFormat(real_url) else 1
|
||||
return {"parse": parse_flag, "playUrl": "", "url": real_url, "header": json.dumps(self.header)}
|
||||
|
||||
iframe_match = re.search(r'<iframe[^>]+src\s*=\s*"([^"]+)"', html)
|
||||
if iframe_match:
|
||||
real_url = iframe_match.group(1)
|
||||
real_url = self._fix_url(real_url)
|
||||
parse_flag = 0 if self.isVideoFormat(real_url) else 1
|
||||
return {"parse": parse_flag, "playUrl": "", "url": real_url, "header": json.dumps(self.header)}
|
||||
|
||||
m3u8_match = re.search(r'["\'](https?://[^"\']+\.m3u8[^"\']*)["\']', html)
|
||||
if m3u8_match:
|
||||
return {"parse": 0, "playUrl": "", "url": m3u8_match.group(1), "header": json.dumps(self.header)}
|
||||
|
||||
mp4_match = re.search(r'["\'](https?://[^"\']+\.(?:mp4|flv|ts))["\']', html)
|
||||
if mp4_match:
|
||||
return {"parse": 0, "playUrl": "", "url": mp4_match.group(1), "header": json.dumps(self.header)}
|
||||
|
||||
return {"parse": 1, "playUrl": "", "url": id, "header": json.dumps(self.header)}
|
||||
except Exception:
|
||||
return {"parse": 0, "playUrl": "", "url": ""}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
videos = []
|
||||
try:
|
||||
url = f"{self.host}/index.php/vod/search/wd/{quote(key)}.html"
|
||||
html = self._get(url)
|
||||
root = etree.HTML(html)
|
||||
items = root.xpath('//div[contains(@class,"module-card-item")]')
|
||||
for item in items:
|
||||
try:
|
||||
a = item.xpath('.//div[contains(@class,"module-card-item-title")]//a')[0]
|
||||
href = a.get('href', '')
|
||||
m = re.search(r'/detail/id/(\d+)\.html', href)
|
||||
if not m:
|
||||
continue
|
||||
vod_id = m.group(1)
|
||||
vod_name = self._parse_text(a)
|
||||
poster_a = item.xpath('.//a[contains(@class,"module-card-item-poster")]')
|
||||
vod_pic = self._parse_pic(poster_a[0]) if poster_a else ''
|
||||
remark = item.xpath('.//div[contains(@class,"module-item-note")]/text()')
|
||||
vod_remarks = remark[0].strip() if remark else ''
|
||||
videos.append({
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_name,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_remarks": vod_remarks
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
'list': videos,
|
||||
'page': int(pg),
|
||||
'pagecount': 1,
|
||||
'limit': len(videos),
|
||||
'total': len(videos)
|
||||
}
|
||||
except Exception:
|
||||
return {'list': [], 'page': 1, 'pagecount': 0, 'limit': 0, 'total': 0}
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return any(url.lower().endswith(fmt) for fmt in ['.m3u8', '.mp4', '.flv', '.ts'])
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, params):
|
||||
return None
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,485 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
import json
|
||||
import logging
|
||||
import urllib.parse
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
try:
|
||||
from base.spider import Spider as BaseSpider
|
||||
except ImportError:
|
||||
BaseSpider = object
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Spider(BaseSpider):
|
||||
"""映像星球爬虫 - 适配 MxPro CMS"""
|
||||
|
||||
BASE_URL = "https://www.yxxq41.cc"
|
||||
|
||||
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",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
||||
}
|
||||
|
||||
CATEGORY_MAP = {
|
||||
"1": "电影",
|
||||
"2": "电视剧",
|
||||
"3": "综艺",
|
||||
"4": "动漫",
|
||||
"7": "纪录片",
|
||||
"39": "短剧",
|
||||
"53": "体育",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
super().__init__()
|
||||
except Exception:
|
||||
pass
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update(self.HEADERS)
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
return "映像星球"
|
||||
|
||||
def homeContent(self, filter=False):
|
||||
"""首页内容"""
|
||||
try:
|
||||
url = f"{self.BASE_URL}/"
|
||||
resp = self.session.get(url, timeout=30)
|
||||
resp.raise_for_status()
|
||||
resp.encoding = 'utf-8'
|
||||
html = resp.text
|
||||
|
||||
classes = []
|
||||
for cate_id, cate_name in self.CATEGORY_MAP.items():
|
||||
classes.append({
|
||||
"type_id": cate_id,
|
||||
"type_name": cate_name,
|
||||
})
|
||||
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
videos = []
|
||||
seen_ids = set()
|
||||
|
||||
for item in soup.select('a.module-poster-item'):
|
||||
if item.get('data-ad-slot') or 'mac-ad-card' in item.get('class', []):
|
||||
continue
|
||||
href = item.get('href', '')
|
||||
m = re.search(r'/html/(\d+)\.html', href)
|
||||
if not m:
|
||||
continue
|
||||
vid = m.group(1)
|
||||
if vid in seen_ids:
|
||||
continue
|
||||
seen_ids.add(vid)
|
||||
|
||||
title_el = item.select_one('.module-poster-item-title')
|
||||
title = title_el.get_text(strip=True) if title_el else ''
|
||||
|
||||
pic_el = item.select_one('.module-item-pic img')
|
||||
pic = ''
|
||||
if pic_el:
|
||||
pic = pic_el.get('data-original', '') or pic_el.get('src', '')
|
||||
|
||||
note_el = item.select_one('.module-item-note')
|
||||
remark = note_el.get_text(strip=True) if note_el else ''
|
||||
|
||||
videos.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": title,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark,
|
||||
})
|
||||
if len(videos) >= 36:
|
||||
break
|
||||
|
||||
return {
|
||||
"class": classes,
|
||||
"list": videos,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"获取首页失败: {e}")
|
||||
return {}
|
||||
|
||||
def homeVideoContent(self):
|
||||
home = self.homeContent()
|
||||
return {"list": home.get("list", [])}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, ext):
|
||||
"""分类内容"""
|
||||
try:
|
||||
page = int(pg) if pg else 1
|
||||
if page == 1:
|
||||
url = f"{self.BASE_URL}/list/{tid}.html"
|
||||
else:
|
||||
url = f"{self.BASE_URL}/list/{tid}-{page}.html"
|
||||
|
||||
resp = self.session.get(url, timeout=30)
|
||||
resp.raise_for_status()
|
||||
resp.encoding = 'utf-8'
|
||||
html = resp.text
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
|
||||
total = 0
|
||||
total_page = 1
|
||||
|
||||
vod_list = []
|
||||
for item in soup.select('a.module-poster-item'):
|
||||
if item.get('data-ad-slot') or 'mac-ad-card' in item.get('class', []):
|
||||
continue
|
||||
href = item.get('href', '')
|
||||
m = re.search(r'/html/(\d+)\.html', href)
|
||||
if not m:
|
||||
continue
|
||||
vid = m.group(1)
|
||||
|
||||
title_el = item.select_one('.module-poster-item-title')
|
||||
title = title_el.get_text(strip=True) if title_el else ''
|
||||
|
||||
pic_el = item.select_one('.module-item-pic img')
|
||||
pic = ''
|
||||
if pic_el:
|
||||
pic = pic_el.get('data-original', '') or pic_el.get('src', '')
|
||||
|
||||
note_el = item.select_one('.module-item-note')
|
||||
remark = note_el.get_text(strip=True) if note_el else ''
|
||||
|
||||
vod_list.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": title,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark,
|
||||
})
|
||||
|
||||
m_total = re.search(r'共(\d+)条', html)
|
||||
if m_total:
|
||||
total = int(m_total.group(1))
|
||||
|
||||
for a in soup.select('.module-page a'):
|
||||
txt = a.get_text(strip=True)
|
||||
if txt.isdigit():
|
||||
num = int(txt)
|
||||
if num > total_page:
|
||||
total_page = num
|
||||
|
||||
return {
|
||||
"list": vod_list,
|
||||
"page": page,
|
||||
"pagecount": total_page,
|
||||
"limit": 20,
|
||||
"total": total,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"获取分类内容失败: {e}")
|
||||
return {"list": [], "page": 1, "pagecount": 1, "limit": 20, "total": 0}
|
||||
|
||||
def detailContent(self, ids):
|
||||
"""详情内容 - 精确提取播放列表"""
|
||||
try:
|
||||
vod_id = ids[0] if isinstance(ids, list) else str(ids)
|
||||
url = f"{self.BASE_URL}/html/{vod_id}.html"
|
||||
resp = self.session.get(url, timeout=30)
|
||||
resp.raise_for_status()
|
||||
resp.encoding = 'utf-8'
|
||||
html = resp.text
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
|
||||
# 基本信息
|
||||
name = ''
|
||||
name_el = soup.select_one('.module-info-heading h1')
|
||||
if name_el:
|
||||
name = name_el.get_text(strip=True)
|
||||
|
||||
pic = ''
|
||||
pic_el = soup.select_one('.module-info-poster .module-item-pic img')
|
||||
if pic_el:
|
||||
pic = pic_el.get('data-original', '') or pic_el.get('src', '')
|
||||
|
||||
year = ''
|
||||
area = ''
|
||||
type_name = ''
|
||||
for tl in soup.select('.module-info-tag-link a'):
|
||||
txt = tl.get_text(strip=True)
|
||||
if re.search(r'20\d{2}', txt) and not year:
|
||||
year = txt
|
||||
if re.search(r'(大陆|香港|台湾|韩国|日本|美国|欧美|海外|国产|泰国|印度)', txt) and not area:
|
||||
area = txt
|
||||
if not type_name:
|
||||
type_name = txt
|
||||
|
||||
remark = ''
|
||||
content = ''
|
||||
for item in soup.select('.module-info-item'):
|
||||
title_el = item.select_one('.module-info-item-title')
|
||||
if title_el:
|
||||
t = title_el.get_text(strip=True)
|
||||
content_el = item.select_one('.module-info-item-content')
|
||||
if content_el and '备注' in t:
|
||||
remark = content_el.get_text(strip=True)
|
||||
|
||||
desc_div = soup.select_one('.module-info-introduction-content')
|
||||
if desc_div:
|
||||
content = desc_div.get_text(strip=True)
|
||||
|
||||
# ===== 精确提取播放列表 =====
|
||||
play_sources = []
|
||||
|
||||
# 获取播放源名称
|
||||
source_names = []
|
||||
for tab in soup.select('#y-playList .tab-item'):
|
||||
name_tmp = tab.get_text(strip=True)
|
||||
if name_tmp:
|
||||
source_names.append(name_tmp)
|
||||
|
||||
# 获取 play-list 分组 (class="tab-list his-tab-list")
|
||||
play_panels = soup.select('.tab-list.his-tab-list')
|
||||
|
||||
if source_names and play_panels:
|
||||
for i, src_name in enumerate(source_names):
|
||||
episodes = []
|
||||
if i < len(play_panels):
|
||||
for link in play_panels[i].select('a.module-play-list-link'):
|
||||
href = link.get('href', '')
|
||||
ep_name_el = link.select_one('span')
|
||||
ep_name = ep_name_el.get_text(strip=True) if ep_name_el else ''
|
||||
if href and ep_name:
|
||||
episodes.append((ep_name, href))
|
||||
if episodes:
|
||||
play_sources.append((src_name, episodes))
|
||||
|
||||
# 备用:正则提取
|
||||
if not play_sources:
|
||||
all_links = re.findall(
|
||||
r'<a class="module-play-list-link"[^>]*href="(/play/\d+-\d+-\d+\.html)"[^>]*>.*?<span>(.*?)</span>',
|
||||
html, re.DOTALL
|
||||
)
|
||||
if all_links:
|
||||
src_names = source_names or ['线路①']
|
||||
for sn in src_names:
|
||||
episodes = [(n.strip(), h) for h, n in all_links]
|
||||
if episodes:
|
||||
play_sources.append((sn, episodes))
|
||||
break
|
||||
|
||||
play_from_list = []
|
||||
play_url_list = []
|
||||
|
||||
for src_name, episodes in play_sources:
|
||||
play_from_list.append(src_name)
|
||||
ep_list = []
|
||||
for ep_name, href in episodes:
|
||||
full_url = href if href.startswith('http') else f"{self.BASE_URL}{href}"
|
||||
ep_list.append(f"{ep_name}${full_url}")
|
||||
play_url_list.append('#'.join(ep_list))
|
||||
|
||||
vod_item = {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"type_name": type_name,
|
||||
"vod_year": year,
|
||||
"vod_area": area,
|
||||
"vod_remarks": remark,
|
||||
"vod_content": content,
|
||||
"vod_play_from": '$$$'.join(play_from_list),
|
||||
"vod_play_url": '$$$'.join(play_url_list),
|
||||
}
|
||||
|
||||
return {"list": [vod_item]}
|
||||
except Exception as e:
|
||||
logger.error(f"获取详情失败: {e}")
|
||||
return {"list": []}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
"""播放内容 - 解析真实m3u8地址"""
|
||||
try:
|
||||
play_url = urllib.parse.unquote(id) if id else ''
|
||||
if not play_url:
|
||||
return {"parse": 0, "url": ""}
|
||||
|
||||
if not play_url.startswith('http'):
|
||||
play_url = f"{self.BASE_URL}{play_url}" if play_url.startswith('/') else f"{self.BASE_URL}/{play_url}"
|
||||
|
||||
resp = self.session.get(play_url, timeout=30)
|
||||
resp.raise_for_status()
|
||||
resp.encoding = 'utf-8'
|
||||
html = resp.text
|
||||
|
||||
m = re.search(r'player_aaaa\s*=\s*({.*?});', html, re.DOTALL)
|
||||
if m:
|
||||
try:
|
||||
data = json.loads(m.group(1))
|
||||
real_url = data.get('url', '')
|
||||
if real_url:
|
||||
return {
|
||||
"parse": 0,
|
||||
"url": real_url,
|
||||
"header": json.dumps(self.HEADERS),
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
"parse": 1,
|
||||
"url": play_url,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"解析播放失败: {e}")
|
||||
return {"parse": 0, "url": ""}
|
||||
|
||||
def searchContent(self, key, quick, pg):
|
||||
"""搜索内容"""
|
||||
try:
|
||||
page = int(pg) if pg else 1
|
||||
encoded_key = urllib.parse.quote(key)
|
||||
url = f"{self.BASE_URL}/search/{encoded_key}-------------{page}.html"
|
||||
|
||||
resp = self.session.get(url, timeout=30)
|
||||
resp.raise_for_status()
|
||||
resp.encoding = 'utf-8'
|
||||
html = resp.text
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
|
||||
vod_list = []
|
||||
seen_ids = set()
|
||||
|
||||
for item in soup.select('.module-item'):
|
||||
detail_link = item.select_one('a[href*="/html/"]')
|
||||
if not detail_link:
|
||||
continue
|
||||
href = detail_link.get('href', '')
|
||||
m = re.search(r'/html/(\d+)\.html', href)
|
||||
if not m:
|
||||
continue
|
||||
vid = m.group(1)
|
||||
if vid in seen_ids:
|
||||
continue
|
||||
seen_ids.add(vid)
|
||||
|
||||
title = detail_link.get('title', '') or detail_link.get_text(strip=True)
|
||||
|
||||
pic = ''
|
||||
img = item.select_one('img')
|
||||
if img:
|
||||
pic = img.get('data-original', '') or img.get('src', '')
|
||||
|
||||
remark = ''
|
||||
remark_el = item.select_one('.module-item-note, .video-note, .note')
|
||||
if remark_el:
|
||||
remark = remark_el.get_text(strip=True)
|
||||
|
||||
vod_list.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": title,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark,
|
||||
})
|
||||
|
||||
if not vod_list:
|
||||
for link in soup.select('a[href*="/html/"]'):
|
||||
href = link.get('href', '')
|
||||
m = re.search(r'/html/(\d+)\.html', href)
|
||||
if not m:
|
||||
continue
|
||||
vid = m.group(1)
|
||||
if vid in seen_ids:
|
||||
continue
|
||||
seen_ids.add(vid)
|
||||
title = link.get_text(strip=True)
|
||||
if not title:
|
||||
continue
|
||||
vod_list.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": title,
|
||||
"vod_pic": '',
|
||||
"vod_remarks": '',
|
||||
})
|
||||
|
||||
total = 0
|
||||
m_total = re.search(r'找到(\d+)部影片', html)
|
||||
if m_total:
|
||||
total = int(m_total.group(1))
|
||||
else:
|
||||
total = len(vod_list)
|
||||
|
||||
return {
|
||||
"list": vod_list,
|
||||
"page": page,
|
||||
"pagecount": 1,
|
||||
"limit": 20,
|
||||
"total": total,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"搜索失败: {e}")
|
||||
return {"list": [], "page": 1, "pagecount": 1, "limit": 20, "total": 0}
|
||||
|
||||
def localProxy(self, param):
|
||||
return []
|
||||
|
||||
|
||||
def main():
|
||||
spider = Spider()
|
||||
|
||||
print("=" * 60)
|
||||
print("【1】测试首页")
|
||||
home = spider.homeContent()
|
||||
print(f"分类: {len(home.get('class', []))}, 推荐: {len(home.get('list', []))}")
|
||||
for v in home.get('list', [])[:5]:
|
||||
print(f" - {v['vod_name']} [{v['vod_remarks']}]")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("【2】测试分类 (电影)")
|
||||
cat = spider.categoryContent("1", "1", False, {})
|
||||
print(f"总数: {cat.get('total')}, 本页: {len(cat.get('list', []))}")
|
||||
for v in cat.get('list', [])[:5]:
|
||||
print(f" - {v['vod_name']} [{v['vod_remarks']}]")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("【3】测试详情 (23026 给阿嬷的情书)")
|
||||
detail = spider.detailContent(["23026"])
|
||||
if detail.get('list'):
|
||||
d = detail['list'][0]
|
||||
print(f"标题: {d['vod_name']}")
|
||||
play_from = d.get('vod_play_from', '')
|
||||
play_url = d.get('vod_play_url', '')
|
||||
sources = play_from.split('$$$')
|
||||
urls = play_url.split('$$$') if play_url else []
|
||||
print(f"播放源: {len(sources)}个")
|
||||
for i, src in enumerate(sources):
|
||||
ep_count = len(urls[i].split('#')) if i < len(urls) else 0
|
||||
print(f" - {src}: {ep_count}集")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("【4】测试播放解析")
|
||||
if detail.get('list') and detail['list'][0].get('vod_play_url'):
|
||||
first = detail['list'][0]['vod_play_url'].split('$$$')[0].split('#')[0]
|
||||
if '$' in first:
|
||||
ep_url = first.split('$')[1]
|
||||
play = spider.playerContent('', ep_url, [])
|
||||
print(f"解析结果: {play.get('url', '')[:80]}...")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("【5】测试搜索")
|
||||
search = spider.searchContent("战狼", False, "1")
|
||||
print(f"结果: {search.get('total')}部")
|
||||
for v in search.get('list', [])[:5]:
|
||||
print(f" - {v['vod_name']}")
|
||||
|
||||
print("\n完成!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,173 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import re
|
||||
import requests
|
||||
from urllib.parse import quote
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def init(self, extend=""):
|
||||
self.host = "https://citapa.com"
|
||||
self.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36",
|
||||
"Referer": self.host + "/",
|
||||
"Origin": self.host
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return "茶杯狐"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return bool(re.search(r'\.(m3u8|mp4|flv|avi|mkv|mov|ts)(\?|$)', url or "", re.I))
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def homeContent(self, filter):
|
||||
return {
|
||||
"class": [
|
||||
{"type_id": "1", "type_name": "电影"},
|
||||
{"type_id": "2", "type_name": "电视剧"},
|
||||
{"type_id": "3", "type_name": "综艺"},
|
||||
{"type_id": "4", "type_name": "动漫"}
|
||||
]
|
||||
}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {"list": self.parseList(self.get(self.host + "/"))}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
html = self.get(self.host + "/search.php?searchtype=5&tid=" + str(tid) + "&page=" + str(pg))
|
||||
return {
|
||||
"page": int(pg),
|
||||
"pagecount": 999,
|
||||
"limit": 24,
|
||||
"total": 999999,
|
||||
"list": self.parseList(html)
|
||||
}
|
||||
|
||||
def detailContent(self, ids):
|
||||
vid = ids[0]
|
||||
html = self.get(self.host + "/movie/index" + vid + ".html")
|
||||
name = self.clean(self.match(html, r'<h1[^>]*>(.*?)</h1>') or self.match(html, r'<meta property="og:title" content="(.*?)"'))
|
||||
pic = self.fix(self.match(html, r'<meta property="og:image" content="(.*?)"') or self.match(html, r'<img[^>]+(?:data-src|data-original|src)=["\']([^"\']+)'))
|
||||
desc = self.clean(self.match(html, r'<meta property="og:description" content="(.*?)"') or self.match(html, r'剧情:([\s\S]*?)在线观看'))
|
||||
tabs = re.findall(r'data-dropdown-value=["\']([^"\']+)["\']', html)
|
||||
panels = re.findall(r'<div class="module-list module-player-list[\s\S]*?</div>\s*</div>\s*</div>', html)
|
||||
play_from = []
|
||||
play_url = []
|
||||
for i, p in enumerate(panels):
|
||||
eps = []
|
||||
for m in re.finditer(r'<a[^>]+title=["\']([^"\']+)["\'][^>]+href=["\']([^"\']*?/play/[^"\']+)["\']', p):
|
||||
t = self.clean(m.group(1))
|
||||
u = self.fix(m.group(2))
|
||||
if t and u:
|
||||
eps.append(t + "$" + u)
|
||||
if not eps:
|
||||
for m in re.finditer(r'<a[^>]+href=["\']([^"\']*?/play/[^"\']+)["\'][^>]*>(.*?)</a>', p):
|
||||
t = self.clean(m.group(2))
|
||||
u = self.fix(m.group(1))
|
||||
if t and u:
|
||||
eps.append(t + "$" + u)
|
||||
if eps:
|
||||
key = tabs[i] if i < len(tabs) else "线路" + str(i + 1)
|
||||
if key not in play_from:
|
||||
play_from.append(key)
|
||||
play_url.append("#".join(eps))
|
||||
if not play_url:
|
||||
eps = []
|
||||
for m in re.finditer(r'<a[^>]+href=["\']([^"\']*?/play/' + vid + r'-[^"\']+)["\'][^>]*>(.*?)</a>', html):
|
||||
t = self.clean(m.group(2)) or "播放"
|
||||
u = self.fix(m.group(1))
|
||||
if t and u:
|
||||
eps.append(t + "$" + u)
|
||||
if eps:
|
||||
play_from.append("默认")
|
||||
play_url.append("#".join(eps))
|
||||
return {
|
||||
"list": [{
|
||||
"vod_id": vid,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_content": desc,
|
||||
"vod_play_from": "$$$".join(play_from),
|
||||
"vod_play_url": "$$$".join(play_url)
|
||||
}]
|
||||
}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
html = ""
|
||||
try:
|
||||
html = requests.post(self.host + "/search.php", headers=self.headers, data={"searchword": key}, timeout=15).text
|
||||
except Exception:
|
||||
html = ""
|
||||
if not html or "module-item" not in html:
|
||||
html = self.get(self.host + "/search.php?searchword=" + quote(key) + "&page=" + str(pg))
|
||||
return {"list": self.parseList(html), "page": int(pg)}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
return {"parse": 1, "url": id, "header": self.headers}
|
||||
|
||||
def localProxy(self, param):
|
||||
return [404, "text/plain", "", ""]
|
||||
|
||||
def destroy(self):
|
||||
return "正在Destroy"
|
||||
|
||||
def get(self, url):
|
||||
try:
|
||||
r = requests.get(url, headers=self.headers, timeout=15)
|
||||
r.encoding = r.apparent_encoding or "utf-8"
|
||||
return r.text
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def match(self, text, rule):
|
||||
m = re.search(rule, text or "", re.S)
|
||||
return m.group(1) if m else ""
|
||||
|
||||
def clean(self, text):
|
||||
return re.sub(r"\s+", " ", re.sub(r"<.*?>", "", text or "")).strip()
|
||||
|
||||
def fix(self, url):
|
||||
if not url:
|
||||
return ""
|
||||
if url.startswith("//"):
|
||||
return "https:" + url
|
||||
if url.startswith("/"):
|
||||
return self.host + url
|
||||
return url
|
||||
|
||||
def parseList(self, html):
|
||||
res = []
|
||||
seen = set()
|
||||
for m in re.finditer(r'<div class="module-item">([\s\S]*?)</div>\s*</div>', html or "", re.S):
|
||||
item = m.group(1)
|
||||
href = self.match(item, r'href=["\']/movie/index(\d+)\.html["\']')
|
||||
if not href or href in seen:
|
||||
continue
|
||||
seen.add(href)
|
||||
name = self.clean(self.match(item, r'alt=["\']([^"\']+)') or self.match(item, r'title=["\']([^"\']+)') or self.match(item, r'class="module-item-title"[^>]*>(.*?)</a>'))
|
||||
pic = self.fix(self.match(item, r'(?:data-src|data-original|src)=["\']([^"\']+\.(?:jpg|jpeg|png|webp|gif)[^"\']*)'))
|
||||
remarks = self.clean(self.match(item, r'class="module-item-text"[^>]*>(.*?)</div>'))
|
||||
if name:
|
||||
res.append({
|
||||
"vod_id": href,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
})
|
||||
if not res:
|
||||
for m in re.finditer(r'<a[^>]+href=["\']/movie/index(\d+)\.html["\'][^>]*title=["\']([^"\']+)["\'][\s\S]*?<img[^>]+(?:data-src|data-original|src)=["\']([^"\']+)["\'][\s\S]*?(?:class="module-item-text"[^>]*>(.*?)</div>)?', html or "", re.S):
|
||||
vid = m.group(1)
|
||||
if vid in seen:
|
||||
continue
|
||||
seen.add(vid)
|
||||
res.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": self.clean(m.group(2)),
|
||||
"vod_pic": self.fix(m.group(3)),
|
||||
"vod_remarks": self.clean(m.group(4))
|
||||
})
|
||||
return res
|
||||
Reference in New Issue
Block a user