本文通过php代码通过抖音链接去获取抖音无水印视频,也可以批量获取下载抖音视频,禁止转载

请求过程分析

我们还是先获取一个抖音链接

1https://v.douyin.com/S2pF3G8/

通过访问重定向

1https://www.douyin.com/video/7190042191169883427

然后提取到其中的视频ID

17190042191169883427

URL参数X-Bogus

X-Bogus你可以理解为是一个根据视频ID及user-agent通过JS生成的用户信息参数,它可以用于校验,详细的一篇分析可以参考Freebuf上的《【JS 逆向百例】某音 X-Bogus 逆向分析,JSVMP 纯算法还原》

X-Bogus简单的理解就是通过js算法生成的一个秘钥,抖音通过这个秘钥能得到身份验证

文章后面会详细介绍X-Bogus的获取

去抖音获取视频的完整链接为:

1https://www.douyin.com/aweme/v1/web/aweme/detail/?aweme_id=7190049956269444386&aid=1128&version_name=23.5.0&device_platform=android&os_version=2333&X-Bogus=DFSzswSLfwUANnEftaw7nt9WcBJN

Cookie参数

完整的Cookie需要包含以下内容

1msToken=uTa38b9QFHB6JtEDzH9S4np17qxpG6OrROHQ8at2cBpoKfUb0UWmTkjCSpf72EcUrJgWTIoN6UgAv5BTXtCbOAhJcIRKyZIT7TMYapeOSpf;odin_tt=324fb4ea4a89c0c05827e18a1ed9cf9bf8a17f7705fcc793fec935b637867e2a5a9b8168c885554d029919117a18ba69; ttwid=1%7CWBuxH_bhbuTENNtACXoesI5QHV2Dt9-vkMGVHSRRbgY%7C1677118712%7C1d87ba1ea2cdf05d80204aea2e1036451dae638e7765b8a4d59d87fa05dd39ff; bd_ticket_guard_client_data=eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWNsaWVudC1jc3IiOiItLS0tLUJFR0lOIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLVxyXG5NSUlCRFRDQnRRSUJBREFuTVFzd0NRWURWUVFHRXdKRFRqRVlNQllHQTFVRUF3d1BZbVJmZEdsamEyVjBYMmQxXHJcbllYSmtNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVKUDZzbjNLRlFBNUROSEcyK2F4bXAwNG5cclxud1hBSTZDU1IyZW1sVUE5QTZ4aGQzbVlPUlI4NVRLZ2tXd1FJSmp3Nyszdnc0Z2NNRG5iOTRoS3MvSjFJc3FBc1xyXG5NQ29HQ1NxR1NJYjNEUUVKRGpFZE1Cc3dHUVlEVlIwUkJCSXdFSUlPZDNkM0xtUnZkWGxwYmk1amIyMHdDZ1lJXHJcbktvWkl6ajBFQXdJRFJ3QXdSQUlnVmJkWTI0c0RYS0c0S2h3WlBmOHpxVDRBU0ROamNUb2FFRi9MQnd2QS8xSUNcclxuSURiVmZCUk1PQVB5cWJkcytld1QwSDZqdDg1czZZTVNVZEo5Z2dmOWlmeTBcclxuLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tXHJcbiJ9

准确来说就是四个参数

  • msToken
  • odin_tt
  • ttwid
  • bd_ticket_guard_client_data

msToken

Cookie中需要包含msToken,该参数其实就是一个107位的随机数,由大小写英文字母、数字组成
php实现方式:

1$msToken = substr(str_shuffle(‘0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ’), 0, 107);

odin_tt

原则上这个字段是通过请求获得的,我们这里直接写死

1odin_tt=324fb4ea4a89c0c05827e18a1ed9cf9bf8a17f7705fcc793fec935b637867e2a5a9b8168c885554d029919117a18ba69; ttwid=1%7CWBuxH_bhbuTENNtACXoesI5QHV2Dt9-vkMGVHSRRbgY%7C1677118712%7C1d87ba1ea2cdf05d80204aea2e1036451dae638e7765b8a4d59d87fa05dd39ff;

ttwid

ttwid直接写死就好

1ttwid=1%7C7ZLJzwjjEw7NLeADTpVd-3eId-ZEIg0jpCEzTV9p_2A%7C1677681848%7C4ff4f97328ddc18b6d46c259bc26a05d2e654b50e3f21b27b8f9e9e8f9fcec82; Path=/; Domain=bytedance.com; Max-Age=31536000; HttpOnly; Secure

