Python实现词云舞

253 阅读8分钟

做刘教练的Python-Boys

本文是一个非常完整的Python实战项目,主要内容包含:

在此感谢周杰伦的《本草纲目》、刘畊宏教练、百度平台以及参考的大佬们的方法,本文仅做个人学习使用

视频下载神器:you-get

you-get是一个下载视频的神器,安装之后一行代码即可下载视频

1
you-get [url]

1
you-get https://www.bilibili.com/video/BV1yY4y1i7Pw?t=1079.2   # 一行代码下载视频

视频切割成图片

下面的代码实现的功能是将上面获取到的代码切割成一张张的图片:

1、opencv中通过VideoCaptrue类对视频进行读取操作以及调用摄像头

filename:打开的视频文件名

device:打开的视频捕获设备id ,如果只有一个摄像头可以填0,表示打开默认的摄像头

2、videoCapture.read():表示读取视频的下一帧

第一个返回值为是否成功获取视频帧:True/False

第二个返回值为返回的视频帧:帧数

1
import cv2
2
import numpy as np
3
import random
4
import os
5
​
6
# 保存图片的函数
7
def save_images(image, addr,num):
8
    address = addr + str(num) + ".jpg"
9
    cv2.imwrite(address, image)
10
    
11
    
12
# 读取视频
13
videoCapture = cv2.VideoCapture("刘畊宏,毽子操x10+蝴蝶袖+臀腿操(自用).mp4")
14
success, frame = videoCapture.read()
15
​
16
time_ = 100  
17
i = 0
18
j = 0
19
​
20
while success:  # 如果成功获取到视频帧
21
    i += 1  
22
    if i % time_ == 0:
23
        s = 0  # 名称的编号
24
        j = j + 1
25
        s += j
26
        save_images(frame, "./picture/",s)  # 写入目录后再继续读取
27
    success, frame = videoCapture.read()
28
        
29
videoCapture.release()  # 释放资源

这样最终我们就将这个视频分成了835张图片

百度人像分割
实际使用

1、先在百度云平台创建人像分割实例

新建一个人像分割的实例,新注册用户可免费领取资源,官网地址:https://cloud.baidu.com/product/body/seg。下面是小编申请的一个实例:

注意点1:一定是安装baidu_aip库,而不是aip

1
pip install baidu_aip  # 安装库,一定要是baidu_aip

注意点2:在当前路径下新建一个mask文件,用来存放分割后的图片。

下面是分割之后的二值图效果:

百度demo

具体的百度官方文档请参考:https://cloud.baidu.com/doc/BODY/s/4k3cpyner

百度官方的案例如下:

1
# 官方demo
2
""" 读取图片 """
3
def get_file_content(filePath):
4
    with open(filePath, 'rb') as fp:
5
        return fp.read()
6
​
7
image = get_file_content('example.jpg')
8
""" 调用人像分割 """
9
client.bodySeg(image);
10
​
11
""" 如果有可选参数 """
12
options = {}
13
options["type"] = "labelmap"
14
​
15
""" 带参数调用人像分割 """
16
client.bodySeg(image, options)

注:返回的二值图像需要进行二次处理才可查看分割效果;灰度图和前景人像图不用处理,直接解码保存图片即可。

获取B站弹幕

接下来是获取上面视频的弹幕,请参考一位NLP大佬:https://github.com/godweiyang/bilibili-danmu

1
import re
2
import requests
3
import pandas as pd
4
import time
5
from tqdm import trange
6
​
7
​
8
# 视频页面点击“浏览器地址栏小锁-Cookie-bilibili.com-Cookie-SESSDATA”进行获取
9
SESSDATA = ""
10
​
11
# 视频页面“按F12-Console-输入document.cookie”进行获取
12
cookie = ""
13
​
14
cookie += f";SESSDATA={SESSDATA}"
15
​
16
headers = {
17
    "user-agent": "",
18
    "cookie": cookie,
19
}
20
​
21
​
22
def get_info(vid):
23
    """
24
    vid:视频号id
25
    """
26
    
27
    # 构造URL地址
28
    # https://api.bilibili.com/x/web-interface/view/detail?bvid=BV1yY4y1i7Pw
29
    url = f"https://api.bilibili.com/x/web-interface/view/detail?bvid={vid}"
30
    
31
    response = requests.get(url, headers=headers)
