介紹:
在自學爬蟲時,一開始就是爬取一些文字(比如小說之類的),再后來變成了圖片,再后來變成了視頻…
其中最簡單的就是對文字的爬取了,從文字過渡到圖片也只是多了幾行代碼而已,但是: 從圖片過渡到視頻就要 分情況了,
很多人學習python,不知道從何學起,
很多人學習python,掌握了基本語法過后,不知道在哪里尋找案例上手,
很多已經做案例的人,卻不知道如何去學習更加高深的知識,
那么針對這三類人,我給大家提供一個好的學習平臺,免費領取視頻教程,電子書籍,以及課程的源代碼!??¤
QQ群:623406465
分情況解釋:
第一種情況: 鏈接明確是以 mp4、mkv、rmvb 這類視頻格式后綴為結尾的鏈接,這種下載很簡單,和圖片下載的方法一樣,就是視頻檔案要比圖片大而已,
第二種情況: 另一種,鏈接是以 m3u8 這類以多個 ts 檔案組成的鏈接,
ts檔案 ? 鏈接: 百度知道,
然而,在進行爬取的程序中,你會發現:第二種情況又有兩種呈現方式:
第一種: 網頁原始碼中的鏈接直接以 m3u8 結尾,這類的鏈接,推薦使用 FFmpeg 進行下載,(注意該工具下載成功后需要配置環境變數才可以使用,)
使用方法:
import ffmpy3
ffmpy3.FFmpeg(inputs={'http://***.m3u8': None}, outputs={name+'.mp4':None}).run()
FFmpeg 可以幫你下載 m3u8 格式的視頻,而且還能幫你自動轉換為 mp4 的格式,并且不會出現拼接 ts 檔案時亂序的情況,
第二種: 也是我今天著重說的一種情況,網頁原始碼中并沒有以 m3u8 格式結尾的鏈接,而是由在網頁中直接請求并決議 ts 檔案,
比如以下的視頻網站:
?

