项目背景
今天一个朋友想下载东方财富网上的2019巴菲特股东大会直播视频,我觉得还挺有趣,之前没有爬取过视频,所以研究了下,用python实现了爬取这个视频。
其实,以现在的带宽网速及wifi普及,我们已经很少去下载视频了,很多视频,都是直接在线观看,但偶尔也有下载的需求,比如下载课程视频,或者下载一些视频后,剪辑合成一些视频,或者下载一些你觉得比较重要有帮助的视频,就像这个例子一样,巴菲特的股东大会视频对很多投资者来说,还是很有价值的。
项目思路
- 确定相关的url
- 爬取相应的ts文件
- 合并ts文件
以下是要爬取的视频网站
url = "http://finance.eastmoney.com/a/201905041112402538.html"
F12打开开发者模式,Netwoking下,选择XHR,当视频播放时,会产生如下的带.ts的url,这实际上将整个视频,分成了众多的小视频片段,任意选择一个,复制网址,打开就会直接下载一个.ts格式的视频小片段,可以直接用播放器播放。所以,我们就是要爬取这些小视频片段,然后合并成一个视频即可。
这样看问题就简单多了,获取或者构造.ts的url,然后去爬取下载视频即可。
分析ts url
比较url_1和url_3,我们会发现,.ts前边是1和14,如果我们看了其他的ts url,会发现就是划分的视频小片段,而且一般划分数量都不少,这里有178个。
比较url_1和url_2,都是在14.ts下的,只不过后边start, end不一样,我们直接去掉 “?”后边的部分,打开前半部分的url后,可以下载整个14片段的视频,相当于这边视频片段14又进行了子划分,我们只需构造问号前半部分的url即可。
再比较url_1和url_3前半部分url,会发现只有“3228861720_780805890_14.ts” 与 “3228861720_1565367120_1.ts”这部分是不一样的,问题简化为,找到或者构造出这部分的内容,发现中间部分很难直接构造。
url_1:
http://1252033264.vod2.myqcloud.com/4697b79evodcq1252033264/3cea53065285890788726506999/
3228861720_780805890_14.ts?start=15163892&end=16534411&type=mpegts
url_2:
http://1252033264.vod2.myqcloud.com/4697b79evodcq1252033264/3cea53065285890788726506999/
3228861720_780805890_14.ts?start=12526252&end=15163891&type=mpegts
url_3:
http://1252033264.vod2.myqcloud.com/4697b79evodcq1252033264/3cea53065285890788726506999/
3228861720_1565367120_1.ts?start=8069900&end=9171203&type=mpegts
这块让我很郁闷,看了网上的帖子后,继续分析Networking, 直到我发现了含有.m3u8的url,我直接打开后,或者打开去掉url后边的参数部分,会下载一个.m3u8的文件。打开文件后,发现里边包含了我们需要字段名,所以构造url的问题也解决了,访问这个网址获取文本内容,正则表达式匹配我们需要的部分,然后就可以构造我们的url了。
http://1252033264.vod2.myqcloud.com/4697b79evodcq1252033264/3cea53065285890788726506999/
playlist.m3u8?t=5f37a52f&exper=0&us=7106fcd370&sign=86432b5694bc6b668690312c790d7d11
以下是代码:
# -*- coding:utf-8 -*-
import requests
import re
from urllib.request import urlretrieve
import os
from concurrent.futures import ThreadPoolExecutor
from shutil import rmtree
class DownloadVideo:
def __init__(self, m3u8_url):
self.m3u8_url = m3u8_url
self.ts_url = []
self.count = 0
# 分析networking中,可以找到一个m3u8的链接,其包含了所有ts video的url名字
def get_ts_url(self):
m3u8_text = requests.get(self.m3u8_url).text
# 正则表达式获取.ts的字段
re_list = re.findall(r".*?\.ts", m3u8_text)
for ts_name in re_list:
# 拼接出ts的url
pre_url = re.match(r"(.*?/)[A-Za-z0-9._]+\.m3u8.*", self.m3u8_url).group(1)
target_url = pre_url + ts_name
if target_url not in self.ts_url:
self.ts_url.append(target_url)
return self.ts_url
# 下载单个ts文件
def __download_ts(self, url, i):
# 创建存放ts的文件夹
path = os.getcwd() + "/ts_file"
if not os.path.exists(path):
os.makedirs(path)
name = "%03d" % i + ".ts"
filename = path + "/" + name
# 开始下载ts
urlretrieve(url, filename=filename)
# 多进程下载
def pool_download(self):
print("需要下载%d个文件" % len(self.ts_url))
print('正在下载...所需时间较长,请耐心等待...')
pool = ThreadPoolExecutor(max_workers=16)
pool.map(self.__download_ts, self.ts_url, range(1, len(self.ts_url)+1))
pool.shutdown()
print("下载完成")
def merge_ts(self):
file = os.getcwd() + "\\ts_file\\*.ts"
target = os.getcwd() + "\\video.ts"
cmd = "copy /b " + file + " " + target
try:
print("开始合并ts文件...")
os.system(cmd)
print("完成ts文件合并")
# 合并完成后,删除其余的ts文件
rmtree(os.getcwd() + "\\ts_file")
except Exception as e:
print(e)
def execute(self):
self.get_ts_url()
print("获取ts url...")
self.pool_download()
self.merge_ts()
print("完成")
if __name__ == "__main__":
url = "http://1252033264.vod2.myqcloud.com/4697b79evodcq1252033264/3cea53065285890788726506999/playlist.m3u8?"
download_video = DownloadVideo(url)
download_video.execute()
几点说明:
- 根据ts url下载视频,我们采用了urllib.request模块下的 urlretrieve(url, filename=filename)函数来下载ts视频片段
- 在__download_ts中,下载ts前,会在当前工作目录下,创建./ts_file文件夹,用来临时存放下载的ts文件,后期合并后(函数merge_ts),会通过shutil.rmtree来移除整个ts_file文件夹和ts文件。
- 我们采用多进程进行下载,使用了concurrent.futures模块中的ThreadPoolExecutor(max_woker=16),以及map(func, iterable)函数,来实现多进程下载。
PS: 如果你有疑问,或者意见、建议,欢迎讨论!!!