前兩天忽然間腦子抽風想要用python來爬一下視頻網站,獲取視頻。一開始無從下手,在網上搜了不少相關的博客,然而也並未找到一個理想的解決方案,可是好在最終可以將視頻網站的視頻給爬下來,儘管吃相難看了點。特此將整個過程以及思考給記錄下來。html
個人目標是爬取騰訊視頻的視頻內容,在網上搜索出來的結果是利用第三方解析網站對視頻進行解析,而後在爬取,這是最簡單的解決方案。因而乎也就照搬照作了。詳細過程以下:python
打開:http://jx.618g.com/?url=這個第三方解析網站,將待解析的視頻url加在後面就好了。如:https://jx.618g.com/?url=https://v.qq.com/x/cover/c949qjcugx9a7gh.htmlapi
這個時候對https://jx.618g.com/?url=https://v.qq.com/x/cover/c949qjcugx9a7gh.html進行抓包。會發現有不少的.ts的文件,後來由查看了不少相關的博客知道了原來這些.ts的文件就是咱們要抓去的對象。關於詳細的介紹.m3u8以及.ts文件推薦一篇博客給你們,若是不懂的話能夠去看看http://www.javashuo.com/article/p-xuctieys-mp.html。緩存
仔細的查看下這些ts文件發現並無須要攜帶的其餘參數直接訪問其url即可實現下載,固然了下載下來的也只是一小段視頻片斷。網絡
按照網上的一種作法直接請求這些url將其下載下來而後在合併成一個完整的視頻片斷。這種作法的代碼我先貼出來以供參考。多線程
1 import time 2 import requests 3 4 5 def loder(i): 6 """直接請求ts文件的url而後在寫入到本地""" 7 url = 'https://doubanzyv3.tyswmp.com:888/2018/12/12/UEtWtHwTc0UniIDQ/out%03d.ts' % i # %03d 左邊補0方式 8 html = requests.get(url).content 9 10 with open(r"D:\txsp_test\%s%03d.ts" % ("a", i), "wb") as f: 11 f.write(html) 12 13 14 if __name__ == "__main__": 15 pool.map(loder, range(400)) 16 pool.join() 17 pool.close()
這裏你可使用多進程或者多線程來進行優化,我在這裏就不將代碼貼出來了。經過這種方式下載下來的ts文件極可能會由於網絡的問題出現漏下,少下的狀況,爲此我也是嘗試了各類方法都沒有找到一個最優解。我嘗試了一個方法是爲進程加鎖,以信號量的形式對文件進行下載,確保ts文件的完整,同時也能保證異步、併發。可是這樣作的話就至關於開啓了400個進程。你的內存必定會溢出。(有興趣的能夠試下線程鎖)併發
1 import time 2 import requests 3 4 5 def loder(i, sem): 6 """直接請求ts文件的url而後在寫入到本地""" 7 sem.acquire() # 獲取鑰匙 8 url = 'https://doubanzyv3.tyswmp.com:888/2018/12/12/UEtWtHwTc0UniIDQ/out%03d.ts' % i # %03d 左邊補0方式 9 html = requests.get(url).content 10 11 with open(r"D:\txsp_test\%s%03d.ts" % ("a", i), "wb") as f: 12 f.write(html) 13 sem.release() 14 15 16 if __name__ == "__main__": 17 start_time = time.time() 18 print(start_time) 19 sem = Semaphore(5) # 規定鎖的個數 20 # pool = Pool(5) 21 p_l = [] 22 for i in range(400): 23 p = Process(target=loder, args=(i, sem)) 24 p.start() 25 p_l.append(p) 26 for i in p_l: 27 i.join() 28 print(time.time()-start_time)
而後在對下載好的文件進行合成app
1 file_dir = r"D:\txsp_test" # 文件的保存路徑 2 new_file = u"%s\out.ts" % file_dir # 合併以後的視頻 3 f = open(new_file, 'wb+') # 二進制文件寫操做 4 5 for i in range(0, 338): 6 file_path = r"D:\txsp_test\%s%03d.ts" % ("a", i) # 視頻片斷名稱 7 print(file_path) 8 for line in open(file_path, "rb"): 9 f.write(line) 10 f.flush() 11 12 f.close()
ok上面的是一種作法,很顯然能夠將視頻的下載與合併發在一塊兒。我也不貼出來了。接下來就是另一種作法。異步
這種作法相對來講更加的合理,就是先找到.m3u8的文件。scrapy
而後向其發送get請求,獲得的響應結果就是一段段的.ts集合。(不知道我在說什麼的請看上面的博客,或者本身動手requests.get()試下)這時能夠經過正則匹配出.ts的文件而後在下載下來。最後合併成完整的視頻。
代碼以下:(採用了多線程對.ts文件進行下載)
1 import re 2 import os 3 import shutil 4 from concurrent.futures import ThreadPoolExecutor 5 from urllib.request import urlretrieve 6 7 import requests 8 from scrapy import Selector 9 10 11 class VideoDownLoader(object): 12 def __init__(self, url): 13 self.api = 'https://jx.618g.com' 14 self.get_url = 'https://jx.618g.com/?url=' + url 15 self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) ' 16 'Chrome/63.0.3239.132 Safari/537.36'} 17 18 self.thread_num = 32 19 self.i = 0 20 html = self.get_page(self.get_url) 21 if html: 22 self.parse_page(html) 23 24 def get_page(self, get_url): 25 """獲取網頁""" 26 try: 27 print('正在請求目標網頁....', get_url) 28 response = requests.get(get_url, headers=self.headers) 29 if response.status_code == 200: 30 # print(response.text) 31 print('請求目標網頁完成....\n準備解析....') 32 self.headers['referer'] = get_url 33 return response.text 34 except Exception: 35 print('請求目標網頁失敗,請檢查錯誤重試') 36 return None 37 38 def parse_page(self, html): 39 """解析網頁""" 40 print('目標信息正在解析........') 41 selector = Selector(text=html) 42 self.title = selector.xpath("//head/title/text()").extract_first() # 獲取標題(電影名稱) 43 print(self.title) 44 m3u8_url = selector.xpath("//div[@id='a1']/iframe/@src").extract_first()[14:] # 獲取視頻地址(m3u8) 45 self.ts_list = self.get_ts(m3u8_url) # 獲得一個包含ts文件的列表 46 print('解析完成,下載ts文件.........') 47 self.pool() 48 49 def get_ts(self, m3u8_url): 50 """解析m3u8文件獲取ts文件""" 51 try: 52 response = requests.get(m3u8_url, headers=self.headers) 53 html = response.text 54 print('獲取ts文件成功,準備提取信息') 55 ret_list = re.findall("(out.*?ts)+", html) # 匹配.ts的字段 56 ts_list = [] 57 for ret in ret_list: 58 ts_url = m3u8_url[:-13] + ret 59 ts_list.append(ts_url) 60 return ts_list 61 except Exception: 62 print('緩存文件請求錯誤1,請檢查錯誤') 63 64 def pool(self): 65 print('經計算須要下載%d個文件' % len(self.ts_list)) 66 if self.title not in os.listdir(): 67 os.mkdir(r"D:" + self.title) # 新建視頻目錄 68 print('正在下載...所需時間較長,請耐心等待..') 69 # 開啓多進程下載 70 pool = pool = ThreadPoolExecutor(max_workers=16) # 多線程下載 71 pool.map(self.save_ts, self.ts_list) 72 pool.shutdown() 73 print('下載完成') 74 self.ts_to_mp4() 75 76 def ts_to_mp4(self): 77 print('ts文件正在進行轉錄mp4......') 78 str = 'copy /b ' + self.title+'\*.ts ' + self.title + '.mp4' # copy /b 命令 79 os.system(str) 80 filename = self.title + '.mp4' 81 if os.path.isfile(filename): 82 print('轉換完成,祝你觀影愉快') 83 shutil.rmtree(self.title) 84 85 def save_ts(self, ts_list): 86 print(self.title) 87 self.i += 1 88 print('當前進度%d' % self.i) 89 urlretrieve(url=ts_list, filename=r"D:" + self.title + '\{}'.format(ts_list[-9:])) 90 91 92 if __name__ == '__main__': 93 url = "https://v.qq.com/x/cover/c949qjcugx9a7gh.html" # 視頻url 94 video_down_loader = VideoDownLoader(url)
運行代碼,喝一杯coffee等待10來分鐘視頻就自動下載好了。可是這裏依然會存在這下載下來的.ts文件不完整的狀況,博客寫到這裏我腦海裏面又想到了一種解決方法,明天試一試把。哦對了,關於視頻的文件下載多線程,多進程我分別都試過,二者的下載速度區別並不大,由於這涉及到了網絡的請求以及文件的讀寫等IO操做。因此採用多線程/進程沒啥區別,建議仍是用多線程來。結果以下所示:
1 # 下載400個.ts文件測試線程、進程的性能 2 3 # >>> multiprocessing ——> 196.01457595825195 默認開啓4個進程 4 # >>> multiprocessing ——> 196.01457595825195 強制開啓16個進程,實際上5個 5 # >>> threading ——> 174.57704424858093 默認開啓4個線程 6 # >>> threading ——> 202.30066895484924 默認開啓40個線程(網絡卡頓) 7 # >>> threading ——> 155.5946135520935 默認開啓16個線程
測試的結果表名,線程開啓的速度確實比進程開啓速度快。然並卵!