浅笑博客
对与错,应该由历史来决定。我们能做的,就是相信自己认为是对的事,然后为此战斗……不过我认为那个强者生,弱者死的……弱肉强食时代,绝对是错的!绝对错!
浅笑博客
PHP实现快手视频解析接口
PHP实现快手视频解析接口

特别提示

本文所述内容与源码仅限用于学习和研究目的,不得将其用于商业或者非法用途,否则,一切后果自负。用其解析获取的视频地址,仅用作以更好的体验观看视频为目的,且您必须在下载后的24小时之内,从您的电脑或手机中彻底删除上述内容 。接口返回的一切信息请勿用作任何违反快手法律条款以及违反法律法规的行为。一切因上述行为而可能遭致的意外、疏忽、侵权及其造成的损失,作者对其概不负责,亦不承担任何法律责任。

快手《用户服务协议》第七条“服务或使用说明”第二点“违规行为界定” 的第4小点明确用户使用任何工具收集或处理快手提供的内容属于侵犯快手公司合法权益的行为。
以及《快手社区管理规定(试行)》第四章“恶意行为”第三点“非法行为”中第6小点第一条明确涉及非原创内容,恶意盗取他人作品、违规拍屏、侵权转发及涉嫌抄袭的内容均属非法行为。

解析原理介绍

参考资料

https://blog.csdn.net/abc8125/article/details/104833555
https://github.com/iqiqiya/iqiqiya-API

我们看一下一个快手作品的分享链接,如 https://v.kuaishou.com/6liamd ,显然为生成的一个短链接。而最终浏览器地址栏显示的地址为https://live.kuaishou.com/u/3x3dzyvbvyugsem/3x6sxm9ixh7ufau?did=web_88336caebf13033ae53ccfcfdb95dd44,所以肯定经过了重定向。通过简单抓包发现,经过了多次重定向,如下图:

http://blog.qianxiao.fun/wp-content/uploads/2020/05/图片-1-1024x608.png

由于个人对Fiddler也不甚熟悉,Fiddler 抓的请求还是不能看到中间的一些过程。结合参考网友博客,用php进行请求获取重定向请求请求头。代码如下(部分代码已省略,只列出关键代码):

<?php
$originurl = 'https://v.kuaishou.com/7En2qL';
$content1 = getResponseHeader($url);
echo $content1;#调试输出

#获取重定向请求头
function getResponseHeader($url) {
    $ch  = curl_init($url);
    $httpheader = [];
    $httpheader[] = 'X-FORWARDED-FOR:'.$rip;
    $httpheader[] = 'CLIENT-IP:'.$rip;
    #请求头中添加cookie
    $httpheader[] = 'cookie:did=web_'.$did.'; didv='.time().'000;clientid=3; client_key=6589'.rand(1000, 9999);
    curl_setopt($ch, CURLOPT_HTTPHEADER,$httpheader);
    #以下两句设置返回响应头不返回响应体
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_NOBODY, true);
    #返回数据不直接输出
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $content = curl_exec($ch);
    curl_close($ch);
    return $content;
}
?>

结果如下:

HTTP/1.1 302 
Date: Wed, 20 May 2020 12:30:33 GMT
Connection: keep-alive
Location: https://c.kuaishou.com/fw/photo/3xdx7459bwuyz49?fid=281200681&cc=share_copylink&shareMethod=TOKEN&docId=0&kpn=KUAISHOU&subBiz=PHOTO&photoId=3xdx7459bwuyz49&shareId=178057380329&shareToken=X3ZBwzZXJHUX1mq_A&shareResourceType=PHOTO_OTHER&userId=3xsqg7tpsmjcbry&shareType=1&et=1_i%2F0_unknown0&groupName=&appType=21&shareObjectId=27263079401&shareUrlOpened=0&timestamp=1589975186219
X-KSLOGID: 589977833260938440

可以看到重定向的请求头里含有Location重定向地址。继续对此地址进行请求,请求时一样设置不一次性输出数据(curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);),请求获取此请求的响应体。代码如下(部分代码已省略,只列出关键代码):