bd_ticket_guard_client_data

bd_ticket_guard_client_data直接写死就行了

1bd_ticket_guard_client_data=eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWNsaWVudC1jc3IiOiItLS0tLUJFR0lOIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLVxyXG5NSUlCRFRDQnRRSUJBREFuTVFzd0NRWURWUVFHRXdKRFRqRVlNQllHQTFVRUF3d1BZbVJmZEdsamEyVjBYMmQxXHJcbllYSmtNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVKUDZzbjNLRlFBNUROSEcyK2F4bXAwNG5cclxud1hBSTZDU1IyZW1sVUE5QTZ4aGQzbVlPUlI4NVRLZ2tXd1FJSmp3Nyszdnc0Z2NNRG5iOTRoS3MvSjFJc3FBc1xyXG5NQ29HQ1NxR1NJYjNEUUVKRGpFZE1Cc3dHUVlEVlIwUkJCSXdFSUlPZDNkM0xtUnZkWGxwYmk1amIyMHdDZ1lJXHJcbktvWkl6ajBFQXdJRFJ3QXdSQUlnVmJkWTI0c0RYS0c0S2h3WlBmOHpxVDRBU0ROamNUb2FFRi9MQnd2QS8xSUNcclxuSURiVmZCUk1PQVB5cWJkcytld1QwSDZqdDg1czZZTVNVZEo5Z2dmOWlmeTBcclxuLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tXHJcbiJ9

请求头其他参数

除了Cookie之外,其他的你还可以携带以下内容

1
2
User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
Referer : https://www.douyin.com/

最终完整请求

为了方便复现,我把curl命令也复制下来,方便各位改写自己的代码

