网页视频抓取软件 格式工厂(本文哔哩视频抓取b站视频好好分析分析(图) )
优采云 发布时间: 2022-03-30 13:06网页视频抓取软件 格式工厂(本文哔哩视频抓取b站视频好好分析分析(图)
)
本文仅作为学习笔记的参考:
哔哩哔哩视频截图
b站的视频还是比较难抓的。与其他网站视频相比,获取难度较大。也是因为我不怕死,因为我的好奇心。我打算对b站的视频进行分析分析。具体抓包流程如下:
初步分析页面如下:
这个页面有点诱人,所以让我们从那里开始。这个页面的请求和响应信息比较容易抓取和分析,主要是获取分页标记和视频跳转到指定播放界面的url地址(如下图)
我使用的方法是在response中下载页框信息,然后和原来合并的Element页面对比,找到url。这一步不难找,就不多说了,直接上代码(恩...通用代码就行了,懒得分开一点了):
html_str01 = self.get_request(self.index_url, self.index_headers).content.decode("utf-8")
seconed_url_list = set(
re.findall(r'''href="//(www\.bilibili\.com/video/.*?\?from=search)"''', html_str01, re.S))
接下来,找到初始url后,我们可以分析原创视频捕获:
以下是来此页面进行分析的:
通过抓包,我们无法获取完整的视频请求地址,而是获取大量.m4s格式文件的请求。最初的猜测是.m4s格式的文件就是我们需要的视频文件,但是从数量上看,它是一个视频剪辑。但是,并非所有 .m4s 格式请求都是相同的。一个请求,哪个被划掉肯定不是,因为响应为null,所以请求有3种类型(30080 / 30216 / 30232).
三个请求,我需要哪一个?继续分析,找了很多资料,得出的结论是返回请求数据最多的是视频格式,返回数据较少的是音频文件(如下图)
30080 所需请求的总数据大小
30216 请求的所需总数据大小
30232 所需请求的总数据大小
相比之下,30080 > 30232 > 30216 个数据请求
现在可以确定30080请求的数据是视频文件,那么30232和30216这两个请求的文件中,哪个是音频文件呢?
别慌,下面我们来分析一下。
这里有个问题,30080文件有这么多碎片,我不能全部下载然后合成⑧,虽然这个确实可行,但是比较复杂。还有一种更简单的方法来获取整个视频文件。这需要将请求头中的Range(如下图)参数改为0-XXXXXXXX字节,而这里的XXXXXXXX就是我们上面分析的url。该类型的数据请求总数,根据我的分析,这个值只能大于或等于数据请求的总数,但不能小于。因此,有两种方法可以获得完整的 .m4s 格式。一种是把请求头的Range值写大,另一种是先请求0-5等短数据,然后返回。头测试,得到响应头后,
在此分析的基础上,对比刚才不确定的30232和30216两种格式的url请求,测试得到两个请求得到的数据进行对比。如图所示:
我们下载了两个请求的数据并将它们全部保存为 .mp3 文件格式。通过本次测试,我们得知这两个文件都是视频音频文件,两者没有区别。因此,我们要求略小的 30216。
这样就可以完成视频和音频文件的获取了,不过这里是单独下载的。如果需要合成,这里有两种我尝试过的方法:
[1] 使用ffmpeg模块完成视音频合成
[2] 使用格式工厂
在这个程序中我没有使用ffmpeg进行合成,原因有二。一是因为太慢了,我的笔记本电脑在转换过程中cpu使用率上升到了不可思议的99%,二是因为格式工厂真的好用,而且合成速度极快。因此,我选择了手动格式工厂来合成视频和音频。
至此,整个分析过程就结束了,剩下的就是写整个程序了。这里就不说各个模块怎么写了,直接提供代码截图和运行截图。
运行截图如下:
代码部分保存并显示结果:
视频播放显示: ✔ 插入一句话,是高清的,是的
因此,本程序介绍结束:
部分代码如下:
源代码的一部分
def run(self):
# print("第一次请求开始。。。。。")
html_str01 = self.get_request(self.index_url, self.index_headers).content.decode("utf-8")
# file_name = re.findall(r'''(.*?)''', html_str01, re.S)[0] + ".mp4"
seconed_url_list = set(
re.findall(r'''href="//(www\.bilibili\.com/video/.*?\?from=search)"''', html_str01, re.S))
# print(seconed_url_list)
for seconed_url in seconed_url_list:
html_str02 = self.get_request("http://" + seconed_url, self.index_headers).content.decode("utf-8")
try:
m4s_30080 = re.findall(r'''"baseUrl":"(.*?)"''', html_str02, re.S)[0]
except Exception as e:
print(e)
continue
if self.audio_condition == 'Y':
mp3_30216 = re.findall(r'''"baseUrl":"(.*?)"''', html_str02, re.S)[-2]
# print(m4s_30080)
Referer_key = seconed_url
# 试探请求头大小
Range_key = 'bytes=0-5'
self.seconed_headers['Referer'] = 'https://' + Referer_key
self.seconed_headers['Range'] = Range_key
# 试探,取得total的值
html_bytes = self.get_request(m4s_30080, headers=self.seconed_headers).headers['Content-Range']
if self.audio_condition == 'Y':
audio_bytes = self.get_request(mp3_30216, headers=self.seconed_headers).headers['Content-Range']
# print(html_bytes)
total = re.findall(r"/(.*)", html_bytes, re.S)[0]
if self.audio_condition == 'Y':
audio_total = re.findall(r"/(.*)", audio_bytes, re.S)[0]
# print("total: " + str(total))
self.seconed_headers['Range'] = total
# print(total)
stream = True
chunk_size = 1024 # 每次块大小为1024
content_size = int(total)
if self.audio_condition == 'Y':
content_size_audio = int(audio_total)
print("文件大小:" + str(round(float((content_size + content_size_audio) / chunk_size / 1024), 4)) + "[MB]")
else:
print("文件大小:" + str(round(float(content_size / chunk_size / 1024), 4)) + "[MB]")
start = time.time()
m4s_bytes = self.get_request(m4s_30080, headers=self.seconed_headers, stream=stream)
self.write_data(str(self.num) + ".mp4", m4s_bytes, chunk_size, content_size)
if self.audio_condition == 'Y':
print("\n")
self.seconed_headers['Range'] = audio_total
mp3_bytes = self.get_request(mp3_30216, headers=self.seconed_headers, stream=stream)
self.write_data(str(self.num) + ".mp3", mp3_bytes, chunk_size, content_size_audio)
end = time.time()
print("总耗时:" + str(end - start) + "秒")
self.num = self.num + 1