增加二级分类及筛选功能

This commit is contained in:
Saul Hetherman
2024-07-16 21:31:36 +08:00
parent f86b055c65
commit 06149fb48b
4 changed files with 499 additions and 1 deletions

View File

@@ -19,10 +19,50 @@
```
# 脚本运行说明
1. 执行每个方法都会为 `webSite` 进行赋值
2. 流程图
```mermaid
graph TD
A[开始] --> A1[uz 调用 getClassList 获取一级分类] -->|返回数据 rep: RepVideoClassList| B[判断 rep.data 列表内 VideoClass 的 hasSubclass 是否为 true]
B -->|是,存在二级分类或者筛选列表| C[调用 getSubclassList 获取二级分类或筛选列表]
B -->|否,不存在二级分类| D[调用 getVideoList 获取视频列表]
C --> C1[调用 getSubclassVideoList 获取二级分类视频列表或者筛选视频列表] -->|点击单个视频| E
E[调用 getVideoDetail 获取视频详情]
D -->|点击单个视频| E
E -->|点击某一集| F[调用 getVideoPlayUrl 获取播放链接]
F --> G[结束]
S[搜索] -->S1[调用 searchVideo 返回视频列表] -->|点击单个视频| E
```
# 修改记录
### v1.4.00
1. 增加二级分类和筛选列表功能
### v1.3.00
1. 去掉 `cat.js`, 更改为 `cheerio` `Crypto` `Encrypt` `parse`
2. `VideoDetail` 新增 `quarkUrl` 支持夸克网盘
3. `RepVideoPlayUrl` 新增 `headers` 支持设置播放 `header`
```
```

View File

@@ -1,3 +1,30 @@
/**
* 筛选标签
*/
class FilterLabel {
constructor() {
// 筛选名称
this.name = "";
// 标识参数
this.id;
}
}
/**
* 筛选标题
*/
class FilterTitle {
constructor() {
// 筛选标题
this.name = "";
/**
* 筛选标签列表
* @type {FilterLabel[]}
*/
this.list = [];
}
}
/**
* 视频分类
*/
@@ -7,6 +34,30 @@ class VideoClass {
this.type_id = "";
// 分类名称
this.type_name = "";
/**
* 是否存在 筛选列表、子分类。 存在会调用 getSubclassList
*/
this.hasSubclass = false;
}
}
/**
* 视频二级分类,二级分类可以是 分类,也可以是筛选,都有值优先取筛选
*/
class VideoSubclass {
constructor() {
/**
* 子分类
* @type {VideoClass[]}
*/
this.class = [];
/**
* 筛选列表
* 请求二级分类列表 getSubclassList 时返回该数据或者 data
* @type {FilterTitle[]}
*/
this.filter = [];
}
}
@@ -66,6 +117,20 @@ class RepVideoClassList {
}
}
/**
* 返回二级分类列表(包括筛选列表)
*/
class RepVideoSubclassList {
constructor() {
/**
* 二级分类数据
* @type {VideoSubclass}
*/
this.data = new VideoSubclass();
this.error = "";
}
}
/**
* 返回视频列表
*/
@@ -74,7 +139,7 @@ class RepVideoList {
/**
* @type {VideoDetail[]}
*/
this.data = null;
this.data = [];
this.error = "";
this.total = 0;
}
@@ -120,10 +185,36 @@ class UZArgs {
this.searchWord = "";
}
}
/**
* getSubclassVideoList 方法传入的参数
*/
class UZSubclassVideoListArgs extends UZArgs {
constructor() {
/**
* 主分类ID 即脚本返回的 @type {RepVideoClassList}.data[0].type_id
*/
this.mainClassId = "";
/**
* 二级分类ID 即脚本返回的 @type {RepVideoSubclassList}.data.class.type_id
*/
this.subclassId = "";
/**
* 筛选标签,按返回的顺序传入 即脚本返回的 @type {RepVideoSubclassList}.data.filter.
* @type {FilterLabel[]}
*/
this.filter = [];
}
}
/**
* 脚本基类
*/
class WebApiBase {
// 网站主页
webSite = "";
/**
* 异步获取分类列表的方法。
* @param {UZArgs} args
@@ -133,6 +224,15 @@ class WebApiBase {
return JSON.stringify(new RepVideoClassList());
}
/**
* 获取二级分类列表筛选列表的方法。
* @param {UZArgs} args
* @returns {@Promise<JSON.stringify(new RepVideoSubclassList())>}
*/
async getSubclassList(args) {
return JSON.stringify(new RepVideoSubclassList());
}
/**
* 获取分类视频列表
* @param {UZArgs} args
@@ -141,6 +241,15 @@ class WebApiBase {
async getVideoList(args) {
return JSON.stringify(new RepVideoList());
}
/**
* 获取二级分类视频列表 或 筛选视频列表
* @param {UZSubclassVideoListArgs} args
* @returns {@Promise<JSON.stringify(new RepVideoList())>}
*/
async getSubclassVideoList(args) {
return JSON.stringify(new RepVideoList());
}
/**
* 获取视频详情

342
js/spider/keke20240712.js Normal file
View File

@@ -0,0 +1,342 @@
// ignore
import { WebApiBase, VideoClass } from "../core/uzCode.js";
import { parse } from "node-html-parser";
// ignore
// 类名要特殊
class Keke20240712 extends WebApiBase {
webSite = "https://www.keke12.com:51111";
/**
* 异步获取分类列表的方法。
* @param {UZArgs} args
* @returns {Promise<RepVideoClassList>}
*/
async getClassList(args) {
let webUrl = args.url;
var backData = new RepVideoClassList();
backData.data = [];
try {
const pro = await req(webUrl);
backData.error = pro.error;
let proData = pro.data;
if (proData) {
var document = parse(proData);
var ulList = document.querySelectorAll("div.main > ul") ?? [];
if (ulList.length >= 1) {
var li = ulList[1].querySelectorAll("li") ?? [];
for (let i = 0; i < li.length; i++) {
const element = li[i];
const title = element.querySelector(".menu-item-label").text;
const path = element.querySelector("a").attributes["href"];
const id = UZUtils.getStrByRegexDefault(/\/(\d+)\.html/, path);
var videoClass = new VideoClass();
videoClass.hasSubclass = true;
videoClass.type_id = id;
videoClass.type_name = title;
backData.data.push(videoClass);
}
}
}
} catch (error) {
backData.error = "获取分类失败~";
}
return JSON.stringify(backData);
}
async getSubclassList(args) {
var backData = new RepVideoSubclassList();
backData.data = new VideoSubclass();
const id = args.url;
try {
var url =
UZUtils.removeTrailingSlash(this.webSite) +
"/show/" +
id +
"------1.html";
const pro = await req(url);
backData.error = pro.error;
let proData = pro.data;
if (proData) {
var document = parse(proData);
var filterTitleList = document.querySelectorAll("div.filter-row") ?? [];
for (let i = 0; i < filterTitleList.length; i++) {
const element = filterTitleList[i];
const title = element.querySelector(".filter-row-side > strong").text;
const items = element.querySelectorAll(".filter-item") ?? [];
// 2-惊悚-中国香港-英语-2022-1-1
// 分类-类型-地区-语言-年代-排序-页码
var filterTitle = new FilterTitle();
filterTitle.name = title.replace(":", "");
filterTitle.list = [];
for (let j = 0; j < items.length; j++) {
const item = items[j];
const name = item.text;
const path = item.attributes["href"] ?? "";
const regex = /\/show\/(.*?)\.html/;
const match = path.match(regex);
const parsStr = match ? match[1] : null;
if (parsStr) {
const parList = parsStr.split("-");
const id = parList[i + 1];
var filterLab = new FilterLabel();
filterLab.name = name;
filterLab.id = id;
filterTitle.list.push(filterLab);
}
}
backData.data.filter.push(filterTitle);
}
if (id === 6 || id === "6") {
// 短剧
if (backData.data.filter.length > 0) {
const list = backData.data.filter[0].list;
var classList = [];
for (let index = 0; index < list.length; index++) {
const element = list[index];
var subclass = new VideoClass();
subclass.type_id = element.id;
subclass.type_name = element.name;
classList.push(subclass);
}
backData.data.filter = [];
backData.data.class = classList;
}
}
}
} catch (error) {
backData.error = "获取分类失败~ " + error;
}
return JSON.stringify(backData);
}
/**
* 获取二级分类视频列表 或 筛选视频列表
* @param {UZSubclassVideoListArgs} args
* @returns {@Promise<JSON.stringify(new RepVideoList())>}
*/
async getSubclassVideoList(args) {
var backData = new RepVideoList();
backData.data = [];
try {
var pList = [args.mainClassId];
if (args.filter.length > 0) {
// 筛选
for (let index = 0; index < args.filter.length; index++) {
const element = args.filter[index];
pList.push(element.id);
}
} else {
pList.push(args.subclassId);
for (let index = 0; index < 4; index++) {
pList.push("");
}
}
pList.push(args.page);
var path = pList.join("-");
const url =
UZUtils.removeTrailingSlash(this.webSite) + "/show/" + path + ".html";
const pro = await req(url);
backData.error = pro.error;
let proData = pro.data;
if (proData) {
var document = parse(proData);
var allVideo = document.querySelectorAll("div.module-item") ?? [];
var videos = [];
for (let index = 0; index < allVideo.length; index++) {
const element = allVideo[index];
var vodUrl = element.querySelector("a")?.attributes["href"] ?? "";
var avaImg =
document.querySelector("img.user-avatar-img")?.attributes["src"] ??
"";
var path =
element.querySelector("img")?.attributes["data-original"] ?? "";
var vodPic = UZUtils.getHostFromURL(avaImg) + path;
var vodName = element.querySelector("img")?.attributes["title"] ?? "";
var vod_remarks =
element.querySelector("div.v-item-bottom > span")?.text ??
element.querySelector("div.v-item-top-left > span")?.text;
if (vodUrl && vodPic && vodName) {
var video = new VideoDetail();
video.vod_id = vodUrl;
video.vod_pic = vodPic;
video.vod_name = vodName;
video.vod_remarks = vod_remarks;
videos.push(video);
}
}
backData.data = videos;
}
} catch (error) {
backData.error = "获取视频列表失败~ " + error;
}
return JSON.stringify(backData);
}
/**
* 获取分类视频列表
* @param {UZArgs} args
* @returns {Promise<RepVideoList>}
*/
async getVideoList(args) {
var backData = new RepVideoList();
return JSON.stringify(backData);
}
/**
* 获取视频详情
* @param {UZArgs} args
* @returns {Promise<RepVideoDetail>}
*/
async getVideoDetail(args) {
var backData = new RepVideoDetail();
try {
var webUrl = UZUtils.removeTrailingSlash(this.webSite) + args.url;
let pro = await req(webUrl, null);
backData.error = pro.error;
let proData = pro.data;
if (proData) {
var document = parse(proData);
var detList = document.querySelectorAll("div.detail-info-row") ?? [];
var vod_year = "";
var vod_director = "";
var vod_actor = "";
for (let index = 0; index < detList.length; index++) {
const element = detList[index];
var title =
element.querySelector("div.detail-info-row-side")?.text ?? "";
var contentE = element.querySelector("div.detail-info-row-main");
if (title.includes("首映")) {
vod_year = contentE.text ?? "";
} else if (title.includes("导演")) {
vod_director = contentE?.querySelector("a")?.text ?? "";
} else if (title.includes("演员")) {
contentE.querySelectorAll("a").forEach((element) => {
vod_actor += element.text + "、";
});
}
}
var vod_content =
document.querySelector("div.detail-desc > p").text?.trim() ?? "";
var fromListE =
document.querySelectorAll("span.source-item-label") ?? [];
var fromListStr = [];
for (let index = 0; index < fromListE.length; index++) {
fromListStr.push(fromListE[index].text);
}
var vod_play_from = fromListStr.join("$$$");
var playListE = document.querySelectorAll("div.episode-list") ?? [];
var vod_play_url = "";
for (let index = 0; index < playListE.length; index++) {
const epList = playListE[index].querySelectorAll("a") ?? [];
for (let index = 0; index < epList.length; index++) {
const element = epList[index];
vod_play_url += element.text;
vod_play_url += "$";
vod_play_url += element.attributes["href"];
vod_play_url += "#";
}
vod_play_url += "$$$";
}
let detModel = new VideoDetail();
detModel.vod_year = vod_year;
detModel.vod_director = vod_director;
detModel.vod_actor = vod_actor;
detModel.vod_content = vod_content;
detModel.vod_play_url = vod_play_url;
detModel.vod_play_from = vod_play_from;
detModel.vod_id = args.url;
backData.data = detModel;
}
} catch (error) {
backData.error = "获取视频详情失败 ~ " + error;
}
return JSON.stringify(backData);
}
/**
* 获取视频的播放地址
* @param {UZArgs} args
* @returns {Promise<RepVideoPlayUrl>}
*/
async getVideoPlayUrl(args) {
var backData = new RepVideoPlayUrl();
backData.error = "获取播放链接失败~";
try {
const pro = await req(
UZUtils.removeTrailingSlash(this.webSite) + args.url
);
backData.error = pro.error;
let proData = pro.data;
const regex = /playSource\s*=\s*{[^}]*src:\s*"([^"]*)"/;
const match = proData.match(regex);
const result = match ? match[1] : "";
backData.data = result;
} catch (error) {
backData.error = "获取视频播放地址失败~ " + error;
}
return JSON.stringify(backData);
}
/**
* 搜索视频
* @param {UZArgs} args
* @returns {Promise<RepVideoList>}
*/
async searchVideo(args) {
var backData = new RepVideoList();
backData.data = [];
try {
const rep = await req(
UZUtils.removeTrailingSlash(this.webSite) +
"/search?k=" +
args.searchWord +
"&page=" +
args.page
);
backData.error = rep.error;
let repData = rep.data;
var document = parse(repData);
var count = document.querySelectorAll("span.highlight-text") ?? [];
if (count.length > 1) {
backData.total = count[1].text.trim();
}
const allVideoE = document.querySelectorAll("a.search-result-item") ?? [];
for (let index = 0; index < allVideoE.length; index++) {
const element = allVideoE[index];
var video = new VideoDetail();
video.vod_id = element.attributes["href"];
var avaImg =
document.querySelector("img.user-avatar-img")?.attributes["src"] ??
"";
var path =
element.querySelector("img")?.attributes["data-original"] ?? "";
var vodPic = UZUtils.getHostFromURL(avaImg) + path;
video.vod_pic = vodPic;
video.vod_name = element.querySelector("div.title")?.text ?? "";
video.vod_remarks =
element.querySelector("div.search-result-item-header > div")?.text ??
"";
backData.data.push(video);
}
} catch (error) {
backData.error = "搜索视频失败 ~ " + error;
}
return JSON.stringify(backData);
}
}
// json 中 instance 的值,这个名称一定要特殊
var keke20240712 = new Keke20240712();

View File

@@ -5,5 +5,12 @@
"instance": "changZhang20240614",
"webSite": "https://www.czzy77.com",
"remark": "部分视频不会解密,不支持搜索"
},
{
"name": "可可影视",
"api": "https://mirror.ghproxy.com/https://raw.githubusercontent.com/YYDS678/uzVideo/main/js/spider/keke20240712.js",
"instance": "keke20240712",
"webSite": "https://www.keke12.com:51111",
"remark": ""
}
]