Python批量爬取抖音APP无水印视频

一、前言

好久没有写文章了,因为最近一直备受小论文的折磨,今天小论文初稿终于写差不多了。看着好几天都没有更新的网站有些心疼,翻了翻爬虫代码存货,决定把抖音APP视频批量下载的代码拿出来水个文章。

二、实战背景

抖音越来越火,感觉它有毒,越刷越上瘾,总感觉下一个视频一定会更精彩,根本停不下来。想将抖音里喜欢的小哥哥/小姐姐的视频全部存到电脑硬盘里该如何操作?不想有抖音的视频水印该如何处理?

**当初写完代码的截屏:****

三、实战

1、带水印视频下载

先说说带水印的视频如何抓去吧。在定好爬取目标的时候,我们应该知道自己需要那些步骤完成这项任务。比如本文中提到的任务:抖音APP固定用户的视频批量下载。

思考过程:

  • 想要批量下载视频首先要获得这些视频的链接;

  • 想要获得这些视频链接可以通过用户的主页进行查看,想进用户主页,我得知道用户主页链接;

  • 用户主页链接可以通过抖音APP的搜索功能获取,那么搜索功能接口如何获取?当然是抓包看看喽!

瞧,这样思考下来,问题是不是梳理的很清楚?

搜索接口:

那么接下来就是抓包分析了,抓包过程请自行尝试。步骤是这样的:

  • 配置好Fiddler,即确定Fiddler可以对手机APP进行抓包;

  • 在手机APP搜索框中输入用户信息,点击搜索;

  • 在Fiddler找到搜索接口;

  • 分析这个接口传递参数规则;

  • 写代码生成相应查询接口。

通过分析你会发现,我们通过搜索接口返回的JSON数据可以找到用户主页信息,接下里用同样的方法抓取主页用户信息再分析一波,这时候就遇到问题了,你会发现用户主页链接使用了as和cp参数进行了加密,这该如何是好?比如链接如下: https://aweme.snssdk.com/aweme/v1/aweme/post/?user_id=63386731255&max_cursor=0&count=20...&as=a18575a0311bfa0c2d

上述链接省略号部分是一些手机信息,这部分不是必须参数,可以省略。user_id是用户id可以通过上个搜索接口获取,count是用户视频数量,同样可以通过上个搜索接口获取。那最后的as和cp参数怎么办?

我没有逆向抖音APP,就是小小测试了一下,看看能不能绕过这个加密接口?抖音APP自带视频分享功能,分享链接格式如下: https://www.douyin.com/share/video/6511132370416962829/?region=CN...share_iid=28037626243

中间参数都不重要,在此省略。www.douyin.com域名下存放的是分享的视频,那么这个用户主页信息是否可以通过这个域名进行访问呢?小小测试一下你会发现,完全没有问题! https://www.douyin.com/aweme/v1/aweme/post/?user_id=63386731255&max_cursor=0&count=20

这就是没有加密的接口,惊不惊喜,意不意外?根据这个用户主页接口,我们就可以轻松获取用户主页所有的视频链接了。

2、无水印视频下载

方法一:

无水印视频下载很简单,有一个通用的方法,就是使用去水印平台即可。

我使用的去水印平台是:http://douyin.iiilab.com/

在输入框中输入视频链接点击视频解析,就可以获得无水印视频链接。

这个网站当初我写代码的时候是好使的,当初用这个网站下了一些无水印视频,不过写这篇文章的时候发现这个取水印平台无法正常解析了,等它修复好了再用这个功能吧。

这个平台不仅包括抖音视频去水印,还支持火山、快手、陌陌、美拍等无水印视频。所以做一个这个网站的接口还是很合适的。

简单测试了一下,这个网站的API是需要付费解析的,如果通过模拟请求的方式有些困难,因此决定上浏览器模拟器Splinter。

Splinter是个好东西,跟Selenium使用类似,它的配置可以参考我的早期Selenium文章:http://blog.csdn.net/c406495762/article/details/72331737

Splinter有个很详细的英文文档:http://splinter.readthedocs.io/en/latest/

这里使用方法就不累述,不过有一点可以说的是,我们可以配置headless参数,来将Splinter配置为无头浏览器,啥事无头浏览器呢?就是运行Splinter不调出浏览器界面,直接在后台模拟各种请求,很是方便。

这部分的代码很简单,无非就是填充元素,确定解析按钮位置,点击按钮,获取视频下载链接即可。这点小问题,就自行分析吧。

整体代码:

# -*- coding:utf-8 -*-  
from splinter.driver.webdriver.chrome import Options, Chrome  
from splinter.browser import Browser  
from contextlib import closing  
import requests, json, time, re, os, sys, time  
from bs4 import BeautifulSoup  

