|
|
@ -8,13 +8,16 @@ use App\BilibiliUpVideos; |
|
|
use App\BilibiliVideoParts; |
|
|
use App\BilibiliVideoParts; |
|
|
use App\BilibiliVideos; |
|
|
use App\BilibiliVideos; |
|
|
use App\Repositories\BilibiliVideoRepository; |
|
|
use App\Repositories\BilibiliVideoRepository; |
|
|
|
|
|
use DateTime; |
|
|
use Exception; |
|
|
use Exception; |
|
|
use GuzzleHttp\Client; |
|
|
|
|
|
|
|
|
use GuzzleHttp\Psr7\Request; |
|
|
use Illuminate\Support\Arr; |
|
|
use Illuminate\Support\Arr; |
|
|
use Illuminate\Support\Facades\App; |
|
|
use Illuminate\Support\Facades\App; |
|
|
use Illuminate\Support\Facades\DB; |
|
|
use Illuminate\Support\Facades\DB; |
|
|
use Illuminate\Support\Facades\Log; |
|
|
use Illuminate\Support\Facades\Log; |
|
|
use Illuminate\Support\Facades\Redis; |
|
|
use Illuminate\Support\Facades\Redis; |
|
|
|
|
|
use Psr\Http\Message\{RequestInterface, ResponseInterface}; |
|
|
|
|
|
use GuzzleHttp\{Client, HandlerStack, Middleware, RetryMiddleware}; |
|
|
|
|
|
|
|
|
class BilibiliServiceV2 |
|
|
class BilibiliServiceV2 |
|
|
{ |
|
|
{ |
|
|
@ -33,12 +36,49 @@ class BilibiliServiceV2 |
|
|
// private $remoteDir = "/data/";
|
|
|
// private $remoteDir = "/data/";
|
|
|
private $remoteDir = "/Volumes/Crucial X6/Video/"; |
|
|
private $remoteDir = "/Volumes/Crucial X6/Video/"; |
|
|
|
|
|
|
|
|
|
|
|
private $mixinKeyEncTab = [ |
|
|
|
|
|
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, |
|
|
|
|
|
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, |
|
|
|
|
|
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, |
|
|
|
|
|
36, 20, 34, 44, 52 |
|
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected $repository; |
|
|
protected $repository; |
|
|
|
|
|
|
|
|
|
|
|
protected $client; |
|
|
|
|
|
|
|
|
public function __construct(BilibiliVideoRepository $repository) |
|
|
public function __construct(BilibiliVideoRepository $repository) |
|
|
{ |
|
|
{ |
|
|
$this->repository = $repository; |
|
|
$this->repository = $repository; |
|
|
|
|
|
$maxRetries = 3; |
|
|
|
|
|
|
|
|
|
|
|
$decider = function (int $retries, RequestInterface $request, ResponseInterface $response = null) use ($maxRetries): bool { |
|
|
|
|
|
return |
|
|
|
|
|
$retries < $maxRetries |
|
|
|
|
|
&& null !== $response |
|
|
|
|
|
&& 429 === $response->getStatusCode(); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
$delay = function (int $retries, ResponseInterface $response): int { |
|
|
|
|
|
if (!$response->hasHeader('Retry-After')) { |
|
|
|
|
|
return RetryMiddleware::exponentialDelay($retries); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$retryAfter = $response->getHeaderLine('Retry-After'); |
|
|
|
|
|
|
|
|
|
|
|
if (!is_numeric($retryAfter)) { |
|
|
|
|
|
$retryAfter = (new DateTime($retryAfter))->getTimestamp() - time(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return (int)$retryAfter * 1000; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
$stack = HandlerStack::create(); |
|
|
|
|
|
$stack->push(Middleware::retry($decider, $delay)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$this->client = new Client(['handler' => $stack]); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public function queryPlayList() |
|
|
public function queryPlayList() |
|
|
@ -76,10 +116,9 @@ class BilibiliServiceV2 |
|
|
// 391073761 女团直拍
|
|
|
// 391073761 女团直拍
|
|
|
// 267781236 韩国女团饭拍直拍
|
|
|
// 267781236 韩国女团饭拍直拍
|
|
|
// 23400436 小雪_juvia
|
|
|
// 23400436 小雪_juvia
|
|
|
$list = BilibiliUpVideos |
|
|
|
|
|
::orderBy('updated_at', 'asc') |
|
|
|
|
|
->take(110) |
|
|
|
|
|
->get(); |
|
|
|
|
|
|
|
|
$list = BilibiliUpVideos::where("id", "<", "150") |
|
|
|
|
|
->orderBy('id', 'desc') |
|
|
|
|
|
->get(); |
|
|
// $list = $list->slice(87);
|
|
|
// $list = $list->slice(87);
|
|
|
foreach ($list as $item) { |
|
|
foreach ($list as $item) { |
|
|
Log::info("schedule queryUpVideoList current up is {$item['up_name']}, started at: " . date("Y-m-d H:i:s")); |
|
|
Log::info("schedule queryUpVideoList current up is {$item['up_name']}, started at: " . date("Y-m-d H:i:s")); |
|
|
@ -88,14 +127,39 @@ class BilibiliServiceV2 |
|
|
$mediaId = $item['mid']; |
|
|
$mediaId = $item['mid']; |
|
|
$videos = []; |
|
|
$videos = []; |
|
|
$pageNo = 1; |
|
|
$pageNo = 1; |
|
|
$url = "https://api.bilibili.com/x/space/arc/search?mid={$mediaId}&ps=30&tid=0&keyword=&order=pubdate&jsonp=jsonp&pn="; |
|
|
|
|
|
$pageAll = 10; |
|
|
|
|
|
|
|
|
$url = "https://api.bilibili.com/x/space/wbi/arc/search?mid={$mediaId}&ps=30&tid=0&keyword=&pn="; |
|
|
|
|
|
$pageAll = 2; |
|
|
|
|
|
|
|
|
|
|
|
// https://api.bilibili.com/x/space/wbi/arc/search?
|
|
|
|
|
|
// mid=363430107&
|
|
|
|
|
|
//ps=30&
|
|
|
|
|
|
//tid=0&
|
|
|
|
|
|
//pn=1&
|
|
|
|
|
|
//keyword=&
|
|
|
|
|
|
//order=pubdate&
|
|
|
|
|
|
//platform=web&
|
|
|
|
|
|
//web_location=1550101&
|
|
|
|
|
|
//order_avoided=true&
|
|
|
|
|
|
//w_rid=a1011501119a6d795f369ec2bafa1af2&
|
|
|
|
|
|
//wts=1685087658
|
|
|
// https://space.bilibili.com/475250/video
|
|
|
// https://space.bilibili.com/475250/video
|
|
|
for ($i = 1; $i < $pageAll; $i++) { |
|
|
for ($i = 1; $i < $pageAll; $i++) { |
|
|
$curl = curl_init(); |
|
|
$curl = curl_init(); |
|
|
|
|
|
|
|
|
|
|
|
$params = [ |
|
|
|
|
|
"mid" => $mediaId, |
|
|
|
|
|
"ps" => 30, |
|
|
|
|
|
"tid" => 0, |
|
|
|
|
|
"keyword" => "", |
|
|
|
|
|
"order" => "pubdate", |
|
|
|
|
|
"platform" => "web", |
|
|
|
|
|
"web_location" => 1550101, |
|
|
|
|
|
"order_avoided" => true, |
|
|
|
|
|
"pn" => $i |
|
|
|
|
|
]; |
|
|
|
|
|
$query = $this->build_params($params); |
|
|
curl_setopt_array($curl, array( |
|
|
curl_setopt_array($curl, array( |
|
|
CURLOPT_URL => "https://api.bilibili.com/x/space/arc/search?mid={$mediaId}&ps=30&tid=0&keyword=&order=pubdate&jsonp=jsonp&pn={$i}", |
|
|
|
|
|
|
|
|
CURLOPT_URL => "https://api.bilibili.com/x/space/wbi/arc/search?$query", |
|
|
CURLOPT_RETURNTRANSFER => true, |
|
|
CURLOPT_RETURNTRANSFER => true, |
|
|
CURLOPT_ENCODING => "", |
|
|
CURLOPT_ENCODING => "", |
|
|
CURLOPT_MAXREDIRS => 10, |
|
|
CURLOPT_MAXREDIRS => 10, |
|
|
@ -730,7 +794,7 @@ class BilibiliServiceV2 |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
$i++; |
|
|
$i++; |
|
|
$list = BilibiliVideos::simplePaginate(20, null, 'page', $i); |
|
|
|
|
|
|
|
|
$list = BilibiliVideos::simplePaginate(50, null, 'page', $i); |
|
|
try { |
|
|
try { |
|
|
usleep(random_int(10, 1000) * 1000); |
|
|
usleep(random_int(10, 1000) * 1000); |
|
|
} catch (Exception $e) { |
|
|
} catch (Exception $e) { |
|
|
@ -1140,4 +1204,154 @@ done && echo "ok"'); |
|
|
} |
|
|
} |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public function downloadDynamics() |
|
|
|
|
|
{ |
|
|
|
|
|
$env = App::environment(); |
|
|
|
|
|
$list = BilibiliUpVideos::orderBy("created_at", "desc")->where("id", "<", "94")->limit(100)->get(); |
|
|
|
|
|
foreach ($list as $item) { |
|
|
|
|
|
$mid = $item["mid"]; |
|
|
|
|
|
$offset = null; |
|
|
|
|
|
$response = null; |
|
|
|
|
|
do { |
|
|
|
|
|
$jsonResponse = $this->dynamicsRequest($mid, $offset); |
|
|
|
|
|
$response = json_decode($jsonResponse, true); |
|
|
|
|
|
if (array_key_exists("code", $response) && $response["code"] == '0' && array_key_exists("data", $response) && array_key_exists("items", $response["data"])) { |
|
|
|
|
|
$dynamics = $response["data"]["items"]; |
|
|
|
|
|
foreach ($dynamics as $dynamic) { |
|
|
|
|
|
if ($dynamic["type"] == "DYNAMIC_TYPE_DRAW") { |
|
|
|
|
|
if (array_key_exists("modules", $dynamic) && |
|
|
|
|
|
array_key_exists("module_dynamic", $dynamic["modules"]) && |
|
|
|
|
|
array_key_exists("major", $dynamic["modules"]["module_dynamic"]) && |
|
|
|
|
|
array_key_exists("draw", $dynamic["modules"]["module_dynamic"]["major"]) && |
|
|
|
|
|
array_key_exists("items", $dynamic["modules"]["module_dynamic"]["major"]["draw"]) |
|
|
|
|
|
) { |
|
|
|
|
|
$imageItems = $dynamic["modules"]["module_dynamic"]["major"]["draw"]["items"]; |
|
|
|
|
|
foreach ($imageItems as $imageItem) { |
|
|
|
|
|
$imageUrl = $imageItem["src"]; |
|
|
|
|
|
$this->downloadImage($imageUrl, "/Users/shixuesen/Downloads/y/bili", $item["up_name"]); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if (array_key_exists("data", $response) && array_key_exists("offset", $response["data"])) { |
|
|
|
|
|
$offset = $response["data"]["offset"]; |
|
|
|
|
|
echo "{$item["up_name"]} offset: $offset \n"; |
|
|
|
|
|
} |
|
|
|
|
|
usleep(random_int(10, 100) * 100); |
|
|
|
|
|
} while ($response != null && array_key_exists("data", $response) && array_key_exists("has_more", $response["data"]) && $response["data"]["has_more"]); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public function dynamicsRequest($mid, $offset) |
|
|
|
|
|
{ |
|
|
|
|
|
$client = new Client(); |
|
|
|
|
|
$headers = [ |
|
|
|
|
|
'authority' => 'api.bilibili.com', |
|
|
|
|
|
'accept' => 'application/json, text/plain, */*', |
|
|
|
|
|
'accept-language' => 'zh-CN,zh;q=0.9', |
|
|
|
|
|
'cache-control' => 'no-cache', |
|
|
|
|
|
'origin' => 'https://space.bilibili.com', |
|
|
|
|
|
'pragma' => 'no-cache', |
|
|
|
|
|
'referer' => "https://space.bilibili.com/$mid/dynamic", |
|
|
|
|
|
'sec-ch-ua' => '"Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"', |
|
|
|
|
|
'sec-ch-ua-mobile' => '?0', |
|
|
|
|
|
'sec-ch-ua-platform' => '"macOS"', |
|
|
|
|
|
'sec-fetch-dest' => 'empty', |
|
|
|
|
|
'sec-fetch-mode' => 'cors', |
|
|
|
|
|
'sec-fetch-site' => 'same-site', |
|
|
|
|
|
'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36' |
|
|
|
|
|
]; |
|
|
|
|
|
if ($offset != null) { |
|
|
|
|
|
$url = "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?offset={$offset}&host_mid={$mid}&timezone_offset=-480&features=itemOpusStyle"; |
|
|
|
|
|
} else { |
|
|
|
|
|
$url = "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?offset=&host_mid={$mid}&timezone_offset=-480&features=itemOpusStyle"; |
|
|
|
|
|
} |
|
|
|
|
|
$request = new Request('GET', $url, $headers); |
|
|
|
|
|
$res = $client->sendAsync($request)->wait(); |
|
|
|
|
|
return $res->getBody(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public function downloadImage($imageUrl, $path, $upName) |
|
|
|
|
|
{ |
|
|
|
|
|
$filePathInfo = pathinfo($imageUrl); |
|
|
|
|
|
$filename = $filePathInfo['basename']; |
|
|
|
|
|
$request = new Request('GET', $imageUrl); |
|
|
|
|
|
try { |
|
|
|
|
|
$res = $this->client->sendAsync($request)->wait(); |
|
|
|
|
|
} catch (\Exception $e) { |
|
|
|
|
|
Log::error("$upName, $imageUrl, is error " . $e->getMessage()); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
$fileLocalName = $path . DIRECTORY_SEPARATOR . $upName . "_" . $filename; |
|
|
|
|
|
if (is_file($fileLocalName) && filesize($fileLocalName) > 10000) { |
|
|
|
|
|
echo $fileLocalName . " exists \n"; |
|
|
|
|
|
} else { |
|
|
|
|
|
file_put_contents($path . DIRECTORY_SEPARATOR . $upName . "_" . $filename, $res->getBody()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function getMixinKey($orig) |
|
|
|
|
|
{ |
|
|
|
|
|
// '对 imgKey 和 subKey 进行字符顺序打乱编码'
|
|
|
|
|
|
return substr(implode('', array_map(function ($i) use ($orig) { |
|
|
|
|
|
return $orig[$i]; |
|
|
|
|
|
}, $this->mixinKeyEncTab)), 0, 32); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public function encWbi($params, $img_key, $sub_key) |
|
|
|
|
|
{ |
|
|
|
|
|
// '为请求参数进行 wbi 签名'
|
|
|
|
|
|
$mixin_key = $this->getMixinKey($img_key . $sub_key); |
|
|
|
|
|
$curr_time = round(time()); |
|
|
|
|
|
$params['wts'] = $curr_time; // 添加 wts 字段
|
|
|
|
|
|
ksort($params); // 按照 key 重排参数
|
|
|
|
|
|
$params = array_map(function ($v) { |
|
|
|
|
|
return preg_replace("#[!'()*/]#", '', $v); // 过滤 value 中的 "!'()*" 字符
|
|
|
|
|
|
}, $params); |
|
|
|
|
|
$query = http_build_query($params); // 序列化参数
|
|
|
|
|
|
$wbi_sign = md5($query . $mixin_key); // 计算 w_rid
|
|
|
|
|
|
$params['w_rid'] = $wbi_sign; |
|
|
|
|
|
return $params; |
|
|
|
|
|
} |
|
|
|
|
|
public function getWbiKeys() |
|
|
|
|
|
{ |
|
|
|
|
|
$img_key = Redis::connection()->get("img_key"); |
|
|
|
|
|
$sub_key = Redis::connection()->get("sub_key"); |
|
|
|
|
|
if ($img_key != null && $sub_key != null) { |
|
|
|
|
|
return [$img_key, $sub_key]; |
|
|
|
|
|
} |
|
|
|
|
|
// '获取最新的 img_key 和 sub_key'
|
|
|
|
|
|
$resp = file_get_contents('https://api.bilibili.com/x/web-interface/nav'); |
|
|
|
|
|
$json_content = json_decode($resp, true); |
|
|
|
|
|
$img_url = $json_content['data']['wbi_img']['img_url']; |
|
|
|
|
|
$sub_url = $json_content['data']['wbi_img']['sub_url']; |
|
|
|
|
|
$img_key = substr($img_url, strrpos($img_url, '/') + 1, strrpos($img_url, '.') - strrpos($img_url, '/') - 1); |
|
|
|
|
|
$sub_key = substr($sub_url, strrpos($sub_url, '/') + 1, strrpos($sub_url, '.') - strrpos($sub_url, '/') - 1); |
|
|
|
|
|
Redis::connection()->setex("img_key", strtotime('23:59:59')-time(), $img_key); |
|
|
|
|
|
Redis::connection()->setex("sub_key", strtotime('23:59:59')-time(), $sub_key); |
|
|
|
|
|
return [$img_key, $sub_key]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public function build_params($params) |
|
|
|
|
|
{ |
|
|
|
|
|
list($img_key, $sub_key) = $this->getWbiKeys(); |
|
|
|
|
|
// dump($img_key);
|
|
|
|
|
|
// dump($sub_key);
|
|
|
|
|
|
$signed_params = $this->encWbi( |
|
|
|
|
|
$params, |
|
|
|
|
|
$img_key, |
|
|
|
|
|
$sub_key |
|
|
|
|
|
); |
|
|
|
|
|
// dump($signed_params);exit;
|
|
|
|
|
|
$query = http_build_query($signed_params); |
|
|
|
|
|
// print_r($signed_params);
|
|
|
|
|
|
return $query; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|