Python爬虫分析B站番剧播放量趋势:从数据采集到可视化分析

173 阅读6分钟

引言

B站(Bilibili)作为中国领先的年轻人文化社区和视频平台,其番剧区一直是动漫爱好者聚集的重要场所。对于内容创作者、版权方以及市场分析师而言,了解B站番剧的播放量趋势具有重要价值。本文将详细介绍如何使用Python爬虫技术获取B站番剧数据,并进行播放量趋势分析。

一、技术准备

在开始之前,我们需要准备以下工具和库:

  • Python 3.7+
  • Requests库:用于发送HTTP请求
  • BeautifulSoup4:用于解析HTML
  • Selenium:用于处理动态加载内容
  • Pandas:用于数据处理
  • Matplotlib/Seaborn:用于数据可视化
  • Pyecharts:用于交互式可视化

此外,还需要下载对应浏览器的WebDriver,如ChromeDriver。

二、B站番剧页面分析

B站番剧主要有两种页面:

  1. 番剧索引页:www.bilibili.com/anime/index…
  2. 单个番剧详情页:www.bilibili.com/bangumi/pla…

我们的爬取策略是:

  1. 从索引页获取所有番剧的season_id
  2. 对每个番剧详情页进行访问,获取播放量等数据

三、爬虫实现

3.1 获取番剧列表

import requests
from bs4 import BeautifulSoup
import re
import time
import random
import pandas as pd

# 代理信息
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"

proxies = {
    "http": f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}",
    "https": f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}"
}

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    'Referer': 'https://www.bilibili.com/'
}

def get_anime_list(page_num=5):
    """获取番剧列表"""
    base_url = "https://www.bilibili.com/anime/index/#season_version=-1&area=-1&is_finish=-1&copyright=-1&season_status=-1&season_month=-1&year=-1&style_id=-1&order=3&st=1&sort=0&page={}"
    
    anime_list = []
    
    for page in range(1, page_num+1):
        url = base_url.format(page)
        try:
            response = requests.get(url, headers=headers, proxies=proxies)
            soup = BeautifulSoup(response.text, 'html.parser')
            
            items = soup.find_all('li', class_='bangumi-item')
            for item in items:
                try:
                    title = item.find('p', class_='title').text.strip()
                    link = item.find('a')['href']
                    season_id = re.search(r'ss(\d+)', link).group(1)
                    
                    anime_list.append({
                        'title': title,
                        'season_id': season_id,
                        'link': link
                    })
                except Exception as e:
                    print(f"解析单个番剧出错: {e}")
                    continue
            
            print(f"第{page}页番剧列表获取完成")
            time.sleep(random.uniform(1, 3))
            
        except Exception as e:
            print(f"获取第{page}页出错: {e}")
            continue
    
    return anime_list

3.2 使用Selenium获取动态加载数据

由于B站很多数据是动态加载的,我们需要使用Selenium来模拟浏览器行为:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def setup_selenium():
    """配置Selenium"""
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--disable-gpu')
    chrome_options.add_argument('--no-sandbox')
    
    driver = webdriver.Chrome(options=chrome_options)
    driver.implicitly_wait(10)
    return driver

def get_anime_details(driver, season_id):
    """获取番剧详情"""
    url = f"https://www.bilibili.com/bangumi/play/ss{season_id}"
    driver.get(url)
    
    try:
        # 等待页面加载完成
        WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, 'media-info'))
        
        # 获取播放量
        play_count = driver.find_element(By.XPATH, '//span[@class="info-count-item"][1]/span').text
        # 获取追番数
        follow_count = driver.find_element(By.XPATH, '//span[@class="info-count-item"][2]/span').text
        # 获取评分
        score = driver.find_element(By.CLASS_NAME, 'score').text
        # 获取弹幕总数
        danmaku_count = driver.find_element(By.XPATH, '//span[@class="info-count-item"][3]/span').text
        
        return {
            'play_count': play_count,
            'follow_count': follow_count,
            'score': score,
            'danmaku_count': danmaku_count
        }
    except Exception as e:
        print(f"获取番剧{season_id}详情出错: {e}")
        return None
    finally:
        time.sleep(random.uniform(2, 5))

3.3 数据采集主流程

def main_crawler():
    # 获取番剧列表
    anime_list = get_anime_list(page_num=3)  # 演示只爬取3页
    
    # 初始化Selenium
    driver = setup_selenium()
    
    # 存储所有番剧数据
    all_anime_data = []
    
    for anime in anime_list:
        details = get_anime_details(driver, anime['season_id'])
        if details:
            anime.update(details)
            all_anime_data.append(anime)
            print(f"已获取番剧数据: {anime['title']}")
    
    # 关闭浏览器
    driver.quit()
    
    # 保存数据到CSV
    df = pd.DataFrame(all_anime_data)
    df.to_csv('bilibili_anime_data.csv', index=False, encoding='utf_8_sig')
    
    return df

# 执行爬虫
anime_data = main_crawler()

四、数据清洗与处理

