抖音 uper 视频爬取

提示:

1.本代码实例仅供学习使用,请遵守你所在地的法律法规。

2.IDE:PyCharm 2021.1.1 python:3.9 

3.selenium,browsermobproxy库的安装方法请自行百度

4.感谢 极客挖掘机(https://www.geekdigging.com/) 分享的python学习内容

import os
import re
import time
import random
import requests
from pyquery import PyQuery
from urllib.parse import urlencode
from tqdm import tqdm  # 下载进度条
from browsermobproxy import Server  # 代理,用于抓取 har 中的视频列表请求参数
from selenium import webdriver  # 调用浏览器,用于访问 uper 页面以产生访问视频列表 api 地址的行为
from selenium.webdriver.chrome.options import Options


# 中文、英文标点符号两者之间互换
def exchange_char(char_in, char_type='cn'):

    char_cn = ['', '', '', '*', '', '', '', '', '', '|', '', '']   # 中文标点符号
    char_en = ['/', '\', ':', ' ', '?', '"', '"', '<', '>', ' ', "'", "'"]     # 要互换的英文标点符号,与上面的中文列表对应
    char_out = str(char_in)

    for i in range(0, len(char_en)):
        if char_type == 'cn':
            # 英文2成中文
            char_out = char_out.replace(char_en[i], char_cn[i])
        else:
            # 中文2成英文
            char_out = char_out.replace(char_cn[i], char_en[i])

    return char_out


# 新建文件夹
def create_dir(path):
    if os.path.exists(path) is False:
        os.makedirs(path)
    # 切换到路径
    os.chdir(path)


# 视频下载
def down_video(vedio_url, file_path):
    file_name = os.path.basename(file_path)
    try:
        '''
        tqdm 参数:
            使用手动设置更新
            total 设置总大小
            initial 当前操作文件的大小
            desc 进度条前的描述
            ncols 设置进度条显示长度
            nit_scale 如果设置,迭代的次数会自动按照十、百、千来添加前缀,默认为false
        '''
        # 下载视频
        rsp = requests.get(vedio_url, stream=True, headers=headers)
        length = float(rsp.headers['content-length'])
        with open(file_path, 'wb') as fb:
            global down_count
            down_count += 1
            msg = '~~~下载中' + str(down_count) + '' + file_name
            pbar = tqdm(initial=os.path.getsize(file_path), desc=msg,
                        total=length, unit_scale=True, ncols=150, mininterval=0.5)
            for chuck in rsp.iter_content(chunk_size=512):
                if chuck:
                    fb.write(chuck)
                    pbar.update(512)  # 手动进度条
            pbar.close()
    except requests.exceptions.ConnectionError:
        print('
!!!连接错误:' + file_name + '
' + vedio_url)
    except requests.exceptions.Timeout:
        print('
!!!连接超时:' + file_name + '
' + vedio_url)


# 下载单个分享的视频
def down_one(share_url):
    # 真实url
    url_real = requests.head(share_url).headers['Location']  # 302 后的url

    # 视频id
    item_ids = re.compile(r'video/(d+)').findall(url_real)[0]  # 获取视频 id
    item_info_url = f"https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids={item_ids}"  # 拼接请求

    # 获取视频源路径
    res_json = session.get(item_info_url, headers=headers).json()
    vedio_url = res_json['item_list'][0]['video']['play_addr']['url_list'][0]
    video_name = res_json['item_list'][0]['share_info']['share_title'].split('#')[0].split('@')[0].replace(' ', '')
    video_name = exchange_char(video_name)  # 英文标点符号无法作为文件名

    file_path = video_name + fileFormat

    # 下载视频
    down_video(vedio_url, file_path)


# 获取视频列表查询参数
def get_list_querypara(share_url):
    # 真实url
    url_real = requests.head(share_url).headers['Location']  # 302 后的url

    # 开启代理
    bmp_server = Server(".\browsermob-proxy-2.1.4\bin\browsermob-proxy.bat")
    bmp_server.start()
    bmp_proxy = bmp_server.create_proxy()

    # 配置 Proxy 启动 WebDriver
    chrome_options = Options()
    # 开启无窗口模式
    chrome_options.add_argument('--headless')
    # 禁用扩展插件,因为我也不是太懂,总之没了这句,浏览器会报警提示如下图。魔法,勿动。
    chrome_options.add_argument('--ignore-certificate-errors')
    # bmp_proxy.proxy返回的是localhost:8081端口
    chrome_options.add_argument('--proxy-server={0}'.format(bmp_proxy.proxy))
    # 如果Selenium驱动放在了python.exe同级目录下,executable_path参数可以省略
    # driver = webdriver.Chrome(executable_path="D:Apythonchromedriver.exe", options=chrome_options)
    driver = webdriver.Chrome(chrome_options=chrome_options)

    # 个人理解是new一个空的har准备接收爬取网站的交互信息
    bmp_proxy.new_har("douyin", options={'captureHeaders': True, 'captureContent': True})
    # 模拟浏览器
    driver.get(url_real)

    # 请求的har
    json_har = bmp_proxy.har
    # 输出 har 到文件
    # import json
    # result_json = json.dumps(json_har, indent=4)
    # with open("douyin.json", "w", errors="igone") as f:
    #     f.write(result_json)
    dict_query = {}
    for entry in json_har['log']['entries']:
        entry_url = entry['request']['url']
        # 根据URL找到数据接口
        if url_api_list in entry_url:
            for query in entry['request']['queryString']:
                dict_query[query['name']] = query['value']
            break
        else:
            continue

    # 请求结果,uper主ID
    html = driver.page_source
    doc = PyQuery(html)
    uper_id = doc('p.shortid').text()

    bmp_server.stop()
    driver.quit()

    # 为 uper 新建目录
    dir_path = save_path + '\' + uper_id
    create_dir(dir_path)

    return {'dict_query': dict_query, 'dir_path': dir_path}


# 下载视频列表
def down_list(dict_info):

    dict_query = dict_info['dict_query']
    dir_path = dict_info['dir_path']

    if dict_query:
        # 视频列表
        url_json = url_api_list + '?' + urlencode(dict_query)
        # print('
###API请求URL:' + url_json)
        json_video_list = session.get(url_json, headers=headers).json()
        dict_info['dict_query']['max_cursor'] = json_video_list['max_cursor']  # 更新参数
        if json_video_list['has_more']:
            # 过滤空列表
            if json_video_list['aweme_list']:
                # 遍历下载
                for aweme in json_video_list['aweme_list']:
                    # aweme_id = aweme['aweme_id']
                    vedio_url = aweme['video']['play_addr']['url_list'][0]
                    video_name = aweme['desc'].split('#')[0].split('@')[0].replace(' ', '')
                    video_name = exchange_char(video_name)  # 英文标点符号无法作为文件名
                    file_path = dir_path + '\' + video_name + fileFormat
                    if os.path.isfile(file_path):
                        global down_count
                        down_count += 1
                        msg = '
***已存在' + str(down_count) + '' + os.path.basename(file_path)
                        print(msg)
                        pass
                    else:
                        time.sleep(random.random() * 1)  # 设置访问时间间隔,防止下载过于频繁
                        down_video(vedio_url, file_path)
                else:
                    # 下载完毕,下一页
                    down_list(dict_info)
            else:
                # 空列表,下一页
                down_list(dict_info)
        else:
            print('
###全部下载完毕。')
    else:
        print('
queryString 错误,无法开始下载')


if __name__ == '__main__':
    # 保存路径
    save_path = 'D:\douyin_down'
    # 文件格式
    fileFormat = '.mp4'
    # 下载统计
    down_count = 0
    # 视频列表api
    url_api_list = 'https://www.iesdouyin.com/web/api/v2/aweme/post/'
    # 创建一个请求头
    headers = {
        'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15'
                      ' (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
    }
    session = requests.Session()

    # 单个分享视频下载
    # down_url_video = "https://v.douyin.com/efvrSgd/"  # 视频分享路径
    # down_one(down_url_video)

    # Uper所有视频下载
    down_url_uper = "https://v.douyin.com/eftqWpm/"  # uper主页分享URL
    dict_uper = get_list_querypara(down_url_uper)
    down_list(dict_uper)
原文地址:https://www.cnblogs.com/nb08611033/p/14790393.html