引言
平时工作闲的时候,会刷刷抖音。于是想爬取一下抖音的视频。网上搜索资料,发现多数都是爬取固定用户的主页视频。我想要的效果是爬取首页的随机视频和评论,于是自己抓包分析,实现效果。在做项目的过程中遇到了一些问题,在此记录下来。
项目地址
抖音爬虫
如果有帮助的话,记得给个star哦
思路
- 爬取首页随机视频的作者、ID、名称、点赞数、评论数、分享数、背景音乐作者、名称和无水印视频下载地址
- 爬取对应视频的前十条评论的作者、内容、点赞数、评论时间。如果有回复别人的情况,还要爬取被回复的用户名、内容、点赞数和评论时间
- 下载无水印视频(已经实现,但考虑到存储空间不足,已注释,暂不使用)
问题
1.Fiddler抓包,手机上设置代理后,无法上网
按照网上教程,用Fiddler抓取iphone手机包,发现不设置代理,可以正常上网,设置了代理之后,就上不了网了。https的请求都会失败,提示错误信息为Failure SSLHandshake: Received fatal alert: unknown_ca 和You may need to configure your browser or application to trust the Charles Root Certificate. 但是相关设置都正确:电脑上安装了Fiddler 的根证书,并且设置了始终信任,然后手机上也安装了描述文件,一切都按正常程序走的, 但是错误始终无法解决。
原因:
虽然Fiddler的根证书已经在安装列表中显示,但它是被关闭的。在iOS 10.3之前,当你将安装一个自定义证书,iOS会默认信任,不需要进一步的设置。而iOS 10.3之后,安装新的自定义证书默认是不受信任的。如果要信任已安装的自定义证书,需要手动打开开关以信任证书。
解决:
设置->通用->关于本机->证书信任设置-> 找到DO_NOT_TRUST_FiddlerRoot然后信任该证书即可。
2.接口url参数加密
抓包分析,找到了首页视频流接口url:https://aweme-eagle.snssdk.com/aweme/v1/feed/
url参数如下(样例):
Name | Value | Description |
---|---|---|
iid | 51050168070 | 设备信息 |
idfa | 887748FC-0DA1-4984-B87F-F2FC9AC5D14B | 设备信息 |
device_type | iPhone5,2 | 设备信息 |
os_version | 10.3.3 | 设备信息 |
screen_width | 640 | 设备信息 |
vid | AECABC99-0F66-4086-86BC-EC4E01B4DEA1 | 设备信息 |
device_id | 59415024289 | 设备信息 |
os_api | 18 | 设备信息 |
device_platform | iphone | 设备信息 |
openudid | 75a4bc255848cd7901e166e5c168b23e3e9394a8 | 设备信息 |
version_code | 3.1.0 | app信息 |
aid | 1128 | app信息 |
app_name | aweme | app信息 |
build_number | 31006 | app信息 |
app_version | 3.1.0 | app信息 |
channel | App Store | app信息 |
pass-region | 1 | 常量 |
js_sdk_version | 1.3.0.1 | 常量 |
ac | mobile | 常量 |
count | 6 | 视频数 |
feed_style | 0 | 常量 |
filter_warn | 0 | 常量 |
max_cursor | 0 | 常量 |
min_cursor | 0 | 常量 |
pull_type | 0 | 常量 |
type | 0 | 常量 |
volume | 0.06 | 常量 |
mas | 0161b6c4a20babcf6829e30950a9f3a577adb04abc0c6da0eeca91 | 加密参数 |
as | a105e18ff4e32b1a102320 | 加密参数 |
ts | 1542462004 | 加密参数 |
其中mas、as、ts为加密参数,其余的设备和app一旦确定下来,就都是常量了。三个加密参数需要一步一步分析JS文件才能得出这三个加密参数如何生成的。
解决:
有万能的GitHub,在此感谢AppSign提供加密签名服务,参照该文档,很快可以得到三个加密参数,和其他参数一起给接口就可以拿到返回的视频流数据了。
同样,评论接口加密参数也是一样的,相同的办法就可以拿到评论数据了。
评论url:https://aweme.snssdk.com/aweme/v2/comment/list/
url参数如下(样例):
Name | Value | Description |
---|---|---|
iid | 51050168070 | 设备信息 |
idfa | 887748FC-0DA1-4984-B87F-F2FC9AC5D14B | 设备信息 |
device_type | iPhone5,2 | 设备信息 |
os_version | 10.3.3 | 设备信息 |
screen_width | 640 | 设备信息 |
vid | AECABC99-0F66-4086-86BC-EC4E01B4DEA1 | 设备信息 |
device_id | 59415024289 | 设备信息 |
os_api | 18 | 设备信息 |
device_platform | iphone | 设备信息 |
openudid | 75a4bc255848cd7901e166e5c168b23e3e9394a8 | 设备信息 |
version_code | 3.1.0 | app信息 |
aid | 1128 | app信息 |
app_name | aweme | app信息 |
build_number | 31006 | app信息 |
app_version | 3.1.0 | app信息 |
channel | App Store | app信息 |
pass-region | 1 | 常量 |
js_sdk_version | 1.3.0.1 | 常量 |
ac | mobile | 常量 |
aweme_id | 6624665048084122888 | 视频ID |
count | 10 | 评论数 |
cursor | 0 | 常量 |
insert_ids | 0 | 常量 |
mas | 0161b6c4a20babcf6829e30950a9f3a577adb04abc0c6da0eeca91 | 加密参数 |
as | a105e18ff4e32b1a102320 | 加密参数 |
ts | 1542462004 | 加密参数 |
3.数据库关闭问题
原本打算程序放到服务器上一直跑的,想了一下由于没有使用代理池,长时间跑可能会被封IP,而且一直跑对AppSign提供加签的服务器也有一定的压力,最后决定每天爬取1万条视频数据。那么这就涉及到数据库关闭的问题。
因为我采用的是爬取和存储是分开的线程,而且考虑到减少加签服务器的压力,爬取线程速度较慢,存储线程
速度快,并且设置两个线程的daemon=True(会随着主线程的结束而结束)。这时候就不能通过queue.join()来控制主线程是否阻塞。因为queue的get速度比put速度要快,这样主程序运行一段时间就会因为queue中待处理的数据为0,从而使queue.join()放通,导致主程序结束,子线程也会随之结束,爬取视频达不到1万条。
解决:
在爬取线程的1万条数据完成之后手动给queue发送一个结束标志,存储线程收到结束标志后不做处理,将其发送给主线程,主线程里循环检查queue中数据是否为结束标志,如果是,则关闭数据库连接,跳出循环,结束主程序,子线程也同时结束,程序终止。代码如下:
def put_into_queue(params, queue): # 获取接口返回的视频和评论数据,放进队列
i = 0
while i < 10000: # 每天抓取10000个左右视频,因为get_video_info()一次返回6个视频数据,最后爬取的视频数不是1万整
video_params = get_video_params(params)
for video_data in get_video_info(video_params):
if video_data['result'] == 'success':
i += 1
print('today video num:', i)
video_data['type'] = 'video'
queue.put_nowait(video_data)
comment_params = get_comment_params(params, video_data['video_id'])
for comment_data in get_comment_info(comment_params):
if comment_data['result'] == 'success':
comment_data['type'] = 'comment'
queue.put_nowait(comment_data)
elif comment_data['result'] == 'error':
continue
# queue.put_nowait(comment_data)
# break
elif video_data['result'] == 'error':
continue
# queue.put_nowait(video_data)
# break
time.sleep(10) # 加密签名为github开源服务,作者要求禁止高并发请求访问公用服务器,所以降低请求频率
data = {}
data = {'result': 'success', 'type': 'finished'} # 抓取完成标志
queue.put_nowait(data)
def get_from_queue(queue, db): # 获取队列里的视频和评论数据,保存到数据库和下载视频
while True:
try:
data = queue.get_nowait()
if data['result'] == 'success':
if data['type'] == 'video':
# download(data['filename'], data['download_url']) # 1w个视频大约需要20G,因存储空间不足,暂不下载
db.save_one_data_to_video(data)
elif data['type'] == 'comment':
db.save_one_data_to_comment(data)
elif data['type'] == 'finished': # 抓取完成后子线程退出循环
queue.put_nowait(data) # 告诉主线程抓取完成
break
# elif data['result'] == 'error':
# queue.put_nowait(data)
# break
except:
print("queue is empty wait for a while")
time.sleep(2)
if __name__ == '__main__':
......
......
......
queue = Queue()
Thread(target=put_into_queue, args=(params, queue), daemon=True).start()
Thread(target=get_from_queue, args=(queue, db), daemon=True).start()
while True: # 该循环是用来判断何时关闭数据库
try:
data = queue.get_nowait()
# if data['result'] == 'error':
# db.close()
# break
if data['type'] == 'finished':
db.close()
break
except:
print('spidering...')
time.sleep(10)
总结
本项目大部分的时间花费在解决问题1和2上。最开始抓不到包,怎么都解决不了,查了好长时间才发现是这个问题。三个加密参数也卡了很长时间,最后发现github上有提供加签服务的。以后遇到些复杂的问题,先去github上看看有没有现成的东西。
P.S.
项目里还实现了一个附加功能:从抖音的分享链接中下载无水印视频。这个功能的实现,下篇文章会讲到。