一、梨视频获取分析、猜想、思考过程以及解决方案
-1、get访问
https://www.pearvideo.com/category_5
2、访问:https://www.pearvideo.com/video_1720499进入某个视频的详细页面
问:这个响应包里的videoStatus.jsp文件里的链接是否就是该视频的链接?
访问:https://video.pearvideo.com/mp4/adshort/20210218/1613654018464-15608885_adpkg-ad_hd.mp4后:
说明这个链接不是该视频实际的链接,但是必然存在联系!!
于是回过头来看看videoStatus.jsp
其headers为:
响应包(Response)为:
{
"resultCode":"1",
"resultMsg":"success", "reqId":"d417436d-8a6a-440a-846e-4457fbb7517b",
"systemTime": "1613654018464",
"videoInfo":{"playSta":"1","video_image":"https://image2.pearvideo.com/cont/20210218/cont-1720499-12554903.png","videos":{"hdUrl":"","hdflvUrl":"","sdUrl":"","sdflvUrl":"","srcUrl":"https://video.pearvideo.com/mp4/adshort/20210218/1613654018464-15608885_adpkg-ad_hd.mp4"}}
}
它是一个json形式的数据包
有什么联系?
-先清除cookies
-然后在某个视频详细页面点击视频播放按钮
在这些文件中发现一个.mp4开头的文件,点击后,访问其Request URL:
https://video.pearvideo.com/mp4/adshort/20210218/cont-1720499-15608885_adpkg-ad_hd.mp4
恰好可以访问该视频:
是不是觉得视频的实际链接和videoStatus.jsp中的链接特别相似?
对比一下:
只有一个部分不同,只需将cont-1720499替代掉红款部分就可以了。
接下来:
思考:1、是不是所有的视频的有效链接都是videoStatus.jsp文件中的srcUrl 将其一部分替换成con-id,而其余不变? 其中id为一串数字
为了验证猜想,点击另外一个视频发现,该视频的有效链接如下:
而其videoStatus.jsp文件的响应包中的json数据如下:
观察发现,依旧是前面不变,只是将1613656560550替换成cont-1720285,从而得到这个视频的有效链接,即实际链接。
2、这个id是什么?
3、如何获取id?
这个id可以通过get方式请求访问https://www.pearvideo.com/category_5 从而得到其网页,再通过xpath解析出href,最后通过正则表达式得到id
(其中该正则表达式,即匹配模式为: ex='video_([0-9]+)' #用于获取视频id的模式)
4、如何获取响应包中的videoStatus.jsp内容?
首先它是通过Request Url所获得的响应包
https://www.pearvideo.com/videoStatus.jsp?contId=1720499&mrd=0.7680917929620115
观察一下这个链接,它有两个参数,一个是contId,另一个是mrd
很明显contId的实参是该视频的id,而mrd是什么?
思考:mrd的值怎样得来的?
猜想:不用mrd这个参数,只用contId这个参数依然可以得到videoStatus.jsp这个响应包
为了验证猜想:直接访问这个链接:https://www.pearvideo.com/videoStatus.jsp?contId=1720499
发现不能获取响应包中的有效内容。原因是:服务器进行了防盗处理。
如何解决?
加个Referer :此内容用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如做来源统计、防盗处理等。
因此在headers中增加 'Referer': detail_url 其中detail_url为 形如:https://www.pearvideo.com/video_1720499 这样的链接,即:该视频的详细页链接
用来表示访问https://www.pearvideo.com/videoStatus.jsp?contId=1720499这个请求是由https://www.pearvideo.com/video_1720499 这个网页发过来的。
5、如何实现响应包中的链接与视频的有效链接部分替换?
-首先确定正则表达式:其匹配模式为ex1='[third,adshort]/.*?/(.*?)-.*?' #需要被替换的模式
-其次根据匹配模式找到响应包中的链接需要被替换的部分
-然后确定替换的字符串
-最后用字符串的replace方法
代码如下:
1 session = requests.Session() #维持会话 2 #从详细页中解析处视频的地址(url) 3 json_url='https://www.pearvideo.com/videoStatus.jsp?contId='+con_id #其中con_id为视频的编号id 4 response=session.get(url=json_url,headers=headers) 5 dic_obj=response.json() 6 7 #被伪装的下载地址 8 down_url=dic_obj['videoInfo']['videos']['srcUrl'] 9 10 #将响应体中的链接转化为真实链接 11 need_replace=re.findall(ex1,down_url)[0] 12 13 #替换的字符串 14 replaced='cont-'+con_id 15 #真实的下载地址 16 down_url=down_url.replace(need_replace,replaced)
二、梨视频爬取流程图
上述问题都分析清楚并解决后,综上:
三、代码
1 import os 2 import re 3 import threading 4 import time 5 from multiprocessing.dummy import Pool 6 from time import sleep 7 8 import requests 9 from lxml import etree 10 #需求:爬取梨视频的视频数据 11 headers = { 12 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36' 13 } 14 15 urls = [] # 用于保存视频下载的所有链接 16 def init(): 17 18 headers = { 19 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36' 20 } 21 #原则:线程池处理的是阻塞且较为耗时的操作 22 23 url='https://www.pearvideo.com/category_5' 24 page_text=requests.get(url=url,headers=headers).text 25 26 tree=etree.HTML(page_text) 27 28 li_list=tree.xpath('//ul[@id="listvideoListUl"]/li') 29 30 #获取响应体中的链接 31 ex='video_([0-9]+)' #用于获取视频id的模式 32 #需要被替换的模式 33 ex1='[third,adshort]/.*?/(.*?)-.*?' 34 35 36 for li in li_list: 37 detail_url='https://www.pearvideo.com/'+li.xpath('./div/a/@href')[0] 38 name=li.xpath('./div/a/div[2]/text()')[0]+'.mp4' 39 con_id=re.findall(ex,li.xpath('./div/a/@href')[0])[0] 40 41 #对详细页的url发起请求 42 headers = { 43 'Referer':detail_url, 44 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36' 45 } 46 session = requests.Session() 47 #从详细页中解析处视频的地址(url) 48 49 json_url='https://www.pearvideo.com/videoStatus.jsp?contId='+con_id 50 response=session.get(url=json_url,headers=headers) 51 dic_obj=response.json() 52 #被伪装的下载地址 53 down_url=dic_obj['videoInfo']['videos']['srcUrl'] 54 55 56 #将响应体中的链接转化为真实链接 57 need_replace=re.findall(ex1,down_url)[0] 58 #print(need_replace) 59 60 #替换的字符串 61 replaced='cont-'+con_id 62 #真实的下载地址 63 down_url=down_url.replace(need_replace,replaced) 64 65 dic={ 66 'name':name, 67 'url':down_url 68 } 69 urls.append(dic) 70 #下载视频 71 def down(dic): 72 url=dic['url'] 73 name=dic['name'] 74 print(name,'正在下载...') 75 76 resPage=requests.get(url=url,headers=headers) 77 #print(resPage.status_code) 78 79 # 创建文件夹 80 if not os.path.exists('../pearvideoLibs'): 81 os.mkdir('../pearvideoLibs') 82 video_path='../pearvideoLibs/'+name 83 if resPage.status_code==200: 84 with open(video_path,'wb') as fp: 85 fp.write(resPage.content) 86 print(name,'下载完成') 87 88 if __name__=="__main__": 89 90 91 ''' 92 time_start = time.time() 93 init() 94 95 for dic in urls: 96 down(dic) 97 time_end = time.time() 98 print("%d second" % (time_end - time_start)) 99 ''' 100 101 102 time_start = time.time() 103 init() 104 mypool = Pool(4) 105 mypool.map(down, urls) 106 time_end = time.time() 107 108 mypool.close() 109 mypool.join() 110 print("%d second" % (time_end - time_start))
运行结果:(注意:运行结果的视频名称会不一样,因为视频更新,但是结果的形式一样)