抖音视频去水印接口分析附代码
本文通过php代码通过抖音链接去获取抖音无水印视频,也可以批量获取下载抖音视频,禁止转载
请求过程分析
我们还是先获取一个抖音链接
1 | https://v.douyin.com/S2pF3G8/ |
通过访问重定向
1 | https://www.douyin.com/video/7190042191169883427 |
然后提取到其中的视频ID
1 | 7190042191169883427 |
URL参数X-Bogus
X-Bogus你可以理解为是一个根据视频ID及user-agent通过JS生成的用户信息参数,它可以用于校验,详细的一篇分析可以参考Freebuf上的《【JS 逆向百例】某音 X-Bogus 逆向分析,JSVMP 纯算法还原》
X-Bogus简单的理解就是通过js算法生成的一个秘钥,抖音通过这个秘钥能得到身份验证
文章后面会详细介绍X-Bogus的获取
去抖音获取视频的完整链接为:
1 | https://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需要包含以下内容
1 | msToken=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
原则上这个字段是通过请求获得的,我们这里直接写死
1 | odin_tt=324fb4ea4a89c0c05827e18a1ed9cf9bf8a17f7705fcc793fec935b637867e2a5a9b8168c885554d029919117a18ba69; ttwid=1%7CWBuxH_bhbuTENNtACXoesI5QHV2Dt9-vkMGVHSRRbgY%7C1677118712%7C1d87ba1ea2cdf05d80204aea2e1036451dae638e7765b8a4d59d87fa05dd39ff; |
ttwid
ttwid直接写死就好
1 | ttwid=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直接写死就行了
1 | bd_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拿到的视频最新链接,你也可以上传到你所在的资源库里面,再获取视频链接地址。