序言:
排版可能不是很好看,如有问题请进行在评论区讨论,本系列文章语言采用typescript或Java来进行举例,本系列不止这一篇,这是第一篇。
这是我的写的小程序,有空可以看看,免费,去水印的功能也在上面
分享链接采用APP端分享功能,如下:
@神童可心 发了一个快手作品,一起来看!https://v.kuaishou.com/9AYaFk 复制此消息,打开【快手】直接观看!
上面分享的内容有用的是 https://v.kuaishou.com/9AYaFk。
我用的Edge浏览器,打开浏览器,按下F12,转到网络那一栏,勾选保留日志和禁用缓存。
如果没有网络这一栏,在这里找到点击就可打开
打开刚刚分享的链接,会发现其中有一个链接返回了视频的数据
//请求地址
https://video.kuaishou.com/graphql
//请求方式
POST
//请求参数
请求参数 | 示例 |
---|---|
variables | {photoId: “3xwgkwpq8hxnvji”, page: “detail”} |
operationName | visionVideoDetail |
query | query visionVideoDetail($photoId: String, $type: String, $page: String, $webPageArea: String) {↵ visionVideoDetail(photoId: $photoId, type: $type, page: $page, webPageArea: $webPageArea) {↵ status↵ type↵ author {↵ id↵ name↵ following↵ headerUrl↵ __typename↵ }↵ photo {↵ id↵ duration↵ caption↵ likeCount↵ realLikeCount↵ coverUrl↵ photoUrl↵ liked↵ timestamp↵ expTag↵ llsid↵ viewCount↵ videoRatio↵ stereoType↵ manifest {↵ mediaType↵ businessType↵ version↵ adaptationSet {↵ id↵ duration↵ representation {↵ id↵ defaultSelect↵ backupUrl↵ codecs↵ url↵ height↵ width↵ avgBitrate↵ maxBitrate↵ m3u8Slice↵ qualityType↵ qualityLabel↵ frameRate↵ featureP2sp↵ hidden↵ disableAdaptive↵ __typename↵ }↵ __typename↵ }↵ __typename↵ }↵ __typename↵ }↵ tags {↵ type↵ name↵ __typename↵ }↵ commentLimit {↵ canAddComment↵ __typename↵ }↵ llsid↵ __typename↵ }↵} |
以上都是请求参数
发现 在variables这一栏有一个photoId这个参数。
没错,这个参数就是视频的参数。
这个参数怎么获取呢?
很简单就在你访问这个分享链接的时候,返回了一个302跳转地址,在响应头标签Location里
你会发现这个地址中 photo/ 后面是不是和上面请求的photoId很相似,就是它,拿到它就行了。
在第一步中返回的参数里 data.visionVideoDetail.photo.photoUrl就是我们要拿取的无水印视频,在浏览器打开试试把。
链接:
https://v1.kwaicdn.com/upic/2020/11/15/12/BMjAyMDExMTUxMjU3MTNfMzc2MTcyMDM2XzM5MjY0MTkyNjQ0XzJfMw==_b_Bc75aa7f7089fc157048b515be3d59a6c.mp4?pkey=AAXMLwNBQ2cjoXw0k9Ycji6YwiqCTJrO1jOxKYkj4_mqdmzrx4RjBaDuhDE0e1sE7_DV1HoXa31989DrYVx5Pv0D18HNE6CXuyaElWonPTE0BmGamr_a6VuPvUGydJxw6mM&tag=1-1618053250-xpcwebdetail-0-jncyvtgaz7-7103eae7524e6753&clientCacheKey=3xwgkwpq8hxnvji_b.mp4&tt=b&di=70201f70&bp=10004
到这里 教程就差不多结束了,但是如果你在服务器上进行操作的话,有可能会出现滑块验证,这是快手为了检测是不是机器,这里的话 我们进行一些处理
如上图,,经过我的测试,Cookie中我们要携带did这个参数过去访问,不然的话会遭遇滑块验证。如果问题重复出现,请把浏览器cookie数据清楚,让其重新生成就可以了。
好了,到最后环节了,这里我用的是typescript写法,很简单
kuaishou.ts
import {
result } from "../routers/response";
import {
formatDate } from "../util/date";
import {
http } from "../util/http";
import {
logger } from "../util/log";
import {
Video } from "./video";
/**
* 快手
*/
export class KuaiShou extends Video {
async unWaterMarker(url: string): Promise<string> {
try {
let headers = {
'Content-Type': 'application/json',
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML,like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 ",
"Cookie":'这里填上你的cookie'
}
let locatioUrl = await http.location(url, headers, null);
let photoReg: RegExp = /\/photo\/(\w+)?/
let photoId = locatioUrl.match(photoReg)![1]
}
// let data = { "operationName": "visionVideoDetail", "variables": { "photoId": photoId, "page": "detail" }, "query": "query visionVideoDetail($photoId: String, $type: String, $page: String, $webPageArea: String) {\n visionVideoDetail(photoId: $photoId, type: $type, page: $page, webPageArea: $webPageArea) {\n status\n type\n author {\n id\n name\n following\n headerUrl\n __typename\n }\n photo {\n id\n duration\n caption\n likeCount\n realLikeCount\n coverUrl\n photoUrl\n liked\n timestamp\n expTag\n llsid\n viewCount\n videoRatio\n stereoType\n manifest {\n mediaType\n businessType\n version\n adaptationSet {\n id\n duration\n representation {\n id\n defaultSelect\n backupUrl\n codecs\n url\n height\n width\n avgBitrate\n maxBitrate\n m3u8Slice\n qualityType\n qualityLabel\n frameRate\n featureP2sp\n hidden\n disableAdaptive\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n tags {\n type\n name\n __typename\n }\n commentLimit {\n canAddComment\n __typename\n }\n llsid\n __typename\n }\n}\n" }
let data = {
"operationName": "visionVideoDetail",
"variables": {
"photoId": photoId, "page": "selected" },
"query": "query visionVideoDetail($photoId: String, $type: String, $page: String) {\n " +
"visionVideoDetail(photoId: $photoId, type: $type, page: $page) {\n status\n type\n " +
"author {\n id\n name\n following\n headerUrl\n __typename\n }\n " +
" photo {\n id\n duration\n caption\n likeCount\n realLikeCount\n " +
" coverUrl\n photoUrl\n liked\n timestamp\n expTag\n llsid\n " +
" __typename\n }\n tags {\n type\n name\n __typename\n }\n " +
"commentLimit {\n canAddComment\n __typename\n }\n llsid\n __typename\n " +
"}\n}\n "
}
let base_url = `https://video.kuaishou.com/graphql`
let res = await http.post(base_url, headers, data, true);
let body = JSON.parse(JSON.stringify(res))
logger('kuaishou', `${
formatDate()}\t${
JSON.stringify(res)}`)
let photo = body.data.visionVideoDetail.photo
let videoUrl = photo.photoUrl
let videoTitle = photo.caption
return result(videoUrl, videoTitle)
} catch (error) {
logger('kuaishou', `${
formatDate()}\t${
error}`)
return ''
}
}
}
video.ts
export abstract class Video {
abstract unWaterMarker(url: string): Promise<string>;
}
http.ts
import request from 'request'
export class http {
/**
* Get请求
*/
public static get(url: string, headers: any, data: any): Promise<string> {
return new Promise((resolve) => {
request({
url: url,
method: 'GET',
qs: data,
headers: headers
}, (err, res, body) => {
if (err) {
resolve('')
}
if (res.statusCode = 200) {
resolve(body)
} else {
resolve('')
}
});
});
}
public static location(url: string, headers: any, data: any): Promise<string> {
return new Promise((resolve) => {
request({
url: url,
method: 'GET',
qs: data,
headers: headers
}, (err, res, body) => {
if (err) {
resolve('')
}
if (res.statusCode = 200 || 301 || 302) {
resolve(res.request.uri.href)
} else {
resolve('')
}
});
});
}
/**
* POST 请求
*/
public static post(url: string, headers: any, data: any, json: boolean): Promise<string> {
return new Promise((resolve) => {
request({
url: url,
method: 'POST',
json: json,
body: data,
headers: headers
}, (err, res, body) => {
if (err) {
resolve('')
}
if (res.statusCode = 200) {
resolve(body)
}
});
});
}
}
response.ts
export function result(url: string, title: string): any {
return {
"url": url,
"title": title
}
}
代码可能写的不怎么样,各位大佬轻喷,也是作为参考用,谢谢大家。