一、梨視頻獲取分析、猜想、思考過程以及解決方案
-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))
運行結果:(注意:運行結果的視頻名稱會不一樣,因為視頻更新,但是結果的形式一樣)