上传文件至「/」
This commit is contained in:
@@ -0,0 +1,622 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 初见影视爬虫 - 爱豆儿解析版
|
||||
import re
|
||||
import json
|
||||
import urllib.parse
|
||||
import sys
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.extend = extend
|
||||
|
||||
def getName(self):
|
||||
return "初见影视"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return False
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
host = 'https://cjysw.cc'
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; Mobile) '
|
||||
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
'Chrome/114.0.0.0 Mobile Safari/537.36',
|
||||
'Referer': host,
|
||||
}
|
||||
|
||||
classes_config = [
|
||||
("电影", "1"),
|
||||
("剧集", "15"),
|
||||
("动漫", "30"),
|
||||
("综艺", "24"),
|
||||
("纪录片", "63"),
|
||||
("短剧", "47"),
|
||||
("Netflix", "60"),
|
||||
]
|
||||
|
||||
# ====== 只使用爱豆儿解析接口 ======
|
||||
PARSE_API = "https://jx.aidouer.net/?url="
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
classes = []
|
||||
|
||||
for name, tid in self.classes_config:
|
||||
classes.append({
|
||||
"type_name": name,
|
||||
"type_id": tid
|
||||
})
|
||||
|
||||
filters = {}
|
||||
|
||||
filters["1"] = [
|
||||
{"key": "class", "name": "类型", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "动作", "v": "动作"},
|
||||
{"n": "喜剧", "v": "喜剧"},
|
||||
{"n": "爱情", "v": "爱情"},
|
||||
{"n": "科幻", "v": "科幻"},
|
||||
{"n": "恐怖", "v": "恐怖"},
|
||||
{"n": "剧情", "v": "剧情"},
|
||||
{"n": "战争", "v": "战争"},
|
||||
{"n": "犯罪", "v": "犯罪"},
|
||||
{"n": "动画", "v": "动画"},
|
||||
{"n": "奇幻", "v": "奇幻"},
|
||||
{"n": "冒险", "v": "冒险"},
|
||||
{"n": "悬疑", "v": "悬疑"},
|
||||
{"n": "惊悚", "v": "惊悚"},
|
||||
{"n": "古装", "v": "古装"},
|
||||
{"n": "历史", "v": "历史"},
|
||||
]},
|
||||
{"key": "area", "name": "地区", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "大陆", "v": "大陆"},
|
||||
{"n": "香港", "v": "香港"},
|
||||
{"n": "台湾", "v": "台湾"},
|
||||
{"n": "美国", "v": "美国"},
|
||||
{"n": "日本", "v": "日本"},
|
||||
{"n": "韩国", "v": "韩国"},
|
||||
{"n": "英国", "v": "英国"},
|
||||
{"n": "法国", "v": "法国"},
|
||||
{"n": "德国", "v": "德国"},
|
||||
{"n": "泰国", "v": "泰国"},
|
||||
{"n": "印度", "v": "印度"},
|
||||
{"n": "其他", "v": "其他"},
|
||||
]},
|
||||
{"key": "year", "name": "年份", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "2026", "v": "2026"},
|
||||
{"n": "2025", "v": "2025"},
|
||||
{"n": "2024", "v": "2024"},
|
||||
{"n": "2023", "v": "2023"},
|
||||
{"n": "2022", "v": "2022"},
|
||||
{"n": "2021", "v": "2021"},
|
||||
{"n": "2020", "v": "2020"},
|
||||
]},
|
||||
]
|
||||
|
||||
filters["15"] = [
|
||||
{"key": "class", "name": "类型", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "国产剧", "v": "国产剧"},
|
||||
{"n": "港剧", "v": "港剧"},
|
||||
{"n": "台剧", "v": "台剧"},
|
||||
{"n": "日剧", "v": "日剧"},
|
||||
{"n": "韩剧", "v": "韩剧"},
|
||||
{"n": "美剧", "v": "美剧"},
|
||||
{"n": "英剧", "v": "英剧"},
|
||||
{"n": "泰剧", "v": "泰剧"},
|
||||
]},
|
||||
{"key": "area", "name": "地区", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "大陆", "v": "大陆"},
|
||||
{"n": "香港", "v": "香港"},
|
||||
{"n": "台湾", "v": "台湾"},
|
||||
{"n": "美国", "v": "美国"},
|
||||
{"n": "日本", "v": "日本"},
|
||||
{"n": "韩国", "v": "韩国"},
|
||||
{"n": "英国", "v": "英国"},
|
||||
]},
|
||||
{"key": "year", "name": "年份", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "2026", "v": "2026"},
|
||||
{"n": "2025", "v": "2025"},
|
||||
{"n": "2024", "v": "2024"},
|
||||
{"n": "2023", "v": "2023"},
|
||||
{"n": "2022", "v": "2022"},
|
||||
]},
|
||||
]
|
||||
|
||||
filters["30"] = [
|
||||
{"key": "area", "name": "地区", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "大陆", "v": "大陆"},
|
||||
{"n": "日本", "v": "日本"},
|
||||
{"n": "美国", "v": "美国"},
|
||||
]},
|
||||
{"key": "year", "name": "年份", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "2026", "v": "2026"},
|
||||
{"n": "2025", "v": "2025"},
|
||||
{"n": "2024", "v": "2024"},
|
||||
]},
|
||||
]
|
||||
|
||||
filters["24"] = [
|
||||
{"key": "year", "name": "年份", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "2026", "v": "2026"},
|
||||
{"n": "2025", "v": "2025"},
|
||||
{"n": "2024", "v": "2024"},
|
||||
]},
|
||||
]
|
||||
|
||||
filters["63"] = [
|
||||
{"key": "year", "name": "年份", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "2026", "v": "2026"},
|
||||
{"n": "2025", "v": "2025"},
|
||||
{"n": "2024", "v": "2024"},
|
||||
]},
|
||||
]
|
||||
|
||||
filters["47"] = [
|
||||
{"key": "year", "name": "年份", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "2026", "v": "2026"},
|
||||
{"n": "2025", "v": "2025"},
|
||||
]},
|
||||
]
|
||||
|
||||
filters["60"] = [
|
||||
{"key": "year", "name": "年份", "value": [
|
||||
{"n": "全部", "v": ""},
|
||||
{"n": "2026", "v": "2026"},
|
||||
{"n": "2025", "v": "2025"},
|
||||
]},
|
||||
]
|
||||
|
||||
result['filters'] = filters
|
||||
|
||||
rsp = self.fetch(self.host, headers=self.headers)
|
||||
html = rsp.text
|
||||
|
||||
vlist = []
|
||||
|
||||
pattern = r'<a[^>]*href="([^"]+)"[^>]*class="[^"]*module-poster-item[^"]*"[^>]*>(.*?)</a>'
|
||||
items = re.findall(pattern, html, re.DOTALL)
|
||||
|
||||
for href, item_html in items:
|
||||
title_match = re.search(r'<div[^>]*class="[^"]*module-poster-item-title[^"]*"[^>]*>([^<]*)</div>', item_html)
|
||||
title = title_match.group(1).strip() if title_match else ''
|
||||
|
||||
note_match = re.search(r'<div[^>]*class="[^"]*module-item-note[^"]*"[^>]*>([^<]*)</div>', item_html)
|
||||
note = note_match.group(1).strip() if note_match else ''
|
||||
|
||||
pic_match = re.search(r'<img[^>]*data-original="([^"]+)"[^>]*>', item_html)
|
||||
pic = pic_match.group(1) if pic_match else ''
|
||||
if not pic:
|
||||
pic_match = re.search(r'<img[^>]*src="([^"]+)"[^>]*>', item_html)
|
||||
pic = pic_match.group(1) if pic_match else ''
|
||||
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
|
||||
if href and not href.startswith('http'):
|
||||
href = self.host + href if href.startswith('/') else self.host + '/' + href
|
||||
|
||||
if title:
|
||||
vlist.append({
|
||||
'vod_id': href,
|
||||
'vod_name': title,
|
||||
'vod_pic': pic,
|
||||
'vod_remarks': note
|
||||
})
|
||||
|
||||
result['class'] = classes
|
||||
result['list'] = vlist[:30]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
page = int(pg) if pg else 1
|
||||
|
||||
class_val = extend.get('class', '') if extend else ''
|
||||
area_val = extend.get('area', '') if extend else ''
|
||||
year_val = extend.get('year', '') if extend else ''
|
||||
|
||||
if page == 1:
|
||||
if class_val and not area_val and not year_val:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/class/{class_val}.html"
|
||||
elif area_val and not class_val and not year_val:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/area/{area_val}.html"
|
||||
elif year_val and not class_val and not area_val:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/year/{year_val}.html"
|
||||
elif class_val and area_val and not year_val:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/class/{class_val}/area/{area_val}.html"
|
||||
elif class_val and year_val and not area_val:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/class/{class_val}/year/{year_val}.html"
|
||||
elif area_val and year_val and not class_val:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/area/{area_val}/year/{year_val}.html"
|
||||
else:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}.html"
|
||||
else:
|
||||
if class_val and not area_val and not year_val:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/class/{class_val}/page/{page}.html"
|
||||
elif area_val and not class_val and not year_val:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/area/{area_val}/page/{page}.html"
|
||||
elif year_val and not class_val and not area_val:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/year/{year_val}/page/{page}.html"
|
||||
elif class_val and area_val and not year_val:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/class/{class_val}/area/{area_val}/page/{page}.html"
|
||||
else:
|
||||
url = f"{self.host}/index.php/vod/show/id/{tid}/page/{page}.html"
|
||||
|
||||
rsp = self.fetch(url, headers=self.headers)
|
||||
html = rsp.text
|
||||
|
||||
videos = []
|
||||
|
||||
pattern = r'<a[^>]*href="([^"]+)"[^>]*class="[^"]*module-poster-item[^"]*"[^>]*>(.*?)</a>'
|
||||
items = re.findall(pattern, html, re.DOTALL)
|
||||
|
||||
for href, item_html in items:
|
||||
title_match = re.search(r'<div[^>]*class="[^"]*module-poster-item-title[^"]*"[^>]*>([^<]*)</div>', item_html)
|
||||
title = title_match.group(1).strip() if title_match else ''
|
||||
|
||||
note_match = re.search(r'<div[^>]*class="[^"]*module-item-note[^"]*"[^>]*>([^<]*)</div>', item_html)
|
||||
note = note_match.group(1).strip() if note_match else ''
|
||||
|
||||
pic_match = re.search(r'<img[^>]*data-original="([^"]+)"[^>]*>', item_html)
|
||||
pic = pic_match.group(1) if pic_match else ''
|
||||
if not pic:
|
||||
pic_match = re.search(r'<img[^>]*src="([^"]+)"[^>]*>', item_html)
|
||||
pic = pic_match.group(1) if pic_match else ''
|
||||
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
|
||||
if href and not href.startswith('http'):
|
||||
href = self.host + href if href.startswith('/') else self.host + '/' + href
|
||||
|
||||
if title:
|
||||
videos.append({
|
||||
'vod_id': href,
|
||||
'vod_name': title,
|
||||
'vod_pic': pic,
|
||||
'vod_remarks': note
|
||||
})
|
||||
|
||||
pagecount = 1
|
||||
page_pattern = r'<a[^>]*class="[^"]*page-link[^"]*"[^>]*>(\d+)</a>'
|
||||
page_matches = re.findall(page_pattern, html)
|
||||
for p in page_matches:
|
||||
if p.isdigit() and int(p) > pagecount:
|
||||
pagecount = int(p)
|
||||
|
||||
result = {
|
||||
'list': videos,
|
||||
'page': page,
|
||||
'pagecount': pagecount,
|
||||
'limit': 90,
|
||||
'total': 999999
|
||||
}
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
vid = ids[0]
|
||||
if not vid.startswith('http'):
|
||||
url = self.host + vid if vid.startswith('/') else self.host + '/' + vid
|
||||
else:
|
||||
url = vid
|
||||
|
||||
rsp = self.fetch(url, headers=self.headers)
|
||||
html = rsp.text
|
||||
|
||||
info = {'vod_id': vid}
|
||||
|
||||
title_match = re.search(r'<h1[^>]*>([^<]+)</h1>', html)
|
||||
if title_match:
|
||||
info['vod_name'] = title_match.group(1).strip()
|
||||
else:
|
||||
title_match = re.search(r'<title>([^<]+)</title>', html)
|
||||
if title_match:
|
||||
info['vod_name'] = title_match.group(1).split('-')[0].strip()
|
||||
|
||||
pic_match = re.search(r'<img[^>]*data-original="([^"]+)"[^>]*class="[^"]*lazy[^"]*"[^>]*alt="[^"]*"[^>]*>', html)
|
||||
if not pic_match:
|
||||
pic_match = re.search(r'<img[^>]*src="([^"]+)"[^>]*class="[^"]*lazy[^"]*"[^>]*alt="[^"]*"[^>]*>', html)
|
||||
if not pic_match:
|
||||
pic_match = re.search(r'<div[^>]*class="[^"]*module-item-pic[^"]*"[^>]*>.*?<img[^>]*src="([^"]+)"', html, re.DOTALL)
|
||||
if pic_match:
|
||||
pic = pic_match.group(1)
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
|
||||
info['vod_pic'] = pic
|
||||
|
||||
desc_match = re.search(r'<div[^>]*class="[^"]*module-info-introduction-content[^"]*"[^>]*>(.*?)</div>', html, re.DOTALL)
|
||||
if desc_match:
|
||||
info['vod_content'] = re.sub(r'<[^>]+>', '', desc_match.group(1)).strip()
|
||||
|
||||
info_items = re.findall(r'<div[^>]*class="[^"]*module-info-item[^"]*"[^>]*>.*?<span[^>]*class="[^"]*module-info-item-title[^"]*"[^>]*>([^<]*)</span>.*?<div[^>]*class="[^"]*module-info-item-content[^"]*"[^>]*>(.*?)</div>', html, re.DOTALL)
|
||||
|
||||
for title, value in info_items:
|
||||
title = title.strip().replace(':', '').replace(':', '')
|
||||
value = re.sub(r'<[^>]+>', '', value).strip()
|
||||
if '导演' in title:
|
||||
info['vod_director'] = value
|
||||
elif '主演' in title:
|
||||
info['vod_actor'] = value
|
||||
elif '集数' in title or '更新' in title:
|
||||
info['vod_remarks'] = value
|
||||
elif '年份' in title or '上映' in title:
|
||||
info['vod_year'] = value
|
||||
|
||||
vod_id = None
|
||||
player_match = re.search(r'var player_aaaa\s*=\s*({[^;]+})', html)
|
||||
if player_match:
|
||||
try:
|
||||
player_data = json.loads(player_match.group(1))
|
||||
if player_data.get('id'):
|
||||
vod_id = player_data['id']
|
||||
except:
|
||||
pass
|
||||
|
||||
if not vod_id:
|
||||
vod_match = re.search(r'/detail/(\d+)\.html', url)
|
||||
if vod_match:
|
||||
vod_id = vod_match.group(1)
|
||||
else:
|
||||
vod_match = re.search(r'/play/(\d+)-\d+-\d+\.html', url)
|
||||
if vod_match:
|
||||
vod_id = vod_match.group(1)
|
||||
|
||||
tabs = []
|
||||
|
||||
tab_pattern1 = r'<(?:div|a)[^>]*class="[^"]*module-tab-item[^"]*"[^>]*>.*?<span>([^<]+)</span>'
|
||||
tabs = re.findall(tab_pattern1, html, re.DOTALL)
|
||||
tabs = list(dict.fromkeys(tabs))
|
||||
|
||||
if not tabs:
|
||||
tab_pattern2 = r'<span[^>]*class="[^"]*module-tab-value[^"]*"[^>]*>([^<]*)</span>'
|
||||
tabs = re.findall(tab_pattern2, html)
|
||||
tabs = list(dict.fromkeys(tabs))
|
||||
|
||||
if not tabs:
|
||||
tab_pattern3 = r'选择播放源\s*([^<]+)'
|
||||
tabs = re.findall(tab_pattern3, html)
|
||||
tabs = list(dict.fromkeys(tabs))
|
||||
|
||||
if not tabs:
|
||||
tabs = ['高清']
|
||||
|
||||
play_items = []
|
||||
|
||||
container_pattern = r'<div[^>]*class="[^"]*module-play-list[^"]*"[^>]*>(.*?)</div>'
|
||||
containers = re.findall(container_pattern, html, re.DOTALL)
|
||||
|
||||
for container in containers:
|
||||
link_pattern = r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>'
|
||||
links = re.findall(link_pattern, container, re.DOTALL)
|
||||
for href, title in links:
|
||||
title = re.sub(r'<[^>]+>', '', title).strip()
|
||||
if not title:
|
||||
title = '播放'
|
||||
if href.startswith('/'):
|
||||
href = self.host + href
|
||||
if href not in [item.split('$')[-1] for item in play_items]:
|
||||
play_items.append(f'{title}${href}')
|
||||
|
||||
if not play_items and vod_id:
|
||||
for idx, tab_name in enumerate(tabs):
|
||||
sid = idx + 1
|
||||
total = 40
|
||||
total_match = re.search(r'<small>(\d+)</small>', html)
|
||||
if total_match:
|
||||
total = int(total_match.group(1))
|
||||
episodes = []
|
||||
for nid in range(1, total + 1):
|
||||
ep_name = f"第{nid}集"
|
||||
ep_url = f"{self.host}/play/{vod_id}-{sid}-{nid}.html"
|
||||
episodes.append(f'{ep_name}${ep_url}')
|
||||
if episodes:
|
||||
play_items.extend(episodes)
|
||||
|
||||
ktabs = []
|
||||
klists = []
|
||||
|
||||
if play_items:
|
||||
if len(tabs) == 1:
|
||||
ktabs = tabs
|
||||
klists = ['#'.join(play_items)]
|
||||
else:
|
||||
items_per_tab = max(1, len(play_items) // len(tabs))
|
||||
for i in range(len(tabs)):
|
||||
start = i * items_per_tab
|
||||
end = start + items_per_tab if i < len(tabs) - 1 else len(play_items)
|
||||
if start < len(play_items):
|
||||
ktabs.append(tabs[i])
|
||||
klists.append('#'.join(play_items[start:end]))
|
||||
|
||||
if not ktabs:
|
||||
link_pattern = r'<a[^>]*class="[^"]*module-play-list-link[^"]*"[^>]*href="([^"]+)"[^>]*>([^<]+)</a>'
|
||||
links = re.findall(link_pattern, html)
|
||||
if links:
|
||||
episodes = []
|
||||
for href, title in links:
|
||||
if href and not href.startswith('#'):
|
||||
if not href.startswith('http'):
|
||||
href = self.host + href if href.startswith('/') else self.host + '/' + href
|
||||
episodes.append(f'{title}${href}')
|
||||
if episodes:
|
||||
ktabs.append("播放")
|
||||
klists.append('#'.join(episodes))
|
||||
|
||||
info["vod_play_from"] = '$$$'.join(ktabs) if ktabs else "初见影视"
|
||||
info["vod_play_url"] = '$$$'.join(klists) if klists else ""
|
||||
|
||||
return {'list': [info]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
page = int(pg) if pg else 1
|
||||
encoded_key = urllib.parse.quote(key, safe='')
|
||||
url = f"{self.host}/index.php/vod/search.html?wd={encoded_key}&page={page}"
|
||||
|
||||
rsp = self.fetch(url, headers=self.headers)
|
||||
html = rsp.text
|
||||
|
||||
videos = []
|
||||
|
||||
card_pattern = r'<div class="module-card-item[^"]*"[^>]*>(.*?)</div>\s*</div>\s*</div>'
|
||||
cards = re.findall(card_pattern, html, re.DOTALL)
|
||||
|
||||
if not cards:
|
||||
card_pattern = r'<div class="module-card-item[^"]*"[^>]*>(.*?)</div>'
|
||||
temp_cards = re.findall(card_pattern, html, re.DOTALL)
|
||||
cards = [c for c in temp_cards if len(c) > 100]
|
||||
|
||||
for card_html in cards:
|
||||
href_match = re.search(r'<a[^>]*href="([^"]+)"[^>]*class="[^"]*module-card-item-poster[^"]*"[^>]*>', card_html)
|
||||
if not href_match:
|
||||
href_match = re.search(r'<a[^>]*href="([^"]+)"[^>]*>.*?<div class="module-card-item-title', card_html, re.DOTALL)
|
||||
if not href_match:
|
||||
href_match = re.search(r'<a[^>]*href="([^"]+)"[^>]*>', card_html)
|
||||
href = href_match.group(1) if href_match else ''
|
||||
|
||||
title_match = re.search(r'<div class="module-card-item-title">.*?<a[^>]*>.*?<strong>([^<]+)</strong>', card_html, re.DOTALL)
|
||||
if not title_match:
|
||||
title_match = re.search(r'<strong>([^<]+)</strong>', card_html)
|
||||
if not title_match:
|
||||
title_match = re.search(r'<div class="module-card-item-title">.*?<a[^>]*>([^<]+)</a>', card_html, re.DOTALL)
|
||||
title = title_match.group(1).strip() if title_match else ''
|
||||
|
||||
note_match = re.search(r'<div class="module-item-note">([^<]*)</div>', card_html)
|
||||
note = note_match.group(1).strip() if note_match else ''
|
||||
|
||||
pic_match = re.search(r'<img[^>]*data-original="([^"]+)"[^>]*>', card_html)
|
||||
if not pic_match:
|
||||
pic_match = re.search(r'<img[^>]*src="([^"]+)"[^>]*>', card_html)
|
||||
pic = pic_match.group(1) if pic_match else ''
|
||||
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
|
||||
if href and not href.startswith('http'):
|
||||
href = self.host + href if href.startswith('/') else self.host + '/' + href
|
||||
|
||||
if title:
|
||||
videos.append({
|
||||
'vod_id': href,
|
||||
'vod_name': title,
|
||||
'vod_pic': pic,
|
||||
'vod_remarks': note
|
||||
})
|
||||
|
||||
if not videos:
|
||||
try:
|
||||
from pyquery import PyQuery as pq
|
||||
data = pq(html)
|
||||
for item in data('.module-card-item').items():
|
||||
href = item('a.module-card-item-poster').attr('href') or ''
|
||||
title = item('.module-card-item-title strong').text() or ''
|
||||
note = item('.module-item-note').text() or ''
|
||||
pic = item('img').attr('data-original') or ''
|
||||
|
||||
if title:
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
|
||||
if href and not href.startswith('http'):
|
||||
href = self.host + href if href.startswith('/') else self.host + '/' + href
|
||||
videos.append({
|
||||
'vod_id': href,
|
||||
'vod_name': title,
|
||||
'vod_pic': pic,
|
||||
'vod_remarks': note
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
return {
|
||||
'list': videos,
|
||||
'page': page,
|
||||
'pagecount': 9999,
|
||||
'limit': 90,
|
||||
'total': 999999
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 使用爱豆儿解析接口
|
||||
# ============================================================
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
if id.startswith('http'):
|
||||
play_url = id
|
||||
else:
|
||||
play_url = self.host + id if id.startswith('/') else self.host + '/' + id
|
||||
|
||||
rsp = self.fetch(play_url, headers=self.headers)
|
||||
html = rsp.text
|
||||
|
||||
real_url = play_url
|
||||
|
||||
# ====== 从 player_aaaa 提取 ======
|
||||
player_match = re.search(r'var player_aaaa\s*=\s*({[^;]+})', html)
|
||||
if player_match:
|
||||
try:
|
||||
player_data = json.loads(player_match.group(1))
|
||||
if player_data.get('url'):
|
||||
real_url = player_data['url'].replace('\\/', '/')
|
||||
if real_url.startswith('//'):
|
||||
real_url = 'https:' + real_url
|
||||
except:
|
||||
pass
|
||||
|
||||
# ====== 从 iframe 提取 ======
|
||||
iframe_match = re.search(r'<iframe[^>]*src="([^"]+)"[^>]*>', html)
|
||||
if iframe_match:
|
||||
real_url = iframe_match.group(1)
|
||||
if real_url.startswith('//'):
|
||||
real_url = 'https:' + real_url
|
||||
if real_url.startswith('/'):
|
||||
real_url = self.host + real_url
|
||||
|
||||
# ====== 判断URL类型 ======
|
||||
# 如果是m3u8/mp4,直接播放
|
||||
if '.m3u8' in real_url or '.mp4' in real_url:
|
||||
return {'parse': 0, 'url': real_url, 'header': self.headers}
|
||||
|
||||
# ====== 如果是第三方网站,使用爱豆儿解析 ======
|
||||
need_parse = False
|
||||
parse_keywords = [
|
||||
'iqiyi.com', 'bilibili.com', 'v.qq.com', 'youku.com',
|
||||
'qq.com', 'mgtv.com', 'sohu.com', 'le.com', 'pptv.com',
|
||||
'爱奇艺', '腾讯', '优酷', '芒果', '乐视', '哔哩'
|
||||
]
|
||||
for keyword in parse_keywords:
|
||||
if keyword in real_url:
|
||||
need_parse = True
|
||||
break
|
||||
|
||||
if need_parse:
|
||||
parse_url = self.PARSE_API + urllib.parse.quote(real_url)
|
||||
parse_headers = self.headers.copy()
|
||||
parse_headers['Referer'] = 'https://jx.aidouer.net'
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': parse_url,
|
||||
'header': parse_headers
|
||||
}
|
||||
|
||||
return {'parse': 0, 'url': real_url, 'header': self.headers}
|
||||
|
||||
def localProxy(self, param):
|
||||
return None
|
||||
@@ -0,0 +1,443 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 少货影视 - TVbox标准版
|
||||
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import ssl
|
||||
import http.cookiejar
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.extend = extend
|
||||
self.host = 'https://shdy3.com'
|
||||
self.cookie_jar = http.cookiejar.CookieJar()
|
||||
self.cookie_handler = urllib.request.HTTPCookieProcessor(self.cookie_jar)
|
||||
self.opener = urllib.request.build_opener(self.cookie_handler)
|
||||
self.fetch(self.host)
|
||||
|
||||
def getName(self):
|
||||
return "少货影视"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return False
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 9; ALN-AL00 Build/PQ3B.190801.05281406; wv) AppleWebKit/537.36',
|
||||
'accept-language': 'zh-CN,zh;q=0.9',
|
||||
'Referer': 'https://shdy3.com/',
|
||||
}
|
||||
|
||||
classes_config = [
|
||||
("电影", "1"),
|
||||
("电视剧", "2"),
|
||||
("动漫", "4"),
|
||||
]
|
||||
|
||||
def fetch(self, url):
|
||||
try:
|
||||
req = urllib.request.Request(url, headers=self.headers)
|
||||
with self.opener.open(req, timeout=15) as response:
|
||||
content = response.read()
|
||||
try:
|
||||
return content.decode('utf-8')
|
||||
except:
|
||||
return content.decode('gbk', errors='ignore')
|
||||
except Exception as e:
|
||||
print(f"请求失败: {e}")
|
||||
return ''
|
||||
|
||||
def clean_html(self, text):
|
||||
if not text:
|
||||
return ''
|
||||
text = re.sub(r'<[^>]+>', '', text)
|
||||
text = re.sub(r'\s+', ' ', text)
|
||||
return text.strip()
|
||||
|
||||
# ==================== 首页 ====================
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
classes = []
|
||||
for name, tid in self.classes_config:
|
||||
classes.append({"type_name": name, "type_id": tid})
|
||||
|
||||
filters = {
|
||||
"1": [{"key": "cateId", "name": "类型", "value": [
|
||||
{"n": "全部", "v": "1"},
|
||||
{"n": "喜剧", "v": "6"},
|
||||
{"n": "爱情", "v": "7"},
|
||||
{"n": "恐怖", "v": "8"},
|
||||
{"n": "动作", "v": "9"},
|
||||
{"n": "科幻", "v": "10"},
|
||||
{"n": "战争", "v": "11"},
|
||||
{"n": "犯罪", "v": "12"},
|
||||
{"n": "动画", "v": "13"},
|
||||
{"n": "奇幻", "v": "14"},
|
||||
{"n": "剧情", "v": "15"},
|
||||
{"n": "冒险", "v": "16"},
|
||||
{"n": "悬疑", "v": "17"},
|
||||
{"n": "惊悚", "v": "18"},
|
||||
{"n": "其他", "v": "20"},
|
||||
]}],
|
||||
"2": [{"key": "cateId", "name": "类型", "value": [
|
||||
{"n": "全部", "v": "2"},
|
||||
{"n": "国产剧", "v": "20"},
|
||||
{"n": "TVB", "v": "21"},
|
||||
{"n": "韩剧", "v": "22"},
|
||||
{"n": "美剧", "v": "23"},
|
||||
{"n": "日剧", "v": "24"},
|
||||
{"n": "英剧", "v": "25"},
|
||||
{"n": "台剧", "v": "26"},
|
||||
{"n": "其他", "v": "27"},
|
||||
]}],
|
||||
}
|
||||
result['filters'] = filters
|
||||
|
||||
html = self.fetch(self.host)
|
||||
vlist = []
|
||||
if html:
|
||||
items = re.findall(
|
||||
r'<a[^>]*class="myui-vodlist__thumb[^"]*"[^>]*href="([^"]+)"[^>]*title="([^"]+)"[^>]*data-original="([^"]+)"[^>]*>.*?<span[^>]*class="pic-text[^"]*"[^>]*>(.*?)</span>',
|
||||
html, re.DOTALL
|
||||
)
|
||||
for href, title, pic, remark in items[:12]:
|
||||
if href and title:
|
||||
if not href.startswith('http'):
|
||||
href = self.host + href if href.startswith('/') else self.host + '/' + href
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
|
||||
if pic and pic.startswith('//'):
|
||||
pic = 'https:' + pic
|
||||
vlist.append({
|
||||
'vod_id': href,
|
||||
'vod_name': title,
|
||||
'vod_pic': pic,
|
||||
'vod_remarks': self.clean_html(remark)
|
||||
})
|
||||
|
||||
result['class'] = classes
|
||||
result['list'] = vlist
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {}
|
||||
|
||||
# ==================== 分类 ====================
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
page = int(pg) if pg else 1
|
||||
|
||||
if extend and 'cateId' in extend:
|
||||
cateId = extend.get('cateId')
|
||||
else:
|
||||
cateId = tid
|
||||
|
||||
if page <= 1:
|
||||
url = f"{self.host}/list/{cateId}.html"
|
||||
else:
|
||||
url = f"{self.host}/list/{cateId}-{page}.html"
|
||||
|
||||
html = self.fetch(url)
|
||||
if not html:
|
||||
return {'list': [], 'page': page, 'pagecount': 1}
|
||||
|
||||
videos = []
|
||||
|
||||
items = re.findall(
|
||||
r'<div class="v_img">.*?<a href="([^"]+)" title="([^"]+)">.*?<img[^>]*data-original="([^"]+)".*?<div class="v_note">(.*?)</div>',
|
||||
html, re.DOTALL
|
||||
)
|
||||
|
||||
for href, title, pic, remark in items:
|
||||
if href and title:
|
||||
if not href.startswith('http'):
|
||||
href = self.host + href if href.startswith('/') else self.host + '/' + href
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
|
||||
if pic and pic.startswith('//'):
|
||||
pic = 'https:' + pic
|
||||
videos.append({
|
||||
'vod_id': href,
|
||||
'vod_name': title,
|
||||
'vod_pic': pic,
|
||||
'vod_remarks': self.clean_html(remark)
|
||||
})
|
||||
|
||||
if not videos:
|
||||
items2 = re.findall(
|
||||
r'<a href="(/movie/[^"]+)" title="([^"]+)">.*?<img[^>]*data-original="([^"]+)"',
|
||||
html, re.DOTALL
|
||||
)
|
||||
for href, title, pic in items2:
|
||||
if href and title:
|
||||
href = self.host + href if href.startswith('/') else self.host + '/' + href
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
|
||||
if pic and pic.startswith('//'):
|
||||
pic = 'https:' + pic
|
||||
videos.append({
|
||||
'vod_id': href,
|
||||
'vod_name': title,
|
||||
'vod_pic': pic,
|
||||
'vod_remarks': ''
|
||||
})
|
||||
|
||||
pagecount = page
|
||||
page_match = re.search(r'<span>\d+/(\d+)</span>', html)
|
||||
if page_match:
|
||||
pagecount = int(page_match.group(1))
|
||||
else:
|
||||
if re.search(r'<a[^>]*href="[^"]*-\d+\.html"[^>]*>下一页</a>', html):
|
||||
pagecount = page + 1
|
||||
else:
|
||||
pagecount = page
|
||||
|
||||
return {
|
||||
'list': videos,
|
||||
'page': page,
|
||||
'pagecount': pagecount,
|
||||
'limit': len(videos),
|
||||
'total': pagecount * len(videos) if videos else 999999
|
||||
}
|
||||
|
||||
# ==================== 详情 ====================
|
||||
def detailContent(self, ids):
|
||||
vid = ids[0] if ids else ''
|
||||
if not vid:
|
||||
return {'list': []}
|
||||
|
||||
if not vid.startswith('http'):
|
||||
vid = self.host + vid if vid.startswith('/') else self.host + '/' + vid
|
||||
|
||||
html = self.fetch(vid)
|
||||
if not html:
|
||||
return {'list': []}
|
||||
|
||||
info = {'vod_id': vid}
|
||||
|
||||
# ====== 标题 ======
|
||||
title_match = re.search(r'<h1[^>]*class="v_title[^"]*"[^>]*>(.*?)</h1>', html, re.DOTALL)
|
||||
info['vod_name'] = self.clean_html(title_match.group(1)) if title_match else '未知'
|
||||
|
||||
# ====== 海报 ======
|
||||
pic_match = re.search(r'<img[^>]*class="lazyload"[^>]*data-original="([^"]+)"', html)
|
||||
if pic_match:
|
||||
pic = pic_match.group(1)
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
|
||||
if pic and pic.startswith('//'):
|
||||
pic = 'https:' + pic
|
||||
info['vod_pic'] = pic
|
||||
else:
|
||||
info['vod_pic'] = ''
|
||||
|
||||
# ====== 简介 ======
|
||||
desc_match = re.search(r'<p[^>]*class="p_txt[^"]*"[^>]*>(.*?)</p>', html, re.DOTALL)
|
||||
info['vod_content'] = self.clean_html(desc_match.group(1)) if desc_match else ''
|
||||
|
||||
# ====== 导演、演员、年份、类型、地区 ======
|
||||
info['vod_director'] = ''
|
||||
info['vod_actor'] = ''
|
||||
info['vod_year'] = ''
|
||||
info['type_name'] = ''
|
||||
info['vod_area'] = ''
|
||||
|
||||
info_p = re.search(r'<p>(.*?)</p>', html, re.DOTALL)
|
||||
if info_p:
|
||||
text = info_p.group(1)
|
||||
# 去除<a>标签
|
||||
text = re.sub(r'<a[^>]*>.*?</a>', '', text)
|
||||
text = self.clean_html(text)
|
||||
|
||||
# 用 / 分割
|
||||
parts = text.split('/')
|
||||
for part in parts:
|
||||
part = part.strip()
|
||||
if not part:
|
||||
continue
|
||||
|
||||
# 导演
|
||||
if '导演:' in part:
|
||||
info['vod_director'] = part.replace('导演:', '').strip()
|
||||
# 主演
|
||||
elif '主演:' in part:
|
||||
info['vod_actor'] = part.replace('主演:', '').strip()
|
||||
# 如果只有纯文本且没有冒号
|
||||
elif ':' not in part:
|
||||
if re.match(r'^\d{4}$', part):
|
||||
info['vod_year'] = part
|
||||
elif part in ['大陆', '美国', '日本', '韩国', '英国', '法国', '德国', '意大利', '西班牙', '加拿大', '澳大利亚', '台湾', '香港', '泰国', '印度', '其他']:
|
||||
info['vod_area'] = part
|
||||
else:
|
||||
info['type_name'] = part
|
||||
|
||||
# 如果没提取到年份,从其他地方匹配
|
||||
if not info['vod_year']:
|
||||
year_match = re.search(r'(\d{4})</a>', html)
|
||||
if year_match:
|
||||
info['vod_year'] = year_match.group(1)
|
||||
|
||||
# ====== 播放列表 ======
|
||||
play_from = []
|
||||
play_url = []
|
||||
|
||||
from_match = re.search(r'<ul[^>]*class="from_list"[^>]*>(.*?)</ul>', html, re.DOTALL)
|
||||
link_match = re.search(r'<ul[^>]*class="play_list"[^>]*id="play_link"[^>]*>(.*?)</ul>', html, re.DOTALL)
|
||||
|
||||
if from_match and link_match:
|
||||
names = re.findall(r'<li[^>]*>(.*?)</li>', from_match.group(1), re.DOTALL)
|
||||
names = [self.clean_html(n) for n in names if n.strip()]
|
||||
link_blocks = re.findall(r'<li[^>]*>(.*?)</li>', link_match.group(1), re.DOTALL)
|
||||
|
||||
if names and link_blocks and len(names) == len(link_blocks):
|
||||
for idx, block in enumerate(link_blocks):
|
||||
eps = re.findall(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', block, re.DOTALL)
|
||||
source_name = names[idx] if idx < len(names) else f"线路{idx+1}"
|
||||
episodes = []
|
||||
for ep_href, ep_name in eps:
|
||||
ep_name = ep_name.strip()
|
||||
if ep_name and ep_href:
|
||||
if not ep_href.startswith('/'):
|
||||
ep_href = '/' + ep_href
|
||||
num = re.sub(r'[^0-9]', '', ep_name)
|
||||
if num:
|
||||
num = int(num)
|
||||
else:
|
||||
num = len(episodes) + 1
|
||||
episodes.append((num, f'{ep_name}${ep_href}'))
|
||||
if episodes:
|
||||
episodes.sort(key=lambda x: x[0])
|
||||
play_from.append(source_name)
|
||||
play_url.append('#'.join([ep[1] for ep in episodes]))
|
||||
|
||||
if not play_from:
|
||||
panels = re.findall(
|
||||
r'<div[^>]*class="myui-panel_bd[^"]*"[^>]*>.*?<ul[^>]*class="myui-content__list[^"]*"[^>]*>(.*?)</ul>',
|
||||
html, re.DOTALL
|
||||
)
|
||||
titles = re.findall(r'<h3[^>]*class="title"[^>]*>(.*?)</h3>', html)
|
||||
if panels:
|
||||
for idx, panel in enumerate(panels):
|
||||
eps = re.findall(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', panel, re.DOTALL)
|
||||
source_name = titles[idx] if idx < len(titles) else f"线路{idx+1}"
|
||||
episodes = []
|
||||
for ep_href, ep_name in eps:
|
||||
ep_name = ep_name.strip()
|
||||
if ep_name and ep_href:
|
||||
if not ep_href.startswith('/'):
|
||||
ep_href = '/' + ep_href
|
||||
num = re.sub(r'[^0-9]', '', ep_name)
|
||||
if num:
|
||||
num = int(num)
|
||||
else:
|
||||
num = len(episodes) + 1
|
||||
episodes.append((num, f'{ep_name}${ep_href}'))
|
||||
if episodes:
|
||||
episodes.sort(key=lambda x: x[0])
|
||||
play_from.append(source_name)
|
||||
play_url.append('#'.join([ep[1] for ep in episodes]))
|
||||
|
||||
if not play_from:
|
||||
all_eps = re.findall(r'<a[^>]*href="(/play/[^"]+)"[^>]*>([^<]+)</a>', html, re.DOTALL)
|
||||
if all_eps:
|
||||
episodes = []
|
||||
for ep_href, ep_name in all_eps:
|
||||
ep_name = ep_name.strip()
|
||||
if ep_name and ep_href:
|
||||
if not ep_href.startswith('/'):
|
||||
ep_href = '/' + ep_href
|
||||
num = re.sub(r'[^0-9]', '', ep_name)
|
||||
if num:
|
||||
num = int(num)
|
||||
else:
|
||||
num = len(episodes) + 1
|
||||
episodes.append((num, f'{ep_name}${ep_href}'))
|
||||
if episodes:
|
||||
episodes.sort(key=lambda x: x[0])
|
||||
play_from.append("骚火影视")
|
||||
play_url.append('#'.join([ep[1] for ep in episodes]))
|
||||
|
||||
info["vod_play_from"] = '$$$'.join(play_from) if play_from else "骚火影视"
|
||||
info["vod_play_url"] = '$$$'.join(play_url) if play_url else ""
|
||||
|
||||
return {'list': [info]}
|
||||
|
||||
# ==================== 搜索 ====================
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
page = int(pg) if pg else 1
|
||||
encoded_key = urllib.parse.quote(key, safe='')
|
||||
url = f"{self.host}/s----------.html?wd={encoded_key}"
|
||||
|
||||
html = self.fetch(url)
|
||||
if not html:
|
||||
return {'list': [], 'page': page, 'pagecount': 0, 'limit': 90, 'total': 0}
|
||||
|
||||
videos = []
|
||||
li_pattern = r'<li[^>]*class="clearfix"[^>]*>(.*?)</li>'
|
||||
items = re.findall(li_pattern, html, re.DOTALL)
|
||||
|
||||
for item in items:
|
||||
pic = ''
|
||||
pic_match = re.search(r'data-original="([^"]+)"', item)
|
||||
if pic_match:
|
||||
pic = pic_match.group(1)
|
||||
|
||||
title_match = re.search(r'<a[^>]*href="([^"]+)"[^>]*title="([^"]+)"', item)
|
||||
if not title_match:
|
||||
continue
|
||||
|
||||
href = title_match.group(1)
|
||||
title = title_match.group(2).strip()
|
||||
|
||||
remark = ''
|
||||
remark_match = re.search(r'<span[^>]*class="pic-text[^"]*"[^>]*>(.*?)</span>', item)
|
||||
if remark_match:
|
||||
remark = self.clean_html(remark_match.group(1))
|
||||
|
||||
if href and title:
|
||||
if not href.startswith('http'):
|
||||
href = self.host + href if href.startswith('/') else self.host + '/' + href
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = self.host + pic if pic.startswith('/') else self.host + '/' + pic
|
||||
if pic and pic.startswith('//'):
|
||||
pic = 'https:' + pic
|
||||
videos.append({
|
||||
'vod_id': href,
|
||||
'vod_name': title,
|
||||
'vod_pic': pic,
|
||||
'vod_remarks': remark
|
||||
})
|
||||
|
||||
return {'list': videos, 'page': page, 'pagecount': 999, 'limit': 90, 'total': 999999}
|
||||
|
||||
# ==================== 播放 ====================
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
"""
|
||||
播放接口 - 完全模仿JS版本的play函数
|
||||
"""
|
||||
if id.startswith('http'):
|
||||
final_url = id
|
||||
else:
|
||||
if id.startswith('/'):
|
||||
final_url = self.host.rstrip('/') + id
|
||||
else:
|
||||
final_url = self.host.rstrip('/') + '/' + id
|
||||
|
||||
return {"parse": 1, "url": final_url, "jx": 0}
|
||||
|
||||
def localProxy(self, param):
|
||||
return None
|
||||
Reference in New Issue
Block a user