更新日志

所有版本更新日志会记录在这里

v0.4.0(2020-03-23)

修复id转eid的一些bug

从该版本开始,爬取视频均为无水印

v0.3.0(2020-03-10)

修复一些因为用户昵称中存在windows下文件(夹)名非法字符导致os无法写入读取的bug

简单看了一点python面向对象,将核心功能提取为crawler类,降低耦合

基于crawler类,分出两个文件,一个用于直接在python环境下跑代码,另一个则用于打包好exe一键运行

提供exe版本

v0.2.0(2020-02-26)

增加对数字id自动查询转换为eid的支持

v0.1.1 (2020-02-25)

修复caption项文字保存为文件名的各种问题

增加对workType 'ksong'、'single'的支持

增加对于爬取过程中请求失败的解决方法

增加CHANGELOG.md

写在前面

代码功能如题,根据快手用户的id来爬取用户所有公开作品,包括图集和视频。

原理:其实就是利用基于chromium内核的浏览器自带的devtools对所有请求进行排查找出包含作品链接的请求,然后用代码模拟请求去获得数据,再根据url下载作品保存就行了,包括一些网站的自动注册登录、操作都可以模拟。这个其实应该算是写过爬虫的同学们都知道,我自己其实不怎么用过python,也没写过什么复杂的python项目,说的不对的还请多多包涵。如果有同学还是想让我讲一下怎么爬的,我考虑再做一期详细的,其实代码应该还是可以看得懂的2333

核心代码

废话不多说,上核心代码

def __crawl_user(self, uid):
if uid.isdigit():
uid = self.__switch_id(uid)
payload = {"operationName": "privateFeedsQuery",
"variables": {"principalId": uid, "pcursor": "", "count": 999},
 "query": "query privateFeedsQuery($principalId: String, $pcursor: String, $count: Int) {\n privateFeeds(principalId: $principalId, pcursor: $pcursor, count: $count) {\n pcursor\n list {\n id\n thumbnailUrl\n poster\n workType\n type\n useVideoPlayer\n imgUrls\n imgSizes\n magicFace\n musicName\n caption\n location\n liked\n onlyFollowerCanComment\n relativeHeight\n timestamp\n width\n height\n counts {\n displayView\n displayLike\n displayComment\n __typename\n }\n user {\n id\n eid\n name\n avatar\n __typename\n }\n expTag\n __typename\n }\n __typename\n }\n}\n"}
res = requests.post(self.__data_url, headers=self.__headers, json=payload)
works = json.loads(res.content.decode(encoding='utf-8', errors='strict'))['data']['privateFeeds']['list']
if not os.path.exists("../data"):
os.makedirs("../data")
# 这两行代码将response写入json供分析
# with open("data/" + uid + ".json", "w") as fp:
# fp.write(json.dumps(works, indent=2))
# 防止该用户在直播,第一个作品默认为直播,导致获取信息为NoneType
if works[0]['id'] is None:
works.pop(0)
name = re.sub(r'[\\/:*?"<>|\r\n]+', "", works[0]['user']['name'])
dir = "data/" + name + "(" + uid + ")/"
# print(len(works))
if not os.path.exists(dir):
os.makedirs(dir)
# if not os.path.exists(dir + ".list"):
# print("")
print("开始爬取用户 " + name + ",保存在目录 " + dir)
print(" 共有" + str(len(works)) + "个作品")
for j in range(len(works)):
self.__crawl_work(uid, dir, works[j], j + 1)
time.sleep(1)
print("用户 " + name + "爬取完成!")
print()
time.sleep(1)
'''

快手分为五种类型的作品,在作品里面表现为workType属性

其中两种图集: vertical和multiple,意味着拼接长图和多图,所有图片的链接在imgUrls里

一种单张图片: single 图片链接也在imgUrls里

K歌: ksong 图片链接一样,不考虑爬取音频...

视频: video 需要解析html获得视频链接

'''
def __crawl_work(self, uid, dir, work, wdx):
w_type = work['workType']
w_caption = re.sub(r"\s+", " ", work['caption'])
w_name = re.sub(r'[\/:*?"<>|\r\n]+', "", w_caption)[0:24]
w_time = time.strftime('%Y-%m-%d', time.localtime(work['timestamp'] / 1000))
if w_type == 'vertical' or w_type == 'multiple' or w_type == "single" or w_type == 'ksong':
 w_urls = work['imgUrls']
