Cet article présente une méthode d'analyse des données vidéo de la plateforme Bilibili en utilisant des technologies de big data, aboutissant à des résultats visualisés.
1. Analyse globale des vidéos
Approche d'analyse : On comemnce par une vue d'ensemble, puis on examine les 100 vidéos les mieux classées par catégorie.
Vue d'ensemble inclut :
- Le volume de lecture par section.
- Les interactions « triple-action » (pièces, favoris, likes) par section.
- Les données de commentaires, barrage (danmaku) et partages.
- Génération d'un nuage de mots-clés synthétique.
Top 100 des vidéos inclut :
- Répartition des catégories dans le top 100.
- Volume de lecture du top 100.
- Distribution moyenne des pièces, favoris et likes.
- Moyenne des commentaires, barrages et partages par section.
1.1 Prétraitement des données
On examine les informations, les valeurs manquantes, les doublons et les types de données. Comme les données sont assez complètes, le traitement supplémentaire est limité.
On effectue des opérations de séparation et d'agrégation pour faciliter l'analyse. La catégorie « Toutes sections » contient des vidéos en tête de classement dans chaque catégorie, ce qui génère des doublons ; ces entrées sont donc exclues.
df.info()
df.isnull().count()
df.nunique().count()
df.dtypes
# Exclusion du classement global
df_filtered = df[df['section_type'] != 'global']
df_filtered['section_type'].value_counts()
# Tri par score (ordre ascendant)
df_top100 = df_filtered.sort_values(by='score', ascending=False)[:100]
df_by_type = df_filtered.drop(['author', 'video_id', 'tag_name', 'video_title', 'rank'], axis=1)
gp_by_type = df_by_type.groupby('section_type').sum().astype('int')
type_list = gp_by_type.index.tolist()
1.2 Visualisation des données
Volume de lecture par section
play_volumes = [round(x/100000000, 2) for x in gp_by_type['play_count'].tolist()]
pie_chart = (
Pie()
.add(
"",
[list(pair) for pair in zip(type_list, play_volumes)],
radius=["40%", "75%"],
)
.set_global_opts(
title_opts=opts.TitleOpts(title="Volume de lecture par section (unité : cent millions)"),
legend_opts=opts.LegendOpts(orient="vertical", pos_top="15%", pos_left="2%"),
)
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
)
pie_chart.render_notebook()
Les trois premières catégories en termes de lecture sont : Vie quotidienne, Animation et Musicalité étrange. Ces deux dernières sont des points forts distinctifs de la plateforme.
Interactions « triple-action » par section
coin_values = [round(x/1000000, 2) for x in gp_by_type['coin_count'].tolist()]
like_values = [round(x/1000000, 2) for x in gp_by_type['like_count'].tolist()]
fav_values = [round(x/1000000, 2) for x in gp_by_type['favorite_count'].tolist()]
def create_bar_chart() -> Bar:
chart = (
Bar()
.add_xaxis(type_list)
.add_yaxis("Pièces", coin_values)
.add_yaxis("Likes", like_values)
.add_yaxis("Favoris", fav_values)
.set_global_opts(
title_opts=opts.TitleOpts(title="Interactions triple-action par section"),
yaxis_opts=opts.AxisOpts(name="Unité : million"),
xaxis_opts=opts.AxisOpts(name="Section", axislabel_opts={"rotate": 45})
)
)
return chart
create_bar_chart().render_notebook()
La section Vie quotidienne domine pour les pièces et les likes, mais les favoris sont plus élevés pour la section Animation. La section Technologie occupe la quatrième place pour les favoris.
Barragse, commentaires et partages
barrage_values = [round(x/100000, 2) for x in gp_by_type['barrage_count'].tolist()]
comment_values = [round(x/100000, 2) for x in gp_by_type['comment_count'].tolist()]
share_values = [round(x/100000, 2) for x in gp_by_type['share_count'].tolist()]
line_chart = (
Line()
.add_xaxis(type_list)
.add_yaxis("Barrages", barrage_values, label_opts=opts.LabelOpts(is_show=False))
.add_yaxis("Commentaires", comment_values, label_opts=opts.LabelOpts(is_show=False))
.add_yaxis("Partages", share_values, label_opts=opts.LabelOpts(is_show=False))
.set_global_opts(
title_opts=opts.TitleOpts(title="Barrages, commentaires et partages"),
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"),
yaxis_opts=opts.AxisOpts(name="Unité : cent mille"),
xaxis_opts=opts.AxisOpts(name="Date", axislabel_opts={"rotate": 45})
)
)
line_chart.render_notebook()
Nuage de tags populaires
tags_concatenated = ','.join(df_filtered['tag_name']).split(',')
tags_frequency = pd.Series(tags_concatenated).value_counts()
word_cloud = (
WordCloud()
.add("", [list(pair) for pair in zip(tags_frequency.index, tags_frequency)],
word_size_range=[10, 100])
.set_global_opts(title_opts=opts.TitleOpts(title="Tags populaires"))
)
word_cloud.render_notebook()
Distribution moyenne des interactions
gp_quality = df_top100.groupby('section_type')[['coin_count', 'favorite_count', 'like_count']].mean().astype('int')
section_names = gp_quality.index.tolist()
coin_means = gp_quality['coin_count'].values.tolist()
fav_means = gp_quality['favorite_count'].values.tolist()
like_means = gp_quality['like_count'].values.tolist()
def create_radar_chart() -> Radar:
chart = (
Radar()
.add_schema(
schema=[opts.RadarIndicatorItem(name=name, max_=600000) for name in section_names]
)
.add("Pièces", [coin_means], color='#40e0d0')
.add("Favoris", [fav_means], color='#1e90ff')
.add("Likes", [like_means], color='#b8860b')
.set_series_opts(
label_opts=opts.LabelOpts(is_show=False),
linestyle_opts=opts.LineStyleOpts(width=3, type_='dotted'),
)
.set_global_opts(title_opts=opts.TitleOpts(title="Distribution moyenne des interactions"))
)
return chart
create_radar_chart().render_notebook()
La section Vie quotidienne conserve une moyenne de pièces et de likes supérieure à celle de l'Animation. Les sections avec les moyennes les plus élevées sont : Vie quotidienne, Cinéma/TV et Mode. La section Mode se distingue par un nombre de favoris nettement supérieur aux pièces et likes.
1.3 Résultats de l'analyse
Les visualisations montrent que les trois catégories les plus lues sont Vie quotidienne, Animation et Musicalité étrange. Fait surprenant : la catégorie la plus regardée sur une plateforme historiquement centrée sur l'animation est la vie quotidienne.
La répartition des catégories dans le top 100 est cohérente avec l'analyse globale. La moyenne des pièces et likes pour la vie quotidienne dépasse celle de l'animation.
2. Analyse d'une vidéo unique
2.1 Prétraitement des données
Démonstration d'un script de collecte de données
import requests, csv, time
import sys
from bs4 import BeautifulSoup
def fetch_barrage_data(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Cookie': 'votre_cookie_ici'
}
response = requests.get(url=url, headers=headers)
soup = BeautifulSoup(response.content, 'lxml')
barrage_elements = soup.find_all('d')
if not barrage_elements:
return []
processed_data = []
for element in barrage_elements:
parts = element.get('p').split(',')
parts.append(element.string)
parts[4] = time.ctime(float(parts[4]))
processed_data.append(parts)
return processed_data
def convert_seconds(seconds):
seconds = float(seconds)
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
return "%02d:%02d:%02d" % (h, m, s)
def main():
video_ids = [16980576, 16980597, 16548432]
output_header = ['time', 'mode', 'font', 'color', 'timestamp', 'pool', 'user_id', 'row_id', 'text']
for idx, vid in enumerate(video_ids):
url = f"https://comment.bilibili.com/{vid}.xml"
file_name = f"barrage_{idx}.csv"
data = fetch_barrage_data(url)
if data:
with open(file_name, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(output_header)
writer.writerows(data)
if __name__ == "__main__":
main()
2.2 Nettoyage des données
Importation des bibliothèques
import numpy as np
import pandas as pd
import glob
import re
import jieba
import matplotlib.pyplot as plt
import seaborn as sns
from pyecharts.charts import *
from pyecharts import options as opts
from snownlp import SnowNLP
from gensim import corpora, models
Fusion des fichiers de données
csv_files = glob.glob('./data/barrage/*.csv')
print(f'Découverte de {len(csv_files)} fichiers CSV.')
with open('merged_barrages.csv', 'w') as outfile:
for fname in csv_files:
with open(fname, 'r') as infile:
outfile.write(infile.read())
outfile.write('\n')
print('Fusion terminée.')
Traitement des doublons et valeurs manquantes
df = pd.read_csv("./merged_barrages.csv", header=None, on_bad_lines='skip')
df = df.iloc[:, [1, 2]]
df = df.drop_duplicates()
df = df.dropna()
df.columns = ["user", "barrage_text"]
Compression des séquences répétées
def compress_repeats(text):
if len(text) < 2:
return text
compressed = text[0]
for char in text[1:]:
if char != compressed[-1]:
compressed += char
return compressed
df["barrage_text"] = df["barrage_text"].apply(compress_repeats)
Filtrage des caractères spéciaux
df['barrage_text'] = df['barrage_text'].str.extract(r"([\u4e00-\u9fa5]+)")
df = df.dropna()
df = df[df["barrage_text"].str.len() >= 4]
2.3 Visualisation des données
Les analyses visuelles révèlent une reconnaissance positive pour la série « Le Silence de la Vérité », avec une mention fréquente du personnage de l'avocat Jiang Yang, incarné par l'acteur Bai Yu.
3. Traitement du langage naturel (NLP)
3.1 Analyse des sentiments
L'analyse des sentiments permet d'évaluer la polarité émotionnelle des textes. On utilise ici la bibliothèque SnowNLP pour attribuer un score entre 0 (négatif) et 1 (positif) à chaque barrage.
df['sentiment_score'] = df["barrage_text"].apply(lambda x: SnowNLP(x).sentiments)
Distribution des sentiments globaux
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(12, 6))
sns.histplot(df['sentiment_score'], kde=True, bins=20, color='green')
plt.title("Distribution des sentiments dans les barrages")
plt.xlabel("Score de sentiment")
plt.ylabel("Densité")
plt.show()
Sentiments envers les acteurs principaux
actor_map = {
'jiangyang': 'Bai Yu|Jiang Yang',
'yanliang': 'Liao Fan|Yan Liang',
'zhangchao': 'Ning Li|Zhang Chao'
}
for actor_key, actor_pattern in actor_map.items():
df[actor_key] = df['barrage_text'].str.contains(actor_pattern)
actor_sentiment = pd.Series({
key: df.loc[df[key], 'sentiment_score'].mean()
for key in actor_map.keys()
})
print(actor_sentiment.sort_values())
Les scores moyens indiquent une perception positive de tous les acteurs. Jiang Yang obtient un score légèrement inférieur, possiblement en raison de l'empathie des spectateurs face aux injustices subies par son personnage.
3.2 Analyse thématique
On segmente les barrages en deux groupes : positifs (score > 0.8) et négatifs (score < 0.3), puis on applique une modélisation thématique LDA pour identifier les thèmes principaux dans chaque groupe.
Prétraitement textuel
# Segmentation des mots
positive_text = df['barrage_text'][df['sentiment_score'] >= 0.8]
negative_text = df['barrage_text'][df['sentiment_score'] < 0.3]
def segment_text(text):
return ' '.join(jieba.cut(text))
positive_segmented = positive_text.apply(segment_text)
negative_segmented = negative_text.apply(segment_text)
# Suppression des mots vides
stopwords = open('./stopwords.txt', 'r', encoding='utf-8').read().splitlines()
def remove_stopwords(text_list):
return [word for word in text_list if word not in stopwords and len(word) > 1]
positive_tokens = positive_segmented.apply(lambda x: remove_stopwords(x.split()))
negative_tokens = negative_segmented.apply(lambda x: remove_stopwords(x.split()))
Modélisation thématique sur les commentaires positifs
positive_dict = corpora.Dictionary(positive_tokens)
positive_corpus = [positive_dict.doc2bow(text) for text in positive_tokens]
positive_lda = models.LdaModel(positive_corpus, num_topics=5, id2word=positive_dict, passes=10)
print("Thèmes positifs :")
for idx, topic in positive_lda.print_topics():
print(f"Thème {idx}: {topic}")
Modélisation thématique sur les commentaires négatifs
negative_dict = corpora.Dictionary(negative_tokens)
negative_corpus = [negative_dict.doc2bow(text) for text in negative_tokens]
negative_lda = models.LdaModel(negative_corpus, num_topics=5, id2word=negative_dict, passes=10)
print("Thèmes négatifs :")
for idx, topic in negative_lda.print_topics():
print(f"Thème {idx}: {topic}")
L'analyse thématique permet d'identifier les raisons sous-jacentes des réactions positives et négatives du public, offrant ainsi des insights appprofondis sur la perception de la série.