Skip to content

Commit 4c4cf0d

Browse files
author
Taois
committed
feat: 完善php标准
1 parent f9011da commit 4c4cf0d

File tree

9 files changed

+136
-55
lines changed

9 files changed

+136
-55
lines changed

spider/php/PHP写源(道长).pdf

75.9 KB
Binary file not shown.

spider/php/config.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848

4949
$filename = pathinfo($file, PATHINFO_FILENAME);
5050

51-
$sites[] = [
51+
$site = [
5252
"key" => "php_" . $filename,
5353
"name" => $filename . "(PHP)",
5454
"type" => 4,
@@ -57,6 +57,14 @@
5757
"quickSearch" => 1,
5858
"changeable" => 0
5959
];
60+
61+
if (strpos($filename, '[书]') !== false) {
62+
$site['类型'] = '小说';
63+
} elseif (strpos($filename, '[画]') !== false) {
64+
$site['类型'] = '漫画';
65+
}
66+
67+
$sites[] = $site;
6068
}
6169

6270
// ==================

spider/php/readme.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,55 @@ $link = $this->pd($html, 'a.next&&href', 'https://m.example.com/list/');
251251

252252
---
253253

254+
## 5. Flutter 环境适配与 PHP 8.5+ 兼容性
255+
256+
在将 PHP 源部署到 Flutter 环境(如 TVBox 及其变种)并使用高版本 PHP (如 8.5.1) 时,可能会遇到类型严格性导致的兼容问题。
257+
258+
### 5.1 核心报错:`type 'String' is not a subtype of type 'int' of 'index'`
259+
**现象**
260+
本地 `test_runner.php` 测试一切正常,但在 Flutter 端运行时报错,提示 String 类型无法作为 List 的索引。
261+
262+
**原因**
263+
PHP 脚本执行结束后**没有输出任何内容**
264+
- `test_runner.php` 是手动实例化类并调用方法,所以能拿到结果。
265+
- Flutter 端通过 CLI 调用 PHP 脚本,如果脚本末尾没有主动调用运行逻辑,输出为空字符串。
266+
- 适配层收到空字符串后,可能默认处理为 `[]` (空 List)。后续逻辑尝试以 Map 方式(如 `['class']`)访问这个 List 时,就会触发 Dart 的类型错误。
267+
268+
**解决方案**
269+
确保每个源文件末尾都包含自动运行指令:
270+
```php
271+
// 必须在文件末尾加入此行
272+
(new Spider())->run();
273+
```
274+
275+
### 5.2 严格类型处理 (JSON 空对象)
276+
**现象**
277+
PHP 的空数组 `[]``json_encode` 时默认为 `[]` (List)。如果在 PHP 8.5+ 环境下,客户端期望的是 Map `{}` (Object),可能会导致解析错误或类型不匹配。
278+
279+
**解决方案**
280+
对于明确应该是对象的字段(如 `header`, `filters`, `ext`),如果为空,必须强制转换为 Object。
281+
```php
282+
// 错误 (输出 [])
283+
'header' => []
284+
285+
// 正确 (输出 {})
286+
'header' => (object)[]
287+
```
288+
289+
### 5.3 HTTPS 与 SSL 证书验证
290+
**现象**
291+
在某些 Flutter 环境或 Android 设备上,cURL 请求 HTTPS 站点失败,无返回或报错。这是 because 系统证书库可能不完整或 curl 配置过严。
292+
293+
**解决方案**
294+
显式关闭 SSL 证书校验。`BaseSpider``fetch` 方法已默认处理,但在重写 `fetch` 或使用原生 cURL 时需注意:
295+
```php
296+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
297+
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
298+
```
299+
300+
### 5.4 健壮性建议
301+
1. **JSON 解析容错**`json_decode($str ?: '{}', true)`,避免对空字符串解析报错。
302+
2. **空 ID 容错**:在 `detailContent``playerContent` 中,检查 ID 是否为空,避免向 API 发送非法请求导致崩溃。
303+
304+
---
254305
*本文档更新于 2026/01/25,基于 Trae IDE 协作环境。*

spider/php/test_runner.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@
5252
echo " - 分类示例: " . implode(', ', $classNames) . (count($classes) > 5 ? ' ...' : '') . "\n";
5353

