爬蟲 | Python下載m3u8視頻


參考資料:

# 配置環境

import requests,re
import sys,time
import os
import numpy as np
import glob

work_dir = os.getcwd()
print(work_dir)

# 用來保存ts文件
file_dir = os.path.join(work_dir,'file_tmp')

if not os.path.exists(file_dir):
    os.mkdir(file_dir)

先定義保存文件的函數

def savefile(file_url,file_name):
    # 配置headers防止被牆,一般問題不大
    headers = {
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36'
        }
    
    r = requests.get(file_url,headers=headers)
    
    if r.status_code == 200:
        with open(file_name, 'wb') as f:
            f.write(r.content)

從 m3u8 文件中解析出 ts 信息

怎么查找m3u8文件?

假設在chrome上打開視頻頁

右鍵檢查,Network -> All ,過濾.m3u8

一般可以看到兩個m3u8地址,其中一個是帶hls的,這個文件可以解析出ts信息

拿個網址來舉例,比如這個視頻

# 如果url中沒有hls的,那就是源m3u8文件
# 源m3u8文件會跳轉到另一個m3u8文件,這個地址中就帶有hls

# 這個是源m3u8文件,不帶hls
url_m3u8 = 'https://wuji.zhulong-zuida.com/20190706/762_c260ca6c/index.m3u8'

r = requests.get(url_m3u8)
r.encoding='utf-8'

# 查看內容

print(r.text)

輸出:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=1080x608
800k/hls/index.m3u8

可以看到最后一行就是跳轉后的m3u8地址

# 合成帶有hls的m3u8地址
if r.text.split('\n')[-1] == '':
    hls_mark = r.text.split('\n')[-2] # 以防\n結尾
else:
    hls_mark = r.text.split('\n')[-1]
url_m3u8_hls = url_m3u8.replace('index.m3u8',hls_mark)
url_m3u8_hls

輸出:
'https://wuji.zhulong-zuida.com/20190706/762_c260ca6c/800k/hls/index.m3u8'

# 不過有時候可能沒法查到跳轉后的帶hls的連接
# 但是視頻加載文件的網址格式為 主url+文件名.ts
# 這個主url是帶hls的
# m3u8的index目錄 格式為 主url/index.m3u8
url_m3u8_hls = 'https://wuji.zhulong-zuida.com/20190706/762_c260ca6c/800k/hls/index.m3u8'
# 帶有hls的m3u8文件中獲得的是ts信息
# 包括ts文件名稱,以及該文件的持續時間

# 這個文件有用,先保存一下
file_m3u8 = url_m3u8_hls.split('/')[-1]
with open(file_m3u8,'wb') as f:
    f.write(r.content)
# iter_lines得到的是bytesstring
text_bytes = list(r.iter_lines())

# 轉化成正常string
text_string = [i.decode('utf-8') for i in text_bytes]
# 篩選以.ts結尾的行
# 有些情況下可能是以其他格式的文件,比如png,下載后修改后綴即可
# ts_name = [i for i in text_string if i.endswith('.ts')]
ts_name = [i for i in text_string if not i.startswith('#')]

ts_name[:3]

輸出:
['36962c1a1b0000000.ts', '36962c1a1b0000001.ts', '36962c1a1b0000002.ts']

有時候ts文件信息中可能還包含一部分路徑信息。
因為路徑都是統一的,所以我們只需要文件名就可以了

if '/' in ts_name[1]:
    # 部分ts文件名中帶有路徑信息,只保留文件名即可
    ts_name = [i.split('/')[-1] for i in ts_name]
    
    ts_name[:3]

接下來處理時間戳。

# 篩選帶有時間的行
ts_time = [float(re.findall('[.\d]+',i)[0]) for i in text_string if i.startswith('#EXTINF')]

ts_time[:3]

輸出:
[4.1283, 4.3785, 4.17]

# 檢驗解析出來的時間戳和文件名數量是否匹配
len(ts_name) == len(ts_time)

輸出:
True

按時間截取視頻

# 建立時間基准
# 得到累計時間序列
time_cum = np.cumsum(ts_time)
# 那如果我要看51分05秒~55分46秒,應該下載哪些文件呢?

time_start = 1*3600+26*60+5
time_end = 1*3600+46*60+20

# 如果有多段時間截取,可以寫個函數將時間序列進行轉化
# 輸入:[(0.0.0,0.9.30),(0.10.0,0.20.0)]
# 輸出 累計時間戳的index [(0,38),(40,80)]
# 對於起始時間
# 篩選累計時間戳<開始時間的最大值,再找對應的index

index_start = sum(time_cum<time_start)+1-1
# +1是為了截取
# -1 是為了矯正index序號

這個就是最終的index,我們這里的原則是最后取到的時間區間是完全包含目標區間的

