上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

[油猴脚本开发指南]实战fetch劫持快手无水印视频

[复制链接]

118

主题

906

帖子

529

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
529
发表于 2021-9-3 10:35:41 | 显示全部楼层 | 阅读模式

抓包

地址https://live.kuaishou.com/profile/3x72uw4df7wdq8q?fid=43648984

这里我们抓包

可以发现地址都是https://**live.kuaishou.com**/live_graphql

通过请求内容的operationName的内容控制获取的数据

根据测试可知,首屏数据的名字为privateFeedsQuery,其他为publicFeedsQuery

返回的数据我大概看了下,只有封面图片,没有视频,点开

图片.png

发现也是存在id的,所以这里我们应该另想办法

这时候我们发现通过手机分享的单个视频url是无水印的

宝贝 看完了吗~ "夹子音 "高能夹子音 "夹子音变装 https://v.kuaishou.com/dPkfN2 复制此消息,打开【快手】直接观看!

图片.png

所以抓包开干!

通过抓包分析可知

图片.png

的返回了无水印视频

data.visionVideoDetail.photo.photoUrl

图片.png

提交数据如下

{"operationName":"visionVideoDetail","variables":{"photoId":"3xqmst68mjpue66","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      croppedPhotoUrl\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    danmakuSwitch\n    __typename\n  }\n}\n"}

图片.png

这里可以看到photoid是变化的,其他应该没有什么变化,photoid在我们之前的第一个页面可以看到,那我们可以先放下来,构造一个获取无水印的获取函数。

function fuckkuaishouvideo(id,index){
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            url:"https://www.kuaishou.com/graphql",
            method :"POST",
            data:'{"operationName":"visionVideoDetail","variables":{"photoId":"'+id+'","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      croppedPhotoUrl\\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    danmakuSwitch\\n    __typename\\n  }\\n}\\n"}'
            ,headers: {
                "Content-type": "application/json"
            },
            onload:function(xhr){
                let obj=JSON.parse(xhr.responseText)
                let res=obj.data.visionVideoDetail.photo
                if(res===null){
                    console.log('失败的id"',id,index)
                    resolve('success')
                }
                downloadurl.push(res.photoUrl)
                resolve('success')
            }
        });
    })

}

这里需要注意的是如果我们把获取的数据直接放到xhr内\n会出现换行的情况,所以我们需要对这类字符进行转义,在记事本里把\n改成\\n,这样提交的时候会把\\n转义成\n。

                                                    function(data){
                                                        let list=JSON.parse(data)
                                                        console.log('获取了list',list)
                                                        let target=null
                                                        if(list.data.privateFeeds!==undefined){
                                                            target=list.data.privateFeeds
                                                        }
                                                        if(list.data.publicFeeds!==undefined){
                                                            target=list.data.publicFeeds
                                                        }
                                                        if(target!==null){

                                                            for(let index=0;index<target.list.length;index++){
                                                                if(target.list[index].id===null){
                                                                    savelistid.push('null')
                                                                    zhibo++
                                                                }
                                                                else{
                                                                    if(target.list[index].imgUrls!==undefined&&target.list[index].imgUrls.length!==0){
                                                                        for(let imgindex=0;imgindex<target.list[index].imgUrls.length;imgindex++){
                                                                            imglist.push(target.list[index].imgUrls[imgindex])
                                                                        }
                                                                        savelistid.push('null')
                                                                    }else{
                                                                        savelistid.push(target.list[index].id)
                                                                    }
                                                                }


                                                            }
                                                        }
                                                        let enddownload=document.querySelector('.enddownload')
                                                        console.log('zhibo',zhibo,savelistid.length,savelistid.length-zhibo)
                                                        if(enddownload!==null){
                                                            enddownload.innerHTML=''+(savelistid.length-zhibo)
                                                        }
                                                        resolve(data)
                                                    }

然后继续像知乎劫持一样,打一波fetch劫持,这里因为名字不同,但是内容是一致的,所以我先判断前缀部分是什么,然后再进行统一处理。

并将所有的id以及图片地址(获取列表的时候,图片会直接返回给我们)都保存起来。

然后写当我们点击按钮的时候的代码

async function StatToGetVideo(){
    alert('已开始,不要重复点击!')
    let imgnumber=0;
    for(let index=0;index<savelistid.length;index++){
        let id=savelistid[index]
        let result='success'
        if(id!=='null'){
            result= await fuckkuaishouvideo(id,index)
        }else{
            imgnumber++;
        }

        if(result==='success'){
            let startdownload=document.querySelector('.startdownload')
            if(startdownload!==null){
                startdownload.innerHTML=''+(index+1)
            }

        }
    }
    GM_setClipboard([...downloadurl,...imglist].join('\n'))
    alert(savelistid.length+'个共成功'+(downloadurl.length+imgnumber)+'可能存在图片')
    downloadurl=[]

}

代码也是非常简单的,就是通过同步promise,一个一个进行post获取无水印地址,然后存储到数组中,最后通过Gm_setClipboard设置到剪辑板上

...downloadurl是一个解构数组的运算符,join是将数组中每个元素拼接起来。然后清空下载数组。

那么快手批量下载的脚本就做完了

结语

撒花~

发表回复

本版积分规则

快速回复 返回顶部 返回列表