HLS流媒体视频获取

介绍

此次爬取的边下载便播放的HLS流媒体视频。HLS流媒体的视频是由数个.ts格式的小片段组合而成,获取这些片段的相关信息,都放在一个.m3u8的索引文件中,它记录这每个片段的请求相关信息以及时长。

目标

  1. 获取视频的.m3u8索引文件。
  2. 根据索引文件下载所有的.ts小片段。
  3. 将数个片段组合成一个完整视频。

实现

  • 分析页面元素,获取视频链接,iframe页面元素内容。
  • 获取请求索引文件的地址。
  • 根据上述地址,请求到.m3u8索引文件。
  • 最后由索引文件请求到所有的.ts文件。
  • 组合.ts文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import math
import os

from bs4 import BeautifulSoup
from bs4 import SoupStrainer
import requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import re
import threading

frame_src = ""
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
# "accept": "*/*",
# "referer": "https://qq.com-ok-qq.com/share/5c0321b6b78eecdfcf72e6a44222fef9",
# "sec-fetch-mode": "cors"
}

# 根据完整地址获取域名
def getDomain(str):
group = re.search(r"http(.*)com", str)
return group.group(0)

def getmsg(url,**kwargs):
r = requests.get(url,**kwargs)
r.raise_for_status()
return r.text

# 分析页面元素,获取视频链接src,以及iframe内容soup。
def get_iframe(url):
'''
:param url:视频网站原始地址
:return:BeautifulSoup对象,含iframe元素信息
'''
chrome_options = Options()
chrome_options.add_argument("--headless")
driver = webdriver.Chrome("chromedriver.exe",options=chrome_options)
driver.get(url)
iframe = driver.find_elements_by_tag_name("iframe")[1]
global frame_src
frame_src = iframe.get_attribute("src")
driver.switch_to.frame(iframe)
soup = BeautifulSoup(driver.page_source,"html.parser")
return soup

# 拼接 能够获取索引文件 的url
def index_url_msg(url):
'''
:param url:视频网站原始地址
:return:
res:.m3u8索引文件内容
index_url[:-10]:.ts文件请求地址的前缀
title:视频标题
cookies:请求cookies
'''
soup = get_iframe(url)
title = soup.find("title").text
tag_only = SoupStrainer("script")
soup.parse_only = tag_only
tmp_str = soup.find(string=re.compile("main"))

s1 = re.search(r'var main = "(.*)";',tmp_str)
s2 = re.search(r'var main = "(.*)index.m3u8',tmp_str)

# 获取请求索引文件的地址pre_index_url
pre_index_url = getDomain(frame_src) + s1.group(1) # https://qq.com-ok-qq.com/20191122/26061_687037eb/index.m3u8?sign=5c5875c23d20cff9b8a5cdc24b008485
sub_index_url = getDomain(frame_src) + s2.group(1)# https://qq.com-ok-qq.com/20191122/26061_687037eb/

cookies = requests.get(frame_src).cookies
pre_indexurl_res = getmsg(pre_index_url,headers=headers,cookies=cookies)
index_url = sub_index_url+ pre_indexurl_res.split("\n")[2]

# 根据上述地址,请求到.m3u8索引文件,得到响应内容
res = getmsg(index_url,headers=headers,cookies=cookies)
return res, index_url[:-10],title, cookies

def func(listTemp, n):
'''
:param listTemp: 列表
:param n: 列表分割的数量
:return: n个列表组合的列表
'''
m = math.ceil(len(listTemp)/n)
for i in range(0, len(listTemp),m):
yield listTemp[i:i + m]

def download_ts(g,pre_url,cookies):
'''
:param g: .ts文件请求地址路由
:param pre_url: .ts文件请求地址前缀
:param cookies: 请求cookies
:return:
'''
i=0
s = requests.session()
while g:
tmp = g.pop()
with open(tmp, "wb") as f:
while i<3:
r = s.get(pre_url+tmp ,headers=headers,cookies=cookies)
if r.content:
f.write(r.content)
break
s.close()

def combine(title):
'''
:param title: 合成后视频的名称
:param path: 所有.ts文件的目录(只能含有相关的.ts文件)
:return:
'''
res = os.listdir()
res.sort(reverse=True)
with open(title+".mp4","ab") as f:
while res:
with open(res.pop(),"rb") as f2:
while True:
data = f2.read(1024)
if data:
f.write(data)
else:
break

def run(url):
print("正在获取资源…")
res, pre_url,title, cookies= index_url_msg(url)
group = re.findall(r'(\w+\.ts)',res)

os.mkdir(title)
os.chdir(title)

tmp = list(func(group,4))
thread_list = []
for i in range(len(tmp)):
thread_list.append(threading.Thread(target=download_ts, args=(tmp[i], pre_url, cookies)))
for th in thread_list:
th.start()
print("开始下载")
for th in thread_list:
th.join()
print("下载完成!!!")
print("开始合并视频…")
combine(title)
print("合并成功!!!")

if __name__ == '__main__':
url = "http://www.kk2w1.com/?m=vod-play-id-50476-src-1-num-14.html"
run(url)

结果

生成一个文件夹

得到完整视频