Sync all projects
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
|
||||
#EXTM3U
|
||||
#EXT-X-APP APTV
|
||||
#EXT-X-APTV-TYPE blank
|
||||
|
||||
#EXTINF:-1 group-title="美亞🎬",美亚电影
|
||||
http://173.208.234.146/live/mymovie.m3u8
|
||||
#EXTINF:-1 group-title="星影🍿爆谷🎬",NOW星影台
|
||||
http://173.208.234.146/live/nowxy.m3u8
|
||||
#EXTINF:-1 group-title="星影🍿爆谷🎬",NOW爆谷台
|
||||
http://173.208.234.146/live/nowbg.m3u8
|
||||
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,111 @@
|
||||
/*
|
||||
@header({
|
||||
searchable: 2,
|
||||
filterable: 1,
|
||||
quickSearch: 0,
|
||||
title: '可可影视[优]',
|
||||
'类型': '影视',
|
||||
lang: 'dr2'
|
||||
})
|
||||
*/
|
||||
|
||||
var rule = {
|
||||
title: '可可影视[优]',
|
||||
host: 'https://www.keke1.app',
|
||||
//host: 'https://www.kkys01.com',
|
||||
url: '/show/fyclass-----2-fypage.html',
|
||||
//url: '/show/fyclass-fyfilter-fypage.html',
|
||||
searchUrl: '/search?t=lw%2FzDeVGGBRTbdH2HVvs7Q%3D%3D&k=**&page=fypage',
|
||||
searchable: 2,
|
||||
quickSearch: 0,
|
||||
filterable: 1,
|
||||
headers: {
|
||||
'User-Agent': 'MOBILE_UA',
|
||||
},
|
||||
class_parse: '#nav-swiper&&.nav-swiper-slide;a&&Text;a&&href;/(\\w+).html',
|
||||
cate_exclude: 'Netflix|今日更新|专题列表|排行榜',
|
||||
tab_exclude:'可可影视提供',
|
||||
tab_order: ['超清', '蓝光', '极速蓝光'],
|
||||
tab_remove:['4K(高峰不卡)'],
|
||||
play_parse: true,
|
||||
lazy: $js.toString(() => {
|
||||
input = {
|
||||
parse: 1,
|
||||
url: input,
|
||||
js: 'document.querySelector("#my-video video").click()',
|
||||
}
|
||||
}),
|
||||
limit: 20,
|
||||
推荐: '.section-box:eq(2)&&.module-box-inner&&.module-item;*;*;*;*',
|
||||
double: false,
|
||||
一级: '.module-box-inner&&.module-item;.v-item-title:eq(1)&&Text;img:last-of-type&&data-original;.v-item-bottom&&span&&Text;a&&href',
|
||||
二级: {
|
||||
title: '.detail-pic&&img&&alt;.detail-tags&&a&&Text',
|
||||
img: '.detail-pic&&img&&data-original',
|
||||
desc: '.detail-info-row-main:eq(-2)&&Text;.detail-tags&&a&&Text;.detail-tags&&a:eq(1)&&Text;.detail-info-row-main:eq(1)&&Text;.detail-info-row-main&&Text',
|
||||
content: '.detail-desc&&Text',
|
||||
tabs: '.source-item-label',
|
||||
//tabs: 'body&&.source-item-label[id]',
|
||||
lists: '.episode-list:eq(#id) a',
|
||||
},
|
||||
搜索: '.search-result-list&&a;.title:eq(0)&&Text;.search-result-item-pic&&img&&data-original;.search-result-item-header&&Text;a&&href;.desc&&Text',
|
||||
图片替换:'https://www.keke1.app=>https://vres.zclmjc.com',
|
||||
预处理: $js.toString(() => {
|
||||
function extractHashFromResponse(ruleHost) {
|
||||
try {
|
||||
let response = request(ruleHost);
|
||||
const regex = /a0_0x2a54\s*=\s*\['([^']+)'/;
|
||||
let match = response.match(regex);
|
||||
return match ? match[1] : '';
|
||||
} catch (error) {
|
||||
console.error('请求失败:', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function sha1ToUint8ArrayLatin1(input) {
|
||||
let hash = CryptoJS.SHA1(input);
|
||||
let latin1String = hash.toString(CryptoJS.enc.Latin1);
|
||||
let uint8Array = new Uint8Array(latin1String.length);
|
||||
|
||||
for (let i = 0; i < latin1String.length; i++) {
|
||||
uint8Array[i] = latin1String.charCodeAt(i);
|
||||
}
|
||||
|
||||
return uint8Array;
|
||||
}
|
||||
|
||||
function run(c, n1) {
|
||||
let i = 0;
|
||||
while (i < 1000000) {
|
||||
let input = c + i;
|
||||
let hash = sha1ToUint8ArrayLatin1(input);
|
||||
if (hash[n1] === 0xb0 && hash[n1 + 1] === 0x0b) {
|
||||
let myck = 'cdndefend_js_cookie=' + c + i;
|
||||
console.log('找到 myck:', myck);
|
||||
rule.headers['cookie'] = myck;
|
||||
setItem('mycookie', myck);
|
||||
setItem('myhash', c);
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
console.log('未找到符合条件的 i');
|
||||
}
|
||||
|
||||
let hash = extractHashFromResponse(rule.host);
|
||||
if (hash != '' && hash != getItem('myhash')) {
|
||||
setItem('mycookie', '');
|
||||
setItem('myhash', '');
|
||||
run(hash, parseInt('0x' + hash[0], 16));
|
||||
}
|
||||
if (getItem('mycookie')) {
|
||||
rule.headers['cookie'] = getItem('mycookie');
|
||||
}
|
||||
|
||||
let html = fetch(HOST, {headers: rule.headers});
|
||||
const regex2 = /<input type="hidden" name="t" value="([^"]+)"/;
|
||||
let match2 = html.match(regex2);
|
||||
rule.searchUrl = rule.searchUrl.replace("lw%2FzDeVGGBRTbdH2HVvs7Q%3D%3D", match2 ? encodeURIComponent(match2[1]) : '');
|
||||
}),
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from base.spider import Spider
|
||||
from pyquery import PyQuery as pq
|
||||
|
||||
class Spider(Spider):
|
||||
def init(self, extend=""):
|
||||
self.host = "https://qswyt4444.com"
|
||||
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',
|
||||
'Referer': f'{self.host}/',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return "QS五月天"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return url.lower().endswith('.m3u8')
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return True
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
classes = []
|
||||
seen_ids = set()
|
||||
try:
|
||||
res = self.fetch(self.host, headers=self.headers)
|
||||
d = pq(res.text)
|
||||
items = d('a[href^="/movie/block/"]')
|
||||
for item in items.items():
|
||||
href = item.attr('href')
|
||||
match = re.search(r'/movie/block/(\d+)', href)
|
||||
if match:
|
||||
tid = match.group(1)
|
||||
name = item.text().strip() or item.find('span').text().strip()
|
||||
if tid and name and tid not in seen_ids:
|
||||
classes.append({'type_name': name, 'type_id': tid})
|
||||
seen_ids.add(tid)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not classes:
|
||||
cateManual = {
|
||||
"最新": "newest",
|
||||
"国产": "50",
|
||||
"淫荡少妇": "21",
|
||||
"人妻诱惑": "22",
|
||||
"大奶萝莉": "23",
|
||||
"丝袜制服": "24",
|
||||
"强奸": "45",
|
||||
"群P": "46"
|
||||
}
|
||||
for k, v in cateManual.items():
|
||||
classes.append({'type_name': k, 'type_id': v})
|
||||
|
||||
result['class'] = classes
|
||||
result['filters'] = {}
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
try:
|
||||
res = self.fetch(self.host, headers=self.headers)
|
||||
return {'list': self.parse_list(res.text)}
|
||||
except Exception:
|
||||
return {'list': []}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
result = {}
|
||||
if tid.isdigit():
|
||||
url = f'{self.host}/movie/block/{tid}?page={pg}'
|
||||
else:
|
||||
url = f'{self.host}/movie/{tid}?page={pg}'
|
||||
try:
|
||||
res = self.fetch(url, headers=self.headers)
|
||||
result['list'] = self.parse_list(res.text)
|
||||
result['page'] = int(pg)
|
||||
result['pagecount'] = 999
|
||||
result['limit'] = 20
|
||||
result['total'] = 9999
|
||||
except Exception:
|
||||
result['list'] = []
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
tid = ids[0]
|
||||
url = tid if tid.startswith('http') else (f'{self.host}{tid}' if tid.startswith('/') else f'{self.host}/movie/detail/{tid}')
|
||||
res = self.fetch(url, headers=self.headers)
|
||||
content = res.text
|
||||
title = ""
|
||||
pic = ""
|
||||
|
||||
title_match = re.search(r'<meta property="og:title" content="(.*?)">', content)
|
||||
if title_match:
|
||||
title = title_match.group(1).split(' - ')[0]
|
||||
|
||||
pic_match = re.search(r'<meta property="og:image" content="(.*?)">', content)
|
||||
if pic_match:
|
||||
pic = pic_match.group(1)
|
||||
|
||||
vod_play_from_list = []
|
||||
vod_play_url_list = []
|
||||
pat = r'(/api/m3u8/p/[a-zA-Z0-9]+\.m3u8)'
|
||||
matches = re.findall(pat, content)
|
||||
unique_urls = []
|
||||
seen = set()
|
||||
|
||||
for m in matches:
|
||||
full_url = m if m.startswith('http') else f"{self.host}{m}"
|
||||
if full_url not in seen:
|
||||
seen.add(full_url)
|
||||
unique_urls.append(full_url)
|
||||
|
||||
for index, u in enumerate(unique_urls):
|
||||
vod_play_from_list.append(f"线路{index + 1}")
|
||||
vod_play_url_list.append(u)
|
||||
|
||||
vod = {
|
||||
'vod_id': tid,
|
||||
'vod_name': title,
|
||||
'vod_pic': pic,
|
||||
'type_name': '',
|
||||
'vod_year': '',
|
||||
'vod_area': '',
|
||||
'vod_remarks': '',
|
||||
'vod_actor': '',
|
||||
'vod_director': '',
|
||||
'vod_content': '',
|
||||
'vod_play_from': '$$$'.join(vod_play_from_list),
|
||||
'vod_play_url': '$$$'.join(vod_play_url_list)
|
||||
}
|
||||
return {'list': [vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
url = f'{self.host}/search/{key}?page={pg}'
|
||||
try:
|
||||
res = self.fetch(url, headers=self.headers)
|
||||
return {'list': self.parse_list(res.text)}
|
||||
except:
|
||||
return {'list': []}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': id,
|
||||
'header': {
|
||||
'User-Agent': self.headers['User-Agent']
|
||||
}
|
||||
}
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def parse_list(self, html):
|
||||
videos = []
|
||||
d = pq(html)
|
||||
items = d('a[href^="/movie/detail/"]')
|
||||
seen_ids = set()
|
||||
for item in items.items():
|
||||
href = item.attr('href')
|
||||
if href in seen_ids:
|
||||
continue
|
||||
title = item.attr('title')
|
||||
if not title:
|
||||
title = item.find('span').text() or item.text()
|
||||
img_tag = item.find('img')
|
||||
pic = img_tag.attr('data-src') or img_tag.attr('src') or ""
|
||||
remarks = item.find('.duration').text()
|
||||
if href and title:
|
||||
seen_ids.add(href)
|
||||
videos.append({
|
||||
'vod_id': href,
|
||||
'vod_name': title.strip(),
|
||||
'vod_pic': pic,
|
||||
'vod_remarks': remarks
|
||||
})
|
||||
return videos
|
||||
@@ -8,7 +8,7 @@ from base.spider import Spider as BaseSpider
|
||||
|
||||
class Spider(BaseSpider):
|
||||
def init(self, extend=""):
|
||||
self.host = "https://maihaolian.com"
|
||||
self.host = "https://www.ht10010.com"
|
||||
self.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
@@ -23,11 +23,11 @@ class Spider(BaseSpider):
|
||||
{'type_id': "/label/qq", 'type_name': "腾讯VIP精选"},
|
||||
{'type_id': "/label/bli", 'type_name': "B站VIP精选"},
|
||||
{'type_id': "/label/youku", 'type_name': "优酷VIP精选"},
|
||||
{"type_id": "5", "type_name": "红果短剧"},
|
||||
{"type_id": "2", "type_name": "电视剧"},
|
||||
{"type_id": "1", "type_name": "电影"},
|
||||
{"type_id": "4", "type_name": "动漫"},
|
||||
{"type_id": "3", "type_name": "综艺"},
|
||||
{"type_id": "5", "type_name": "热门短剧"},
|
||||
], "filters": self._build_filters()}
|
||||
|
||||
def _build_filters(self):
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import urllib.parse
|
||||
import re
|
||||
from lxml import etree
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
def getName(self):
|
||||
return "禁片天堂"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
cateManual = {
|
||||
"中文": "278",
|
||||
"巨乳": "15",
|
||||
"熟女": "95",
|
||||
"騎乘位": "74",
|
||||
"口交": "34",
|
||||
"癡女": "75",
|
||||
"潮吹": "32",
|
||||
"企劃片": "84",
|
||||
"美尻": "156",
|
||||
"打手槍": "98",
|
||||
"戲劇、連續劇": "58",
|
||||
"制服": "19",
|
||||
"美腿": "157",
|
||||
"舔鮑": "122",
|
||||
"美乳": "166",
|
||||
"搭訕": "12",
|
||||
"妄想族": "184",
|
||||
"第一人稱視點": "167",
|
||||
"媽媽系": "193",
|
||||
"人妻・主婦": "26",
|
||||
"多種職業": "84",
|
||||
"羞辱": "163",
|
||||
"女教師": "131",
|
||||
"淫語": "151",
|
||||
"肉感": "136",
|
||||
"愛美臀": "111",
|
||||
"背後位": "178",
|
||||
"調教": "395",
|
||||
"處男": "23",
|
||||
"護士": "283",
|
||||
"修長": "147",
|
||||
"露內褲": "169",
|
||||
"絲襪": "115",
|
||||
"愛巨乳": "200",
|
||||
"眼鏡": "290",
|
||||
"超乳": "211",
|
||||
"顏面騎乘": "263",
|
||||
"惡作劇": "145",
|
||||
"義母": "144",
|
||||
"淫亂・過激系": "63",
|
||||
"愛美腿": "11",
|
||||
"爆乳": "483",
|
||||
"女上司": "137",
|
||||
"正太": "415",
|
||||
"穿衣幹砲": "179",
|
||||
"緊身皮衣": "304",
|
||||
"學園": "421",
|
||||
"空姐": "132",
|
||||
"粉絲感謝祭": "190",
|
||||
"背面騎乗位": "646",
|
||||
"秘書": "363",
|
||||
"女主播": "106",
|
||||
"反向搭訕": "305",
|
||||
"健身教練": "233",
|
||||
"部下・同僚": "150",
|
||||
"舞蹈": "130",
|
||||
"緊身衣激凸": "321",
|
||||
"3D影片": "508",
|
||||
"早洩": "403"
|
||||
}
|
||||
result = {'class': [{'type_name': k, 'type_id': v} for k, v in cateManual.items()]}
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
result = {}
|
||||
url = f'https://jptt.tv/tag_list?tid={tid}&idx={pg}'
|
||||
try:
|
||||
rsp = self.fetch(url)
|
||||
root = etree.HTML(rsp.text)
|
||||
videos = root.xpath('//div[contains(@class,"oneVideo")]')
|
||||
vodList = []
|
||||
for video in videos:
|
||||
try:
|
||||
name_elements = video.xpath('.//h3/text()')
|
||||
if not name_elements:
|
||||
continue
|
||||
name = name_elements[0].strip()
|
||||
|
||||
img_elements = video.xpath('.//img/@src')
|
||||
if not img_elements:
|
||||
continue
|
||||
img = img_elements[0]
|
||||
if not img.startswith('http'):
|
||||
img = 'https://jptt.tv' + img
|
||||
|
||||
desc_elements = video.xpath('.//p[contains(@class,"p_duration")]/text()')
|
||||
desc = desc_elements[0].strip() if desc_elements else ''
|
||||
|
||||
link_elements = video.xpath('.//a/@href')
|
||||
if not link_elements:
|
||||
continue
|
||||
link = link_elements[0]
|
||||
|
||||
vodList.append({
|
||||
"vod_name": name,
|
||||
"vod_pic": img,
|
||||
"vod_remarks": desc,
|
||||
"vod_id": link
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[categoryContent video parse error]: {e}")
|
||||
continue
|
||||
|
||||
result['list'] = vodList
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
except Exception as e:
|
||||
print(f"[categoryContent fetch error]: {e}")
|
||||
result['list'] = []
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 0
|
||||
result['limit'] = 0
|
||||
result['total'] = 0
|
||||
return result
|
||||
|
||||
def detailContent(self, array):
|
||||
tid = array[0]
|
||||
url = tid if tid.startswith('http') else f'https://jptt.tv{tid}'
|
||||
try:
|
||||
rsp = self.fetch(url)
|
||||
root = etree.HTML(rsp.text)
|
||||
|
||||
title_elements = root.xpath('//h1[@class="h1_title"]/text()')
|
||||
title = title_elements[0].strip() if title_elements else "未知标题"
|
||||
|
||||
pic_elements = root.xpath('//video/@poster')
|
||||
pic = pic_elements[0] if pic_elements else ""
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = 'https://jptt.tv' + pic
|
||||
|
||||
desc_elements = root.xpath('//div[contains(@class,"info_original")]//p/text()')
|
||||
desc = desc_elements[0].strip() if desc_elements else title
|
||||
|
||||
play_url = self.extractVideoUrl(rsp.text)
|
||||
|
||||
vod = {
|
||||
"vod_id": tid,
|
||||
"vod_name": title,
|
||||
"vod_pic": pic,
|
||||
"vod_content": desc,
|
||||
"vod_play_from": "注意身体",
|
||||
"vod_play_url": "多看少打卡$" + play_url
|
||||
}
|
||||
return {'list': [vod]}
|
||||
except Exception as e:
|
||||
print(f"[detailContent error]: {e}")
|
||||
return {'list': []}
|
||||
|
||||
def extractVideoUrl(self, html):
|
||||
try:
|
||||
source_match = re.search(r'<source\s+src="([^"]+)"', html)
|
||||
if source_match:
|
||||
video_url = source_match.group(1)
|
||||
if video_url.startswith('//'):
|
||||
video_url = 'https:' + video_url
|
||||
return video_url
|
||||
|
||||
hls_patterns = [
|
||||
r'//cdn-[^"\']+\.m3u8[^"\']*',
|
||||
r'https?://[^"\']+\.m3u8[^"\']*',
|
||||
r'/hlsredirect/[^"\']+\.m3u8'
|
||||
]
|
||||
for pattern in hls_patterns:
|
||||
matches = re.findall(pattern, html)
|
||||
if matches:
|
||||
for match in matches:
|
||||
if match.startswith('//'):
|
||||
return 'https:' + match
|
||||
elif match.startswith('http'):
|
||||
return match
|
||||
else:
|
||||
return 'https://jptt.tv' + match
|
||||
|
||||
js_patterns = [
|
||||
r'src\s*:\s*["\']([^"\']+\.m3u8[^"\']*)["\']',
|
||||
r'url\s*:\s*["\']([^"\']+\.m3u8[^"\']*)["\']',
|
||||
r'file\s*:\s*["\']([^"\']+\.m3u8[^"\']*)["\']'
|
||||
]
|
||||
for pattern in js_patterns:
|
||||
match = re.search(pattern, html)
|
||||
if match:
|
||||
video_url = match.group(1)
|
||||
if video_url.startswith('//'):
|
||||
return 'https:' + video_url
|
||||
elif video_url.startswith('http'):
|
||||
return video_url
|
||||
else:
|
||||
return 'https://jptt.tv' + video_url
|
||||
|
||||
all_m3u8 = re.findall(r'["\'](https?://[^"\']+\.m3u8[^"\']*)["\']', html)
|
||||
if all_m3u8:
|
||||
return all_m3u8[0]
|
||||
except Exception as e:
|
||||
print(f"[extractVideoUrl error]: {e}")
|
||||
|
||||
return "https://cdn-mso2.jptt1.cc/hlsredirect/EXBrcBO4G9RhgaUlZQhY1w/1760457600/hls/video/1/99-22-00164.3gp/index.m3u8"
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
result = {}
|
||||
url = f'https://jptt.tv/search?kw={urllib.parse.quote(key)}'
|
||||
try:
|
||||
rsp = self.fetch(url)
|
||||
root = etree.HTML(rsp.text)
|
||||
videos = root.xpath('//div[contains(@class,"oneVideo")]')
|
||||
vodList = []
|
||||
for video in videos:
|
||||
try:
|
||||
name_elements = video.xpath('.//h3/text()')
|
||||
if not name_elements:
|
||||
continue
|
||||
name = name_elements[0].strip()
|
||||
|
||||
img_elements = video.xpath('.//img/@src')
|
||||
if not img_elements:
|
||||
continue
|
||||
img = img_elements[0]
|
||||
if not img.startswith('http'):
|
||||
img = 'https://jptt.tv' + img
|
||||
|
||||
desc_elements = video.xpath('.//p[contains(@class,"p_duration")]/text()')
|
||||
desc = desc_elements[0].strip() if desc_elements else ''
|
||||
|
||||
link_elements = video.xpath('.//a/@href')
|
||||
if not link_elements:
|
||||
continue
|
||||
link = link_elements[0]
|
||||
|
||||
vodList.append({
|
||||
"vod_name": name,
|
||||
"vod_pic": img,
|
||||
"vod_remarks": desc,
|
||||
"vod_id": link
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[searchContent video parse error]: {e}")
|
||||
continue
|
||||
|
||||
result['list'] = vodList
|
||||
except Exception as e:
|
||||
print(f"[searchContent fetch error]: {e}")
|
||||
result['list'] = []
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
result = {}
|
||||
if flag == "注意身体":
|
||||
try:
|
||||
if id.startswith('http') and '.m3u8' in id:
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = id
|
||||
else:
|
||||
url = id if id.startswith('http') else f'https://jptt.tv{id}'
|
||||
rsp = self.fetch(url)
|
||||
play_url = self.extractVideoUrl(rsp.text)
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = play_url
|
||||
except Exception as e:
|
||||
print(f"[playerContent error]: {e}")
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = "https://cdn-mso2.jptt1.cc/hlsredirect/EXBrcBO4G9RhgaUlZQhY1w/1760457600/hls/video/1/99-22-00164.3gp/index.m3u8"
|
||||
|
||||
result["header"] = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36",
|
||||
"Referer": "https://jptt.tv/",
|
||||
"Origin": "https://jptt.tv"
|
||||
}
|
||||
return result
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,218 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
import sys
|
||||
from urllib.parse import urlparse
|
||||
import base64
|
||||
from pyquery import PyQuery as pq
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host=self.gethost()
|
||||
self.headers.update({'referer': f'{self.host}/'})
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
headers = {
|
||||
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="130", "Google Chrome";v="130"',
|
||||
'sec-ch-ua-platform': '"Android"',
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
data=self.getpq()
|
||||
result = {}
|
||||
classes = []
|
||||
filters = {"1": {"name": "类型","key": "tid","value": [{"n": "喜剧","v": 6},{"n": "爱情","v": 7},{"n": "恐怖","v": 8},{"n": "动作","v": 9},{"n": "科幻","v": 10},{"n": "战争","v": 11},{"n": "犯罪","v": 12},{"n": "动画","v": 13},{"n": "奇幻","v": 14},{"n": "剧情","v": 15},{"n": "冒险","v": 16},{"n": "悬疑","v": 17},{"n": "惊悚","v": 18},{"n": "其它","v": 19}]},"2": {"name": "类型","key": "tid","value": [{"n": "大陆剧","v": 20},{"n": "港剧","v": 21},{"n": "韩剧","v": 22},{"n": "美剧","v": 23},{"n": "日剧","v": 24},{"n": "英剧","v": 25},{"n": "台剧","v": 26},{"n": "其它","v": 27}]}}
|
||||
for k in data('.top_bar.clearfix a').items():
|
||||
j = k.attr('href')
|
||||
if j and 'list' in j:
|
||||
id = re.search(r'\d+', j).group(0)
|
||||
classes.append({
|
||||
'type_name': k.text(),
|
||||
'type_id': id
|
||||
})
|
||||
result['class'] = classes
|
||||
result['filters'] = filters
|
||||
result['list'] = self.getlist(data('.grid_box ul li'))
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
data=self.getpq(f"/list/{extend.get('tid',tid)}-{pg}.html")
|
||||
result = {}
|
||||
result['list'] = self.getlist(data('.grid_box ul li'))
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
data=self.getpq(ids[0])
|
||||
vod = {
|
||||
'vod_remarks': data('.grid_box.v_info_box p').text(),
|
||||
'vod_content': data('.p_txt.show_part').text().split('\n')[0],
|
||||
}
|
||||
n=list(data('.play_from ul li').items())
|
||||
p=list(data('ul.play_list li').items())
|
||||
ns,ps=[],[]
|
||||
for i,j in enumerate(n):
|
||||
ns.append(j.text())
|
||||
ps.append('#'.join([f"{k.text()}${k.attr('href')}" for k in list(p[i]('a').items())[::-1]]))
|
||||
vod['vod_play_from']='$$$'.join(ns)
|
||||
vod['vod_play_url']='$$$'.join(ps)
|
||||
return {'list':[vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
pass
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
data=self.getpq(id)
|
||||
try:
|
||||
surl=data('section[style*="padding-top"] iframe').eq(0).attr('src')
|
||||
sd=pq(self.fetch(surl,headers=self.headers).text)('body script').html()
|
||||
jdata=self.extract_values(sd)
|
||||
jdata['key']=self.hhh(jdata['key'])
|
||||
parsed_url = urlparse(surl)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
headers = {
|
||||
'accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
'cache-control': 'no-cache',
|
||||
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'dnt': '1',
|
||||
'origin': durl,
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': f'{surl}',
|
||||
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="130", "Google Chrome";v="130"',
|
||||
'sec-ch-ua-mobile': '?1',
|
||||
'sec-ch-ua-platform': '"Android"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'sec-fetch-storage-access': 'active',
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
|
||||
'x-requested-with': 'XMLHttpRequest',
|
||||
}
|
||||
jjb=self.post(f"{durl}/api.php",headers=headers,data=jdata).json()
|
||||
url,p=jjb['url'],0
|
||||
except Exception as e:
|
||||
self.log(f"失败: {e}")
|
||||
url,p=f'{self.host}{id}',1
|
||||
phd={
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
|
||||
'sec-ch-ua-platform': '"Android"',
|
||||
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="130", "Google Chrome";v="130"',
|
||||
'sec-fetch-dest': 'video',
|
||||
'referer': f'{self.host}/',
|
||||
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
}
|
||||
return {'parse': p, 'url': url, 'header': phd}
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def liveContent(self, url):
|
||||
pass
|
||||
|
||||
def gethost(self):
|
||||
data=pq(self.fetch("http://shapp.us",headers=self.headers).text)
|
||||
for i in data('.content-top ul li').items():
|
||||
h=i('a').attr('href')
|
||||
if h:
|
||||
data = self.fetch(h, headers=self.headers, timeout=5)
|
||||
if data.status_code == 200:
|
||||
return h
|
||||
|
||||
def extract_values(self, text):
|
||||
url_match = re.search(r'var url = "([^"]+)"', text)
|
||||
url = url_match.group(1) if url_match else None
|
||||
t_match = re.search(r'var t = "([^"]+)"', text)
|
||||
t = t_match.group(1) if t_match else None
|
||||
key_match = re.search(r'var key = hhh\("([^"]+)"\)', text)
|
||||
key_param = key_match.group(1) if key_match else None
|
||||
act_match = re.search(r'var act = "([^"]+)"', text)
|
||||
act = act_match.group(1) if act_match else None
|
||||
play_match = re.search(r'var play = "([^"]+)"', text)
|
||||
play = play_match.group(1) if play_match else None
|
||||
return {
|
||||
"url": url,
|
||||
"t": t,
|
||||
"key": key_param,
|
||||
"act": act,
|
||||
"play": play
|
||||
}
|
||||
|
||||
def getlist(self,data):
|
||||
videos = []
|
||||
for i in data.items():
|
||||
videos.append({
|
||||
'vod_id': i('a').attr('href'),
|
||||
'vod_name': i('a').attr('title'),
|
||||
'vod_pic': i('a img').attr('data-original'),
|
||||
'vod_remarks': i('.v_note').text()
|
||||
})
|
||||
return videos
|
||||
|
||||
def getpq(self, path=''):
|
||||
data=self.fetch(f"{self.host}{path}",headers=self.headers).text
|
||||
try:
|
||||
return pq(data)
|
||||
except Exception as e:
|
||||
print(f"{str(e)}")
|
||||
return pq(data.encode('utf-8'))
|
||||
|
||||
def hhh(self, t):
|
||||
ee = {
|
||||
"0Oo0o0O0": "a", "1O0bO001": "b", "2OoCcO2": "c", "3O0dO0O3": "d",
|
||||
"4OoEeO4": "e", "5O0fO0O5": "f", "6OoGgO6": "g", "7O0hO0O7": "h",
|
||||
"8OoIiO8": "i", "9O0jO0O9": "j", "0OoKkO0": "k", "1O0lO0O1": "l",
|
||||
"2OoMmO2": "m", "3O0nO0O3": "n", "4OoOoO4": "o", "5O0pO0O5": "p",
|
||||
"6OoQqO6": "q", "7O0rO0O7": "r", "8OoSsO8": "s", "9O0tO0O9": "t",
|
||||
"0OoUuO0": "u", "1O0vO0O1": "v", "2OoWwO2": "w", "3O0xO0O3": "x",
|
||||
"4OoYyO4": "y", "5O0zO0O5": "z", "0OoAAO0": "A", "1O0BBO1": "B",
|
||||
"2OoCCO2": "C", "3O0DDO3": "D", "4OoEEO4": "E", "5O0FFO5": "F",
|
||||
"6OoGGO6": "G", "7O0HHO7": "H", "8OoIIO8": "I", "9O0JJO9": "J",
|
||||
"0OoKKO0": "K", "1O0LLO1": "L", "2OoMMO2": "M", "3O0NNO3": "N",
|
||||
"4OoOOO4": "O", "5O0PPO5": "P", "6OoQQO6": "Q", "7O0RRO7": "R",
|
||||
"8OoSSO8": "S", "9O0TTO9": "T", "0OoUO0": "U", "1O0VVO1": "V",
|
||||
"2OoWWO2": "W", "3O0XXO3": "X", "4OoYYO4": "Y", "5O0ZZO5": "Z"
|
||||
}
|
||||
n = ""
|
||||
o = base64.b64decode(t).decode('utf-8', errors='replace')
|
||||
i = 0
|
||||
while i < len(o):
|
||||
l = o[i]
|
||||
found = False
|
||||
for key, value in ee.items():
|
||||
if o[i:i + len(key)] == key:
|
||||
l = value
|
||||
i += len(key) - 1
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
pass
|
||||
n += l
|
||||
i += 1
|
||||
return n
|
||||
@@ -0,0 +1,14 @@
|
||||
哈TV18+,#genre#
|
||||
彩虹R,http://58.99.33.2:1935/liveedge2/TnIsFYAs_408_1/chunklist_w114642605.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
松視1台,http://58.99.33.2:1935/liveedge2/TnIsFYAs_405_1/chunklist_w2097694717.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&=&dr=123&dt=&cust_type=NE
|
||||
松視2台,http://58.99.33.2:1935/liveedge2/TnIsFYAs_406_1/chunklist_w1350191092.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
松視3台,http://58.99.33.2:1935/liveedge2/TnIsFYAs_407_1/chunklist_w452892769.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
松視4台,http://58.99.33.2:1935/liveedge2/TnIsFYAs_404_1/chunklist_w1221017520.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
玩家,http://58.99.33.2:1935/liveedge2/TnIsFYAs_401_1/chunklist_w452892769.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
happy,http://58.99.33.2:1935/liveedge2/TnIsFYAs_402_1/chunklist_w452892769.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
HOT,http://58.99.33.2:1935/liveedge2/TnIsFYAs_403_1/chunklist_w452892769.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
乐活,http://58.99.33.2:1935/liveedge2/TnIsFYAs_410_1/chunklist_w452892769.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
潘多拉完美,http://58.99.33.2:1935/liveedge2/TnIsFYAs_411_1/chunklist_w452892769.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
潘多拉粉红,http://58.99.33.2:1935/liveedge2/TnIsFYAs_412_1/chunklist_w452892769.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
惊艳电影台,http://58.99.33.2:1935/liveedge2/TnIsFYAs_413_1/chunklist_w452892769.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
香蕉台,http://58.99.33.2:1935/liveedge2/TnIsFYAs_414_1/chunklist_w452892769.m3u8?checkCode=37050688asdfsdfsadf&aa=60999&as=abcdefg&mmmm=&dr=123&dt=&cust_type=NE
|
||||
@@ -0,0 +1,231 @@
|
||||
# coding=utf-8
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
import sys
|
||||
import urllib.parse
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# 禁用SSL证书验证警告
|
||||
import urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def getName(self):
|
||||
return "永乐视频"
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = "https://www.ylys.tv/"
|
||||
self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': self.host}
|
||||
self.session = requests.Session()
|
||||
self.session.verify = False
|
||||
self.session.headers.update(self.headers)
|
||||
|
||||
def fetch(self, url, timeout=30):
|
||||
try:
|
||||
response = self.session.get(url, timeout=timeout, verify=False)
|
||||
response.encoding = response.encoding if response.encoding != 'ISO-8859-1' else 'UTF-8'
|
||||
return response
|
||||
except:
|
||||
return None
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {
|
||||
"class": [{'type_id': str(i), 'type_name': t} for i, t in enumerate(['电影', '剧集', '综艺', '动漫'], 1)],
|
||||
"filters": self._get_filters(),
|
||||
"list": []
|
||||
}
|
||||
rsp = self.fetch(self.host)
|
||||
if rsp and rsp.status_code == 200:
|
||||
result['list'] = self._extract_videos(rsp.text, 20)
|
||||
return result
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
result = {"list": [], "page": int(pg), "pagecount": 99, "limit": 20, "total": 1980}
|
||||
url = f"{self.host}/vodtype/{tid}/page/{pg}/" if int(pg) > 1 else f"{self.host}/vodtype/{tid}/"
|
||||
rsp = self.fetch(url)
|
||||
if rsp and rsp.status_code == 200:
|
||||
result['list'] = self._extract_videos(rsp.text)
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg=1):
|
||||
result = {"list": []}
|
||||
search_key = urllib.parse.quote(key)
|
||||
url = f"{self.host}/vodsearch/{search_key}-------------/page/{pg}/" if int(pg) > 1 else f"{self.host}/vodsearch/{search_key}-------------/"
|
||||
rsp = self.fetch(url)
|
||||
if rsp and rsp.status_code == 200:
|
||||
result['list'] = self._extract_search_results(rsp.text)
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
result = {"list": []}
|
||||
vid = ids[0]
|
||||
rsp = self.fetch(f"{self.host}/voddetail/{vid}/")
|
||||
if not rsp or rsp.status_code != 200:
|
||||
return result
|
||||
|
||||
html = rsp.text
|
||||
play_from, play_url = self._extract_play_info(html, vid)
|
||||
|
||||
if play_from:
|
||||
result['list'] = [{
|
||||
'vod_id': vid,
|
||||
'vod_name': self._extract_title(html),
|
||||
'vod_pic': self._extract_pic(html),
|
||||
'vod_content': self._extract_desc(html),
|
||||
'vod_remarks': self._extract_remarks(html),
|
||||
'vod_play_from': "$$$".join(play_from),
|
||||
'vod_play_url': "$$$".join(play_url)
|
||||
}]
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
result = {"parse": 1, "playUrl": "", "url": ""}
|
||||
if "-" not in id:
|
||||
return result
|
||||
|
||||
rsp = self.fetch(f"{self.host}/play/{id}/")
|
||||
if not rsp or rsp.status_code != 200:
|
||||
return result
|
||||
|
||||
real_url_match = re.search(r'var player_aaaa=.*?"url":"([^"]+\.m3u8)"', rsp.text, re.S | re.I)
|
||||
if real_url_match:
|
||||
real_url = real_url_match.group(1).replace(r'\u002F', '/').replace(r'\/', '/')
|
||||
result["parse"] = 0
|
||||
result["url"] = real_url
|
||||
else:
|
||||
result["url"] = f"{self.host}/play/{id}/"
|
||||
return result
|
||||
|
||||
def _get_filters(self):
|
||||
return {
|
||||
"1": [{"key": "class", "name": "类型", "value": [
|
||||
{"n": "全部", "v": ""}, {"n": "动作片", "v": "6"}, {"n": "喜剧片", "v": "7"},
|
||||
{"n": "爱情片", "v": "8"}, {"n": "科幻片", "v": "9"}, {"n": "恐怖片", "v": "11"}
|
||||
]}],
|
||||
"2": [{"key": "class", "name": "类型", "value": [
|
||||
{"n": "全部", "v": ""}, {"n": "国产剧", "v": "13"}, {"n": "港台剧", "v": "14"},
|
||||
{"n": "日剧", "v": "15"}, {"n": "韩剧", "v": "33"}, {"n": "欧美剧", "v": "16"}
|
||||
]}],
|
||||
"3": [{"key": "class", "name": "类型", "value": [
|
||||
{"n": "全部", "v": ""}, {"n": "内地综艺", "v": "27"}, {"n": "港台综艺", "v": "28"},
|
||||
{"n": "日本综艺", "v": "29"}, {"n": "韩国综艺", "v": "36"}
|
||||
]}],
|
||||
"4": [{"key": "class", "name": "类型", "value": [
|
||||
{"n": "全部", "v": ""}, {"n": "国产动漫", "v": "31"}, {"n": "日本动漫", "v": "32"},
|
||||
{"n": "欧美动漫", "v": "42"}, {"n": "其他动漫", "v": "43"}
|
||||
]}]
|
||||
}
|
||||
|
||||
def _extract_videos(self, html, limit=0):
|
||||
videos = []
|
||||
pattern = r'<a href="/voddetail/(\d+)/".*?title="([^"]+)".*?<div class="module-item-note">([^<]+)</div>.*?data-original="([^"]+)"'
|
||||
for vid, title, remark, pic in re.findall(pattern, html, re.S | re.I):
|
||||
videos.append({
|
||||
'vod_id': vid.strip(),
|
||||
'vod_name': title.strip(),
|
||||
'vod_pic': (self.host + pic if pic.startswith('/') else pic).strip(),
|
||||
'vod_remarks': remark.strip()
|
||||
})
|
||||
return videos[:limit] if limit and len(videos) > limit else videos
|
||||
|
||||
def _extract_search_results(self, html):
|
||||
videos = []
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
for item in soup.select('.module-card-item'):
|
||||
link = item.select_one('a[href^="/voddetail/"]')
|
||||
if not link:
|
||||
continue
|
||||
|
||||
href = link.get('href', '')
|
||||
vid_match = re.search(r'/voddetail/(\d+)/', href)
|
||||
if not vid_match:
|
||||
continue
|
||||
|
||||
vid = vid_match.group(1)
|
||||
title_elem = item.select_one('.module-card-item-title strong')
|
||||
img_elem = item.select_one('img')
|
||||
pic = (img_elem.get('data-original') or img_elem.get('src')) if img_elem else ""
|
||||
note_elem = item.select_one('.module-item-note')
|
||||
|
||||
videos.append({
|
||||
'vod_id': vid,
|
||||
'vod_name': title_elem.get_text(strip=True) if title_elem else "",
|
||||
'vod_pic': self.host + pic if pic.startswith('/') else pic,
|
||||
'vod_remarks': note_elem.get_text(strip=True) if note_elem else ""
|
||||
})
|
||||
return videos
|
||||
|
||||
def _extract_play_info(self, html, vid):
|
||||
play_from, play_url = [], []
|
||||
line_pattern = r'<(?:div|a)[^>]*class="[^"]*module-tab-item[^"]*"[^>]*>(?:.*?<span>([^<]+)</span>.*?<small>(\d+)</small>|.*?<span>([^<]+)</span>.*?<small class="no">(\d+)</small>)</(?:div|a)>'
|
||||
|
||||
for match in re.findall(line_pattern, html, re.S | re.I):
|
||||
line_name = match[0] or match[2]
|
||||
if line_name in play_from:
|
||||
continue
|
||||
|
||||
play_from.append(line_name)
|
||||
line_id = self._get_line_id(html, vid, line_name)
|
||||
|
||||
ep_matches = re.findall(rf'<a class="module-play-list-link" href="/play/{vid}-{line_id}-(\d+)/"[^>]*>.*?<span>([^<]+)</span></a>', html, re.S | re.I)
|
||||
eps = [f"{ep_name.strip()}${vid}-{line_id}-{ep_num.strip()}" for ep_num, ep_name in ep_matches]
|
||||
play_url.append("#".join(eps))
|
||||
|
||||
return play_from, play_url
|
||||
|
||||
def _get_line_id(self, html, vid, line_name):
|
||||
line_id_match = re.search(rf'<a[^>]*href="/play/{vid}-(\d+)-1/"[^>]*>.*?<span>{re.escape(line_name)}</span>', html, re.S | re.I)
|
||||
if line_id_match:
|
||||
return line_id_match.group(1)
|
||||
|
||||
line_id_map = {"全球3线": "3", "大陆0线": "1", "大陆3线": "4", "大陆5线": "2", "大陆6线": "3"}
|
||||
return line_id_map.get(line_name, "1")
|
||||
|
||||
def _extract_title(self, html):
|
||||
title_match = re.search(r'<meta property="og:title" content="([^"]+)-[^-]+$"', html, re.S | re.I)
|
||||
return title_match.group(1).strip() if title_match else ""
|
||||
|
||||
def _extract_pic(self, html):
|
||||
pic_match = re.search(r'<meta property="og:image" content="([^"]+)"', html, re.S | re.I)
|
||||
pic = pic_match.group(1).strip() if pic_match else ""
|
||||
return self.host + pic if pic and pic.startswith('/') else pic
|
||||
|
||||
def _extract_desc(self, html):
|
||||
desc_match = re.search(r'<meta property="og:description" content="([^"]+)"', html, re.S | re.I)
|
||||
return desc_match.group(1).strip() if desc_match else "暂无简介"
|
||||
|
||||
def _extract_remarks(self, html):
|
||||
year_match = re.search(r'<a title="(\d+)" href="/vodshow/\d+-----------\1/">', html, re.S | re.I)
|
||||
year = year_match.group(1) if year_match else "未知年份"
|
||||
|
||||
area_match = re.search(r'<a title="([^"]+)" href="/vodshow/\d+-%E5%A2%A8%E8%A5%BF%E5%93%A5----------/">', html, re.S | re.I)
|
||||
area = area_match.group(1) if area_match else "未知产地"
|
||||
|
||||
type_match = re.search(r'vod_class":"([^"]+)"', html, re.S | re.I)
|
||||
type_str = type_match.group(1).replace(",", "/") if type_match else "未知类型"
|
||||
|
||||
return f"{year} | {area} | {type_str}"
|
||||
|
||||
# 本地测试
|
||||
if __name__ == "__main__":
|
||||
spider = Spider()
|
||||
spider.init()
|
||||
|
||||
# 测试详情页解析
|
||||
detail_result = spider.detailContent(["86027"])
|
||||
if detail_result['list']:
|
||||
detail = detail_result['list'][0]
|
||||
print(f"视频名称: {detail.get('vod_name', '未知')}")
|
||||
|
||||
# 测试搜索功能
|
||||
search_result = spider.searchContent("仙逆", False, 1)
|
||||
print(f"搜索结果数量: {len(search_result['list'])}")
|
||||
|
||||
# 测试播放功能
|
||||
play_result = spider.playerContent("", "86027-5-1", {})
|
||||
print(f"播放URL: {play_result.get('url', '')}")
|
||||
@@ -0,0 +1,739 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
from urllib.parse import quote, unquote, urljoin
|
||||
from pyquery import PyQuery as pq
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def init(self, extend=""):
|
||||
"""初始化 适配配置"""
|
||||
# 修复:删除末尾空格
|
||||
self.host = "https://www.mtyy1.com"
|
||||
self.headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 11; MI 11 Build/RKQ1.201022.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.159 Mobile Safari/537.36 TVBox/1.0',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Connection': 'keep-alive',
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'Referer': self.host
|
||||
}
|
||||
self.source_map = {"NBY": "高清NB源", "1080zyk": "超清YZ源", "ffm3u8": "极速FF源", "lzm3u8": "稳定LZ源", "yzzy": "YZ源"}
|
||||
self.ua_list = [
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0',
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1'
|
||||
]
|
||||
|
||||
def getName(self):
|
||||
return "麦田影院"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
"""判断是否为直接播放格式"""
|
||||
video_exts = ['.mp4', '.m3u8', '.flv', '.avi', '.mov', '.rmvb', '.m3u8']
|
||||
return any(ext in url.lower() for ext in video_exts)
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def fix_encoding(self, text):
|
||||
"""加强版编码修复,适配中文显示"""
|
||||
if not text:
|
||||
return ""
|
||||
try:
|
||||
# 处理可能的乱码情况
|
||||
if isinstance(text, bytes):
|
||||
try:
|
||||
text = text.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
text = text.decode('gbk', errors='ignore')
|
||||
|
||||
# 处理Unicode转义序列
|
||||
if '\\u' in text:
|
||||
try:
|
||||
text = text.encode('utf-8').decode('unicode_escape')
|
||||
except:
|
||||
pass
|
||||
|
||||
garbled_replace = {
|
||||
'\u00e4\u00b8\u00ad': '中', '\u00e6\u0096\u0087': '文',
|
||||
'\u00e5\u00bd\u00b1': '影', '\u00e8\u00a7\u0086': '视',
|
||||
'\u00e9\u00a2\u0091': '频', '\u00c3\u00a4': 'ä', '\u00c3\u00b6': 'ö'
|
||||
}
|
||||
for garbled, correct in garbled_replace.items():
|
||||
text = text.replace(garbled, correct)
|
||||
|
||||
# 清理不可见字符
|
||||
text = re.sub(r'[\x00-\x1f\x7f]', '', text).strip()
|
||||
return text
|
||||
except Exception as e:
|
||||
self.log(f"编码修复异常: {str(e)}")
|
||||
return str(text) if text else ""
|
||||
|
||||
def fetch_with_encoding(self, url, **kwargs):
|
||||
"""带编码处理的请求方法,确保正确获取页面内容"""
|
||||
try:
|
||||
# 随机延迟,避免被识别为爬虫
|
||||
time.sleep(random.uniform(0.5, 1.5))
|
||||
|
||||
# 随机选择User-Agent
|
||||
headers = kwargs.get('headers', self.headers.copy())
|
||||
headers['User-Agent'] = random.choice(self.ua_list)
|
||||
kwargs['headers'] = headers
|
||||
|
||||
response = self.fetch(
|
||||
url,
|
||||
timeout=15,
|
||||
allow_redirects=True,
|
||||
**kwargs
|
||||
)
|
||||
response.encoding = 'utf-8'
|
||||
if response.status_code != 200 or len(response.text) < 1000:
|
||||
self.log(f"请求失败,状态码: {response.status_code},内容长度: {len(response.text)}")
|
||||
raise Exception(f"页面内容无效或获取失败")
|
||||
return response
|
||||
except Exception as e:
|
||||
self.log(f"请求 {url} 出错: {str(e)}")
|
||||
raise
|
||||
|
||||
def getpq(self, text):
|
||||
"""安全的pyquery解析,处理可能的解析错误"""
|
||||
try:
|
||||
return pq(text)
|
||||
except Exception as e:
|
||||
self.log(f"PyQuery 解析失败: {str(e)}")
|
||||
clean_text = re.sub(r'[^\x20-\x7e\u4e00-\u9fff]', '', text)
|
||||
return pq(clean_text) if clean_text else pq('')
|
||||
|
||||
def homeContent(self, filter):
|
||||
"""获取首页内容和分类"""
|
||||
try:
|
||||
response = self.fetch_with_encoding(self.host)
|
||||
doc = self.getpq(response.text)
|
||||
|
||||
result = {}
|
||||
classes = []
|
||||
nav_items = doc('div.head-nav a[href*="/vodtype/"], .this-wap a[href*="/vodtype/"]').items()
|
||||
seen_cate = set()
|
||||
for item in nav_items:
|
||||
cate_text = self.fix_encoding(item.text().strip())
|
||||
cate_href = item.attr('href')
|
||||
if not cate_text or not cate_href or '/vodtype/' not in cate_href:
|
||||
continue
|
||||
cate_id = re.search(r'/vodtype/(\d+)\.html', cate_href)
|
||||
if not cate_id or cate_id.group(1) in seen_cate:
|
||||
continue
|
||||
cate_id = cate_id.group(1)
|
||||
seen_cate.add(cate_id)
|
||||
classes.append({
|
||||
'type_name': cate_text,
|
||||
'type_id': cate_id
|
||||
})
|
||||
|
||||
videos = []
|
||||
seen_ids = set()
|
||||
# 修复:精准选择器,确保抓取首页影片
|
||||
video_boxes = doc('.public-list-box.public-pic-b, .wap-diy-vod-a .public-list-box').items()
|
||||
for box in video_boxes:
|
||||
link = box.find('a.public-list-exp')
|
||||
if not link:
|
||||
continue
|
||||
vod_href = link.attr('href')
|
||||
vod_id = re.search(r'/voddetail/(\d+)\.html', vod_href)
|
||||
if not vod_id or vod_id.group(1) in seen_ids:
|
||||
continue
|
||||
vod_id = vod_id.group(1)
|
||||
seen_ids.add(vod_id)
|
||||
|
||||
img = link.find('img')
|
||||
# --- 修复标题:优先使用 a 标签的 title 属性或文本,避免使用 img.alt ---
|
||||
vod_title = self.fix_encoding(
|
||||
link.attr('title') or
|
||||
link.text().strip() or
|
||||
img.attr('alt') or ""
|
||||
)
|
||||
if not vod_title:
|
||||
continue
|
||||
|
||||
# --- 修复封面图:优先使用 data-src ---
|
||||
vod_pic = img.attr('data-src') or img.attr('src') or ""
|
||||
vod_pic = urljoin(self.host, vod_pic) if vod_pic else ""
|
||||
|
||||
vod_remarks = self.fix_encoding(
|
||||
box.find('.public-prt, .episode, .public-list-prb').text().strip() or
|
||||
(re.search(r'第\d+[集期]|更新至|完结|HD|超清', box.text()).group() if re.search(r'第\d+[集期]|更新至|完结|HD|超清', box.text()) else "")
|
||||
)
|
||||
videos.append({
|
||||
'vod_id': vod_id,
|
||||
'vod_name': vod_title,
|
||||
'vod_pic': vod_pic,
|
||||
'vod_year': '',
|
||||
'vod_remarks': vod_remarks
|
||||
})
|
||||
|
||||
result['class'] = classes
|
||||
result['list'] = videos
|
||||
return result
|
||||
except Exception as e:
|
||||
self.log(f"获取首页内容时出错: {e}")
|
||||
return {'class': [], 'list': []}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {'list': []}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
"""获取分类内容"""
|
||||
try:
|
||||
url = f"{self.host}/vodtype/{tid}.html"
|
||||
if int(pg) > 1:
|
||||
url = f"{self.host}/vodtype/{tid}-{pg}.html"
|
||||
|
||||
response = self.fetch_with_encoding(url)
|
||||
doc = self.getpq(response.text)
|
||||
|
||||
videos = []
|
||||
seen_ids = set()
|
||||
# 修复:精准选择器
|
||||
video_boxes = doc('.public-list-box.public-pic-b').items()
|
||||
for box in video_boxes:
|
||||
link = box.find('a.public-list-exp')
|
||||
if not link:
|
||||
continue
|
||||
vod_href = link.attr('href')
|
||||
vod_id = re.search(r'/voddetail/(\d+)\.html', vod_href)
|
||||
if not vod_id or vod_id.group(1) in seen_ids:
|
||||
continue
|
||||
vod_id = vod_id.group(1)
|
||||
seen_ids.add(vod_id)
|
||||
|
||||
img = link.find('img')
|
||||
# --- 修复标题:优先使用 a 标签的 title 属性或文本 ---
|
||||
vod_title = self.fix_encoding(
|
||||
link.attr('title') or
|
||||
link.text().strip() or
|
||||
img.attr('alt') or ""
|
||||
)
|
||||
# --- 修复封面图:优先使用 data-src ---
|
||||
vod_pic = urljoin(self.host, img.attr('data-src') or img.attr('src') or "")
|
||||
vod_remarks = self.fix_encoding(box.find('.public-prt, .public-list-prb').text().strip() or "")
|
||||
|
||||
if vod_title:
|
||||
videos.append({
|
||||
'vod_id': vod_id,
|
||||
'vod_name': vod_title,
|
||||
'vod_pic': vod_pic,
|
||||
'vod_year': '',
|
||||
'vod_remarks': vod_remarks
|
||||
})
|
||||
|
||||
result = {
|
||||
'list': videos,
|
||||
'page': pg,
|
||||
'pagecount': 9999,
|
||||
'limit': 80,
|
||||
'total': 999999
|
||||
}
|
||||
return result
|
||||
except Exception as e:
|
||||
self.log(f"获取分类内容时出错: {e}")
|
||||
return {'list': [], 'page': pg, 'pagecount': 1, 'limit': 80, 'total': 0}
|
||||
|
||||
def detailContent(self, ids):
|
||||
"""获取视频详情,修复播放源显示问题"""
|
||||
result = {"list": []}
|
||||
if not ids or len(ids) == 0:
|
||||
return result
|
||||
vod_id = ids[0]
|
||||
|
||||
try:
|
||||
# 先访问详情页获取基本信息
|
||||
detail_url = f"{self.host}/voddetail/{vod_id}.html"
|
||||
response = self.fetch_with_encoding(detail_url)
|
||||
doc = self.getpq(response.text)
|
||||
|
||||
vod_info = {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": self.fix_encoding(doc('h1.player-title-link').text().strip()),
|
||||
# --- 修复封面图:优先使用 data-src ---
|
||||
"vod_pic": urljoin(self.host, doc('.role-card img').attr('data-src') or doc('.role-card img').attr('src') or ""),
|
||||
"vod_year": self.fix_encoding(doc('.player-details a[href*="2025"]').text().strip() or doc('.player-details li:contains("年份")').text().split(':')[-1].strip()),
|
||||
"vod_remarks": self.fix_encoding(doc('.co3').text().strip() or "7.7分"),
|
||||
"vod_actor": self.fix_encoding(doc('.vod_data').attr('vod_actor') if doc('.vod_data') else ""),
|
||||
"vod_director": self.fix_encoding(doc('.vod_data').attr('vod_director') if doc('.vod_data') else ""),
|
||||
"vod_content": self.fix_encoding(doc('.card-text').text().strip()),
|
||||
"vod_play_from": "",
|
||||
"vod_play_url": ""
|
||||
}
|
||||
|
||||
# 尝试获取播放页URL
|
||||
play_page_url = None
|
||||
play_link = doc('.anthology-list-play a:first').attr('href')
|
||||
if play_link:
|
||||
play_page_url = urljoin(self.host, play_link)
|
||||
|
||||
# 如果无法获取播放页URL,使用默认URL
|
||||
if not play_page_url:
|
||||
play_page_url = f"{self.host}/vodplay/{vod_id}-1-1.html"
|
||||
|
||||
# 访问播放页获取播放源信息
|
||||
play_response = self.fetch_with_encoding(play_page_url)
|
||||
play_doc = self.getpq(play_response.text)
|
||||
|
||||
# 存储所有线路信息
|
||||
all_sources_data = {} # {source_name: [episode1$url1, episode2$url2], ...}
|
||||
|
||||
# --- 解析所有播放源 ---
|
||||
# 查找播放源选项卡
|
||||
tab_items = play_doc('a.vod-playerUrl[data-form]').items()
|
||||
|
||||
for tab in tab_items:
|
||||
data_form = tab.attr('data-form')
|
||||
source_name = self.fix_encoding(tab.text().replace('', '').strip())
|
||||
|
||||
# 标准化线路名称
|
||||
display_source_name = self.source_map.get(data_form, source_name)
|
||||
|
||||
# 查找对应的播放列表
|
||||
episodes = []
|
||||
# 获取当前选项卡的索引
|
||||
tab_index = list(play_doc('a.vod-playerUrl[data-form]')).index(tab[0])
|
||||
|
||||
# 找到对应的剧集列表
|
||||
episode_boxes = play_doc('.anthology-list-box').eq(tab_index)
|
||||
episode_links = episode_boxes.find('a').items()
|
||||
|
||||
for ep in episode_links:
|
||||
ep_title = self.fix_encoding(ep.text().strip())
|
||||
ep_href = ep.attr('href')
|
||||
if ep_title and ep_href:
|
||||
# 为每个播放源生成不同的URL(根据data-form参数)
|
||||
full_href = urljoin(self.host, ep_href)
|
||||
# 添加播放源标识到URL
|
||||
if '?' in full_href:
|
||||
full_href += f'&source={data_form}'
|
||||
else:
|
||||
full_href += f'?source={data_form}'
|
||||
episodes.append(f"{ep_title}${full_href}")
|
||||
|
||||
if episodes:
|
||||
all_sources_data[display_source_name] = '#'.join(episodes)
|
||||
|
||||
# --- 排序和优先使用YZ源 ---
|
||||
final_play_from = []
|
||||
final_play_url = []
|
||||
|
||||
yz_source_name = "超清YZ源"
|
||||
if yz_source_name in all_sources_data:
|
||||
final_play_from.append(yz_source_name)
|
||||
final_play_url.append(all_sources_data.pop(yz_source_name))
|
||||
|
||||
for src_name, src_urls in all_sources_data.items():
|
||||
final_play_from.append(src_name)
|
||||
final_play_url.append(src_urls)
|
||||
|
||||
if not final_play_from:
|
||||
default_ep = play_doc('.anthology-list-play a:first').attr('href')
|
||||
if default_ep:
|
||||
final_play_from.append("默认源")
|
||||
final_play_url.append(f"正片${urljoin(self.host, default_ep)}")
|
||||
|
||||
vod_info["vod_play_from"] = "$$$".join(final_play_from)
|
||||
vod_info["vod_play_url"] = "$$$".join(final_play_url)
|
||||
|
||||
result["list"].append(vod_info)
|
||||
return result
|
||||
except Exception as e:
|
||||
self.log(f"获取视频详情时出错: {e}")
|
||||
return {'list': []}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
"""搜索功能 - 带验证码处理"""
|
||||
result = {"list": [], "page": int(pg)}
|
||||
|
||||
try:
|
||||
# 编码搜索关键词
|
||||
encoded_key = quote(key)
|
||||
search_url = f"{self.host}/vodsearch/-------------.html?wd={encoded_key}"
|
||||
|
||||
if int(pg) > 1:
|
||||
search_url = f"{self.host}/vodsearch/-------------.html?wd={encoded_key}&page={pg}"
|
||||
|
||||
# 添加随机延迟,避免被识别为爬虫
|
||||
time.sleep(random.uniform(1.0, 2.5))
|
||||
|
||||
# 使用更真实的浏览器头
|
||||
headers = self.headers.copy()
|
||||
headers.update({
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
|
||||
'Connection': 'keep-alive',
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'Cache-Control': 'max-age=0',
|
||||
'TE': 'Trailers'
|
||||
})
|
||||
|
||||
response = self.fetch(search_url, headers=headers, timeout=15)
|
||||
|
||||
# 检查是否被重定向到验证码页面
|
||||
if response.url and "captcha" in response.url.lower():
|
||||
self.log("检测到验证码页面,尝试绕过...")
|
||||
# 尝试使用不同的方法绕过验证码
|
||||
return self.handle_captcha_search(key, pg)
|
||||
|
||||
# 检查响应内容是否包含验证码提示
|
||||
html_content = response.text
|
||||
if "验证码" in html_content or "captcha" in html_content.lower():
|
||||
self.log("页面包含验证码提示,尝试绕过...")
|
||||
return self.handle_captcha_search(key, pg)
|
||||
|
||||
# 正常解析搜索结果
|
||||
doc = self.getpq(html_content)
|
||||
|
||||
# 多种选择器尝试获取结果
|
||||
selectors = [
|
||||
'.public-list-box',
|
||||
'.public-pic-b',
|
||||
'.public-list-div',
|
||||
'.vod-item',
|
||||
'.search-result-item',
|
||||
'[class*="vod"]'
|
||||
]
|
||||
|
||||
videos = []
|
||||
seen_ids = set()
|
||||
|
||||
for selector in selectors:
|
||||
items = doc(selector)
|
||||
if items.length > 0:
|
||||
for item in items.items():
|
||||
try:
|
||||
link = item.find('a[href*="/voddetail/"]').first()
|
||||
if not link:
|
||||
continue
|
||||
|
||||
vod_href = link.attr('href')
|
||||
if not vod_href:
|
||||
continue
|
||||
|
||||
vod_id_match = re.search(r'/voddetail/(\d+)\.html', vod_href)
|
||||
if not vod_id_match:
|
||||
continue
|
||||
|
||||
vod_id = vod_id_match.group(1)
|
||||
if vod_id in seen_ids:
|
||||
continue
|
||||
|
||||
seen_ids.add(vod_id)
|
||||
|
||||
# 提取标题
|
||||
title_selectors = [
|
||||
'.time-title', '.vod-name', '.title',
|
||||
'img[alt]', '[title]', 'h3', 'h4'
|
||||
]
|
||||
|
||||
vod_title = ""
|
||||
for title_sel in title_selectors:
|
||||
title_elem = item.find(title_sel).first()
|
||||
if title_elem:
|
||||
title_text = title_elem.attr('title') or title_elem.attr('alt') or title_elem.text()
|
||||
if title_text and len(title_text.strip()) > 0:
|
||||
vod_title = self.fix_encoding(title_text.strip())
|
||||
break
|
||||
|
||||
if not vod_title:
|
||||
continue
|
||||
|
||||
# 提取封面
|
||||
img = item.find('img').first()
|
||||
vod_pic = img.attr('data-src') or img.attr('src') or ""
|
||||
if vod_pic and not vod_pic.startswith(('http://', 'https://')):
|
||||
vod_pic = urljoin(self.host, vod_pic)
|
||||
|
||||
# 提取备注
|
||||
remark_selectors = ['.public-prt', '.public-list-prb', '.remark', '.episode']
|
||||
vod_remarks = ""
|
||||
for remark_sel in remark_selectors:
|
||||
remark_elem = item.find(remark_sel).first()
|
||||
if remark_elem:
|
||||
remark_text = remark_elem.text()
|
||||
if remark_text and len(remark_text.strip()) > 0:
|
||||
vod_remarks = self.fix_encoding(remark_text.strip())
|
||||
break
|
||||
|
||||
videos.append({
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_title,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_year": "",
|
||||
"vod_remarks": vod_remarks
|
||||
})
|
||||
except:
|
||||
continue
|
||||
|
||||
if videos:
|
||||
break
|
||||
|
||||
result["list"] = videos
|
||||
self.log(f"搜索 '{key}' 找到 {len(videos)} 个结果")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"搜索失败: {str(e)}")
|
||||
# 尝试备用搜索方法
|
||||
try:
|
||||
return self.backupSearch(key, pg)
|
||||
except Exception as backup_error:
|
||||
self.log(f"备用搜索也失败: {str(backup_error)}")
|
||||
|
||||
return result
|
||||
|
||||
def handle_captcha_search(self, key, pg):
|
||||
"""处理验证码的搜索方法"""
|
||||
result = {"list": [], "page": int(pg)}
|
||||
|
||||
try:
|
||||
# 方法1: 尝试使用不同的User-Agent
|
||||
encoded_key = quote(key)
|
||||
search_url = f"{self.host}/vodsearch/-------------.html?wd={encoded_key}"
|
||||
|
||||
if int(pg) > 1:
|
||||
search_url = f"{self.host}/vodsearch/-------------.html?wd={encoded_key}&page={pg}"
|
||||
|
||||
# 尝试不同的User-Agent
|
||||
for ua in self.ua_list:
|
||||
try:
|
||||
headers = {
|
||||
'User-Agent': ua,
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
|
||||
'Connection': 'keep-alive',
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'Cache-Control': 'max-age=0'
|
||||
}
|
||||
|
||||
time.sleep(random.uniform(2.0, 4.0))
|
||||
response = self.fetch(search_url, headers=headers, timeout=15)
|
||||
|
||||
# 检查是否仍然有验证码
|
||||
html_content = response.text
|
||||
if "验证码" not in html_content and "captcha" not in html_content.lower():
|
||||
# 解析搜索结果
|
||||
doc = self.getpq(html_content)
|
||||
videos = []
|
||||
seen_ids = set()
|
||||
|
||||
# 简化选择器
|
||||
items = doc('.public-list-box, .public-pic-b, .public-list-div')
|
||||
for item in items.items():
|
||||
link = item.find('a[href*="/voddetail/"]').first()
|
||||
if not link:
|
||||
continue
|
||||
|
||||
vod_href = link.attr('href')
|
||||
vod_id_match = re.search(r'/voddetail/(\d+)\.html', vod_href)
|
||||
if not vod_id_match:
|
||||
continue
|
||||
|
||||
vod_id = vod_id_match.group(1)
|
||||
if vod_id in seen_ids:
|
||||
continue
|
||||
|
||||
seen_ids.add(vod_id)
|
||||
|
||||
# 提取标题
|
||||
vod_title = self.fix_encoding(
|
||||
link.attr('title') or
|
||||
item.find('.time-title').text() or
|
||||
item.find('img').attr('alt') or
|
||||
link.text() or ""
|
||||
).strip()
|
||||
|
||||
if not vod_title:
|
||||
continue
|
||||
|
||||
# 提取封面
|
||||
img = item.find('img').first()
|
||||
vod_pic = img.attr('data-src') or img.attr('src') or ""
|
||||
if vod_pic and not vod_pic.startswith(('http://', 'https://')):
|
||||
vod_pic = urljoin(self.host, vod_pic)
|
||||
|
||||
videos.append({
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_title,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_year": "",
|
||||
"vod_remarks": ""
|
||||
})
|
||||
|
||||
result["list"] = videos
|
||||
self.log(f"使用备用UA成功搜索 '{key}',找到 {len(videos)} 个结果")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"使用UA {ua[:30]}... 搜索失败: {str(e)}")
|
||||
continue
|
||||
|
||||
# 如果所有方法都失败,返回空结果
|
||||
self.log("所有绕过验证码的方法都失败了")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"验证码处理失败: {str(e)}")
|
||||
|
||||
return result
|
||||
|
||||
def backupSearch(self, key, pg):
|
||||
"""备用搜索方法 - 使用更简单的选择器"""
|
||||
result = {"list": [], "page": int(pg)}
|
||||
|
||||
try:
|
||||
encoded_key = quote(key)
|
||||
search_url = f"{self.host}/vodsearch/-------------.html?wd={encoded_key}"
|
||||
|
||||
response = self.fetch_with_encoding(search_url)
|
||||
html_content = response.text
|
||||
|
||||
# 使用正则表达式提取搜索结果
|
||||
pattern = r'<a\s+href="(/voddetail/\d+\.html)"[^>]*>(.*?)</a>'
|
||||
matches = re.findall(pattern, html_content, re.DOTALL)
|
||||
|
||||
seen_ids = set()
|
||||
for href, content in matches:
|
||||
vod_id_match = re.search(r'/voddetail/(\d+)\.html', href)
|
||||
if not vod_id_match:
|
||||
continue
|
||||
|
||||
vod_id = vod_id_match.group(1)
|
||||
if vod_id in seen_ids:
|
||||
continue
|
||||
|
||||
seen_ids.add(vod_id)
|
||||
|
||||
# 提取标题
|
||||
title_match = re.search(r'<img[^>]*alt="([^"]*)"', content)
|
||||
if not title_match:
|
||||
title_match = re.search(r'title="([^"]*)"', content)
|
||||
if not title_match:
|
||||
# 尝试从链接内容提取文本
|
||||
text_match = re.search(r'>([^<]+)<', content)
|
||||
if text_match:
|
||||
vod_title = text_match.group(1).strip()
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
vod_title = title_match.group(1)
|
||||
|
||||
vod_title = self.fix_encoding(vod_title)
|
||||
if not vod_title:
|
||||
continue
|
||||
|
||||
# 提取封面图
|
||||
img_match = re.search(r'<img[^>]*src="([^"]*)"', content)
|
||||
vod_pic = img_match.group(1) if img_match else ""
|
||||
if vod_pic and not vod_pic.startswith(('http://', 'https://')):
|
||||
vod_pic = urljoin(self.host, vod_pic)
|
||||
|
||||
result["list"].append({
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_title,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_year": "",
|
||||
"vod_remarks": ""
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"备用搜索失败: {str(e)}")
|
||||
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
"""播放地址解析 - 终极修复版"""
|
||||
try:
|
||||
play_page_url = urljoin(self.host, id)
|
||||
if not play_page_url.startswith(('http://', 'https://')):
|
||||
self.log(f"无效播放地址: {id}")
|
||||
return {"parse": 1, "url": "", "header": self.headers}
|
||||
|
||||
response = self.fetch_with_encoding(play_page_url)
|
||||
html_content = response.text
|
||||
|
||||
# --- 核心:提取播放器配置 ---
|
||||
player_match = re.search(r'var\s+player_aaaa\s*=\s*({[^}]+?url\s*:\s*["\'][^"\']+["\'][^}]*})', html_content, re.DOTALL)
|
||||
if not player_match:
|
||||
self.log("未找到播放器配置脚本 player_aaaa")
|
||||
return {"parse": 1, "url": play_page_url, "header": self.headers}
|
||||
|
||||
player_json_str = player_match.group(1)
|
||||
# 修复可能的JSON格式问题(尾部逗号)
|
||||
player_json_str = re.sub(r',\s*([}\]])', r'\1', player_json_str)
|
||||
|
||||
try:
|
||||
player_data = json.loads(player_json_str)
|
||||
except json.JSONDecodeError as e:
|
||||
self.log(f"解析播放器 JSON 失败: {str(e)}")
|
||||
return {"parse": 1, "url": play_page_url, "header": self.headers}
|
||||
|
||||
# --- 提取主播放地址和备用地址 ---
|
||||
main_url = player_data.get("url", "").strip()
|
||||
backup_url = player_data.get("url_next", "").strip()
|
||||
|
||||
# URL解码
|
||||
if '%' in main_url:
|
||||
main_url = unquote(main_url)
|
||||
if '%' in backup_url:
|
||||
backup_url = unquote(backup_url)
|
||||
|
||||
self.log(f"[修复版] 提取的主播放地址: {main_url}")
|
||||
self.log(f"[修复版] 提取的备用播放地址: {backup_url}")
|
||||
|
||||
# --- 极简播放逻辑:谁有效播谁 ---
|
||||
# 优先使用主地址
|
||||
if main_url and self.isVideoFormat(main_url):
|
||||
return {
|
||||
"parse": 0, # 0 = 直接播放
|
||||
"url": main_url,
|
||||
"header": {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
|
||||
"Referer": play_page_url, # ⚠️ 关键!必须添加 Referer
|
||||
"Origin": self.host.rstrip('/')
|
||||
}
|
||||
}
|
||||
# 主地址无效,尝试备用地址
|
||||
elif backup_url and self.isVideoFormat(backup_url):
|
||||
return {
|
||||
"parse": 0,
|
||||
"url": backup_url,
|
||||
"header": {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
|
||||
"Referer": play_page_url, # ⚠️ 关键!必须添加 Referer
|
||||
"Origin": self.host.rstrip('/')
|
||||
}
|
||||
}
|
||||
|
||||
# --- 万不得已的兜底方案 ---
|
||||
self.log("未能提取有效视频地址,返回播放页")
|
||||
return {
|
||||
"parse": 1,
|
||||
"url": play_page_url,
|
||||
"header": self.headers
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"播放地址解析异常: {str(e)}")
|
||||
return {
|
||||
"parse": 1,
|
||||
"url": urljoin(self.host, id),
|
||||
"header": self.headers
|
||||
}
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
Reference in New Issue
Block a user