最近,騰訊視頻上映了一部名為《傳聞中的陳芊芊》的網劇,又甜又虐又搞笑,就連我這個不怎么看劇的人,看了幾分鐘左右的剪輯就突然開始了我的追劇之路,劇情大概是男主…
呀!跑題了!差點就又開始了…/偷笑/偷笑
很多人學習python,不知道從何學起,
很多人學習python,掌握了基本語法過后,不知道在哪里尋找案例上手,
很多已經做案例的人,卻不知道如何去學習更加高深的知識,
那么針對這三類人,我給大家提供一個好的學習平臺,免費領取視頻教程,電子書籍,以及課程的源代碼!??¤
QQ群:623406465
想著最近也沒有什么事做,所以就寫一個實體吧,用來爬取這部電視劇,順便還可以看看劇,嘻嘻嘻…
事先說明: 我是以會員的身份看到了最新的一集,并非在此傳播盜版視頻,只是為了分享我的思路及方法, 所以我將所有的鏈接域名都進行了修改,
直達模塊:
- 匯入:分析及代碼實作:原始碼及運行程序示例:原始碼:運行程序示例:
匯入:
咳咳!言歸正傳,當我們在寫爬蟲實體時,就肯定會對每個網頁的鏈接進行分析,然后才能模擬請求,就 目前而言 ,根據我的 不完全統計 顯示,有以下三種情況:
在瀏覽器地址欄 顯示 域名加搜索的關鍵字(和其他引數):
https://www.*****.tv/tv/wNiNmMnRjZ.html?q=傳聞中的陳芊芊在瀏覽器地址欄 顯示 域名和加密后的搜索關鍵字(和其他引數):
https://www.*****.tv/tv/wNiNmMnRjZ.html?q=%E4%BC%A0%E9%97%BB%E4%B8%AD%E7%9A%84%E9%99%88%E8%8A%8A%E8%8A%8A在瀏覽器地址欄 顯示 的是經過網站處理過的鏈接,
其實這種處理并不是特別麻煩,是要你知道是怎么處理的(嘿嘿嘿,好像說了句廢話??????),這第三種正是我今天要說的內容之一,且聽我慢慢道來,
(其實這第一種和第二種也可以說成為是一種情況,因為 URL加密 是瀏覽器自動加密的,在模擬請求時不需要做任何處理,)
分析及代碼實作:
為了以后下載想看的電影或者電視劇,所以我直接從搜索影片入手:當我以 傳聞中 為關鍵詞搜索時:情況如下:
瀏覽器地址欄中的地址一看就知道,這并不僅僅是 URL加密 ,可是這又是怎么處理的呢?一開始我也是摸不著頭腦,看了原始碼中的幾個 JS 腳本,一直都沒有找到關鍵資訊,后來在我無意中發現:這里的地址和 加密后的URL 有關,如下:(忽略后綴)
偽原鏈接: https://www.****.tv/tv/wNiNmMnRjZ.html?q=傳聞中
URL加密: https://www.****.tv/tv/wNiNmMnRjZ.html?q=%E4%BC%A0%E9%97%BB%E4%B8%AD
此處鏈接: https://www.****.tv/tv/wNiNmMnRjZ.html?q=_E4_BC_A0_E9_97_BB_E4_B8_AD
你看出來了嗎?沒錯,困擾我這么幾個小時的疑惑總算是解決了,
此處的鏈接是將加密后的URL鏈接中的 “ % ” 更換為 “ _ ” ,最后再加上 “ .html ” 的后綴,
解決了這個問題,我們就來開始寫請求:先是沒頭沒腦的導庫,接著開始拼接成正確的網址:
from urllib.parse import quote def user_ui(): print('***********************************************************') keyword = '傳聞中' # input('請輸入搜索的視頻關鍵字:') url = 'https://www.****.tv/s/go.php?q=' + quote(keyword, 'utf-8') # 進行url加密 URL = (url.split('go.php?q=')[0] + url.split('go.php?q=')[1]).replace('%', '_') + '.html' # 根據網頁情況,更改加密后的url print(URL) user_ui ''' 輸出: https://www.****.tv/s/_E4_BC_A0_E9_97_BB_E4_B8_AD.html '''
注解: 匯入from urllib.parse import quote 庫,以便于我們 手動加密 原URL,
由于整個運行程序中需要多次發送請求,所以,為減少代碼量,直接寫一個請求函式:
import requests referer = 'https://www.****.tv' header = { 'referer': referer, 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37' } proxies = ['HTTP://60.13.42.120:9999', 'HTTP://163.204.244.207:9999', 'HTTP://113.121.39.121:9999', 'HTTP://125.117.134.99:9000', 'HTTP://123.169.114.81:9999', 'HTTP://58.253.159.230:9999', 'HTTP://182.46.99.247:9999', 'HTTP://171.15.48.235:9999', 'HTTP://120.83.105.226:9999', 'HTTP://113.124.95.210:9999', 'HTTP://171.13.103.241:9999', 'HTTP://123.163.96.42:9999', 'HTTP://27.42.168.46:49345', 'HTTP://122.234.27.22:9000', 'HTTP://125.108.73.47:9000', 'HTTP://125.108.97.209:9000', 'HTTP://113.124.85.24:9999', 'HTTP://27.220.51.228:9000', 'HTTP://113.124.84.205:9999', 'HTTP://110.243.31.147:9999'] def requests_url(url): # 請求網址 try: proxy = {'http': random.choice(proxies)} print(proxy) r = requests.get(url, proxies=proxy, headers=header) r.raise_for_status() r.encoding = 'UTF-8' return r except: print("*********請求失敗!***********")
經過以上處理后,我們已經獲得了搜索的正確鏈接和請求函式,接下來我們開始根據原始碼來提取出我們所需要的各種視頻資訊,保存在 videos_info 中:
開始提取資料:
html = requests_url(URL).text # 請求網頁 # 搜索時間以及數量 search_time = parsel.Selector(html).xpath('//div[@]/text()').extract()[0] # 視頻資訊標簽 videos_info = parsel.Selector(html).xpath('//dd').extract() print(search_time) print(videos_info)
本次以 傳聞中 作為關鍵詞搜索時只搜索到了 1 條記錄,但是如果以后搜索的記錄很多的話,就需要翻頁了,所以后期我在這里做了一點小改進:
html = requests_url(URL).text
# 搜索時間以及數量
search_time = parsel.Selector(html).xpath('//div[@]/text()').extract()[0]
# 視頻資訊標簽
videos_info = parsel.Selector(html).xpath('//dd').extract()
try: # 若搜索結果小于10項,則無頁碼資訊
print(search_time, end='')
# 頁碼
page_num = parsel.Selector(html).xpath('//div[@]/a/text()').extract()[-2]
print(',共{}頁,每頁10項,'.format(page_num))
except:
print(end='\n')
注解: 為什么要用 try…except 呢?
那是因為當搜索的結果每一頁顯示 10 條記錄,而當結果小于 10 條時,原始碼中并沒有出現顯示頁碼的標簽,
現在,我們已經獲取了搜索頁面的所有視頻的資訊標簽(此時只有一條),接下來就開始提取關鍵資訊:
- 由 <em>…</em> 標簽包裹起來的文字在網頁中顯示為紅色字體,即我們搜索的關鍵字,但是我們并不關心誰是關鍵字,所以用 replace 去掉此標簽, 之后用 BeautifulSoup庫 進行決議提取,并將提取的視頻名與拼接后的鏈接保存到 video 中,
注解: videos_info 中儲存的是搜索的所有記錄,而 video_info 中是某一條記錄,
def split_info(videos_info): num = 1 for video_info in videos_info: # 去掉關鍵字高亮標簽 info = str(video_info).replace('<em>', '').replace('</em>', '') # 去掉原始碼中紅色字體的標注 soup = BeautifulSoup(info, 'html.parser') info = soup.find_all('p') # 將通過關鍵字搜索到的視頻名與對應鏈接保存下來 # 對應鏈接為拼接后的完整鏈接,如:https://www.*****.tv/tv/wNiNmMnRjZ.html video[info[0].strong.a.string + info[0].span.string] = 'https://www.pianku.tv' + info[0].strong.a.attrs['href'] name2_info = info[1].string # 又名 area_info = info[2].string # 地區與型別 actor_info = info[3].string # 演員 introduction = info[4].string # 簡介 print('***********************************************************') print('{:0>2d}: '.format(num), end='') num += 1 print('\t' + info[0].strong.a.string + info[0].span.string) # 名稱 print('\t' + name2_info) print('\t' + area_info) print('\t' + actor_info) print('\t' + introduction)
運行結果:
接下來: 要處理的是:根據前面的序號來選擇下載的視頻:
怎么樣根據序號來選擇呢?
- 我們之前已經將視頻的名字與拼接后的鏈接保存到了 video字典 中,那么我們就可以通過視頻的名字來訪問該視頻的鏈接,
- 那么問題又來了,怎樣通過序號來確定視頻的名字呢?這首先啊,必須先通過 .keys() 來取出所有的名字,接著強轉為 list 型別,這樣的話不就可以通過下標來訪問了咩,
choice = input('\n請輸入序號選擇:')
NAME = list(video.keys())[int(choice) - 1] # 通過下標確定進一步搜索的類容
URL = video[NAME]
通過 input 輸入的資料為字串型別,需強轉為 int 型別,
當我們選擇好要下載視頻時,就可以通過名字來獲取視頻的主頁鏈接,之后就可以請求具體的視頻鏈接了:
如下圖:我們通過控制臺的預覽可以發現,請求當前的網址獲得的并不是我們所需要的!
- 但是啊,我們發現,按照原鏈接爬取的網頁原始碼之中,沒有任何與播放集數有關的資料,難道是 動態加載?
既然當前網頁中有資源下載與在線播放的鏈接,根據 可見既可爬的原理,我們得出:一定有來處! 只不過還需我們去尋找,
終于,在所有的請求里面,我們很容易的就找到了資料來源:
于是我們就拿到了下載的請求鏈接:
https://www.++++.tv/ajax/downurl/wNiNmMnRjZ_tv/
為了更方便的來理解并構造新的請求鏈接:我去查看了一下其他型別的鏈接,又發現了以下的幾種情況:
劇集:
https://www.++++.tv/tv/wNiNmMnRjZ.html (原鏈接)
https://www.++++.tv/ajax/downurl/wNiNmMnRjZtv/ (播放鏈接)
電影:
https://www.++++.tv/mv/wNjNWYyATY.html (原鏈接)
https://www.++++.tv/ajax/downurl/wNjNWYyATYmv/ (播放鏈接)
動漫:
https://www.++++.tv/ac/wNiFjMnNmM.html (原鏈接)
https://www.++++.tv/ajax/downurl/wNiFjMnNmM_ac/ (播放鏈接)
def get_video(url):
# 正確的鏈接拼接
URL = 'https://www.****.tv/ajax/downurl/' + url.split('/')[-1].split('.')[0] + '_'+url.split('/')[3]+'/' # 根據重定向拼接真實的鏈接
提示: 下載鏈接最后的 “ / ” ,是必須要加上的,沒有的話,依然會加載不出來,
乍一看這個鏈接,鏈接中包含 ajax 的字樣,我立馬就想到了 json 資料的處理,嘿嘿!機智的我,???????????????
可理想是美好的,現實總是不要太殘酷!
還沒有一分鐘我就把處理代碼寫好了,卻一直在報錯,(又是寫bug的一天呢!)
結果白白浪費了那幾分鐘,又回到剛剛分析的地方,查看了網頁的 回應 部分,結果:
這那里是什么 json 資料哦,明明還是一段不完整的 html 代碼,
我寫了個小例子來格式化這段代碼:
import requests from bs4 import BeautifulSoup url='https://www.****.tv/ajax/downurl/wNiNmMnRjZ_tv/' header = { 'referer': 'https://www.****.tv', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37' } r = requests.get(url, headers=header) soup=BeautifulSoup(r.text,'html.parser') print(soup.prettify())
結果如下:
然后再來重新寫這段代碼:
def get_video(url): # 根據重定向拼接真實的鏈接 URL = 'https://www.****.tv/ajax/downurl/' + url.split('/')[-1].split('.')[0] + '_'+url.split('/')[3]+'/' # 請求視頻鏈接 response = requests_url(URL) # 找到第一集的播放鏈接 soup = BeautifulSoup(response.text, 'html.parser').find('ul', attrs={'class': "player ckp"}).find('li').a.attrs['href'] # 重新拼接每一集主頁鏈接 URL1 = 'https://www.****.tv' + soup
知道了如何請求播放鏈接,就以第一集來作為例子進行分析:
在第一集的播放頁面中,捕獲到了兩個 index. m3u8 的檔案,我們知道這是視頻的一種格式,由多個 ts 檔案 組成,
我們再次查看 預覽 ,可以看到:其實第一個 index. m3u8 的內容指向了第二個 index. m3u8 檔案,而視頻的內容(ts 檔案)就儲存在這里面,
不信?那就看看吧:
- 經過驗證,的的確確是第二個沒錯了,所以,接下來的問題是:這個鏈接是怎么出來的?
打開 第一集 的網頁原始碼可以看到:這第一個 index. m3u8 檔案的鏈接就在源代碼中:
我們可以看到,每一集的 偽鏈接(第一個 index. m3u8 檔案) 都在這里了,
注解: 這里必須從第一集開始分析爬取,為什么? 那是因為當我們選擇的不是第一集的話,播放頁的原始碼中將會只含有從本集開始到最新一集的鏈接,之前的都沒有,這就是我一直強調第一集的原因,
至此,我們也看到了所有的鏈接都包含在 <script>…</script> 的標簽中,可是,標簽的內容全是字串,既不是 json 格式,也沒有網頁標簽,該如何提取呢?
答案是: 正則運算式!
通過 正則運算式,我們可以提取任意滿足運算式的字串,在這里,我用了很簡單的運算式來方便大家看懂:
def get_video(url): # 正確的鏈接拼接 URL = 'https://www.****.tv/ajax/downurl/' + url.split('/')[-1].split('.')[0] + '_'+url.split('/')[3]+'/' # 根據重定向拼接真實的鏈接 # 請求視頻鏈接 response = requests_url(URL) # 找到每一集的鏈接 soup = BeautifulSoup(response.text, 'html.parser').find('ul', attrs={'class': "player ckp"}).find('li').a.attrs['href'] # 重新拼接每一集主頁鏈接 URL1 = 'https://www.****.tv' + soup response1 = requests_url(URL1).text soup1 = BeautifulSoup(response1, 'html.parser').find_all('script')[12] # 獲取script標簽 # 通過正則運算式找出所有下載鏈接 p = re.compile(r"https://.*?/index.m3u8") information = p.findall(str(soup1)) num = 1 for info in information: download['第{}集'.format(num)] = str(info).replace('index.m3u8','1000k/hls/index.m3u8') num += 1
現在我們已經獲取到了每一集的第一個 index. m3u8 檔案鏈接,怎么才能跳轉到第二個 index. m3u8 檔案鏈接呢?
我們繼續觀察兩個鏈接的區別在哪里:
第一個:
https:// .com/20200518/7302_c75efddb/index.m3u8
第二個:
https://.com/20200518/7302_c75efddb/ 1000k/hls/ index.m3u8
看出來了嗎?對了,就是增添了路徑而已,這樣的話,繼續添加代碼:
def get_video(url): ''' 與上面的代碼一樣,所以就省略了 ''' response1 = requests_url(URL1).text # 獲取script標簽 soup1 = BeautifulSoup(response1, 'html.parser').find_all('script')[12] # 通過正則運算式找出所有下載鏈接 p = re.compile(r"https://.*?/index.m3u8") information = p.findall(str(soup1)) num = 1 for info in information: # download = {} 是全域變數 download['第{}集'.format(num)] = str(info).replace('index.m3u8','1000k/hls/index.m3u8') num += 1
到目前為止,我們已經獲得了所有的 m3u8 格式的真實鏈接,接下來就是下載的問題了,簡單點就是通過 ffmpy3 下載,這里我就不過多的講了,因為在我之前的文章中有寫到使用方法:Python 爬蟲用最普通的方法爬取ts檔案并合成為mp4格式,
原始碼及運行程序示例:
原始碼:
import re import os import time import ffmpy3 import random import parsel import requests from bs4 import BeautifulSoup from urllib.parse import quote from multiprocessing.pool import ThreadPool referer = 'https://www.****.tv' header = { 'referer': referer, 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37' } proxies = ['HTTP://60.13.42.120:9999', 'HTTP://163.204.244.207:9999', 'HTTP://113.121.39.121:9999', 'HTTP://125.117.134.99:9000', 'HTTP://123.169.114.81:9999', 'HTTP://58.253.159.230:9999', 'HTTP://182.46.99.247:9999', 'HTTP://171.15.48.235:9999', 'HTTP://120.83.105.226:9999', 'HTTP://113.124.95.210:9999', 'HTTP://171.13.103.241:9999', 'HTTP://123.163.96.42:9999', 'HTTP://27.42.168.46:49345', 'HTTP://122.234.27.22:9000', 'HTTP://125.108.73.47:9000', 'HTTP://125.108.97.209:9000', 'HTTP://113.124.85.24:9999', 'HTTP://27.220.51.228:9000', 'HTTP://113.124.84.205:9999', 'HTTP://110.243.31.147:9999'] path = './Spider/' video = {} # 保存搜索記錄 download = {} # 保存下載鏈接 def requests_url(url): # 請求網址 try: proxy = {'http': random.choice(proxies)} print(proxy) r = requests.get(url, proxies=proxy, headers=header) r.raise_for_status() r.encoding = 'UTF-8' return r except: print("*********請求失敗!***********") def split_info(videos_info): num = 1 for video_info in videos_info: info = str(video_info).replace('<em>', '').replace('</em>', '') # 去掉原始碼中紅色字體的標注 soup = BeautifulSoup(info, 'html.parser') info = soup.find_all('p') # 將通過關鍵字搜索到的視頻名與對應鏈接保存下來 (對應鏈接為拼接后的完整鏈接,如:https://www.****.tv/tv/wNiNmMnRjZ.html) video[info[0].strong.a.string + info[0].span.string] = 'https://www.pianku.tv' + info[0].strong.a.attrs['href'] name2_info = info[1].string # 又名 area_info = info[2].string # 地區與型別 actor_info = info[3].string # 演員 introduction = info[4].string # 簡介 print('***********************************************************') print('{:0>2d}: '.format(num), end='') num += 1 print('\t' + info[0].strong.a.string + info[0].span.string) # 名稱 print('\t' + name2_info) print('\t' + area_info) print('\t' + actor_info) print('\t' + introduction) def get_video(url): global referer referer = url # 根據重定向拼接真實的鏈接 URL = 'https://www.****.tv/ajax/downurl/' + url.split('/')[-1].split('.')[0] + '_' + url.split('/')[3] + '/' # 請求視頻鏈接 response = requests_url(URL) soup = \ BeautifulSoup(response.text, 'html.parser').find('ul', attrs={'class': "player ckp"}).find('li').a.attrs[ 'href'] # 找到每一集的鏈接 URL1 = 'https://www.****.tv' + soup # 重新拼接每一集主頁鏈接 response1 = requests_url(URL1).text soup1 = BeautifulSoup(response1, 'html.parser').find_all('script')[12] # 獲取script標簽 # 通過正則運算式找出所有下載鏈接 p = re.compile(r"https://.*?/index.m3u8") information = p.findall(str(soup1)) num = 1 for info in information: download['第{}集'.format(num)] = str(info).replace('index.m3u8', '1000k/hls/index.m3u8') num += 1 def video_download(name): # 通過ffmpy3下載 try: if os.path.exists(path + NAME + '/'): pass else: os.makedirs(path + NAME + '/') ffmpy3.FFmpeg(inputs={download[name]: None}, outputs={path + NAME + '/' + name + '.mp4': None}).run() print('************' + name + '下載成功!' + '************') except: print('============' + name + '下載失敗!' + '============') def user_ui(): global NAME, page_num print('***********************************************************') keyword = '傳聞中' # input('請輸入搜索的視頻關鍵字:') url = 'https://www.****.tv/s/go.php?q=' + quote(keyword, 'utf-8') # 進行url加密 URL = (url.split('go.php?q=')[0] + url.split('go.php?q=')[1]).replace('%', '_') + '.html' # 根據網頁,更改加密后的url html = requests_url(URL).text search_time = parsel.Selector(html).xpath('//div[@]/text()').extract()[0] # 搜索時間以及數量 videos_info = parsel.Selector(html).xpath('//dd').extract() # 視頻資訊標簽 try: # 若搜索結果小于10項,則無頁碼資訊 print(search_time, end='') page_num = parsel.Selector(html).xpath('//div[@]/a/text()').extract()[-2] print(',共{}頁,每頁10項,'.format(page_num)) except: print(end='\n') split_info(videos_info) # 拆分顯示視頻資訊 choice = input('\n請輸入序號選擇:') NAME = list(video.keys())[int(choice) - 1] # 通過下標確定進一步搜索的類容 URL = video[NAME] get_video(URL) # 獲取下載鏈接 user_ui() time1 = time.time() pool = ThreadPool(10) # 開啟執行緒池 results = pool.map(video_download, download.keys()) pool.close() pool.join() time2 = time.time() print('*********耗時:{}*********'.format(time2 - time1))
運行程序示例:
最后的最后:來點小福利:
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/182731.html
標籤:Python