1
2
3
4
5
6
7
curl –location –request GET ‘https://www.douyin.com/aweme/v1/web/aweme/detail/?aweme_id=7190042191169883427&aid=1128&version_name=23.5.0&device_platform=android&os_version=2333&X-Bogus=DFSzswSLEjvANnEftawF309WcBjK’ \
–header ‘User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36’ \
–header ‘Referer: https://www.douyin.com/’ \
–header ‘Cookie: msToken=uTa38b9QFHB6JtEDzH9S4np17qxpG6OrROHQ8at2cBpoKfUb0UWmTkjCSpf72EcUrJgWTIoN6UgAv5BTXtCbOAhJcIRKyZIT7TMYapeOSpf;odin_tt=324fb4ea4a89c0c05827e18a1ed9cf9bf8a17f7705fcc793fec935b637867e2a5a9b8168c885554d029919117a18ba69; ttwid=1%7CWBuxH_bhbuTENNtACXoesI5QHV2Dt9-vkMGVHSRRbgY%7C1677118712%7C1d87ba1ea2cdf05d80204aea2e1036451dae638e7765b8a4d59d87fa05dd39ff; bd_ticket_guard_client_data=eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWNsaWVudC1jc3IiOiItLS0tLUJFR0lOIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLVxyXG5NSUlCRFRDQnRRSUJBREFuTVFzd0NRWURWUVFHRXdKRFRqRVlNQllHQTFVRUF3d1BZbVJmZEdsamEyVjBYMmQxXHJcbllYSmtNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVKUDZzbjNLRlFBNUROSEcyK2F4bXAwNG5cclxud1hBSTZDU1IyZW1sVUE5QTZ4aGQzbVlPUlI4NVRLZ2tXd1FJSmp3Nyszdnc0Z2NNRG5iOTRoS3MvSjFJc3FBc1xyXG5NQ29HQ1NxR1NJYjNEUUVKRGpFZE1Cc3dHUVlEVlIwUkJCSXdFSUlPZDNkM0xtUnZkWGxwYmk1amIyMHdDZ1lJXHJcbktvWkl6ajBFQXdJRFJ3QXdSQUlnVmJkWTI0c0RYS0c0S2h3WlBmOHpxVDRBU0ROamNUb2FFRi9MQnd2QS8xSUNcclxuSURiVmZCUk1PQVB5cWJkcytld1QwSDZqdDg1czZZTVNVZEo5Z2dmOWlmeTBcclxuLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tXHJcbiJ9’ \
–header ‘Accept: */*’ \
–header ‘Host: www.douyin.com’ \
–header ‘Connection: keep-alive’

注意:通过这个完整的请求就能拿到抖音的视频链接地址,上面的完整请求里面,最重要的就是url后面携带的参数X-Bogus,有这个参数才能拿到视频地址。

X-Bogus的获取

原理:X-Bogus参数的原理其实是通过python去执行js文件,得到这个参数,js里面具体是一个逻辑很复杂的一套算法在里面,通过这套算法就能拿到这个参数;
python执行文件和js文件请移步到这里下载:https://github.com/xiexiangnow/X-Bogus

本文的案例是通过php去执行python脚本,再通过python脚本去执行js文件得到X-Bogus参数,python环境请使用2.7版本,python文件和js文件放在同一目录下
python文件请使用如下案例:
x-bogus.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# calc.py
import sys
import execjs
import json
import os
from urlparse import urlparse

def add(url, user_agent):
query = urlparse(url).query
bogusJsPath = os.path.abspath(os.path.dirname( os.path.abspath(__file__)) + os.path.sep + “X-Bogus.js”)
xbogus = execjs.compile(open(bogusJsPath, ‘r’).read()).call(‘sign’, query, user_agent)
new_url = url + “&X-Bogus=” + xbogus
return new_url

if __name__ == ‘__main__’:
try:
payload = sys.argv[1]
params = json.loads(payload)
result = add(params[‘url’], params[‘user_agent’])
print(result)
except ”, e:
print(e)

x-bogus.js到这里去下载:https://github.com/xiexiangnow/X-Bogus

注意:python脚本执行完成,会返回一个全新的且带有X-Bogus参数的一个链接地址。

php源码

请注意代码里面的注释位置,通过php去执行python脚本,python脚本去执行js脚本,在python脚本里面拼接好带有X-Bogus参数的链接地址并返回,再由php通过这个全新的链接去请求得到视频下载地址,并且已去除水印。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/**
* @notes 下载抖音视频
* @param $url // 原始抖音视频链接地址
* @return array|false
*/
public function parseDouyinVideoLink($url)
{
// 获取重定向链接真实地址
$headers = get_headers($url, 1);
$redirect_url = $headers[‘Location’] ?? $url;
if ($redirect_url) {
$url = $redirect_url;
}
// 获取视频id
preg_match(‘/(\d+)/’, $url, $matches);
if (empty($matches)) {
$this->errorMsg = ‘视频id获取失败’;
return false;
}
$num = $matches[1];
// 调用 Python 程序
$output = [];
$params = [
‘url’ => ‘https://www.douyin.com/aweme/v1/web/aweme/detail/?aweme_id=’ . $num . ‘&aid=1128&version_name=23.5.0&device_platform=android&os_version=2333’,
‘user_agent’ => ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36’
];
// 拼接python执行文件地址
$pyExe = app_path() . ‘bin’ . DIRECTORY_SEPARATOR . ‘x-bogus.py’;
$payload = json_encode($params);
// 执行python文件,在url上拼接x-bogus参数
$cmd = “python {$pyExe} ” . escapeshellarg($payload); // escapeshellarg:把字符串转码为可以在 shell 命令里使用的参数
// 执行python文件获取执行结果
exec($cmd, $output, $returnVar);
// 输出计算结果php think collection –type downloaddouyinvideo –url https://v.douyin.com/AAYyxQh/
if ($returnVar != 0) {
$this->errorMsg = ‘计算X-Bogus出错!’;
return false;
}
//echo ‘最新url:’ . $output[0] . “\n”; // 为最终带x-bogus的参数的url地址
$realVideoLink = $this->_parseDouyinVideoLink($output[0]);
return $realVideoLink;
}

