Desenvolvimento de um Extrator de Vídeos do Bilibili em Java: Integração de APIs e Download Multithread

Arquitetura Cross-Platform e Integração de APIs

O desenvolvimento de ferramentas de extração de mídia exige uma arquitetura robusta para lidar com diferentes ambientes e restrições de API. Ao construir um cliente de download para o Bilibili utilizando Java, a estratégia central baseia-se na unificação de endpoints da plataforma Web e TV. O sistema implementa um mecanismo de fallback inteligente: prioriza a API da TV para obter fluxos de vídeo sem marca d'água e, em caso de falha ou indisponibilidade, recorre à API Web para garantir a compatibilidade.

Camadas do Sistema

  • Camada de Entrada: Processamento de identificadores (BV/AV), autenticação de sessão e seleção de resolução.
  • Camada de Roteamento de API: Despacho de requisições para os endpoints da TV (foco em qualidade) e Web (foco em disponibilidade).
  • Camada de Processamento: Parsing de metadados, ordenação de streams e preparação para multiplexação.
  • Camada de Execução: Motor de download fragmentado com suporte a retomada e balanceamento de carga.

Adaptação Cross-Platform

A natureza "write once, run anywhere" do Java é aproveitada através de uma camada de abstração de sistema de arquivos e processos. A tabela abaixo detalha como as discrepâncias entre sistemas operacionais são normalizadas:

Componente Ambiente Windows Ambiente Unix-like Estratégia de Abstração
Manipulação de Paths Barra invertida (\) Barra (/) Uso de Path e Paths da NIO
Execução do FFmpeg ffmpeg.exe ffmpeg Detecção dinâmica via ProcessBuilder
Diretório do Usuário %USERPROFILE% $HOME Propriedade user.home do sistema
Concorrência Threads nativas Windows Pthreads Abstração via ExecutorService

Engenharia Reversa e Extração sem Marca d'Água

A obtenção de fluxos de vídeo limpos (sem marca d'água) requer a interação com a API destinada a dispositivos de TV. O parâmetro de controle accept_watermark é manipluado para forçar o retorno de streams originais. Isso é alcançado simulando o fingerprint de um cliente de TV legítimo.

Implementação do Cliente HTTP

O código abaixo demonstra a refatoração do gerenciador de requisições utilizando a API HttpClient nativa do Java 11+, substituindo a antiga HttpURLConnection para melhor suporte a HTTP/2 e requisições assíncronas.


import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class BilibiliApiClient {
    private final HttpClient client;
    private final String sessionCookie;

    public BilibiliApiClient(String sessionCookie) {
        this.sessionCookie = sessionCookie;
        this.client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .followRedirects(HttpClient.Redirect.NORMAL)
                .build();
    }

    public String fetchStreamMetadata(String videoId, boolean useTvEndpoint) throws Exception {
        String targetUrl = useTvEndpoint 
            ? "https://api.bilibili.com/x/tv/playurl?avid=" + videoId + "&accept_watermark=false"
            : "https://api.bilibili.com/x/player/playurl?avid=" + videoId;

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
                .uri(URI.create(targetUrl))
                .header("Cookie", "SESSDATA=" + sessionCookie)
                .GET();

        if (useTvEndpoint) {
            requestBuilder.header("User-Agent", "Mozilla/5.0 (Linux; BiliTV/2.1.0)");
            requestBuilder.header("Referer", "https://app.bilibili.com/");
        } else {
            requestBuilder.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
        }

        HttpResponse<string> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
        return response.body();
    }
}
</string>

Autenticação e Gerenciamento de Sessão

O suporte a múltiplos vetores de autenticação (QR Code Web, QR Code TV e injeção direta de cookies) é gerenciado por um orquestrador de estado. Os tokens SESSDATA e bili_jct são serializados em formato YAML e recarregados em execuções subsequentes, eliminando a necessidade de reautenticação constante.

Motor de Download Multithread e Resiliência

Para otimizar a transferência de arquivos de grande porte, o sistema utiliza uma abordagem de download segmentado. O número de threads é alocado dinamicamente com base no tamanho do payload e na latência da rede.

Algoritmo de Alocação de Threads

  • Até 10MB: Execução em thread única para evitar overhead de I/O.
  • 10MB a 500MB: 4 a 8 threads, com ajuste baseado no RTT (Round-Trip Time).
  • Acima de 500MB: 16 a 32 threads, ativando balanceamento de carga e buffers de memória otimizados.

Lógica de Retomada (Resume)

A resiliência a falhas de rede é garantida pelo rastreamento de offsets de bytes. O código a seguir ilustra a lógica de um worker de download que verifica o estado do arquivo temporário antes de iniciar a transferência.


import java.io.RandomAccessFile;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;

public class ChunkWorker implements Runnable {
    private final String url;
    private final Path tempFile;
    private long startByte;
    private long endByte;

    public ChunkWorker(String url, Path tempFile, long startByte, long endByte) {
        this.url = url;
        this.tempFile = tempFile;
        this.startByte = startByte;
        this.endByte = endByte;
    }

    @Override
    public void run() {
        try {
            long existingBytes = 0;
            if (tempFile.toFile().exists()) {
                existingBytes = tempFile.toFile().length();
                startByte += existingBytes;
            }
            
            if (startByte >= endByte) return;

            HttpRequest request = HttpRequest.newBuilder()
                    .uri(java.net.URI.create(url))
                    .header("Range", "bytes=" + startByte + "-" + endByte)
                    .build();

            HttpClient client = HttpClient.newHttpClient();
            HttpResponse<java.io.inputstream> response = client.send(
                request, HttpResponse.BodyHandlers.ofInputStream()
            );

            try (java.io.InputStream in = response.body();
                 RandomAccessFile raf = new RandomAccessFile(tempFile.toFile(), "rw")) {
                
                raf.seek(existingBytes);
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = in.read(buffer)) != -1) {
                    raf.write(buffer, 0, bytesRead);
                }
            }
        } catch (Exception e) {
            System.err.println("Falha no chunk: " + e.getMessage());
        }
    }
}
</java.io.inputstream>

