您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Python

Python爬取视频的几种方法

时间:2022-08-10 11:42:09  来源:  作者:南国天空

今天和大家分享下我近段时间get的新技能,用单线程、多线程和协程三种方式爬取并下载梨视频的小视频,话不多说,我们开始叭。冲鸭冲鸭!

 

目标

将梨视频上的科技相关的视频资源下载保存到电脑本地

工具

  • Python/ target=_blank class=infotextkey>Python3.9
  • Pycharm2020

需要用到的第三方库

1) requests # 发送请求2) parsel # 解析数据(支持re, xpath, css)3) fake_useragent # 构建请求头4) random # 生成随机数5) os # 操作路径/生成文件夹6) json # 处理json数据7) concurrent # 处理线程池8) asyncioAIohttpaiofiles # 处理协程

 

分析并使用单线程下载视频

我们需要将梨视频网站上的视频资源下载到电脑本地,那必不可少的两个元素必然是视频名称视频资源url。获取视频资源url后,针对视频资源的url发起请求,得到响应,再将响应内容以视频名称为名保存到电脑本地即可。

起始页:科技热点资讯短视频_科技热点新闻-梨视频官网-Pear Video

URL地址
https://www.pearvideo.com/category_8

1.分析起始页

F12对起始页刷新抓包,拿到数据请求接口

 

梨视频(科技)主页

 

对比观察抓包获取到的url:

https://www.pearvideo.com/category_loading.jsp?reqType=5&categoryId=8&start=12&mrd=0.6312621497255415&filterIds=1745912,1745729,1745750,1745761,1745809,1745640,1745278,1745506,1745193,1606956,1745335,1745147

https://www.pearvideo.com/category_loading.jsp?reqType=5&categoryId=8&start=24&mrd=0.9021185727219558&filterIds=1745912,1745729,1745750,1745254,1745034,1744996,1744970,1744646,1744743,1744838,1744567,1744308,1744225,1744727,1744649

https://www.pearvideo.com/category_loading.jsp?reqType=5&categoryId=8&start=36&mrd=0.6598737970838424&filterIds=1745912,1745729,1745750,1744642,1744353,1744377,1744291,1744127,1744055,1744106,1744126,1744040,1743939,1743997,1744012

对比上方三个url可见,除了其中的start, mrd以及filterIds不同之外,其余部分均为https://www.pearvideo.com/category_loading.jsp?reqType=5&categoryId=8&start=。其中start每次增长为12,即每次加载12段视频;mrd为一个随机数,filterIds即为视频资源的cid号。

2. 发送起始页请求

我们可以根据抓包获取到的信息构建请求,获取响应内容。全文将模仿scrapy框架的写法,将代码封装在一个类之中,再定义不同的函数实现各个阶段的功能。

# 导入需要用到的模块
import requests
from parsel import Selector
from fake_useragent import UserAgent
import random
import json
import os

创建类并定义相关函数、属性

class PearVideo:
    def __init__(self, page):
        self.headers = {
            "User-Agent": UserAgent().chrome,    # 构建谷歌请求头
        }
        self.page = page    # 设置要爬取的页数
        self.base_url = "https://www.pearvideo.com/category_loading.jsp?reqType=5&categoryId=8&start="   

    def start_request(self):
        for page in range(self.page):
            start_url = self.base_url + str(page * 12)    # 拼接起始页url
            res = requests.get(start_url, headers=self.headers)
            if res.status_code == 200:
                # 将获取到的请求转换成一个parsel.selector.Selector对象,之后方便解析文本;
                # 类似scrapy框架中的response对象,可直接调用re(), xpath()和css()方法。
                selector = Selector(res.text)
                self.parse(selector)

获取到响应之后就可以解析响应文本了,在响应文本中我们可以提取到视频的详情页url及视频名称,代码如下:

 

3. 解析起始页响应获取视频名称、视频详情页url

    def parse(self, response):
        videos = response.xpath("//div[@class='vervideo-bd']")
        for video in videos:
            # 拼接视频详情页url
            detail_url = "https://www.pearvideo.com/" + video.xpath("./a/@href").get()
            # 提取视频名称
            video_name = video.xpath(".//div[@class='vervideo-title']/text()").get()
            # 将视频详情页url和视频名称传递给parse_detail方法,对详情页发送请求获取响应。
            self.parse_detail(detail_url, video_name)