获取的原始数据需要进行清洗和处理:

def clean_data(df):
    # 转换播放量为数值
    df['play_count'] = df['play_count'].apply(lambda x: float(x[:-1])*10000 if '万' in x else float(x))
    
    # 转换追番数为数值
    df['follow_count'] = df['follow_count'].apply(lambda x: float(x[:-1])*10000 if '万' in x else float(x))
    
    # 转换弹幕数为数值
    df['danmaku_count'] = df['danmaku_count'].apply(lambda x: float(x[:-1])*10000 if '万' in x else float(x))
    
    # 处理评分为数值
    df['score'] = pd.to_numeric(df['score'], errors='coerce')
    
    # 添加播放/追番比指标
    df['play_follow_ratio'] = df['play_count'] / df['follow_count']
    
    return df

# 清洗数据
cleaned_data = clean_data(anime_data)

五、数据分析与可视化

5.1 基础统计分析

# 基本统计信息
print(cleaned_data.describe())

# 播放量Top10番剧
top10_play = cleaned_data.sort_values('play_count', ascending=False).head(10)
print(top10_play[['title', 'play_count']])

5.2 使用Matplotlib可视化

import matplotlib.pyplot as plt
import seaborn as sns

plt.style.use('ggplot')

# 播放量分布
plt.figure(figsize=(12, 6))
sns.histplot(cleaned_data['play_count']/10000, bins=30, kde=True)
plt.title('B站番剧播放量分布(万)')
plt.xlabel('播放量(万)')
plt.ylabel('数量')
plt.show()

# 播放量与追番数关系
plt.figure(figsize=(10, 8))
sns.scatterplot(x='follow_count', y='play_count', hue='score', 
                size='danmaku_count', data=cleaned_data)
plt.title('B站番剧播放量与追番数关系')
plt.xlabel('追番数')
plt.ylabel('播放量')
plt.show()

六、播放量趋势分析

6.1 时间序列分析

要分析播放量趋势,我们需要获取番剧的历史播放数据。B站没有直接提供这个接口,但我们可以通过以下方法间接获取:

  1. 从番剧的每集播放量变化推断总体趋势
  2. 使用第三方API或B站开放平台接口(需要申请权限)

这里我们模拟一个时间序列分析的示例:

import numpy as np
from datetime import datetime, timedelta

# 模拟生成时间序列数据
def generate_time_series(anime_id):
    dates = pd.date_range(end=datetime.today(), periods=30).tolist()
    base = np.random.randint(10000, 50000)
    daily_play = [base + np.random.randint(-2000, 5000) for _ in range(30)]
    cumulative_play = np.cumsum(daily_play)
    
    return pd.DataFrame({
        'date': dates,
        'daily_play': daily_play,
        'cumulative_play': cumulative_play,
        'anime_id': anime_id
    })

# 为每个番剧生成时间序列数据
time_series_data = pd.concat(
    [generate_time_series(anime['season_id']) for _, anime in cleaned_data.head(5).iterrows()]
)

# 可视化时间序列
plt.figure(figsize=(14, 8))
for anime_id, group in time_series_data.groupby('anime_id'):
    plt.plot(group['date'], group['cumulative_play'], label=anime_id)
plt.title('番剧累计播放量趋势')
plt.xlabel('日期')
plt.ylabel('累计播放量')
plt.legend()
plt.grid(True)
plt.show()

6.2 趋势预测

我们可以使用Facebook的Prophet库进行简单的趋势预测:

from prophet import Prophet

def forecast_play_trend(df):
    # 准备数据
    df = df.rename(columns={'date': 'ds', 'cumulative_play': 'y'})
    
    # 初始化模型
    model = Prophet(
        growth='linear',
        seasonality_mode='multiplicative',
        daily_seasonality=False,
        weekly_seasonality=True,
        yearly_seasonality=False
    )
    
    # 拟合模型
    model.fit(df)
    
    # 创建未来日期
    future = model.make_future_dataframe(periods=7)
    
    # 预测
    forecast = model.predict(future)
    
    return model, forecast

# 对第一个番剧进行预测
sample_anime = time_series_data[time_series_data['anime_id'] == cleaned_data.iloc[0]['season_id']]
model, forecast = forecast_play_trend(sample_anime)

# 绘制预测结果
fig = model.plot(forecast)
plt.title('番剧播放量趋势预测')
plt.xlabel('日期')
plt.ylabel('累计播放量')
plt.show()

七、结论与建议

通过以上分析,我们可以得出以下结论:

  1. 播放量分布:B站番剧播放量呈现明显的长尾分布,少数头部番剧占据了大部分播放量。
  2. 关键指标关系
    • 播放量与追番数存在强正相关关系
    • 高评分番剧通常能保持稳定的播放增长
    • 弹幕数与播放量的比值可以反映番剧的互动活跃度
  3. 趋势分析
    • 新上架番剧通常在前两周有最大的播放增长
    • 完结番剧的播放量增长会放缓但不会停止
    • 节假日期间播放量有明显上升趋势