feat(06-01): create metadata directory and move bilibili modules

- Rename kb-workflow to kb_workflow for Python package compatibility
- Move bilibili_extractor.py and bilibili_metadata.py to kb_workflow/metadata/
- Create metadata/__init__.py with BilibiliExporter export

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 23:59:23 +08:00
parent 444ac22e44
commit e2edce2e29
8 changed files with 10 additions and 0 deletions
+228
View File
@@ -0,0 +1,228 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
B站元数据提取 CLI 工具
支持命令行调用,输出 JSON 格式数据
"""
import argparse
import asyncio
import json
import os
import sys
from pathlib import Path
from typing import Dict, Any
from bilibili_extractor import BilibiliExtractor
def format_number(num: int) -> str:
"""格式化数字"""
if num >= 100000000:
return f"{num / 100000000:.1f}亿"
elif num >= 10000:
return f"{num / 10000:.1f}万"
else:
return f"{num:,}"
def extract_video(extractor: BilibiliExtractor, bvid: str, output_file: str = None) -> Dict[str, Any]:
"""提取视频元数据"""
result = extractor.extract_video_metadata(bvid)
# 如果需要输出到文件
if output_file:
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
return result
def extract_article(extractor: BilibiliExtractor, cvid: str, output_file: str = None) -> Dict[str, Any]:
"""提取专栏元数据"""
result = extractor.extract_article_metadata(cvid)
if output_file:
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
return result
def extract_up(extractor: BilibiliExtractor, mid: str, output_file: str = None) -> Dict[str, Any]:
"""提取UP主信息(包含订阅跟踪所需信息)"""
result = asyncio.run(extractor._extract_up_async(mid))
if output_file:
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
return result
def extract_series(extractor: BilibiliExtractor, mid: str, series_id: str, output_file: str = None) -> Dict[str, Any]:
"""提取系列合集元数据"""
result = asyncio.run(extractor._extract_series_async(mid, series_id))
if output_file:
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
return result
def extract_readlist(extractor: BilibiliExtractor, rl_id: str, output_file: str = None) -> Dict[str, Any]:
"""提取专栏合集元数据"""
result = asyncio.run(extractor._extract_readlist_async(rl_id))
if output_file:
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
return result
def extract_favlist(extractor: BilibiliExtractor, fid: str, output_file: str = None) -> Dict[str, Any]:
"""提取收藏夹元数据"""
result = asyncio.run(extractor._extract_favlist_async(fid))
if output_file:
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
return result
def main():
"""主函数"""
parser = argparse.ArgumentParser(
description='B站元数据提取工具',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
# 提取视频元数据
python bilibili_metadata.py --id "BV1xx411c7mD" --type video
# 提取专栏元数据
python bilibili_metadata.py --id "cv43780186" --type article
# 提取UP主信息
python bilibili_metadata.py --id "7770724" --type up
# 提取系列合集信息
python bilibili_metadata.py --id "123456" --type series --mid "7770724"
# 提取专栏合集信息
python bilibili_metadata.py --id "rl123456" --type readlist
# 提取收藏夹信息
python bilibili_metadata.py --id "123456" --type favlist
# 输出到文件
python bilibili_metadata.py --id "BV1xx411c7mD" --type video --output result.json
"""
)
parser.add_argument(
'--id',
required=True,
help='B站资源ID(BV号、CV号或UP主ID)'
)
parser.add_argument(
'--type',
required=True,
choices=['video', 'article', 'up', 'series', 'readlist', 'favlist'],
help='资源类型'
)
parser.add_argument(
'--mid',
help='UP主ID (用于series类型)'
)
parser.add_argument(
'--output',
'-o',
help='输出文件路径(JSON格式)'
)
parser.add_argument(
'--headless',
action='store_true',
default=True,
help='使用无头模式(浏览器,默认开启,使用API)'
)
parser.add_argument(
'--timeout',
type=int,
default=30,
help='超时时间(秒,默认30)'
)
parser.add_argument(
'--pretty',
action='store_true',
default=True,
help='格式化输出(默认开启)'
)
args = parser.parse_args()
# 创建提取器
extractor = BilibiliExtractor(headless=args.headless, timeout=args.timeout)
try:
# 根据类型提取数据
if args.type == 'video':
result = extract_video(extractor, args.id, args.output)
elif args.type == 'article':
result = extract_article(extractor, args.id, args.output)
elif args.type == 'up':
result = extract_up(extractor, args.id, args.output)
elif args.type == 'series':
if not args.mid:
print(json.dumps({
"success": False,
"error": "series类型需要提供 --mid 参数"
}, ensure_ascii=False, indent=2))
sys.exit(1)
result = extract_series(extractor, args.mid, args.id, args.output)
elif args.type == 'readlist':
result = extract_readlist(extractor, args.id, args.output)
elif args.type == 'favlist':
result = extract_favlist(extractor, args.id, args.output)
else:
print(json.dumps({
"success": False,
"error": f"不支持的类型 {args.type}"
}, ensure_ascii=False, indent=2))
sys.exit(1)
# 生成文件名
platform = "bilibili"
filename = f"{platform}_{args.id}.meta.json"
filepath = args.output or filename
# 统一输出格式
output = {
"success": True,
"filename": filename,
"filepath": str(Path(filepath).resolve()) if filepath else None,
"message": f"{args.type}元数据提取成功"
}
print(json.dumps(output, ensure_ascii=False, indent=2))
# 如果指定了输出路径,则写入文件
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
except Exception as e:
print(json.dumps({
"success": False,
"error": str(e),
"detail": type(e).__name__
}, ensure_ascii=False, indent=2))
sys.exit(1)
if __name__ == '__main__':
main()