<?php
$url2 = 'https://c.kuaishou.com/fw/photo/3xdx7459bwuyz49?fid=281200681&cc=share_copylink&shareMethod=TOKEN&docId=0&kpn=KUAISHOU&subBiz=PHOTO&photoId=3xdx7459bwuyz49&shareId=178057380329&shareToken=X3ZBwzZXJHUX1mq_A&shareResourceType=PHOTO_OTHER&userId=3xsqg7tpsmjcbry&shareType=1&et=1_i%2F0_unknown0&groupName=&appType=21&shareObjectId=27263079401&shareUrlOpened=0&timestamp=1589975186219';
$content2 = getResponseBody($url2);
echo $content1;#调试输出

#获取响应体
function getResponseBody($url) {
    $ch = curl_init();
    #5秒超时
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5000);
    #设置默认ua  这里经常测试,尽量用手机的ua,电脑的ua获取不到数据
    curl_setopt($ch, CURLOPT_USERAGENT,'User-Agent: Mozilla/5.0 (Linux; Android 5.1.1; vivo X9 Plus Build/LMY48Z) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36');
    #把随机ip添加进请求头 
    $httpheader = [];
    $httpheader[] = 'X-FORWARDED-FOR:'.$rip;
    $httpheader[] = 'CLIENT-IP:'.$rip;
    #请求头中添加cookie
    $httpheader[] = 'cookie:did=web_'.$did.'; didv='.time().'000;';
    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
    #返回数据不直接输出
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    #设置请求地址
    curl_setopt($ch, CURLOPT_URL, $url);
    #关闭ssl验证
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    #设置默认referer
    curl_setopt($ch, CURLOPT_REFERER, 'http://m.gifshow.com');
    #get方式请求
    curl_setopt($ch, CURLOPT_POST, false);
    $contents = curl_exec($ch);
    curl_close($ch);
    return $contents;
}
?>

结果:

http://blog.qianxiao.fun/wp-content/uploads/2020/05/图片-2-1024x612.png

返回内容为html,其中发现有一个id为hide-pagedata的div,包含属性data-pagedata,其内容像json格式,字符被url编码了,将其复制出来进行url解码后,json解析测试,发现完美解析成功。里面的数据包含了我们需要的数据,返回自己所需要的数据即可。

拓展

由于发现重定向的地址https://c.kuaishou.com/fw/photo/后面都是一个视频id字符串,get请求的参数中photoId也是为 视频id字符串,大胆尝试用其他视频的视频id与原来的参数拼接起来,请求发现,仍能成功获取到视频数据。于是将接口进一步拓展,以支持长链接如 https://live.kuaishou.com/u/3xv78fxycm35nn4/3xwrbiqsgmc9arg 的解析,只需判断如果是长链接分享链接,正则最后面的那个视频id字符串( 3xwrbiqsgmc9arg ),然后拼接成一个新链接,继续处理,获取解析数据。

注意事项

为了更好的请求不出错,可以请求时通过抓包尽可能的对原请求进行模拟,如加上模拟的请求cookie(可通过抓包获取),使用随机ip等。

在获取重定向地址响应体时用手机ua,经测试电脑ua获取不到数据。

完整代码

<?php
#接受所有请求源
header('Access-Control-Allow-Origin:*');
#指定返回数据为json utf-8
header('Content-type:application/json; charset=utf-8');

!empty($_REQUEST['url']) ? $originurl = $_REQUEST['url'] : retn(0,"请求参数错误");

