写在最前边:
很长时间没有来CSDN上来写博客了。最近看到有位UP主的微信公众号推出了通过爬取B站上弹幕生成词云,再将词云合成小姐姐跳舞的视频,并且这位博主还给出了实现思路和源码。照着那位博主的方法,也最终实现了这个效果,并且在这个实现过程中也学到了不少其他的东西。夜深人静的时候就做一个总结,权当做一个笔记。
目录
准备环境:
环境这里实际上是没有什么问题的,但是微信公众号上那位博主使用的MACOS系统,所以在具体实现中还是会有一些区别,所以我这里还是交代一下环境:
操作系统:win10
编译器:anaconda3---------jupyter(base环境下)-------------python3
涉及到的技术:
视频下载====》人像分割====》python爬虫(爬取弹幕)====》分词、词云====》视频合成
实现过程:
1. 视频下载
B站视频下载的方式有很多,这里我们使用的是you-get工具来进行下载
具体操作:
1.1 安装好anaconda3以后,打开win运行窗口,输入cmd回车,进入命令行窗口
1.2 执行 pip install you-get
1.3 安装成功以后,执行 you-get -i https://www.bilibili.com/video/BV11C4y1h7nX
你可以试着将上边的这个网址复制在浏览器中打开,你所下载的视频就是这位跳《无价之姐》舞蹈的小姐姐。这里冒一句:我是第一次听《无价之姐》这歌,听多了还挺带劲!
补充:
我刚刚有提到过,实际上下载视频的方式有很多种,你可以随意,只要你喜欢。我这里想说的是,也是想对我自己说的,研究一下 bilibili_api 这个库,挺有意思的库,因为后来爬取弹幕使用这个库的话是非常方便的,这里下载B站视频用它也是可以的。
https://github.com/MoyuScript/bilibili-api/https://github.com/MoyuScript/bilibili-api/上边这个网址就是在GitHub上bilibili_api的地址,下边我把在这个库里边关于下载视频的demo链接也放上。
总之一个目的,获得小姐姐,不,不是,是获得小姐姐的跳舞视频。
1.4 使用opencv将视频逐帧保存
之前的博主讲是他只截取其中的部分视频然后逐帧进行保存,我这里把整个视频逐帧保存了,因为实际上也没有多少图片。我们大概算一下,这个视频是3分3秒,也就是183秒,然后按每秒30帧吧,那就是:5490帧,实际上跟我实际中存的帧数也差不多,我最后分下来是5503帧。
print(183*30)
废话不多讲了,我把读取视频,然后逐帧保存图片的代码奉上:
但是需要注意的是,你得提前在anaconda的工作目录下创建 dancepictrues 文件夹,还有如果你发现在导入cv2的时候报错的话,那你就是没有安装这个库,你还是直接打开cmd命令行窗口,使用pip install opencv-python 进行安装。
import cv2
#读取视频
cap=cv2.VideoCapture(r"无价之姐~让我乘风破浪~~~.flv")
#逐帧读取并且将图片存在指定文件夹下的指定文件
num=1
while 1:
ret,frame=cap.read()
if ret:
cv2.imwrite(f".\dancepictrues\img_{num}.jpg",frame)
num+=1
else:
break
#释放cap对象
cap.release()
2. 人像分割
这里是直接调用百度AI上的人体分析中的图像分割功能(放心,基本属于白嫖,因为只要注册成功,你是可以免费调用人体分析系列接口10000次)。
这里主要涉及两步:
第一步,注册百度云账号,在里边找一个人工智能的产品,然后找人体分析的模块,在里边有一个图像分割的功能,立即使用。然后创建应用,回到应用列表中。这些繁琐的步骤是干嘛呢?主要是为了获取到图像分割应用的APP_ID 、API_KEY 、SECRET_KEY
第二步,使用如下代码,将第一步获取到的APP_ID 、API_KEY 、SECRET_KEY写入代码即可。
对了,在执行代码之前还是要创建文件夹mask_img
import cv2
import base64
import numpy as np
import os
from aip import AipBodyAnalysis
import time
import random
#调用百度云的人体分割模块
APP_ID="你的"
API_KEY="你的"
SECRET_KEY="你的"
client=AipBodyAnalysis(APP_ID,API_KEY,SECRET_KEY)
path="./mask_img/"
img_files=os.listdir("./dancepictrues")
print(img_files)
for num in range(1,len(img_files)+1):
img=f"./dancepictrues/img_{num}.jpg"
img1=cv2.imread(img)
height,width,_=img1.shape
with open(img,"rb") as fp:
img_info=fp.read()
seg_res=client.bodySeg(img_info)
labelmap = base64.b64decode(seg_res['labelmap']) # res为通过接口获取的返回json
nparr = np.fromstring(labelmap, np.uint8)
labelimg = cv2.imdecode(nparr, 1)
# width, height为图片原始宽、高
labelimg = cv2.resize(labelimg, (width, height), interpolation=cv2.INTER_NEAREST)
im_new = np.where(labelimg==1, 255, labelimg)
mask_name=path+"mask_{}.png".format(num)
cv2.imwrite(mask_name, im_new)
print(f"=============第{num}张图分割完成=============")
执行完上边的命令后你就可以在 mask_img 文件夹下查看到图像分割完成的图片了,但是这个过程因为用到了百度第三方的接口,执行速度很慢。而且糟糕的话,可能最后不会把你所有的图片进行分割,不过这倒无所谓,只要分割一部分就可以了,不是吗?
3. 弹幕爬取
经过漫长的等待,恭喜你终于来到了第三步。对于从来没有学习过网站爬取数据的我这个地方着实花费了我很多时间,所以,我会用简单的方式帮你获取到弹幕,实际上,也就是获取到弹幕这个简单。这里我想吐槽一下之前的博主,使用他的方法理论上是没有问题的,但是过于复杂、繁琐。我这里讲一个简单的。
还记得在第一步中提到的 bilibili_api 这个库吗?对的,使用简单的方法就是去调用这个库的video类的获取弹幕的方法,简单实用。video类的源码我也把地址放在下方。
https://github.com/MoyuScript/bilibili-api/blob/main/bilibili_api/video.pyhttps://github.com/MoyuScript/bilibili-api/blob/main/bilibili_api/video.py在 video 类中有一个 get_danmakus() 方法,没错,就是这个方法,就可以获得对应视频的弹幕。执行下边的代码就可以获取到该视频对应的弹幕。
import asyncio #协程
import nest_asyncio
from bilibili_api import video
import re
import pandas as pd
nest_asyncio.apply()
# BVid(B站视频编号)、fileName存放弹幕
BVid = "BV11C4y1h7nX"
file_name = 'barrages.txt'
# 获取弹幕
my_video = video.Video(bvid=BVid)
danmu = await my_video.get_danmakus(cid="211304647")
#danmu.encoding="utf-8"
#数据处理
data = [data.text for data in danmu]
for i in data:
i = re.sub('\s+', '', i)
# #查看数量
print("弹幕数量为:{}".format(len(data)+1))
# 输出到文件
df = pd.DataFrame(data)
df.to_csv(file_name, index=False, header=None, encoding="utf_8_sig")
print("写入文件成功")
补充:
上边的代码中,其中bvid是B站视频的编号,具体什么机制我也不是很懂,懂的朋友可以评论区里留言。cid是视频所在专栏编号。
获取bvid我觉得大家不是问题,因为你在网站看视频的时候看网址就可以看到,难的就是获取视频的cid,其实这个也简单,只需要执行下边的代码就可以,获取到制定视频的详细信息。当然,还是使用了bilibili_api这个库。
import asyncio #协程
import nest_asyncio
from bilibili_api import video
nest_asyncio.apply()
# 加上这两句就不会报错,主要因为ipthon不支持
# import nest_asyncio
# nest_asyncio.apply()
async def main():
# 实例化 Video 类
v = video.Video(bvid="BV11C4y1h7nX")
# 获取信息
info = await v.get_info()
# 打印信息
print(info)
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
在info字典中你会看到几乎所有关于视频的信息,当然也包含我们刚刚提到的cid号
4. 分词与构造词云
4.1 分词的目的在于将比较长的句子分为若干个词语,例如“我是你的小宝贝”这个句子,可以分为“我是”、“你的”、“小宝贝”。为什么这样?因为最后生成词云以后,如果有一些长句子会显得人体图像不自然不逼真,类似于像素,如果像素点很大很少,最后生成的图片不细腻。
4.2 绘制词云使用 wordcloud 库绘制就好。
实现代码如下:
from wordcloud import WordCloud
import collections
import jieba
import re
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
import os
result_list=[]
#读取弹幕
with open('barrages.txt') as f:
data = f.read()
#正则,匹配汉字“\u4e00-\u9fa5”表示UNICODE中的汉字编码范围
new_data = re.findall('[\u4e00-\u9fa5]+', data , re.S)
new_data = "".join(new_data)
#文本分词
seg_list_exact = jieba.cut(new_data, cut_all=True)
#将分好的词存入列表result_list
for word in seg_list_exact:
result_list.append(word)
# 筛选后统计词频
word_counts = collections.Counter(result_list)
path = './wordcloud/'
img_files = os.listdir('./mask_img')
for num in range(1, len(img_files) + 1):
img = fr'.\mask_img\mask_{num}.png'
# 获取蒙版图片
mask_ = 255 - np.array(Image.open(img))
# 绘制词云
plt.figure(figsize=(8, 5), dpi=200) #这行代码貌似多余
my_cloud = WordCloud(
background_color='white', # 设置背景颜色 默认是black
mask=mask_, # 自定义蒙版
mode='RGBA',
max_words=200,
font_path='simhei.ttf', # 设置字体 显示中文
).generate_from_frequencies(word_counts)
# 显示生成的词云图片
# plt.imshow(my_cloud)
# 显示设置词云图中无坐标轴
#plt.axis('off')
word_cloud_name = path + 'wordcloud_{}.png'.format(num)
my_cloud.to_file(word_cloud_name) # 保存词云图片
print(f'======== 第{num}张词云图生成 ========')
5. 视频合成
视频合成这里使用 opencv库、moviepy 库
import cv2
import os
# 输出视频的保存路径
video_dir = 'result.mp4'
# 帧率
fps = 30
# 图片尺寸
img_size = (1920, 1080)
fourcc = cv2.VideoWriter_fourcc('M', 'P', '4', 'V') # opencv3.0 mp4会有警告但可以播放
videoWriter = cv2.VideoWriter(video_dir, fourcc, fps, img_size)
img_files = os.listdir('.//wordcloud') #读取词云图片
#对所有的词云图片循环构造视频,我这里之前构造了2711张词云图片
for i in range(1, 2712):
img_path = './/wordcloud//wordcloud_{}.png'.format(i)
frame = cv2.imread(img_path)
frame = cv2.resize(frame, img_size) # 生成视频 图片尺寸和设定尺寸相同
videoWriter.write(frame) # 写进视频里
print(f'======== 按照视频顺序第{i}张图片合进视频 ========')
videoWriter.release() # 释放资源
最后一步,将合成的视频添加背景音乐
import moviepy.editor as mpy
# 读取词云视频
my_clip = mpy.VideoFileClip('result.mp4')
# 截取背景音乐,我这里截取了背景音乐90秒,这个要按照你合成的视频时间长短来修改数字
audio_background = mpy.AudioFileClip('song.mp3').subclip(0,90)
audio_background.write_audiofile('song1.mp3')
# 视频中插入音频
final_clip = my_clip.set_audio(audio_background)
# 保存为最终的视频 动听的音乐!漂亮小姐姐词云跳舞视频!
final_clip.write_videofile('final_video.mp4')
写在最后:
每个人情况都不同,可能按照我写的你也会出现一些问题,但是不要紧,你可以给我留言讨论,谢谢!
常见的问题总结:
1. 安装opencv时的问题
在安装OpenCV时,我按照网上很多教程,说是先要下载什么OpenCV的whl包,然后放在python的包文件夹下然后pip安装,这个地方我也花了不少时间费了很大功夫,因为安装OpenCV需要和你的python版本及操作系统还有计算机架构匹配,所以你一般很难确定这些,也就安装不成功,结果最后直接使用 pip install opencv-python 解决问题。如果使用pip安装OpenCV出错了,也不要急,只需要对应提示去找原因就可以了,总之这应该是全网最简单的方法。
2. 协程问题
在执行爬取弹幕代码的时候,如果出现一行报错,只需要加上下边两行代码即可。
# 加上这两句就不会报错,主要因为ipthon不支持
# import nest_asyncio
# nest_asyncio.apply()