在浏览器中打开视频详情页,按F12观察浏览器渲染之后的代码可见视频资源的url, 如下图所示:

 

此处的视频资源url为:
https://video.pearvideo.com/mp4/third/20211028/cont-1744727-11315812-110716-hd.mp4

但是实际获取视频详情页响应后,并没有找到视频资源的url,能找到的只有一张视频图片预览的url,如下图所示(可在浏览器视频详情页,鼠标右键查看网页源代码获取):

 

于是,我们再次针对视频详情页抓包,找到视频资源url的相关请求和响应内容,如下图所示:

 


 

其中的contId即为详情页响应的data-cid属性值(详见下文),而mrd为一个随机值,可通过random.random()生成,在发送请求的时候Referer必不可少,否则将无法获取到正确的响应内容。

点击preview,可以查看请求的响应结果,如下图所示:

 

在图中,我们可以得到一个后缀为mp4的srcUrl链接,这看起来像是我们需要的视频资源url,但是如果直接使用这个链接发送请求,将会提示如下错误:

 

对比观察浏览器渲染之后的视频资源url和抓包获取的视频资源url:

浏览器渲染:
https://video.pearvideo.com/mp4/third/20211028/cont-1744727-11315812-110716-hd.mp4抓包获取:
https://video.pearvideo.com/mp4/third/20211028/1637679343220-11315812-110716-hd.mp4

通过观察可得出,除了上文加黑标粗的部分不同外,其余部分均相同;而其中的1744727即为视频资源的data-cid属性值。

 

浏览器视频详情页中获取

于是我们可以将抓包所获取到的假的视频资源url中的1637679343220替换为cont-1744727(即视频data-cid属性值),即可获取到真正的视频资源url, 从而下载视频资源!

经过漫长的分析之后,终于可以着手写代码啦!

4. 针对视频详情页url发送请求,获取响应

    def parse_detail(self, detail_url, video_name):
        detail_res = requests.get(detail_url, headers=self.headers)
        detail_selector = Selector(detail_res.text)
        init_cid = detail_selector.xpath("//div[@id='poster']/@data-cid").get()  # 提取网页中data-cid的属性值(初始cid)
        mrd = random.random()  # 生成随机数,构建mrd
        ajax_url = f"https://www.pearvideo.com/videoStatus.jsp?contId={init_cid}&mrd={mrd}"
        global ajax_header  # 将ajax_header设置为全局变量,以便在后续的函数中调用
        ajax_header = {"Referer": f"https://www.pearvideo.com/video_{init_cid}"}
        self.parse_ajax(ajax_url, init_cid, video_name)

5. 对视频详情页抓包,获取假的视频资源url

    def parse_ajax(self, ajax_url, init_cid, video_name):
        ajax_res = requests.get(ajax_url, headers=ajax_header)
        fake_video_url = json.loads(ajax_res.text)["videoInfo"]["videos"]["srcUrl"]  # 获取假的视频资源url
        fake_cid = fake_video_url.split("/")[-1].split("-")[0]  # 从假的视频资源url中抽取假的cid
        real_cid = "cont-" + init_cid  # 真的cid等于cont-加上初始的cid
        # 将假的视频资源url中假的cid(fake_cid)替换为真的cid(real_cid)即可得到真正的视频资源url啦!!!
        # 这段代码,你品,你细品...
        real_video_url = fake_video_url.replace(fake_cid, real_cid)
        self.download_video(video_name, real_video_url)

6. 对视频资源url发送请求,获取响应

有了视频名称和视频资源url,就可以下载视频啦!!!

    def download_video(self, video_name, video_url):
        video_res = requests.get(video_url, headers=ajax_header)
        video_path = os.path.join(os.getcwd(), "单线程视频下载")
        # 如果不存在则创建视频文件夹存放视频
        if not os.path.exists(video_path):
            os.mkdir(video_path)
        with open(f"{video_path}/{video_name}.mp4", "wb") as video_file:
            video_file.write(video_res.content)
        print(f"{video_name}下载完毕")

最后,定义一个run()方法作为整个类的入口,调用最开始的start_request()函数即可!(套娃,一个函数套另一个函数)

    def run(self):
        self.start_request()

if __name__ == '__main__':
    pear_video = PearVideo(3)   # 先获取它三页的视频资源
    pear_video.run()

 

使用线程池下载视频

