上传文件至「py」

This commit is contained in:
2026-08-15 10:30:50 +02:00
parent 11e71aa836
commit 7f5b38ed4b
5 changed files with 1873 additions and 0 deletions
+469
View File
@@ -0,0 +1,469 @@
import cheerio from 'assets://js/lib/cheerio.min.js';
const appConfig = {
siteName: "伦理专题影片",
siteUrl: "http://yoxayg.com"
};
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
async function init(ext) {
console.log("初始化爬虫:", appConfig.siteName);
}
const classList = [
{ type_id: "1", type_name: "电影" },
{ type_id: "2", type_name: "电视剧" },
{ type_id: "3", type_name: "综艺" },
{ type_id: "4", type_name: "动漫" },
{ type_id: "36", 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", type_name: "战争片" },
{ type_id: "20", type_name: "记录片" },
{ type_id: "34", type_name: "伦理片" },
{ type_id: "13", type_name: "国产剧" },
{ type_id: "14", type_name: "香港剧" },
{ type_id: "15", type_name: "韩国剧" },
{ type_id: "16", type_name: "欧美剧" },
{ type_id: "21", type_name: "台湾剧" },
{ type_id: "22", type_name: "日本剧" },
{ type_id: "23", type_name: "海外剧" },
{ type_id: "24", type_name: "泰国剧" },
{ type_id: "25", type_name: "大陆综艺" },
{ type_id: "26", type_name: "港台综艺" },
{ type_id: "27", type_name: "日韩综艺" },
{ type_id: "28", type_name: "欧美综艺" },
{ type_id: "29", type_name: "国产动漫" },
{ type_id: "30", type_name: "日韩动漫" },
{ type_id: "31", type_name: "欧美动漫" },
{ type_id: "32", type_name: "港台动漫" },
{ type_id: "33", type_name: "海外动漫" }
];
const typeSlugMap = {
"1": "dianying", "2": "dianshiju", "3": "zongyi", "4": "dongman", "6": "dongzuopian",
"7": "xijupian", "8": "aiqingpian", "9": "kehuanpian", "10": "kongbupian", "11": "juqingpian",
"12": "zhanzhengpian", "13": "guochanju", "14": "xianggangju", "15": "hanguoju", "16": "oumeiju",
"20": "jilupian", "21": "taiwanju", "22": "ribenju", "23": "haiwaiju", "24": "taiguoju",
"25": "daluzongyi", "26": "gangtaizongyi", "27": "rihanzongyi", "28": "oumeizongyi",
"29": "guochandongman", "30": "rihandongman", "31": "oumeidongman", "32": "gangtaidongman",
"33": "haiwaidongman", "34": "lunlipian", "36": "duanju"
};
const typeParentMap = {
"1": 0, "2": 0, "3": 0, "4": 0, "6": 1, "7": 1, "8": 1, "9": 1, "10": 1,
"11": 1, "12": 1, "13": 2, "14": 2, "15": 2, "16": 2, "20": 1, "21": 2, "22": 2,
"23": 2, "24": 2, "25": 3, "26": 3, "27": 3, "28": 3, "29": 4, "30": 4,
"31": 4, "32": 4, "33": 4, "34": 1, "36": 2
};
function topType(tid) {
tid = parseInt(tid || 0, 10);
if (tid === 36) return 36;
const p = parseInt(typeParentMap[String(tid)] || 0, 10);
return p > 0 ? p : tid;
}
function typeSlug(tid) {
return typeSlugMap[String(parseInt(tid || 0, 10))] || ('type' + parseInt(tid || 0, 10));
}
function getAreaFilter() {
return {
"key": "area", "name": "地区", "value": [
{ "n": "全部", "v": "" }, { "n": "大陆", "v": "大陆" }, { "n": "香港", "v": "香港" },
{ "n": "台湾", "v": "台湾" }, { "n": "美国", "v": "美国" }, { "n": "日本", "v": "日本" },
{ "n": "韩国", "v": "韩国" }, { "n": "英国", "v": "英国" }, { "n": "法国", "v": "法国" },
{ "n": "德国", "v": "德国" }, { "n": "泰国", "v": "泰国" }, { "n": "印度", "v": "印度" },
{ "n": "其他", "v": "其他" }
]
};
}
function getYearFilter() {
let years = [{ "n": "全部", "v": "" }];
const currentYear = new Date().getFullYear();
for (let y = currentYear; y >= 2010; y--) {
years.push({ "n": String(y), "v": String(y) });
}
return { "key": "year", "name": "年份", "value": years };
}
const commonFilters = [getAreaFilter(), getYearFilter()];
const myFilters = {};
classList.forEach(item => {
myFilters[item.type_id] = commonFilters;
});
function fixUrl(u) {
if (!u) return '';
if (u.startsWith('http')) return u;
if (u.startsWith('//')) return 'http:' + u;
if (u.startsWith('/')) return appConfig.siteUrl + u;
return u;
}
async function home(filter) {
let list = [];
try {
const html = (await req(appConfig.siteUrl, {
method: "GET",
headers: { "User-Agent": UA }
})).content;
const $ = cheerio.load(html);
let seen = {};
$(".tpl-card[href]").each(function () {
let vod_id = $(this).attr("href");
if (!vod_id || seen[vod_id]) return;
let vod_name = $(this).attr("title") || $(this).find("strong").first().text().trim() || "";
vod_name = vod_name.replace(/免费在线观看$/, "");
let vod_pic = fixUrl($(this).find("img").first().attr("src") || "");
let vod_remarks = $(this).find("em").first().text().trim();
if (vod_name && vod_id) {
seen[vod_id] = true;
list.push({ vod_id, vod_name, vod_pic, vod_remarks });
}
});
} catch (e) {
console.error("首页推荐获取失败:", e.message);
}
return JSON.stringify({
class: classList,
filters: myFilters,
list: list.slice(0, 30)
});
}
function buildCategoryUrl(tid, pg, extend) {
extend = extend || {};
let top = topType(tid);
let topSlug = typeSlug(top);
let childSlug = (tid != top) ? typeSlug(tid) : '';
let path;
if (childSlug && tid != 36) {
path = `/${topSlug}/${childSlug}/`;
} else {
path = `/${topSlug}/`;
}
if (pg > 1) {
path += `page-${pg}.html`;
}
let area = extend.area || '';
let year = extend.year || '';
if (area || year) {
let params = [];
if (area) params.push('area=' + encodeURIComponent(area));
if (year) params.push('year=' + encodeURIComponent(year));
return appConfig.siteUrl + `/api_proxy.php?ac=list&t=${tid}&pg=${pg}&${params.join('&')}`;
}
return appConfig.siteUrl + path;
}
function parseListHtml(html) {
const $ = cheerio.load(html);
let list = [];
let vodIds = {};
$(".tpl-card.card-poster, .tpl-card.card-wide").each(function () {
let $a = $(this);
let vod_id = $a.attr("href");
if (!vod_id || vodIds[vod_id]) return;
let vod_name = $a.attr("title") || $a.find("strong").first().text().trim() || "";
vod_name = vod_name.replace(/免费在线观看$/, "");
let vod_pic = fixUrl($a.find("img").first().attr("src") || "");
let vod_remarks = $a.find("em").first().text().trim() || $a.find("small").first().text().trim();
if (vod_name && vod_id) {
vodIds[vod_id] = true;
list.push({ vod_id, vod_name, vod_pic, vod_remarks });
}
});
if (list.length === 0) {
try {
let data = JSON.parse(html);
if (data.list && Array.isArray(data.list)) {
data.list.forEach(item => {
let vod_id = `/${typeSlug(topType(item.type_id))}/${(item.vod_en || item.vod_name || '').toLowerCase().replace(/[^a-z0-9]+/g, '')}.html`;
let vod_name = item.vod_name || "";
let vod_pic = item.vod_pic || "";
let vod_remarks = item.vod_remarks || "";
if (vod_name) {
list.push({ vod_id, vod_name, vod_pic, vod_remarks });
}
});
}
} catch (e) {
}
}
let pagecount = 1;
$(".pagination a").each(function () {
let href = $(this).attr("href") || '';
let m = href.match(/page-(\d+)/);
if (m) {
let p = parseInt(m[1]);
if (p > pagecount) pagecount = p;
}
});
return { list, pagecount };
}
async function category(tid, pg, filter, extend) {
pg = pg || 1;
extend = extend || {};
let url = buildCategoryUrl(tid, pg, extend);
try {
const html = (await req(url, {
method: "GET",
headers: { "User-Agent": UA, "Referer": appConfig.siteUrl }
})).content;
const result = parseListHtml(html);
return JSON.stringify(result);
} catch (e) {
console.error("分类列表获取失败:", e.message);
return JSON.stringify({ list: [], pagecount: 0 });
}
}
async function search(wd, quick, page) {
page = page || 1;
try {
const url = `${appConfig.siteUrl}/search-${encodeURIComponent(wd)}${page > 1 ? '-page-' + page : ''}.html`;
const html = (await req(url, {
method: "GET",
headers: { "User-Agent": UA, "Referer": appConfig.siteUrl }
})).content;
const result = parseListHtml(html);
return JSON.stringify(result);
} catch (e) {
console.error("搜索失败:", e.message);
return JSON.stringify({ list: [], pagecount: 0 });
}
}
async function detail(id) {
try {
const html = (await req(appConfig.siteUrl + id, {
method: "GET",
headers: { "User-Agent": UA, "Referer": appConfig.siteUrl }
})).content;
const $ = cheerio.load(html);
let vod_name = $("h1").first().text().trim();
let vod_pic = fixUrl($(".detail-poster img").first().attr("src") || "");
let vod_director = "";
let vod_actor = "";
let vod_area = "";
let vod_year = "";
let vod_content = "";
let vod_class = "";
let vod_remarks = "";
$(".detail-tags span").each(function () {
let text = $(this).text().trim();
if (/^\d{4}$/.test(text) && !vod_year) vod_year = text;
else if (/大陆|香港|台湾|美国|日本|韩国|英国|法国|德国|泰国|印度|其他/.test(text) && !vod_area) vod_area = text;
else if (!vod_class && text !== vod_year && text !== vod_area && !/更新至|集|期|TC|HD|正片/.test(text)) vod_class = text;
});
vod_remarks = $(".detail-poster span").first().text().trim();
$(".detail-meta p").each(function () {
let text = $(this).text();
if (text.includes("主演:") && !vod_actor) {
vod_actor = text.replace("主演:", "").trim();
}
if (text.includes("导演:") && !vod_director) {
vod_director = text.replace("导演:", "").trim();
}
});
let $intro = $(".detail-intro");
if ($intro.length > 0) {
vod_content = $intro.first().text().replace("简介:", "").trim();
}
if (!vod_content) {
$intro = $(".article-text");
if ($intro.length > 0) {
vod_content = $intro.first().text().trim();
}
}
let lines = [];
let playlists = [];
let epArray = [];
$(".episode-grid a").each(function () {
let name = $(this).text().trim();
let href = $(this).attr('href') || '';
if (name && href) {
epArray.push({ name, href });
}
});
epArray.sort((a, b) => {
let numA = parseInt(a.name.match(/第(\d+)/)?.[1] || 0);
let numB = parseInt(b.name.match(/第(\d+)/)?.[1] || 0);
return numA - numB;
});
let episodes = epArray.map(ep => `${ep.name}$${ep.href}`);
if (episodes.length > 0) {
lines.push("默认");
playlists.push(episodes);
} else {
let playHref = $(".detail-actions a.btn-primary").attr("href") || "";
if (playHref) {
lines.push("默认");
playlists.push([`正片$${playHref}`]);
}
}
if (lines.length === 0) {
lines.push("默认");
playlists.push([`暂无播放地址$${id}`]);
}
const { vod_play_from, vod_play_url } = buildVodPlayData(lines, playlists);
return JSON.stringify({
list: [{
vod_id: id,
vod_name,
vod_pic,
vod_actor,
vod_director,
vod_remarks,
vod_year,
vod_area,
vod_content,
vod_class,
vod_play_from,
vod_play_url
}]
});
} catch (error) {
console.error(`解析详情页异常 [ID: ${id}]:`, error);
return JSON.stringify({ list: [] });
}
}
function buildVodPlayData(lines, playlists) {
const processedPlaylists = playlists.map(eps => eps.join('#'));
return {
vod_play_from: lines.filter(Boolean).join('$$$'),
vod_play_url: processedPlaylists.join('$$$')
};
}
async function play(flag, id, flags) {
try {
if (id.startsWith("http")) {
return JSON.stringify({
parse: 0,
Header: { "User-Agent": UA, "Referer": appConfig.siteUrl },
url: id
});
}
const html = (await req(`${appConfig.siteUrl}${id}`, {
method: "GET",
headers: { "User-Agent": UA, "Referer": appConfig.siteUrl }
})).content;
// 方式1:从 noscript 中提取 m3u8 URL
let noscriptMatch = html.match(/<noscript>.*?当前播放地址[:]\s*(https?:\/\/[^\s<"]+\.m3u8[^\s<"]*)/);
if (noscriptMatch) {
return JSON.stringify({
parse: 0,
Header: { "User-Agent": UA, "Referer": appConfig.siteUrl },
url: noscriptMatch[1]
});
}
// 方式2:从 JS 中提取 var src="..."
let srcMatch = html.match(/var\s+src\s*=\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)/);
if (srcMatch) {
return JSON.stringify({
parse: 0,
Header: { "User-Agent": UA, "Referer": appConfig.siteUrl },
url: srcMatch[1]
});
}
// 方式3:直接匹配 m3u8 URL
let urlMatch = html.match(/(https?:\/\/[^"'\s<>]+\.m3u8[^"'\s<>]*)/);
if (urlMatch) {
return JSON.stringify({
parse: 0,
Header: { "User-Agent": UA, "Referer": appConfig.siteUrl },
url: urlMatch[1]
});
}
// 方式4player_aaaa
let playerMatch = html.match(/var player_aaaa\s*=\s*(\{.+?\});/);
if (playerMatch) {
try {
let playerData = JSON.parse(playerMatch[1]);
if (playerData.url) {
return JSON.stringify({
parse: 0,
Header: { "User-Agent": UA, "Referer": appConfig.siteUrl },
url: playerData.url
});
}
} catch (e) {}
}
// 方式5video 标签的 src
const $ = cheerio.load(html);
let videoSrc = $("video").attr("src") || $("video source").attr("src");
if (videoSrc) {
return JSON.stringify({
parse: 0,
Header: { "User-Agent": UA, "Referer": appConfig.siteUrl },
url: fixUrl(videoSrc)
});
}
return JSON.stringify({
parse: 1,
Header: { "User-Agent": UA, "Referer": appConfig.siteUrl },
url: appConfig.siteUrl + id
});
} catch (e) {
console.error("播放失败:", e);
return JSON.stringify({ parse: 0, url: "" });
}
}
export default {
init,
home,
category,
detail,
search,
play
};
+476
View File
@@ -0,0 +1,476 @@
"""
@header({
searchable: 1,
filterable: 1,
quickSearch: 1,
title: '中央电视台',
lang: 'hipy'
})
"""
#coding=utf-8
#!/usr/bin/python
import sys
sys.path.append('..')
from base.spider import Spider
import json
import time
import base64
import re
from urllib import request, parse
import urllib
import urllib.request
import time
class Spider(Spider): # 元类 默认的元类 type
def getName(self):
return "中央电视台"#可搜索
def init(self,extend=""):
print("============{0}============".format(extend))
pass
def destroy(self):
pass
def isVideoFormat(self,url):
pass
def manualVideoCheck(self):
pass
def homeContent(self,filter):
result = {}
cateManual = {
"央视大全":"节目大全",
"电视剧": "电视剧",
"动画片": "动画片",
"纪录片": "纪录片",
"特别节目": "特别节目"
}
classes = []
for k in cateManual:
classes.append({
'type_name':k,
'type_id':cateManual[k]
})
result['class'] = classes
if(filter):
result['filters'] = self.config['filter']
return result
def homeVideoContent(self):
result = {
'list':[]
}
return result
def categoryContent(self,tid,pg,filter,extend):
result = {}
month = ""#月
year = ""#年
area=''#地区
channel=''#频道
datafl=''#类型
letter=''#字母
pagecount=24
if tid=='动画片':
id=urllib.parse.quote(tid)
if 'datadq-area' in extend.keys():
area=urllib.parse.quote(extend['datadq-area'])
if 'dataszm-letter' in extend.keys():
letter=extend['dataszm-letter']
if 'datafl-sc' in extend.keys():
datafl=urllib.parse.quote(extend['datafl-sc'])
url='https://api.cntv.cn/list/getVideoAlbumList?channelid=CHAL1460955899450127&area={0}&sc={4}&fc={1}&letter={2}&p={3}&n=24&serviceId=tvcctv&topv=1&t=json'.format(area,id,letter,pg,datafl)
elif tid=='纪录片':
id=urllib.parse.quote(tid)
if 'datapd-channel' in extend.keys():
channel=urllib.parse.quote(extend['datapd-channel'])
if 'datafl-sc' in extend.keys():
datafl=urllib.parse.quote(extend['datafl-sc'])
if 'datanf-year' in extend.keys():
year=extend['datanf-year']
if 'dataszm-letter' in extend.keys():
letter=extend['dataszm-letter']
url='https://api.cntv.cn/list/getVideoAlbumList?channelid=CHAL1460955924871139&fc={0}&channel={1}&sc={2}&year={3}&letter={4}&p={5}&n=24&serviceId=tvcctv&topv=1&t=json'.format(id,channel,datafl,year,letter,pg)
elif tid=='电视剧':
id=urllib.parse.quote(tid)
if 'datafl-sc' in extend.keys():
datafl=urllib.parse.quote(extend['datafl-sc'])
if 'datanf-year' in extend.keys():
year=extend['datanf-year']
if 'dataszm-letter' in extend.keys():
letter=extend['dataszm-letter']
url='https://api.cntv.cn/list/getVideoAlbumList?channelid=CHAL1460955853485115&area={0}&sc={1}&fc={2}&year={3}&letter={4}&p={5}&n=24&serviceId=tvcctv&topv=1&t=json'.format(area,datafl,id,year,letter,pg)
elif tid=='特别节目':
id=urllib.parse.quote(tid)
if 'datapd-channel' in extend.keys():
channel=urllib.parse.quote(extend['datapd-channel'])
if 'datafl-sc' in extend.keys():
datafl=urllib.parse.quote(extend['datafl-sc'])
if 'dataszm-letter' in extend.keys():
letter=extend['dataszm-letter']
url='https://api.cntv.cn/list/getVideoAlbumList?channelid=CHAL1460955953877151&channel={0}&sc={1}&fc={2}&bigday=&letter={3}&p={4}&n=24&serviceId=tvcctv&topv=1&t=json'.format(channel,datafl,id,letter,pg)
elif tid=='节目大全':
cid=''#频道
if 'cid' in extend.keys():
cid=extend['cid']
fc=''#分类
if 'fc' in extend.keys():
fc=extend['fc']
fl=''#字母
if 'fl' in extend.keys():
fl=extend['fl']
url = 'https://api.cntv.cn/lanmu/columnSearch?&fl={0}&fc={1}&cid={2}&p={3}&n=20&serviceId=tvcctv&t=json&cb=ko'.format(fl,fc,cid,pg)
pagecount=20
else:
url = 'https://tv.cctv.com/epg/index.shtml'
videos=[]
htmlText =self.webReadFile(urlStr=url,header=self.header)
if tid=='节目大全':
index=htmlText.rfind(');')
if index>-1:
htmlText=htmlText[3:index]
videos =self.get_list1(html=htmlText,tid=tid)
else:
videos =self.get_list(html=htmlText,tid=tid)
#print(videos)
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999 if len(videos)>=pagecount else pg
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self,array):
result={}
aid = array[0].split('###')
tid = aid[0]
logo = aid[3]
lastVideo = aid[2]
title = aid[1]
id= aid[4]
vod_year= aid[5]
actors= aid[6]
brief= aid[7]
fromId='CCTV'
if tid=="节目大全":
lastUrl = 'https://api.cntv.cn/video/videoinfoByGuid?guid={0}&serviceId=tvcctv'.format(id)
htmlTxt = self.webReadFile(urlStr=lastUrl,header=self.header)
topicId=json.loads(htmlTxt)['ctid']
Url = "https://api.cntv.cn/NewVideo/getVideoListByColumn?id={0}&d=&p=1&n=100&sort=desc&mode=0&serviceId=tvcctv&t=json".format(topicId)
htmlTxt = self.webReadFile(urlStr=Url,header=self.header)
else:
Url='https://api.cntv.cn/NewVideo/getVideoListByAlbumIdNew?id={0}&serviceId=tvcctv&p=1&n=100&mode=0&pub=1'.format(id)
jRoot = ''
videoList = []
try:
if tid=="搜索":
fromId='中央台'
videoList=[title+"$"+lastVideo]
else:
htmlTxt=self.webReadFile(urlStr=Url,header=self.header)
jRoot = json.loads(htmlTxt)
data=jRoot['data']
jsonList=data['list']
videoList=self.get_EpisodesList(jsonList=jsonList)
if len(videoList)<1:
htmlTxt=self.webReadFile(urlStr=lastVideo,header=self.header)
if tid=="电视剧" or tid=="纪录片":
patternTxt=r"'title':\s*'(?P<title>.+?)',\n{0,1}\s*'brief':\s*'(.+?)',\n{0,1}\s*'img':\s*'(.+?)',\n{0,1}\s*'url':\s*'(?P<url>.+?)'"
elif tid=="特别节目":
patternTxt=r'class="tp1"><a\s*href="(?P<url>https://.+?)"\s*target="_blank"\s*title="(?P<title>.+?)"></a></div>'
elif tid=="动画片":
patternTxt=r"'title':\s*'(?P<title>.+?)',\n{0,1}\s*'img':\s*'(.+?)',\n{0,1}\s*'brief':\s*'(.+?)',\n{0,1}\s*'url':\s*'(?P<url>.+?)'"
elif tid=="节目大全":
patternTxt=r'href="(?P<url>.+?)" target="_blank" alt="(?P<title>.+?)" title=".+?">'
videoList=self.get_EpisodesList_re(htmlTxt=htmlTxt,patternTxt=patternTxt)
fromId='央视'
except:
pass
if len(videoList) == 0:
return {}
vod = {
"vod_id":array[0],
"vod_name":title,
"vod_pic":logo,
"type_name":tid,
"vod_year":vod_year,
"vod_area":"",
"vod_remarks":'',
"vod_actor":actors,
"vod_director":'',
"vod_content":brief
}
vod['vod_play_from'] = fromId
vod['vod_play_url'] = "#".join(videoList)
result = {
'list':[
vod
]
}
return result
def get_lineList(self,Txt,mark,after):
circuit=[]
origin=Txt.find(mark)
while origin>8:
end=Txt.find(after,origin)
circuit.append(Txt[origin:end])
origin=Txt.find(mark,end)
return circuit
def get_RegexGetTextLine(self,Text,RegexText,Index):
returnTxt=[]
pattern = re.compile(RegexText, re.M|re.S)
ListRe=pattern.findall(Text)
if len(ListRe)<1:
return returnTxt
for value in ListRe:
returnTxt.append(value)
return returnTxt
def searchContent(self,key,quick):
return self.searchContentPage(key, quick, '1')
def searchContentPage(self, key, quick, page):
key=urllib.parse.quote(key)
Url='https://search.cctv.com/ifsearch.php?page=1&qtext={0}&sort=relevance&pageSize=20&type=video&vtime=-1&datepid=1&channel=&pageflag=0&qtext_str={0}'.format(key)
htmlTxt=self.webReadFile(urlStr=Url,header=self.header)
videos=self.get_list_search(html=htmlTxt,tid='搜索')
result = {
'list':videos
}
return result
def playerContent(self,flag,id,vipFlags):
result = {}
url=''
parse=0
headers = {
'User-Agent':'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1'
}
if flag=='CCTV':
url=self.get_m3u8(urlTxt=id)
else:
try:
html=self.webReadFile(urlStr=id,header=self.header)
guid=self.get_RegexGetText(Text=html,RegexText=r'var\sguid\s*=\s*"(.+?)";',Index=1)
url=self.get_m3u8(urlTxt=guid)
except :
url=id
parse=1
if url.find('https:')<0:
url=id
parse=1
result["parse"] = parse#1=嗅探,0=播放
result["playUrl"] = ''
result["url"] = url
result["header"] =headers
return result
config = {
"player": {},
"filter": {
"电视剧":[
{"key":"datafl-sc","name":"类型","value":[{"n":"全部","v":""},{"n":"谍战","v":"谍战"},{"n":"悬疑","v":"悬疑"},{"n":"刑侦","v":"刑侦"},{"n":"历史","v":"历史"},{"n":"古装","v":"古装"},{"n":"武侠","v":"武侠"},{"n":"军旅","v":"军旅"},{"n":"战争","v":"战争"},{"n":"喜剧","v":"喜剧"},{"n":"青春","v":"青春"},{"n":"言情","v":"言情"},{"n":"偶像","v":"偶像"},{"n":"家庭","v":"家庭"},{"n":"年代","v":"年代"},{"n":"革命","v":"革命"},{"n":"农村","v":"农村"},{"n":"都市","v":"都市"},{"n":"其他","v":"其他"}]},
{"key":"datadq-area","name":"地区","value":[{"n":"全部","v":""},{"n":"中国大陆","v":"中国大陆"},{"n":"中国香港","v":"香港"},{"n":"美国","v":"美国"},{"n":"欧洲","v":"欧洲"},{"n":"泰国","v":"泰国"}]},
{"key":"datanf-year","name":"年份","value":[{"n":"全部","v":""},{"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"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"},{"n":"1999","v":"1999"},{"n":"1998","v":"1998"},{"n":"1997","v":"1997"}]},
{"key":"dataszm-letter","name":"字母","value":[{"n":"全部","v":""},{"n":"A","v":"A"},{"n":"C","v":"C"},{"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"}]}
],
"动画片":[
{"key":"datafl-sc","name":"类型","value":[{"n":"全部","v":""},{"n":"亲子","v":"亲子"},{"n":"搞笑","v":"搞笑"},{"n":"冒险","v":"冒险"},{"n":"动作","v":"动作"},{"n":"宠物","v":"宠物"},{"n":"体育","v":"体育"},{"n":"益智","v":"益智"},{"n":"历史","v":"历史"},{"n":"教育","v":"教育"},{"n":"校园","v":"校园"},{"n":"言情","v":"言情"},{"n":"武侠","v":"武侠"},{"n":"经典","v":"经典"},{"n":"未来","v":"未来"},{"n":"古代","v":"古代"},{"n":"神话","v":"神话"},{"n":"真人","v":"真人"},{"n":"励志","v":"励志"},{"n":"热血","v":"热血"},{"n":"奇幻","v":"奇幻"},{"n":"童话","v":"童话"},{"n":"剧情","v":"剧情"},{"n":"夺宝","v":"夺宝"},{"n":"其他","v":"其他"}]},
{"key":"datadq-area","name":"地区","value":[{"n":"全部","v":""},{"n":"中国大陆","v":"中国大陆"},{"n":"美国","v":"美国"},{"n":"欧洲","v":"欧洲"}]},
{"key":"dataszm-letter","name":"字母","value":[{"n":"全部","v":""},{"n":"A","v":"A"},{"n":"C","v":"C"},{"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"}]}
],
"纪录片":[
{"key":"datapd-channel","name":"频道","value":[{"n":"全部","v":""},{"n":"CCTV{1 综合","v":"CCTV{1 综合"},{"n":"CCTV{2 财经","v":"CCTV{2 财经"},{"n":"CCTV{3 综艺","v":"CCTV{3 综艺"},{"n":"CCTV{4 中文国际","v":"CCTV{4 中文国际"},{"n":"CCTV{5 体育","v":"CCTV{5 体育"},{"n":"CCTV{6 电影","v":"CCTV{6 电影"},{"n":"CCTV{7 国防军事","v":"CCTV{7 国防军事"},{"n":"CCTV{8 电视剧","v":"CCTV{8 电视剧"},{"n":"CCTV{9 纪录","v":"CCTV{9 纪录"},{"n":"CCTV{10 科教","v":"CCTV{10 科教"},{"n":"CCTV{11 戏曲","v":"CCTV{11 戏曲"},{"n":"CCTV{12 社会与法","v":"CCTV{12 社会与法"},{"n":"CCTV{13 新闻","v":"CCTV{13 新闻"},{"n":"CCTV{14 少儿","v":"CCTV{14 少儿"},{"n":"CCTV{15 音乐","v":"CCTV{15 音乐"},{"n":"CCTV{17 农业农村","v":"CCTV{17 农业农村"}]},
{"key":"datafl-sc","name":"类型","value":[{"n":"全部","v":""},{"n":"人文历史","v":"人文历史"},{"n":"人物","v":"人物"},{"n":"军事","v":"军事"},{"n":"探索","v":"探索"},{"n":"社会","v":"社会"},{"n":"时政","v":"时政"},{"n":"经济","v":"经济"},{"n":"科技","v":"科技"}]},
{"key":"datanf-year","name":"年份","value":[{"n":"全部","v":""},{"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"}]},
{"key":"dataszm-letter","name":"字母","value":[{"n":"全部","v":""},{"n":"A","v":"A"},{"n":"C","v":"C"},{"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"}]}
],
"特别节目":[
{"key":"datapd-channel","name":"频道","value":[{"n":"全部","v":""},{"n":"CCTV{1 综合","v":"CCTV{1 综合"},{"n":"CCTV{2 财经","v":"CCTV{2 财经"},{"n":"CCTV{3 综艺","v":"CCTV{3 综艺"},{"n":"CCTV{4 中文国际","v":"CCTV{4 中文国际"},{"n":"CCTV{5 体育","v":"CCTV{5 体育"},{"n":"CCTV{6 电影","v":"CCTV{6 电影"},{"n":"CCTV{7 国防军事","v":"CCTV{7 国防军事"},{"n":"CCTV{8 电视剧","v":"CCTV{8 电视剧"},{"n":"CCTV{9 纪录","v":"CCTV{9 纪录"},{"n":"CCTV{10 科教","v":"CCTV{10 科教"},{"n":"CCTV{11 戏曲","v":"CCTV{11 戏曲"},{"n":"CCTV{12 社会与法","v":"CCTV{12 社会与法"},{"n":"CCTV{13 新闻","v":"CCTV{13 新闻"},{"n":"CCTV{14 少儿","v":"CCTV{14 少儿"},{"n":"CCTV{15 音乐","v":"CCTV{15 音乐"},{"n":"CCTV{17 农业农村","v":"CCTV{17 农业农村"}]},
{"key":"datafl-sc","name":"类型","value":[{"n":"全部","v":""},{"n":"全部","v":"全部"},{"n":"新闻","v":"新闻"},{"n":"经济","v":"经济"},{"n":"综艺","v":"综艺"},{"n":"体育","v":"体育"},{"n":"军事","v":"军事"},{"n":"影视","v":"影视"},{"n":"科教","v":"科教"},{"n":"戏曲","v":"戏曲"},{"n":"青少","v":"青少"},{"n":"音乐","v":"音乐"},{"n":"社会","v":"社会"},{"n":"公益","v":"公益"},{"n":"其他","v":"其他"}]},
{"key":"dataszm-letter","name":"字母","value":[{"n":"全部","v":""},{"n":"A","v":"A"},{"n":"C","v":"C"},{"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"}]}
],
"节目大全":[{"key":"cid","name":"频道","value":[{"n":"全部","v":""},{"n":"CCTV-1综合","v":"EPGC1386744804340101"},{"n":"CCTV-2财经","v":"EPGC1386744804340102"},{"n":"CCTV-3综艺","v":"EPGC1386744804340103"},{"n":"CCTV-4中文国际","v":"EPGC1386744804340104"},{"n":"CCTV-5体育","v":"EPGC1386744804340107"},{"n":"CCTV-6电影","v":"EPGC1386744804340108"},{"n":"CCTV-7国防军事","v":"EPGC1386744804340109"},{"n":"CCTV-8电视剧","v":"EPGC1386744804340110"},{"n":"CCTV-9纪录","v":"EPGC1386744804340112"},{"n":"CCTV-10科教","v":"EPGC1386744804340113"},{"n":"CCTV-11戏曲","v":"EPGC1386744804340114"},{"n":"CCTV-12社会与法","v":"EPGC1386744804340115"},{"n":"CCTV-13新闻","v":"EPGC1386744804340116"},{"n":"CCTV-14少儿","v":"EPGC1386744804340117"},{"n":"CCTV-15音乐","v":"EPGC1386744804340118"},{"n":"CCTV-16奥林匹克","v":"EPGC1634630207058998"},{"n":"CCTV-17农业农村","v":"EPGC1563932742616872"},{"n":"CCTV-5+体育赛事","v":"EPGC1468294755566101"}]},{"key":"fc","name":"分类","value":[{"n":"全部","v":""},{"n":"新闻","v":"新闻"},{"n":"体育","v":"体育"},{"n":"综艺","v":"综艺"},{"n":"健康","v":"健康"},{"n":"生活","v":"生活"},{"n":"科教","v":"科教"},{"n":"经济","v":"经济"},{"n":"农业","v":"农业"},{"n":"法治","v":"法治"},{"n":"军事","v":"军事"},{"n":"少儿","v":"少儿"},{"n":"动画","v":"动画"},{"n":"纪实","v":"纪实"},{"n":"戏曲","v":"戏曲"},{"n":"音乐","v":"音乐"},{"n":"影视","v":"影视"}]},{"key":"fl","name":"字母","value":[{"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"}]},{"key":"year","name":"年份","value":[{"n":"全部","v":""},{"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"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"}]},{"key":"month","name":"月份","value":[{"n":"全部","v":""},{"n":"12","v":"12"},{"n":"11","v":"11"},{"n":"10","v":"10"},{"n":"09","v":"09"},{"n":"08","v":"08"},{"n":"07","v":"07"},{"n":"06","v":"06"},{"n":"05","v":"05"},{"n":"04","v":"04"},{"n":"03","v":"03"},{"n":"02","v":"02"},{"n":"01","v":"01"}]}]
}
}
header = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36",
"Host": "tv.cctv.com",
"Referer": "https://tv.cctv.com/"
}
def localProxy(self,param):
return [200, "video/MP2T", ""]
#-----------------------------------------------自定义函数-----------------------------------------------
#访问网页
def webReadFile(self,urlStr,header):
html=''
req=urllib.request.Request(url=urlStr)#,headers=header
with urllib.request.urlopen(req) as response:
html = response.read().decode('utf-8')
return html
#判断网络地址是否存在
def TestWebPage(self,urlStr,header):
html=''
req=urllib.request.Request(url=urlStr,method='HEAD')#,headers=header
with urllib.request.urlopen(req) as response:
html = response.getcode ()
return html
#正则取文本
def get_RegexGetText(self,Text,RegexText,Index):
returnTxt=""
Regex=re.search(RegexText, Text, re.M|re.S)
if Regex is None:
returnTxt=""
else:
returnTxt=Regex.group(Index)
return returnTxt
#取集数
def get_EpisodesList(self,jsonList):
videos=[]
for vod in jsonList:
url = vod['guid']
title =vod['title']
if len(url) == 0:
continue
videos.append(title+"$"+url)
return videos
#取集数
def get_EpisodesList_re(self,htmlTxt,patternTxt):
ListRe=re.finditer(patternTxt, htmlTxt, re.M|re.S)
videos=[]
for vod in ListRe:
url = vod.group('url')
title =vod.group('title')
if len(url) == 0:
continue
videos.append(title+"$"+url)
return videos
#取剧集区
def get_lineList(self,Txt,mark,after):
circuit=[]
origin=Txt.find(mark)
while origin>8:
end=Txt.find(after,origin)
circuit.append(Txt[origin:end])
origin=Txt.find(mark,end)
return circuit
#正则取文本,返回数组
def get_RegexGetTextLine(self,Text,RegexText,Index):
returnTxt=[]
pattern = re.compile(RegexText, re.M|re.S)
ListRe=pattern.findall(Text)
if len(ListRe)<1:
return returnTxt
for value in ListRe:
returnTxt.append(value)
return returnTxt
#删除html标签
def removeHtml(self,txt):
soup = re.compile(r'<[^>]+>',re.S)
txt =soup.sub('', txt)
return txt.replace("&nbsp;"," ")
#取m3u8
def get_m3u8(self,urlTxt):
url = "https://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid={0}".format(urlTxt)
html=self.webReadFile(urlStr=url,header=self.header)
jo =json.loads(html)
link = jo['hls_url'].strip()
html = self.webReadFile(urlStr=link,header=self.header)
content = html.strip()
arr = content.split('\n')
urlPrefix = self.get_RegexGetText(Text=link,RegexText='(http[s]?://[a-zA-z0-9.]+)/',Index=1)
subUrl = arr[-1].split('/')
subUrl[3] = '1200'
subUrl[-1] = '1200.m3u8'
hdUrl = urlPrefix + '/'.join(subUrl)
url = urlPrefix + arr[-1]
hdRsp = self.TestWebPage(urlStr=hdUrl,header=self.header)
if hdRsp == 200:
url = hdUrl
else:
url=''
return url
#搜索
def get_list_search(self,html,tid):
jRoot = json.loads(html)
jsonList=jRoot['list']
videos=[]
for vod in jsonList:
url = vod['urllink']
title =self.removeHtml(txt=vod['title'])
img=vod['imglink']
id=vod['id']
brief=vod['channel']
year=vod['uploadtime']
if len(url) == 0:
continue
guid="{0}###{1}###{2}###{3}###{4}###{5}###{6}###{7}".format(tid,title,url,img,id,year,'',brief)
videos.append({
"vod_id":guid,
"vod_name":title,
"vod_pic":img,
"vod_remarks":year
})
return videos
return videos
def get_list1(self,html,tid):
jRoot = json.loads(html)
videos = []
data=jRoot['response']
if data is None:
return []
jsonList=data['docs']
for vod in jsonList:
id = vod['lastVIDE']['videoSharedCode']
title =vod['column_name']
url=vod['column_website']
img=vod['column_logo']
year=vod['column_playdate']
brief=vod['column_brief']
actors=''
if len(url) == 0:
continue
guid="{0}###{1}###{2}###{3}###{4}###{5}###{6}###{7}".format(tid,title,url,img,id,year,actors,brief)
#print(vod_id)
videos.append({
"vod_id":guid,
"vod_name":title,
"vod_pic":img,
"vod_remarks":''
})
#print(videos)
return videos
#分类取结果
def get_list(self,html,tid):
jRoot = json.loads(html)
videos = []
data=jRoot['data']
if data is None:
return []
jsonList=data['list']
for vod in jsonList:
url = vod['url']
title =vod['title']
img=vod['image']
id=vod['id']
try:
brief=vod['brief']
except:
brief=''
try:
year=vod['year']
except:
year=''
try:
actors=vod['actors']
except:
actors=''
if len(url) == 0:
continue
guid="{0}###{1}###{2}###{3}###{4}###{5}###{6}###{7}".format(tid,title,url,img,id,year,actors,brief)
#print(vod_id)
videos.append({
"vod_id":guid,
"vod_name":title,
"vod_pic":img,
"vod_remarks":''
})
return videos
+242
View File
@@ -0,0 +1,242 @@
#coding=utf-8
#!/usr/bin/python
import sys
sys.path.append('..')
from base.spider import Spider
import json
import time
import base64
import requests
class Spider(Spider):
def getName(self):
return "央视综艺"
def init(self, extend=""):
print("============{0}============".format(extend))
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def homeContent(self, filter):
result = {}
cateManual = {
"中华情": "TOPC1451541564922207",
"回声嘹亮": "TOPC1451535575561597",
"你好生活第三季": "TOPC1627961377879898",
"我的艺术清单": "TOPC1582272259917160",
"黄金100秒": "TOPC1451468496522494",
"非常6+1": "TOPC1451467940101208",
"向幸福出发": "TOPC1451984638791216",
"幸福账单": "TOPC1451984801613379",
"中国文艺报道": "TOPC1601348042760302",
"舞蹈世界": "TOPC1451547605511387",
"艺览天下": "TOPC1451984851125433",
"天天把歌唱": "TOPC1451535663610626",
"金牌喜剧班": "TOPC1611826337610628",
"环球综艺秀": "TOPC1571300682556971",
"挑战不可能第五季": "TOPC1579169060379297",
"我们有一套": "TOPC1451527089955940",
"为了你": "TOPC1451527001597710",
"朗读者第一季": "TOPC1487120479377477",
"挑战不可能第二季": "TOPC1474277421637816",
"精彩一刻": "TOPC1451464786232149",
"挑战不可能之加油中国": "TOPC1547519813971570",
"挑战不可能第一季": "TOPC1452063816677656",
"机智过人第三季": "TOPC1564019920570762",
"经典咏流传第二季": "TOPC1547521714115947",
"挑战不可能第三季": "TOPC1509500865106312",
"经典咏流传第一季": "TOPC1513676755770201",
"欢乐中国人第二季": "TOPC1516784350726581",
"故事里的中国第一季": "TOPC1569729252342702",
"你好生活第二季": "TOPC1604397385056621",
"喜上加喜": "TOPC1590026042145705",
"走在回家的路上": "TOPC1577697653272281",
"综艺盛典": "TOPC1451985071887935",
"艺术人生": "TOPC1451984891490556",
"全家好拍档": "TOPC1474275463547690",
"大魔术师": "TOPC1451984047073332",
"欢乐一家亲": "TOPC1451984214170587",
"开心辞典": "TOPC1451984378754815",
"综艺星天地": "TOPC1451985188986150",
"激情广场": "TOPC1451984341218765",
"笑星大联盟": "TOPC1451984731428297",
"天天乐": "TOPC1451984447718918",
"欢乐英雄": "TOPC1451984242834620",
"欢乐中国行": "TOPC1451984301286720",
"我爱满堂彩": "TOPC1451538709371329",
"综艺头条": "TOPC1569226855085860",
"中华情": "TOPC1451541564922207",
"魔法奇迹": "TOPC1451542029126607"
}
classes = []
for k in cateManual:
classes.append({
'type_name': k,
'type_id': cateManual[k]
})
result['class'] = classes
if filter:
result['filters'] = self.config['filter']
return result
def homeVideoContent(self):
result = {
'list': []
}
return result
def categoryContent(self, tid, pg, filter, extend):
result = {}
extend['id'] = tid
extend['p'] = pg
filterParams = ["id", "p", "d"]
params = ["", "", ""]
for idx in range(len(filterParams)):
fp = filterParams[idx]
if fp in extend.keys():
params[idx] = '{0}={1}'.format(filterParams[idx], extend[fp])
suffix = '&'.join(params)
url = 'https://api.cntv.cn/NewVideo/getVideoListByColumn?{0}&n=20&sort=desc&mode=0&serviceId=tvcctv&t=json'.format(suffix)
if not tid.startswith('TOPC'):
url = 'https://api.cntv.cn/NewVideo/getVideoListByAlbumIdNew?{0}&n=20&sort=desc&mode=0&serviceId=tvcctv&t=json'.format(suffix)
rsp = self.fetch(url, headers=self.header)
jo = json.loads(rsp.text)
vodList = jo['data']['list']
videos = []
for vod in vodList:
guid = vod['guid']
title = vod['title']
img = vod['image']
brief = vod['brief']
videos.append({
"vod_id": guid + "###" + img,
"vod_name": title,
"vod_pic": img,
"vod_remarks": ''
})
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, array):
aid = array[0].split('###')
tid = aid[0]
url = "https://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid={0}".format(tid)
rsp = self.fetch(url, headers=self.header)
jo = json.loads(rsp.text)
title = jo['title'].strip()
link = jo['hls_url'].strip()
vod = {
"vod_id": tid,
"vod_name": title,
"vod_pic": aid[1],
"type_name": '',
"vod_year": "",
"vod_area": "",
"vod_remarks": "",
"vod_actor": "",
"vod_director": "",
"vod_content": ""
}
vod['vod_play_from'] = 'CCTV'
vod['vod_play_url'] = title + "$" + link
result = {
'list': [vod]
}
return result
def searchContent(self, key, quick):
result = {
'list': []
}
return result
def playerContent(self, flag, id, vipFlags):
result = {}
# 先尝试获取原始m3u8文件
rsp = self.fetch(id, headers=self.header)
content = rsp.text.strip()
if not content:
# 如果获取失败,直接返回原始链接
result["parse"] = 0
result["playUrl"] = ''
result["url"] = id
result["header"] = self.header
return result
arr = content.split('\n')
urlPrefix = self.regStr(id, '(http[s]?://[a-zA-z0-9.]+)/')
# 尝试获取高清链接
resolutions = ['2000', '1200', '800'] # 从高到低尝试
final_url = id # 默认使用原始链接
for res in resolutions:
try:
subUrl = arr[-1].split('/')
subUrl[3] = res
subUrl[-1] = f'{res}.m3u8'
hdUrl = urlPrefix + '/'.join(subUrl)
# 检查高清链接是否有效
hdRsp = requests.head(hdUrl, headers=self.header, timeout=5)
if hdRsp.status_code == 200:
final_url = hdUrl
break
except:
continue
result["parse"] = 0
result["playUrl"] = ''
result["url"] = final_url
result["header"] = self.header
return result
config = {
"player": {},
"filter": {
"TOPC1451557970755294": [
{
"key": "d",
"name": "年份",
"value": [
{"n": "全部", "v": ""},
{"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"}
]
}
]
}
}
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36",
"Referer": "https://www.cctv.com/",
"Origin": "https://www.cctv.com"
}
def localProxy(self, param):
return [200, "video/MP2T", None, ""]
+350
View File
@@ -0,0 +1,350 @@
# -*- coding: utf-8 -*-
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
#junyouyun
import sys
import json
import time
import base64
import hashlib
import urllib3
import concurrent.futures
from urllib.parse import quote
from base.spider import Spider
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sys.path.append('..')
class Spider(Spider):
host, userid, episode_list = '', '', []
# ---------- 加密与签名相关常量 ----------
PUB_KEY_B64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoYt0BP77U+DM08BiI/QbSRIfxijXo85BTPqIM1Ow8BNwhLETzRIZ+dEwdWDbydG/PspgBAfRpGaYVdJYtvaC2JnoO8+Ik6qMWojfEJxSFLa0Pb0A892tun4gsxoEMjcreZ+YGyaBxAfqX0BSMfdrOgIYaZQjYrw9TRLlUT31QoQIDAQAB"
APP_SIGN_SHA1 = "09a8dc51639a31801af5f6418caebfabc695eb24"
DEVICE_ID = "2d590b9842d064a1"
# RSA 私钥(用于解密响应)
PRIV_KEY_B64 = """MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCquQQ5r6+yJI8CDFkXRp8vUsdD45ov8EP12ooLs56ca2DQXaSNGS9910bAPVA9chkp0mKIvKqjAsHz5Tl9EeNPblarGEeJUIxpxZtiSqNTpvtiD/TjhpzuHYic7RAfQ/h7p/ypE8ymU42pYjsB5t26Mv6XgkLV+jzrSf73HlCuS0iMyLmt6zz3Mw9izM13EpB8iFLtfbbYymycKTx4RAmPQLwhNGex/AlUIYxXP4R2yyaa4W6mEtc6aME2QuzJFxPgP3HJ9NBx/LWVn4skxWjZ7zg+VRQRHnjyVaSLu3Z5gN5ITWCyE32qaHJa6WBahZj5jWhRyAG1bQ+xKJa8lBL5AgMBAAECggEAUwv9SjJ0PSwbhNuM2w23kcWquROWhYtTA91zGY4esehqB/IFgb2mpIh8Gje5OKqwIu/8jpd4SiOlRYdUF8sD0DfUYRZGdj2AkFNX6tBz8tVfo6wvbB6naA1lzzBij1L5JO3qsjS3cJFkb+kg2yP66AC2Z+0tpfk8eRhdtshAZwfcd1DEGt1uAvYL1eaUK9HRvpt9lPeGcHERDl2hBd4uyaF0K1O+zF9y59nYbTySWPxRZq3sFEE85xRMlstD7YZi7W2gKvMFRD4/FKmrZ3m7aKJRITtyKOyyPcYmepNv3Qv7kk59Pg38n2WWQ0Ra/bCH3E48YNCnQvZMpitkTfJhoQKBgQDbnROOYTP8OTJ6f/qhoGjxeO3x1VOaOp8l0x7b0SCfoqNGS0Cyiqj72BmJtPMPqSTjn6MmNzqbg1KOdhXyzNozs+i5ccW1M56j96mr5I/Z0FpE3oyIHNfDDBlf9M8YQqEF9oYxniYYft9oapO7cRQkHER6qpvnHTavwlv4m78CXwKBgQDHAjs2YlpKDdI1lcbZJCc7TwtH+Pd2bUki8YXafWNcPhITQHbOZjr310eK1QJC6GJncjkOqbX7yv3ivvTO35FZTQhuA1xEG1P00FG8bE0tHYPIwQHi9y0eA5cieMdo8E6XYria1mw/3fqSQEsfZyJlR32JQIoGAipM8iO1X2nZpwKBgDkMFIhnt5lNQk+P7wsNIDWZtDWdtJnboHuy29E+Abt2A/O+mI/IdRz2hau/1WO8DFkUnszOi+rZshhPlGP90rCbi1igtTrcrdjp/KkqNjPea5R4OwkgdOu1uOG0NheXNzzVTQaWjk7Opjn5dWa7eP/oV+GFb/oZHJuLYVizHGsBAoGADA7rjZEKDYCm4w5PPSr+oY5ZjaPdQrS+gLqHtMRyN82fBMGcMUdqfUfzEstzVqCEDeaS5HuOBlK3bXzKkppjUTjksN3NQmcxgBz7RuJ9DqXCLXDcb2cwuafYCYOt+YLOEEgwDVm+t2P44dG5e46hO+fICH/7nP+WlpD5buz4GfMCgYB57r3g/6hi9WUDnfc7ZAzWMqR0EhJVYKYy+KFEtdIPzhkkIHq5RASe88E9kzoGoZFdb3tIjvGZWcHerirrqWkMsuQtP/Qi0zjieid5tAPj+r4kbiCVTw0E0jnmPBzGInQi7lpeTTKnG1fbyS5lBS+WmHfIuzpECgCkxhaT+LJJkg=="""
headers = {
'User-Agent': "okhttp/4.12.0",
'Connection': "Keep-Alive",
'Accept-Encoding': "gzip",
'Content-Type': "application/json;charset=UTF-8",
'Cache-Control': "no-cache",
'token': "",
'deviceId': DEVICE_ID,
'client': "app",
'deviceType': "Android"
}
# ---------- RSA 加密 ----------
def rsa_encrypt(self, data: str) -> str:
key = RSA.import_key(base64.b64decode(self.PUB_KEY_B64))
cipher = PKCS1_v1_5.new(key)
encrypted = cipher.encrypt(data.encode('utf-8'))
return base64.b64encode(encrypted).decode('utf-8')
# ---------- RSA 解密(支持分块) ----------
def rsa_decrypt(self, encrypted_b64: str) -> str:
key = RSA.import_key(base64.b64decode(self.PRIV_KEY_B64))
cipher = PKCS1_v1_5.new(key)
encrypted_bytes = base64.b64decode(encrypted_b64)
block_size = 256
decrypted_parts = []
for i in range(0, len(encrypted_bytes), block_size):
block = encrypted_bytes[i:i+block_size]
decrypted_parts.append(cipher.decrypt(block, None))
return b''.join(decrypted_parts).decode('utf-8')
# ---------- 构建签名参数 ----------
def build_params_string(self, episode_id="", episode_index="", vid="", player_id="", type_id="", user_id=""):
return (f"episodeId{episode_id}"
f"episodeIndex{episode_index}"
f"id{vid}"
f"playerId{player_id}"
f"source0"
f"typeId{type_id}"
f"userId{user_id}")
def generate_sign(self, timestamp: str, params_str: str, device_id: str) -> str:
raw = f"SaltLSFBTimestamp{timestamp}Params{params_str}ClientappDeviceId{device_id}"
b64 = base64.b64encode(raw.encode('utf-8')).decode('utf-8')
md5 = hashlib.md5(b64.encode('utf-8')).hexdigest().upper()
return md5
def build_encrypted_headers(self, body_json: str, params_str: str) -> dict:
timestamp = str(int(time.time()))
encrypted_key = self.rsa_encrypt(body_json)
snjm = self.rsa_encrypt("113")
appsign = self.rsa_encrypt(self.APP_SIGN_SHA1)
sign = self.generate_sign(timestamp, params_str, self.DEVICE_ID)
headers = {
"snjm": snjm,
"appsign": appsign,
"timestamp": timestamp,
"sign": sign,
"deviceId": self.DEVICE_ID,
"token": self.headers.get('token', ''),
"client": "app",
"deviceType": "Android",
"Content-Type": "application/json;charset=UTF-8",
"Cache-Control": "no-cache",
"User-Agent": "okhttp/4.12.0"
}
return headers, {"key": encrypted_key}
# ---------- 原有接口(保持不变) ----------
def init(self, extend=''):
self.headers['deviceId'] = self.DEVICE_ID
self.host = 'http://qkys.qukanwh.com'
response = self.fetch(f'{self.host}/api/v1/app/user/visitorInfo', headers=self.headers).json()
self.userid = response['data']['id']
token = response['data']['token']
self.headers['token'] = token
def homeContent(self, filter):
response = self.post(f'{self.host}/api/v1/app/screen/screenType', headers=self.headers).json()
data = response['data']
classes = []
for i in data:
classes.append({'type_id': i['id'], 'type_name': i['name']})
return {'class': classes}
def homeVideoContent(self):
response = self.post(f'{self.host}/api/v1/app/recommend/recommendList', headers=self.headers).json()
data = response['data']
videos = []
with concurrent.futures.ThreadPoolExecutor() as executor:
future_to_id = {
executor.submit(
self.post,
f'{self.host}/api/v1/app/recommend/recommendSubList',
data=json.dumps({
"condition": item['id'],
"pageNum": 1,
"pageSize": 6
}),
headers=self.headers
): item['id'] for item in data
}
for future in concurrent.futures.as_completed(future_to_id):
try:
response = future.result().json()
for video in response['data']['records']:
videos.append({
"vod_id": video['id'],
"vod_name": video['name'],
"vod_pic": video['cover']
})
except Exception as e:
print(f"Request failed for item {future_to_id[future]}: {str(e)}")
return {'list': videos}
def categoryContent(self, tid, pg, filter, extend):
payload = {
"condition": {
"classify": "",
"region": "",
"sreecnTypeEnum": "NEWEST",
"typeId": tid,
"year": ""
},
"pageNum": pg,
"pageSize": 40
}
response = self.post(f'{self.host}/api/v1/app/screen/screenMovie', data=json.dumps(payload), headers=self.headers).json()
videos = []
for i in response['data']['records']:
videos.append({
"vod_id": i['id'],
"vod_name": i['name'],
"vod_pic": i['cover'],
"vod_remarks": i['area'],
"vod_year": i['year']
})
return {'list': videos, 'page': pg}
def searchContent(self, key, quick, pg='1'):
payload = {
"condition": {
"value": key
},
"pageNum": pg,
"pageSize": 40
}
response = self.post(f'{self.host}/api/v1/app/search/searchMovie', data=json.dumps(payload), headers=self.headers).json()
videos = []
for i in response['data']['records']:
videos.append({
'vod_id': i['id'],
'vod_name': i['name'],
'vod_pic': i['cover'],
'vod_remarks': i['area'],
'vod_year': i['year'],
'vod_area': i['area'],
'vod_content': i['desc']
})
return {'list': videos, 'page': pg}
# ---------- 详情页(已集成解密) ----------
def detailContent(self, ids):
type_id = "M15" # 注意:原脚本写死为 M17,可根据需要修改
vid = ids[0]
body = {
"id": vid,
"source": 0,
"typeId": type_id,
"userId": self.userid,
"episodeId": "",
"episodeIndex": "",
"playerId": ""
}
body_json = json.dumps(body, separators=(',', ':'))
params_str = self.build_params_string(
episode_id="",
episode_index="",
vid=str(vid),
player_id="",
type_id=type_id,
user_id=str(self.userid)
)
headers, payload = self.build_encrypted_headers(body_json, params_str)
# 发送加密请求
resp_raw = self.post(f'{self.host}/api/v1/app/play/movieDetails', data=json.dumps(payload), headers=headers).json()
encrypted_data = resp_raw.get('data')
if not encrypted_data:
raise Exception("响应中 data 为空")
# 解密 data 字段
decrypted_json_str = self.rsa_decrypt(encrypted_data)
data = json.loads(decrypted_json_str)
# 后续处理与原脚本相同
currentplayerid = data['playerId']
play_urls = []
play_url = []
show = []
for i in data['episodeList']:
play_url.append(f"{i['episode']}${ids[0]}@{currentplayerid}@{i['id']}@episode")
play_urls.append('#'.join(play_url))
moviePlayerList = data['moviePlayerList']
for i2 in moviePlayerList:
if i2['id'] == currentplayerid:
show.append(i2['moviePlayerName'])
for j in moviePlayerList:
playerid = j['id']
episodeTotal = j.get('episodeTotal')
if playerid == currentplayerid or episodeTotal is None:
continue
play_url = []
for k in range(1, episodeTotal + 1):
play_url.append(f"{k}集${k}@{playerid}@{ids[0]}@virtual")
play_urls.append('#'.join(play_url))
if j['moviePlayerName'] not in show:
show.append(j['moviePlayerName'])
# 获取简介(此接口可能无需加密,保持原样)
payload_desc = {
"id": ids[0],
"typeId": type_id
}
response_desc = self.post(f'{self.host}/api/v1/app/play/movieDesc', data=json.dumps(payload_desc), headers=self.headers).json()
data2 = response_desc['data']
video = {
'vod_id': data2['id'],
'vod_name': data2['name'],
'vod_pic': data2['cover'],
'vod_content': data2['introduce'],
'vod_year': data2['year'],
'vod_area': data2['area'],
'vod_remarks': '',
'vod_score': data2['score'],
'type_name': data2['classify'],
'vod_director': data2['director'],
'vod_actor': data2['star'],
'vod_play_from': '$$$'.join(show),
'vod_play_url': '$$$'.join(play_urls)
}
return {'list': [video]}
# ---------- 播放页(已集成解密) ----------
def playerContent(self, flag, id, vipflags):
param, playerid, param2, param3 = id.split('@')
if param3 == 'virtual':
payload = {
"episodeIndex": str(int(param) - 1),
"id": int(param2),
"playerId": playerid,
"source": 0,
"typeId": "M15",
"userId": self.userid,
"episodeId": ""
}
else:
payload = {
"episodeId": param2,
"id": int(param),
"playerId": playerid,
"source": 0,
"typeId": "M15",
"userId": self.userid,
"episodeIndex": ""
}
body_json = json.dumps(payload, separators=(',', ':'))
print(body_json)
params_str = self.build_params_string(
episode_id=payload.get("episodeId", ""),
episode_index=payload.get("episodeIndex", ""),
vid=str(payload["id"]),
player_id=payload["playerId"],
type_id=payload["typeId"],
user_id=str(payload["userId"])
)
print(params_str)
headers, encrypted_payload = self.build_encrypted_headers(body_json, params_str)
print(headers)
print(encrypted_payload)
# 获取播放信息(加密响应)
resp_raw = self.post(f'{self.host}/api/v1/app/play/movieDetails', data=json.dumps(encrypted_payload), headers=headers).json()
encrypted_data = resp_raw.get('data')
if not encrypted_data:
raise Exception("响应中 data 为空")
decrypted_json_str = self.rsa_decrypt(encrypted_data)
data = json.loads(decrypted_json_str)
print(data)
parse_url = data['url']
playerid = data['playerId']
# 调用分析接口(注:analysisMovieUrl 的响应可能也是加密的,但原脚本直接取 data,这里暂不做额外解密)
analysis_body = {
"playerUrl": parse_url,
"playerId": playerid
}
analysis_json = json.dumps(analysis_body, separators=(',', ':'))
# analysisMovieUrl 接口的参数拼接?理论上也需要签名,但原脚本是 GET 方式,为了兼容,我们沿用原脚本的 GET 方式
# 原脚本使用 fetch GET 带参数,并未加密。这里也采用 GET 方式,不使用加密 headers
resp_analysis = self.fetch(f"{self.host}/api/v1/app/play/analysisMovieUrl?playerUrl={quote(parse_url,safe='')}&playerId={playerid}", headers=self.headers).json()
url = resp_analysis.get('data')
return {'jx': '0', 'parse': '0', 'url': url, 'header': {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}}
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def localProxy(self, param):
pass
+336
View File
@@ -0,0 +1,336 @@
# -*- coding: utf-8 -*-
# by @PyramidStore AutoGen
import re
import sys
sys.path.append('..')
import json
from urllib.parse import quote
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.nav_host = 'https://www.xiguadh.com'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
self.host = self._get_host()
def _get_host(self):
"""获取视频站点 URL,失败时从导航页获取"""
default_host = 'https://www.bzzdyy.com'
try:
r = self.fetch(default_host, headers=self.headers, timeout=10, verify=False)
if r.status_code == 200:
return default_host
except Exception:
pass
try:
r = self.fetch(self.nav_host, headers=self.headers, timeout=15, verify=False)
html = r.text
urls = re.findall(r'url:\s*["\']([^"\']+)["\']', html)
for url in urls:
if url.startswith('http') and 'xiguadh' not in url:
return url.rstrip('/')
except Exception:
pass
return default_host
def getName(self):
return '西瓜影院'
def isVideoFormat(self, url):
return False
def manualVideoCheck(self):
return True
def homeContent(self, filter):
try:
r = self.fetch(self.host, headers=self.headers, timeout=15, verify=False)
html = r.text
# 提取主要分类
nav_match = re.search(r'<ul class="stui-header__menu">(.*?)</ul>', html, re.DOTALL)
if nav_match:
nav_html = nav_match.group(1)
categories = re.findall(r'<li[^>]*><a href="/index.php/vod/type/id/(\d+)\.html">([^<]+)</a></li>', nav_html)
else:
categories = []
seen = set()
classes = []
for tid, name in categories:
if tid not in seen:
seen.add(tid)
classes.append({'type_name': name, 'type_id': tid})
if not classes:
raise Exception('No categories found')
# 提取首页推荐视频
videos = self._parse_vodlist(html)
except Exception:
classes = [
{'type_name': '电影', 'type_id': '20'},
{'type_name': '连续剧', 'type_id': '37'},
{'type_name': '动漫', 'type_id': '43'},
{'type_name': '综艺', 'type_id': '45'},
{'type_name': 'B站', 'type_id': '47'},
{'type_name': '人人专区', 'type_id': '60'},
]
videos = []
return {"class": classes, "list": videos}
def _parse_vodlist(self, html):
"""解析视频列表"""
items = re.findall(
r'<a class="stui-vodlist__thumb[^"]*"\s+href="([^"]+)"\s+title="([^"]*)"[^>]*data-original="([^"]*)"',
html
)
videos = []
for href, title, pic in items:
vod_id = re.search(r'/id/(\d+)\.html', href)
if vod_id:
vod_id = vod_id.group(1)
else:
continue
remark_match = re.search(r'<span class="pic-text text-right"><b>([^<]+)</b></span>',
html[html.find(href):html.find(href)+500] if href in html else '')
remark = remark_match.group(1) if remark_match else ''
if pic.startswith('/'):
pic = self.host + pic
videos.append({
'vod_id': vod_id,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': remark,
})
return videos
def homeVideoContent(self):
return ''
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg)
url = f'{self.host}/index.php/vod/type/id/{tid}/page/{pg}.html'
try:
r = self.fetch(url, headers=self.headers, timeout=15, verify=False)
html = r.text
items = re.findall(
r'<a class="stui-vodlist__thumb[^"]*"\s+href="([^"]+)"\s+title="([^"]*)"[^>]*data-original="([^"]*)"',
html
)
videos = []
for href, title, pic in items:
vod_id = re.search(r'/id/(\d+)\.html', href)
if vod_id:
vod_id = vod_id.group(1)
else:
continue
remark_match = re.search(r'<span class="pic-text text-right"><b>([^<]+)</b></span>',
html[html.find(href):html.find(href)+500] if href in html else '')
remark = remark_match.group(1) if remark_match else ''
if pic.startswith('/'):
pic = self.host + pic
videos.append({
'vod_id': vod_id,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': remark,
})
return {
"list": videos,
"page": pg,
"pagecount": 9999,
"limit": 90,
"total": len(videos),
}
except Exception as e:
return {"list": [], "page": pg, "pagecount": 1, "limit": 90, "total": 0}
def detailContent(self, ids):
try:
vod_id = ids[0] if isinstance(ids, list) else ids
url = f'{self.host}/index.php/vod/detail/id/{vod_id}.html'
r = self.fetch(url, headers=self.headers, timeout=15, verify=False)
html = r.text
title_match = re.search(r'<h1 class="title">([^<]+)</h1>', html)
title = title_match.group(1).strip() if title_match else ''
self._vod_name = title
pic_match = re.search(r'<img class="lazyload" data-original="([^"]*)"', html)
pic = pic_match.group(1) if pic_match else ''
if pic.startswith('/'):
pic = self.host + pic
info_match = re.search(r'类型:([^/]+)\s*/\s*地区:([^/]+)\s*/\s*年份:(\d+)', html)
type_name = info_match.group(1).strip() if info_match else ''
area = info_match.group(2).strip() if info_match else ''
year = info_match.group(3) if info_match else ''
remark_match = re.search(r'状态:<span[^>]*>([^<]+)</span>', html)
remark = remark_match.group(1).strip() if remark_match else ''
director_match = re.search(r'导演:(.*?)</p>', html, re.DOTALL)
director = ''
if director_match:
director = re.sub(r'<[^>]+>', '', director_match.group(1)).strip()
actor_match = re.search(r'主演:([^<]+)', html)
actor = actor_match.group(1).strip() if actor_match else ''
desc_match = re.search(r'<span class="detail-content"[^>]*>([^<]+)</span>', html)
desc = desc_match.group(1).strip() if desc_match else ''
play_from = []
play_url = []
source_tabs = re.findall(r'<li><a href="#playlist\d+"[^>]*>([^<]+)</a></li>', html)
for idx, source_name in enumerate(source_tabs):
source_id = idx + 1
episodes_match = re.search(
f'<div id="playlist{source_id}" class="tab-pane[^"]*"[^>]*>.*?<ul class="stui-content__playlist[^"]*"[^>]*>(.*?)</ul>',
html, re.DOTALL
)
if episodes_match:
episodes = re.findall(r'<a href="([^"]+)">([^<]+)</a>', episodes_match.group(1))
episode_list = []
for ep_url, ep_name in episodes:
episode_list.append(f'{ep_name}${self.host}{ep_url}')
play_from.append(source_name)
play_url.append('#'.join(episode_list))
vod_play_from = '$$$'.join(play_from) if play_from else '默认'
vod_play_url = '$$$'.join(play_url) if play_url else ''
vod = {
'vod_id': vod_id,
'vod_name': title,
'vod_pic': pic,
'vod_year': year,
'vod_area': area,
'vod_remarks': remark,
'vod_director': director,
'vod_actor': actor,
'vod_content': desc,
'vod_play_from': vod_play_from,
'vod_play_url': vod_play_url,
}
return {"list": [vod]}
except Exception as e:
return {"list": []}
def searchContent(self, key, quick, pg="1"):
pg = int(pg)
url = f'{self.host}/index.php/vod/search/wd/{quote(key)}.html'
try:
r = self.fetch(url, headers=self.headers, timeout=15, verify=False)
html = r.text
items = re.findall(
r'<a class="stui-vodlist__thumb[^"]*"\s+href="([^"]+)"\s+title="([^"]*)"[^>]*data-original="([^"]*)"',
html
)
videos = []
for href, title, pic in items:
vod_id = re.search(r'/id/(\d+)\.html', href)
if vod_id:
vod_id = vod_id.group(1)
else:
continue
remark_match = re.search(r'<span class="pic-text text-right"><b>([^<]+)</b></span>',
html[html.find(href):html.find(href)+500] if href in html else '')
remark = remark_match.group(1) if remark_match else ''
if pic.startswith('/'):
pic = self.host + pic
videos.append({
'vod_id': vod_id,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': remark,
})
return {
"list": videos,
"page": pg,
"pagecount": 9999,
"limit": 90,
"total": len(videos),
}
except Exception as e:
return {"list": [], "page": pg, "pagecount": 1, "limit": 90, "total": 0}
def _clean_vod_name(self, name):
import re
if not name:
return ''
cleaned = re.sub(r'\s*\d+\s*[集話话章部期]', '', name)
cleaned = re.sub(r'EP\s*\d+', '', cleaned, flags=re.IGNORECASE)
cleaned = re.sub(r'\d+集', '', cleaned)
cleaned = re.sub(r'更新至\d+集', '', cleaned)
cleaned = re.sub(r'\d+集全', '', cleaned)
cleaned = re.sub(r'[(].*?[)]', '', cleaned)
cleaned = re.sub(r'\s*-\s*.*$', '', cleaned)
cleaned = re.sub(r'\s+', ' ', cleaned)
cleaned = re.sub(r'^[\s\-_,.,。、]+|[\s\-_,.,。、]+$', '', cleaned)
return cleaned.strip()
def _build_danmaku_url(self, vod_name, vod_index=''):
import re
idx = 0
if vod_index:
s = str(vod_index).strip()
m = re.search(r'\s*(\d+)\s*[集話话章部期]', s)
if m:
idx = int(m.group(1))
else:
m = re.search(r'(\d+)', s)
if m:
idx = int(m.group(1))
cleaned_name = self._clean_vod_name(vod_name)
params = []
if cleaned_name:
params.append(f'vodName={quote(cleaned_name)}')
params.append(f'vodIndex={idx}')
query = '&'.join(params)
return f'http://127.0.0.1:9978/proxy?do=appdanmu&{query}'
def playerContent(self, flag, id, vipFlags):
try:
ep_name = ''
vod_index = ''
if '$' in id:
parts = id.split('$', 1)
ep_name = parts[0]
url = parts[1] if len(parts) > 1 else ''
else:
url = id if id.startswith('http') else f'{self.host}{id}'
# 从 URL 中提取集数 (nid 参数)
nid_match = re.search(r'nid/(\d+)\.html', url)
if nid_match:
vod_index = nid_match.group(1)
danmaku_url = self._build_danmaku_url(self._vod_name, vod_index)
r = self.fetch(url, headers=self.headers, timeout=15, verify=False)
html = r.text
iframe_match = re.search(r'<iframe[^>]+src="([^"]+)"', html)
if iframe_match:
iframe_url = iframe_match.group(1)
if not iframe_url.startswith('http'):
iframe_url = self.host + iframe_url
return {
"parse": 1,
"url": iframe_url,
"header": self.headers,
"danmaku": danmaku_url
}
src_match = re.search(r'(https?://[^"\'<>\s]+\.m3u8[^"\'<>\s]*)', html)
if src_match:
return {
"parse": 0,
"url": src_match.group(1),
"header": self.headers,
"danmaku": danmaku_url
}
return {
"parse": 1,
"url": url,
"header": self.headers,
"danmaku": danmaku_url
}
except Exception as e:
danmaku_url = self._build_danmaku_url(self._vod_name, '')
return {"parse": 1, "url": id, "header": {}, "danmaku": danmaku_url}
def localProxy(self, param):
return [200, {}, ""]
def destroy(self):
pass