feat: kb-zhihu-metadata 支持 ZHIHU_COOKIE

- 安装 markdownify 依赖
- PlaywrightBrowser 支持传入 Cookie
- ZhihuPlaywrightExtractor 支持传入 Cookie
- zhihu_metadata.py 读取 ZHIHU_COOKIE 环境变量
- 更新 SKILL.md 添加 Cookie 配置说明
This commit is contained in:
2026-02-27 15:39:19 +08:00
parent 202960eb64
commit 85797d0a89
3 changed files with 76 additions and 22 deletions
+18
View File
@@ -9,6 +9,24 @@
- User information extraction
- JSON output format
- Markdown conversion support
- **Cookie 支持**:可配置 ZHIHU_COOKIE 访问需要登录的内容
## 环境变量
| 变量名 | 用途 | 必需 |
|--------|------|------|
| `ZHIHU_COOKIE` | 知乎登录 Cookie | 推荐(访问专栏等需要) |
**获取 ZHIHU_COOKIE**:
1. 登录知乎网页版
2. 打开开发者工具 (F12) → Application → Cookies → zhihu.com
3. 复制 Cookie 字符串
```bash
# 添加到 ~/.kb_env
echo 'export ZHIHU_COOKIE="你的知乎Cookie"' >> ~/.kb_env
source ~/.kb_env
```
## Commands
+35 -7
View File
@@ -30,16 +30,42 @@ class PlaywrightBrowser:
self.context = None
self.page = None
def start(self):
def start(self, cookie: str = None):
"""启动浏览器"""
from playwright.sync_api import sync_playwright
self.playwright = sync_playwright().start()
self.browser = self.playwright.chromium.launch(headless=self.headless)
self.context = self.browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
)
# 创建浏览器上下文
context_options = {
'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
}
# 如果提供了 Cookie,设置 Cookie
if cookie:
context_options['cookies'] = self._parse_cookie(cookie)
self.context = self.browser.new_context(**context_options)
self.page = self.context.new_page()
self.page.set_default_timeout(self.timeout)
def _parse_cookie(self, cookie_str: str) -> list:
"""解析 Cookie 字符串为 Playwright 格式"""
cookies = []
for item in cookie_str.split(';'):
item = item.strip()
if '=' in item:
name, value = item.split('=', 1)
cookies.append({
'name': name.strip(),
'value': value.strip(),
'domain': '.zhihu.com',
'path': '/',
'secure': True,
'httpOnly': False,
'sameSite': 'Lax'
})
return cookies
def close(self):
"""关闭浏览器"""
@@ -84,14 +110,16 @@ class ZhihuPlaywrightExtractor:
'question': r'zhihu\.com/question/(\d+)',
}
def __init__(self, headless: bool = True, timeout: int = 30):
def __init__(self, headless: bool = True, timeout: int = 30, cookie: str = None):
"""
初始化提取器
Args:
headless: 是否使用无头浏览器
timeout: 超时时间(秒)
cookie: 知乎 Cookie 字符串(可选)
"""
self.cookie = cookie
self.browser = PlaywrightBrowser(headless=headless, timeout=timeout)
self.markdown_converter = MarkdownConverter()
@@ -383,8 +411,8 @@ class ZhihuPlaywrightExtractor:
print(f"URL 类型: {url_type}")
print(f"启动浏览器...")
# 启动浏览器
self.browser.start()
# 启动浏览器(传入 Cookie)
self.browser.start(cookie=self.cookie)
print(f"访问页面: {url}")
# 访问页面
+23 -15
View File
@@ -23,43 +23,46 @@ if current_dir not in sys.path:
from zhihu_extractor import ZhihuPlaywrightExtractor
def extract_zvideo(video_id: str, headless: bool = True) -> Dict[str, Any]:
def extract_zvideo(video_id: str, headless: bool = True, cookie: str = None) -> Dict[str, Any]:
"""
获取知乎视频元数据
Args:
video_id: 视频 ID (数字字符串)
headless: 是否使用无头浏览器
cookie: 知乎 Cookie(可选)
Returns:
视频元数据字典
"""
url = f"https://www.zhihu.com/zvideo/{video_id}"
return _extract_metadata(url, "zvideo", headless)
return _extract_metadata(url, "zvideo", headless, cookie)
def extract_zhuanlan(article_id: str, headless: bool = True) -> Dict[str, Any]:
def extract_zhuanlan(article_id: str, headless: bool = True, cookie: str = None) -> Dict[str, Any]:
"""
获取知乎专栏文章元数据
Args:
article_id: 文章 ID (数字字符串)
headless: 是否使用无头浏览器
cookie: 知乎 Cookie(可选)
Returns:
文章元数据字典
"""
url = f"https://zhuanlan.zhihu.com/p/{article_id}"
return _extract_metadata(url, "zhuanlan", headless)
return _extract_metadata(url, "zhuanlan", headless, cookie)
def extract_answer(answer_id: str, headless: bool = True) -> Dict[str, Any]:
def extract_answer(answer_id: str, headless: bool = True, cookie: str = None) -> Dict[str, Any]:
"""
获取知乎回答元数据
Args:
answer_id: 回答 ID (数字字符串)
headless: 是否使用无头浏览器
cookie: 知乎 Cookie(可选)
Returns:
回答元数据字典
@@ -67,25 +70,26 @@ def extract_answer(answer_id: str, headless: bool = True) -> Dict[str, Any]:
# 回答需要构建包含问题ID的URL,但answer_id本身足以识别
# 使用一个占位符问题ID,因为提取器会从URL中提取answer_id
url = f"https://www.zhihu.com/question/0/answer/{answer_id}"
return _extract_metadata(url, "answer", headless)
return _extract_metadata(url, "answer", headless, cookie)
def extract_question(question_id: str, headless: bool = True) -> Dict[str, Any]:
def extract_question(question_id: str, headless: bool = True, cookie: str = None) -> Dict[str, Any]:
"""
获取知乎问题元数据
Args:
question_id: 问题 ID (数字字符串)
headless: 是否使用无头浏览器
cookie: 知乎 Cookie(可选)
Returns:
问题元数据字典
"""
url = f"https://www.zhihu.com/question/{question_id}"
return _extract_metadata(url, "question", headless)
return _extract_metadata(url, "question", headless, cookie)
def _extract_metadata(url: str, content_type: str, headless: bool) -> Dict[str, Any]:
def _extract_metadata(url: str, content_type: str, headless: bool, cookie: str = None) -> Dict[str, Any]:
"""
内部函数:提取元数据
@@ -93,11 +97,12 @@ def _extract_metadata(url: str, content_type: str, headless: bool) -> Dict[str,
url: 知乎 URL
content_type: 内容类型 (zvideo/zhuanlan)
headless: 是否使用无头浏览器
cookie: 知乎 Cookie(可选)
Returns:
元数据字典
"""
extractor = ZhihuPlaywrightExtractor(headless=headless)
extractor = ZhihuPlaywrightExtractor(headless=headless, cookie=cookie)
result = extractor.extract(url)
if result:
@@ -170,15 +175,18 @@ def main():
args = parser.parse_args()
# 提取元数据
# 获取知乎 Cookie(优先使用环境变量,其次使用参数)
cookie = os.environ.get('ZHIHU_COOKIE')
# 提取元数据(传入 Cookie)
if args.type == "zvideo":
result = extract_zvideo(args.id, headless=args.headless)
result = extract_zvideo(args.id, headless=args.headless, cookie=cookie)
elif args.type == "zhuanlan":
result = extract_zhuanlan(args.id, headless=args.headless)
result = extract_zhuanlan(args.id, headless=args.headless, cookie=cookie)
elif args.type == "answer":
result = extract_answer(args.id, headless=args.headless)
result = extract_answer(args.id, headless=args.headless, cookie=cookie)
elif args.type == "question":
result = extract_question(args.id, headless=args.headless)
result = extract_question(args.id, headless=args.headless, cookie=cookie)
else:
print(f"不支持的类型: {args.type}")
sys.exit(1)