线程池这部分的代码总体和单线程类似,只是将其中的视频名称和视频资源url单独抽取出来,作为全局变量。获取视频名称和视频资源url这部分仍为单线程,仅在下载视频资源这部分才用了线程池处理,可以同时针对多个视频资源url发送请求获取响应。

主要代码如下:

class PearVideo:
    def __init__(self, page):
        self.headers = {
            "User-Agent": UserAgent().chrome,
        }
        self.page = page
        self.base_url = "https://www.pearvideo.com/category_loading.jsp?reqType=5&categoryId=8&start="
        self.video_list = []    # 新增了video_list为全局变量,用来保存视频名称和视频资源url

1.获取真正的视频资源url代码

    def parse_ajax(self, ajax_url, init_cid, video_name):
        ajax_res = requests.get(ajax_url, headers=ajax_header)
        fake_video_url = json.loads(ajax_res.text)["videoInfo"]["videos"]["srcUrl"]
        fake_cid = fake_video_url.split("/")[-1].split("-")[0]
        real_cid = "cont-" + init_cid
        real_video_url = fake_video_url.replace(fake_cid, real_cid)
        # video_dict每次请求都会刷新,最终保存到video_list中
        video_dict = {
            "video_url": real_video_url,
            "video_name": video_name
        }
        self.video_list.Append(video_dict)

2. 多线程下载视频资源代码

    def download_video(self, video_dict):  # 此处传递的是一个字典而非video_list这个列表
        video_res = requests.get(video_dict["video_url"], headers=ajax_header)
        video_path = os.path.join(os.getcwd(), "线程池视频下载")
        if not os.path.exists(video_path):
            os.mkdir(video_path)
        with open(f"{video_path}/{video_dict['video_name']}.mp4", "wb") as video_file:
            video_file.write(video_res.content)
        print(f"{video_dict['video_name']}下载完毕")

3. 启动多线程

if __name__ == '__main__':
    pear_video = PearVideo(2)
    pear_video.run()
    pool = ThreadPoolExecutor(4)  # 此处的4表示每次只开启4个线程下载视频资源
    # 此处的map方法和Python自带的map(x,y)含义类似,即将可迭代对象y中的每一个元素执行函数x。
    pool.map(pear_video.download_video, pear_video.video_list)

 

使用协程下载视频

使用协程下载视频资源中最为重要的三个库为asyncio(创建协程对象),aiohttp(发送异步请求),aiofiles(异步保存文件)。

重点:

1)在函数前加上async关键字,函数即被创建为一个协程对象;2)协程对象中所有需要io耗时操作的部分均需使用await将任务挂起;3)协程对象不能直接运行,需要创建一个事件循环(类似无限循环),然后再运行协程对象。

注意:

1)不能使用requests发送异步请求,需要使用aiohttphttpx;2)不能直接使用open()保存文件,需要使用aiofiles进行异步操作保存。

主要代码如下

        # 将视频资源url和视频名称作为全局变量
        self.video_urls = []
        self.video_names = []

1.定义协程对象下载视频

    # 下载视频信息
    async def download_videos(self, session, video_url, video_name, video_path):
        # 发送异步请求
        async with session.get(video_url, headers=ajax_header) as res:
            # 获取异步响应,前面必须加上await,表示挂起
            content = await res.content.read()
            # 异步保存视频资源到电脑本地
            async with aiofiles.open(f"{video_path}/{video_name}.mp4", "wb") as file:
                print(video_name + "  下载完毕...")
                await file.write(content)

2. 创建main()运行协程对象

    async def main(self):
        video_path = os.path.join(os.getcwd(), "协程视频下载")
        if not os.path.exists(video_path):
            os.mkdir(video_path)
        async with aiohttp.ClientSession() as session:  # 创建session,保持会话
            # 创建协程任务,每一个视频资源url即为一个协程任务
            tasks = [
                asyncio.create_task(self.download_videos(session, url, name, video_path))
                for url, name in zip(self.video_urls, self.video_names)
            ]
            # 等待所有的任务完成
            done, pending = await asyncio.wait(tasks)

3. 调用整个类并运行协程对象

if __name__ == '__main__':
    pear_video = PearVideo(3)
    pear_video.run()
    loop = asyncio.get_event_loop()    # 创建事件循环
    loop.run_until_complete(pear_video.main())    # 运行协程对象

 

