優酷視頻採用動態加密機制,播放地址需經過 getPlayList API 取得加密參數,再本地解密組合真實 CDN 鏈接。本文提供完整 PHP 實作,涵蓋 HTTP 請求、JSON 解析、Seed 混淆演算法、SID 生成與最終 URL 組裝。
id_XMzkyODA2NTEy)。https://v.youku.com/player/getPlayList/VideoIDS/{id} 取得 JSON。seed、streamfileids、segs、key1/key2。getMixString(seed) 生成混淆字串,解密 streamfileids 獲得 fileid。sid,組合最終 FLV/MP4 下載地址。<?php
/**
* 取得優酷視頻真實下載地址 (FLV/MP4)
* @param string $url 分享頁 URL,如 https://v.youku.com/v_show/id_XMzkyODA2NTEy.html
* @return string|false 真實下載地址,失敗回傳 false
*/
function getYoukuFlv(string $url)
{
// 1. 提取 Video ID
if (!preg_match('#id_(.*?)\.html#', $url, $m)) {
return false;
}
$videoId = $m[1];
// 2. 請求 API 取得播放清單
$apiUrl = "https://v.youku.com/player/getPlayList/VideoIDS/$videoId";
$json = getCurlContents($apiUrl);
if (!$json) return false;
$data = json_decode($json);
if (json_last_error() !== JSON_ERROR_NONE || empty($data->data)) {
return false;
}
$video = $data->data[0];
$seed = $video->seed;
$segs = $video->segs; // 各畫質分段資訊,含 k (加密金鑰)
$streamIds = $video->streamfileids; // stdClass: flv => "加密id", mp4 => "加密id", ...
// 3. 遍歷畫質優先順序(flv > mp4 > hd2 > ...)
$preferred = ['flv', 'mp4', 'hd2', 'hd3'];
foreach ($preferred as $format) {
if (!isset($streamIds->$format) || !isset($segs->$format)) continue;
$encryptedId = $streamIds->$format;
$segInfo = $segs->$format[0]; // 取第一分段
$k = $segInfo->k; // 分段解密金鑰
// 4. 解密 fileid
$fileId = decryptFileId($encryptedId, $seed);
if (!$fileId) continue;
// 5. 生成 sid (時間戳 + 隨機數)
$sid = time() . mt_rand(10000, 99999);
// 6. 組裝最終下載地址
$downloadUrl = "https://f.youku.com/player/getFlvPath/sid/{$sid}_00/st/{$format}/fileid/{$fileId}?K={$k}";
return $downloadUrl;
}
return false;
}
/**
* cURL 取得內容(模擬瀏覽器 UA)
*/
function getCurlContents(string $url, int $timeout = 10): string|false
{
if (!function_exists('curl_init')) return false;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
CURLOPT_REFERER => 'https://v.youku.com/',
CURLOPT_SSL_VERIFYPEER => false, // 僅測試環境
CURLOPT_HEADER => false,
]);
$content = curl_exec($ch);
$errno = curl_errno($ch);
curl_close($ch);
return $errno === 0 ? $content : false;
}
/**
* 解密 fileid:依據 seed 生成混淆字串,按加密 id 索引取字元
*/
function decryptFileId(string $encryptedId, int $seed): string
{
$mixed = getMixString($seed); // 64 字元混淆字串
$parts = explode('*', $encryptedId); // 如 "12*34*56*"
array_pop($parts); // 移除尾部空元素
$realId = '';
foreach ($parts as $idx) {
$realId .= $mixed[(int)$idx] ?? '';
}
return $realId;
}
/**
* 生成混淆字串(優酷演算法移植)
* @param int $seed
* @return string 64 字元
*/
function getMixString(int $seed): string
{
$source = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\:._-1234567890';
$mixed = '';
$len = strlen($source);
for ($i = 0; $i < $len; $i++) {
$seed = ($seed * 211 + 30031) % 65536; // 線性同餘生成器
$index = (int)($seed / 65536 * strlen($source)); // 比例索引
$mixed .= $source[$index];
$source = substr_replace($source, '', $index, 1); // 移除已用字元
}
return $mixed;
}
/* ---------- 使用範例 ---------- */
// $url = 'https://v.youku.com/v_show/id_XMzkyODA2NTEy.html';
// $realUrl = getYoukuFlv($url);
// echo $realUrl ?: '解析失敗';
| 步驟 | 說明 |
|---|---|
| Seed 混淆 | 線性同餘生成器 (LCG):seed = (seed * 211 + 30031) % 65536,產生偽隨機序列。 |
| 字元池洗牌 | 每輪從 source 按比例取一字元,加入 mixed,並從池中移除,確保不重複。 |
| 加密 ID 結構 | 以 * 分隔的索引序列,如 12*34*5*,每個數字對應 mixed 的位置。 |
| SID 生成 | time() . rand(10000,99999),伺服器端不嚴格驗證,但需格式正確。 |
| K 值 | 分段加密金鑰,從 segs.{format}[0].k 取得,直接附加在 URL 參數 ?K=。 |
segs.{format} 陣列長度 > 1),需迴圈組裝所有段落 URL 並合併(或用 ffmpeg -i "concat:...")。# 安裝
pip install yt-dlp
# 取得真實下載鏈接(自動處理加密、分段、字幕)
yt-dlp -g "https://v.youku.com/v_show/id_XMzkyODA2NTEy.html"
# 直接下載最佳畫質
yt-dlp "https://v.youku.com/v_show/id_XMzkyODA2NTEy.html"
維護自建解析器成本極高,生產專案強烈建議直接呼叫 yt-dlp(支援 1000+ 網站、持續更新)或使用成熟的 PHP 封裝庫。