Sync all projects
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,389 @@
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import hashlib
|
||||
from base64 import b64decode, b64encode
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from pyquery import PyQuery as pq
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider as BaseSpider
|
||||
|
||||
img_cache = {}
|
||||
|
||||
class Spider(BaseSpider):
|
||||
|
||||
def init(self, extend=""):
|
||||
try:
|
||||
self.proxies = json.loads(extend)
|
||||
except:
|
||||
self.proxies = {}
|
||||
self.headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Connection': 'keep-alive',
|
||||
'Cache-Control': 'no-cache',
|
||||
}
|
||||
self.host = self.get_working_host()
|
||||
self.headers.update({'Origin': self.host, 'Referer': f"{self.host}/"})
|
||||
print(f"使用站点: {self.host}")
|
||||
|
||||
def getName(self):
|
||||
return "🌈 91吃瓜中心|终极完美版"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return any(ext in (url or '') for ext in ['.m3u8', '.mp4', '.ts'])
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
global img_cache
|
||||
img_cache.clear()
|
||||
|
||||
def get_working_host(self):
|
||||
dynamic_urls = [
|
||||
'https://but.vncchqw.cc/'
|
||||
]
|
||||
for url in dynamic_urls:
|
||||
try:
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=10)
|
||||
if response.status_code == 200:
|
||||
return url
|
||||
except Exception:
|
||||
continue
|
||||
return dynamic_urls[0]
|
||||
|
||||
def homeContent(self, filter):
|
||||
try:
|
||||
response = requests.get(self.host, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
if response.status_code != 200: return {'class': [], 'list': []}
|
||||
data = self.getpq(response.text)
|
||||
|
||||
classes = []
|
||||
category_selectors = ['.category-list ul li', '.nav-menu li', '.menu li', 'nav ul li']
|
||||
for selector in category_selectors:
|
||||
for k in data(selector).items():
|
||||
link = k('a')
|
||||
href = (link.attr('href') or '').strip()
|
||||
name = (link.text() or '').strip()
|
||||
if not href or href == '#' or not name: continue
|
||||
classes.append({'type_name': name, 'type_id': href})
|
||||
if classes: break
|
||||
|
||||
if not classes:
|
||||
classes = [{'type_name': '最新', 'type_id': '/latest/'}, {'type_name': '热门', 'type_id': '/hot/'}]
|
||||
|
||||
return {'class': classes, 'list': self.getlist(data('#index article, article'))}
|
||||
except Exception as e:
|
||||
return {'class': [], 'list': []}
|
||||
|
||||
def homeVideoContent(self):
|
||||
try:
|
||||
response = requests.get(self.host, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
if response.status_code != 200: return {'list': []}
|
||||
data = self.getpq(response.text)
|
||||
return {'list': self.getlist(data('#index article, article'))}
|
||||
except Exception as e:
|
||||
return {'list': []}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
try:
|
||||
if '@folder' in tid:
|
||||
v = self.getfod(tid.replace('@folder', ''))
|
||||
return {'list': v, 'page': 1, 'pagecount': 1, 'limit': 90, 'total': len(v)}
|
||||
|
||||
pg = int(pg) if pg else 1
|
||||
|
||||
if tid.startswith('http'):
|
||||
base_url = tid.rstrip('/')
|
||||
else:
|
||||
path = tid if tid.startswith('/') else f"/{tid}"
|
||||
base_url = f"{self.host}{path}".rstrip('/')
|
||||
|
||||
if pg == 1:
|
||||
url = f"{base_url}/"
|
||||
else:
|
||||
url = f"{base_url}/{pg}/"
|
||||
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
if response.status_code != 200: return {'list': [], 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 0}
|
||||
|
||||
data = self.getpq(response.text)
|
||||
videos = self.getlist(data('#archive article, #index article, article'), tid)
|
||||
|
||||
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
|
||||
except Exception as e:
|
||||
return {'list': [], 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 0}
|
||||
|
||||
def detailContent(self, ids):
|
||||
try:
|
||||
url = ids[0] if ids[0].startswith('http') else f"{self.host}{ids[0]}"
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
data = self.getpq(response.text)
|
||||
|
||||
plist = []
|
||||
used_names = set()
|
||||
if data('.dplayer'):
|
||||
for c, k in enumerate(data('.dplayer').items(), start=1):
|
||||
try:
|
||||
config_attr = k.attr('data-config')
|
||||
if config_attr:
|
||||
config = json.loads(config_attr)
|
||||
video_url = config.get('video', {}).get('url', '')
|
||||
|
||||
if video_url:
|
||||
ep_name = ''
|
||||
parent = k.parents().eq(0)
|
||||
for _ in range(4):
|
||||
if not parent: break
|
||||
heading = parent.find('h2, h3, h4').eq(0).text().strip()
|
||||
if heading:
|
||||
ep_name = heading
|
||||
break
|
||||
parent = parent.parents().eq(0)
|
||||
|
||||
base_name = ep_name if ep_name else f"视频{c}"
|
||||
name = base_name
|
||||
count = 2
|
||||
while name in used_names:
|
||||
name = f"{base_name} {count}"
|
||||
count += 1
|
||||
used_names.add(name)
|
||||
|
||||
plist.append(f"{name}${video_url}")
|
||||
except: continue
|
||||
|
||||
if not plist:
|
||||
content_area = data('.post-content, article')
|
||||
for i, link in enumerate(content_area('a').items(), start=1):
|
||||
link_text = link.text().strip()
|
||||
link_href = link.attr('href')
|
||||
|
||||
if link_href and any(kw in link_text for kw in ['点击观看', '观看', '播放', '视频', '第一弹', '第二弹', '第三弹', '第四弹', '第五弹', '第六弹', '第七弹', '第八弹', '第九弹', '第十弹']):
|
||||
ep_name = link_text.replace('点击观看:', '').replace('点击观看', '').strip()
|
||||
if not ep_name: ep_name = f"视频{i}"
|
||||
|
||||
if not link_href.startswith('http'):
|
||||
link_href = f"{self.host}{link_href}" if link_href.startswith('/') else f"{self.host}/{link_href}"
|
||||
|
||||
plist.append(f"{ep_name}${link_href}")
|
||||
|
||||
play_url = '#'.join(plist) if plist else f"未找到视频源${url}"
|
||||
|
||||
vod_content = ''
|
||||
try:
|
||||
tags = []
|
||||
seen_names = set()
|
||||
seen_ids = set()
|
||||
|
||||
tag_links = data('.tags a, .keywords a, .post-tags a')
|
||||
|
||||
candidates = []
|
||||
for k in tag_links.items():
|
||||
title = k.text().strip()
|
||||
href = k.attr('href')
|
||||
if title and href:
|
||||
candidates.append({'name': title, 'id': href})
|
||||
|
||||
candidates.sort(key=lambda x: len(x['name']), reverse=True)
|
||||
|
||||
for item in candidates:
|
||||
name = item['name']
|
||||
id_ = item['id']
|
||||
|
||||
if id_ in seen_ids: continue
|
||||
|
||||
is_duplicate = False
|
||||
for seen in seen_names:
|
||||
if name in seen:
|
||||
is_duplicate = True
|
||||
break
|
||||
|
||||
if not is_duplicate:
|
||||
target = json.dumps({'id': id_, 'name': name})
|
||||
tags.append(f'[a=cr:{target}/]{name}[/a]')
|
||||
seen_names.add(name)
|
||||
seen_ids.add(id_)
|
||||
|
||||
if tags:
|
||||
vod_content = ' '.join(tags)
|
||||
else:
|
||||
vod_content = data('.post-title').text()
|
||||
except Exception:
|
||||
vod_content = '获取标签失败'
|
||||
|
||||
if not vod_content:
|
||||
vod_content = data('h1').text() or '91吃瓜中心'
|
||||
|
||||
return {'list': [{'vod_play_from': '91吃瓜中心', 'vod_play_url': play_url, 'vod_content': vod_content}]}
|
||||
except:
|
||||
return {'list': [{'vod_play_from': '91吃瓜中心', 'vod_play_url': '获取失败'}]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
try:
|
||||
pg = int(pg) if pg else 1
|
||||
|
||||
if pg == 1:
|
||||
url = f"{self.host}/search/{key}/"
|
||||
else:
|
||||
url = f"{self.host}/search/{key}/{pg}/"
|
||||
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
return {'list': self.getlist(self.getpq(response.text)('article')), 'page': pg, 'pagecount': 9999}
|
||||
except:
|
||||
return {'list': [], 'page': pg, 'pagecount': 9999}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
parse = 0 if self.isVideoFormat(id) else 1
|
||||
url = self.proxy(id) if '.m3u8' in id else id
|
||||
return {'parse': parse, 'url': url, 'header': self.headers}
|
||||
|
||||
def localProxy(self, param):
|
||||
try:
|
||||
type_ = param.get('type')
|
||||
url = param.get('url')
|
||||
if type_ == 'cache':
|
||||
key = param.get('key')
|
||||
if content := img_cache.get(key):
|
||||
return [200, 'image/jpeg', content]
|
||||
return [404, 'text/plain', b'Expired']
|
||||
elif type_ == 'img':
|
||||
real_url = self.d64(url) if not url.startswith('http') else url
|
||||
res = requests.get(real_url, headers=self.headers, proxies=self.proxies, timeout=10)
|
||||
content = self.aesimg(res.content)
|
||||
return [200, 'image/jpeg', content]
|
||||
elif type_ == 'm3u8':
|
||||
return self.m3Proxy(url)
|
||||
else:
|
||||
return self.tsProxy(url)
|
||||
except:
|
||||
return [404, 'text/plain', b'']
|
||||
|
||||
def proxy(self, data, type='m3u8'):
|
||||
if data and self.proxies: return f"{self.getProxyUrl()}&url={self.e64(data)}&type={type}"
|
||||
return data
|
||||
|
||||
def m3Proxy(self, url):
|
||||
url = self.d64(url)
|
||||
res = requests.get(url, headers=self.headers, proxies=self.proxies)
|
||||
data = res.text
|
||||
base = res.url.rsplit('/', 1)[0]
|
||||
lines = []
|
||||
for line in data.split('\n'):
|
||||
if '#EXT' not in line and line.strip():
|
||||
if not line.startswith('http'):
|
||||
line = f"{base}/{line}"
|
||||
lines.append(self.proxy(line, 'ts'))
|
||||
else:
|
||||
lines.append(line)
|
||||
return [200, "application/vnd.apple.mpegurl", '\n'.join(lines)]
|
||||
|
||||
def tsProxy(self, url):
|
||||
return [200, 'video/mp2t', requests.get(self.d64(url), headers=self.headers, proxies=self.proxies).content]
|
||||
|
||||
def e64(self, text):
|
||||
return b64encode(str(text).encode()).decode()
|
||||
|
||||
def d64(self, text):
|
||||
return b64decode(str(text).encode()).decode()
|
||||
|
||||
def aesimg(self, data):
|
||||
if len(data) < 16: return data
|
||||
keys = [(b'f5d965df75336270', b'97b60394abc2fbe1'), (b'75336270f5d965df', b'abc2fbe197b60394')]
|
||||
for k, v in keys:
|
||||
try:
|
||||
dec = unpad(AES.new(k, AES.MODE_CBC, v).decrypt(data), 16)
|
||||
if dec.startswith(b'\xff\xd8') or dec.startswith(b'\x89PNG'): return dec
|
||||
except: pass
|
||||
try:
|
||||
dec = unpad(AES.new(k, AES.MODE_ECB).decrypt(data), 16)
|
||||
if dec.startswith(b'\xff\xd8'): return dec
|
||||
except: pass
|
||||
return data
|
||||
|
||||
def getlist(self, data, tid=''):
|
||||
videos = []
|
||||
is_folder = '/mrdg' in (tid or '')
|
||||
for k in data.items():
|
||||
card_html = k.outer_html() if hasattr(k, 'outer_html') else str(k)
|
||||
a = k if k.is_('a') else k('a').eq(0)
|
||||
href = a.attr('href')
|
||||
title = k('h2').text() or k('.entry-title').text() or k('.post-title').text()
|
||||
if not title and k.is_('a'): title = k.text()
|
||||
|
||||
if href and title:
|
||||
img = self.getimg(k('script').text(), k, card_html)
|
||||
videos.append({
|
||||
'vod_id': f"{href}{'@folder' if is_folder else ''}",
|
||||
'vod_name': title.strip(),
|
||||
'vod_pic': img,
|
||||
'vod_remarks': k('time').text() or '',
|
||||
'vod_tag': 'folder' if is_folder else '',
|
||||
'style': {"type": "rect", "ratio": 1.33}
|
||||
})
|
||||
return videos
|
||||
|
||||
def getfod(self, id):
|
||||
url = f"{self.host}{id}"
|
||||
data = self.getpq(requests.get(url, headers=self.headers, proxies=self.proxies).text)
|
||||
videos = []
|
||||
for i, h2 in enumerate(data('.post-content h2').items()):
|
||||
p_txt = data('.post-content p').eq(i * 2)
|
||||
p_img = data('.post-content p').eq(i * 2 + 1)
|
||||
p_html = p_img.outer_html() if hasattr(p_img, 'outer_html') else str(p_img)
|
||||
videos.append({
|
||||
'vod_id': p_txt('a').attr('href'),
|
||||
'vod_name': p_txt.text().strip(),
|
||||
'vod_pic': self.getimg('', p_img, p_html),
|
||||
'vod_remarks': h2.text().strip()
|
||||
})
|
||||
return videos
|
||||
|
||||
def getimg(self, text, elem=None, html_content=None):
|
||||
if m := re.search(r"loadBannerDirect\('([^']+)'", text or ''):
|
||||
return self._proc_url(m.group(1))
|
||||
|
||||
if html_content is None and elem is not None:
|
||||
html_content = elem.outer_html() if hasattr(elem, 'outer_html') else str(elem)
|
||||
if not html_content: return ''
|
||||
|
||||
html_content = html_content.replace('"', '"').replace(''', "'").replace('&', '&')
|
||||
|
||||
if 'data:image' in html_content:
|
||||
m = re.search(r'(data:image/[a-zA-Z0-9+/=;,]+)', html_content)
|
||||
if m: return self._proc_url(m.group(1))
|
||||
|
||||
m = re.search(r'(https?://[^"\'\s)]+\.(?:jpg|png|jpeg|webp))', html_content, re.I)
|
||||
if m: return self._proc_url(m.group(1))
|
||||
|
||||
if 'url(' in html_content:
|
||||
m = re.search(r'url\s*\(\s*[\'"]?([^"\'\)]+)[\'"]?\s*\)', html_content, re.I)
|
||||
if m: return self._proc_url(m.group(1))
|
||||
|
||||
return ''
|
||||
|
||||
def _proc_url(self, url):
|
||||
if not url: return ''
|
||||
url = url.strip('\'" ')
|
||||
if url.startswith('data:'):
|
||||
try:
|
||||
_, b64_str = url.split(',', 1)
|
||||
raw = b64decode(b64_str)
|
||||
if not (raw.startswith(b'\xff\xd8') or raw.startswith(b'\x89PNG') or raw.startswith(b'GIF8')):
|
||||
raw = self.aesimg(raw)
|
||||
key = hashlib.md5(raw).hexdigest()
|
||||
img_cache[key] = raw
|
||||
return f"{self.getProxyUrl()}&type=cache&key={key}"
|
||||
except: return ""
|
||||
if not url.startswith('http'):
|
||||
url = f"{self.host}{url}" if url.startswith('/') else f"{self.host}/{url}"
|
||||
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=img"
|
||||
|
||||
def getpq(self, data):
|
||||
try: return pq(data)
|
||||
except: return pq(data.encode('utf-8'))
|
||||
@@ -0,0 +1,473 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵推荐 🚓 内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import binascii
|
||||
import requests
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://www.4kvm.net"
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; U; Android 8.0.0; zh-cn; Mi Note 2 Build/OPR1.170623.032) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/61.0.3163.128 Mobile Safari/537.36 XiaoMi/MiuiBrowser/10.1.1'
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
global xurl
|
||||
global headerx
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
|
||||
if pl == 3:
|
||||
plx = []
|
||||
while True:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
break
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
break
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
plx.append(middle_text)
|
||||
text = text.replace(start_str + middle_text + end_str, '')
|
||||
if len(plx) > 0:
|
||||
purl = ''
|
||||
for i in range(len(plx)):
|
||||
matches = re.findall(start_index1, plx[i])
|
||||
output = ""
|
||||
for match in matches:
|
||||
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
|
||||
if match3:
|
||||
number = match3.group(1)
|
||||
else:
|
||||
number = 0
|
||||
if 'http' not in match[0]:
|
||||
output += f"#{match[1]}${number}{xurl}{match[0]}"
|
||||
else:
|
||||
output += f"#{match[1]}${number}{match[0]}"
|
||||
output = output[1:]
|
||||
purl = purl + output + "$$$"
|
||||
purl = purl[:-3]
|
||||
return purl
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
return ""
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
return ""
|
||||
|
||||
if pl == 0:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
return middle_text.replace("\\", "")
|
||||
|
||||
if pl == 1:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
jg = ' '.join(matches)
|
||||
return jg
|
||||
|
||||
if pl == 2:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
new_list = [f'{item}' for item in matches]
|
||||
jg = '$$$'.join(new_list)
|
||||
return jg
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {"class": []}
|
||||
|
||||
detail = requests.get(url=xurl, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
soups = doc.find_all('ul', class_="main-header")
|
||||
|
||||
for soup in soups:
|
||||
vods = soup.find_all('li')
|
||||
|
||||
for vod in vods:
|
||||
|
||||
name = vod.text.strip()
|
||||
|
||||
if any(keyword in name for keyword in ["首页", "电视剧", "高分电影", "影片下载", "热门播放"]):
|
||||
continue
|
||||
|
||||
id = vod.find('a')['href']
|
||||
if 'http' not in id:
|
||||
id = xurl + id
|
||||
|
||||
result["class"].append({"type_id": id, "type_name": name})
|
||||
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
|
||||
detail = requests.get(url=xurl, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
|
||||
soups = doc.find_all('article', class_="item movies")
|
||||
|
||||
for vod in soups:
|
||||
|
||||
name = vod.find('img')['alt']
|
||||
|
||||
ids = vod.find('div', class_="poster")
|
||||
id = ids.find('a')['href']
|
||||
|
||||
pic = vod.find('img')['src']
|
||||
|
||||
remarks = vod.find('div', class_="rating")
|
||||
remark = remarks.text.strip()
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result = {'list': videos}
|
||||
return result
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
result = {}
|
||||
videos = []
|
||||
|
||||
if 'movies' not in cid:
|
||||
|
||||
if '@' in cid:
|
||||
fenge = cid.split("@")
|
||||
detail = requests.get(url=fenge[0], headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
|
||||
soups = doc.find_all('div', class_="se-c")
|
||||
|
||||
for vod in soups:
|
||||
|
||||
name = vod.text.strip()
|
||||
|
||||
id = vod.find('a')['href']
|
||||
|
||||
pic = self.extract_middle_text(str(res), '<meta property="og:image" content="', '"', 0).replace('#038;', '')
|
||||
|
||||
remark = "推荐"
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
else:
|
||||
if pg:
|
||||
page = int(pg)
|
||||
else:
|
||||
page = 1
|
||||
|
||||
url = f'{cid}/page/{str(page)}'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
|
||||
soups = doc.find_all('article', class_="item tvshows")
|
||||
|
||||
for vod in soups:
|
||||
name = vod.find('img')['alt']
|
||||
|
||||
ids = vod.find('div', class_="poster")
|
||||
id = ids.find('a')['href']
|
||||
|
||||
pic = vod.find('img')['src']
|
||||
|
||||
remarks = vod.find('div', class_="update")
|
||||
remark = remarks.text.strip()
|
||||
|
||||
video = {
|
||||
"vod_id": id + '@' + name,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_tag": "folder",
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
else:
|
||||
if pg:
|
||||
page = int(pg)
|
||||
else:
|
||||
page = 1
|
||||
|
||||
url = f'{cid}/page/{str(page)}'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
|
||||
soups = doc.find_all('div', class_="animation-2")
|
||||
|
||||
for item in soups:
|
||||
vods = item.find_all('article')
|
||||
|
||||
for vod in vods:
|
||||
|
||||
name = vod.find('img')['alt']
|
||||
|
||||
ids = vod.find('div', class_="poster")
|
||||
id = ids.find('a')['href']
|
||||
|
||||
pic = vod.find('img')['src']
|
||||
|
||||
remarks = vod.find('div', class_="rating")
|
||||
remark = remarks.text.strip()
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
if len(videos)<30:
|
||||
pagecount=1
|
||||
else:
|
||||
pagecount = 9999
|
||||
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = pagecount
|
||||
result['total'] = 999
|
||||
result['limit'] = len(videos)
|
||||
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
result = {}
|
||||
videos = []
|
||||
xianlu = ''
|
||||
bofang = ''
|
||||
|
||||
if 'movies' not in did:
|
||||
res = requests.get(url=did, headers=headerx)
|
||||
res.encoding = "utf-8"
|
||||
res = res.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
|
||||
content = '剧情介绍📢' + self.extract_middle_text(res,'<meta name="description" content="','"', 0)
|
||||
|
||||
postid = self.extract_middle_text(res, 'postid:', ',', 0)
|
||||
|
||||
res1 = self.extract_middle_text(res,'videourls:[','],', 0)
|
||||
data = json.loads(res1)
|
||||
|
||||
for vod in data:
|
||||
|
||||
name = str(vod['name'])
|
||||
|
||||
id = f"{vod['url']}@{postid}"
|
||||
|
||||
bofang = bofang + name + '$' + id + '#'
|
||||
|
||||
bofang = bofang[:-1]
|
||||
xianlu = '4K影院'
|
||||
|
||||
else:
|
||||
res = requests.get(url=did, headers=headerx)
|
||||
res.encoding = "utf-8"
|
||||
res = res.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
|
||||
content = '剧情介绍📢' + self.extract_middle_text(res, '<meta name="description" content="', '"', 0)
|
||||
|
||||
bofang = self.extract_middle_text(res, "data-postid='", "'", 0)
|
||||
xianlu = '4K影院'
|
||||
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_content": content,
|
||||
"vod_play_from": xianlu,
|
||||
"vod_play_url": bofang
|
||||
})
|
||||
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
|
||||
if '@' in id:
|
||||
fenge = id.split("@")
|
||||
|
||||
url = f'{xurl}/artplayer?id={fenge[1]}&source=0&ep={fenge[0]}'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
|
||||
expires = self.extract_middle_text(res, "expires: '", "'", 0)
|
||||
client = self.extract_middle_text(res, "client: '", "'", 0)
|
||||
nonce = self.extract_middle_text(res, "nonce: '", "'", 0)
|
||||
token = self.extract_middle_text(res, "token: '", "'", 0)
|
||||
source = self.extract_middle_text(res, "source: '", "'", 0)
|
||||
|
||||
payload = {
|
||||
"expires": expires,
|
||||
"client": client,
|
||||
"nonce": nonce,
|
||||
"token": token,
|
||||
"source": source
|
||||
}
|
||||
|
||||
response = requests.post(url=source, headers=headerx, json=payload)
|
||||
response_data = json.loads(response.text)
|
||||
url = response_data['url']
|
||||
else:
|
||||
url = f'{xurl}/artplayer?mvsource=0&id={id}&type=hls'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
|
||||
expires = self.extract_middle_text(res, "expires: '", "'", 0)
|
||||
client = self.extract_middle_text(res, "client: '", "'", 0)
|
||||
nonce = self.extract_middle_text(res, "nonce: '", "'", 0)
|
||||
token = self.extract_middle_text(res, "token: '", "'", 0)
|
||||
source = self.extract_middle_text(res, "source: '", "'", 0)
|
||||
|
||||
payload = {
|
||||
"expires": expires,
|
||||
"client": client,
|
||||
"nonce": nonce,
|
||||
"token": token,
|
||||
"source": source
|
||||
}
|
||||
|
||||
response = requests.post(url=source, headers=headerx, json=payload)
|
||||
response_data = json.loads(response.text)
|
||||
url = response_data['url']
|
||||
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = url
|
||||
result["header"] = headerx
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
result = {}
|
||||
videos = []
|
||||
|
||||
url = f'{xurl}/xssearch?s={key}'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
|
||||
soups = doc.find_all('div', class_="result-item")
|
||||
|
||||
for vod in soups:
|
||||
ids = vod.find('div', class_="title")
|
||||
id = ids.find('a')['href']
|
||||
|
||||
if 'movies' not in id:
|
||||
|
||||
name = vod.find('img')['alt']
|
||||
|
||||
pic = vod.find('img')['src']
|
||||
|
||||
remark = "推荐"
|
||||
|
||||
video = {
|
||||
"vod_id": id + '@' + name,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_tag": "folder",
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
else:
|
||||
name = vod.find('img')['alt']
|
||||
|
||||
pic = vod.find('img')['src']
|
||||
|
||||
remark = "推荐"
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 1
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.packages.urllib3.util.retry import Retry
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def getName(self):
|
||||
return "TOPTV"
|
||||
|
||||
def init(self, extend=""):
|
||||
super().init(extend)
|
||||
self.site_url = "https://toptv15.cyou"
|
||||
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": self.site_url
|
||||
}
|
||||
self.sess = requests.Session()
|
||||
self.sess.mount("https://", HTTPAdapter(max_retries=Retry(total=3, backoff_factor=1)))
|
||||
|
||||
def fetch(self, url, timeout=10):
|
||||
try:
|
||||
res = self.sess.get(url, headers=self.headers, timeout=timeout, verify=False)
|
||||
res.encoding = "utf-8"
|
||||
return res
|
||||
except:
|
||||
return None
|
||||
|
||||
def homeContent(self, filter):
|
||||
cate_list = [
|
||||
{"type_name": "国产自拍", "type_id": "1"},
|
||||
{"type_name": "国产传媒", "type_id": "2"},
|
||||
{"type_name": "探花系列", "type_id": "3"},
|
||||
{"type_name": "人妻熟女", "type_id": "4"},
|
||||
{"type_name": "日本无码", "type_id": "5"},
|
||||
{"type_name": "美乳巨乳", "type_id": "6"},
|
||||
{"type_name": "强制侵犯", "type_id": "7"},
|
||||
{"type_name": "制服诱惑", "type_id": "8"},
|
||||
{"type_name": "绝色佳人", "type_id": "9"},
|
||||
{"type_name": "家庭乱伦", "type_id": "10"},
|
||||
{"type_name": "绝顶潮吹", "type_id": "11"},
|
||||
{"type_name": "网红主播", "type_id": "12"}
|
||||
]
|
||||
return {"class": cate_list}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not hasattr(self, 'site_url'): self.init()
|
||||
pg = int(pg) if str(pg).isdigit() else 1
|
||||
list_url = f"{self.site_url}/index.php/vod/type/id/{tid}/page/{pg}.html"
|
||||
res = self.fetch(list_url)
|
||||
video_list = []
|
||||
if res:
|
||||
pattern = r'href="(/index.php/vod/detail/id/(\d+).html)".*?data-original="(.*?)".*?vod-name.*?>(.*?)<'
|
||||
matches = re.findall(pattern, res.text, re.S)
|
||||
for href, v_id, pic, name in matches:
|
||||
video_list.append({
|
||||
"vod_id": v_id,
|
||||
"vod_name": name.strip(),
|
||||
"vod_pic": pic if pic.startswith("http") else self.site_url + pic,
|
||||
"vod_remarks": ""
|
||||
})
|
||||
return {'list': video_list, 'page': pg, 'pagecount': 999, 'limit': 20, 'total': 9999}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not hasattr(self, 'site_url'): self.init()
|
||||
vod_id = ids[0]
|
||||
res = self.fetch(f"{self.site_url}/index.php/vod/detail/id/{vod_id}.html")
|
||||
if not res: return {}
|
||||
html = res.text
|
||||
name_match = re.search(r'vod-name.*?>(.*?)<', html) or re.search(r'title-box.*?>(.*?)<', html)
|
||||
pic_match = re.search(r'detail-pic.*?src="(.*?)"', html) or re.search(r'data-original="(.*?)"', html)
|
||||
|
||||
play_matches = re.findall(r'href="(/index.php/vod/play/id/(\d+)/sid/(\d+)/nid/(\d+).html)">(.*?)<', html)
|
||||
play_urls = []
|
||||
for m in play_matches:
|
||||
play_urls.append(f"{m[4]}${m[1]}-{m[2]}-{m[3]}")
|
||||
|
||||
if not play_urls:
|
||||
play_urls.append(f"立即播放${vod_id}-1-1")
|
||||
|
||||
vod = {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": name_match.group(1).strip() if name_match else "视频详情",
|
||||
"vod_pic": pic_match.group(1) if pic_match else "",
|
||||
"vod_play_from": "TOP-TV",
|
||||
"vod_play_url": "#".join(play_urls)
|
||||
}
|
||||
return {"list": [vod]}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
if not hasattr(self, 'site_url'): self.init()
|
||||
parts = id.split('-')
|
||||
if len(parts) == 3:
|
||||
v_id, s_id, n_id = parts
|
||||
play_url = f"{self.site_url}/index.php/vod/play/id/{v_id}/sid/{s_id}/nid/{n_id}.html"
|
||||
else:
|
||||
play_url = f"{self.site_url}/index.php/vod/play/id/{id}.html"
|
||||
res = self.fetch(play_url)
|
||||
if res:
|
||||
data_json = re.search(r'var player_aaaa=(.*?)</script>', res.text)
|
||||
if data_json:
|
||||
try:
|
||||
url = json.loads(data_json.group(1)).get("url", "")
|
||||
return {"parse": 0, "url": url, "header": self.headers}
|
||||
except:
|
||||
pass
|
||||
return {"parse": 1, "url": play_url}
|
||||
|
||||
def searchContent(self, key, quick, pg=1):
|
||||
if not hasattr(self, 'site_url'): self.init()
|
||||
res = self.fetch(f"{self.site_url}/index.php/vod/search/page/{pg}/wd/{key}.html")
|
||||
video_list = []
|
||||
if res:
|
||||
pattern = r'href="(/index.php/vod/detail/id/(\d+).html)".*?data-original="(.*?)".*?vod-name.*?>(.*?)<'
|
||||
matches = re.findall(pattern, res.text, re.S)
|
||||
for href, v_id, pic, name in matches:
|
||||
video_list.append({
|
||||
"vod_id": v_id,
|
||||
"vod_name": name.strip(),
|
||||
"vod_pic": pic if pic.startswith("http") else self.site_url + pic
|
||||
})
|
||||
return {"list": video_list}
|
||||
@@ -0,0 +1,259 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 123AV短视频 - Fongmi影视App适配爬虫
|
||||
# 优化为短视频模式,支持滑动切换
|
||||
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
import urllib.parse
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
def getName(self):
|
||||
return "123AV"
|
||||
|
||||
def init(self, extend=''):
|
||||
self.home_url = 'https://123av.fun'
|
||||
self.ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
|
||||
|
||||
def getDependence(self):
|
||||
return []
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return False
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def homeContent(self, filter):
|
||||
return {
|
||||
'class': [
|
||||
{'type_id': 'publish-time/sort-desc', 'type_name': '最新发布'},
|
||||
{'type_id': 'view-count/sort-desc', 'type_name': '最多播放'},
|
||||
{'type_id': 'comment-count/sort-desc', 'type_name': '最多评论'},
|
||||
{'type_id': 'favorite-count/sort-desc', 'type_name': '最多收藏'},
|
||||
{'type_id': 'explore', 'type_name': '探索发现'},
|
||||
{'type_id': 'list', 'type_name': '排行榜'},
|
||||
],
|
||||
'filters': {}
|
||||
}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return self.categoryContent('publish-time/sort-desc', 1, {}, {})
|
||||
|
||||
def _fetch_html(self, url):
|
||||
try:
|
||||
rsp = self.fetch(url, headers={
|
||||
"User-Agent": self.ua,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
}, timeout=15)
|
||||
if rsp and hasattr(rsp, 'text') and rsp.text:
|
||||
return rsp.text
|
||||
except Exception as e:
|
||||
print(f'fetch error: {e}')
|
||||
return ''
|
||||
|
||||
def _extract_video_list(self, html):
|
||||
videos = []
|
||||
if not html:
|
||||
return videos
|
||||
|
||||
# 匹配视频卡片
|
||||
card_pattern = re.compile(
|
||||
r'<a\s+([^>]*data-src="https://static\.123av\.fun/[^"]+\.m3u8"[^>]*)>(.*?)</a>',
|
||||
re.S
|
||||
)
|
||||
|
||||
cards = card_pattern.findall(html)
|
||||
|
||||
for attrs, content in cards:
|
||||
try:
|
||||
src_match = re.search(r'data-src="(https://static\.123av\.fun/[^"]+\.m3u8)"', attrs)
|
||||
poster_match = re.search(r'data-poster="([^"]*)"', attrs)
|
||||
id_match = re.search(r'data-id="(\d+)"', attrs)
|
||||
dur_match = re.search(r'data-duration="(\d+)"', attrs)
|
||||
|
||||
title_match = re.search(r'<xwya-video[^>]*alt="([^"]*)"', content)
|
||||
|
||||
if src_match and id_match:
|
||||
m3u8_url = src_match.group(1)
|
||||
vid = id_match.group(1)
|
||||
poster = poster_match.group(1) if poster_match else ''
|
||||
duration = dur_match.group(1) if dur_match else '0'
|
||||
title = title_match.group(1).strip() if title_match else f'视频{vid}'
|
||||
|
||||
dur = int(duration)
|
||||
if dur >= 3600:
|
||||
duration_str = f'{dur // 3600}:{(dur % 3600) // 60:02d}:{dur % 60:02d}'
|
||||
else:
|
||||
duration_str = f'{dur // 60:02d}:{dur % 60:02d}'
|
||||
|
||||
videos.append({
|
||||
'vod_id': vid,
|
||||
'vod_name': title,
|
||||
'vod_pic': poster,
|
||||
'vod_remarks': duration_str,
|
||||
})
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
return videos
|
||||
|
||||
def categoryContent(self, tid, page, filter, ext):
|
||||
video_list = []
|
||||
|
||||
if tid in ('explore', 'list', 'subscribe'):
|
||||
url = f'{self.home_url}/{tid}/page-{page}'
|
||||
else:
|
||||
url = f'{self.home_url}/{tid}/page-{page}'
|
||||
|
||||
html = self._fetch_html(url)
|
||||
video_list = self._extract_video_list(html)
|
||||
|
||||
return {
|
||||
'list': video_list,
|
||||
'page': int(page),
|
||||
'pagecount': 999,
|
||||
'limit': 20,
|
||||
'total': 999 * 20
|
||||
}
|
||||
|
||||
def detailContent(self, did):
|
||||
"""视频详情 - 关键修改:返回播放URL让playerContent处理"""
|
||||
video_list = []
|
||||
try:
|
||||
vid = did[0]
|
||||
detail_url = f'{self.home_url}/detail/{vid}'
|
||||
html = self._fetch_html(detail_url)
|
||||
|
||||
if html:
|
||||
src_match = re.search(r'data-src="(https://static\.123av\.fun/[^"]+\.m3u8)"', html)
|
||||
poster_match = re.search(r'data-poster="([^"]*)"', html)
|
||||
title_match = re.search(r'<h1[^>]*>([^<]+)</h1>', html)
|
||||
if not title_match:
|
||||
title_match = re.search(r'property="og:title"\s+content="([^"]*)"', html)
|
||||
if not title_match:
|
||||
title_match = re.search(r'<xwya-video[^>]*alt="([^"]*)"', html)
|
||||
|
||||
desc_match = re.search(r'property="og:description"\s+content="([^"]*)"', html)
|
||||
dur_match = re.search(r'data-duration="(\d+)"', html)
|
||||
|
||||
m3u8_url = src_match.group(1) if src_match else ''
|
||||
vod_pic = poster_match.group(1) if poster_match else ''
|
||||
vod_name = title_match.group(1).strip() if title_match else ''
|
||||
vod_content = desc_match.group(1) if desc_match else ''
|
||||
|
||||
duration_str = ''
|
||||
if dur_match:
|
||||
dur = int(dur_match.group(1))
|
||||
if dur >= 3600:
|
||||
duration_str = f'{dur // 3600}:{(dur % 3600) // 60:02d}:{dur % 60:02d}'
|
||||
else:
|
||||
duration_str = f'{dur // 60:02d}:{dur % 60:02d}'
|
||||
else:
|
||||
m3u8_url = ''
|
||||
vod_pic = ''
|
||||
vod_name = ''
|
||||
vod_content = ''
|
||||
duration_str = ''
|
||||
|
||||
# 关键修改:如果直接有m3u8,放入播放URL
|
||||
# 使用特殊格式让Fongmi识别为短视频
|
||||
if m3u8_url:
|
||||
# 格式: 集数名称$url#集数名称$url
|
||||
vod_play_url = f'正片${m3u8_url}'
|
||||
else:
|
||||
vod_play_url = ''
|
||||
|
||||
video_list.append({
|
||||
'vod_id': vid,
|
||||
'vod_name': vod_name,
|
||||
'vod_pic': vod_pic,
|
||||
'vod_remarks': duration_str,
|
||||
'vod_content': vod_content,
|
||||
'vod_play_from': '短视频', # 改为短视频,可能触发滑动模式
|
||||
'vod_play_url': vod_play_url,
|
||||
'type_name': '短视频',
|
||||
'vod_year': '',
|
||||
'vod_area': '',
|
||||
'vod_director': '',
|
||||
'vod_actor': '',
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f'detailContent error: {e}')
|
||||
|
||||
return {
|
||||
'list': video_list,
|
||||
'parse': 0,
|
||||
'jx': 0
|
||||
}
|
||||
|
||||
def searchContent(self, key, quick, page='1'):
|
||||
video_list = []
|
||||
try:
|
||||
encoded_key = urllib.parse.quote(key)
|
||||
url = f'{self.home_url}/search/{encoded_key}/page-{page}'
|
||||
html = self._fetch_html(url)
|
||||
video_list = self._extract_video_list(html)
|
||||
except Exception as e:
|
||||
print(f'searchContent error: {e}')
|
||||
|
||||
return {
|
||||
'list': video_list,
|
||||
'page': int(page),
|
||||
'pagecount': 99,
|
||||
'limit': 20,
|
||||
'total': 99 * 20
|
||||
}
|
||||
|
||||
def playerContent(self, flag, pid, vipFlags):
|
||||
"""播放器内容 - 关键修改"""
|
||||
# 如果pid已经是m3u8地址,直接返回
|
||||
if pid.startswith('http') and '.m3u8' in pid:
|
||||
return {
|
||||
'parse': 0, # 直接播放
|
||||
'url': pid,
|
||||
'header': {
|
||||
'User-Agent': self.ua,
|
||||
'Referer': self.home_url + '/'
|
||||
}
|
||||
}
|
||||
|
||||
# 如果是详情页URL,获取m3u8
|
||||
if '/detail/' in pid:
|
||||
html = self._fetch_html(pid)
|
||||
if html:
|
||||
src_match = re.search(r'data-src="(https://static\.123av\.fun/[^"]+\.m3u8)"', html)
|
||||
if src_match:
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': src_match.group(1),
|
||||
'header': {
|
||||
'User-Agent': self.ua,
|
||||
'Referer': self.home_url + '/'
|
||||
}
|
||||
}
|
||||
|
||||
# 默认返回,让外部解析
|
||||
return {
|
||||
'parse': 1,
|
||||
'url': pid,
|
||||
'header': {
|
||||
'User-Agent': self.ua,
|
||||
'Referer': self.home_url + '/'
|
||||
}
|
||||
}
|
||||
|
||||
def localProxy(self, params):
|
||||
return {}
|
||||
|
||||
def destroy(self):
|
||||
return '正在Destroy'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
@@ -0,0 +1,470 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.extend = extend
|
||||
self.cookie_cache = ""
|
||||
|
||||
def getName(self):
|
||||
return "抖音直播"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return False
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
return None
|
||||
|
||||
host = "https://live.douyin.com"
|
||||
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
|
||||
headers = {
|
||||
"User-Agent": ua,
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
}
|
||||
|
||||
classes_config = [
|
||||
{"type_id": "10000$3", "type_name": "娱乐天地"},
|
||||
{"type_id": "10001$3", "type_name": "科技文化"},
|
||||
{"type_id": "102$4", "type_name": "音乐"},
|
||||
{"type_id": "103$4", "type_name": "游戏"},
|
||||
{"type_id": "105$4", "type_name": "舞蹈"},
|
||||
{"type_id": "101$4", "type_name": "聊天"},
|
||||
{"type_id": "108$4", "type_name": "运动"},
|
||||
{"type_id": "107$4", "type_name": "生活"},
|
||||
{"type_id": "106$4", "type_name": "文化"},
|
||||
{"type_id": "104$4", "type_name": "二次元"},
|
||||
]
|
||||
|
||||
# ==================== 工具函数 ====================
|
||||
def _generate_device_id(self):
|
||||
timestamp = self._base36_encode(int(time.time() * 1000))
|
||||
random_part = ''.join(random.choices(string.ascii_lowercase + string.digits, k=13))
|
||||
return f"{timestamp}{random_part}"
|
||||
|
||||
@staticmethod
|
||||
def _base36_encode(num):
|
||||
alphabet = '0123456789abcdefghijklmnopqrstuvwxyz'
|
||||
if num == 0:
|
||||
return '0'
|
||||
res = []
|
||||
while num > 0:
|
||||
num, rem = divmod(num, 36)
|
||||
res.append(alphabet[rem])
|
||||
return ''.join(reversed(res))
|
||||
|
||||
def _get_cookie(self):
|
||||
if self.cookie_cache:
|
||||
return self.cookie_cache
|
||||
try:
|
||||
resp = self.fetch(self.host, headers=self.headers, verify=False)
|
||||
cookies = resp.headers.get('set-cookie', '')
|
||||
if cookies:
|
||||
match = re.search(r'ttwid=([^;]+)', cookies)
|
||||
if match:
|
||||
self.cookie_cache = f"ttwid={match.group(1)}"
|
||||
except Exception:
|
||||
pass
|
||||
return self.cookie_cache
|
||||
|
||||
def _get_headers(self):
|
||||
cookie = self._get_cookie()
|
||||
hd = self.headers.copy()
|
||||
hd["Referer"] = self.host
|
||||
if cookie:
|
||||
hd["Cookie"] = cookie
|
||||
return hd
|
||||
|
||||
def _first_non_empty(self, *values):
|
||||
for v in values:
|
||||
if v is not None and v != '':
|
||||
return v
|
||||
return None
|
||||
|
||||
def _parse_raw_live_data(self, item):
|
||||
if not isinstance(item, dict):
|
||||
return None
|
||||
candidates = [
|
||||
item.get('lives', {}).get('rawdata'),
|
||||
item.get('lives', {}).get('raw_data'),
|
||||
item.get('live', {}).get('rawdata'),
|
||||
item.get('live_info', {}).get('rawdata'),
|
||||
item.get('aweme_info', {}).get('live_info', {}).get('rawdata'),
|
||||
item.get('data', {}).get('rawdata'),
|
||||
item.get('rawdata'),
|
||||
item.get('lives'),
|
||||
item.get('live'),
|
||||
item.get('live_info'),
|
||||
item.get('aweme_info', {}).get('live_info'),
|
||||
item.get('aweme_info'),
|
||||
item.get('data'),
|
||||
item
|
||||
]
|
||||
for c in candidates:
|
||||
if isinstance(c, str):
|
||||
try:
|
||||
parsed = json.loads(c)
|
||||
if isinstance(parsed, dict):
|
||||
return parsed
|
||||
except Exception:
|
||||
continue
|
||||
elif isinstance(c, dict):
|
||||
return c
|
||||
return None
|
||||
|
||||
def _normalize_search_item(self, raw, fallback=None):
|
||||
if not isinstance(raw, dict):
|
||||
return None
|
||||
fallback = fallback or {}
|
||||
|
||||
room_id = self._first_non_empty(
|
||||
raw.get('id_str'),
|
||||
raw.get('room_id_str'),
|
||||
raw.get('room', {}).get('id_str'),
|
||||
raw.get('room', {}).get('id'),
|
||||
raw.get('room_id'),
|
||||
raw.get('roomId')
|
||||
)
|
||||
if not room_id:
|
||||
return None
|
||||
|
||||
web_rid = self._first_non_empty(
|
||||
raw.get('owner', {}).get('web_rid'),
|
||||
raw.get('web_rid'),
|
||||
raw.get('room', {}).get('owner', {}).get('web_rid')
|
||||
) or self._generate_device_id()
|
||||
|
||||
nickname = self._first_non_empty(
|
||||
raw.get('owner', {}).get('nickname'),
|
||||
raw.get('nickname'),
|
||||
raw.get('room', {}).get('owner', {}).get('nickname'),
|
||||
fallback.get('nickname')
|
||||
) or '抖音直播'
|
||||
|
||||
title = self._first_non_empty(
|
||||
raw.get('title'),
|
||||
raw.get('room', {}).get('title'),
|
||||
fallback.get('title'),
|
||||
nickname
|
||||
)
|
||||
|
||||
pic = self._first_non_empty(
|
||||
raw.get('owner', {}).get('avatar_large', {}).get('url_list', [None])[0],
|
||||
raw.get('room', {}).get('cover', {}).get('url_list', [None])[0],
|
||||
raw.get('cover', {}).get('url_list', [None])[0],
|
||||
raw.get('cover_url')
|
||||
) or ''
|
||||
|
||||
online_text = self._first_non_empty(
|
||||
raw.get('room', {}).get('stats', {}).get('user_count_str'),
|
||||
raw.get('user_count_str'),
|
||||
raw.get('room', {}).get('user_count_str'),
|
||||
raw.get('user_count')
|
||||
)
|
||||
tag_text = self._first_non_empty(
|
||||
raw.get('video_feed_tag'),
|
||||
raw.get('room', {}).get('partition_road_map', [{}])[0].get('title'),
|
||||
raw.get('partition', {}).get('title'),
|
||||
fallback.get('tag')
|
||||
)
|
||||
remark = ' '.join(filter(None, [tag_text, online_text]))
|
||||
|
||||
return {
|
||||
"vod_id": f"{web_rid}@@{room_id}",
|
||||
"vod_name": nickname,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark,
|
||||
"vod_content": title
|
||||
}
|
||||
|
||||
def _extract_search_videos(self, payload):
|
||||
if not isinstance(payload, list):
|
||||
return []
|
||||
results = []
|
||||
seen = set()
|
||||
for item in payload:
|
||||
raw = self._parse_raw_live_data(item)
|
||||
norm = self._normalize_search_item(raw, {
|
||||
"nickname": item.get('nickname'),
|
||||
"title": item.get('title') or item.get('desc'),
|
||||
"tag": item.get('search_keyword')
|
||||
})
|
||||
if not norm:
|
||||
continue
|
||||
if norm['vod_id'] in seen:
|
||||
continue
|
||||
seen.add(norm['vod_id'])
|
||||
results.append(norm)
|
||||
return results
|
||||
|
||||
# ==================== 框架标准方法 ====================
|
||||
def homeContent(self, filter):
|
||||
classes = self.classes_config
|
||||
return {
|
||||
"class": classes,
|
||||
"list": []
|
||||
}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
category_id = str(tid)
|
||||
page = int(pg or 1)
|
||||
offset = 15 * (page - 1)
|
||||
parts = category_id.split('$')
|
||||
if len(parts) < 2:
|
||||
return {"list": [], "page": page, "pagecount": 0, "limit": 15, "total": 9999}
|
||||
partition, ptype = parts[0], parts[1]
|
||||
|
||||
params = {
|
||||
"aid": "6383",
|
||||
"app_name": "douyin_web",
|
||||
"live_id": "1",
|
||||
"device_platform": "web",
|
||||
"language": "zh-CN",
|
||||
"browser_language": "zh-CN",
|
||||
"browser_platform": "Win32",
|
||||
"browser_name": "Chrome",
|
||||
"browser_version": "120.0.0.0",
|
||||
"partition": partition,
|
||||
"partition_type": ptype,
|
||||
"count": "15",
|
||||
"offset": str(offset),
|
||||
"web_rid": self._generate_device_id(),
|
||||
"cookie_enabled": "true",
|
||||
"screen_width": "1920",
|
||||
"screen_height": "1080"
|
||||
}
|
||||
|
||||
headers = self._get_headers()
|
||||
urls = [
|
||||
"https://live.douyin.com/webcast/web/partition/detail/room/v2/",
|
||||
"https://webcast.amemv.com/webcast/web/partition/detail/room/v2/",
|
||||
]
|
||||
list_ = []
|
||||
for url in urls:
|
||||
try:
|
||||
resp = self.fetch(url, headers=headers, params=params, verify=False)
|
||||
data = resp.json()
|
||||
if data.get('status_code') != 0:
|
||||
continue
|
||||
if not data.get('data', {}).get('data'):
|
||||
break
|
||||
items = data['data']['data']
|
||||
for it in items:
|
||||
web_rid = it.get('web_rid') or self._generate_device_id()
|
||||
room = it['room']
|
||||
list_.append({
|
||||
"vod_id": f"{web_rid}@@{room['id_str']}",
|
||||
"vod_name": room['title'],
|
||||
"vod_pic": room['cover']['url_list'][0],
|
||||
"vod_remarks": f"{room['owner']['nickname']} (🔥{room['stats']['user_count_str']})"
|
||||
})
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return {
|
||||
"list": list_,
|
||||
"page": page,
|
||||
"pagecount": 9999,
|
||||
"limit": 15,
|
||||
"total": 999999
|
||||
}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
kw = key.strip()
|
||||
if not kw:
|
||||
return {"list": [], "page": 1}
|
||||
page = 1
|
||||
offset = 0
|
||||
|
||||
headers = self._get_headers()
|
||||
|
||||
# 策略1 专用直播搜索
|
||||
try:
|
||||
params1 = {
|
||||
"device_platform": "webapp",
|
||||
"aid": "6383",
|
||||
"channel": "channel_pc_web",
|
||||
"search_channel": "aweme_live",
|
||||
"search_source": "switch_tab",
|
||||
"query_correct_type": "1",
|
||||
"need_filter_settings": "1",
|
||||
"list_type": "single",
|
||||
"keyword": kw,
|
||||
"offset": str(offset),
|
||||
"count": "20",
|
||||
"os_version": "10"
|
||||
}
|
||||
r = self.fetch("https://www.douyin.com/aweme/v1/web/live/search/",
|
||||
params=params1, headers=headers, verify=False)
|
||||
data = r.json()
|
||||
list_ = self._extract_search_videos(data.get('data'))
|
||||
if list_:
|
||||
return {"list": list_, "page": 1}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 策略2 通用搜索
|
||||
try:
|
||||
params2 = {
|
||||
"device_platform": "webapp",
|
||||
"aid": "6383",
|
||||
"channel": "channel_pc_web",
|
||||
"search_channel": "aweme_live",
|
||||
"keyword": kw,
|
||||
"offset": str(offset),
|
||||
"count": "20",
|
||||
"os_version": "10"
|
||||
}
|
||||
r = self.fetch("https://www.douyin.com/aweme/v1/web/general/search/stream/",
|
||||
params=params2, headers=headers, verify=False)
|
||||
data = r.json()
|
||||
list_ = self._extract_search_videos(data.get('data'))
|
||||
if list_:
|
||||
return {"list": list_, "page": 1}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 降级分区搜索
|
||||
try:
|
||||
part_url = f"https://live.douyin.com/webcast/web/partition/search/?keyword={kw}&aid=6383"
|
||||
r = self.fetch(part_url, headers=self._get_headers(), verify=False)
|
||||
data = r.json()
|
||||
partitions = data.get('data', {}).get('SearchResult', [])
|
||||
if not partitions:
|
||||
return {"list": [], "page": 1}
|
||||
|
||||
merged = []
|
||||
seen = set()
|
||||
for i in range(min(3, len(partitions))):
|
||||
part = partitions[i].get('partition', {})
|
||||
p_id = part.get('id_str')
|
||||
p_type = part.get('type')
|
||||
if not p_id or p_type is None:
|
||||
continue
|
||||
cate_ret = self.categoryContent(f"{p_id}${p_type}", 1, None, None)
|
||||
for item in cate_ret.get('list', []):
|
||||
if item['vod_id'] in seen:
|
||||
continue
|
||||
seen.add(item['vod_id'])
|
||||
item['vod_remarks'] = item['vod_remarks'] or part.get('title', kw)
|
||||
merged.append(item)
|
||||
if len(merged) >= 20:
|
||||
break
|
||||
if len(merged) >= 20:
|
||||
break
|
||||
return {"list": merged, "page": 1}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {"list": [], "page": 1}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not ids:
|
||||
return {"list": []}
|
||||
raw_id = ids[0]
|
||||
parts = raw_id.split('@@')
|
||||
if len(parts) != 2:
|
||||
return {"list": []}
|
||||
web_rid, room_id = parts[0], parts[1]
|
||||
|
||||
url = "https://live.douyin.com/webcast/room/web/enter/"
|
||||
params = {
|
||||
"aid": "6383",
|
||||
"app_name": "douyin_web",
|
||||
"live_id": "1",
|
||||
"device_platform": "web",
|
||||
"enter_from": "web_live",
|
||||
"browser_language": "zh-CN",
|
||||
"browser_platform": "Win32",
|
||||
"browser_name": "Chrome",
|
||||
"browser_version": "120.0.0.0",
|
||||
"web_rid": web_rid,
|
||||
"room_id_str": room_id,
|
||||
"enter_source": "",
|
||||
"is_need_double_stream": "false"
|
||||
}
|
||||
headers = self._get_headers()
|
||||
try:
|
||||
r = self.fetch(url, params=params, headers=headers, verify=False)
|
||||
data = r.json()
|
||||
if not data.get('data', {}).get('data'):
|
||||
return {"list": []}
|
||||
info = data['data']['data'][0]
|
||||
|
||||
resolution_map = {
|
||||
"FULL_HD1": "蓝光",
|
||||
"HD1": "超清",
|
||||
"ORIGION": "原画",
|
||||
"SD1": "标清",
|
||||
"SD2": "高清"
|
||||
}
|
||||
|
||||
flv_pull = info.get('stream_url', {}).get('flv_pull_url', {})
|
||||
flv_episodes = []
|
||||
for k, v in flv_pull.items():
|
||||
name = resolution_map.get(k, k)
|
||||
flv_episodes.append(f"{name}${v}")
|
||||
|
||||
hls_pull = info.get('stream_url', {}).get('hls_pull_url_map', {})
|
||||
hls_episodes = []
|
||||
for k, v in hls_pull.items():
|
||||
name = resolution_map.get(k, k)
|
||||
hls_episodes.append(f"{name}${v}")
|
||||
|
||||
vod_play_from = ""
|
||||
vod_play_url = ""
|
||||
if flv_episodes:
|
||||
vod_play_from += "FLV$$$"
|
||||
vod_play_url += "#".join(flv_episodes) + "$$$"
|
||||
if hls_episodes:
|
||||
vod_play_from += "HLS"
|
||||
vod_play_url += "#".join(hls_episodes)
|
||||
|
||||
vod_play_from = vod_play_from.rstrip("$$$")
|
||||
vod_play_url = vod_play_url.rstrip("$$$")
|
||||
|
||||
vod = {
|
||||
"vod_id": raw_id,
|
||||
"vod_name": info['title'],
|
||||
"vod_pic": info['cover']['url_list'][0],
|
||||
"vod_actor": info['owner']['nickname'],
|
||||
"vod_content": "【天神IY】"+info['title'],
|
||||
"vod_play_from": vod_play_from,
|
||||
"vod_play_url": vod_play_url
|
||||
}
|
||||
return {"list": [vod]}
|
||||
except Exception:
|
||||
return {"list": []}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
if not id:
|
||||
return {"parse": 0, "url": "", "header": self.headers}
|
||||
return {
|
||||
"parse": 0,
|
||||
"url": id,
|
||||
"header": {
|
||||
"User-Agent": self.ua,
|
||||
"Referer": self.host
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
from base.spider import Spider
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import base64
|
||||
from urllib.parse import quote
|
||||
|
||||
class Spider(Spider):
|
||||
def getName(self):
|
||||
return "小心儿悠悠"
|
||||
|
||||
def init(self, extend=""):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
cateId = [
|
||||
{"type_name": "华语男", "type_id": "1"},
|
||||
{"type_name": "华语女", "type_id": "2"},
|
||||
{"type_name": "华语组合", "type_id": "3"},
|
||||
{"type_name": "日韩男", "type_id": "4"},
|
||||
{"type_name": "日韩女", "type_id": "5"},
|
||||
{"type_name": "日韩组合", "type_id": "6"},
|
||||
{"type_name": "欧美男", "type_id": "7"},
|
||||
{"type_name": "欧美女", "type_id": "8"},
|
||||
{"type_name": "欧美组合", "type_id": "9"},
|
||||
{"type_name": "其他", "type_id": "0"}
|
||||
]
|
||||
result['class'] = cateId
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
result = self.categoryContent("1", 1, False, {})
|
||||
return result
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
result = {}
|
||||
url = f"http://wapi.kuwo.cn/api/www/artist/artistInfo?category={tid}&prefix=&pn={pg}&rn=30"
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Referer': 'http://www.kuwo.cn/'
|
||||
}
|
||||
|
||||
try:
|
||||
r = requests.get(url, headers=headers, timeout=10)
|
||||
data = r.json()
|
||||
videos = []
|
||||
if data.get('data') and data['data'].get('artistList'):
|
||||
for item in data['data']['artistList']:
|
||||
video = {
|
||||
"vod_id": str(item.get('id', '')),
|
||||
"vod_name": item.get('name', ''),
|
||||
"vod_pic": item.get('pic300') or item.get('pic') or item.get('pic120', ''),
|
||||
"vod_remarks": f""
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
|
||||
except Exception as e:
|
||||
result['list'] = []
|
||||
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
rid = ids[0]
|
||||
result = {}
|
||||
|
||||
info_url = f"http://wapi.kuwo.cn/api/www/artist/artist?artistid={rid}"
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Referer': 'http://www.kuwo.cn/'
|
||||
}
|
||||
|
||||
try:
|
||||
r = requests.get(info_url, headers=headers, timeout=10)
|
||||
info_data = r.json().get('data', {})
|
||||
|
||||
artist_name = info_data.get('name', '')
|
||||
|
||||
all_songs = self._get_artist_songs(rid)
|
||||
|
||||
artist_info = info_data.get('info', '')
|
||||
artist_info = re.sub(r'<[^>]+>', '', artist_info)
|
||||
artist_info = artist_info.replace(' ', ' ')
|
||||
artist_info = artist_info.replace('\r\n', '\n').replace('\r', '\n')
|
||||
artist_info = artist_info.strip()
|
||||
|
||||
max_songs = 300
|
||||
if len(all_songs) > max_songs:
|
||||
all_songs = all_songs[:max_songs]
|
||||
|
||||
play_arr = []
|
||||
for i, song in enumerate(all_songs):
|
||||
name = re.sub(r'[$#]', '', song.get('name', '')).strip()
|
||||
song_id = song.get('rid', '')
|
||||
album = song.get('album', '')
|
||||
|
||||
if album:
|
||||
play_arr.append(f"{name} - {album}${song_id}")
|
||||
else:
|
||||
play_arr.append(f"{name}${song_id}")
|
||||
|
||||
vod = {
|
||||
"vod_id": rid,
|
||||
"vod_name": artist_name,
|
||||
"vod_pic": info_data.get('pic300') or info_data.get('pic', ''),
|
||||
"vod_content": "【天神IY】"+artist_info if artist_info else "暂无歌手简介",
|
||||
"vod_remarks": f"歌曲 : {len(all_songs)}首",
|
||||
"vod_actor": artist_name,
|
||||
"vod_play_from": "酷我音乐",
|
||||
"vod_play_url": "#".join(play_arr)
|
||||
}
|
||||
|
||||
result['list'] = [vod]
|
||||
|
||||
except Exception as e:
|
||||
vod = {
|
||||
"vod_id": rid,
|
||||
"vod_name": "加载失败",
|
||||
"vod_content": f"加载歌手信息失败: {str(e)}",
|
||||
"vod_remarks": "加载失败",
|
||||
"vod_actor": "未知",
|
||||
"vod_play_from": "酷我音乐",
|
||||
"vod_play_url": ""
|
||||
}
|
||||
result['list'] = [vod]
|
||||
|
||||
return result
|
||||
|
||||
def _get_artist_songs(self, rid):
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Referer': 'http://www.kuwo.cn/'
|
||||
}
|
||||
|
||||
songs = []
|
||||
max_pages = 10
|
||||
|
||||
for page in range(1, max_pages + 1):
|
||||
try:
|
||||
url = f"http://wapi.kuwo.cn/api/www/artist/artistMusic?artistid={rid}&pn={page}&rn=30"
|
||||
response = requests.get(url, headers=headers, timeout=10)
|
||||
data = response.json()
|
||||
|
||||
if data.get('code') == 200:
|
||||
music_data = data.get('data', {})
|
||||
song_list = music_data.get('list', [])
|
||||
|
||||
if not song_list:
|
||||
break
|
||||
|
||||
for song in song_list:
|
||||
song_name = song.get('name', '').strip()
|
||||
if song_name:
|
||||
songs.append({
|
||||
'name': song_name,
|
||||
'rid': song.get('rid', ''),
|
||||
'album': song.get('album', ''),
|
||||
'duration': song.get('duration', '')
|
||||
})
|
||||
|
||||
if len(songs) >= 300:
|
||||
songs = songs[:300]
|
||||
break
|
||||
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return songs
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
result = {}
|
||||
rid = id
|
||||
|
||||
qualities = []
|
||||
|
||||
quality_list = [
|
||||
("无损FLAC", 2000, "flac"),
|
||||
("高品质320K", 320, "mp3"),
|
||||
("标准128K", 128, "mp3")
|
||||
]
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 10)',
|
||||
'Referer': 'https://www.kuwo.cn/'
|
||||
}
|
||||
|
||||
for quality_name, bitrate, format_type in quality_list:
|
||||
try:
|
||||
api_url = f"https://nmobi.kuwo.cn/mobi.s?f=web&user=0&source=kwplayer_ar_4.4.2.7_B_nuoweida_vh.apk&type=convert_url_with_sign&rid={rid}&bitrate={bitrate}&format={format_type}"
|
||||
r = requests.get(api_url, headers=headers, timeout=5)
|
||||
data = r.json()
|
||||
if data.get('code') == 200 and data.get('data') and data['data'].get('url'):
|
||||
qualities.append((quality_name, data['data']['url']))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not qualities:
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ""
|
||||
result["url"] = ""
|
||||
result["header"] = {}
|
||||
return result
|
||||
|
||||
urls = []
|
||||
for quality_name, quality_url in qualities:
|
||||
urls.append(quality_name)
|
||||
urls.append(quality_url)
|
||||
|
||||
lrc = ""
|
||||
pic = ""
|
||||
|
||||
try:
|
||||
lrc_api = f"https://kuwo.cn/openapi/v1/www/lyric/getlyric?musicId={rid}"
|
||||
lr = requests.get(lrc_api, timeout=5)
|
||||
lj = lr.json()
|
||||
if lj.get('data') and lj['data'].get('lrclist'):
|
||||
lrc = "\n".join([f"[{self._format_time(float(item.get('time', 0)))}]{item.get('lineLyric', '')}"
|
||||
for item in lj['data']['lrclist']])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
pic_url = f"http://artistpicserver.kuwo.cn/pic.web?type=rid_pic&pictype=url&size=500&rid={rid}"
|
||||
pr = requests.get(pic_url, timeout=5)
|
||||
if pr.text.startswith('http'):
|
||||
pic = pr.text.strip()
|
||||
else:
|
||||
pic = pic_url
|
||||
except Exception:
|
||||
pic = f"http://artistpicserver.kuwo.cn/pic.web?type=rid_pic&pictype=url&size=500&rid={rid}"
|
||||
|
||||
if lrc:
|
||||
try:
|
||||
ssa_lrc = self._create_ssa_subtitle(lrc)
|
||||
ssa_base64 = base64.b64encode(ssa_lrc.encode('utf-8')).decode('utf-8')
|
||||
ssa_url = f"data:text/x-ssa;base64,{ssa_base64}"
|
||||
|
||||
result["subs"] = [{
|
||||
"name": "5行歌词",
|
||||
"url": ssa_url,
|
||||
"format": "text/x-ssa",
|
||||
"selected": True
|
||||
}]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ""
|
||||
result["url"] = urls
|
||||
result["header"] = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||
"Referer": "https://www.kuwo.cn/"
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def _format_time(self, seconds):
|
||||
m = int(seconds // 60)
|
||||
s = seconds % 60
|
||||
return f"{m:02d}:{s:05.2f}"
|
||||
|
||||
def _create_ssa_subtitle(self, lrc_text):
|
||||
lines = []
|
||||
pattern = r'\[(\d{2}):(\d{2})\.(\d{2})\](.*)'
|
||||
|
||||
for line in lrc_text.split('\n'):
|
||||
match = re.match(pattern, line)
|
||||
if match:
|
||||
minutes = int(match.group(1))
|
||||
seconds = int(match.group(2))
|
||||
hundredths = int(match.group(3))
|
||||
text = match.group(4).strip()
|
||||
|
||||
total_seconds = minutes * 60 + seconds + hundredths / 100.0
|
||||
if text:
|
||||
lines.append({
|
||||
'start': total_seconds,
|
||||
'text': text
|
||||
})
|
||||
|
||||
if not lines:
|
||||
return ""
|
||||
|
||||
ssa_header = """[Script Info]
|
||||
ScriptType: v4.00+
|
||||
Collisions: Normal
|
||||
PlayResX: 1280
|
||||
PlayResY: 720
|
||||
Timer: 100.0000
|
||||
WrapStyle: 0
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: WAITING_TOP2,Roboto,55,&H0000FFFF,&H00808080,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,1,1,1,2,0,0,180,1
|
||||
Style: WAITING_TOP1,Roboto,55,&H0000FFFF,&H00808080,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,1,1,1,2,0,0,260,1
|
||||
Style: PLAYING_CENTER,Roboto,60,&H0000FF00,&H00808080,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,1,2,2,2,0,0,340,1
|
||||
Style: PLAYED_BOTTOM1,Roboto,55,&H0000FFFF,&H00808080,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,1,1,1,2,0,0,420,1
|
||||
Style: PLAYED_BOTTOM2,Roboto,55,&H0000FFFF,&H00808080,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,1,1,1,2,0,0,500,1
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
"""
|
||||
|
||||
def format_ssa_time(seconds):
|
||||
h = int(seconds // 3600)
|
||||
m = int((seconds % 3600) // 60)
|
||||
s = int(seconds % 60)
|
||||
cs = int((seconds * 100) % 100)
|
||||
return f"{h}:{m:02d}:{s:02d}.{cs:02d}"
|
||||
|
||||
events = []
|
||||
|
||||
for i in range(len(lines)):
|
||||
current = lines[i]
|
||||
current_end = lines[i+1]['start'] if i+1 < len(lines) else current['start'] + 5.0
|
||||
|
||||
wait2 = lines[i+2] if i+2 < len(lines) else None
|
||||
wait1 = lines[i+1] if i+1 < len(lines) else None
|
||||
played1 = lines[i-1] if i-1 >= 0 else None
|
||||
played2 = lines[i-2] if i-2 >= 0 else None
|
||||
|
||||
start_str = format_ssa_time(current['start'])
|
||||
end_str = format_ssa_time(current_end)
|
||||
|
||||
if wait2:
|
||||
events.append(f"Dialogue: 1,{start_str},{end_str},WAITING_TOP2,,0,0,0,,{wait2['text']}")
|
||||
|
||||
if wait1:
|
||||
events.append(f"Dialogue: 2,{start_str},{end_str},WAITING_TOP1,,0,0,0,,{wait1['text']}")
|
||||
|
||||
events.append(f"Dialogue: 3,{start_str},{end_str},PLAYING_CENTER,,0,0,0,,{current['text']}")
|
||||
|
||||
if played1:
|
||||
events.append(f"Dialogue: 4,{start_str},{end_str},PLAYED_BOTTOM1,,0,0,0,,{played1['text']}")
|
||||
|
||||
if played2:
|
||||
events.append(f"Dialogue: 5,{start_str},{end_str},PLAYED_BOTTOM2,,0,0,0,,{played2['text']}")
|
||||
|
||||
return ssa_header + "\n".join(events)
|
||||
|
||||
def searchContent(self, key, quick, pg=1):
|
||||
result = {}
|
||||
wd = quote(key)
|
||||
page_num = (int(pg) - 1) * 30
|
||||
url = f"https://search.kuwo.cn/r.s?client=kt&pn={page_num}&rn=30&all={wd}&vipver=1&ft=artist&encoding=utf8&rformat=json&mobi=1"
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Referer': 'http://www.kuwo.cn/'
|
||||
}
|
||||
|
||||
try:
|
||||
r = requests.get(url, headers=headers, timeout=10)
|
||||
data = r.json()
|
||||
videos = []
|
||||
if data.get('abslist'):
|
||||
base_path = data.get('BASEPICPATH', 'http://img1.kuwo.cn/star/starheads/')
|
||||
for item in data['abslist']:
|
||||
aid = item.get('ARTISTID') or item.get('DC_TARGETID', '')
|
||||
pic = item.get('hts_PICPATH') or (base_path + item['PICPATH'] if item.get('PICPATH') else '')
|
||||
video = {
|
||||
"vod_id": str(aid),
|
||||
"vod_name": item.get('ARTIST', ''),
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": f"歌曲 : {item.get('SONGNUM', 0)}首"
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 30
|
||||
result['total'] = 999999
|
||||
|
||||
except Exception as e:
|
||||
result['list'] = []
|
||||
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
return self.searchContent(key, quick, pg)
|
||||
|
||||
def localProxy(self, param):
|
||||
return None
|
||||
+343
-343
@@ -1,343 +1,343 @@
|
||||
# coding = utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import binascii
|
||||
import requests
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://app.whjzjx.cn"
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Linux; Android 12; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.101 Mobile Safari/537.36'
|
||||
}
|
||||
|
||||
headerf = {
|
||||
"platform": "1",
|
||||
"user_agent": "Mozilla/5.0 (Linux; Android 9; V1938T Build/PQ3A.190705.08211809; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36",
|
||||
"content-type": "application/json; charset=utf-8"
|
||||
}
|
||||
|
||||
times = int(time.time() * 1000)
|
||||
|
||||
data = {
|
||||
"device": "2a50580e69d38388c94c93605241fb306",
|
||||
"package_name": "com.jz.xydj",
|
||||
"android_id": "ec1280db12795506",
|
||||
"install_first_open": True,
|
||||
"first_install_time": 1752505243345,
|
||||
"last_update_time": 1752505243345,
|
||||
"report_link_url": "",
|
||||
"authorization": "",
|
||||
"timestamp": times
|
||||
}
|
||||
|
||||
plain_text = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
|
||||
|
||||
key = "B@ecf920Od8A4df7"
|
||||
key_bytes = key.encode('utf-8')
|
||||
plain_bytes = plain_text.encode('utf-8')
|
||||
cipher = AES.new(key_bytes, AES.MODE_ECB)
|
||||
padded_data = pad(plain_bytes, AES.block_size)
|
||||
ciphertext = cipher.encrypt(padded_data)
|
||||
encrypted = base64.b64encode(ciphertext).decode('utf-8')
|
||||
|
||||
response = requests.post("https://u.shytkjgs.com/user/v3/account/login", headers=headerf, data=encrypted)
|
||||
response_data = response.json()
|
||||
Authorization = response_data['data']['token']
|
||||
|
||||
headerx = {
|
||||
'authorization': Authorization,
|
||||
'platform': '1',
|
||||
'version_name': '3.8.3.1'
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
global xurl
|
||||
global headerx
|
||||
global headers
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
|
||||
if pl == 3:
|
||||
plx = []
|
||||
while True:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
break
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
break
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
plx.append(middle_text)
|
||||
text = text.replace(start_str + middle_text + end_str, '')
|
||||
if len(plx) > 0:
|
||||
purl = ''
|
||||
for i in range(len(plx)):
|
||||
matches = re.findall(start_index1, plx[i])
|
||||
output = ""
|
||||
for match in matches:
|
||||
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
|
||||
if match3:
|
||||
number = match3.group(1)
|
||||
else:
|
||||
number = 0
|
||||
if 'http' not in match[0]:
|
||||
output += f"#{match[1]}${number}{xurl}{match[0]}"
|
||||
else:
|
||||
output += f"#{match[1]}${number}{match[0]}"
|
||||
output = output[1:]
|
||||
purl = purl + output + "$$$"
|
||||
purl = purl[:-3]
|
||||
return purl
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
return ""
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
return ""
|
||||
|
||||
if pl == 0:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
return middle_text.replace("\\", "")
|
||||
|
||||
if pl == 1:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
jg = ' '.join(matches)
|
||||
return jg
|
||||
|
||||
if pl == 2:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
new_list = [f'{item}' for item in matches]
|
||||
jg = '$$$'.join(new_list)
|
||||
return jg
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
result = {"class": [{"type_id": "1", "type_name": "剧场"},
|
||||
{"type_id": "3", "type_name": "新剧"},
|
||||
{"type_id": "2", "type_name": "热播"},
|
||||
{"type_id": "7", "type_name": "星选"},
|
||||
{"type_id": "5", "type_name": "阳光"}],
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
|
||||
url= f'{xurl}/v1/theater/home_page?theater_class_id=1&class2_id=4&page_num=1&page_size=24'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
if detail.status_code == 200:
|
||||
data = detail.json()
|
||||
|
||||
for vod in data['data']['list']:
|
||||
|
||||
name = vod['theater']['title']
|
||||
|
||||
id = vod['theater']['id']
|
||||
|
||||
pic = vod['theater']['cover_url']
|
||||
|
||||
remark = vod['theater']['play_amount_str']
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result = {'list': videos}
|
||||
return result
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
result = {}
|
||||
videos = []
|
||||
|
||||
url = f'{xurl}/v1/theater/home_page?theater_class_id={cid}&page_num={pg}&page_size=24'
|
||||
detail = requests.get(url=url,headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
if detail.status_code == 200:
|
||||
data = detail.json()
|
||||
|
||||
for vod in data['data']['list']:
|
||||
|
||||
name = vod['theater']['title']
|
||||
|
||||
id = vod['theater']['id']
|
||||
|
||||
pic = vod['theater']['cover_url']
|
||||
|
||||
remark = vod['theater']['theme']
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
result = {}
|
||||
videos = []
|
||||
xianlu = ''
|
||||
bofang = ''
|
||||
|
||||
url = f'{xurl}/v2/theater_parent/detail?theater_parent_id={did}'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
if detail.status_code == 200:
|
||||
data = detail.json()
|
||||
|
||||
url = 'https://fs-im-kefu.7moor-fs1.com/ly/4d2c3f00-7d4c-11e5-af15-41bf63ae4ea0/1732707176882/jiduo.txt'
|
||||
response = requests.get(url)
|
||||
response.encoding = 'utf-8'
|
||||
code = response.text
|
||||
name = self.extract_middle_text(code, "s1='", "'", 0)
|
||||
Jumps = self.extract_middle_text(code, "s2='", "'", 0)
|
||||
|
||||
content = '剧情:' + data['data']['introduction']
|
||||
|
||||
area = data['data']['desc_tags'][0]
|
||||
|
||||
remarks = data['data']['filing']
|
||||
|
||||
# 修复剧集只有一集的问题 - 检查theaters数据是否存在且不为空
|
||||
if 'theaters' in data['data'] and data['data']['theaters']:
|
||||
for sou in data['data']['theaters']:
|
||||
id = sou['son_video_url']
|
||||
name = sou['num']
|
||||
bofang = bofang + str(name) + '$' + id + '#'
|
||||
|
||||
bofang = bofang[:-1] if bofang.endswith('#') else bofang
|
||||
xianlu = '星芽'
|
||||
else:
|
||||
# 如果没有theaters数据,检查是否有单个视频URL
|
||||
if 'video_url' in data['data'] and data['data']['video_url']:
|
||||
bofang = '1$' + data['data']['video_url']
|
||||
xianlu = '星芽'
|
||||
else:
|
||||
bofang = Jumps
|
||||
xianlu = '1'
|
||||
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_content": content,
|
||||
"vod_remarks": remarks,
|
||||
"vod_area": area,
|
||||
"vod_play_from": xianlu,
|
||||
"vod_play_url": bofang
|
||||
})
|
||||
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = id
|
||||
result["header"] = headers
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, page):
|
||||
result = {}
|
||||
videos = []
|
||||
|
||||
payload = {
|
||||
"text": key
|
||||
}
|
||||
|
||||
url = f"{xurl}/v3/search"
|
||||
detail = requests.post(url=url, headers=headerx, json=payload)
|
||||
if detail.status_code == 200:
|
||||
detail.encoding = "utf-8"
|
||||
data = detail.json()
|
||||
|
||||
for vod in data['data']['theater']['search_data']:
|
||||
|
||||
name = vod['title']
|
||||
|
||||
id = vod['id']
|
||||
|
||||
pic = vod['cover_url']
|
||||
|
||||
remark = vod['score_str']
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result['list'] = videos
|
||||
result['page'] = page
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
# coding = utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import binascii
|
||||
import requests
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://app.whjzjx.cn"
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Linux; Android 12; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.101 Mobile Safari/537.36'
|
||||
}
|
||||
|
||||
headerf = {
|
||||
"platform": "1",
|
||||
"user_agent": "Mozilla/5.0 (Linux; Android 9; V1938T Build/PQ3A.190705.08211809; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36",
|
||||
"content-type": "application/json; charset=utf-8"
|
||||
}
|
||||
|
||||
times = int(time.time() * 1000)
|
||||
|
||||
data = {
|
||||
"device": "2a50580e69d38388c94c93605241fb306",
|
||||
"package_name": "com.jz.xydj",
|
||||
"android_id": "ec1280db12795506",
|
||||
"install_first_open": True,
|
||||
"first_install_time": 1752505243345,
|
||||
"last_update_time": 1752505243345,
|
||||
"report_link_url": "",
|
||||
"authorization": "",
|
||||
"timestamp": times
|
||||
}
|
||||
|
||||
plain_text = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
|
||||
|
||||
key = "B@ecf920Od8A4df7"
|
||||
key_bytes = key.encode('utf-8')
|
||||
plain_bytes = plain_text.encode('utf-8')
|
||||
cipher = AES.new(key_bytes, AES.MODE_ECB)
|
||||
padded_data = pad(plain_bytes, AES.block_size)
|
||||
ciphertext = cipher.encrypt(padded_data)
|
||||
encrypted = base64.b64encode(ciphertext).decode('utf-8')
|
||||
|
||||
response = requests.post("https://u.shytkjgs.com/user/v3/account/login", headers=headerf, data=encrypted)
|
||||
response_data = response.json()
|
||||
Authorization = response_data['data']['token']
|
||||
|
||||
headerx = {
|
||||
'authorization': Authorization,
|
||||
'platform': '1',
|
||||
'version_name': '3.8.3.1'
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
global xurl
|
||||
global headerx
|
||||
global headers
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
|
||||
if pl == 3:
|
||||
plx = []
|
||||
while True:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
break
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
break
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
plx.append(middle_text)
|
||||
text = text.replace(start_str + middle_text + end_str, '')
|
||||
if len(plx) > 0:
|
||||
purl = ''
|
||||
for i in range(len(plx)):
|
||||
matches = re.findall(start_index1, plx[i])
|
||||
output = ""
|
||||
for match in matches:
|
||||
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
|
||||
if match3:
|
||||
number = match3.group(1)
|
||||
else:
|
||||
number = 0
|
||||
if 'http' not in match[0]:
|
||||
output += f"#{match[1]}${number}{xurl}{match[0]}"
|
||||
else:
|
||||
output += f"#{match[1]}${number}{match[0]}"
|
||||
output = output[1:]
|
||||
purl = purl + output + "$$$"
|
||||
purl = purl[:-3]
|
||||
return purl
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
return ""
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
return ""
|
||||
|
||||
if pl == 0:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
return middle_text.replace("\\", "")
|
||||
|
||||
if pl == 1:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
jg = ' '.join(matches)
|
||||
return jg
|
||||
|
||||
if pl == 2:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
new_list = [f'{item}' for item in matches]
|
||||
jg = '$$$'.join(new_list)
|
||||
return jg
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
result = {"class": [{"type_id": "1", "type_name": "剧场"},
|
||||
{"type_id": "3", "type_name": "新剧"},
|
||||
{"type_id": "2", "type_name": "热播"},
|
||||
{"type_id": "7", "type_name": "星选"},
|
||||
{"type_id": "5", "type_name": "阳光"}],
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
|
||||
url= f'{xurl}/v1/theater/home_page?theater_class_id=1&class2_id=4&page_num=1&page_size=24'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
if detail.status_code == 200:
|
||||
data = detail.json()
|
||||
|
||||
for vod in data['data']['list']:
|
||||
|
||||
name = vod['theater']['title']
|
||||
|
||||
id = vod['theater']['id']
|
||||
|
||||
pic = vod['theater']['cover_url']
|
||||
|
||||
remark = vod['theater']['play_amount_str']
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result = {'list': videos}
|
||||
return result
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
result = {}
|
||||
videos = []
|
||||
|
||||
url = f'{xurl}/v1/theater/home_page?theater_class_id={cid}&page_num={pg}&page_size=24'
|
||||
detail = requests.get(url=url,headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
if detail.status_code == 200:
|
||||
data = detail.json()
|
||||
|
||||
for vod in data['data']['list']:
|
||||
|
||||
name = vod['theater']['title']
|
||||
|
||||
id = vod['theater']['id']
|
||||
|
||||
pic = vod['theater']['cover_url']
|
||||
|
||||
remark = vod['theater']['theme']
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
result = {}
|
||||
videos = []
|
||||
xianlu = ''
|
||||
bofang = ''
|
||||
|
||||
url = f'{xurl}/v2/theater_parent/detail?theater_parent_id={did}'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
if detail.status_code == 200:
|
||||
data = detail.json()
|
||||
|
||||
url = 'https://fs-im-kefu.7moor-fs1.com/ly/4d2c3f00-7d4c-11e5-af15-41bf63ae4ea0/1732707176882/jiduo.txt'
|
||||
response = requests.get(url)
|
||||
response.encoding = 'utf-8'
|
||||
code = response.text
|
||||
name = self.extract_middle_text(code, "s1='", "'", 0)
|
||||
Jumps = self.extract_middle_text(code, "s2='", "'", 0)
|
||||
|
||||
content = '剧情:' + data['data']['introduction']
|
||||
|
||||
area = data['data']['desc_tags'][0]
|
||||
|
||||
remarks = data['data']['filing']
|
||||
|
||||
# 修复剧集只有一集的问题 - 检查theaters数据是否存在且不为空
|
||||
if 'theaters' in data['data'] and data['data']['theaters']:
|
||||
for sou in data['data']['theaters']:
|
||||
id = sou['son_video_url']
|
||||
name = sou['num']
|
||||
bofang = bofang + str(name) + '$' + id + '#'
|
||||
|
||||
bofang = bofang[:-1] if bofang.endswith('#') else bofang
|
||||
xianlu = '星芽'
|
||||
else:
|
||||
# 如果没有theaters数据,检查是否有单个视频URL
|
||||
if 'video_url' in data['data'] and data['data']['video_url']:
|
||||
bofang = '1$' + data['data']['video_url']
|
||||
xianlu = '星芽'
|
||||
else:
|
||||
bofang = Jumps
|
||||
xianlu = '1'
|
||||
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_content": content,
|
||||
"vod_remarks": remarks,
|
||||
"vod_area": area,
|
||||
"vod_play_from": xianlu,
|
||||
"vod_play_url": bofang
|
||||
})
|
||||
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = id
|
||||
result["header"] = headers
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, page):
|
||||
result = {}
|
||||
videos = []
|
||||
|
||||
payload = {
|
||||
"text": key
|
||||
}
|
||||
|
||||
url = f"{xurl}/v3/search"
|
||||
detail = requests.post(url=url, headers=headerx, json=payload)
|
||||
if detail.status_code == 200:
|
||||
detail.encoding = "utf-8"
|
||||
data = detail.json()
|
||||
|
||||
for vod in data['data']['theater']['search_data']:
|
||||
|
||||
name = vod['title']
|
||||
|
||||
id = vod['id']
|
||||
|
||||
pic = vod['cover_url']
|
||||
|
||||
remark = vod['score_str']
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result['list'] = videos
|
||||
result['page'] = page
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#coding=utf-8
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def init(self,extend=""):
|
||||
self.base_url='http://api.hclyz.com:81/mf'
|
||||
|
||||
def homeContent(self,filter):
|
||||
classes = [{"type_name": "色播聚合","type_id":"/json.txt"}]
|
||||
result = {"class": classes}
|
||||
return result
|
||||
|
||||
def categoryContent(self,tid,pg,filter,extend):
|
||||
home = self.fetch(f'{self.base_url}/json.txt').json()
|
||||
data = home.get("pingtai")[1:]
|
||||
videos = [
|
||||
{
|
||||
"vod_id": "/" + item['address'],
|
||||
"vod_name": item['title'],
|
||||
"vod_pic": item['xinimg'].replace("http://cdn.gcufbd.top/img/",
|
||||
"https://slink.ltd/https://raw.githubusercontent.com/fish2018/lib/refs/heads/main/imgs/"),
|
||||
"vod_remarks": item['Number'],
|
||||
"style": {"type": "rect", "ratio": 1.33}
|
||||
} for item in sorted(data, key=lambda x: int(x['Number']), reverse=True)
|
||||
]
|
||||
result = {
|
||||
"page": pg,
|
||||
"pagecount": 1,
|
||||
"limit": len(videos),
|
||||
"total": len(videos),
|
||||
"list": videos
|
||||
}
|
||||
return result
|
||||
|
||||
def detailContent(self,array):
|
||||
id = array[0]
|
||||
data = self.fetch(f'{self.base_url}/{id}').json()
|
||||
zhubo = data['zhubo']
|
||||
playUrls = '#'.join([f"{vod['title']}${vod['address']}" for vod in zhubo])
|
||||
vod = [{
|
||||
"vod_play_from": 'sebo',
|
||||
"vod_play_url": playUrls,
|
||||
"vod_content": 'https://github.com/fish2018',
|
||||
}]
|
||||
result = {"list": vod}
|
||||
return result
|
||||
|
||||
def playerContent(self,flag,id,vipFlags):
|
||||
result = {
|
||||
'parse': 0,
|
||||
'url': id
|
||||
}
|
||||
return result
|
||||
|
||||
def getName(self):
|
||||
return '色播聚合'
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
def isVideoFormat(self,url):
|
||||
pass
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
def searchContent(self,key,quick):
|
||||
pass
|
||||
def destroy(self):
|
||||
pass
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
Reference in New Issue
Block a user