随着互联网经济的快速发展和自媒体行业的普及,越来越多的视频创作者加入了短视频平台,不仅快速的推动了短视频平台行业的发展,也给大众带来了更多新鲜有趣的知识和内容。
据最新数据显示,短视频用户规模为8.88亿,较2020年12月增长1440万,占网民整体的87.8%。数据显示,2021年3月,短视频APP的人均单日使用时长为125分钟,较长视频高出27分钟,且差距呈增长趋势;53.5%的短视频用户每天都会看短视频节目,这一比例较长视频(36.3%)高出17.2个百分点。
可见短视频占据了我们不少的日常生活时间,那么如何从短视频平台快速筛选到有效的信息呢,由此引入我们今天的内容,爬取快手指定博主的作品,这样可以避免我们观看短视频的时候,分散我们的注意力,也可以更高效的获取到我们想要的内容。
话不多说,直奔主题,首先打开快手网页版,选取一个指定博主的主页进行打开。主页链接如下:
https://www.kuaishou.com/profile/3xxcvi49q2r52gu
请记得选择用户登陆,出于两个方面考虑:
- 只有登陆后才能看到博主的全部视频
- 后面的视频列表获取需要使用到cookie
在浏览器打开页面后,通过 F12 调出审查元素界面,然后切到NetWork重新刷新界面。得到数据如下图:
嗯?一看这个数据,还真是瞌睡遇到枕头,这up主的数据相关信息已到手,那么切到请求信息看看需要哪些数据?
通过以上几张截图,可以看到获取up主相关信息请求所需的数据,请求地址,请求头,请求方法,请求数据格式,用户cookie以及user_id, 用户ID的获取方法也很简单明了,如图所示即用户主页最后的一串字符串即可获取。
接着往下点击,看看能否找到视频数据信息,没看到有其他的链接,接着点击graphql,嗯,这…
可以通过这个接口清楚的看到,返回的feed里面的数据好像是当前页面的视频信息,额,不好意思,用词不当,不是好像而是就是。此时的我彷佛有点飘了,来看看请求视频列表信息需要哪些数据。
关于获取视频列表请求头的信息不做过多赘述,参考获取up主相关信息的请求头。主要来说一下数据,query里面的内容是固定,我们需要关注的是红框框住的内容,可以清楚的看到userId,pcursor,page三个参数,userId刚说过了,page是个固定的字符串,关键点在于pcursor,这玩意是干嘛用的,目前还不知道。
刚才请求的只是一页的数据,当作品多的时候数据结构是什么样的呢?接着滚动下页面,发现同样发送了一个请求,请求数据如下图:
可以看到pcursor竟然有了值,那么值从哪里来的呢,通过接口返回的数据比对发现,他的值是由上一次视频列表中返回的pcursor,如下图:
那么什么时候视频数据全部加载完呢,我们一直往下拉,拉到最后没数据的时候发现pcursor的值为no_more时,则用户的视频数据已经加载完毕了,如下图 :
上面的逻辑已经分析完毕了,接下来进入编码环节,代码如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import time
import requests
import json
class KsStd(object):
ksHeaders = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)'
' Chrome/98.0.4758.82 Safari/537.36',
'content-type': 'application/json',
'Host': 'www.kuaishou.com',
'Origin': 'https://www.kuaishou.com',
'Cookie': "did=web_dd78404c06b81b5ec9934816c5068807; didv=1652079520588; _bl_uid=Opl4j2jvybvdskkb"
"gt0v8gv6dggz; kpf=PC_WEB; kpn=KUAISHOU_VISION; clientid=3; userId=1843103571; kuaishou."
"server.web_st=ChZrdWFpc2hvdS5zZXJ2ZXIud2ViLnN0EqABflkKr9RDu5xiek9c8QGs2xMA1WPfpNmrhj1yN"
"LYL1DTa9CSGXREEsvGjkZmbsTevXb9KfjuM0Loc5FfRGSpkptr07YoE0xIL9qD6wjaPbNGYSzYW2tVYGpXCiUP"
"eas8MBS_Rpf9CiTiSAGlM_ZVPlm4I-n0HLcBr8ahBkQK0CfShogzrOdqtNrmSeTYofISUwu7NfKgJiFCb7"
"Y-gHXklFhoSBmjJ_SI5Snf4O3WXJiYJKm0lIiCamuTlwoSafyegLDSa3y5gJhH50rhBp_Eonl0FPPIv2ygFMAE; "
"kuaishou.server.web_ph=5b0603df2789bb23dcd6959cda23f59ed81e"
}
ks_base_url = "https://www.kuaishou.com/graphql"
def __init__(self):
pass
# 构建公共接口请求头部
def header_data_build(self, meg_param={}):
meg_param.update(self.ksHeaders)
return meg_param
# 请求用户主页
def req_monitor(self, profile_url):
ks_user_id = profile_url.split('/')[-1]
ks_headers = self.header_data_build({'Referer': profile_url})
# upper 信息
self.ks_upper_api_data(ks_user_id, ks_headers)
# 视频信息
self.handle_video_info(ks_user_id, ks_headers)
print("程序待机中……")
# 构建快手 upper apiData
def ks_upper_api_data(self, ks_user_id, ks_headers):
ks_upper_data = {
"operationName": "visionProfile",
"variables": {
"userId": ks_user_id
},
"query": "query visionProfile($userId: String) {\n visionProfile(userId: $userId) {\n"
" result\n hostName\n userProfile {\n ownerCount {\n "
"fan\n photo\n follow\n photo_public\n __"
"typename\n }\n profile {\n gender\n user_name\n "
"user_id\n headurl\n user_text\n user_profile_bg_url\n "
"__typename\n }\n isFollowing\n __typename\n }\n __typename\n }\n}\n"
}
upper_data = requests.post(url=self.ks_base_url, headers=ks_headers, data=json.dumps(ks_upper_data)).json()
self.upper_info_detail(upper_data['data']['visionProfile']['userProfile'])
# 构建用户信息
def upper_info_detail(self, user_data):
upper_infos = {}
upper_infos['nickname'] = user_data['profile']['user_name']
upper_infos['signature'] = user_data['profile']['user_text']
upper_infos['uid'] = user_data['profile']['user_id']
upper_infos['video_count'] = user_data['ownerCount']['photo_public']
upper_infos['avatar'] = user_data['profile']['headurl']
upper_infos['attention'] = user_data['ownerCount']['follow']
upper_infos['fans'] = user_data['ownerCount']['fan']
upper_infos['create_time'] = int(time.time())
print("用户信息", upper_infos)
# 构建快手 video apiData
def ks_video_api_data(self, ks_user_id, ks_headers, pcursor_index=""):
ks_video_data = {
"operationName": "visionProfilePhotoList",
"variables": {
"userId": ks_user_id,
"pcursor": pcursor_index,
"page": "profile"
},
"query": "fragment photoContent on PhotoEntity {\n id\n duration\n "
"caption\n likeCount\n viewCount\n realLikeCount\n coverUrl\n "
"photoUrl\n photoH265Url\n manifest\n manifestH265\n videoResource\n "
"coverUrls {\n url\n __typename\n }\n timestamp\n expTag\n "
"animatedCoverUrl\n distance\n videoRatio\n liked\n stereoType\n "
"profileUserTopPhoto\n __typename\n}\n\nfragment feedContent on Feed {\n "
"type\n author {\n id\n name\n headerUrl\n following\n "
"headerUrls {\n url\n __typename\n }\n __typename\n }\n "
"photo {\n ...photoContent\n __typename\n }\n canAddComment\n "
"llsid\n status\n currentPcursor\n __typename\n}\n\nquery "
"visionProfilePhotoList($pcursor: String, $userId: String, $page: String, "
"$webPageArea: String) {\n visionProfilePhotoList(pcursor: $pcursor, userId: "
"$userId, page: $page, webPageArea: $webPageArea) {\n result\n llsid\n "
"webPageArea\n feeds {\n ...feedContent\n __typename\n }\n "
"hostName\n pcursor\n __typename\n }\n}\n"
}
video_data = requests.post(url=self.ks_base_url, headers=ks_headers, data=json.dumps(ks_video_data)).json()
return video_data['data']['visionProfilePhotoList']
# 构建 视频信息
def handle_video_save(self, video_datas):
for videoItem in video_datas:
video_whv = videoItem['photo']['manifest']['adaptationSet'][0]['representation'][0]
video_title_data = videoItem['photo']['caption'].split()
video_save_data = {
'title': video_title_data[0],
'desc': videoItem['photo']['caption'],
'share_url': videoItem['photo']['photoUrl'],
'digg_count': videoItem['photo']['likeCount'],
'play_count': videoItem['photo']['viewCount'],
'uid': videoItem['author']['id'],
'music_url': videoItem['photo']['photoH265Url'],
'video_url': videoItem['photo']['photoUrl'],
'duration': videoItem['photo']['duration'],
'width': video_whv['width'],
'ratio': video_whv['qualityType'],
'height': video_whv['height'],
'origin_cover': videoItem['photo']['coverUrl'],
'create_time': videoItem['photo']['timestamp'] / 1000,
}
tags = ""
for index, tag in enumerate(video_title_data[1:]):
if index == len(video_title_data[1:]) - 1:
tags += tag.replace("#", "")
else:
tags += tag.replace("#", "") + ";"
video_save_data.update({'tags': tags})
try:
video_save_data.update({'aweme_id': videoItem['photo']['videoResource']['hevc']['videoId']})
except Exception as e:
video_save_data.update({'aweme_id': "hexId888nonevideoId112"})
print(video_save_data)
# 保存视频方法自行扩展
# 构建 视频请求 信息 && 合并为一起
def handle_video_info(self, ks_user_id, ks_headers):
the_video_datas = []
page_index = ""
while page_index != 'no_more':
video_data = self.ks_video_api_data(ks_user_id, ks_headers, page_index)
page_index = video_data['pcursor']
the_video_datas += video_data['feeds']
time.sleep(20)
print(the_video_datas)
self.handle_video_save(the_video_datas)
if __name__ == "__main__":
profile_url = "https://www.kuaishou.com/profile/3xs8937rkv7u256"
ks = KsStd()
ks.req_monitor(profile_url)