记录学习历程~
欢迎广大道友指导与交流~
本文仅供学习、交流等非商业性用途使用。
转载请声明附带链接:https://blog.csdn.net/Ws_Zhou_Xu/article/details/119709502
B站视频自某年以后其缓存的视频皆为分段分开的,比较不方便。因此个人想着用Python将其更加自动化。
既然视频和音频分开了,那么必然需要先请求获取视频和音频的url再通过各自的url分别获取各自的内容。最后再借助moviepy将视频和音频合成。
关于批量处理爬取,一开始想得过于复杂(当然,相较于其他更快的方式,基于此方式来说是复杂的),导致连连异常。经过一觉,发现豁然开朗。复杂点在于当找到位于Network中的关键name之后,想着更加自动化的获取通用ID走了不少弯路。后面才发现通用ID就在网址中。
大体思路如下:在进入喜欢UP主的空间后,点击播放全部(图1),
图1
→进入新页面,发现网址(图2),并没有附着视频BV,因此需要换一种方式思考。
图2
→右键检查页面,在Network栏下发现了特殊name(图3),里面包含了Up主其他的视频(图4,5)。
图3
图4 图5
→思路清晰起来了,我们可以通过请求图2页面,找到图3所示信息群,再请求该URL获取json数据迭代索引获取各bv_id。
代码及分析如下:
①获取bv_id并导入相关
importrequestsimportreimportosfrommoviepy.editorimportVideoFileClip, AudioFileClipfromlxmlimportetreeimportjsonimporttime# 构建bv的headersheaders_bv = {'User-Agent':'XXX'}# 此处为爬取视频的Url所需params不同于获取bv的Urldata = {'accept_description':'高清 1080P+','accept_quality':112}# 专属bvUrlget_bvUrl ='https://api.bilibili.com/x/v2/medialist/resource/list?type=1&otype=2&biz_id='+ \ input('请输入您要的UP主的url的id:') +'&bvid=&with_current=true&mobi_app=web&ps=20&'\'direction=false&sort_field=1&tid=0&desc=true'r = requests.get(get_bvUrl, headers=headers_bv)# 获取bv列表bv_list = []bv = json.loads(r.content)# 将获取到的内容json化foriinrange(20): bvID = bv['data']['media_list'][i]['bv_id']# 根据preview显示数据索引bv_list.append(bvID)print(bv_list)downloadCount =0# 此处计数是控制下载视频数量
说明:User-Agent获取在网页Headers部,可自行获取键入。
②构建循环附带跳出条件
if__name__ =='__main__':whileTrue:ifdownloadCount <20:# 此处的url和headers专门用于请求视频数据不同于请求bv_idurl ='https://www.bilibili.com/video/'+ bv_list[downloadCount] headers = {'Referer':'https://www.bilibili.com/video/'+ str(bv_list[downloadCount]),'User-Agent':'XXX'} biliSpider = bilibiliSpider()# 此处见下文biliSpider.run() downloadCount +=1# 运行一次加1,到20就跳出结束下载视频time.sleep(6)else:break
③构建bilibiliSpider()类
1.主体框架
classbilibiliSpider():defsend_requests(self, url):# 发出请求通用格式(方便defget_data(self, webData):# 获取标题,JSON数据,视频和音频Urldefsave_data(self, fileName, videoUrl, audioUrl):# 请求视频和音频Url并获取内容存储defmerge_data(self, videoName):# 利用moviepy将视频和音频合成defrun(self):# 调用各方法
说明:此处的合成视频和音频方法是调整过的,起初使用ffmpeg发现不太好使,经过交流群前辈指导改用moivepy,较方便且好使。
2.发出通用请求格式(方便
defsend_requests(self, url):self.url = urlglobalres res = requests.get(url, headers=headers, params=data)returnres
说明:global意味在于其他方法处需要请求内容。
3.请求获取标题,JSON数据,视频和音频Url
defget_data(self, webData):self.webData = webData# 提取标题pattern ='(.*?)'title = re.findall(pattern, res.text, re.S)[0]# 提取json数据_element = etree.HTML(res.content) jsonData = str(_element.xpath('//head/script[5]/text()')[0].encode('utf-8').decode('utf-8'))[20:] jsonData = json.loads(jsonData)# 提取视频的地址videoUrl = jsonData['data']['dash']['video'][0]['backupUrl'][0] print('提取到视频:', videoUrl) audioUrl = jsonData['data']['dash']['audio'][0]['backupUrl'][0] print('提取到音频:', audioUrl) videoData = [title, videoUrl, audioUrl]returnvideoData
说明:此处的title索引、jsonData索引、videoUrl索引、audioUrl索引和上文的Referer等信息皆可在图中找到,获取大概流程皆为右键检查页面获取信息(图6,7,8)。
图6
图7
图8
4.保存数据
defsave_data(self, fileName, videoUrl, audioUrl):self.fileName = fileName self.videoUrl = videoUrl self.audioUrl = audioUrl# 发出请求,获取内容get_videoData = self.send_requests(videoUrl).content get_audioData = self.send_requests(audioUrl).content# 存储数据withopen(str(fileName) +'.mp4','wb')asf: f.write(get_videoData)withopen(str(fileName) +'.mp3','wb')asf: f.write(get_audioData)
5.合成视频和音频
defmerge_data(self, videoName):self.videoName = videoName print('开始合成', videoName)# 设置路径path1 =r'D:\result'+f'\{videoName}'+'.mp4'path2 =r'D:\result'+f'\{videoName}'+'.mp3'video_path = path1# 获取视频切片get_video = VideoFileClip(video_path)# 切片+切片video_audio_combine = get_video.set_audio(AudioFileClip(path2)) video_audio_combine.write_videofile(f'{videoName}+'+'.mp4', audio_codec='aac') print('合成完毕', videoName)ifos.path.exists(path1): os.remove(path1)else:passifos.path.exists(path2): os.remove(path2)else:pass
说明:路径一定要对,不然找不到会报错(可以自己设置路径,前后统一即可)。后面条件语句那开始是清楚之前单独的视频和音频,合成后的名字多了加号,以防被系统按照单独的视频给误删。
起初使用ffmpeg合成视频和音频并不顺利(图9),因此改用moviepy。
图9
6.调用方法
def run(self):
webData = self.send_requests(url)
videoData = self.get_data(webData)
self.save_data(videoData[0], videoData[1], videoData[2])
self.merge_data(videoData[0])
# 这也就是为什么当初构造videoData列表并return了
说明:全文尽量统一
全文代码如下:
importrequestsimportreimportosfrommoviepy.editorimportVideoFileClip, AudioFileClipfromlxmlimportetreeimportjsonimporttimeos.chdir(r'D:\result')headers_bv = {'User-Agent':'XXX'}data = {'accept_description':'高清 1080P+','accept_quality':112}get_bvUrl ='https://api.bilibili.com/x/v2/medialist/resource/list?type=1&otype=2&biz_id='+ \ input('请输入您要的UP主的url的id:') +'&bvid=&with_current=true&mobi_app=web&ps=20&'\'direction=false&sort_field=1&tid=0&desc=true'r = requests.get(get_bvUrl, headers=headers_bv)# 获取bv列表bv_list = []bv = json.loads(r.content)foriinrange(20): bvID = bv['data']['media_list'][i]['bv_id'] bv_list.append(bvID)print(bv_list)downloadCount =0classbilibiliSpider():defsend_requests(self, url):self.url = urlglobalres res = requests.get(url, headers=headers, params=data)returnresdefget_data(self, webData):self.webData = webData# 提取标题pattern ='(.*?)'title = re.findall(pattern, res.text, re.S)[0]# 提取json数据_element = etree.HTML(res.content) jsonData = str(_element.xpath('//head/script[5]/text()')[0].encode('utf-8').decode('utf-8'))[20:] jsonData = json.loads(jsonData)# 提取视频的地址videoUrl = jsonData['data']['dash']['video'][0]['backupUrl'][0] print('提取到视频:', videoUrl) audioUrl = jsonData['data']['dash']['audio'][0]['backupUrl'][0] print('提取到音频:', audioUrl) videoData = [title, videoUrl, audioUrl]returnvideoDatadefsave_data(self, fileName, videoUrl, audioUrl):self.fileName = fileName self.videoUrl = videoUrl self.audioUrl = audioUrl# 发出请求,获取内容get_videoData = self.send_requests(videoUrl).content get_audioData = self.send_requests(audioUrl).content# 存储数据withopen(str(fileName) +'.mp4','wb')asf: f.write(get_videoData)withopen(str(fileName) +'.mp3','wb')asf: f.write(get_audioData)defmerge_data(self, videoName):self.videoName = videoName print('开始合成', videoName)# 设置路径path1 =r'D:\result'+f'\{videoName}'+'.mp4'path2 =r'D:\result'+f'\{videoName}'+'.mp3'video_path = path1# 获取视频切片get_video = VideoFileClip(video_path)# 切片+切片video_audio_combine = get_video.set_audio(AudioFileClip(path2)) video_audio_combine.write_videofile(f'{videoName}+'+'.mp4', audio_codec='aac') print('合成完毕', videoName)ifos.path.exists(path1): os.remove(path1)else:passifos.path.exists(path2): os.remove(path2)else:passdefrun(self):webData = self.send_requests(url) videoData = self.get_data(webData) self.save_data(videoData[0], videoData[1], videoData[2]) self.merge_data(videoData[0])if__name__ =='__main__':whileTrue:ifdownloadCount <20: url ='https://www.bilibili.com/video/'+ bv_list[downloadCount] headers = {'Referer':'https://www.bilibili.com/video/'+ str(bv_list[downloadCount]),'User-Agent':'XXX'} biliSpider = bilibiliSpider() biliSpider.run() downloadCount +=1time.sleep(6)else:break
试运行:
综上,还有提升空间,比如下载速度太慢。然后是画质的选择,爬的时候默认是最高的,具体自定义画质还在研究(即文中的params貌似没啥用,待测试)。
敬请指教与交流!