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:
2026-02-16 22:19:25 +08:00
parent d2c262973e
commit 9259a3731a
4 changed files with 283 additions and 6 deletions
+26 -5
View File
@@ -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: 获取元数据
+2 -1
View File
@@ -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"}
+58
View File
@@ -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 语音识别
- 视频下载需要能访问知乎(可能需要代理)
+197
View File
@@ -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)