李恒道 发表于 2021-9-3 10:35:41

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

# 抓包

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

这里我们抓包

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

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

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

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

![图片.png](data/attachment/forum/202109/03/102443rwf44yu5ufrcyuec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

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

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

![图片.png](data/attachment/forum/202109/03/102559bcau978z8k9laa94.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

所以抓包开干!

通过抓包分析可知

![图片.png](data/attachment/forum/202109/03/102710ymf91zy49v9wk07b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

的返回了无水印视频

为**data**.**visionVideoDetail**.**photo**.**photoUrl**

![图片.png](data/attachment/forum/202109/03/102756kj2d2j0kzjbbbzoo.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

提交数据如下

```javascript
{"operationName":"visionVideoDetail","variables":{"photoId":"3xqmst68mjpue66","page":"detail"},"query":"query visionVideoDetail($photoId: String, $type: String, $page: String, $webPageArea: String) {\nvisionVideoDetail(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](data/attachment/forum/202109/03/102828itjsz63tyskkspp4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

```javascript
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) {\\nvisionVideoDetail(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。

```javascript
                                                    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.id===null){
                                                                  savelistid.push('null')
                                                                  zhibo++
                                                                }
                                                                else{
                                                                  if(target.list.imgUrls!==undefined&&target.list.imgUrls.length!==0){
                                                                        for(let imgindex=0;imgindex<target.list.imgUrls.length;imgindex++){
                                                                            imglist.push(target.list.imgUrls)
                                                                        }
                                                                        savelistid.push('null')
                                                                  }else{
                                                                        savelistid.push(target.list.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以及图片地址(获取列表的时候,图片会直接返回给我们)都保存起来。

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

```javascript
async function StatToGetVideo(){
    alert('已开始,不要重复点击!')
    let imgnumber=0;
    for(let index=0;index<savelistid.length;index++){
      let id=savelistid
      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是将数组中每个元素拼接起来。然后清空下载数组。

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

# 结语

撒花~

user_湫 发表于 2023-2-3 20:04:35

楼主有木有写好的代码 看不懂{:4_86:}

user_湫 发表于 2023-2-3 20:07:09

有整理好的吗?可以直接安装的 而且给的链接好像是快手直播不是快手的

【快手】https://www.kuaishou.com/profile
【快手直播】https://live.kuaishou.com/profile/

李恒道 发表于 2023-2-3 20:15:50

user_湫 发表于 2023-2-3 20:04
楼主有木有写好的代码 看不懂

不维护的了
是拿做当教程做举例的{:4_110:}
页: [1]
查看完整版本: [油猴脚本开发指南]实战fetch劫持快手无水印视频