32
    response.encoding = "utf-8"
33
    data = response.json()  # json格式数据
34
    
35
    info = {}
36
    info["标题"] = data["data"]["View"]["title"]
37
    info["总弹幕数"] = data["data"]["View"]["stat"]["danmaku"]
38
    info["视频数量"] = data["data"]["View"]["videos"]
39
    info["cid"] = [dic["cid"] for dic in data["data"]["View"]["pages"]]
40
    
41
    if info["视频数量"] > 1:
42
        info["子标题"] = [dic["part"] for dic in data["data"]["View"]["pages"]]
43
        
44
#     for k, v in info.items():
45
#         print(k + ":", v)
46
    return info
47
​
48
​
49
def get_danmu(info, start, end):
50
    
51
    # 爬取的时间范围设置
52
    date_list = [i for i in pd.date_range(start, end).strftime("%Y-%m-%d")]
53
    all_dms = []
54
    
55
    for i, cid in enumerate(info["cid"]):
56
        dms = []
57
        for j in trange(len(date_list)):
58
            url = f"https://api.bilibili.com/x/v2/dm/web/history/seg.so?type=1&oid={cid}&date={date_list[j]}"
59
            response = requests.get(url, headers=headers)
60
            response.encoding = "utf-8"
61
            
62
            data = re.findall(r"[:](.*?)[@]", response.text)
63
            dms += [dm[1:] for dm in data]
64
            time.sleep(3)
65
#         if info["视频数量"] > 1:
66
#             print("cid:", cid, "弹幕数:", len(dms), "子标题:", info["子标题"][i])
67
        all_dms += dms
68
    print(f"共获取弹幕{len(all_dms)}条!")
69
    return all_dms
70
​
71
​
72
if __name__ == "__main__":
73
    vid = input("输入视频编号: ")
74
#     vid = "BV1yY4y1i7Pw"
75
    info = get_info(vid)
76
    print("info的内容:",info)
77
    
78
    start = input("弹幕开始时间(年-月-日): ")
79
    end = input("弹幕结束时间(年-月-日): ")
80
    
81
    danmu = get_danmu(info, start, end)
82
    with open("danmu3.txt", "w", encoding="utf-8") as f:
83
        for dm in danmu:
84
            f.write(dm + "\n")

合成词云图

弹幕的分词是自己的方法和收集的一份常用的停用词表:

1、分词使用的jieba分词。关于jieba分词的使用入门,参考:https://github.com/fxsjy/jieba

快速安装jieba:

1
pip install jieba
1
import pandas as pd
2
import numpy as np
3
import jieba
4
​
5
from wordcloud import WordCloud
6
from tkinter import _flatten
7
import matplotlib.pyplot as plt
8
%matplotlib inline
9
​
10
import collections
11
import re
12
import os
13
from PIL import Image
14
​
15
​
16
df = pd.DataFrame()
17
# 获取了3个和刘教练相关的视频弹幕
18
txt_list = ["danmu.txt", "danmu1.txt", "danmu2.txt"]
19
​
20
for txt in txt_list:
21
    df1 = pd.read_table(txt, header=None, on_bad_lines='skip')
22
    df1.columns = ["information"]  # 重命名
23
    df1.drop_duplicates("information",inplace=True)
24
    
25
    df = pd.concat([df, df1])
26
​
27
df.head()

总共是10415个弹幕:查看前10条弹幕信息

2、实施分词

stopwords = [i.strip() for i in open(r'/Users/peter/Desktop/spider/nlp_stopwords.txt').readlines()]

def data_cut(sentence):
  	# [\u4e00-\u9fa5] 表示只需要弹幕的中文内容
    cut_list = jieba.lcut(''.join(re.findall('[\u4e00-\u9fa5]', sentence)), cut_all = False)
    
    # 循环整个cut_list
    for i in range(len(cut_list)-1, -1, -1):
        if cut_list[i] in stopwords:   # 如果元素在停用词表中则删除该信息
            del cut_list[i]
    return cut_list


result = list(map(lambda x: data_cut(x), information_list))
useful_result = [j for i in result for j in i]  # 双层列表的转换

3、统计词频

统计切割之后每个单词的总数:

显示出前80个词云图的效果:

