B站弹幕存储与分析:Java爬虫+数据库

0 阅读3分钟

1. 引言

B站(哔哩哔哩)作为国内领先的视频分享平台,其弹幕功能是其核心特色之一。弹幕数据不仅反映了用户的实时互动情绪,还能用于内容分析、舆情监控、用户行为研究等场景。本文将介绍如何使用 Java爬虫 抓取B站弹幕,并将其存储到 MySQL数据库,最后进行简单的数据分析。

1.1 技术栈

  • Java爬虫:使用 HttpClient 发送HTTP请求,JsoupJSON解析库 处理数据。
  • 数据库:MySQL 存储弹幕数据。
  • 数据分析:使用Java进行词频统计,或结合Python进行更复杂的分析(如情感分析)。

2. B站弹幕抓取

B站的弹幕数据通常以 XML 或 JSON 格式存储,可以通过分析B站API获取弹幕数据。

2.1 分析B站弹幕API

  1. 打开B站任意视频(如 https://www.bilibili.com/video/BV1GJ411x7h7)。
  2. F12 打开开发者工具,在 Network(网络) 选项卡中筛选 danmubullet 相关请求。

找到弹幕API,通常是类似:

  1. text复制下载api.bilibili.com/x/v1/dm/lis…
    • oid 是视频的唯一标识,可以通过B站API或网页源码获取。

2.2 Java爬虫实现

我们使用 HttpClient 发送请求,并用 Jsoup 解析XML格式的弹幕数据。

(1)添加Maven依赖

<dependencies>
    <!-- HTTP请求 -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
    </dependency>
    
    <!-- XML解析 -->
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.15.3</version>
    </dependency>
    
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
</dependencies>

(2)获取弹幕数据

import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class BiliBiliDanmuCrawler {

    // 代理配置
    private static final String PROXY_HOST = "www.16yun.cn";
    private static final int PROXY_PORT = 5445;
    private static final String PROXY_USER = "16QMSOML";
    private static final String PROXY_PASS = "280651";

    public static List<String> fetchDanmu(String oid) throws IOException {
        String url = "https://api.bilibili.com/x/v1/dm/list.so?oid=" + oid;

        // 1. 设置代理
        HttpHost proxy = new HttpHost(PROXY_HOST, PROXY_PORT);

        // 2. 代理认证(如果代理需要用户名/密码)
        CredentialsProvider credsProvider = new BasicCredentialsProvider();
        credsProvider.setCredentials(
                new AuthScope(PROXY_HOST, PROXY_PORT),
                new UsernamePasswordCredentials(PROXY_USER, PROXY_PASS)
        );

        // 3. 配置HTTP客户端(带代理)
        CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultCredentialsProvider(credsProvider)
                .setProxy(proxy)
                .build();

        // 4. 设置请求超时(可选)
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(5000)  // 连接超时(毫秒)
                .setSocketTimeout(5000)    // 读取超时(毫秒)
                .build();

        HttpGet httpGet = new HttpGet(url);
        httpGet.setConfig(config);  // 应用超时设置

        List<String> danmuList = new ArrayList<>();

        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            String xml = EntityUtils.toString(response.getEntity());
            Document doc = Jsoup.parse(xml);
            Elements danmus = doc.select("d");

            for (Element danmu : danmus) {
                danmuList.add(danmu.text());
            }
        } finally {
            httpClient.close();  // 确保关闭HTTP客户端
        }

        return danmuList;
    }

    public static void main(String[] args) throws IOException {
        String oid = "12345678"; // 替换为真实oid
        List<String> danmus = fetchDanmu(oid);
        danmus.forEach(System.out::println);
    }
}

3. 存储弹幕到MySQL

3.1 创建数据库表

CREATE TABLE bilibili_danmu (
    id INT AUTO_INCREMENT PRIMARY KEY,
    content TEXT,
    video_id VARCHAR(50),
    crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

3.2 Java写入MySQL

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

public class DanmuStorage {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/bilibili?useSSL=false";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";
    
    public static void saveDanmu(List<String> danmus, String videoId) {
        String sql = "INSERT INTO bilibili_danmu (content, video_id) VALUES (?, ?)";
        
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            for (String danmu : danmus) {
                pstmt.setString(1, danmu);
                pstmt.setString(2, videoId);
                pstmt.executeUpdate();
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws IOException {
        String oid = "12345678"; // 替换为真实oid
        List<String> danmus = BiliBiliDanmuCrawler.fetchDanmu(oid);
        saveDanmu(danmus, "BV1GJ411x7h7");
    }
}

4. 弹幕数据分析

4.1 词频统计

import java.util.*;
import java.util.stream.Collectors;

public class DanmuAnalysis {
    
    public static Map<String, Long> wordFrequency(List<String> danmus) {
        return danmus.stream()
                .flatMap(danmu -> Arrays.stream(danmu.split(" ")))
                .filter(word -> word.length() > 1) // 过滤单字
                .collect(Collectors.groupingBy(
                        word -> word,
                        Collectors.counting()
                ))
                .entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
                .limit(20)
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (e1, e2) -> e1,
                        LinkedHashMap::new
                ));
    }
    
    public static void main(String[] args) throws IOException {
        String oid = "12345678";
        List<String> danmus = BiliBiliDanmuCrawler.fetchDanmu(oid);
        Map<String, Long> wordFreq = wordFrequency(danmus);
        wordFreq.forEach((word, count) -> System.out.println(word + ": " + count));
    }
}

5. 总结

本文介绍了:

  1. B站弹幕API分析:如何找到弹幕数据接口。
  2. Java爬虫实现:使用 HttpClient + Jsoup 抓取弹幕。
  3. 数据存储:将弹幕存入MySQL数据库。
  4. 数据分析:词频统计及情感分析(可选)。

扩展方向

  • 反爬策略:B站可能有反爬机制,可以尝试使用代理IP或模拟浏览器(Selenium)。
  • 实时弹幕:使用WebSocket监听B站实时弹幕。
  • 可视化:用ECharts或Python Matplotlib 绘制弹幕词云。