这次来个重头好戏———抖音无水印播放解析下载。抖音的也是炒鸡简单啦,上图开始分析。
原理分析
习惯性的在浏览器中打开一个分享链接https://v.douyin.com/JFY8sqW/
开始访问,访问发生重定向,地址栏里变成了重定向的一大串https://www.iesdouyin.com/share/video/6847790700554177805/?region=CN&mid=6847790811422509838&u_code=0&titleType=title&utm_source=copy_link&utm_campaign=client_share&utm_medium=android&app=aweme
F12打开控制台调整到手机端访问,打开network XHR请求,发现如图左边的第一个请求比较可疑。果断点开请求看响应,见名知义寻找线索,发现右边的那个地址play_addr
比较像要找的播放地址。
啥也不说了,手机模式下
访问这个地址,果不其然,真的是视频地址,居然还有抖音logo,这是带水印的播放地址哎,仔细观察下,这个播放地址里有个字符串playwm
像似playwatermark
的简写,去掉wm试下,哇真的没有水印了,完美,目标已经找到。
是时候好好分析一下这个请求的request了,很显然item_ids
是一个参数。拎出来这个请求url,完美获取数据。那关键点开始转义到这个 item_ids
字段怎么获取,往地址栏一看,url里面呆着呢,至此,大公告成。
总结归纳一下, 获取重定向地址栏里的类似video_id的字段值6847790700554177805
直接拼接好url后 https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=6847790700554177805
发起请求获取带水印的 播放地址https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200fc30000bs43pp80c78p0q1l80gg&ratio=720p&line=0
去掉水印播放url里的wm
即可获取到无水印播放地址。
PHP代码实现
首先安装Guzzle库并引入
composer require guzzlehttp/guzzle
。
require __DIR__.'/vendor/autoload.php';
use GuzzleHttp\Psr7\Stream;
class RemoveWater
{
protected $headers = [
'Connection' => 'keep-alive',
'User-Agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16D57 Version/12.0 Safari/604.1'
];
/**
* 简单请求数据
*/
static function guzzleRequest($url, $params = [], $timeout, $headers = [])
{
$client = new \GuzzleHttp\Client(['timeout' => $timeout, 'headers' => $headers, 'http_errors' => false,]);
try {
$data = [];
if ($headers) {
$data['headers'] = $headers;
}
$jar = new \GuzzleHttp\Cookie\CookieJar;
$data['cookies'] = $jar;
if (!$params) {
$response = $client->request('GET', $url, $data);
} else {
$data ['form_params'] = $params;
$response = $client->request('POST', $url, $data);
}
$body = $response->getBody();
if ($body instanceof Stream) {
$body = $body->getContents();
}
$res = json_decode($body, true);
if ($res) {
return $res;
}
return $body;
} catch (\GuzzleHttp\Exception\GuzzleException $e) {
return false;
}
}
function douYin($url)
{
$userAgent = $this->headers;
$client = new \GuzzleHttp\Client();
$res = $client->request('GET', $url, ['headers' => $userAgent, 'allow_redirects' => false , 'referer' => true]);
$content = $res->getBody()->getContents();
preg_match("#video/(.*)?/\?#", $content, $match);
$itemId = $match[1] ?? '';
preg_match("#dytk: \"(.*)\"#", $content, $match);
$dytk = $match[1] ?? '';
if (empty($itemId)) {
$content = self::guzzleRequest($url, [], 2, $userAgent, false);
$pattern = '#video/([0-9]{1,})/#';
preg_match($pattern, $content, $match);
$itemId = $match[1];
}
$apiUrl = "https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=$itemId&dytk=$dytk";
$content = self::guzzleRequest($apiUrl, [], 2, $userAgent);
$videoUrl = $content['item_list'][0]['video']['play_addr']['url_list'][0];
$videoUrl = str_replace('playwm', 'play', $videoUrl);
$coverImage = $content['item_list'][0]['video']['dynamic_cover']['url_list'][0];
$title = $content['item_list'][0]['desc'] ?? '';
$bgmSrc = $content['item_list'][0]['music']['play_url']['uri'] ?? '';
$client = new \GuzzleHttp\Client();
$jar = new \GuzzleHttp\Cookie\CookieJar;
$content = $client->request('GET', $videoUrl,[
'allow_redirects' => false,
'cookies' => $jar,
'headers' => $this->headers,
])->getBody()->getContents();
preg_match("#href=\"(.*)\"#", $content, $match);
$endUrl = $match[1];
return [
'video_src' => $endUrl,
'cover_image' => $coverImage,
'title' => $title,
'bgm_src' => $bgmSrc,
];
}
}
$url = $_GET['url'] ?? 'https://v.douyin.com/JFY8sqW/';
$result = (new RemoveWater())->douYin($url);
var_dump($result);