l = len(w_urls)
print(" " + str(wdx) + ")图集作品:" + w_caption + "," + "共有" + str(l) + "张图片")
for i in range(l):
p_name = w_time + "_" + w_name + "_" + str(i + 1) + ".jpg"
pic = dir + p_name
if not os.path.exists(pic):
r = requests.get(w_urls[i])
r.raise_for_status()
with open(pic, "wb") as f:
f.write(r.content)
print(" " + str(i + 1) + "/" + str(l) + " 图片 " + p_name + " 下载成功 √")
else:
print(" " + str(i + 1) + "/" + str(l) + " 图片 " + p_name + " 已存在 √")
elif w_type == 'video':
w_url = self.__work_url + work['id']
res = requests.get(w_url, headers=self.__headers_mobile,
params={"fid": 1841409882, "cc": "share_copylink", "shareId": "143108986354"})
html = res.text
waitreplace = work['id'] + '".*?"srcNoMark":"(.*?)"'
v_url = re.findall(waitreplace, html)
# pattern = re.compile(r"playUrl", re.MULTILINE | re.DOTALL)
# script = soup.find("script", text=pattern)
# s = pattern.search(script.text).string
# v_url = s.split('playUrl":"')[1].split('.mp4')[0].encode('utf-8').decode('unicode-escape') + '.mp4'
try:
print(" " + str(wdx) + ")视频作品:" + w_caption)
except:
print(" 这里似乎有点小错误,已跳过")
v_name = w_time + "_" + w_name + ".mp4"
video = dir + v_name
if v_url:
if not os.path.exists(video):
r = requests.get(v_url[0])
r.raise_for_status()
with open(video, "wb") as f:
f.write(r.content)
print(" 视频 " + v_name + " 下载成功 √")
else:
print(" 视频 " + v_name + " 已存在 √")
else:
print("未找到视频")
else:
print("错误的类型")

payload就是post参数,这个是在devtools的request请求底下可以找到的

其实就是解析json,然后里面有图片的url和视频的id,我注释掉的两行代码可以保存完整的json的,你可以去掉注释然后看分析保存的json

剩下的看源码吧,不难理解的

既然是编程语言区嘛,主要还是代码,https://github.com/oGsLP/kuaishou-crawler ,链接查看源码,这是编程语言区,不是工具软件区,我只是提供代码供大家学习,不是只让你用这个爬视频图片的。既然在编程语言区,相信大家多多少少还是会一点编程的,或者说这样的代码学习成本不是很大,不希望大家是伸手党,自己折腾出来的才是经验。

(ps: 从v0.3版本开始有打包好的exe提供了 >_>)

至于什么评分热心值和毕看着给吧23333,第一次发帖希望能帮到大家就好,如果代码真的帮到你了,或者用起来很舒服,可以github上给一波star什么的 link 各位看心情看心情

具体的使用说明,直接抄我github上的readme的原文了。。

kuaishou-crawler
As you can see, a crawler for kuaishou pictures and videos
Latest
Version 0.4.0 (2020-03-23)

现在已经提供exe版本一键执行 查看 | 或者查看如何运行代码 查看

Python 3.7.3
requests
json
os
BeautifulSoup
re

自v0.3.0版本开始,已用面向对象重构,核心代码在lib/crawler.py中,启动文件为crawl.py / ks.py

功能:根据用户ID来爬取快手用户的作品,包括视频和图片

在preset文件(使用exe版本忽略此文件)中一行行填写用户id,若缺少文件会自动创建(目前版本已提供自动根据数字id获取真实eid)

或者手机里点开快手用户的头像,底下会告诉你快手号的

使用时请自己用账号登录快手网站,并使用自己的cookie['headers']和didweb替换,不保证源代码中对应值可用

关于cookie的did值,你在电脑浏览器中打开快手网站登录后随便打开一个用户的视频作品,然后再地址栏中找到这一项,咱们以giao哥的第一个视频为例

因为快手官网会根据cookie,识别你是否在线,爬取的时候要将网页登录并挂着

实测快手网站的用户验证存在30-60分钟左右的有效时长,出现list index out of range时极可能是有效期已过,登录网站验证即可

暂且不知道快手官方对过多请求的处理,目前碰到的有上述验证失效,也许也会有请求达到数量会中断请求,此时注释preset中已爬取的用户id,重新开始运行脚本即可

爬取的视频暂时是带水印的(以后考虑获取无水印视频) 是无水印的 感谢@tjftjftjf提供手机抓包链接和方法

注意事项:

不考虑提供列表可选的批量下载功能

有需要的合理功能可以issue反馈,看到后会考虑是否修改

如果需要自定义自己的需求,可以拿走代码自行修改,喜欢的话给个star给个follow

本代码仅供学习使用,不可违反法律爬取视频,以及私自盗用搬运视频,后果自负

本代码仅供学习使用,不可违反法律爬取视频,以及私自盗用搬运视频,后果自负

本代码仅供学习使用,不可违反法律爬取视频,以及私自盗用搬运视频,后果自负

重要的说三遍

Run

python3环境与命令行工具

进入项目目录 cd kuaishou-crawler

安装依赖 pip install -r requirements.txt

运行,有两个版本,crawl.py为运行版本,ks.py是用于构建exe的版本,当然也可以运行

python crawl.py / python ks.py

Release

下载打包好的exe一键运行(点击download下载即可)

ks.exe

ks.7z

Future

自动根据id获取eid √

获取无水印视频 √

进一步丰富preset预设文件的可配置选项

优化代码和log √

提供便捷的打包exe √

Again