天天看點

Python3——根據m3u8下載下傳視訊(上)之urllib.request

    幹活幹活,區區懶癌已經阻擋不了澎湃的洪荒之力了。。。   

運作環境:Windows 基于python3.6

     ------------------------------------------------------------------------------------------------------------------------------------------------------------------------

        抓取視訊時遇到m3u8的确挺煩人的,去年年底實習,由于項目需求是以和一個同僚主攻python爬蟲,抓取含有清晰人臉的圖檔和視訊,在爬取一些視訊網站和直播網站時就被“它”糊了一臉,作為一隻,呃,不對,是兩隻剛入爬蟲坑的菜鳥,在視訊加載播放時找不到啥關于.mp4、.mkv的連結,反而出現不少.ts的連結,然後爬取找度娘,重點如下:

        m3u8是蘋果公司推出一種視訊播放标準,是一種檔案檢索格式,将視訊切割成一小段一小段的ts格式的視訊檔案,然後存在伺服器中(現在為了減少I/o通路次數,一般存在伺服器的記憶體中),通過m3u8解析出來路徑,然後去請求,是現在比較流行的一種加載方式,諸如騰訊視訊之類大多都是切割成ts流進行加載。

        但當時還是暈,理論和實際處理是兩回事,那時候關于如何爬取m3u8的部落格不是很多而且很少說到重點(給俺下載下傳代碼,理論改天再補)樣麼是其它語言寫的更懵,近來倒是越來越多了(可惜當時沒趕上),最後找到篇關于下載下傳的卻發現是python2的代碼,因為那時候剛開始使用python之前學的是Java,是以調了好一會還是運作失敗,但是也收獲了不少,之後和同僚研究了兩天試驗了幾個方案最終确定采用邊下邊合的方式進行處理,上個月底從公司離職了,由于公司管理較為嚴格禁止向外網(公司區域網路以外)發送消息或拷貝檔案,(嗯,拍照也不行,就似~這麼嚴)是以這幾天晚上下班回來抽空寫寫改改又另起了爐竈,代碼如下,随意寫的是以異常處理和模拟浏覽器啥的就沒加了(懶)!

注:僅限windows下使用,如果要在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子產品(可以看作os的進階版)的move方法,雖然move主要是用來移動檔案的,重命名算是附帶的,不過強制覆寫的特點在這裡很有用,避免中斷後重新下載下傳時重命名産生異常。

ps:說來有趣,7月31号,也就是離職的那天上午,靈感突顯,利用requests下載下傳檔案的寫入特點,徹底解決了調用指令行導緻的不相容windows以外環境的問題,哈哈,是以下一篇就它了。

繼續閱讀