/**
* 获取抖音视频资源,解析数据
* @param $url // $url为带x-bogus的url地址
* @return array|false
*/
private function _parseDouyinVideoLink($url)
{
$msToken = substr(str_shuffle(‘0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ’), 0, 107);
$curlHandle = curl_init();
curl_setopt_array($curlHandle, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => ”,
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_CUSTOMREQUEST => ‘GET’,
CURLOPT_HTTPHEADER => [
‘User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36’,
‘Referer: https://www.douyin.com/’,
‘Cookie: msToken=’ . $msToken . ‘;odin_tt=324fb4ea4a89c0c05827e18a1ed9cf9bf8a17f7705fcc793fec935b637867e2a5a9b8168c885554d029919117a18ba69; ttwid=1%7CWBuxH_bhbuTENNtACXoesI5QHV2Dt9-vkMGVHSRRbgY%7C1677118712%7C1d87ba1ea2cdf05d80204aea2e1036451dae638e7765b8a4d59d87fa05dd39ff; bd_ticket_guard_client_data=eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWNsaWVudC1jc3IiOiItLS0tLUJFR0lOIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLVxyXG5NSUlCRFRDQnRRSUJBREFuTVFzd0NRWURWUVFHRXdKRFRqRVlNQllHQTFVRUF3d1BZbVJmZEdsamEyVjBYMmQxXHJcbllYSmtNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVKUDZzbjNLRlFBNUROSEcyK2F4bXAwNG5cclxud1hBSTZDU1IyZW1sVUE5QTZ4aGQzbVlPUlI4NVRLZ2tXd1FJSmp3Nyszdnc0Z2NNRG5iOTRoS3MvSjFJc3FBc1xyXG5NQ29HQ1NxR1NJYjNEUUVKRGpFZE1Cc3dHUVlEVlIwUkJCSXdFSUlPZDNkM0xtUnZkWGxwYmk1amIyMHdDZ1lJXHJcbktvWkl6ajBFQXdJRFJ3QXdSQUlnVmJkWTI0c0RYS0c0S2h3WlBmOHpxVDRBU0ROamNUb2FFRi9MQnd2QS8xSUNcclxuSURiVmZCUk1PQVB5cWJkcytld1QwSDZqdDg1czZZTVNVZEo5Z2dmOWlmeTBcclxuLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tXHJcbiJ9’,
‘Accept: */*’,
‘Host: www.douyin.com’,
‘Connection: keep-alive’
],
]);
$response = curl_exec($curlHandle);
//返回结果
if (!$response) {
$error = curl_errno($curlHandle);
echo $url.PHP_EOL;
$this->errorMsg = ‘网络错误:’ . $error;
curl_close($curlHandle);
return false;
}
curl_close($curlHandle);
// 再次发送请求,获得视频最终结果参数
$arr = json_decode($response, true);
if (json_last_error() != JSON_ERROR_NONE) {
$this->errorMsg = ‘json解析错误’;
return false;
}
if ($arr[‘status_code’] == 0) {
try {
// 重新组装需要的视频参数
$data = [
‘code’ => 200,
‘msg’ => ‘解析成功’,
‘author’ => $arr[‘aweme_detail’][‘author’][‘nickname’],
‘uid’ => $arr[‘aweme_detail’][‘author’][‘unique_id’],
//’avatar’ => $arr[‘aweme_detail’][‘music’][‘avatar_large’][‘url_list’][0],
‘like’ => $arr[‘aweme_detail’][‘statistics’][‘digg_count’],
‘time’ => $arr[‘aweme_detail’][“create_time”],
‘title’ => $arr[‘aweme_detail’][‘desc’],
‘cover’ => $arr[‘aweme_detail’][‘video’][‘origin_cover’][‘url_list’][0],
‘url’ => $arr[‘aweme_detail’][‘video’][‘play_addr’][‘url_list’][0],
‘musicurl’ => $arr[‘aweme_detail’][‘music’][‘play_url’][‘url_list’][0],
‘music’ => [
‘author’ => $arr[‘aweme_detail’][‘music’][‘author’],
‘avatar’ => $arr[‘aweme_detail’][‘music’][‘cover_large’][‘url_list’][0],
‘url’ => $arr[‘aweme_detail’][‘music’][‘play_url’][‘url_list’][0],
]
];
return $data;
} catch (Exception $exception) {
if (is_null($arr[‘aweme_detail’]) && isset($arr[‘filter_detail’][‘detail_msg’])) {
$errorMsg = $arr[‘filter_detail’][‘detail_msg’];
$this->errorReason = $arr[‘filter_detail’][‘filter_reason’];
} else {
$errorMsg = $exception->getMessage();
}
$this->errorMsg = $errorMsg;
return false;
}
} else {
$this->errorMsg = ‘抖音数据返回错误’ . $arr[‘status_code’];
return false;
}
}

上面代码解析了部分重要的视频参数,请自由获取,url为视频链接地址,特别特别注意:视频链接地址是有时效的,我在做这个需求的时候是将视频下载下来后上传到了阿里云的OSS,再从OSS拿到的视频最新链接,你也可以上传到你所在的资源库里面,再获取视频链接地址。