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

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

[复制链接]
  • TA的每日心情

    2022-6-4 20:51
  • 签到天数: 32 天

    [LV.5]常住居民I

    321

    主题

    2838

    帖子

    2846

    积分

    荣誉开发者

    非物质文化遗产社会摇传承人

    Rank: 10Rank: 10Rank: 10

    积分
    2846

    猫咪币纪念章

    发表于 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是将数组中每个元素拼接起来。然后清空下载数组。

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

    结语

    撒花~

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。

    发表回复

    本版积分规则

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