這里的index實際上做了一個位移的,本來index是0開始,所以說是351

e.g. 假設前3個ts的時間為2,3,3

現在我要的是第4秒后的信息,得到的累計時間序列是2,5,8

按照我們剛才的那個判斷,<4的只有1個時間位,但從自然順序上我們是要從第二個ts文件開始截取

這就相當於index不用再變動了,如果其他軟件序列語法是1開始的話,那么這個index

# 對於截止時間
index_end = len(time_cum) - sum(time_cum>time_end) +1 - 1

同樣假設 整個累計時間序列為 2,5,8,10

現在截取到6秒,所以要取到第3個ts文件(Python index為2)

時間序列長度4-大於6的個數2+偏移1位-index矯正1位

這就是最終的index

print(index_start,index_end)

輸出:
1290 1594

抓取 ts 文件

單文件測試

ts_name[0] # 這個是片頭

輸出:
'36962c1a1b0000000.ts'

# 這個網址去除掉最后的文件名就是**主url**了。
file_url = 'https://wuji.zhulong-zuida.com/20190706/762_c260ca6c/800k/hls/36962c1a1b0000000.ts'

# 提取文件名
file_name = os.path.join(file_dir,file_url.split('/')[-1])

# 下載ts文件到本地
savefile(file_url,file_name)

批量下載

# 先看下我們要抓取的ts文件的index的起始位置
print(index_start,index_end)

輸出:
1290 1594

# ts文件的主url以/hls/結束
url_m3u8_hls

輸出:
'https://wuji.zhulong-zuida.com/20190706/762_c260ca6c/800k/hls/index.m3u8'

# 提取主url
url_ts_main = url_m3u8_hls.replace('index.m3u8','')
# 絕大部分hls文件的名稱都是index.m3u8,個別的也可能是其他名字


# range 函數取頭不取尾,所以+1
for idx in range(index_start,index_end+1):
    # 拼接url
    file_name = ts_name[idx]
    file_url = url_ts_main+file_name
    
    # 對於后綴可能是其他格式的情況下,保存為以.ts結尾的文件即可
    # 有的服務器可能會改變后綴假裝自己不是ts文件
    if not file_name.endswith('ts'):
        tmp_name = file_name.split('.')[:-1]
        tmp_name.append('ts')
        file_name = '.'.join(tmp_name)

    file_path = os.path.join(file_dir,file_name)
    
    # 保存文件
    savefile(file_url,file_path)
    
    # 提示進度
    sys.stdout.write('\r當前進度 第%d頁 剩余%d頁'%(idx,index_end-idx))
    sys.stdout.flush()
    time.sleep(0.1)

輸出:
當前進度 第1594頁 剩余0頁

合並 ts 文件

第一種方式可以考慮,使用命令行操作

如果是在windows上操作

  • copy /b 路徑\*.ts 路徑\合並文件.ts
  • copy /b “1.ts”+“2.ts”+…+”n.ts” /y “combine.ts”

如果是在mac上操作

  • cat 1.ts 2.ts > combine.ts

注意:文件的順序要正確才行

# 如果有ts文件的index
# 那么可以用index直接來生成有順序的list即可
file_list = [os.path.join(file_dir,ts_name[i]) \
             for i in  range(index_start,index_end+1)]

# 直接掃描路徑下的ts文件也是可以的
# 也可以刪掉部分ts文件
# file_list = glob.glob(os.path.join(file_dir,'*.ts'))
# file_list.sort()

# 這里是在mac上操作,所以名稱以空格相連
filepath_cat = ' '.join(file_list)

cmd_str = 'cat ' + filepath_cat + '> merge.ts'
# cat 1.ts 2.ts > combine.ts

os.system(cmd_str)

執行成功的話,會返回0

第二種合並ts文件的方式,可以將所有的ts文件按順序寫到一個新的文件中

file_out = 'merge_02.ts'

with open(file_out,'wb') as f_out:
    for f_in in file_list:
        f_out.write(open(f_in,'rb').read())

將合並的ts文件轉化為視頻文件

最后,我們將合成的ts文件轉化成視頻文件(比如MP4格式)
這里我們調用 ffmpeg 將 ts 文件轉化為視頻文件

轉化命令為

  • ffmpeg -i 文件名稱.ts -c copy [視頻名稱]
  • e.g. ffmpeg -i merge.ts -c copy '視頻截取片段.mp4'
file_in = os.path.join(work_dir,'merge.ts')

#如果路徑中有空格,所以路徑需要用上雙引號,否則會找不到該文件

file_out = "merge.mp4"

# 這里是去ffmpeg官網下載編譯好的軟件包,免安裝的
cmd = './ffmpeg -i '+file_in +' -c copy ' + file_out

os.system(cmd) # 運行正常返回0


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM