知乎高赞回答爬虫:从零开始,建立你的专属知识库

知乎十年,沉淀了全网最优质的中文问答。与其在信息流中迷失,不如用代码把这些精华装进自己的数据库。

一、为什么要爬取知乎高赞回答?

知乎上有一个经典问题:"你见过最不求上进的人是什么样子?"下面有个高赞回答:"他们为现状焦虑,又没有毅力践行决心去改变自己,以最普通的身份埋没在人群中,却过着最煎熬的日子。"

这条回答获得了10万+点赞,评论区无数人表示"是我本人没错了"。

这就是知乎高赞回答的魅力------它们往往击中人心,蕴含着深厚的专业知识、独特的经验见解或深刻的人生感悟。把这些高赞回答系统化地收集起来,就是一座取之不尽的知识宝库。

1.1 高赞回答的价值

应用场景 价值体现 实例
个人学习 获取高质量知识,避免信息噪音 编程、职场、心理学等领域精华
内容创作 素材库、选题参考 写文章、做视频的灵感来源
行业研究 洞察用户痛点和需求 产品经理分析用户评论
数据分析 情感分析、话题演化 舆情监控、社会研究

1.2 知乎爬虫的难点

知乎的反爬虫机制,在国内知识平台中属于第一梯队

难点 表现 应对策略
强制登录 未登录状态只能看到截断的回答 使用Cookie维持登录态
动态加载 回答通过AJAX异步加载 分析XHR请求或使用Selenium
浏览器指纹 检测Canvas/WebGL等特征 深度伪装+行为模拟
频率限制 高频请求直接封IP 随机延迟+代理池

本文将带你绕过层层反爬,稳定抓取知乎高赞回答,并构建结构化的知识库。

二、技术选型与准备工作

2.1 核心依赖库

bash 复制代码
# 基础爬虫
pip install requests beautifulsoup4 lxml

# 动态渲染(备选)
pip install selenium webdriver-manager

# 数据处理
pip install pandas numpy

# 数据库
pip install sqlite3  # Python内置

2.2 获取Cookie(最关键的一步)

知乎大部分高质量回答(包括完整内容和真实点赞数)都需要登录状态才能获取。未登录状态下,长回答会被截断,点赞数可能显示为"0"或"100+"(实际可能是5000+)。

获取步骤

  1. 用Chrome浏览器登录知乎账号
  2. 按F12打开开发者工具 → 切换到 Application(应用)标签
  3. 左侧找到 Cookies → https://www.zhihu.com
  4. 找到名为 z_c0 的Cookie,复制其值
python 复制代码
# Cookie格式示例
COOKIE = 'z_c0="2|1:0|10:1648123456|4:z_c0|92:xxxxxxxxxxxxx"; '

** 注意**:Cookie有效期通常几小时到几天,过期需要重新获取。

三、核心爬虫实现(Requests + Session)

3.1 分析页面结构

