selenium + ThreadPool + urllib 实现自动下载视频


其实代码整体并没有什么难度,主要就是一些逻辑和抄袭罢了、屌爆侠(手动狗头)。
还有 声明,此部分是为了完成python作业,实际技术实现上肯定会有问题。
本人主要瞎搞的是计算机视觉,python并不是那么的特别精通,也提前谢谢大佬们清喷。
后面有时间会更新一些关于cv相关文章(已经积累了很多md。。。。)
涉及版权问题:htmlString字段需自己到对应网站进行复制补充

代码结构

类名作用
_progress()回调函数
getPageUrl()取单页当中的所有a标签的url
getVideoUrl()获取视频URL列表
checkExist()检查元素存在
videoDownloadLink()获取视频真实下载链接
startDownload()执行下载
get_target()实际主函数

代码解析

1. _progress

def _progress(block_num, block_size, total_size):
    """回调函数
       @block_num: 已经下载的数据块
       @block_size: 数据块的大小
       @total_size: 远程文件的大小
    """
    sys.stdout.write('\r>> Downloading %s %.1f%%' % ("filename",
                                                     float(block_num * block_size) / float(total_size) * 100.0))
    sys.stdout.flush()

此部分定义回调函数,用以显示下载进度(类似于下方)
类似于这

2. getPageUrl

def getPageUrl(htmlString: str, driver, pages: int):
    """
    获取单页当中的所有a标签的url
    :param pages:
    :param htmlString: 列表页
    :param driver: 浏览器实例
    :return: 一页当中的url列表
    """
    if pages == 1:
        HrefList = []
        driver.get(htmlString)
        for i in range(1, 25):
            aPath = f'//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[1]/div/div[{i}]/div/div[2]/a'
            iCecreamHref = driver.find_element_by_xpath(aPath).get_attribute('href')
            print(iCecreamHref)
            HrefList.append(iCecreamHref)
        return HrefList
    else:
        HrefList = []
        driver.get(htmlString)
        for i in range(1, 25):
            aPath = f'/html/body/div[3]/div/div[2]/div[2]/div/div/div[1]/div[{i}]/div/div[2]/a'
            while 1:
                if checkExist(driver, aPath):
                    break
            iCecreamHref = driver.find_element_by_xpath(aPath).get_attribute('href')
            print(iCecreamHref)
            HrefList.append(iCecreamHref)
        return HrefList

此部分用于将一页当中的所有a标签即视频地址获取(例如)
个人比较喜欢用xpath获取(省脑子)

 driver.find_element_by_xpath(xPathString)

关于此部分代码主要为selenium中内容,emmmm可以看官网

3.getVideoUrl

def getVideoUrl(keyWord: str, pages: int):
    """
    获取视频URL列表
    :param keyWord: 关键词
    :param pages: 页数
    :return: 视频真实url列表
    """
    print("------------开始获取视频URL列表----------\n")

    htmlString2 = 
    if pages == 1:
        Driver = webdriver.Chrome()
        print("--------开始获取第 1 页------\n")
        hrefList = getPageUrl(htmlString=htmlString2, driver=Driver, pages=pages)
        Driver.close()
        print("--------获取第 1 页成功------\n")
        return hrefList
    elif pages > 1:
        Driver = webdriver.Chrome()
        print("--------开始获取第 1 页------\n")
        hrefList = getPageUrl(htmlString=htmlString2, driver=Driver, pages=1)
        Driver.close()
        print("--------获取第 1 页成功------\n")
        for page in range(2, pages + 1):
            htmlString1 = 
            print("--------开始获取第 {} 页------\n".format(page))
            # time.sleep(3)
            Driver = webdriver.Chrome()
            tempList = getPageUrl(htmlString=htmlString1, driver=Driver, pages=pages)
            for temp in tempList:
                hrefList.append(temp)
            # if page == 24:
            #     break
            Driver.close()
            print("--------获取第 {} 页成功------\n".format(page))
        # Driver.close()
        print("--------获取第 {} 页成功------\n".format(pages))
        return hrefList
    else:
        warnings.WarningMessage(message="您输入的页数有问题!", category=Warning)

关于此部分,是上一部分获取单页的一个承接,主要作业仍为返回链接列表。
值得注意的可能就是 pageSize这个参数o,其大小并不是自己打开的,而是selenium自动化浏览器的大小、
还有一个小细节就是每一页对于的xpath标签表示法是不一样的。emmmm(有bing)

4.checkExist

def checkExist(driver, xpath):
    """
    检查元素存在
    :param driver: 浏览前
    :param xpath: xpath
    :return:
    """
    print("---------开始检查元素是否存在----------\n")
    try:
        driver.find_element_by_xpath(xpath).get_attribute('href')
        print("---------------Exist!--------------\n")
        return True
    except Exception as e:
        print(e)
        return False