在保存视频的时候,如果视频名称中含有"", "/", "*", "?", "<", ">", "|"在内的非法字符,视频将无法保存,程序将报错,可用如下代码过滤视频名称:

    def rename(self, name):
        stop = ["\", "/", "*", "?", "<", ">", "|"]
        new_name = ""
        for i in name:
            if i not in stop:
                new_name += i
        return new_name

在使用多线程和协程下载视频资源这部分代码中都是使用单线程和线程池/协程结合,均是在获取到视频名称和视频资源url后再针对视频资源发送请求,获取响应,此部分代码仍有待优化,如使用生产者/消费者模式一边生产视频资源url,一边根据url下载视频;而协程部分也可将其它需要发送网络请求的部分修改为协程模式,从而提高下载速度。

 

总结

下载梨视频的视频资源难点在于破解真正的视频资源url, 先后需要对视频起始页(主页)发送请求,再对视频详情页发送请求,然后再对视频详情页抓包获取真正的视频资源url,最后再针对视频资源url发送请求,下载视频资源。其中线程池和协程的部分仍有待优化,以便更好地提高下载效率!



Tags:Python   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
一篇文章教会你使用Python中三种简单的函数
所谓函数,就是指:把某些特定功能的代码组成为一个整体,这个整体就叫做函数。一、函数简介所谓函数,就是指:把某些特定功能的代码组成为一个整体,这个整体就叫做函数。二、函数定义...【详细内容】
2024-04-11  Search: Python  点击:(12)  评论:(0)  加入收藏
一篇文章带你了解Python的分布式进程接口
在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。一、前言在Thread和Process中,应当优...【详细内容】
2024-04-11  Search: Python  点击:(10)  评论:(0)  加入收藏
Python 可视化:Plotly 库使用基础
当使用 Plotly 进行数据可视化时,我们可以通过以下示例展示多种绘图方法,每个示例都会有详细的注释和说明。1.创建折线图import plotly.graph_objects as go# 示例1: 创建简单...【详细内容】
2024-04-01  Search: Python  点击:(15)  评论:(0)  加入收藏
Python 办公神器:教你使用 Python 批量制作 PPT
介绍本文将介绍如何使用openpyxl和pptx库来批量制作PPT奖状。本文假设你已经安装了python和这两个库。本文的场景是:一名基层人员,要给一次比赛活动获奖的500名选手制作奖状,并...【详细内容】
2024-03-26  Search: Python  点击:(21)  评论:(0)  加入收藏
Python实现工厂模式、抽象工厂,单例模式
工厂模式是一种常见的设计模式,它可以帮助我们创建对象的过程更加灵活和可扩展。在Python中,我们可以使用函数和类来实现工厂模式。一、Python中实现工厂模式工厂模式是一种常...【详细内容】
2024-03-07  Search: Python  点击:(38)  评论:(0)  加入收藏
不可不学的Python技巧:字典推导式使用全攻略
Python的字典推导式是一种优雅而强大的工具,用于创建字典(dict)。这种方法不仅代码更加简洁,而且执行效率高。无论你是Python新手还是有经验的开发者,掌握字典推导式都将是你技能...【详细内容】
2024-02-22  Search: Python  点击:(43)  评论:(0)  加入收藏
如何进行Python代码的代码重构和优化?
Python是一种高级编程语言,它具有简洁、易于理解和易于维护的特点。然而,代码重构和优化对于保持代码质量和性能至关重要。什么是代码重构?代码重构是指在不改变代码外部行为的...【详细内容】
2024-02-22  Search: Python  点击:(44)  评论:(0)  加入收藏
Python开发者必备的八个PyCharm插件
在编写代码的过程中,括号几乎无处不在,以至于有时我们会拼命辨别哪个闭合括号与哪个开头的括号相匹配。这款插件能帮助解决这个众所周知的问题。前言在PyCharm中浏览插件列表...【详细内容】
2024-01-26  Search: Python  点击:(92)  评论:(0)  加入收藏
Python的Graphlib库,再也不用手敲图结构了
Python中的graphlib库是一个功能强大且易于使用的工具。graphlib提供了许多功能,可以帮助您创建、操作和分析图形对象。本文将介绍graphlib库的主要用法,并提供一些示例代码和...【详细内容】
2024-01-26  Search: Python  点击:(95)  评论:(0)  加入收藏
大语言模型插件功能在携程的Python实践
作者简介成学,携程高级安全研发工程师,关注Python/Golang后端开发、大语言模型等领域。一、背景2023年初,科技圈最火爆的话题莫过于大语言模型了,它是一种全新的聊天机器人模型,...【详细内容】
2024-01-26  Search: Python  点击:(84)  评论:(0)  加入收藏
▌简易百科推荐
一篇文章教会你使用Python中三种简单的函数
所谓函数,就是指:把某些特定功能的代码组成为一个整体,这个整体就叫做函数。一、函数简介所谓函数,就是指:把某些特定功能的代码组成为一个整体,这个整体就叫做函数。二、函数定义...【详细内容】
2024-04-11  Go语言进阶学习  微信公众号  Tags:Python   点击:(12)  评论:(0)  加入收藏
一篇文章带你了解Python的分布式进程接口
在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。一、前言在Thread和Process中,应当优...【详细内容】
2024-04-11  Go语言进阶学习    Tags:Python   点击:(10)  评论:(0)  加入收藏
Python 可视化:Plotly 库使用基础
当使用 Plotly 进行数据可视化时,我们可以通过以下示例展示多种绘图方法,每个示例都会有详细的注释和说明。1.创建折线图import plotly.graph_objects as go# 示例1: 创建简单...【详细内容】
2024-04-01  Python技术    Tags:Python   点击:(15)  评论:(0)  加入收藏
Python 办公神器:教你使用 Python 批量制作 PPT
介绍本文将介绍如何使用openpyxl和pptx库来批量制作PPT奖状。本文假设你已经安装了python和这两个库。本文的场景是:一名基层人员,要给一次比赛活动获奖的500名选手制作奖状,并...【详细内容】
2024-03-26  Python技术  微信公众号  Tags:Python   点击:(21)  评论:(0)  加入收藏
Python实现工厂模式、抽象工厂,单例模式
工厂模式是一种常见的设计模式,它可以帮助我们创建对象的过程更加灵活和可扩展。在Python中,我们可以使用函数和类来实现工厂模式。一、Python中实现工厂模式工厂模式是一种常...【详细内容】
2024-03-07  Python都知道  微信公众号  Tags:Python   点击:(38)  评论:(0)  加入收藏
不可不学的Python技巧:字典推导式使用全攻略
Python的字典推导式是一种优雅而强大的工具,用于创建字典(dict)。这种方法不仅代码更加简洁,而且执行效率高。无论你是Python新手还是有经验的开发者,掌握字典推导式都将是你技能...【详细内容】
2024-02-22  子午Python  微信公众号  Tags:Python技巧   点击:(43)  评论:(0)  加入收藏
如何进行Python代码的代码重构和优化?
Python是一种高级编程语言,它具有简洁、易于理解和易于维护的特点。然而,代码重构和优化对于保持代码质量和性能至关重要。什么是代码重构?代码重构是指在不改变代码外部行为的...【详细内容】
2024-02-22  编程技术汇    Tags:Python代码   点击:(44)  评论:(0)  加入收藏
Python开发者必备的八个PyCharm插件
在编写代码的过程中,括号几乎无处不在,以至于有时我们会拼命辨别哪个闭合括号与哪个开头的括号相匹配。这款插件能帮助解决这个众所周知的问题。前言在PyCharm中浏览插件列表...【详细内容】
2024-01-26  Python学研大本营  微信公众号  Tags:PyCharm插件   点击:(92)  评论:(0)  加入收藏
Python的Graphlib库,再也不用手敲图结构了
Python中的graphlib库是一个功能强大且易于使用的工具。graphlib提供了许多功能,可以帮助您创建、操作和分析图形对象。本文将介绍graphlib库的主要用法,并提供一些示例代码和...【详细内容】
2024-01-26  科学随想录  微信公众号  Tags:Graphlib库   点击:(95)  评论:(0)  加入收藏
Python分布式爬虫打造搜索引擎
简单分布式爬虫结构主从模式是指由一台主机作为控制节点负责所有运行网络爬虫的主机进行管理,爬虫只需要从控制节点那里接收任务,并把新生成任务提交给控制节点就可以了,在这个...【详细内容】
2024-01-25  大雷家吃饭    Tags:Python   点击:(63)  评论:(0)  加入收藏
站内最新
站内热门
站内头条