from pyecharts.globals import CurrentConfig, OnlineHostType
from pyecharts import options as opts  # 配置项
from pyecharts.charts import WordCloud  # 词云图
from pyecharts.commons.utils import JsCode
from pyecharts.globals import ThemeType,SymbolType

rec_words = [tuple(z) for z in zip(information["word"].tolist(), information["number"].tolist())]
c = (
    WordCloud()
    .add("", rec_words[:80], word_size_range=[20, 100], shape=SymbolType.DIAMOND)
    .set_global_opts(title_opts=opts.TitleOpts(title="B站词云图"))
)

c.render_notebook()

效果是动态的:

采用的是wordcloud来绘制静态的词云图,并且保存到本地:

wordcloud.WordCloud(
  font_path=None,  # 字体路径,英文不用设置路径,中文需要,否则无法正确显示图形
  width=400, # 默认宽度
  height=200, # 默认高度
  margin=2, # 边缘
  ranks_only=None, 
  prefer_horizontal=0.9, 
  mask=None, # 背景图形,如果想根据图片绘制,则需要设置
  scale=1, 
  color_func=None, 
  max_words=200, # 最多显示的词汇量
  min_font_size=4, # 最小字号
  stopwords=None, # 停用词设置,修正词云图时需要设置
  random_state=None, 
  background_color='black', # 背景颜色设置,可以为具体颜色,比如white或者16进制数值
  max_font_size=None, # 最大字号
  font_step=1, 
  mode='RGB', 
  relative_scaling='auto', 
  regexp=None, 
  collocations=True, 
  colormap='viridis', # matplotlib 色图,可更改名称进而更改整体风格
  normalize_plurals=True, 
  contour_width=0, 
  contour_color='black', 
  repeat=False

通过下面的代码来生成词云图。注意点:需要新建一个目录wordcloud,来存放生成的词云图

word_counts = collections.Counter(useful_result)  # 筛选后统计词频
path = './wordcloud/'  # 新建:存放词云图的路径

img_files = os.listdir('./mask')

# 遍历mask目录下的全部文件
for num in range(1, len(img_files) + 1):
    img = r'./mask/mask_{}.png'.format(num)    # 原图片路径
    mask_ = 255 - np.array(Image.open(img))  # 获取蒙版图片
    # 绘制词云
    plt.figure(figsize=(8, 5), dpi=200)
    
    my_cloud = WordCloud(
        background_color='black',  # 背景颜色  
        mask=mask_,      # 自定义蒙版
        mode='RGBA',
        max_words=500,
        # 地址路径要改成自己的ttf文件路径
        font_path=r'/Users/peter/Desktop/spider/SimHei.ttf'  
    ).generate_from_frequencies(word_counts)

    # 显示词云图
    plt.imshow(my_cloud)
    # 词云图中无坐标轴
    plt.axis('off')
    wordcloud_name = path + 'wordcloud_{}.png'.format(num)
    my_cloud.to_file(wordcloud_name)    # 保存词云图片

对应生成的词云图效果:

合成词云视频

基于上面的835张词云图来生成视频:

import cv2
import os

# 输出视频的保存路径
video_dir = 'jianshen.mp4'

# 帧率,控制视频快慢
fps = 5

# 图片尺寸
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')

for i in range(1, 836):
    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()   

到达这个步骤我们完成了视频的生成,就只剩下添加【本草纲目】的音乐了

添加本草纲目.MP3

添加音频使用的是moviepy。详细使用文档参考官网:

中文:https://moviepy-cn.readthedocs.io/zh/latest/

英文:https://zulko.github.io/moviepy/install.html

先安装很简单:

pip install moviepy
import moviepy.editor as mpy

# 读取词云视频:上个步骤生成的视频
my_clip = mpy.VideoFileClip('jianshen.mp4')

# 截取背景音乐 指定时间范围
# 本草纲目的MP3自己下载,放到同一个目录下
audio_background = mpy.AudioFileClip('本草纲目.mp3').subclip(28,60)
audio_background.write_audiofile('本草纲目1.mp3')

# 插入音频
final_clip = my_clip.set_audio(audio_background)
# 最终视频  
final_clip.write_videofile('LGH.mp4')

大功告成👏

整体细节

将you-get获取到的视频和【本草纲目.MP3】放到本地

本地需要建立3个文件,存放不同的图像

代码的步骤参考1-2-3-4-5-6部分;顺序一定不能乱