5454
if (!empty($filters)) {
55-
echo " - 包含筛选配置 (Filters): " . count($filters) . "\n";
55+
$filterCount = is_object($filters) ? count(get_object_vars($filters)) : count($filters);
56+
echo " - 包含筛选配置 (Filters): " . $filterCount . "\n";
5657
}
5758
} else {
5859
echo " ⚠️ 警告: 未获取到分类列表 (class 为空)\n";

spider/php/七猫小说 ᵈᶻ[书].php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public function homeContent($filter) {
9595

9696
return [
9797
'class' => $classes,
98-
'filters' => $filters
98+
'filters' => (object)$filters
9999
];
100100
}
101101

@@ -290,14 +290,14 @@ public function playerContent($flag, $id, $vipFlags = []) {
290290
return [
291291
'parse' => 0,
292292
'url' => 'novel://' . json_encode(['title' => "Error: $code - $msg ($preview)", 'content' => ''], JSON_UNESCAPED_UNICODE),
293-
'header' => []
293+
'header' => (object)[]
294294
];
295295
}
296296

297297
return [
298298
'parse' => 0,
299299
'url' => 'novel://' . json_encode(['title' => $title, 'content' => $content], JSON_UNESCAPED_UNICODE),
300-
'header' => []
300+
'header' => (object)[]
301301
];
302302
}
303303

@@ -352,3 +352,6 @@ private function decodeContent($base64Response) {
352352
return trim($decrypted);
353353
}
354354
}
355+
356+
// 运行爬虫
357+
(new Spider())->run();

