feat: kb-zhihu-metadata 支持 ZHIHU_COOKIE
- 安装 markdownify 依赖 - PlaywrightBrowser 支持传入 Cookie - ZhihuPlaywrightExtractor 支持传入 Cookie - zhihu_metadata.py 读取 ZHIHU_COOKIE 环境变量 - 更新 SKILL.md 添加 Cookie 配置说明
This commit is contained in:
@@ -9,6 +9,24 @@
|
|||||||
- User information extraction
|
- User information extraction
|
||||||
- JSON output format
|
- JSON output format
|
||||||
- Markdown conversion support
|
- 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
|
## Commands
|
||||||
|
|
||||||
|
|||||||
@@ -30,16 +30,42 @@ class PlaywrightBrowser:
|
|||||||
self.context = None
|
self.context = None
|
||||||
self.page = None
|
self.page = None
|
||||||
|
|
||||||
def start(self):
|
def start(self, cookie: str = None):
|
||||||
"""启动浏览器"""
|
"""启动浏览器"""
|
||||||
from playwright.sync_api import sync_playwright
|
from playwright.sync_api import sync_playwright
|
||||||
self.playwright = sync_playwright().start()
|
self.playwright = sync_playwright().start()
|
||||||
self.browser = self.playwright.chromium.launch(headless=self.headless)
|
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 = self.context.new_page()
|
||||||
self.page.set_default_timeout(self.timeout)
|
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):
|
def close(self):
|
||||||
"""关闭浏览器"""
|
"""关闭浏览器"""
|
||||||
@@ -84,14 +110,16 @@ class ZhihuPlaywrightExtractor:
|
|||||||
'question': r'zhihu\.com/question/(\d+)',
|
'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:
|
Args:
|
||||||
headless: 是否使用无头浏览器
|
headless: 是否使用无头浏览器
|
||||||
timeout: 超时时间(秒)
|
timeout: 超时时间(秒)
|
||||||
|
cookie: 知乎 Cookie 字符串(可选)
|
||||||
"""
|
"""
|
||||||
|
self.cookie = cookie
|
||||||
self.browser = PlaywrightBrowser(headless=headless, timeout=timeout)
|
self.browser = PlaywrightBrowser(headless=headless, timeout=timeout)
|
||||||
self.markdown_converter = MarkdownConverter()
|
self.markdown_converter = MarkdownConverter()
|
||||||
|
|
||||||
@@ -383,8 +411,8 @@ class ZhihuPlaywrightExtractor:
|
|||||||
print(f"URL 类型: {url_type}")
|
print(f"URL 类型: {url_type}")
|
||||||
print(f"启动浏览器...")
|
print(f"启动浏览器...")
|
||||||
|
|
||||||
# 启动浏览器
|
# 启动浏览器(传入 Cookie)
|
||||||
self.browser.start()
|
self.browser.start(cookie=self.cookie)
|
||||||
|
|
||||||
print(f"访问页面: {url}")
|
print(f"访问页面: {url}")
|
||||||
# 访问页面
|
# 访问页面
|
||||||
|
|||||||
@@ -23,43 +23,46 @@ if current_dir not in sys.path:
|
|||||||
from zhihu_extractor import ZhihuPlaywrightExtractor
|
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:
|
Args:
|
||||||
video_id: 视频 ID (数字字符串)
|
video_id: 视频 ID (数字字符串)
|
||||||
headless: 是否使用无头浏览器
|
headless: 是否使用无头浏览器
|
||||||
|
cookie: 知乎 Cookie(可选)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
视频元数据字典
|
视频元数据字典
|
||||||
"""
|
"""
|
||||||
url = f"https://www.zhihu.com/zvideo/{video_id}"
|
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:
|
Args:
|
||||||
article_id: 文章 ID (数字字符串)
|
article_id: 文章 ID (数字字符串)
|
||||||
headless: 是否使用无头浏览器
|
headless: 是否使用无头浏览器
|
||||||
|
cookie: 知乎 Cookie(可选)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
文章元数据字典
|
文章元数据字典
|
||||||
"""
|
"""
|
||||||
url = f"https://zhuanlan.zhihu.com/p/{article_id}"
|
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:
|
Args:
|
||||||
answer_id: 回答 ID (数字字符串)
|
answer_id: 回答 ID (数字字符串)
|
||||||
headless: 是否使用无头浏览器
|
headless: 是否使用无头浏览器
|
||||||
|
cookie: 知乎 Cookie(可选)
|
||||||
|
|
||||||
Returns:
|
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本身足以识别
|
||||||
# 使用一个占位符问题ID,因为提取器会从URL中提取answer_id
|
# 使用一个占位符问题ID,因为提取器会从URL中提取answer_id
|
||||||
url = f"https://www.zhihu.com/question/0/answer/{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:
|
Args:
|
||||||
question_id: 问题 ID (数字字符串)
|
question_id: 问题 ID (数字字符串)
|
||||||
headless: 是否使用无头浏览器
|
headless: 是否使用无头浏览器
|
||||||
|
cookie: 知乎 Cookie(可选)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
问题元数据字典
|
问题元数据字典
|
||||||
"""
|
"""
|
||||||
url = f"https://www.zhihu.com/question/{question_id}"
|
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
|
url: 知乎 URL
|
||||||
content_type: 内容类型 (zvideo/zhuanlan)
|
content_type: 内容类型 (zvideo/zhuanlan)
|
||||||
headless: 是否使用无头浏览器
|
headless: 是否使用无头浏览器
|
||||||
|
cookie: 知乎 Cookie(可选)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
元数据字典
|
元数据字典
|
||||||
"""
|
"""
|
||||||
extractor = ZhihuPlaywrightExtractor(headless=headless)
|
extractor = ZhihuPlaywrightExtractor(headless=headless, cookie=cookie)
|
||||||
result = extractor.extract(url)
|
result = extractor.extract(url)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
@@ -170,15 +175,18 @@ def main():
|
|||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# 提取元数据
|
# 获取知乎 Cookie(优先使用环境变量,其次使用参数)
|
||||||
|
cookie = os.environ.get('ZHIHU_COOKIE')
|
||||||
|
|
||||||
|
# 提取元数据(传入 Cookie)
|
||||||
if args.type == "zvideo":
|
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":
|
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":
|
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":
|
elif args.type == "question":
|
||||||
result = extract_question(args.id, headless=args.headless)
|
result = extract_question(args.id, headless=args.headless, cookie=cookie)
|
||||||
else:
|
else:
|
||||||
print(f"不支持的类型: {args.type}")
|
print(f"不支持的类型: {args.type}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user