本日和大家分享下我近段韶光get的新技能,用单线程、多线程和协程三种办法爬取并下载梨视频的小视频,话不多说,我们开始叭。
冲鸭冲鸭!

目标

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

jsp怎么提取出全局变量Python爬取视频的几种办法 Python

工具

Python3.9Pycharm2020

须要用到的第三方库

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

剖析并利用单线程下载视频

我们须要将梨视频网站上的视频资源下载到电脑本地,那必不可少的两个元素一定是视频名称和视频资源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框架的写法,将代码封装在一个类之中,再定义不同的函数实现各个阶段的功能。

#导入须要用到的模块importrequestsfromparselimportSelectorfromfake_useragentimportUserAgentimportrandomimportjsonimportos

创建类并定义干系函数、属性

classPearVideo: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="defstart_request(self):forpageinrange(self.page):start_url=self.base_url+str(page12)#拼接起始页urlres=requests.get(start_url,headers=self.headers)ifres.status_code==200:#将获取到的要求转换成一个parsel.selector.Selector工具,之后方便解析文本;#类似scrapy框架中的response工具,可直接调用re(), xpath()和css()方法。
selector=Selector(res.text)self.parse(selector)

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

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

defparse(self,response):videos=response.xpath("//div[@class='vervideo-bd']")forvideoinvideos:#拼接视频详情页urldetail_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发送要求,获取相应

defparse_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()#天生随机数,构建mrdajax_url=f"https://www.pearvideo.com/videoStatus.jsp?contId={init_cid}&mrd={mrd}"globalajax_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

defparse_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"]#获取假的视频资源urlfake_cid=fake_video_url.split("/")[-1].split("-")[0]#从假的视频资源url中抽取假的cidreal_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,就可以下载视频啦!


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

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

defrun(self):self.start_request()if__name__=='__main__':pear_video=PearVideo(3)#先获取它三页的视频资源pear_video.run()

利用线程池下载视频

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

紧张代码如下:

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

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

defparse_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_cidreal_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. 多线程下载视频资源代码

defdownload_video(self,video_dict):#此处通报的是一个字典而非video_list这个列表video_res=requests.get(video_dict["video_url"],headers=ajax_header)video_path=os.path.join(os.getcwd(),"线程池视频下载")ifnotos.path.exists(video_path):os.mkdir(video_path)withopen(f"{video_path}/{video_dict['video_name']}.mp4","wb")asvideo_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发送异步要求,须要利用aiohttp或httpx;2)不能直策应用open()保存文件,须要利用aiofiles进行异步操作保存。

紧张代码如下

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

1.定义协程工具下载视频

#下载视频信息asyncdefdownload_videos(self,session,video_url,video_name,video_path):#发送异步要求asyncwithsession.get(video_url,headers=ajax_header)asres:#获取异步相应,前面必须加上await,表示挂起content=awaitres.content.read()#异步保存视频资源到电脑本地asyncwithaiofiles.open(f"{video_path}/{video_name}.mp4","wb")asfile:print(video_name+"下载完毕...")awaitfile.write(content)

2. 创建main()运行协程工具

asyncdefmain(self):video_path=os.path.join(os.getcwd(),"协程视频下载")ifnotos.path.exists(video_path):os.mkdir(video_path)asyncwithaiohttp.ClientSession()assession:#创建session,保持会话#创建协程任务,每一个视频资源url即为一个协程任务tasks=[asyncio.create_task(self.download_videos(session,url,name,video_path))forurl,nameinzip(self.video_urls,self.video_names)]#等待所有的任务完成done,pending=awaitasyncio.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())#运行协程工具

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

defrename(self,name):stop=["\\","/","","?","<",">","|"]new_name=""foriinname:ifinotinstop:new_name+=ireturnnew_name

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

总结

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