Implantação e Automação

Empacotamento e Execução via CLI

O ciclo de build utiliza o Maven com o plugin de assembly para gerar um JAR executável autônomo (fat jar).


# Compilação e empacotamento
mvn clean package -DskipTests

# Execução do binário gerado
java -Xmx512m -jar target/bilibili-extractor-2.0.0-jar-with-dependencies.jar

Processamento em Lote (Batch)

Para pipelines de automação, a ferramenta aceita um arquivo de configuração não interativo:


# batch_config.txt
BV1xX4y1Y7abc
auto_quality=true
output_dir=/var/media/archive/
threads=12
merge_audio=true
ffmpeg_path=/usr/local/bin/ffmpeg

Containerização com Docker

A imagem Docker é otimizada utilizando multi-stage builds para reduzir a superfície de ataque e o tamanho final da imagem.


FROM maven:3.8.4-openjdk-11-slim AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests

FROM eclipse-temurin:11-jre-alpine
RUN apk add --no-cache ffmpeg
WORKDIR /app
COPY --from=builder /build/target/*-jar-with-dependencies.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

Desafios Técnicos e Mitigações

Evasão de Mecanismos Anti-Bot

Problema: A plataforma aplica rate limiting e validação de assinatura WBI em endpoints críticos.
Solução: Implementação de um gerador de assinaturas WBI baseado em engenharia reversa, combinado com um proxy rotativo e jitter aleatório entre requisições para mimetizar comportamento humano.

Integração Cross-Platform do FFmpeg

Problema: Discrepâncias na nomeação de binários e variáveis de ambiente entre sistemas operacionais.
Solução: Criação de um wrapper de processo que busca o executável no PATH do sistema, em diretórios padrão (como C:\ffmpeg\bin ou /usr/bin) e permite override via variável de ambiente FFMPEG_BIN.

Estabilidade em Arquivos Massivos

Problema: Downloads de streams 4K/8K podem exceder limites de memória heap e causar corrupção de arquivo.
Solução: Adoção de RandomAccessFile para escrita direta em disco, eliminando o buffering em memória. O sistema monitora o uso de heap via MemoryMXBean e pausa a alocação de novos chunks se a memória livre cair abaixo de um threshold crítico.

Tolerância a Falhas de Rede

Problema: Quedas de conexão e throttlign por parte dos CDNs.
Solução: Implementação de fallback automático para CDNs secundários (como Boson ou PCDN) caso o primário retorne erros 5xx ou timeout. O tamanho do chunk é reduzido dinamicamente em redes com alta taxa de perda de pacotes.

Tags: java Bilibili API Multithreading FFmpeg engenharia reversa

Publicado em 7-9 20:42