Files
xvip/py/枫叶影院.py
2026-08-15 10:30:30 +02:00

393 lines
22 KiB
Python

# -*- coding: utf-8 -*-
import re, urllib.parse
import json
from bs4 import BeautifulSoup
import requests
from base.spider import Spider as BaseSpider
class Spider(BaseSpider):
def init(self, extend=""):
self.host = "https://maihaolian.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",
"Accept-Language": "zh-CN,zh;q=0.9",
}
def getName(self):
return '枫叶影院'
def homeContent(self, filter):
return {"class": [
{'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": "综艺"},
], "filters": self._build_filters()}
def _build_filters(self):
area = [{"n": "全部", "v": ""}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"},
{"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"},
{"n": "日本", "v": "日本"}, {"n": "泰国", "v": "泰国"}, {"n": "新加坡", "v": "新加坡"},
{"n": "马来西亚", "v": "马来西亚"}, {"n": "印度", "v": "印度"}, {"n": "英国", "v": "英国"},
{"n": "法国", "v": "法国"}, {"n": "加拿大", "v": "加拿大"}, {"n": "西班牙", "v": "西班牙"},
{"n": "俄罗斯", "v": "俄罗斯"}, {"n": "其它", "v": "其它"}]
year = [{"n": "全部", "v": ""}, {"n": "2026", "v": "2026"}, {"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"}, {"n": "2023", "v": "2023"}, {"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"}, {"n": "2020", "v": "2020"}, {"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"}, {"n": "2017", "v": "2017"}, {"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"}, {"n": "2014", "v": "2014"}, {"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"}, {"n": "2011", "v": "2011"}, {"n": "2010", "v": "2010"},
{"n": "2009", "v": "2009"}, {"n": "2008", "v": "2008"}, {"n": "2007", "v": "2007"},
{"n": "2006", "v": "2006"}, {"n": "2005", "v": "2005"}, {"n": "2004", "v": "2004"}]
lang = [{"n": "全部", "v": ""}, {"n": "国语", "v": "国语"}, {"n": "英语", "v": "英语"},
{"n": "粤语", "v": "粤语"}, {"n": "闽南语", "v": "闽南语"}, {"n": "韩语", "v": "韩语"},
{"n": "日语", "v": "日语"}, {"n": "法语", "v": "法语"}, {"n": "德语", "v": "德语"},
{"n": "其它", "v": "其它"}]
sort = [{"n": "时间", "v": "time"}, {"n": "人气", "v": "hits"}, {"n": "评分", "v": "score"}]
letter = [{"n": "全部", "v": ""}, {"n": "A", "v": "A"}, {"n": "B", "v": "B"}, {"n": "C", "v": "C"},
{"n": "D", "v": "D"}, {"n": "E", "v": "E"}, {"n": "F", "v": "F"}, {"n": "G", "v": "G"},
{"n": "H", "v": "H"}, {"n": "I", "v": "I"}, {"n": "J", "v": "J"}, {"n": "K", "v": "K"},
{"n": "L", "v": "L"}, {"n": "M", "v": "M"}, {"n": "N", "v": "N"}, {"n": "O", "v": "O"},
{"n": "P", "v": "P"}, {"n": "Q", "v": "Q"}, {"n": "R", "v": "R"}, {"n": "S", "v": "S"},
{"n": "T", "v": "T"}, {"n": "U", "v": "U"}, {"n": "V", "v": "V"}, {"n": "W", "v": "W"},
{"n": "X", "v": "X"}, {"n": "Y", "v": "Y"}, {"n": "Z", "v": "Z"}, {"n": "0-9", "v": "0-9"}]
return {
"2": [
{"key": "class", "name": "类型",
"value": [{"n": "全部", "v": "2"}, {"n": "国产剧", "v": "13"}, {"n": "日韩剧", "v": "15"},
{"n": "海外剧", "v": "16"}]},
{"key": "area", "name": "地区", "value": area},
{"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in
[("全部", ""), ("古装", "古装"), ("战争", "战争"),
("青春偶像", "青春偶像"), ("喜剧", "喜剧"),
("家庭", "家庭"), ("犯罪", "犯罪"), ("动作", "动作"),
("奇幻", "奇幻"), ("剧情", "剧情"), ("历史", "历史"),
("经典", "经典"), ("乡村", "乡村"), ("情景", "情景"),
("商战", "商战"), ("网剧", "网剧"), ("其他", "其他")]]},
{"key": "year", "name": "年份", "value": year},
{"key": "lang", "name": "语言", "value": lang},
{"key": "letter", "name": "字母", "value": letter},
{"key": "sort", "name": "排序", "value": sort},
],
"1": [
{"key": "class", "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": "20"}]},
{"key": "area", "name": "地区", "value": area},
{"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in
[("全部", ""), ("喜剧", "喜剧"), ("爱情", "爱情"),
("恐怖", "恐怖"), ("动作", "动作"), ("科幻", "科幻"),
("剧情", "剧情"), ("战争", "战争"), ("警匪", "警匪"),
("犯罪", "犯罪"), ("动画", "动画"), ("奇幻", "奇幻"),
("武侠", "武侠"), ("冒险", "冒险"), ("枪战", "枪战"),
("悬疑", "悬疑"), ("惊悚", "惊悚"), ("经典", "经典"),
("青春", "青春"), ("文艺", "文艺"), ("微电影", "微电影"),
("古装", "古装"), ("历史", "历史"), ("运动", "运动"),
("农村", "农村"), ("儿童", "儿童"),
("网络电影", "网络电影")]]},
{"key": "year", "name": "年份", "value": year},
{"key": "lang", "name": "语言", "value": lang},
{"key": "letter", "name": "字母", "value": letter},
{"key": "sort", "name": "排序", "value": sort},
],
"4": [
{"key": "class", "name": "类型",
"value": [{"n": "全部", "v": "4"}, {"n": "国产动漫", "v": "25"}, {"n": "日韩动漫", "v": "26"}]},
{"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in
[("全部", ""), ("情感", "情感"), ("科幻", "科幻"),
("热血", "热血"), ("推理", "推理"), ("搞笑", "搞笑"),
("冒险", "冒险"), ("奇幻", "奇幻"), ("战斗", "战斗"),
("校园", "校园"), ("萝莉", "萝莉"), ("治愈", "治愈"),
("原创", "原创"), ("亲子", "亲子"), ("益智", "益智"),
("励志", "励志"), ("其他", "其他")]]},
{"key": "area", "name": "地区",
"value": [{"n": "全部", "v": ""}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"},
{"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"},
{"n": "日本", "v": "日本"}, {"n": "法国", "v": "法国"}, {"n": "英国", "v": "英国"},
{"n": "其它", "v": "其它"}]},
{"key": "year", "name": "年份", "value": year},
{"key": "lang", "name": "语言", "value": lang},
{"key": "letter", "name": "字母", "value": letter},
{"key": "sort", "name": "排序", "value": sort},
],
"3": [
{"key": "class", "name": "类型",
"value": [{"n": "全部", "v": "3"}, {"n": "大陆综艺", "v": "21"}, {"n": "日韩综艺", "v": "22"}]},
{"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in
[("全部", ""), ("选秀", "选秀"), ("情感", "情感"),
("访谈", "访谈"), ("播报", "播报"), ("音乐", "音乐"),
("美食", "美食"), ("旅游", "旅游"), ("搞笑", "搞笑"),
("游戏", "游戏"), ("亲子", "亲子"), ("其它", "其它")]]},
{"key": "area", "name": "地区",
"value": [{"n": "全部", "v": ""}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"},
{"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"},
{"n": "日本", "v": "日本"}, {"n": "英国", "v": "英国"}, {"n": "其它", "v": "其它"}]},
{"key": "year", "name": "年份", "value": year},
{"key": "lang", "name": "语言", "value": lang},
{"key": "letter", "name": "字母", "value": letter},
{"key": "sort", "name": "排序", "value": sort},
],
}
def homeVideoContent(self):
html = self._fetch('/')
return {"list": self._parse_video_list(html)}
def categoryContent(self, tid, pg, filter, extend):
# 构建筛选参数:参照歪比巴卜,直接取extend里的值,fallback到filter
if tid.startswith('/label'):
url = f'{tid}/page/{pg}.html'
html = self._fetch(url)
items = self._parse_video_list(html)
page = int(pg)
page_count = page if len(items) < 24 else page + 2
return {"list": items, "page": page, "pagecount": page_count, "limit": 24, "total": page_count * 24}
args = {}
if extend and isinstance(extend, dict):
for k, v in extend.items():
if v:
args[k] = str(v)
if isinstance(filter, dict):
for k, v in filter.items():
if v and k not in args:
args[k] = str(v)
route_tid = args.get('class', args.get('tid', str(tid)))
area = args.get('area', '')
genre = args.get('genre', '')
year = args.get('year', '')
lang = args.get('lang', '')
letter = args.get('letter', '')
sort = args.get('sort', '')
# 无筛选走正常分页
if not area and not genre and not year and not lang and not letter and not sort:
url = f'/cupfox-list/{route_tid}--------{pg}---.html'
html = self._fetch(url)
items = self._parse_video_list(html)
page = int(pg)
soup = BeautifulSoup(html, 'html.parser')
pagecount = page
for a in soup.select('a.page-link'):
if a.text == '尾页':
m = re.search(r'---(\d+)---', a.get('href', ''))
if m:
pagecount = int(m.group(1))
break
if not items:
pagecount = 0
return {"list": items, "page": page, "pagecount": pagecount, "limit": 36, "total": 9999}
# 有筛选:{tid}-{area}-{sort}-{genre}-{lang}-{letter}------{year}.html
segs = [route_tid, area, sort, genre, lang, letter, '', '', year]
url = '/cupfox-list/' + '-'.join(segs) + '.html'
html = self._fetch(url)
items = self._parse_video_list(html)
return {"list": items, "page": 1, "pagecount": 1, "limit": 36, "total": 9999}
def detailContent(self, ids):
result = {"list": []}
vid = ids[0].split(',')[0].strip()
try:
html = self._fetch(f'/detail/{vid}.html')
if not html: return result
soup = BeautifulSoup(html, 'html.parser')
vod_name = soup.select_one('h3.slide-info-title')
vod_name = vod_name.text.strip() if vod_name else ''
vod_pic = soup.select_one('img.lazy')
vod_pic = self._fix_pic(vod_pic.get('data-src', '')) if vod_pic else ''
vod_director = ''
vod_actor = ''
for el in soup.select('.slide-info'):
text = el.get_text(' ').strip()
if text.startswith('导演:'):
vod_director = text.replace('导演:', '').strip()
elif text.startswith('演员:'):
vod_actor = text.replace('演员:', '').strip()
vod_content = soup.select_one('#height_limit')
vod_content = vod_content.get_text(' ', strip=True) if vod_content else ''
play_from, play_url = [], []
for tab in soup.select('.anthology-tab a.swiper-slide'):
src_name = re.sub(r'<[^>]+>', '', str(tab)).strip() or tab.get_text(' ', strip=True).strip()
if src_name:
play_from.append(src_name)
tab_blocks = soup.select('.anthology-list-box')
for i, block in enumerate(tab_blocks):
ep_list = []
for a in block.select('li a'):
href = a.get('href', '')
m = re.search(r'/play/(.*?)\.html', href)
if m:
ep_list.append(f'{a.text.strip()}${vid}-{m.group(1)}')
ep_list.reverse()
if ep_list and i < len(play_from):
play_url.append('#'.join(ep_list))
valid_from = [pf for i, pf in enumerate(play_from) if i < len(play_url)]
result["list"].append({
"vod_id": vid, "vod_name": vod_name, "vod_pic": vod_pic,
"vod_director": vod_director, "vod_actor": vod_actor,
"vod_content": vod_content,
"vod_play_from": "$$$".join(valid_from),
"vod_play_url": "$$$".join(play_url),
})
except:
pass
return result
def searchContent(self, key, quick, pg="1"):
try:
decoded = urllib.parse.unquote(key)
except:
decoded = key
html = self._fetch(f'/cupfox-search/{urllib.parse.quote(decoded)}----------{pg}---.html')
items = self._parse_search_list(html)
return {"list": items, "page": int(pg), "pagecount": 1, "limit": 36, "total": len(items)}
def playerContent(self, flag, id, vipFlags):
url = ''
try:
url = id if id.startswith('http') else f'{self.host}/play/{id}.html'
html = self._fetch(url)
if html:
m = re.search(r'player_aaaa=(.*?)</script>', html, re.S)
if m:
try:
pd = json.loads(m.group(1))
except Exception as e:
print(e)
pd = {}
# print('pd:', pd)
play_url = pd.get('url')
play_id = pd.get('from')
api_map = {
'YYNB': 'https://zzrs.mfdyvip.com/player/mplayer.php',
'JD4K': 'https://fgsrg.hzqingshan.com/player/mplayer.php',
}
if not play_url:
return {"parse": 0, "url": 'https://php.doube.eu.org/error.m3u8',
"header": {'User-Agent': 'Mozilla/5.0'}}
if play_url.startswith('http') and (play_url.endswith('.m3u8') or play_url.endswith('.mp4')):
return {"parse": 0, "url": play_url, "header": {'User-Agent': 'Mozilla/5.0'}}
else:
headers = {
'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
'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",
'cache-control': "no-cache",
'pragma': "no-cache",
'priority': "u=0, i",
'referer': "https://www.ht10010.com/",
'Content-Type': 'application/x-www-form-urlencoded',
}
response = requests.get(f"https://fgsrg.hzqingshan.com/player/?url={play_url}", headers=headers)
token = re.search(r'data-te="(.*?)"', response.text)
if token:
token = token.group(1)
payload = {
'url': play_url,
'token': token
}
# print('payload', payload)
try:
response = self.post(api_map[play_id], data=payload, headers=headers)
response.raise_for_status()
result = response.json()
# print('result:', result)
if result['code'] == 200 and 'url' in result:
play_url = result['url']
return {"parse": 0, "url": play_url, "header": {
'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'}}
except Exception as e:
print(e)
except Exception as e:
print(e)
return {"parse": 1, "url": url}
def localProxy(self, param=''):
return {}
def isVideoFormat(self, url):
return False
def manualVideoCheck(self):
return False
def _fetch(self, url):
try:
if not url.startswith('http'):
url = self.host + url
rsp = self.fetch(url, headers=self.headers)
return rsp.text if rsp else ''
except:
return ''
def _fix_pic(self, u):
if not u: return ''
if u.startswith('//'): return 'https:' + u
return u.replace('&amp;', '&')
def _parse_video_list(self, html):
videos, seen = [], set()
soup = BeautifulSoup(html, 'html.parser')
cards = soup.select('a.public-list-exp')
for a in cards:
href = a.get('href', '')
m = re.search(r'/detail/(\d+)\.html', href)
if not m: continue
vod_id = m.group(1)
if vod_id in seen: continue
seen.add(vod_id)
span = ','.join([span.text for span in a.select('span.public-prt')])
# print('span', span)
vod_name = a.get('title', '') or (a.select_one('img') and a.select_one('img').get('alt', '')) or ''
pic_el = a.select_one('img')
vod_pic = self._fix_pic(pic_el.get('data-src', '')) if pic_el else ''
remark_el = a.select_one('.ft2') or a.select_one('.public-list-prb')
vod_remarks = remark_el.text.strip() if remark_el else ''
videos.append(
{"vod_id": vod_id, "vod_name": vod_name.strip(), "vod_pic": vod_pic, "vod_remarks": vod_remarks, "vod_year": span})
return videos
def _parse_search_list(self, html):
videos, seen = [], set()
soup = BeautifulSoup(html, 'html.parser')
cards = soup.select('a.public-list-exp')
for a in cards:
href = a.get('href', '')
m = re.search(r'/detail/(\d+)\.html', href)
if not m: continue
vod_id = m.group(1)
if vod_id in seen: continue
seen.add(vod_id)
pic_el = a.select_one('img')
vod_pic = self._fix_pic(pic_el.get('data-src', '')) if pic_el else ''
title_el = soup.select_one(f'a.thumb-txt[href="/detail/{vod_id}.html"]')
if title_el:
vod_name = title_el.text.strip()
else:
vod_name = a.select_one('img') and a.select_one('img').get('alt', '') or ''
remark_el = a.select_one('.public-list-prb') or a.select_one('.ft2')
vod_remarks = remark_el.text.strip() if remark_el else ''
videos.append(
{"vod_id": vod_id, "vod_name": vod_name.strip(), "vod_pic": vod_pic, "vod_remarks": vod_remarks})
return videos
if __name__ == '__main__':
sp = Spider()
sp.init()
# 20067-5-189
print(sp.categoryContent('/label/qq','1',True, {}))
# print(sp.playerContent('', '20067-6-189', []))
# print(sp.playerContent('', '20067-5-189', []))
pass