以知乎问题页面为例(https://www.zhihu.com/question/123456789),回答内容通过异步加载:

  • 初始HTML只包含问题标题和部分元数据
  • 回答数据通过XHR请求获取:https://www.zhihu.com/api/v4/questions/{question_id}/answers
  • 返回JSON格式,包含回答列表、作者信息、点赞数、评论数等

3.2 完整爬虫代码

python 复制代码
# zhihu_spider.py
import requests
import json
import time
import random
import sqlite3
from datetime import datetime
import pandas as pd

class ZhihuAnswerSpider:
    """知乎高赞回答爬虫"""
    
    def __init__(self, cookie):
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
            'Accept': 'application/json, text/plain, */*',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Referer': 'https://www.zhihu.com/',
            'Cookie': cookie,
            'Authorization': 'oauth 2.0',  # 知乎API需要的认证头
        })
        
        # 回答数据缓存
        self.answers = []
    
    def get_question_answers(self, question_id, limit=20, offset=0):
        """
        获取问题下的回答列表(API方式)
        :param question_id: 问题ID
        :param limit: 每页数量
        :param offset: 偏移量
        """
        url = f"https://www.zhihu.com/api/v4/questions/{question_id}/answers"
        params = {
            'include': 'data[*].content,data[*].voteup_count,data[*].comment_count,data[*].author.name,data[*].author.headline',
            'limit': limit,
            'offset': offset,
            'sort_by': 'default'
        }
        
        try:
            # 随机延迟
            time.sleep(random.uniform(2, 4))
            
            response = self.session.get(url, params=params, timeout=10)
            response.raise_for_status()
            
            data = response.json()
            return data
            
        except requests.exceptions.RequestException as e:
            print(f"API请求失败: {e}")
            return None
    
    def parse_answers(self, api_response):
        """
        解析API返回的回答数据
        """
        if not api_response or 'data' not in api_response:
            return []
        
        answers = []
        for item in api_response['data']:
            try:
                answer = {
                    'answer_id': item.get('id'),
                    'question_id': item.get('question', {}).get('id'),
                    'question_title': item.get('question', {}).get('title'),
                    'author_name': item.get('author', {}).get('name'),
                    'author_headline': item.get('author', {}).get('headline', ''),
                    'content': item.get('content', ''),  # HTML格式的富文本
                    'voteup_count': item.get('voteup_count', 0),
                    'comment_count': item.get('comment_count', 0),
                    'created_time': datetime.fromtimestamp(item.get('created_time', 0)).strftime('%Y-%m-%d %H:%M:%S'),
                    'updated_time': datetime.fromtimestamp(item.get('updated_time', 0)).strftime('%Y-%m-%d %H:%M:%S'),
                    'crawl_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                }
                answers.append(answer)
            except Exception as e:
                print(f"解析单条回答出错: {e}")
                continue
        
        return answers
    
    def fetch_all_answers(self, question_id, max_answers=100):
        """
        获取某个问题的所有回答(支持翻页)
        """
        print(f"开始爬取问题 {question_id} 的回答...")
        
        all_answers = []
        offset = 0
        limit = 20
        
        while offset < max_answers:
            print(f"  正在抓取 offset={offset}...")
            
            data = self.get_question_answers(question_id, limit=limit, offset=offset)
            if not data:
                break
            
            answers = self.parse_answers(data)
            all_answers.extend(answers)
            
            # 检查是否还有更多数据
            paging = data.get('paging', {})
            is_end = paging.get('is_end', True)
            
            if is_end:
                print("  已到达最后一页")
                break
            
            offset += limit
            time.sleep(random.uniform(3, 6))  # 翻页间隔
        
        print(f"完成!共获取 {len(all_answers)} 条回答")
        self.answers = all_answers
        return all_answers
    
    def filter_by_vote(self, min_votes=1000):
        """
        筛选高赞回答
        """
        high_vote_answers = [a for a in self.answers if a['voteup_count'] >= min_votes]
        print(f"筛选出 {len(high_vote_answers)} 条点赞数 ≥ {min_votes} 的回答")
        return high_vote_answers
    
    def save_to_sqlite(self, db_path='zhihu.db'):
        """
        保存到SQLite数据库
        """
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        
        # 创建表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS answers (
                answer_id INTEGER PRIMARY KEY,
                question_id INTEGER,
                question_title TEXT,
                author_name TEXT,
                author_headline TEXT,
                content TEXT,
                voteup_count INTEGER,
                comment_count INTEGER,
                created_time TEXT,
                updated_time TEXT,
                crawl_time TEXT
            )
        ''')
        
        # 插入数据
        for answer in self.answers:
            cursor.execute('''
                INSERT OR REPLACE INTO answers 
                (answer_id, question_id, question_title, author_name, author_headline, 
                 content, voteup_count, comment_count, created_time, updated_time, crawl_time)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ''', (
                answer['answer_id'], answer['question_id'], answer['question_title'],
                answer['author_name'], answer['author_headline'], answer['content'],
                answer['voteup_count'], answer['comment_count'], answer['created_time'],
                answer['updated_time'], answer['crawl_time']
            ))
        
        conn.commit()
        conn.close()
        print(f"数据已保存到 {db_path}")
    
    def save_to_csv(self, filename=None):
        """
        保存到CSV文件
        """
        if not filename:
            filename = f"zhihu_answers_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
        
        df = pd.DataFrame(self.answers)
        df.to_csv(filename, index=False, encoding='utf-8-sig')
        print(f"数据已保存到 {filename}")
        return df


# 使用示例
if __name__ == '__main__':
    # 替换成你自己的Cookie
    MY_COOKIE = 'z_c0="2|1:0|10:1648123456|4:z_c0|92:xxxxxxxxxxxxx";'
    
    spider = ZhihuAnswerSpider(cookie=MY_COOKIE)
    
    # 爬取某个问题的回答(例如:Python 有哪些好的学习资料?)
    question_id = "29116517"  # 问题ID可从URL获取
    spider.fetch_all_answers(question_id, max_answers=100)
    
    # 筛选高赞回答
    high_vote = spider.filter_by_vote(min_votes=1000)
    
    # 保存数据
    spider.save_to_sqlite()
    spider.save_to_csv()

四、进阶:使用Selenium应对复杂反爬

如果API方式频繁失效,可以采用Selenium模拟真实浏览器。

4.1 Selenium配置

python 复制代码
# selenium_spider.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import random

def create_stealth_driver():
    """创建深度伪装的Chrome浏览器"""
    chrome_options = Options()
    
    # 1. 禁用自动化特征
    chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
    chrome_options.add_experimental_option('useAutomationExtension', False)
    chrome_options.add_argument('--disable-blink-features=AutomationControlled')
    
    # 2. 随机User-Agent
    user_agents = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.36'
    ]
    chrome_options.add_argument(f'user-agent={random.choice(user_agents)}')
    
    # 3. 窗口大小
    chrome_options.add_argument('--window-size=1920,1080')
    
    # 4. 禁用图片加载(提升速度)
    prefs = {"profile.managed_default_content_settings.images": 2}
    chrome_options.add_experimental_option("prefs", prefs)
    
    driver = webdriver.Chrome(options=chrome_options)
    
    # 5. 注入JS修改webdriver属性
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": """
            Object.defineProperty(navigator, 'webdriver', {
                get: () => undefined
            });
            Object.defineProperty(navigator, 'plugins', {
                get: () => [1, 2, 3, 4, 5]
            });
            Object.defineProperty(navigator, 'languages', {
                get: () => ['zh-CN', 'zh', 'en']
            });
        """
    })
    
    return driver

def fetch_with_selenium(question_url):
    """使用Selenium获取回答内容"""
    driver = create_stealth_driver()
    
    try:
        # 访问页面
        driver.get(question_url)
        
        # 等待回答加载
        wait = WebDriverWait(driver, 10)
        wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".AnswerItem")))
        
        # 模拟人类滚动,触发懒加载
        for _ in range(5):
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight * 0.8);")
            time.sleep(random.uniform(1, 3))
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(random.uniform(2, 4))
        
        # 获取回答元素
        answer_items = driver.find_elements(By.CSS_SELECTOR, ".AnswerItem")
        
        results = []
        for item in answer_items:
            try:
                # 提取点赞数
                vote = item.find_element(By.CSS_SELECTOR, ".VoteButton").text
                
                # 提取作者
                author = item.find_element(By.CSS_SELECTOR, ".AuthorInfo-name").text
                
                # 提取内容
                content = item.find_element(By.CSS_SELECTOR, ".RichText").text
                
                results.append({
                    'author': author,
                    'vote': vote,
                    'content': content[:200] + "..."
                })
            except:
                continue
        
        return results
        
    finally:
        driver.quit()

五、构建知识库系统

爬下来的数据只有经过结构化存储和索引,才能真正发挥价值。

5.1 数据库设计

sql 复制代码
-- 问题表
CREATE TABLE questions (
    question_id INTEGER PRIMARY KEY,
    title TEXT,
    description TEXT,
    follower_count INTEGER,
    view_count INTEGER,
    answer_count INTEGER,
    crawl_time TEXT
);

-- 回答表(已在前面的代码中创建)
CREATE TABLE answers (
    answer_id INTEGER PRIMARY KEY,
    question_id INTEGER,
    question_title TEXT,
    author_name TEXT,
    author_headline TEXT,
    content TEXT,
    voteup_count INTEGER,
    comment_count INTEGER,
    created_time TEXT,
    updated_time TEXT,
    crawl_time TEXT,
    FOREIGN KEY (question_id) REFERENCES questions(question_id)
);

-- 用户表(可选)
CREATE TABLE authors (
    author_name TEXT PRIMARY KEY,
    headline TEXT,
    follower_count INTEGER,
    answer_count INTEGER,
    crawl_time TEXT
);

5.2 知识库查询工具

python 复制代码
# knowledge_base.py
import sqlite3
import pandas as pd

class KnowledgeBase:
    """知识库查询工具"""
    
    def __init__(self, db_path='zhihu.db'):
        self.conn = sqlite3.connect(db_path)
    
    def search_by_keyword(self, keyword):
        """关键词搜索回答内容"""
        query = """
            SELECT question_title, author_name, content, voteup_count
            FROM answers
            WHERE content LIKE ?
            ORDER BY voteup_count DESC
            LIMIT 20
        """
        df = pd.read_sql_query(query, self.conn, params=[f'%{keyword}%'])
        return df
    
    def get_top_answers_by_question(self, question_id, limit=10):
        """获取某个问题下的高赞回答"""
        query = """
            SELECT author_name, content, voteup_count, comment_count
            FROM answers
            WHERE question_id = ?
            ORDER BY voteup_count DESC
            LIMIT ?
        """
        df = pd.read_sql_query(query, self.conn, params=[question_id, limit])
        return df
    
    def get_author_stats(self, author_name):
        """获取作者的统计信息"""
        query = """
            SELECT 
                COUNT(*) as answer_count,
                AVG(voteup_count) as avg_votes,
                MAX(voteup_count) as max_votes,
                SUM(voteup_count) as total_votes
            FROM answers
            WHERE author_name = ?
        """
        df = pd.read_sql_query(query, self.conn, params=[author_name])
        return df
    
    def export_to_markdown(self, answer_id, output_file=None):
        """将回答导出为Markdown格式"""
        query = "SELECT * FROM answers WHERE answer_id = ?"
        df = pd.read_sql_query(query, self.conn, params=[answer_id])
        
        if df.empty:
            print("未找到该回答")
            return
        
        row = df.iloc[0]
        
        md = f"""# {row['question_title']}

> 作者:{row['author_name']}  
> 点赞:{row['voteup_count']}  |  评论:{row['comment_count']}  
> 创建时间:{row['created_time']}

---

{row['content']}

---

*本文档由知乎知识库自动生成*
"""
        
        if output_file:
            with open(output_file, 'w', encoding='utf-8') as f:
                f.write(md)
            print(f"已导出到 {output_file}")
        else:
            print(md)
    
    def close(self):
        self.conn.close()

六、数据分析与洞察

有了数据,我们可以做一些有趣的分析。

6.1 高赞回答的特征分析

python 复制代码
# analysis.py
import pandas as pd
import matplotlib.pyplot as plt
import jieba
from wordcloud import WordCloud

plt.rcParams['font.sans-serif'] = ['SimHei']

def analyze_answers(csv_file):
    """分析高赞回答的特征"""
    df = pd.read_csv(csv_file)
    
    print("=== 基础统计 ===")
    print(f"总回答数: {len(df)}")
    print(f"平均点赞: {df['voteup_count'].mean():.0f}")
    print(f"最高点赞: {df['voteup_count'].max()}")
    print(f"平均评论: {df['comment_count'].mean():.0f}")
    
    # 作者活跃度分布
    plt.figure(figsize=(10, 6))
    author_counts = df['author_name'].value_counts().head(20)
    author_counts.plot(kind='bar')
    plt.title('高产作者TOP20')
    plt.xlabel('作者')
    plt.ylabel('回答数量')
    plt.xticks(rotation=45)
    plt.tight_layout()
    plt.savefig('author_distribution.png')
    plt.show()
    
    # 点赞数分布
    plt.figure(figsize=(10, 6))
    plt.hist(df['voteup_count'], bins=50, edgecolor='black')
    plt.title('点赞数分布')
    plt.xlabel('点赞数')
    plt.ylabel('回答数量')
    plt.tight_layout()
    plt.savefig('vote_distribution.png')
    plt.show()
    
    # 生成词云
    all_text = ' '.join(df['content'].astype(str).tolist())
    words = jieba.lcut(all_text)
    
    # 停用词
    stop_words = {'的', '了', '是', '在', '和', '与', '这', '那', '也', '都'}
    filtered = [w for w in words if len(w) > 1 and w not in stop_words]
    
    word_freq = {}
    for w in filtered:
        word_freq[w] = word_freq.get(w, 0) + 1
    
    wc = WordCloud(
        font_path='simhei.ttf',
        width=1000,
        height=600,
        background_color='white',
        max_words=100
    ).generate_from_frequencies(word_freq)
    
    plt.figure(figsize=(12, 8))
    plt.imshow(wc, interpolation='bilinear')
    plt.axis('off')
    plt.title('高赞回答词云图')
    plt.savefig('wordcloud.png', dpi=300)
    plt.show()

七、反爬避坑指南

7.1 常见问题及解决方案

问题 现象 解决方案
Cookie过期 API返回401或空数据 重新登录获取Cookie
请求频率过高 突然返回403 增加随机延迟(3-8秒),使用代理池
浏览器指纹检测 Selenium被识别 注入JS修改webdriver属性
验证码弹窗 出现滑块验证 降低频率,或使用打码平台

7.2 代理IP配置

python 复制代码
# 使用代理
proxies = {
    'http': 'http://username:password@proxy.example.com:8080',
    'https': 'http://username:password@proxy.example.com:8080'
}

response = requests.get(url, proxies=proxies, timeout=10)

7.3 深度伪装方案

如果常规反爬措施失效,可以参考最新的"指纹+行为"双检测伪装方案:

  • 修改Canvas/WebGL指纹
  • 模拟真实的鼠标轨迹
  • 随机化操作间隔
  • 匹配IP地域与时区

八、法律与道德规范

在享受技术便利的同时,请务必遵守以下准则:

  1. 遵守Robots协议:知乎的robots.txt对爬虫有限制
  2. 控制访问频率:设置合理延迟,不给服务器造成压力
  3. 仅限个人使用:爬取的数据不得用于商业用途
  4. 尊重用户隐私:不爬取用户个人信息、私信等
  5. 合法合规:遵守《个人信息保护法》《数据安全法》

九、总结:从爬虫到知识库的完整路径

通过本文,我们完成了:

阶段 成果 技术要点
数据获取 知乎高赞回答采集 Cookie登录、API调用、反爬应对
数据存储 SQLite知识库 表结构设计、批量插入
数据查询 关键词搜索、作者统计 SQL查询优化
数据分析 特征洞察、词云图 数据可视化、文本挖掘

扩展方向

  • 增量更新:定时爬取,监控新回答
  • 知识图谱:构建回答之间的引用关系
  • 推荐系统:基于用户兴趣推荐高赞回答
  • Web可视化:Flask+ECharts搭建知识库前端

最后的话:知乎是一座知识的富矿,但真正的价值不在于数据的多寡,而在于你如何组织和利用这些数据。当你亲手把散落在各个问题下的高赞回答汇聚成自己的知识库时,你收获的不仅是技术能力的提升,更是一份可以随时查阅的智慧结晶。

相关推荐
李昊哲小课2 小时前
Python json模块完整教程
开发语言·python·json
易醒是好梦2 小时前
Python flask demo
开发语言·python·flask
怪侠_岭南一只猿2 小时前
爬虫工程师入门阶段一:基础知识点完全学习文档
css·爬虫·python·学习·html
易龙祥2 小时前
批量下载IGS气象文件(利用python爬虫下载igs的气象数据)
python·igs·气象文件
阿_旭2 小时前
基于YOLO26深度学习的交警手势识别系统【python源码+Pyqt5界面+数据集+训练代码】
人工智能·python·深度学习·交警手势识别
6+h2 小时前
【Spring】AOP核心之原始对象与代理对象
java·python·spring
w_a_o3 小时前
传统配方+机器学习:福尔蒂新材料用15年经验构建梯度回归预测模型(Python开源预告)
python·机器学习·回归·kmeans·宽度优先
jiet_h3 小时前
Python tempfile 深入实战:安全、优雅地处理临时文件与临时目录
python
摩尔曼斯克的海3 小时前
力扣面试题--双指针类
python·算法·leetcode