此部分其实涉及到了selenium的一个不完美的地方,就是driver并不能检测某一条命令是否已经执行或者是执行状态,所以会导致程序中断,直接退出,所以以抛出异常的方式来进行click、clear等时间的执行情况,以及内容是否存在。视情况更改吧,毕竟写这个目的性有点强。

5. videoDownloadLink

def videoDownloadLink(videoOriUrl: str, driver, _i):
    """
    获取视频真实下载链接
    :param videoOriUrl: 视频原始url
    :return:
    """
    print("--------开始获取真实地址链接------\n")

    inputXpath = f'//*[@id="url"]'
    buttonXpath = f'//*[@id="search"]'
    downloadUrl = f'/html/body/div[3]/div/form/div/div[3]/div[1]/a[1]'
    titleXpath = f'/html/body/div[3]/div/form/div/div[3]/div[1]/p'
    print(_i)
    if _i != 0:
        driver.find_element_by_xpath(inputXpath).clear()
    time.sleep(1)
    driver.find_element_by_xpath(inputXpath).send_keys(videoOriUrl)
    time.sleep(1)
    driver.find_element_by_xpath(buttonXpath).click()
    # while element2:
    #     break
    while 1:
        if checkExist(driver, downloadUrl):
            break
    element3 = driver.find_element_by_xpath(downloadUrl).get_attribute('href')
    # print(element3)
    element4 = driver.find_element_by_xpath(titleXpath).text
    # print(element4)
    element4 = re.sub(u"([^\u4e00-\u9fa5\u0030-\u0039\u0041-\u005a\u0061-\u007a])", "", element4)
    print(element4)
    hrefNameDict[element4] = element3
    print('当前字典共有 {} 项数据'.format(len(hrefNameDict)))
    _i += 1

此部分 使用selenium + 视频解析网站的方式,获取到视频的真实地址,并以字典的方式将title与linkString保存传递。
并且使用正则表达式对所有非法字符进行过滤,主要是为了直接以title为file命名。

6.startDownload