#生成随机did,用于请求快手链接的cookie
$did = md5(time() . mt_rand(1,1000000));
#每次请求生成一个随机ip
$rip = Rand_IP();
#方便用户使用,如用户传递一个包含链接的文本,将链接正则出来
$originurl = preg_match("~[a-zA-z]+://[^\s]*~", $originurl, $originurlmatches);
if (count($originurlmatches) == 0) {
	#没有正则到要解析的地址
	retn(-4,"没有检测到要解析的地址");
}else{
	$url = $originurlmatches[0];
	#判断要解析的链接是短链接还是长链接
	preg_match("~[a-zA-z]+://live.kuaishou.com/u/[a-zA-z0-9]+/[a-zA-z0-9]+~", $url, $videoidmatches);
	if (count($videoidmatches) != 0) {
		#长链接
		#正则成功后截取最后的那个视频id
		$videoid = substr(strrchr($videoidmatches[0], "/"), 1);
		$url2 = 'https://c.kuaishou.com/fw/photo/'.$videoid.'?fid=281200681&cc=share_copylink&shareMethod=TOKEN&docId=0&kpn=KUAISHOU&subBiz=PHOTO&photoId='.$videoid.'&shareId=177551279794&shareToken=X-48680WzimADJVn_A&shareResourceType=PHOTO_OTHER&userId=3x3dzyvbvyugsem&shareType=1&et=1_i%2F0_unknown0&groupName=&appType=21&shareObjectId=26782848098&shareUrlOpened=0&timestamp=1589908450616';
	}else{
		#短链接
		#获取302重定向请求头
		$content1 = getResponseHeader($url);
		#从请求头里正则解析出重定向地址
		preg_match("~[a-zA-z]+://[^\s]*~", $content1, $matches);
		if (count($matches) == 0) {
			#没有正则到重定向地址
			retn(-5,"这可能不是一个有效的快手链接");
		}else{
			$url2 = $matches[0];#获取到302重定向地址
		}
	}
	#获取302重定向地址页面的响应体
	$content2 = getResponseBody($url2);
	#正则取出关键数据
	preg_match("~data-pagedata=\"(.*?)\"~", $content2, $matches);
	if (count($matches) <= 1) {
		#没有正则到关键数据
		retn(-6,"解析失败002");
	}else{
		$pagedata = $matches[1];
		#关键:将html实体转回字符串(如&#34;转")
		$pagedata= htmlspecialchars_decode($pagedata);
		#解析json为数组(去除pom头3空白字符 防止解析json失败)
		$pagedata_json = json_decode(trim($pagedata,chr(239).chr(187).chr(191)),true);
		if($pagedata_json == null){
			#关键数据解析为json失败
			retn(-7,"解析失败003");
		}else{
			if($pagedata_json['status']==1){
				$sharetype = $pagedata_json['share']['type'];

				$data = [];
				$data["type"] = $sharetype;
				$data["title"] = $pagedata_json['share']['title'];;
				$data["username"] = $pagedata_json['user']['name'];
				$data["poster"] = $pagedata_json['video']['poster'];
				if($sharetype=="video"){
					#视频
					$mp4url = $pagedata_json['video']['srcNoMark'];
					$data["mp4url"] = $mp4url;
					retn(1,"请求成功",$data);
				}elseif($sharetype=="images"||$sharetype=="image_long"){
					#图组或长图
					$data["images"] = $pagedata_json['video']['images'];
					$imageCdn = $pagedata_json['video']['imageCDN'];
					for ($i=0; $i < count($data["images"]); $i++) {
					    $data["images"][$i]['path'] = "http://".$imageCdn.$data["images"][$i]['path'];
					}
					$data["audio"] = "http://".$imageCdn.$pagedata_json['video']['audio'];
					retn(1,"请求成功",$data);
				}elseif($sharetype=="image"){
					#图片
					$data["image"] = $data["poster"];
					$imageCdn = $pagedata_json['video']['imageCDN'];
					$data["audio"] = "http://".$imageCdn.$pagedata_json['video']['audio'];
					retn(1,"请求成功",$data);
				}else{
					#暂时写了图片、图组、长图、视频的解析。其他作品类型可自行测试添加
					retn(-10,"该作品类型暂不支持,敬请期待");
				}
			}else{
				#如果状态码不为1,看下是否有错误并输出错误信息
				if($pagedata_json['error']==True){
					#有时会返回错误:快手验证码 经测试,使用作品最新分享链接即可正常获取
					if($pagedata_json['error_msg']=="快手验证码"){
						retn(-11,"请用作品最新分享链接重试");
					}else{
						retn(-8,$pagedata_json['error_msg']);
					}
				}else{
					retn(-9,"解析失败004");
				}
			}
		}
	}	
}
//随机IP
function Rand_IP(){
	#第一种方法,直接生成
    $ip2id= round(rand(600000, 2550000) / 10000);
    $ip3id= round(rand(600000, 2550000) / 10000);
    $ip4id= round(rand(600000, 2550000) / 10000);
	#第二种方法,随机抽取
    $arr_1 = array("218","218","66","66","218","218","60","60","202","204","66","66","66","59","61","60","222","221","66","59","60","60","66","218","218","62","63","64","66","66","122","211");
    $randarr= mt_rand(0,count($arr_1)-1);
    $ip1id = $arr_1[$randarr];
    return $ip1id.".".$ip2id.".".$ip3id.".".$ip4id;
}
#获取重定向请求头
function getResponseHeader($url) {
    $ch  = curl_init($url);
    $httpheader = [];
    $httpheader[] = 'X-FORWARDED-FOR:'.$rip;
    $httpheader[] = 'CLIENT-IP:'.$rip;
    #请求头中添加cookie
    $httpheader[] = 'cookie:did=web_'.$did.'; didv='.time().'000;clientid=3; client_key=6589'.rand(1000, 9999);
    curl_setopt($ch, CURLOPT_HTTPHEADER,$httpheader);
    #以下两句设置返回响应头不返回响应体
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_NOBODY, true);
    #返回数据不直接输出
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $content = curl_exec($ch);
    curl_close($ch);
    return $content;
}
#获取响应体
function getResponseBody($url) {
    $ch = curl_init();
    #5秒超时
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5000);
    #设置默认ua  这里经常测试,尽量用手机的ua,电脑的ua获取不到数据
    curl_setopt($ch, CURLOPT_USERAGENT,'User-Agent: Mozilla/5.0 (Linux; Android 5.1.1; vivo X9 Plus Build/LMY48Z) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36');
    #把随机ip添加进请求头 
    $httpheader = [];
    $httpheader[] = 'X-FORWARDED-FOR:'.$rip;
    $httpheader[] = 'CLIENT-IP:'.$rip;
    #请求头中添加cookie
    $httpheader[] = 'cookie:did=web_'.$did.'; didv='.time().'000;';
    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
    #返回数据不直接输出
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    #设置请求地址
    curl_setopt($ch, CURLOPT_URL, $url);
    #关闭ssl验证
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    #设置默认referer
    curl_setopt($ch, CURLOPT_REFERER, 'http://m.gifshow.com');
    #get方式请求
    curl_setopt($ch, CURLOPT_POST, false);
    $contents = curl_exec($ch);
    curl_close($ch);
    return $contents;
}
#数据返回
function retn($code,$str,$data=null){
    if($data==null){
        exit(json_encode([
            "code"=>$code,
            "msg"=>$str
        ],JSON_UNESCAPED_UNICODE));
    }else{
        exit(json_encode([
            "code"=>$code,
            "msg"=>$str,
            "data"=>$data
        ],JSON_UNESCAPED_UNICODE));
    }  
}
?>