class DouYin(object):  
    def __init__(self, width = 500, height = 300):  
        """  
        抖音App视频下载  
        """  
        # 无头浏览器  
        chrome_options = Options()  
        chrome_options.add_argument('user-agent="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"')  
        self.driver = Browser(driver_name='chrome', executable_path='D:/chromedriver', options=chrome_options, headless=True)  

    def get_video_urls(self, user_id):  
        """  
        获得视频播放地址  
        Parameters:  
            user_id:查询的用户ID  
        Returns:  
            video_names: 视频名字列表  
            video_urls: 视频链接列表  
            nickname: 用户昵称  
        """  
        video_names = []  
        video_urls = []  
        unique_id = ''  
        while unique_id != user_id:  
            search_url = 'https://api.amemv.com/aweme/v1/discover/search/?cursor=0&keyword=%s&count=10&type=1&retry_type=no_retry&iid=17900846586&device_id=34692364855&ac=wifi&channel=xiaomi&aid=1128&app_name=aweme&version_code=162&version_name=1.6.2&device_platform=android&ssmix=a&device_type=MI+5&device_brand=Xiaomi&os_api=24&os_version=7.0&uuid=861945034132187&openudid=dc451556fc0eeadb&manifest_version_code=162&resolution=1080*1920&dpi=480&update_version_code=1622' % user_id  
            req = requests.get(url = search_url, verify = False)  
            html = json.loads(req.text)  
            aweme_count = html['user_list'][0]['user_info']['aweme_count']  
            uid = html['user_list'][0]['user_info']['uid']  
            nickname = html['user_list'][0]['user_info']['nickname']  
            unique_id = html['user_list'][0]['user_info']['unique_id']  
        user_url = 'https://www.douyin.com/aweme/v1/aweme/post/?user_id=%s&max_cursor=0&count=%s' % (uid, aweme_count)  
        req = requests.get(url = user_url, verify = False)  
        html = json.loads(req.text)  
        i = 1  
        for each in html['aweme_list']:  
            share_desc = each['share_info']['share_desc']  
            if '抖音-原创音乐短视频社区' == share_desc:  
                video_names.append(str(i) + '.mp4')  
                i += 1  
            else:  
                video_names.append(share_desc + '.mp4')  
            video_urls.append(each['share_info']['share_url'])  

        return video_names, video_urls, nickname  

    def get_download_url(self, video_url):  
        """  
        获得带水印的视频播放地址  
        Parameters:  
            video_url:带水印的视频播放地址  
        Returns:  
            download_url: 带水印的视频下载地址  
        """  
        req = requests.get(url = video_url, verify = False)  
        bf = BeautifulSoup(req.text, 'lxml')  
        script = bf.find_all('script')[-1]  
        video_url_js = re.findall('var data = \[(.+)\];', str(script))[0]  
        video_html = json.loads(video_url_js)  
        download_url = video_html['video']['play_addr']['url_list'][0]  
        return download_url  

    def video_downloader(self, video_url, video_name, watermark_flag=False):  
        """  
        视频下载  
        Parameters:  
            video_url: 带水印的视频地址  
            video_name: 视频名  
            watermark_flag: 是否下载不带水印的视频  
        Returns:  
            无  
        """  
        size = 0  
        if watermark_flag == True:  
            video_url = self.remove_watermark(video_url)  
        else:  
            video_url = self.get_download_url(video_url)  
        with closing(requests.get(video_url, stream=True, verify = False)) as response:  
            chunk_size = 1024  
            content_size = int(response.headers['content-length'])   
            if response.status_code == 200:  
                sys.stdout.write('  [文件大小]:%0.2f MB\n' % (content_size / chunk_size / 1024))  

                with open(video_name, "wb") as file:    
                    for data in response.iter_content(chunk_size = chunk_size):  
                        file.write(data)  
                        size += len(data)  
                        file.flush()  

                        sys.stdout.write('  [下载进度]:%.2f%%' % float(size / content_size * 100) + '\r')  
                        sys.stdout.flush()  


    def remove_watermark(self, video_url):  
        """  
        获得无水印的视频播放地址  
        Parameters:  
            video_url: 带水印的视频地址  
        Returns:  
            无水印的视频下载地址  
        """  
        self.driver.visit('http://douyin.iiilab.com/')  
        self.driver.find_by_tag('input').fill(video_url)  
        self.driver.find_by_xpath('//button[@class="btn btn-default"]').click()  
        html = self.driver.find_by_xpath('//div[@class="thumbnail"]/div/p')[0].html  
        bf = BeautifulSoup(html, 'lxml')  
        return bf.find('a').get('href')  

    def run(self):  
        """  
        运行函数  
        Parameters:  
            None  
        Returns:  
            None  
        """  
        self.hello()  
        user_id = input('请输入ID(例如40103580):')  
        video_names, video_urls, nickname = self.get_video_urls(user_id)  
        if nickname not in os.listdir():  
            os.mkdir(nickname)  
        print('视频下载中:共有%d个作品!\n' % len(video_urls))  
        for num in range(len(video_urls)):  
            print('  解析第%d个视频链接 [%s] 中,请稍后!\n' % (num+1, video_urls[num]))  
            if '\\' in video_names[num]:  
                video_name = video_names[num].replace('\\', '')  
            elif '/' in video_names[num]:  
                video_name = video_names[num].replace('/', '')  
            else:  
                video_name = video_names[num]  
            self.video_downloader(video_urls[num], os.path.join(nickname, video_name))  
            print('\n')  

        print('下载完成!')  

    def hello(self):  
        """  
        打印欢迎界面  
        Parameters:  
            None  
        Returns:  
            None  
        """  
        print('*' * 100)  
        print('\t\t\t\t抖音App视频下载小助手')  
        print('\t\t作者:Jack Cui')  
        print('*' * 100)  


if __name__ == '__main__':  
    douyin = DouYin()  
    douyin.run()  

方法二:

这个方法是通过网友@羽葵的反馈得知的,对下载链接直接修改即可得到无水印下载链接。 download_url = video_html['video']['play_addr']['url_list'][0].replace('playwm','play')

方法简单粗暴,很好用。好处就是处理速度飞快,缺点是这种方法通用性不强,不同视频发布平台的打码方法可能有不同,需要自行分析。

作者:JackCui
原文链接:http://cuijiahua.com/blog/2018/03/spider-5.html



✄------------------------------------------------

原文地址:https://mp.weixin.qq.com/s/RFZywRAWcu4BS2ukrVKCSA

转载请标明来之:阿猫学编程
更多教程:阿猫学编程-python基础教程

所有评论

如果对文章有异议,请加qq:1752338621