def startDownload(parmList):
    """
    执行下载
    :param parmList:
    :param trulyUrl: 真实url
    :param savePath: 保存路径
    :return: True & False
    """
    print("-----------开始下载----------\n")
    trulyUrl = parmList[1]
    savePath = parmList[2]
    saveName = parmList[0]

    # 添加header
    opener = urllib.request.build_opener()
    opener.addheaders = [
        ('User-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                       'Chrome/108.0.0.0 Safari/537.36')]
    urllib.request.install_opener(opener)

    # if not os.path.exists(pathLocal + savePath + '/'):
    #     os.makedirs(pathLocal + savePath)
    if urllib.request.urlretrieve(trulyUrl, f'../code/{savePath}/' + saveName + '.mp4', _progress):
        print("-----------下载 {} 完成----------\n".format(saveName))
        return True
    # else:
    #     os.makedirs(pathLocal + savePath)
    #     print("-----------创建 {} 完成----------\n".format(pathLocal + savePath))
    #     if urllib.request.urlretrieve(trulyUrl, f'../code/{savePath}/' + saveName + '.mp4', _progress):
    #         print("-----------下载 {} 完成----------\n".format(saveName))
    #         return True

此部分使用urllib进行视频的下载,其中需要注意的是需要给request加入headers信息,因为如果不加会导致session重叠的问题,进而导致程序异常终止。

7.get_target

def get_target(keyWord: str, page: int, saveName: str):
    """
    实际主函数
    :param keyWord:
    :param page:
    :param saveName:
    :return:
    """
    pathLocal = '/code/'
    if not os.path.exists(pathLocal + saveName + '/'):
        print("-----------创建 {} 成功----------".format(pathLocal + saveName))
        os.makedirs(pathLocal + saveName)
    urlList = getVideoUrl(keyWord=keyWord, pages=page)
    _i = 0

    webUrl = f'https://www.fodownloader.com/bilibili-video-downloader'
    driver = webdriver.Chrome()
    driver.get(webUrl)
    for url in urlList:
        videoDownloadLink(url, driver, _i)
        time.sleep(5)
        _i += 1
        # 用以测试效果,故缩短
        # if _i == 4:
        #     break
    driver.close()

    titleNames = []
    urls = []
    print("------------------")
    for key in hrefNameDict:
        titleNames.append(key)
        urls.append(hrefNameDict[key])
    for i in urls:
        print("-----------将要下载的url如下所示----------")
        print(i)
    with ThreadPoolExecutor(max_workers=4) as pool:
        x = len(titleNames)
        i = 0
        goalPool = 0
        while i < x:
            taskList = [
                [titleNames[i], urls[i], saveName],
                [titleNames[i + 1], urls[i + 1], saveName],
                [titleNames[i + 2], urls[i + 2], saveName],
                [titleNames[i + 3], urls[+3], saveName]
            ]
            print(taskList)
            print("------------ 线程池开始工作------------")
            results = pool.map(startDownload, taskList)
            for result in results:
                if result:
                    goalPool += 1
                    if goalPool % 4 == 0:
                        i += 4
    print("Success !!!")

此部分主要对以上获取到的url进行下载,在下载过程中采用线程池的方式,用线程池来加快下载速度,多线程同步download。并且以map列表的方式进行参数传递。
需要注意的为ThreadPool的maxWorkers问题,此处要注意了解自己电脑情况,不建议太大,因为同时会影响其他进程,本初只是实验,故只取4.

小小改进点

此处对于线程下载有比较大的优化之处,最明显的就是线程等待问题的出现,当四个文件大小不一致时,就会出现线程等待问题,为了使cpu资源的利用率最大化,可以采用动态更新的方式,对下载进度动态观测,使用队列对其进行下载排序。及可将其以四个队列的形式存在,进行动态更新,最后在考虑对已完成队列的资源分配。

在这里插入图片描述

完整代码

# author  : jerrylee
# data    : 2022/12/4
# time    : 19:52
# encoding: utf-8
import os
import re
import sys
import time
import urllib.request
import warnings
from selenium import webdriver
from concurrent.futures import ThreadPoolExecutor
import threading

hrefNameDict = dict()


def _progress(block_num, block_size, total_size):
    """回调函数
       @block_num: 已经下载的数据块
       @block_size: 数据块的大小
       @total_size: 远程文件的大小
    """
    sys.stdout.write('\r>> Downloading %s %.1f%%' % ("filename",
                                                     float(block_num * block_size) / float(total_size) * 100.0))
    sys.stdout.flush()


def getPageUrl(htmlString: str, driver, pages: int):
    """
    获取单页当中的所有a标签的url
    :param pages:
    :param htmlString: 列表页
    :param driver: 浏览器实例
    :return: 一页当中的url列表
    """
    if pages == 1:
        HrefList = []
        driver.get(htmlString)
        for i in range(1, 25):
            aPath = f'//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[1]/div/div[{i}]/div/div[2]/a'
            iCecreamHref = driver.find_element_by_xpath(aPath).get_attribute('href')
            print(iCecreamHref)
            HrefList.append(iCecreamHref)
        return HrefList
    else:
        HrefList = []
        driver.get(htmlString)
        for i in range(1, 25):
            aPath = f'/html/body/div[3]/div/div[2]/div[2]/div/div/div[1]/div[{i}]/div/div[2]/a'
            while 1:
                if checkExist(driver, aPath):
                    break
            iCecreamHref = driver.find_element_by_xpath(aPath).get_attribute('href')
            print(iCecreamHref)
            HrefList.append(iCecreamHref)
        return HrefList


def getVideoUrl(keyWord: str, pages: int):
    """
    获取视频URL列表
    :param keyWord: 关键词
    :param pages: 页数
    :return: 视频真实url列表
    """
    print("------------开始获取视频URL列表----------\n")

    htmlString2 = f'" \
             '
    if pages == 1:
        Driver = webdriver.Chrome()
        print("--------开始获取第 1 页------\n")
        hrefList = getPageUrl(htmlString=htmlString2, driver=Driver, pages=pages)
        Driver.close()
        print("--------获取第 1 页成功------\n")
        return hrefList
    elif pages > 1:
        Driver = webdriver.Chrome()
        print("--------开始获取第 1 页------\n")
        hrefList = getPageUrl(htmlString=htmlString2, driver=Driver, pages=1)
        Driver.close()
        print("--------获取第 1 页成功------\n")
        for page in range(2, pages + 1):
            htmlString1 = 
            print("--------开始获取第 {} 页------\n".format(page))
            # time.sleep(3)
            Driver = webdriver.Chrome()
            tempList = getPageUrl(htmlString=htmlString1, driver=Driver, pages=pages)
            for temp in tempList:
                hrefList.append(temp)
            # if page == 24:
            #     break
            Driver.close()
            print("--------获取第 {} 页成功------\n".format(page))
        # Driver.close()
        print("--------获取第 {} 页成功------\n".format(pages))
        return hrefList
    else:
        warnings.WarningMessage(message="您输入的页数有问题!", category=Warning)


def checkExist(driver, xpath):
    """
    检查元素存在
    :param driver: 浏览前
    :param xpath: xpath
    :return:
    """
    print("---------开始检查元素是否存在----------\n")
    try:
        driver.find_element_by_xpath(xpath).get_attribute('href')
        print("---------------Exist!--------------\n")
        return True
    except Exception as e:
        print(e)
        return False


def videoDownloadLink(videoOriUrl: str, driver, _i):
    """
    获取视频真实下载链接
    :param videoOriUrl: 视频原始url
    :return:
    """
    print("--------开始获取真实地址链接------\n")

    inputXpath = f'//*[@id="url"]'
    buttonXpath = f'//*[@id="search"]'
    downloadUrl = f'/html/body/div[3]/div/form/div/div[3]/div[1]/a[1]'
    titleXpath = f'/html/body/div[3]/div/form/div/div[3]/div[1]/p'
    print(_i)
    if _i != 0:
        driver.find_element_by_xpath(inputXpath).clear()
    time.sleep(1)
    driver.find_element_by_xpath(inputXpath).send_keys(videoOriUrl)
    time.sleep(1)
    driver.find_element_by_xpath(buttonXpath).click()
    # while element2:
    #     break
    while 1:
        if checkExist(driver, downloadUrl):
            break
    element3 = driver.find_element_by_xpath(downloadUrl).get_attribute('href')
    # print(element3)
    element4 = driver.find_element_by_xpath(titleXpath).text
    # print(element4)
    element4 = re.sub(u"([^\u4e00-\u9fa5\u0030-\u0039\u0041-\u005a\u0061-\u007a])", "", element4)
    print(element4)
    hrefNameDict[element4] = element3
    print('当前字典共有 {} 项数据'.format(len(hrefNameDict)))
    _i += 1


def startDownload(parmList):
    """
    执行下载
    :param parmList:
    :param trulyUrl: 真实url
    :param savePath: 保存路径
    :return: True & False
    """
    print("-----------开始下载----------\n")
    trulyUrl = parmList[1]
    savePath = parmList[2]
    saveName = parmList[0]

    # 添加header
    opener = urllib.request.build_opener()
    opener.addheaders = [
        ('User-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                       'Chrome/108.0.0.0 Safari/537.36')]
    urllib.request.install_opener(opener)

    # if not os.path.exists(pathLocal + savePath + '/'):
    #     os.makedirs(pathLocal + savePath)
    if urllib.request.urlretrieve(trulyUrl, f'../code/{savePath}/' + saveName + '.mp4', _progress):
        print("-----------下载 {} 完成----------\n".format(saveName))
        return True
    # else:
    #     os.makedirs(pathLocal + savePath)
    #     print("-----------创建 {} 完成----------\n".format(pathLocal + savePath))
    #     if urllib.request.urlretrieve(trulyUrl, f'../code/{savePath}/' + saveName + '.mp4', _progress):
    #         print("-----------下载 {} 完成----------\n".format(saveName))
    #         return True


def get_target(keyWord: str, page: int, saveName: str):
    """
    实际主函数
    :param keyWord:
    :param page:
    :param saveName:
    :return:
    """
    pathLocal = '/code/'
    if not os.path.exists(pathLocal + saveName + '/'):
        print("-----------创建 {} 成功----------".format(pathLocal + saveName))
        os.makedirs(pathLocal + saveName)
    urlList = getVideoUrl(keyWord=keyWord, pages=page)
    _i = 0

    webUrl = f'https://www.fodownloader.com/bilibili-video-downloader'
    driver = webdriver.Chrome()
    driver.get(webUrl)
    for url in urlList:
        videoDownloadLink(url, driver, _i)
        time.sleep(5)
        _i += 1
        # 用以测试效果,故缩短
        # if _i == 4:
        #     break
    driver.close()

    titleNames = []
    urls = []
    print("------------------")
    for key in hrefNameDict:
        titleNames.append(key)
        urls.append(hrefNameDict[key])
    for i in urls:
        print("-----------将要下载的url如下所示----------")
        print(i)
    with ThreadPoolExecutor(max_workers=4) as pool:
        x = len(titleNames)
        i = 0
        goalPool = 0
        while i < x:
            taskList = [
                [titleNames[i], urls[i], saveName],
                [titleNames[i + 1], urls[i + 1], saveName],
                [titleNames[i + 2], urls[i + 2], saveName],
                [titleNames[i + 3], urls[+3], saveName]
            ]
            print(taskList)
            print("------------ 线程池开始工作------------")
            results = pool.map(startDownload, taskList)
            for result in results:
                if result:
                    goalPool += 1
                    if goalPool % 4 == 0:
                        i += 4
    print("Success !!!")


if __name__ == "__main__":
    keyword = input("请输入要搜索的关键词: ")
    page = int(input("请输入要爬取的页数: "))
    saveName = input("请输入要保存的文件名: ")
    get_target(keyword, page, saveName)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JerryAuas

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值