Python爬虫:爬取Bilibili视频(.m4s)

本文仅作为学习笔记参考使用:
哔哩哔哩视频抓取
b站视频的抓取还是比较困难的,相对于其他的网站的视频获取相对难一些,也是因为我的好奇不怕死心理,打算拿b站视频好好分析分析,具体抓取流程如下:

初始分析页面如下:
在这里插入图片描述
这个页面有点养眼,就从这个开始。本页面的请求以及响应信息较为容易抓取分析,主要就是获取分页标记以及视频跳转到指定播放界面的url地址(如下图所示)在这里插入图片描述
我用的办法是把响应中的页面框架信息下载下来,然后与原始合并之后的Element页面对比,从而找出url,这一步不难找,就不多说了,直接上代码(en…大体代码就这样,我也懒得一点点分离了):

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文件有那么多个片段,我不能全部都下载下来然后再合成⑧,虽然这确实可行,但是比较复杂。还有更简单的方式获得整个视频文件,这个就需要在requests请求头里 把 Range(如下图所示) 这个参数改成 bytes 0-XXXXXXXX 这种形式,而这里的 XXXXXXXX 就是在上文中我们分析url类型的数据请求总数量,并且据我分析,这个值只能大于或者等于数据请求总量,而不能小于。所以这里有两种的方法获取完整的.m4s格式的方法,一种就是,把请求头的Range的值写的贼大,另一种就是,先请求 0-5 这样比较短的数据,进行返回头部试探,获取到响应头部以后再取到准确的数据量的值,然后再发送请求获取.m4s全部数据的请求,这样就获取到完整的.m4s格式的文件了在这里插入图片描述
在这个分析的基础之上,对刚刚拿不准的 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'''<title data-vue-meta="true">(.*?)</title>''', 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
发布了41 篇原创文章 · 获赞 85 · 访问量 9336

猜你喜欢

转载自blog.csdn.net/weixin_44449518/article/details/104088507