5507b1d986
- kb-link-parser: Multi-platform URL parser (Bilibili, YouTube, Zhihu) - kb-bilibili-metadata: Bilibili video/article/up主 metadata extractor - kb-zhihu-metadata: Zhihu question/answer/user metadata extractor - kb-bilibili-content: Bilibili video download and content extraction - kb-ocr-processor: OCR text extraction from video frames - kb-deep-analyzer: AI-powered deep content analysis - kb-workflow: Workflow orchestration (inbox, archive, subscriptions, report) Also add: - ARCHITECTURE_FLOWCHART.md: System architecture diagrams - DETAILED_DESIGN.md: Complete design documentation
176 lines
5.8 KiB
Python
176 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
使用旧版 API 获取并下载视频流
|
|
"""
|
|
import asyncio
|
|
import json
|
|
import os
|
|
from playwright.async_api import async_playwright
|
|
|
|
async def download_video_stream(bvid, output_dir="/tmp/bilibili_downloads"):
|
|
"""下载视频流"""
|
|
url = f"https://www.bilibili.com/video/{bvid}"
|
|
|
|
# 创建输出目录
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
async with async_playwright() as playwright:
|
|
browser = await playwright.chromium.launch(headless=True)
|
|
context = await browser.new_context()
|
|
page = await context.new_page()
|
|
|
|
print(f"正在下载视频: {url}")
|
|
print("=" * 80)
|
|
|
|
# 首先获取 aid 和 cid
|
|
print("\n1. 获取视频信息...")
|
|
try:
|
|
api_url = "https://api.bilibili.com/x/web-interface/view"
|
|
response = await page.request.get(api_url, params={'bvid': bvid})
|
|
data = await response.json()
|
|
|
|
if data.get('code') != 0:
|
|
print(f" ❌ 失败: {data.get('message')}")
|
|
await browser.close()
|
|
return None
|
|
|
|
video_data = data.get('data', {})
|
|
title = video_data.get('title')
|
|
aid = video_data.get('aid')
|
|
cid = video_data.get('cid')
|
|
duration = video_data.get('duration')
|
|
print(f" 标题: {title}")
|
|
print(f" 时长: {duration}秒")
|
|
except Exception as e:
|
|
print(f" ❌ 异常: {e}")
|
|
await browser.close()
|
|
return None
|
|
|
|
# 获取视频流 URL
|
|
print("\n2. 获取视频流 URL...")
|
|
try:
|
|
playurl_api = "https://api.bilibili.com/x/player/playurl"
|
|
params = {
|
|
'bvid': bvid,
|
|
'cid': cid,
|
|
'qn': 80, # 1080P
|
|
'otype': 'json',
|
|
'fnver': 0,
|
|
'fnval': 0, # FLV 格式
|
|
}
|
|
|
|
playurl_response = await page.request.get(playurl_api, params=params)
|
|
|
|
if not playurl_response.ok:
|
|
print(f" ❌ HTTP 错误: {playurl_response.status}")
|
|
await browser.close()
|
|
return None
|
|
|
|
playurl_data = await playurl_response.json()
|
|
|
|
if playurl_data.get('code') != 0:
|
|
print(f" ❌ API 错误: {playurl_data.get('message')}")
|
|
await browser.close()
|
|
return None
|
|
|
|
play_data = playurl_data.get('data', {})
|
|
|
|
# 检查支持的清晰度
|
|
print(f"\n 支持的清晰度:")
|
|
for i, (q, desc) in enumerate(zip(play_data.get('accept_quality', []), play_data.get('accept_description', []))):
|
|
current = " (当前)" if q == play_data.get('quality') else ""
|
|
print(f" {desc} ({q}){current}")
|
|
|
|
# 获取视频 URL
|
|
durl = play_data.get('durl', [])
|
|
if not durl:
|
|
print(f" ❌ 没有找到视频流")
|
|
await browser.close()
|
|
return None
|
|
|
|
print(f"\n 找到 {len(durl)} 个视频分段")
|
|
video_urls = []
|
|
for i, segment in enumerate(durl):
|
|
segment_url = segment.get('url')
|
|
segment_size = segment.get('size', 0)
|
|
video_urls.append(segment_url)
|
|
print(f" 分段 {i+1}: {segment_size / 1024 / 1024:.2f}MB")
|
|
print(f" {segment_url[:100]}...")
|
|
|
|
except Exception as e:
|
|
print(f" ❌ 异常: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
await browser.close()
|
|
return None
|
|
|
|
# 下载视频
|
|
print(f"\n3. 开始下载视频...")
|
|
all_segments = []
|
|
total_size = 0
|
|
|
|
for i, video_url in enumerate(video_urls, 1):
|
|
print(f"\n 下载分段 {i}/{len(video_urls)}...")
|
|
|
|
try:
|
|
# 添加必要的请求头
|
|
headers = {
|
|
'Referer': 'https://www.bilibili.com/',
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
}
|
|
|
|
# 下载分段
|
|
segment_response = await page.request.get(
|
|
video_url,
|
|
headers=headers,
|
|
timeout=60000
|
|
)
|
|
|
|
if not segment_response.ok:
|
|
print(f" ❌ 下载失败: {segment_response.status}")
|
|
continue
|
|
|
|
segment_data = await segment_response.body()
|
|
segment_size = len(segment_data)
|
|
total_size += segment_size
|
|
all_segments.append(segment_data)
|
|
|
|
print(f" ✅ 下载成功: {segment_size / 1024 / 1024:.2f}MB")
|
|
|
|
except Exception as e:
|
|
print(f" ❌ 下载异常: {e}")
|
|
continue
|
|
|
|
if not all_segments:
|
|
print(f"\n ❌ 没有成功下载任何分段")
|
|
await browser.close()
|
|
return None
|
|
|
|
# 合并分段
|
|
print(f"\n4. 合并视频分段...")
|
|
video_data = b''.join(all_segments)
|
|
|
|
# 保存文件
|
|
safe_title = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).strip()[:50]
|
|
output_path = os.path.join(output_dir, f"{safe_title}.flv")
|
|
|
|
with open(output_path, 'wb') as f:
|
|
f.write(video_data)
|
|
|
|
print(f" ✅ 视频已保存: {output_path}")
|
|
print(f" 文件大小: {total_size / 1024 / 1024:.2f}MB")
|
|
|
|
await browser.close()
|
|
|
|
return output_path
|
|
|
|
if __name__ == '__main__':
|
|
bvid = "BV1KtFzzqEUG"
|
|
result = asyncio.run(download_video_stream(bvid))
|
|
if result:
|
|
print(f"\n✅ 下载完成!")
|
|
print(f"文件路径: {result}")
|
|
else:
|
|
print(f"\n❌ 下载失败")
|