某乎爬虫进阶:爬取问题+回答+用户信息,构建知识图谱数据源

发布时间:2026/7/31 13:53:09
某乎爬虫进阶:爬取问题+回答+用户信息,构建知识图谱数据源 引言在信息过载的时代如何从海量的UGC用户生成内容中高效提取结构化知识是每一位数据从业者都在探索的问题。某乎作为中文互联网最大的知识分享社区之一汇聚了数以亿计的问题、回答和用户互动数据是构建知识图谱的天然数据宝库。然而爬取某乎数据绝非易事。其前端大量采用React服务端渲染SSR加客户端水合的混合模式初始HTML仅含骨架结构真实数据依赖后续AJAX接口动态加载。与此同时某乎的反爬体系经过多年迭代已形成了一套覆盖请求频率检测、请求头验证、行为模式分析等多维度的智能防护系统。很多开发者可能有过这样的经历昨天还能正常运行的爬虫脚本今天一运行就弹出验证码、返回乱码甚至IP直接被封。本文将从零开始系统介绍如何爬取某乎的问题、回答和用户信息并以此为基础构建知识图谱数据源。文章将涵盖数据建模、接口分析、反爬突破、隧道代理集成以及知识图谱的初步构建思路。一、为什么要爬取某乎构建知识图谱1.1 某乎数据的知识价值某乎的数据具有典型的知识图谱特征实体丰富问题、回答、用户、话题标签构成了多类型的实体节点关系多样用户-提问、用户-回答、问题-话题、回答-评论等多种关系属性完整每个实体都带有丰富的属性信息——问题的关注数、浏览数、回答数用户的职业、教育背景、关注数回答的点赞数、评论数、创建时间这些数据的结构化程度高、语义密度大非常适合用于知识图谱的构建。1.2 知识图谱的数据需求构建一个完整的知识图谱至少需要三类核心数据数据类型某乎对应内容图谱中的角色实体数据问题、用户、话题图谱节点关系数据提问关系、回答关系、关注关系图谱边属性数据标题、内容、点赞数、创建时间节点/边的属性某乎恰好能够同时满足这三类数据的获取需求是知识图谱数据源的理想选择。二、数据建模爬什么、存什么在动手写代码之前先搞清楚要爬取哪些数据、如何存储。2.1 实体模型设计根据某乎的数据结构建议建立三张核心数据表问题表questions字段类型说明question_idVARCHAR(32)问题ID主键titleTEXT问题标题descriptionTEXT问题描述follower_countINT关注数view_countINT浏览数answer_countINT回答数topic_tagsJSON话题标签列表created_atDATETIME创建时间回答表answers字段类型说明answer_idVARCHAR(32)回答ID主键question_idVARCHAR(32)所属问题ID外键author_idVARCHAR(32)作者ID外键contentLONGTEXT回答内容voteup_countINT点赞数comment_countINT评论数created_atDATETIME创建时间用户表users字段类型说明user_idVARCHAR(32)用户ID主键nameVARCHAR(100)昵称headlineVARCHAR(500)一句话简介follower_countINT关注者数following_countINT关注数answer_countINT回答数这三张表通过外键关联构成了一个完整的关系网络——用户通过回答连接到问题问题通过话题标签相互关联。2.2 数据采集的优先级策略考虑到某乎的反爬限制建议采用分层采集策略第一层采集问题列表元信息第二层根据问题ID采集回答列表第三层根据回答中的作者ID采集用户信息这种“先宽后深”的策略可以在有限的请求配额内最大化数据覆盖面。三、技术选型与接口分析3.1 为什么不用页面解析直接使用requestsBeautifulSoup解析某乎的HTML页面会面临两个致命问题动态加载某乎页面采用SSR客户端水合模式初始HTML只有骨架真实数据通过AJAX异步加载结构多变某乎的DOM结构频繁变更类名和嵌套层次经常调整基于XPath/CSS的选择器极易失效因此直接调用API接口才是正确且高效的做法。3.2 核心API接口某乎的内部API主要遵循v4版本规范获取问题下的回答列表GET https://www.zhihu.com/api/v4/questions/{question_id}/answers 参数offset0limit20sort_bydefault支持按时间、热度双维度排序。获取用户信息GET https://www.zhihu.com/api/v4/members/{user_id}返回用户名、头像、简介、关注数等详细信息。获取话题下的问题列表GET https://www.zhihu.com/api/v4/topics/{topic_id}/feeds/timeline_questions特别提醒根据开源社区的反馈某乎在2025年3月前后加密了部分API接口原有的爬取方案可能已失效。这意味着在实际开发中需要持续关注接口变化并及时调整策略。3.3 Cookie认证某乎的API接口通常需要登录态才能访问。核心Cookie字段包括z_c0主要的授权令牌_xsrfCSRF防护参数获取方式在浏览器中登录某乎打开开发者工具 → Network → 找到任意请求 → 复制Cookie中的这两个值。强烈建议使用小号防止主号被封禁。四、代码实现从API到结构化数据4.1 基础爬虫框架import requests import json import time import random import pandas as pd from typing import List, Dict, Optional class ZhihuScraper: def __init__(self, cookie: str, use_proxy: bool False): 初始化爬虫 :param cookie: 登录后的Cookie字符串含z_c0和_xsrf :param use_proxy: 是否启用隧道代理 self.session requests.Session() self.session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Cookie: cookie, Referer: https://www.zhihu.com, X-Requested-With: XMLHttpRequest }) self.use_proxy use_proxy # 隧道代理配置以站大爷为例 if use_proxy: self.proxies { http: http://隧道代理地址:端口, https: https://隧道代理地址:端口 } else: self.proxies None def _request(self, url: str, params: Dict None, retry: int 3) - Optional[Dict]: 发送请求支持重试和隧道代理 for i in range(retry): try: response self.session.get( url, paramsparams, proxiesself.proxies, timeout15 ) if response.status_code 200: return response.json() elif response.status_code 403: print(f第{i1}次请求被拒绝(403)等待后重试...) time.sleep(5 * (i 1)) else: print(f请求失败状态码: {response.status_code}) time.sleep(2) except Exception as e: print(f第{i1}次请求异常: {e}) time.sleep(3 * (i 1)) return None def get_answers_by_question( self, question_id: str, limit: int 100, sort_by: str default ) - List[Dict]: 获取某个问题下的回答列表 :param question_id: 问题ID :param limit: 最大获取数量 :param sort_by: 排序方式 (default/created) url fhttps://www.zhihu.com/api/v4/questions/{question_id}/answers answers [] offset 0 page_size 20 while len(answers) limit: params { offset: offset, limit: page_size, sort_by: sort_by } data self._request(url, params) if not data or data not in data: break batch data.get(data, []) if not batch: break for item in batch: author item.get(author, {}) answers.append({ answer_id: item.get(id), question_id: question_id, author_id: author.get(id), author_name: author.get(name), content: item.get(content, ), voteup_count: item.get(voteup_count, 0), comment_count: item.get(comment_count, 0), created_at: item.get(created_at, 0) }) # 检查是否还有下一页 paging data.get(paging, {}) if not paging.get(is_end, True): offset page_size # 随机延迟避免触发反爬 time.sleep(random.uniform(1, 3)) else: break return answers[:limit] def get_user_info(self, user_id: str) - Optional[Dict]: 获取用户详细信息 url fhttps://www.zhihu.com/api/v4/members/{user_id} data self._request(url) if data: return { user_id: data.get(id), name: data.get(name), headline: data.get(headline, ), follower_count: data.get(follower_count, 0), following_count: data.get(following_count, 0), answer_count: data.get(answer_count, 0), voteup_count: data.get(voteup_count, 0) } return None4.2 完整采集流程# 使用示例 if __name__ __main__: # 1. 配置Cookie从浏览器复制 COOKIE z_c0你的令牌; _xsrf你的验证参数 # 2. 初始化爬虫启用隧道代理 scraper ZhihuScraper(cookieCOOKIE, use_proxyTrue) # 3. 采集某个问题下的回答 question_id 19550255 # 替换为目标问题ID answers scraper.get_answers_by_question(question_id, limit200) print(f共采集 {len(answers)} 条回答) # 4. 提取所有作者ID采集用户信息 author_ids set([a[author_id] for a in answers if a[author_id]]) users [] for uid in author_ids: user_info scraper.get_user_info(uid) if user_info: users.append(user_info) time.sleep(random.uniform(1, 2)) print(f共采集 {len(users)} 位用户信息) # 5. 保存数据 pd.DataFrame(answers).to_csv(answers.csv, indexFalse, encodingutf-8-sig) pd.DataFrame(users).to_csv(users.csv, indexFalse, encodingutf-8-sig)4.3 扩展爬取话题下的问题列表如果需要从某个话题出发采集数据可以使用话题接口def get_questions_by_topic(self, topic_id: str, limit: int 1000): 获取某个话题下的问题列表 注意某乎限制了每个话题板块最多展示1000条数据 url fhttps://www.zhihu.com/api/v4/topics/{topic_id}/feeds/timeline_questions # 实现逻辑与get_answers_by_question类似 # ...重要提示某乎从服务器端限制了每个话题板块最多展示1000条数据。对于时间跨度大、热度高的话题这一限制意味着无法获取全部历史数据。建议通过爬取多个相似话题来最大化数据覆盖度。五、反爬策略与隧道代理5.1 某乎的反爬体系某乎的反爬机制是多层次的反爬手段具体表现触发条件请求频率检测短时间内大量相同模式的请求被识别为爬虫行为请求间隔1秒请求头验证缺少必要请求头或使用非常规请求头会被拦截User-Agent异常行为模式分析固定时间间隔发送完全相同的请求会被识别请求模式过于规律验证码拦截弹出滑块验证码或人机校验频率过高或IP异常内容加密检测到爬虫时返回加密乱码服务器端动态检测某乎对高频请求会触发验证码或封IP建议每次请求间隔3-5秒。有开发者反馈即使用自己的账号Cookie自建RSSHub用不了几天也会失效。由此可见其反爬之严格。5.2 UA池与请求头伪装单一User-Agent特征会显著增加爬虫暴露风险。建议构建多样化的UA池USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0.0.0, Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0, # 覆盖Windows、macOS、iOS、Android等平台的主流浏览器版本 ]每次请求时随机选择一个UA模拟不同终端设备的访问特征。5.3 站大爷隧道代理突破IP限制的利器即使完美配置了请求头和请求频率当采集规模达到一定量级时IP封禁依然不可避免。某乎对单个IP的请求频率有着严格的限制——同一个IP在短时间内发出大量请求必然触发保护机制。传统的解决方案是自行维护代理IP池但这种方法存在两个核心痛点IP质量参差不齐免费代理资源已被大量滥用可用性低手动维护麻烦需要持续检测IP有效性被封后手动更换隧道代理正是为解决这些问题而生。它的工作原理是你不需要手动维护IP池只需配置好隧道代理的接入信息。每次发送请求时隧道代理会自动把流量引导至不同的出口IP。站大爷隧道代理在爬虫场景中有几个突出的优势自动切换无感知每次请求自动更换出口IP无需手动干预覆盖范围广覆盖全国99%地域高可用性在独立第三方评测中连续7天运行连接成功率达99.3%主备双隧道持续更新IP池稳定性有保障灵活配置支持精细化IP轮换周期自定义在代码中集成站大爷隧道代理import requests # 站大爷隧道代理配置 PROXY_CONFIG { http: http://隧道代理地址:端口, https: https://隧道代理地址:端口 } def fetch_with_tunnel(url, headers, cookies, retry3): 使用隧道代理发送请求自动处理IP切换 for i in range(retry): try: response requests.get( url, headersheaders, cookiescookies, proxiesPROXY_CONFIG, timeout15 ) # 如果遇到403隧道代理会自动切换出口IP if response.status_code 403: print(f第{i1}次请求被拒绝隧道代理自动切换IP重试...) time.sleep(2) continue return response except Exception as e: print(f第{i1}次请求异常: {e}) time.sleep(3) continue return None实际应用中隧道代理可以将IP封禁率从80%以上降至5%以下大幅提升采集的稳定性。六、从爬取数据到知识图谱6.1 数据清洗与预处理爬取到的原始数据需要进行清洗内容清洗去除HTML标签、特殊字符、表情符号时间归一化将时间戳统一转换为标准格式去重处理使用answer_id、question_id作为唯一键避免重复插入6.2 实体关系抽取从采集的数据中可以抽取以下关系关系类型起点终点说明提问用户问题用户提出了某个问题回答用户问题用户回答了某个问题关注用户话题用户关注了某个话题属于问题话题问题被打上了某个话题标签6.3 使用Neo4j存储知识图谱Neo4j是构建知识图谱的理想图数据库// 创建用户节点 CREATE (u:User {id: 123, name: 张三, follower_count: 1000}) // 创建问题节点 CREATE (q:Question {id: 456, title: 如何学习Python, view_count: 10000}) // 创建回答关系 MATCH (u:User {id: 123}), (q:Question {id: 456}) CREATE (u)-[:ANSWERED {content: ..., voteup_count: 100}]-(q)6.4 知识图谱的应用场景基于某乎数据构建的知识图谱可以应用于知识推荐根据用户关注的话题推荐相关问题专家发现通过回答质量和数量识别领域专家热点追踪分析话题下问题的增长趋势用户画像通过回答主题分布构建用户兴趣画像七、常见问题与避坑指南7.1 Cookie频繁过期怎么办某乎的Cookie有效期通常为7天左右。建议编写定时任务每周自动重新登录并更新Cookie使用多个账号的Cookie池轮换使用7.2 API接口失效怎么办某乎会不定期更新API接口。应对策略定期检查接口变化及时更新代码建立多级备选选择器或解析方案参考开源社区的最新适配方案7.3 遇到验证码怎么处理半自动方案检测到验证码时暂停脚本人工完成验证后继续降低频率出现验证码时自动降低采集速率切换账号使用备用账号继续采集7.4 数据采集不全怎么办某乎话题页最多展示1000条数据解决方案爬取多个相似话题合并数据按时间分段采集突破单次请求的限制7.5 乱码问题某乎在检测到爬虫时可能会返回加密乱码。解决方案使用隧道代理切换IP使用登录态Cookie访问模拟真实浏览器的完整请求头八、总结本文从某乎的数据价值出发系统介绍了爬取问题、回答、用户信息以构建知识图谱数据源的完整方案。核心要点可以概括为挑战解决方案动态加载内容直接调用API接口而非解析HTML登录态校验获取z_c0和_xsrf Cookie持久化使用DOM结构多变使用API接口避免依赖页面选择器请求频率限制随机延迟、UA池轮换IP封禁站大爷隧道代理自动切换IP数据建模问题、回答、用户三表设计外键关联知识图谱存储Neo4j图数据库技术选型速览推荐“API接口调用 Cookie认证 UA池轮换 站大爷隧道代理”的组合方案兼顾效率与稳定性。某乎的知识数据是一座尚未被充分开采的金矿。通过合理的爬虫策略和数据建模我们可以将这座金矿转化为结构化的知识图谱为推荐系统、专家发现、热点追踪等应用提供坚实的数据基础。最后需要提醒的是数据采集行为应当遵循行业规范控制请求频率以避免对目标服务器造成过载。请遵守目标网站的robots.txt协议及相关法律法规仅将爬虫技术用于学习和研究目的勿对目标网站造成过大压力。希望本文能帮助你在知识图谱构建的道路上迈出坚实的一步。