干活干活,區區懶癌已經阻擋不了澎湃的洪荒之力了......
運行環境:Windows基於python3.6
-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------
抓取視頻時遇到M3U8的確挺煩人的,去年年底實習,由於項目需求所以和一個同事主攻蟒蛇爬蟲,抓取含有清晰人臉的圖片和視頻,在爬取一些視頻網站和直播網站時就被“它”糊了一臉,作為一只,呃,不對,是兩只剛入爬蟲坑的菜鳥,在視頻加載播放時找不到啥關於的.mp4,.MKV的鏈接,反而出現不少.ts的鏈接,然后爬取找度娘,重點如下:
M3U8是蘋果公司推出一種視頻播放標准,是一種文件檢索格式,將視頻切割成一小段一小段的TS格式的視頻文件,然后存在服務器中(現在為了減少I / O訪問次數,一般存在服務器的內存中),通過M3U8解析出來路徑,然后去請求,是現在比較流行的一種加載方式,諸如騰訊視頻之類大多都是切割成TS流進行加載。
但當時還是暈,理論和實際處理是兩回事,那時候關於如何爬取M3U8的博客不是很多而且很少說到重點(給俺下載代碼,理論改天再補)樣么是其它語言寫的更懵,近來倒是越來越多了(可惜當時沒趕上),最后找到篇關於下載的卻發現是python2的代碼,因為那時候剛開始使用蟒之前學的是Java中,所以調了好一會還是運行失敗,但是也收獲了不少,之后和同事研究了兩天試驗了幾個方案最終確定采用邊下邊合的方式進行處理,上個月底從公司離職了,由於公司管理較為嚴格禁止向外網(公司局域網以外)發送消息或拷貝文件,(嗯,拍照也不行,就似〜這么嚴)所以這幾天晚上下班回來抽空寫寫改改又另起了爐灶,代碼如下,隨意寫的所以異常處理和模擬瀏覽器啥的就沒加了(懶)!
注:僅限窗口下使用,如果要在Linux的上使用需要修改合並命令,嗯,或者等幾天我再來篇兼容的
# !/user/bin/env python
# -*- coding: utf-8 -*-
# au: caopeiya
# 20180808
import os, shutil
import urllib.request, urllib.error, requests
# 打開並讀取網頁內容
def getUrlData(url):
try:
urlData = urllib.request.urlopen(url, timeout=20) # .read().decode('utf-8', 'ignore')
return urlData
except Exception as err:
print(f'err getUrlData({url})\n', err)
return -1
# 下載文件-urllib.request
def getDown_urllib(url, file_path):
try:
urllib.request.urlretrieve(url, filename=file_path)
return True
except urllib.error.URLError as e:
# hasttr(e, 'code'),判斷e 是否有.code屬性,因為不確定是不是HTTPError錯誤,URLError包含HTTPError,但是HTTPError以外的錯誤是不返回錯誤碼(狀態碼)的
if hasattr(e, 'code'):
print(e.code) # 打印服務器返回的錯誤碼(狀態碼),如403,404,501之類的
elif hasattr(e, 'reason'):
print(e.reason) # 打印錯誤原因
def getVideo_urllib(url_m3u8, path, videoName):
print('begin run ~~\n')
# urlData = getUrlData(url_m3u8).readlines()
urlData = getUrlData(url_m3u8)
num = 0
tempName_video = os.path.join(path, f'{videoName}.ts') # f'{}' 相當於'{}'.format() 或 '%s'%videoName
# print(urlData)
for line in urlData:
# 解碼,由於是直接使用了所抓取的鏈接內容,所以需要按行解碼,如果提前解碼則不能使用直接進行for循環,會報錯
# 改用上面的readlines()或readline()也可以,但更繁瑣些,同樣需要按行解碼,效率更低
url_ts = line.decode('utf-8')
tempName_ts = os.path.join(path, f'{num}.ts') # f'{}' 相當於'{}'.format()
if not '.ts' in url_ts:
continue
else:
if not url_ts.startswith('http'): # 判斷字符串是否以'http'開頭,如果不是則說明url鏈接不完整,需要拼接
# 拼接ts流視頻的url
url_ts = url_m3u8.replace(url_m3u8.split('/')[-1], url_ts)
print(url_ts)
getDown_urllib(url_ts, tempName_ts) # 下載視頻流
if num == 0:
# 重命名,已存在則自動覆蓋
shutil.move(tempName_ts, tempName_video)
num += 1
continue
cmd = f'copy /b {tempName_video}+{tempName_ts} {tempName_video}'
res = os.system(cmd)
if res == 0:
os.system(f'del {tempName_ts}')
if num == 20: # 限制下載的ts流個數,這個視頻挺長有四百多個.ts文件,所以限制一下
break
num += 1
continue
print(f'Wrong, copy {num}.ts-->{videoName}.ts failure')
return False
os.system(f'del {path}/*.ts') # 調用windows命令行(即cmd)工具,運行命令
filename = os.path.join(path, f'{videoName}.mp4')
shutil.move(tempName_video, filename)
print(f'{videoName}.mp4 finish down!')
if __name__ == '__main__':
url_m3u8 = 'http://wscdn.alhls.xiaoka.tv/201886/2f5/75a/HoHdTc1LjUaBjZbJ/index.m3u8'
path = r'D:\videos'
videoName = url_m3u8.split('/')[-2]
getVideo_urllib(url_m3u8, path, videoName)
注:修改文件名時,特意選擇shutil模塊(可以看作操作系統的高級版)的移動方法,雖然移動主要是用來移動文件的,重命名算是附帶的,不過強制覆蓋的特點在這里很有用,避免中斷后重新下載時重命名產生異常。
PS:說來有趣,7月31號,也就是離職的那天上午,靈感突顯,利用請求下載文件的寫入特點,徹底解決了調用命令行導致的不兼容的窗口以外環境的問題,哈哈,所以下一篇就它了。
