Files
xbpq/初见影视.py
T
2026-07-23 08:08:41 +02:00

622 lines
24 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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