Skip to content

Commit f5b6905

Browse files
author
Taois
committed
add: 增加php示例源
1 parent a9be6aa commit f5b6905

File tree

1 file changed

+326
-0
lines changed

1 file changed

+326
-0
lines changed

spider/php/B站.php

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
<?php
2+
// B站视频爬虫 - 简洁可用版(移除search相关代码)
3+
header('Content-Type: application/json; charset=utf-8');
4+
5+
class BiliBiliSpider {
6+
private $extendDict = [];
7+
private $cookie = [];
8+
private $header = [
9+
"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",
10+
"Referer" => "https://www.bilibili.com"
11+
];
12+
13+
public function __construct() {
14+
$this->extendDict = $this->getExtendDict();
15+
$this->cookie = $this->getCookie();
16+
}
17+
18+
private function getExtendDict() {
19+
return [
20+
'cookie' => $this->getConfigCookie(),
21+
'thread' => '0'
22+
];
23+
}
24+
25+
private function getConfigCookie() {
26+
// 配置您的B站Cookie
27+
return 'buvid3=xxxx; SESSDATA=xxxx;';
28+
}
29+
30+
private function getCookie() {
31+
$cookie = $this->extendDict['cookie'] ?? '';
32+
if (empty($cookie)) return [];
33+
34+
$cookies = [];
35+
$pairs = explode(';', $cookie);
36+
foreach ($pairs as $pair) {
37+
$pair = trim($pair);
38+
if (strpos($pair, '=') !== false) {
39+
list($name, $value) = explode('=', $pair, 2);
40+
$cookies[trim($name)] = trim($value);
41+
}
42+
}
43+
return $cookies;
44+
}
45+
46+
private function httpRequest($url, $params = []) {
47+
$ch = curl_init();
48+
49+
if (!empty($params)) {
50+
$url .= '?' . http_build_query($params);
51+
}
52+
53+
$headers = [];
54+
foreach ($this->header as $key => $value) {
55+
$headers[] = $key . ': ' . $value;
56+
}
57+
58+
curl_setopt_array($ch, [
59+
CURLOPT_URL => $url,
60+
CURLOPT_RETURNTRANSFER => true,
61+
CURLOPT_TIMEOUT => 10,
62+
CURLOPT_HTTPHEADER => $headers,
63+
CURLOPT_COOKIE => $this->buildCookieString(),
64+
CURLOPT_SSL_VERIFYPEER => false,
65+
CURLOPT_FOLLOWLOCATION => true
66+
]);
67+
68+
$response = curl_exec($ch);
69+
curl_close($ch);
70+
71+
return json_decode($response, true) ?: [];
72+
}
73+
74+
private function buildCookieString() {
75+
$pairs = [];
76+
foreach ($this->cookie as $name => $value) {
77+
$pairs[] = $name . '=' . $value;
78+
}
79+
return implode('; ', $pairs);
80+
}
81+
82+
// homeContent - 首页分类
83+
public function homeContent() {
84+
$classes = [
85+
["type_id" => "沙雕仙逆", "type_name" => "傻屌仙逆"],
86+
["type_id" => "沙雕动画", "type_name" => "沙雕动画"],
87+
["type_id" => "纪录片超清", "type_name" => "纪录片"],
88+
["type_id" => "演唱会超清", "type_name" => "演唱会"],
89+
["type_id" => "音乐超清", "type_name" => "流行音乐"],
90+
["type_id" => "美食超清", "type_name" => "美食"],
91+
["type_id" => "食谱", "type_name" => "食谱"],
92+
["type_id" => "体育超清", "type_name" => "体育"],
93+
["type_id" => "球星", "type_name" => "球星"],
94+
["type_id" => "中小学教育", "type_name" => "教育"],
95+
["type_id" => "幼儿教育", "type_name" => "幼儿教育"],
96+
["type_id" => "旅游", "type_name" => "旅游"],
97+
["type_id" => "风景4K", "type_name" => "风景"],
98+
["type_id" => "说案", "type_name" => "说案"],
99+
["type_id" => "知名UP主", "type_name" => "知名UP主"],
100+
["type_id" => "探索发现超清", "type_name" => "探索发现"],
101+
["type_id" => "鬼畜", "type_name" => "鬼畜"],
102+
["type_id" => "搞笑超清", "type_name" => "搞笑"],
103+
["type_id" => "儿童超清", "type_name" => "儿童"],
104+
["type_id" => "动物世界超清", "type_name" => "动物世界"],
105+
["type_id" => "相声小品超清", "type_name" => "相声小品"],
106+
["type_id" => "戏曲", "type_name" => "戏曲"],
107+
["type_id" => "解说", "type_name" => "解说"],
108+
["type_id" => "演讲", "type_name" => "演讲"],
109+
["type_id" => "小姐姐超清", "type_name" => "小姐姐"],
110+
["type_id" => "荒野求生超清", "type_name" => "荒野求生"],
111+
["type_id" => "健身", "type_name" => "健身"],
112+
["type_id" => "帕梅拉", "type_name" => "帕梅拉"],
113+
["type_id" => "太极拳", "type_name" => "太极拳"],
114+
["type_id" => "广场舞", "type_name" => "广场舞"],
115+
["type_id" => "舞蹈", "type_name" => "舞蹈"],
116+
["type_id" => "音乐", "type_name" => "音乐"],
117+
["type_id" => "歌曲", "type_name" => "歌曲"],
118+
["type_id" => "MV4K", "type_name" => "MV"],
119+
["type_id" => "舞曲超清", "type_name" => "舞曲"],
120+
["type_id" => "4K", "type_name" => "4K"],
121+
["type_id" => "电影", "type_name" => "电影"],
122+
["type_id" => "电视剧", "type_name" => "电视剧"],
123+
["type_id" => "白噪音超清", "type_name" => "白噪音"],
124+
["type_id" => "考公考证", "type_name" => "考公考证"],
125+
["type_id" => "平面设计教学", "type_name" => "平面设计教学"],
126+
["type_id" => "软件教程", "type_name" => "软件教程"],
127+
["type_id" => "Windows", "type_name" => "Windows"]
128+
];
129+
130+
return ['class' => $classes];
131+
}
132+
133+
// homeVideoContent - 首页推荐视频
134+
public function homeVideoContent() {
135+
$url = 'https://api.bilibili.com/x/web-interface/popular';
136+
$data = $this->httpRequest($url, ['ps' => 20, 'pn' => 1]);
137+
138+
$videos = [];
139+
if (isset($data['data']['list'])) {
140+
foreach ($data['data']['list'] as $item) {
141+
$videos[] = [
142+
'vod_id' => $item['aid'],
143+
'vod_name' => strip_tags($item['title']),
144+
'vod_pic' => $item['pic'],
145+
'vod_remarks' => $this->formatDuration($item['duration'])
146+
];
147+
}
148+
}
149+
150+
return ['list' => $videos];
151+
}
152+
153+
// categoryContent - 分类内容(使用搜索API)
154+
public function categoryContent($tid, $page, $filters = []) {
155+
$page = max(1, intval($page));
156+
157+
$url = 'https://api.bilibili.com/x/web-interface/search/type';
158+
$params = [
159+
'search_type' => 'video',
160+
'keyword' => $tid,
161+
'page' => $page
162+
];
163+
164+
$data = $this->httpRequest($url, $params);
165+
166+
$videos = [];
167+
if (isset($data['data']['result'])) {
168+
foreach ($data['data']['result'] as $item) {
169+
if ($item['type'] !== 'video') continue;
170+
171+
$videos[] = [
172+
'vod_id' => $item['aid'],
173+
'vod_name' => strip_tags($item['title']),
174+
'vod_pic' => 'https:' . $item['pic'],
175+
'vod_remarks' => $this->formatSearchDuration($item['duration'])
176+
];
177+
}
178+
}
179+
180+
$pageCount = $data['data']['numPages'] ?? 1;
181+
$total = $data['data']['numResults'] ?? count($videos);
182+
183+
return [
184+
'list' => $videos,
185+
'page' => $page,
186+
'pagecount' => $pageCount,
187+
'limit' => 20,
188+
'total' => $total
189+
];
190+
}
191+
192+
// detailContent - 视频详情
193+
public function detailContent($vid) {
194+
$url = 'https://api.bilibili.com/x/web-interface/view';
195+
$data = $this->httpRequest($url, ['aid' => $vid]);
196+
197+
if (!isset($data['data'])) {
198+
return ['list' => []];
199+
}
200+
201+
$video = $data['data'];
202+
203+
// 构建播放列表
204+
$playUrl = '';
205+
foreach ($video['pages'] as $index => $page) {
206+
$part = $page['part'] ?: '' . ($index + 1) . '';
207+
$duration = $this->formatDuration($page['duration']);
208+
$playUrl .= "{$part}\${$vid}_{$page['cid']}#";
209+
}
210+
211+
$vod = [
212+
"vod_id" => $vid,
213+
"vod_name" => strip_tags($video['title']),
214+
"vod_pic" => $video['pic'],
215+
"vod_content" => $video['desc'],
216+
"vod_play_from" => "B站视频",
217+
"vod_play_url" => rtrim($playUrl, '#')
218+
];
219+
220+
return ['list' => [$vod]];
221+
}
222+
223+
// playContent - 播放地址(高清优化)
224+
public function playContent($vid) {
225+
if (strpos($vid, '_') !== false) {
226+
list($avid, $cid) = explode('_', $vid);
227+
} else {
228+
return $this->errorResponse('无效的视频ID格式');
229+
}
230+
231+
// 使用高质量参数
232+
$url = 'https://api.bilibili.com/x/player/playurl';
233+
$params = [
234+
'avid' => $avid,
235+
'cid' => $cid,
236+
'qn' => 112, // 原画质量
237+
'fnval' => 0,
238+
];
239+
240+
$data = $this->httpRequest($url, $params);
241+
242+
if (!isset($data['data']) || $data['code'] !== 0) {
243+
return $this->errorResponse('获取播放地址失败');
244+
}
245+
246+
// 直接返回第一个播放地址
247+
if (isset($data['data']['durl'][0]['url'])) {
248+
$playUrl = $data['data']['durl'][0]['url'];
249+
250+
$headers = $this->header;
251+
$headers['Referer'] = 'https://www.bilibili.com/video/av' . $avid;
252+
$headers['Origin'] = 'https://www.bilibili.com';
253+
254+
return [
255+
'parse' => 0,
256+
'url' => $playUrl,
257+
'header' => $headers,
258+
'danmaku' => "https://api.bilibili.com/x/v1/dm/list.so?oid={$cid}"
259+
];
260+
}
261+
262+
return $this->errorResponse('无法获取播放地址');
263+
}
264+
265+
// 工具函数
266+
private function formatDuration($seconds) {
267+
if ($seconds <= 0) return '00:00';
268+
$minutes = floor($seconds / 60);
269+
$secs = $seconds % 60;
270+
return sprintf('%02d:%02d', $minutes, $secs);
271+
}
272+
273+
private function formatSearchDuration($duration) {
274+
$parts = explode(':', $duration);
275+
if (count($parts) === 2) {
276+
return $duration;
277+
}
278+
return '00:00';
279+
}
280+
281+
private function errorResponse($message) {
282+
return [
283+
'parse' => 0,
284+
'url' => '',
285+
'error' => $message
286+
];
287+
}
288+
}
289+
290+
// 主处理逻辑
291+
$ac = $_GET['ac'] ?? 'detail';
292+
$t = $_GET['t'] ?? '';
293+
$pg = $_GET['pg'] ?? '1';
294+
$f = $_GET['f'] ?? '';
295+
$ids = $_GET['ids'] ?? '';
296+
$id = $_GET['id'] ?? '';
297+
298+
$spider = new BiliBiliSpider();
299+
300+
try {
301+
switch ($ac) {
302+
case 'detail':
303+
if (!empty($ids)) {
304+
echo json_encode($spider->detailContent($ids));
305+
} elseif (!empty($t)) {
306+
$filters = !empty($f) ? json_decode($f, true) : [];
307+
echo json_encode($spider->categoryContent($t, $pg, $filters));
308+
} else {
309+
$result = $spider->homeContent();
310+
$videoResult = $spider->homeVideoContent();
311+
$result['list'] = $videoResult['list'];
312+
echo json_encode($result);
313+
}
314+
break;
315+
316+
case 'play':
317+
echo json_encode($spider->playContent($id));
318+
break;
319+
320+
default:
321+
echo json_encode(['error' => '未知操作: ' . $ac]);
322+
}
323+
} catch (Exception $e) {
324+
echo json_encode(['error' => $e->getMessage()]);
325+
}
326+
?>

0 commit comments

Comments
 (0)