xxxx
This commit is contained in:
319
scripts/spider.php
Normal file
319
scripts/spider.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 道长所有
|
||||
* Date: 2026/01/23
|
||||
*/
|
||||
/**
|
||||
* PHP Spider Base Class
|
||||
* 旨在模仿 JS 版 TVBox Spider 的写法,简化 PHP 源开发
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
// 屏蔽一般警告,避免污染 JSON 输出
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
abstract class BaseSpider {
|
||||
|
||||
// 默认请求头
|
||||
protected $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,image/avif,image/webp,image/apng,*/*;q=0.8',
|
||||
'Accept-Language' => 'zh-CN,zh;q=0.9',
|
||||
];
|
||||
|
||||
/**
|
||||
* 初始化方法
|
||||
* @param string $extend 扩展参数
|
||||
*/
|
||||
public function init($extend = '') {
|
||||
// 子类实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首页分类
|
||||
* @param array $filter 筛选条件
|
||||
* @return array
|
||||
*/
|
||||
public function homeContent($filter) {
|
||||
return ['class' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首页推荐视频
|
||||
* @return array
|
||||
*/
|
||||
public function homeVideoContent() {
|
||||
return ['list' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类详情
|
||||
* @param string $tid 分类ID
|
||||
* @param int $pg 页码
|
||||
* @param array $filter 筛选条件
|
||||
* @param array $extend 扩展参数
|
||||
* @return array
|
||||
*/
|
||||
public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) {
|
||||
return ['list' => [], 'page' => $pg, 'pagecount' => 1, 'limit' => 20, 'total' => 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频详情
|
||||
* @param array $ids 视频ID列表
|
||||
* @return array
|
||||
*/
|
||||
public function detailContent($ids) {
|
||||
return ['list' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索视频
|
||||
* @param string $key 关键词
|
||||
* @param bool $quick 快速搜索
|
||||
* @param int $pg 页码
|
||||
* @return array
|
||||
*/
|
||||
public function searchContent($key, $quick = false, $pg = 1) {
|
||||
return ['list' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取播放地址
|
||||
* @param string $flag 播放线路
|
||||
* @param string $id 视频播放ID
|
||||
* @param array $vipFlags VIP标识
|
||||
* @return array
|
||||
*/
|
||||
public function playContent($flag, $id, $vipFlags = []) {
|
||||
return ['parse' => 0, 'url' => '', 'header' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理请求 (可选)
|
||||
* @param array $params
|
||||
* @return mixed
|
||||
*/
|
||||
public function localProxy($params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 Action (可选)
|
||||
* @param string $action 动作名称
|
||||
* @param string $value 参数值
|
||||
* @return mixed
|
||||
*/
|
||||
public function action($action, $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// ================== 辅助方法 ==================
|
||||
|
||||
/**
|
||||
* 快速构建分页返回结果
|
||||
* @param array $list 视频列表
|
||||
* @param int $pg 当前页码
|
||||
* @param int $total 总记录数 (可选)
|
||||
* @param int $limit 每页条数 (默认 20)
|
||||
* @return array
|
||||
*/
|
||||
protected function pageResult($list, $pg, $total = 0, $limit = 20) {
|
||||
$pg = max(1, intval($pg));
|
||||
$count = count($list);
|
||||
|
||||
if ($total > 0) {
|
||||
$pagecount = ceil($total / $limit);
|
||||
} else {
|
||||
// 如果没有提供 total,尝试根据当前列表数量估算
|
||||
if ($count < $limit) {
|
||||
// 当前页数据少于限制,说明是最后一页
|
||||
$pagecount = $pg;
|
||||
$total = ($pg - 1) * $limit + $count;
|
||||
} else {
|
||||
// 还有下一页,设置一个较大的页数
|
||||
$pagecount = 9999;
|
||||
$total = 99999;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'list' => $list,
|
||||
'page' => $pg,
|
||||
'pagecount' => intval($pagecount),
|
||||
'limit' => intval($limit),
|
||||
'total' => intval($total)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装 HTTP 请求
|
||||
* @param string $url 请求地址
|
||||
* @param array $options CURL 选项
|
||||
* @param array $headers 请求头
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function fetch($url, $options = [], $headers = []) {
|
||||
$ch = curl_init();
|
||||
|
||||
// 1. 解析自定义 header 为关联数组
|
||||
$customHeaders = [];
|
||||
foreach ($headers as $k => $v) {
|
||||
if (is_numeric($k)) {
|
||||
// 处理 "Key: Value" 格式
|
||||
$parts = explode(':', $v, 2);
|
||||
if (count($parts) === 2) {
|
||||
$key = trim($parts[0]);
|
||||
$value = trim($parts[1]);
|
||||
$customHeaders[$key] = $value;
|
||||
}
|
||||
} else {
|
||||
$customHeaders[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 合并请求头 (自定义覆盖默认)
|
||||
$finalHeadersMap = array_merge($this->headers, $customHeaders);
|
||||
|
||||
// 3. 转换回 CURL 所需的索引数组
|
||||
$mergedHeaders = [];
|
||||
foreach ($finalHeadersMap as $k => $v) {
|
||||
$mergedHeaders[] = "$k: $v";
|
||||
}
|
||||
|
||||
$defaultOptions = [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_ENCODING => '', // 支持 GZIP 自动解压
|
||||
CURLOPT_HTTPHEADER => $mergedHeaders,
|
||||
];
|
||||
|
||||
// 处理 POST 数据
|
||||
if (isset($options['body'])) {
|
||||
$defaultOptions[CURLOPT_POST] = true;
|
||||
$defaultOptions[CURLOPT_POSTFIELDS] = $options['body'];
|
||||
unset($options['body']);
|
||||
}
|
||||
|
||||
// 处理 Cookie
|
||||
if (isset($options['cookie'])) {
|
||||
$defaultOptions[CURLOPT_COOKIE] = $options['cookie'];
|
||||
unset($options['cookie']);
|
||||
}
|
||||
|
||||
// 合并用户自定义选项
|
||||
foreach ($options as $k => $v) {
|
||||
$defaultOptions[$k] = $v;
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, $defaultOptions);
|
||||
$result = curl_exec($ch);
|
||||
|
||||
if (is_resource($ch)) {
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动运行,处理路由
|
||||
*/
|
||||
public function run() {
|
||||
$ac = $_GET['ac'] ?? '';
|
||||
$t = $_GET['t'] ?? '';
|
||||
$pg = $_GET['pg'] ?? '1';
|
||||
$wd = $_GET['wd'] ?? '';
|
||||
$ids = $_GET['ids'] ?? '';
|
||||
$play = $_GET['play'] ?? ''; // 某些源使用 play 参数传递播放ID
|
||||
$flag = $_GET['flag'] ?? ''; // 播放线路
|
||||
$filter = isset($_GET['filter']) && $_GET['filter'] === 'true'; // 是否过滤
|
||||
$extend = $_GET['ext'] ?? ''; // 扩展参数
|
||||
if (!empty($extend) && is_string($extend)) {
|
||||
$decoded = json_decode(base64_decode($extend), true);
|
||||
if (is_array($decoded)) {
|
||||
$extend = $decoded;
|
||||
}
|
||||
}
|
||||
$action = $_GET['action'] ?? ''; // Action 动作
|
||||
$value = $_GET['value'] ?? ''; // Action 参数
|
||||
|
||||
$this->init($extend);
|
||||
|
||||
try {
|
||||
// 0. Action (优先处理)
|
||||
if ($ac === 'action') {
|
||||
echo json_encode($this->action($action, $value), JSON_UNESCAPED_UNICODE);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 播放 (Play)
|
||||
// 优先检测 play 参数或 ac=play
|
||||
if ($ac === 'play' || !empty($play)) {
|
||||
$playId = !empty($play) ? $play : ($_GET['id'] ?? '');
|
||||
echo json_encode($this->playContent($flag, $playId), JSON_UNESCAPED_UNICODE);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 搜索 (Search)
|
||||
// 有 wd 则是搜索
|
||||
if (!empty($wd)) {
|
||||
echo json_encode($this->searchContent($wd, false, $pg), JSON_UNESCAPED_UNICODE);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 详情 (Detail)
|
||||
// 有 ids 且 ac 不为空
|
||||
if (!empty($ids) && !empty($ac)) {
|
||||
// ids 可能是逗号分隔的字符串
|
||||
$idList = explode(',', $ids);
|
||||
echo json_encode($this->detailContent($idList), JSON_UNESCAPED_UNICODE);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 分类 (Category)
|
||||
// 有 t 且 ac 不为空
|
||||
if ($t !== '' && !empty($ac)) {
|
||||
// 处理 filter
|
||||
$filterData = []; // 暂未实现复杂 filter 解析,可根据需要扩展
|
||||
echo json_encode($this->categoryContent($t, $pg, $filterData, $extend), JSON_UNESCAPED_UNICODE);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 首页 (默认)
|
||||
// 通常返回 {class: [...], list: [...]}
|
||||
// 可以分别调用 homeContent 和 homeVideoContent 合并
|
||||
$homeData = $this->homeContent($filter);
|
||||
$videoData = $this->homeVideoContent();
|
||||
|
||||
$result = [
|
||||
'class' => $homeData['class'] ?? [],
|
||||
];
|
||||
|
||||
// 如果 homeContent 只有 class,合并 homeVideoContent 的 list
|
||||
if (isset($videoData['list'])) {
|
||||
$result['list'] = $videoData['list'];
|
||||
}
|
||||
// 如果 homeContent 也有 list,优先使用 homeContent 的 list (视具体逻辑而定,这里简单的合并)
|
||||
if (isset($homeData['list']) && !empty($homeData['list'])) {
|
||||
$result['list'] = $homeData['list'];
|
||||
}
|
||||
// 兼容:如果 homeContent 返回了 filters
|
||||
if (isset($homeData['filters'])) {
|
||||
$result['filters'] = $homeData['filters'];
|
||||
}
|
||||
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['code' => 500, 'msg' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
} catch (Throwable $e) {
|
||||
echo json_encode(['code' => 500, 'msg' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user