?
分析及爬取:
我們就在當前網站爬取一個前段時間熱播的動漫:《天氣之子》 來演示用最為基礎的 with open() 來爬取 ts 檔案并合成為 mp4 格式的方法,
進入 天氣之子 的主頁,通過控制臺我們需要構建一點簡單的頭檔案來防止反爬: (主鏈接被我修改了,怕被和諧,這里主要分享的是方法,)
header = {
'origin': 'https://www.******.tv',
'referer': 'https://www.******.tv/py/lJWMpVmYqRWb_1.html?158064',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.72'
}
由于我曾經被拉進過網站的黑名單: 所以還是默默的寫個代理 IP 吧,
?
不會寫 IP代理 ? 鏈接: Python爬蟲,自建IP地址池.
proxies = ['HTTP://110.243.30.23:9999', 'HTTP://222.189.191.206:9999', 'HTTP://118.212.104.138:9999',
'HTTP://182.149.83.97:9999', 'HTTP://106.42.163.100:9999', 'HTTP://120.83.107.69:9999',
'HTTP://60.13.42.135:9999', 'HTTP://60.205.188.24:3128', 'HTTP://113.195.232.23:9999',
'HTTP://59.62.36.74:9000', 'HTTP://218.2.226.42:80']
proxy = {'HTTP': random.choice(proxies)} #隨機選取一個IP
通過點擊每一個 ts 檔案,我們發現,對于每一個 ts 檔案的請求地址都差不多:具體為:
https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb180400(****).ts
括號中的 * 代表數字,如: 0001、0002、0003…9999
通過拉動進度條可以快速地查看,最大的數字為 1613,
我們來試著請求 一下某段 ts 檔案:
import os import requests import random header = { 'origin': 'https://www.pianku.tv', 'referer': 'https://www.pianku.tv/py/lJWMpVmYqRWb_1.html?158064', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.72' } proxies = ['HTTP://110.243.30.23:9999', 'HTTP://222.189.191.206:9999', 'HTTP://118.212.104.138:9999', 'HTTP://182.149.83.97:9999', 'HTTP://106.42.163.100:9999', 'HTTP://120.83.107.69:9999', 'HTTP://60.13.42.135:9999', 'HTTP://60.205.188.24:3128', 'HTTP://113.195.232.23:9999', 'HTTP://59.62.36.74:9000', 'HTTP://218.2.226.42:80'] proxy = {'HTTP': random.choice(proxies)} print(proxy) path = './Spider/' if os.path.exists(path + "天氣之子/"): pass else: os.makedirs(path + "天氣之子/") def download(): url = 'https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb1804000001.ts' with open(path + "天氣之子/" + str(url).split('/')[-1][-7:], 'wb') as f: #已知最大的數字為 1613 ,所以以最后4位數字作為 ts 檔案的名字, try: r = requests.get(url, proxies=proxy, headers=header) r.raise_for_status() r.encoding = 'utf-8' f.write(r.content) except: print('請求失敗!') if __name__ == '__main__': download()
于是我們就得到了第一個 ts 檔案:
?
雙擊查看,結果是:可以打開播放:
?
接下來我們試著將所有的 ts 檔案下載下來,并且以最后4位數字作為 ts 檔案的名字:
首先想到的就是用 for 回圈,問題倒是沒有,就是太慢了,所以改進為使用執行緒池來下載:
# # 前面的部分不變,在此省略 # def download(num): # https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb180400(****).ts url = 'https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb180400{:0>4d}.ts'.format(num) #在這里用了 str.format() 中的 {:0>4d} 來控制位數 with open(path + "天氣之子/" + str(url).split('/')[-1][-7:], 'wb') as f: try: r = requests.get(url, proxies=proxy, headers=header) r.raise_for_status() r.encoding = 'utf-8' f.write(r.content) print('正在下載第 {} 個片段,'.format(num)) except: print('請求失敗!') if __name__ == '__main__': # 開啟執行緒池 start_time = time.time() pool = ThreadPool(100) results = pool.map(download, range(1, 1613+1)) # range含左不含右 pool.close() pool.join()
但是: 一波未平一波又起,下載時,有些片段會秒下載,而有些片段則會下載的特別慢,甚至有些片段直接下載失敗!!!!!
如果沒有某個片段,劇情不就會跳著走嗎?這可咋整,剛進入劇情,正為男女主角捏把汗的時候,一下就給我踢出來了,滿頭黑人問號,氣得我直介面吐芬芳,
?
解決方案: 我們知道,當我們修改最后的四位數字時,它會直接跳轉下載對應的片段,難道要一個一個的去看 ts 檔案?不、不、不,當然不需要,
接下來,看看我的簡單想法: 創建一個 list 將所有失敗的尾數全都追加進去,在第一次程式下載結束時,僅對該 list 中對應的 ts 檔案進行下載:
# # 前面的部分不變,在此省略 # def download(num, flag=0): # # https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb1804001613.ts url = 'https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb180400{:0>4d}.ts'.format(num) with open(path + "天氣之子/" + str(url).split('/')[-1][-7:], 'wb') as f: try: r = requests.get(url, proxies=proxy, headers=header, timeout=5) #對于下載比較慢的情況,設定 timeout = 5 r.raise_for_status() r.encoding = 'utf-8' print('正在下載第 {} 個片段,'.format(num)) f.write(r.content) if flag == 1: # 對于從 list 中第二次下載的情況,下載成功,則從 list 中刪去, failure_list.remove(num) except: print('請求失敗!') if num not in failure_list: failure_list.append(num) # 如果下載失敗或者超時,則添加進 list def check_ts(): print("開始檢查:") while failure_list: #()、{}、[] 都相當于 false for num in failure_list: download(num, 1) # 傳入標識 1 if __name__ == '__main__': # 開啟執行緒池 start_time = time.time() pool = ThreadPool(100) results = pool.map(download, range(1, 1613+1)) pool.close() pool.join() check_ts()
結果如下:
?
ts 檔案的合并與轉換為 mp4 格式:
經過小小的等待,現在,我們已經下載完成了所有的 ts 檔案,
下載完成,有人會說:誒~, ts 檔案都是可以播放的,直接用播放器順序播放不久行了嘛,
但是: 我還沒發現有哪款視頻播放器能做到完全無卡頓感的切換這么多的視頻,每幾秒鐘就 “ 卡頓 ” 一下,觀影效果大打折扣,
所以還是乖乖的把它們都合并了吧,至于怎么合并,且聽我慢慢道來:
第一步:我們先定義一個用于合并 ts 檔案的函式:def get_video():
第二步:利用 files = os.listdir( path ) 拿到所有的檔案名,所有的 ts 檔案都是從小到大排列的,所以我們拿到的檔案名也是有順序的,
第三步:遍歷每一個檔案名, 通過 with open() 以二進制的方式打開并讀取該檔案(f1),接著再使用一個 with open()打開目標檔案(f2),將從 f1 中讀取出來的二進制檔案以二進制追加寫的方式寫入到 f2 中 ,
遍歷時用 tqdm 庫進行顯示進度條,結果因為轉換的速度太快,根本看不到進度條時怎么走的,幾乎是秒轉換,所以:我是為了什么加的進度條來著???????
def get_video(): files = os.listdir(path + "天氣之子/") for file in tqdm(files, desc="正在轉換視頻格式:"): if os.path.exists(path + "天氣之子/" + file): with open(path + "天氣之子/" + file, 'rb') as f1: with open(path + "天氣之子.mp4", 'ab') as f2: f2.write(f1.read()) else: print("失敗")
運行結果:

?
雙擊查看,結果是:可以打開播放: (快樂呀!)

?
快樂的白嫖生活就要開始了!
?
原始碼如下:
import os import requests import random from multiprocessing.pool import ThreadPool from tqdm import tqdm header = { 'origin': 'https://www.pianku.tv', 'referer': 'https://www.pianku.tv/py/lJWMpVmYqRWb_1.html?158064', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.72' } proxies = ['HTTP://110.243.30.23:9999', 'HTTP://222.189.191.206:9999', 'HTTP://118.212.104.138:9999', 'HTTP://182.149.83.97:9999', 'HTTP://106.42.163.100:9999', 'HTTP://120.83.107.69:9999', 'HTTP://60.13.42.135:9999', 'HTTP://60.205.188.24:3128', 'HTTP://113.195.232.23:9999', 'HTTP://59.62.36.74:9000', 'HTTP://218.2.226.42:80'] proxy = {'HTTP': random.choice(proxies)} print(proxy) path = './Spider/' if os.path.exists(path + "天氣之子/"): pass else: os.makedirs(path + "天氣之子/") failure_list = [] # 保存下載失敗的片段 def download(num, flag=0): # https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb1804001613.ts url = 'https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb180400{:0>4d}.ts'.format(num) with open(path + "天氣之子/" + str(url).split('/')[-1][-7:], 'wb') as f: try: r = requests.get(url, proxies=proxy, headers=header, timeout=5) r.raise_for_status() r.encoding = 'utf-8' print('正在下載第 {} 個片段,'.format(num)) f.write(r.content) if flag == 1: failure_list.remove(num) except: print('請求失敗!') if num not in failure_list: failure_list.append(num) def get_video(): files = os.listdir(path + "天氣之子/") for file in tqdm(files, desc="正在轉換視頻格式:"): if os.path.exists(path + "天氣之子/" + file): with open(path + "天氣之子/" + file, 'rb') as f1: with open(path + "天氣之子.mp4", 'ab') as f2: f2.write(f1.read()) else: print("失敗") def check_ts(): print("開始檢查:") while failure_list: for num in failure_list: download(num, 1) print("ts 文件下載完成!") get_video() if __name__ == '__main__': # 開啟執行緒池 pool = ThreadPool(100) results = pool.map(download, range(1, 1613+1)) pool.close() pool.join() check_ts()
運行時示例:
正在下載第 396 個片段,
正在下載第 31 個片段,
,,,,,,
正在下載第 257 個片段,
請求失敗!
正在下載第 1570 個片段,
正在下載第 1585 個片段,
開始檢查:
請求失敗!
正在下載第 141 個片段
正在下載第 855 個片
,,,,,,
請求失敗!
,,,,,,
正在下載第 945 個片段,
正在下載第 485 個片段,
正在轉換視頻格式:: 100%|██████████| 1613/1613 [00:03<00:00, 496.47it/s]
共耗時:195.7295339107513
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/182734.html
標籤:Python