spider/php/人人影视 ᵈᶻ.php

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ protected function getHeaders($isJson = true) {
2222
public function homeContent($filter) {
2323
$classes = [
2424
['type_id' => '1', 'type_name' => '电影'],
25-
['type_id' => '2', 'type_name' => '逕オ隗�?],
25+
['type_id' => '2', 'type_name' => '电视剧'],
2626
['type_id' => '3', 'type_name' => '综艺'],
2727
['type_id' => '5', 'type_name' => '动漫'],
28-
['type_id' => '4', 'type_name' => '郤ェ蠖慕?],
28+
['type_id' => '4', 'type_name' => '纪录片'],
2929
['type_id' => '6', 'type_name' => '短剧'],
3030
['type_id' => '7', 'type_name' => '特别节目'],
3131
['type_id' => '8', 'type_name' => '少儿内容']
@@ -36,7 +36,8 @@ public function homeContent($filter) {
3636

3737
return [
3838
'class' => $classes,
39-
'list' => $data['list'] ?? []
39+
'list' => $data['list'] ?? [],
40+
'filters' => (object)[]
4041
];
4142
}
4243

@@ -58,21 +59,26 @@ public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) {
5859
CURLOPT_POST => 1,
5960
CURLOPT_POSTFIELDS => json_encode($payload),
6061
CURLOPT_HTTPHEADER => $this->getHeaders(),
61-
CURLOPT_SSL_VERIFYPEER => false
62+
CURLOPT_SSL_VERIFYPEER => false,
63+
CURLOPT_SSL_VERIFYHOST => false // 补全SSL校验关闭,避免HTTPS请求失败
6264
]);
6365

64-
$jsonObj = json_decode($jsonStr, true);
66+
$jsonObj = json_decode($jsonStr ?: '{}', true);
6567
$list = [];
6668

67-
if (isset($jsonObj['data']['list'])) {
69+
if (isset($jsonObj['data']['list']) && is_array($jsonObj['data']['list'])) {
6870
$list = $this->arr2vods($jsonObj['data']['list']);
6971
}
72+
// 补全total参数,适配分页逻辑
73+
$total = isset($jsonObj['data']['pagecount']) ? $jsonObj['data']['pagecount'] * 60 : 0;
7074

71-
return $this->pageResult($list, $pg, $jsonObj['data']['pagecount'] ?? 0);
75+
return $this->pageResult($list, $pg, $total, 60);
7276
}
7377

7478
public function detailContent($ids) {
75-
$id = is_array($ids) ? $ids[0] : $ids;
79+
$id = is_array($ids) ? ($ids[0] ?? '') : $ids;
80+
if (empty($id)) return ['list' => []]; // 空ID容错
81+
7682
$apiUrl = $this->HOST . '/api.php/player/details/';
7783

7884
$payload = ['id' => (string)$id];
@@ -81,35 +87,36 @@ public function detailContent($ids) {
8187
CURLOPT_POST => 1,
8288
CURLOPT_POSTFIELDS => json_encode($payload),
8389
CURLOPT_HTTPHEADER => $this->getHeaders(),
84-
CURLOPT_SSL_VERIFYPEER => false
90+
CURLOPT_SSL_VERIFYPEER => false,
91+
CURLOPT_SSL_VERIFYHOST => false
8592
]);
8693

87-
$jsonObj = json_decode($jsonStr, true);
94+
$jsonObj = json_decode($jsonStr ?: '{}', true);
8895
$vod = [];
8996

90-
if (isset($jsonObj['detailData'])) {
97+
if (isset($jsonObj['detailData']) && is_array($jsonObj['detailData'])) {
9198
$d = $jsonObj['detailData'];
9299
$vod = [
93-
'vod_id' => $d['vod_id'],
94-
'vod_name' => $d['vod_name'],
95-
'vod_pic' => $d['vod_pic'],
96-
'vod_remarks' => $d['vod_remarks'],
97-
'vod_year' => $d['vod_year'],
98-
'vod_area' => $d['vod_area'],
99-
'vod_actor' => $d['vod_actor'],
100-
'vod_director' => $d['vod_director'],
101-
'vod_content' => $d['vod_content'],
102-
'vod_play_from' => $d['vod_play_from'],
103-
'vod_play_url' => $d['vod_play_url'],
104-
'type_name' => $d['vod_class']
100+
'vod_id' => $d['vod_id'] ?? '',
101+
'vod_name' => $d['vod_name'] ?? '未知影片',
102+
'vod_pic' => $d['vod_pic'] ?? '',
103+
'vod_remarks' => $d['vod_remarks'] ?? '',
104+
'vod_year' => $d['vod_year'] ?? '',
105+
'vod_area' => $d['vod_area'] ?? '',
106+
'vod_actor' => $d['vod_actor'] ?? '',
107+
'vod_director' => $d['vod_director'] ?? '',
108+
'vod_content' => $d['vod_content'] ?? '暂无影片介绍',
109+
'vod_play_from' => $d['vod_play_from'] ?? '',
110+
'vod_play_url' => $d['vod_play_url'] ?? '',
111+
'type_name' => $d['vod_class'] ?? ''
105112
];
106113
}
107114

108115
return ['list' => [$vod]];
109116
}
110117

111118
public function searchContent($key, $quick = false, $pg = 1) {
112-
if ($pg > 1) return $this->pageResult([], $pg, 0);
119+
if (empty($key) || $pg > 1) return $this->pageResult([], $pg, 0);
113120

114121
$apiUrl = $this->HOST . '/api.php/search/syntheticalSearch/';
115122
$payload = ['keyword' => $key];
@@ -118,23 +125,24 @@ public function searchContent($key, $quick = false, $pg = 1) {
118125
CURLOPT_POST => 1,
119126
CURLOPT_POSTFIELDS => json_encode($payload),
120127
CURLOPT_HTTPHEADER => $this->getHeaders(),
121-
CURLOPT_SSL_VERIFYPEER => false
128+
CURLOPT_SSL_VERIFYPEER => false,
129+
CURLOPT_SSL_VERIFYHOST => false
122130
]);
123131

124-
$jsonObj = json_decode($jsonStr, true);
132+
$jsonObj = json_decode($jsonStr ?: '{}', true);
125133
$videos = [];
126134

127-
if (isset($jsonObj['data'])) {
135+
if (isset($jsonObj['data']) && is_array($jsonObj['data'])) {
128136
$data = $jsonObj['data'];
129-
if (!empty($data['chasingFanCorrelation'])) {
137+
if (!empty($data['chasingFanCorrelation']) && is_array($data['chasingFanCorrelation'])) {
130138
$videos = array_merge($videos, $this->arr2vods($data['chasingFanCorrelation']));
131139
}
132-
if (!empty($data['moviesCorrelation'])) {
140+
if (!empty($data['moviesCorrelation']) && is_array($data['moviesCorrelation'])) {
133141
$videos = array_merge($videos, $this->arr2vods($data['moviesCorrelation']));
134142
}
135143
}
136144

137-
return $this->pageResult($videos, $pg, 1);
145+
return $this->pageResult($videos, $pg, count($videos));
138146
}
139147

140148
public function playerContent($flag, $id, $vipFlags = []) {
@@ -157,7 +165,8 @@ public function playerContent($flag, $id, $vipFlags = []) {
157165
$url = $jsonObj['data']['url'];
158166
}
159167

160-
// 蛹ケ驟咲ャャ荳画婿螟ァ遶吝シ蜷ッ隗」譫? if (preg_match('/(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com/', $url)) {
168+
// 匹配第三方大站开启解析
169+
if (preg_match('/(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com/', $url)) {
161170
$jx = 1;
162171
}
163172

@@ -175,15 +184,16 @@ public function playerContent($flag, $id, $vipFlags = []) {
175184
private function arr2vods($arr) {
176185
$videos = [];
177186
foreach ($arr as $i) {
187+
// 修复符号错误
178188
$remarks = ($i['vod_serial'] == '1')
179-
? $i['vod_serial'] . '髮?
180-
: '隸���? . ($i['vod_score'] ?? $i['vod_douban_score'] ?? '0');
189+
? $i['vod_serial'] . ''
190+
: '评分' . ($i['vod_score'] ?? $i['vod_douban_score'] ?? '0');
181191

182192
$videos[] = [
183-
'vod_id' => $i['vod_id'],
184-
'vod_name' => $i['vod_name'],
185-
'vod_pic' => $i['vod_pic'],
186-
'vod_remarks' => $remarks
193+
'vod_id' => $i['vod_id'] ?? '',
194+
'vod_name' => $i['vod_name'] ?? '',
195+
'vod_pic' => $i['vod_pic'] ?? '',
196+
'vod_remarks' => $remarks ?? ''
187197
];
188198
}
189199
return $videos;

spider/php/星星短剧 ᵈᶻ.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class Spider extends BaseSpider {
55
private $HOST = 'http://read.api.duodutek.com';
66
private $UA = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36';
77

8-
// 固定�API 参数
8+
// 固定的 API 参数
99
private $COMMON_PARAMS = [
1010
"productId" => "2a8c14d1-72e7-498b-af23-381028eb47c0",
1111
"vestId" => "2be070e0-c824-4d0e-a67a-8f688890cadb",
@@ -31,12 +31,14 @@ public function homeContent($filter) {
3131
["type_id" => "1291", "type_name" => "古代"]
3232
];
3333

34-
// 首页推荐:取第一个分类的前几个视� $list = $this->categoryContent('1287', 1)['list'];
34+
// 首页推荐:取第一个分类的前几个视频
35+
$list = $this->categoryContent('1287', 1)['list'];
3536
$list = array_slice($list, 0, 12);
3637

3738
return [
3839
'class' => $classes,
39-
'list' => $list
40+
'list' => $list,
41+
'filters' => (object)[]
4042
];
4143
}
4244

@@ -57,11 +59,11 @@ public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) {
5759
if ($jsonObj && isset($jsonObj['data']['datalist'])) {
5860
foreach ($jsonObj['data']['datalist'] as $vod) {
5961
$list[] = [
60-
// 仿照�Python:id@@name@@introduction 存储
62+
// 仿照原 Python:id@@name@@introduction 存储
6163
'vod_id' => $vod['id'] . '@@' . $vod['name'] . '@@' . ($vod['introduction'] ?? ''),
6264
'vod_name' => $vod['name'],
6365
'vod_pic' => $vod['icon'],
64-
'vod_remarks' => $vod['heat'] . '万播�
66+
'vod_remarks' => $vod['heat'] . '万播放'
6567
];
6668
}
6769
}
@@ -77,7 +79,7 @@ public function detailContent($ids) {
7779
$bookName = $parts[1];
7880
$intro = $parts[2] ?? '';
7981
} else {
80-
// 兼容旧格�id@intro
82+
// 兼容旧格式 id@intro
8183
$parts = explode('@', $did);
8284
$bookId = $parts[0];
8385
$bookName = '';
@@ -100,7 +102,7 @@ public function detailContent($ids) {
100102
// 提取短剧播放地址
101103
if (isset($chapter['shortPlayList'][0]['chapterShortPlayVoList'][0]['shortPlayUrl'])) {
102104
$vUrl = $chapter['shortPlayList'][0]['chapterShortPlayVoList'][0]['shortPlayUrl'];
103-
$epName = "� . ($index + 1) . "�;
105+
$epName = "" . ($index + 1) . "";
104106
$playUrls[] = $epName . '$' . $vUrl;
105107
}
106108
}
@@ -118,7 +120,7 @@ public function detailContent($ids) {
118120
}
119121

120122
public function searchContent($key, $quick = false, $pg = 1) {
121-
// �Python 代码�searchContentPage �pass,故此处留空返回
123+
// Python 代码中 searchContentPage 为 pass,故此处留空返回
122124
return $this->pageResult([], $pg);
123125
}
124126

spider/php/番茄小说 ᵈᶻ[书].php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function homeContent($filter = []) {
5959

6060
return [
6161
'class' => $classes,
62-
'filters' => $filters
62+
'filters' => (object)$filters
6363
];
6464
}
6565

@@ -253,7 +253,7 @@ public function playerContent($flag, $id, $vipFlags = []) {
253253
'parse' => 0,
254254
'playUrl' => '',
255255
'url' => $url, // 仅作记录
256-
'header' => [],
256+
'header' => (object)[],
257257
// 如果客户端支持直接显示文本内容,通常放在 header 或其他字段?
258258
// 实际上,播放接口返回 content 可能需要客户端特殊处理
259259
// 这里我们返回一个 data url 或者 模拟的 html
@@ -339,3 +339,6 @@ private function decodeText($text, $type = 0) {
339339
return $result;
340340
}
341341
}
342+
343+
// 运行爬虫
344+
(new Spider())->run();

0 commit comments

Comments
 (0)