
简介这份资源面向具备Python基础、希望入门网络数据采集的开发者与数据分析学习者围绕知乎问答社区场景讲解如何抓取回答中的文本与图片。内容涉及HTTP请求发送、HTML解析、文件读写与延时策略等环节帮助读者理解从问题ID定位页面到提取回答正文、图片链接及作者信息的完整思路。压缩包共3个文件包含2个py脚本与1个txt依赖清单分别承担数据抓取、结果保存和运行环境说明的职责整体约4KB结构精简便于快速阅读与二次修改。目前已有1144人学习下载适合作为爬虫练手项目参考。读者可从中获得请求构造、解析提取、图片下载与本地存储的代码范例并了解通过time.sleep控制请求频率、设置计数器限制下载数量等防封禁思路为后续数据分析和信息挖掘打下实践基础。1. 从一份 Python 爬虫源码说起知乎回答文本与图片怎么落地知乎回答页面的内容结构是很多做数据采集的开发者绕不开的一道坎。正文里混着富文本、代码块、外链图片图片还分「点开大图」和「缩略图」两套地址直接拿 requests 抓 HTML 往往只能捞到一堆懒加载占位符。这份 Python 爬虫源码包针对的就是「把某个问题下所有回答的文本和图片完整扒下来」这个具体诉求输出结构化的文本文件和本地图片目录。它适合做舆情分析、内容归档、数据集构建的从业者也适合刚学完 requests 和 BeautifulSoup、想找一个真实站点练手的开发者。源码里对请求头、分页、图片下载做了封装但知乎的反爬策略这几年一直在变直接跑之前得先搞清楚它的边界在哪。2. 拆解爬取链路从请求构造到图片落盘2.1 为什么不能只靠 requests BeautifulSoup知乎回答列表页在未登录状态下只返回前几条回答往下滚动触发的是 XHR 接口返回的是 JSON 结构而不是完整 HTML。这意味着纯静态解析的路子走不通。源码里采用的是「先拿页面里的初始数据再走接口翻页」的混合策略。初始页面里嵌了一个js-initialData的 script 标签里面是 JSON 格式的初始状态包含前几个回答的完整内容后续回答则通过api/v4/questions/{id}/answers这个接口按limit和offset分页拉取。这个接口返回的 JSON 里data数组每一项的content字段是 HTML 字符串excerpt是纯文本摘要。图片信息藏在content的img标签里>import requests import json import re import os from urllib.parse import urlparse # 基础请求头Cookie 需要替换成自己浏览器里抓到的有效值 HEADERS { 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, Referer: https://www.zhihu.com/, Cookie: 你的_cookie_字符串, # 关键字段缺失会导致分页受限 } def fetch_answers(question_id, offset0, limit20): 按偏移量拉取回答列表返回 JSON 中的 data 数组 api fhttps://www.zhihu.com/api/v4/questions/{question_id}/answers params { include: data[*].content,excerpt,author,created_time, limit: limit, offset: offset, sort_by: default, } resp requests.get(api, headersHEADERS, paramsparams, timeout15) if resp.status_code ! 200: print(f请求失败状态码{resp.status_code}) return [] return resp.json().get(data, [])这段代码里include参数决定了返回字段的丰富程度不传的话content可能不返回。limit单次最大一般不超过 20设太大容易被截断。offset按 20 递增直到返回的data为空或paging.is_end为 true 为止。实际跑的时候建议在每次请求之间加 1 到 2 秒延时源码里用的是time.sleep(random.uniform(1, 2))这个习惯能省掉很多封禁麻烦。2.3 文本清洗与图片地址提取拿到content字段后它是一段 HTML。文本提取不能直接上正则去标签因为代码块里的会被误伤。源码里用的是 BeautifulSoup 的get_text()配合分隔符把块级元素之间补上换行。from bs4 import BeautifulSoup def extract_text_and_images(html_content): 从回答 HTML 中提取纯文本和原图地址列表 soup BeautifulSoup(html_content, html.parser) # 把 br 和 p 替换成换行避免文本粘连 for tag in soup.find_all([br, p, div]): tag.append(\n) text soup.get_text(separator\n, stripTrue) image_urls [] for img in soup.find_all(img): # 优先取>import hashlib import os def download_images(image_urls, save_dir): 下载图片到指定目录返回成功数量 os.makedirs(save_dir, exist_okTrue) success 0 for url in image_urls: try: resp requests.get(url, headersHEADERS, timeout20, streamTrue) if resp.status_code 200: # 用 URL 的 MD5 做文件名避免重名 ext os.path.splitext(urlparse(url).path)[1] or .jpg fname hashlib.md5(url.encode()).hexdigest() ext fpath os.path.join(save_dir, fname) with open(fpath, wb) as f: for chunk in resp.iter_content(1024): f.write(chunk) success 1 except Exception as e: print(f下载失败 {url}{e}) return successstreamTrue配合iter_content是为了避免大图一次性读进内存。超时设 20 秒比文本请求宽松一些因为图片服务器响应偶尔会慢。如果某张图反复失败源码里不会中断整个流程只是记录日志继续下一张这个设计在批量任务里很实用。3. 避坑与排查那些跑起来才会遇到的问题3.1 现象接口返回 403 或空数据原因通常是 Cookie 过期或请求头里缺少Referer。知乎对接口的校验不只看 Cookie还会检查来源页。解决方法是重新从浏览器开发者工具里复制最新 Cookie并确保Referer指向https://www.zhihu.com/。如果换了 IP 或短时间内请求过多也可能触发风控这时候需要降低频率把延时拉到 3 秒以上。3.2 现象图片下载下来是 1KB 左右的空白图这是典型的懒加载占位图。原因在于提取时取了src而不是>import yaml import os def load_config(pathconfig.yaml): with open(path, r, encodingutf-8) as f: cfg yaml.safe_load(f) # 环境变量优先级高于配置文件 cfg[cookie] os.getenv(ZHIHU_COOKIE, cfg.get(cookie, )) return cfg4.2 断点续爬与日志记录批量任务最怕跑到一半中断重新跑又得从头来。源码里用了一个简单的状态文件记录已完成的 offset 和已下载的图片 URL 集合。每次启动先读状态跳过已完成部分。日志用logging模块输出到文件格式包含时间戳、级别和消息方便事后排查哪一页出了问题。参数建议值说明请求间隔1.53 秒太短触发风控太长效率低单次 limit20超过 20 容易被截断图片超时20 秒大图下载留足时间重试次数2 次失败后间隔 3 秒重试状态保存频率每页一次避免频繁写盘4.3 验证抓取完整性的方法跑完之后怎么确认没漏可以对比接口返回的paging.totals字段和实际保存的回答数量。如果差距超过 5%说明中间有失败页需要检查日志里的 403 或超时记录。图片方面统计每个回答目录下的文件数和提取阶段的 URL 数量做对比差值就是下载失败的图片。我一般会在脚本最后打印一份汇总总回答数、成功文本数、图片总数、下载成功数、失败列表。这份汇总比任何日志都直观。从那以后我每次写采集脚本都强制先跑一个「只抓一页、只下三张图」的冒烟测试确认链路通了再放开批量。这个习惯帮我省下了不少因为 Cookie 过期或接口变更导致的整夜空跑。希望这份源码的拆解能帮到你拿到手之后先改配置、再跑冒烟、最后开批量顺序别乱。本文还有配套的精品资源点击获取