#!/usr/bin/env python3 """ 知乎回答全文抓取脚本 (bypass zse-ck) 用法: python3 zhihu_fetch.py [output.txt] 示例: python3 zhihu_fetch.py https://www.zhihu.com/question/24326030/answer/2001766056285467052 python3 zhihu_fetch.py 2001766056285467052 /tmp/out.txt 原理: 用 playwright 真实浏览器渲染, 先访问首页种 cookie(d_c0), 再访问回答页, 等待 zse-ck JS 验证完成, 点击"阅读全文"展开, 提取 .RichText 正文。 需要: pip3 install playwright --break-system-packages 浏览器: /root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome (已存在) """ import sys, re, json, argparse from playwright.sync_api import sync_playwright CHROME = "/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome" UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36") def extract_answer_id(url_or_id: str) -> str: m = re.search(r"/answer/(\d+)", url_or_id) if m: return m.group(1) if url_or_id.isdigit(): return url_or_id raise ValueError("无法解析 answer id: " + url_or_id) def fetch(url_or_id: str, timeout_ms: int = 45000) -> dict: aid = extract_answer_id(url_or_id) # 从链接中提取 question id(如有),否则用占位符(知乎按 answer_id 定位,question id 不影响) mq = re.search(r"/question/(\d+)", url_or_id) qid = mq.group(1) if mq else "0" url = f"https://www.zhihu.com/question/{qid}/answer/{aid}" # 尝试通过 API 拿元数据(快), 正文用浏览器 result = {"answer_id": aid, "url": url, "text": "", "html_len": 0, "error": None} with sync_playwright() as p: browser = p.chromium.launch( executable_path=CHROME, headless=True, args=["--no-sandbox", "--disable-dev-shm-usage", "--disable-blink-features=AutomationControlled"], ) ctx = browser.new_context(user_agent=UA, viewport={"width": 1280, "height": 900}, locale="zh-CN") page = ctx.new_page() try: # 第一步: 访问首页种 cookie (d_c0 匿名token) page.goto("https://www.zhihu.com/", timeout=30000, wait_until="domcontentloaded") page.wait_for_timeout(5000) except Exception as e: result["error"] = f"home: {e}" try: # 第二步: 访问回答页 page.goto(url, timeout=timeout_ms, wait_until="domcontentloaded") page.wait_for_timeout(12000) # 第三步: 点"阅读全文"展开 for sel in ["button:has-text('阅读全文')", "button:has-text('展开')", ".RichContent-collapsedText"]: try: btn = page.query_selector(sel) if btn: btn.click(timeout=3000) page.wait_for_timeout(3000) except Exception: pass # 第四步: 提取正文 rich = page.query_selector(".RichText, .RichContent-inner, .Post-RichTextContainer") if rich: result["text"] = rich.inner_text() html = page.content() result["html_len"] = len(html) except Exception as e: result["error"] = f"page: {e}" browser.close() return result if __name__ == "__main__": ap = argparse.ArgumentParser() ap.add_argument("url_or_id", help="知乎回答链接或 answer_id") ap.add_argument("output", nargs="?", default=None, help="输出文件路径(默认打印到stdout)") args = ap.parse_args() r = fetch(args.url_or_id) if r["error"]: print("WARN:", r["error"], file=sys.stderr) if r["text"]: if args.output: with open(args.output, "w", encoding="utf-8") as f: f.write(r["text"]) print(f"已保存 {len(r['text'])} 字符 -> {args.output}", file=sys.stderr) else: print(r["text"]) else: print("FAILED: 未提取到正文, html_len=", r["html_len"], file=sys.stderr) sys.exit(1)