feat: 添加知乎视频支持
新增: - kb-zhihu-content/: 知乎视频下载模块 - zhihu_content.py: 视频下载工具(使用 yt-dlp) - SKILL.md: 技能说明文档 修改: - kb_workflow.py: - CONTENT_AGENTS 添加 zhihu - VIDEO_CONTENT_TYPES 添加 zvideo - SKILL.md: - 添加 4.1.2 知乎视频处理流程 - 更新子技能清单,添加知乎视频条目 - 更新内容类型判断表格
This commit is contained in:
+26
-5
@@ -157,11 +157,12 @@ python3 /path/to/zhihu_metadata.py --id "[ID]" --type question --output "${metad
|
||||
### 步骤 4: 内容下载
|
||||
|
||||
> ⚠️ **根据内容类型选择处理方式**:
|
||||
> - **视频 (video)**:下载字幕/视频
|
||||
> - **视频 (video)**:下载字幕/视频(B站视频)
|
||||
> - **知乎视频 (zvideo)**:下载视频
|
||||
> - **专栏文章 (article)**:提取文字内容(跳过步骤5)
|
||||
> - **知乎问答/专栏**:提取文字内容(跳过步骤5)
|
||||
|
||||
#### 4.1 视频内容处理(type=video)
|
||||
#### 4.1 视频内容处理(B站视频 type=video)
|
||||
|
||||
1. 获取目录路径:
|
||||
```bash
|
||||
@@ -183,6 +184,22 @@ python3 /path/to/bilibili_content.py --id "[ID]" --type subtitle-download --outp
|
||||
|
||||
> ⚠️ **重要提示**:没有找到官方字幕是**正常的**!很多视频没有自动生成的字幕。不要因为没有字幕就停止工作流,继续执行 Whisper 步骤即可。
|
||||
|
||||
#### 4.1.2 知乎视频处理(type=zvideo)
|
||||
|
||||
1. 获取目录路径:
|
||||
```bash
|
||||
python3 kb_workflow.py --content_downloads
|
||||
python3 kb_workflow.py --content
|
||||
```
|
||||
|
||||
2. 启动子代理调用知乎 content 技能:
|
||||
```bash
|
||||
# 知乎视频下载
|
||||
python3 /path/to/zhihu_content.py --id "[ID]" --type video --output [content_dir]
|
||||
```
|
||||
|
||||
3. 知乎视频没有官方字幕,直接进入步骤 5(Whisper 语音识别)
|
||||
|
||||
#### 4.2 专栏文章处理(type=article/zhuanlan/question)
|
||||
|
||||
1. 获取目录路径:
|
||||
@@ -321,10 +338,13 @@ python3 kb_workflow.py --archive-file "[分析报告文件路径]"
|
||||
| 2 | kb-link-parser | `--url "BVxxx"` | `{p}_{id}.link.json` | |
|
||||
| 3 | kb-bilibili-metadata | `--id "BVxxx" --type video` | `{p}_{id}.meta.json` | |
|
||||
| 3 | kb-bilibili-metadata | `--id "cvxxx" --type article` | `{p}_{id}.meta.json` | 专栏元数据 |
|
||||
| 3 | kb-zhihu-metadata | `--id "xxx" --type question` | `{p}_{id}.meta.json` | |
|
||||
| 3 | kb-zhihu-metadata | `--id "xxx" --type question` | `{p}_{id}.meta.json` | 知乎问答 |
|
||||
| 3 | kb-zhihu-metadata | `--id "xxx" --type answer` | `{p}_{id}.meta.json` | 知乎回答 |
|
||||
| 3 | kb-zhihu-metadata | `--id "xxx" --type zvideo` | `{p}_{id}.meta.json` | 知乎视频 |
|
||||
| 4 | kb-bilibili-content | `--id "BVxxx" --type subtitle-download` | `{p}_{id}.srt` | 如果没有字幕是正常的 |
|
||||
| 4 | kb-bilibili-content | `--id "BVxxx" --type video` | `{p}_{id}.mp4` | 如果需要下载视频 |
|
||||
| 4 | kb-bilibili-content | `--id "cvxxx" --type article` | `{p}_{id}.txt` | **专栏文章提取文字** |
|
||||
| 4 | kb-zhihu-content | `--id "xxx" --type video` | `{p}_{id}.mp4` | **知乎视频下载** |
|
||||
| 5 | kb-whisper | `--input xxx.mp4 --model base` | `{p}_{id}.json`, `{p}_{id}.txt` | 仅当没有官方字幕时执行 |
|
||||
| 5 | kb-whisper-diarization | `--input xxx.mp4 --model base --diarize` | `{p}_{id}_diarized.json` | 带说话者分离(需 HF_TOKEN) |
|
||||
| 6 | kb-deep-analyzer | `--metadata x.meta.json --content x.json --output x.md --provider zhipu --model glm-5` | `{p}_{id}.md` | **使用 g5 模型,必须成功** |
|
||||
@@ -337,13 +357,14 @@ python3 kb_workflow.py --archive-file "[分析报告文件路径]"
|
||||
|
||||
| type 值 | 内容类型 | 处理方式 |
|
||||
|---------|---------|---------|
|
||||
| `video` | 视频 | 下载视频/字幕,可能需要 Whisper |
|
||||
| `video` | B站视频 | 下载视频/字幕,可能需要 Whisper |
|
||||
| `zvideo` | 知乎视频 | 下载视频,需要 Whisper 语音识别 |
|
||||
| `article` | B站专栏 | 直接提取文字内容 |
|
||||
| `zhuanlan` | 知乎专栏 | 直接提取文字内容 |
|
||||
| `question` / `answer` | 知乎问答 | 直接提取文字内容 |
|
||||
| `note` | 小红书 | 下载图片 |
|
||||
|
||||
### 视频处理流程(type=video)
|
||||
### 视频处理流程(type=video/zvideo)
|
||||
|
||||
```
|
||||
步骤3: 获取元数据
|
||||
|
||||
@@ -30,13 +30,14 @@ METADATA_AGENTS = {
|
||||
|
||||
CONTENT_AGENTS = {
|
||||
"bilibili": "kb-bilibili-content",
|
||||
"zhihu": "kb-zhihu-content",
|
||||
}
|
||||
|
||||
|
||||
# ==================== 内容类型判断 ====================
|
||||
|
||||
# 需要下载视频/字幕的内容类型
|
||||
VIDEO_CONTENT_TYPES = {"video"}
|
||||
VIDEO_CONTENT_TYPES = {"video", "zvideo"}
|
||||
|
||||
# 需要提取文字的内容类型
|
||||
TEXT_CONTENT_TYPES = {"article", "zhuanlan", "question", "answer"}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# KB Zhihu Content Skill
|
||||
|
||||
**kb-zhihu-content** - Download Zhihu videos and extract content.
|
||||
|
||||
## Features
|
||||
|
||||
- Video download using **yt-dlp**
|
||||
- Subtitle extraction (returns no subtitle, triggers Whisper)
|
||||
- Supports `ZHIHU_COOKIE` environment variable
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `ZHIHU_COOKIE` | No | 知乎登录 Cookie (for member-only videos) |
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Download video
|
||||
python zhihu_content.py --id "12345678" --type video --output "/path/to/downloads/"
|
||||
|
||||
# Extract subtitle (returns no subtitle, triggers Whisper)
|
||||
python zhihu_content.py --id "12345678" --type subtitle --output "/path/to/subtitles/"
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"type": "video",
|
||||
"filename": "video_title.mp4",
|
||||
"filepath": "/path/to/downloads/video_title.mp4",
|
||||
"message": "下载完成: video_title.mp4"
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Already included in kb-skills repo
|
||||
# Ensure yt-dlp is installed
|
||||
pip install yt-dlp aiohttp
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
Video download uses **yt-dlp**:
|
||||
1. Parses `ZHIHU_COOKIE` environment variable (optional)
|
||||
2. Creates temporary Netscape-format cookie file
|
||||
3. Runs `yt-dlp -f best -o <output> <url>`
|
||||
4. Cleans up temporary file
|
||||
|
||||
## Notes
|
||||
|
||||
- 知乎视频通常没有官方字幕,将自动触发 Whisper 语音识别
|
||||
- 视频下载需要能访问知乎(可能需要代理)
|
||||
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
知乎内容获取工具
|
||||
|
||||
支持:
|
||||
- zvideo: 知乎视频下载
|
||||
- 从页面提取视频播放地址
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
|
||||
class ZhihuContent:
|
||||
"""知乎内容获取器"""
|
||||
|
||||
def __init__(self, output_dir: str = "./output", cookie: str = None):
|
||||
self.output_dir = Path(output_dir)
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.cookie = cookie
|
||||
|
||||
def _create_cookie_file(self) -> str:
|
||||
"""创建 Netscape 格式的 Cookie 文件"""
|
||||
if not self.cookie:
|
||||
return None
|
||||
|
||||
cookies = {}
|
||||
for item in self.cookie.split(';'):
|
||||
item = item.strip()
|
||||
if '=' in item:
|
||||
key, value = item.split('=', 1)
|
||||
cookies[key.strip()] = value.strip()
|
||||
|
||||
cookie_content = """# Netscape HTTP Cookie File
|
||||
# Generated by zhihu_content.py
|
||||
|
||||
"""
|
||||
|
||||
for name, value in cookies.items():
|
||||
cookie_content += f".zhihu.com\tTRUE\t/\tFALSE\t0\t{name}\t{value}\n"
|
||||
|
||||
fd, path = tempfile.mkstemp(suffix='.txt')
|
||||
with os.fdopen(fd, 'w') as f:
|
||||
f.write(cookie_content)
|
||||
|
||||
return path
|
||||
|
||||
async def download_video(self, video_id: str, quality: int = 720) -> Dict[str, Any]:
|
||||
"""下载知乎视频(使用 yt-dlp)"""
|
||||
result = {"success": False, "video_id": video_id, "message": "", "filepath": None, "metadata": {}}
|
||||
|
||||
try:
|
||||
print(f"获取知乎视频信息: {video_id}")
|
||||
|
||||
cookie_file = self._create_cookie_file()
|
||||
url = f"https://www.zhihu.com/zvideo/{video_id}"
|
||||
|
||||
# 先获取视频标题
|
||||
try:
|
||||
if cookie_file:
|
||||
cmd = ["yt-dlp", "--cookies", cookie_file, "--get-title", url]
|
||||
else:
|
||||
cmd = ["yt-dlp", "--get-title", url]
|
||||
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = await proc.communicate()
|
||||
|
||||
title = stdout.decode('utf-8').strip()
|
||||
if not title or 'error' in title.lower():
|
||||
title = f"zhihu_video_{video_id}"
|
||||
except Exception as e:
|
||||
title = f"zhihu_video_{video_id}"
|
||||
|
||||
# 构建 yt-dlp 命令
|
||||
safe_title = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).strip()[:50]
|
||||
|
||||
print(f"下载视频: {title}")
|
||||
output_template = str(self.output_dir / f"{safe_title}.%(ext)s")
|
||||
|
||||
# 构建下载命令
|
||||
cmd = [
|
||||
"yt-dlp",
|
||||
"-f", "bestvideo+bestaudio/best",
|
||||
"-o", output_template,
|
||||
"--no-download-archive",
|
||||
url
|
||||
]
|
||||
|
||||
if cookie_file:
|
||||
cmd.extend(["--cookies", cookie_file])
|
||||
|
||||
print(f"执行命令: {' '.join(cmd[:4])}...")
|
||||
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = await proc.communicate()
|
||||
|
||||
# 查找下载的文件
|
||||
downloaded_files = list(self.output_dir.glob(f"{safe_title}.*"))
|
||||
if downloaded_files:
|
||||
video_file = downloaded_files[0]
|
||||
result["success"] = True
|
||||
result["filepath"] = str(video_file)
|
||||
result["message"] = f"下载完成: {video_file.name}"
|
||||
result["metadata"] = {"title": title, "video_id": video_id}
|
||||
print(f"✅ {result['message']}")
|
||||
else:
|
||||
result["message"] = "未找到下载的文件"
|
||||
print(f"❌ {result['message']}")
|
||||
|
||||
# 清理临时 Cookie 文件
|
||||
if cookie_file and os.path.exists(cookie_file):
|
||||
os.remove(cookie_file)
|
||||
|
||||
except Exception as e:
|
||||
result["message"] = f"错误: {str(e)}"
|
||||
print(f"❌ {result['message']}")
|
||||
|
||||
return result
|
||||
|
||||
async def extract_subtitle(self, video_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
提取字幕
|
||||
|
||||
知乎视频通常没有官方字幕,需要使用 Whisper 进行语音识别。
|
||||
此函数返回 has_subtitle=False,让工作流继续执行 Whisper 步骤。
|
||||
"""
|
||||
result = {"success": True, "video_id": video_id, "has_subtitle": False, "message": "知乎视频无官方字幕,将使用 Whisper 进行语音识别"}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser(description='知乎内容获取工具')
|
||||
parser.add_argument('--id', '-i', required=True, help='知乎视频ID')
|
||||
parser.add_argument('--type', '-t', required=True, choices=['video', 'subtitle'], help='内容类型')
|
||||
parser.add_argument('--output', '-o', default='./output', help='输出目录')
|
||||
parser.add_argument('--quality', '-q', type=int, default=720, help='视频质量')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
cookie = os.environ.get('ZHIHU_COOKIE')
|
||||
downloader = ZhihuContent(output_dir=args.output, cookie=cookie)
|
||||
|
||||
if args.type == 'video':
|
||||
result = await downloader.download_video(args.id, quality=args.quality)
|
||||
else:
|
||||
result = await downloader.extract_subtitle(args.id)
|
||||
|
||||
# 统一输出格式
|
||||
if result.get("success"):
|
||||
output = {
|
||||
"success": True,
|
||||
"type": args.type,
|
||||
"has_subtitle": result.get("has_subtitle", False),
|
||||
"filename": Path(result.get('filepath', '')).name if result.get('filepath') else None,
|
||||
"filepath": str(Path(result.get('filepath', '')).resolve()) if result.get('filepath') else None,
|
||||
"message": result.get('message', '处理成功')
|
||||
}
|
||||
else:
|
||||
output = {
|
||||
"success": False,
|
||||
"error": result.get('message', '处理失败'),
|
||||
"detail": f"ID: {args.id}, 类型: {args.type}"
|
||||
}
|
||||
|
||||
print(json.dumps(output, ensure_ascii=False, indent=2))
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
result = asyncio.run(main())
|
||||
sys.exit(0 if result.get("success") else 1)
|
||||
except KeyboardInterrupt:
|
||||
print(json.dumps({"success": False, "error": "用户取消"}, ensure_ascii=False, indent=2))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"success": False, "error": str(e)}, ensure_ascii=False, indent=2))
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user