请求测试:

http://blog.qianxiao.fun/wp-content/uploads/2020/05/图片-3-1024x612.png

接口演示与源码分享

演示网站:http://api.qianxiao.fun/ksvideoparse/demo.html

GitHub:https://github.com/qx0657/ksvideoparse

更多

这里的源码我没有加时间戳验证与签名验证,您可以根据自己需求自行加请求验证。

浅笑API平台:http://api.qianxiao.fun

本文所述内容与源码仅限用于学习和研究目的,不得将其用于商业或者非法用途,否则,一切后果自负。用其解析获取的视频地址,仅用作以更好的体验观看视频为目的,且您必须在下载后的24小时之内,从您的电脑或手机中彻底删除上述内容 。接口返回的一切信息请勿用作任何违反快手法律条款以及违反法律法规的行为。一切因上述行为而可能遭致的意外、疏忽、侵权及其造成的损失,作者对其概不负责,亦不承担任何法律责任。

发表评论

textsms
account_circle
email

浅笑博客

PHP实现快手视频解析接口
特别提示 本文所述内容与源码仅限用于学习和研究目的,不得将其用于商业或者非法用途,否则,一切后果自负。用其解析获取的视频地址,仅用作以更好的体验观看视频为目的,且您必须在下载…
扫描二维码继续阅读
2020-05-20