
我一直觉得刚入门的爬虫学习者最容易走偏的地方不是写不出代码而是不知道代码该往哪里发。很多人学了好几个requests的API拿到一个真实网站还是懵这个数据到底从哪里来页面源码里搜不到直接GET又全是乱码。这篇案例十三就是专门治这个问题的。我选了知乎热榜作为切入点把抓包→分析→写代码→存数据整个链路完整走一遍。标题里提到的截图和动图演示在静态博文里没法原样呈现我会把动图里的每一步操作拆成文字步骤你照着做看到的效果是一样的。为什么是热榜而不是别的页面三个原因第一热榜数据是页面加载后用JavaScript异步请求回来的这种场景天然适合练习看Network面板第二接口返回的是标准JSON新手不用去啃嵌套几十层的HTML标签第三热榜数据结构清晰标题、热度值、回答数、关注数全都有业务含义学完直接可以往数据分析和表格方向延伸。这篇代码部分我给了完整可运行的版本连带Cookie处理、数据清洗、CSV和JSON双格式存储都写进去了只要你Python环境装好requests库就能跑通。1. 为什么选知乎热榜当入门案例以及动手前必须想清楚的边界1.1 热榜接口的三个特点正好踩中入门练习的甜蜜点我在给新手选案例的时候心里有一套自己的标准。太简单的没有学习价值比如直接抓一个静态HTML页面requests.get然后正则匹配就搞定了学完你对整个爬虫链路还是没概念。太难的又不适合入门比如需要逆向JavaScript签名参数的接口光一个加密逻辑就能劝退大半人。热榜属于中间那档——恰到好处。第一个特点是异步JSON接口。打开知乎热榜页面浏览器先拿到的是HTML骨架真正的内容是JavaScript随后发起的XHR请求带回来的。这个特性意味着Network面板里一定存在一条独立的、可定位的数据接口而不是像静态页面那样数据散落在HTML源码里。练习抓包的人最喜欢的就是这种结构。第二个特点是数据规整。接口返回的JSON是标准的数组套对象字段名都是可读的英文单词比如title表示标题answer_count表示回答数follower_count表示关注数。处理JSON比解析HTML舒服太多了你不需要关心class名字、标签嵌套这些烦人的东西直接按键取值就行。第三个特点是反爬措施适中。热榜接口需要你伪装User-Agent有时候要带Cookie但不涉及加密签名参数。我用一个对比表格来说明为什么这个难度合适接口类型是否需要签名参数是否需要登录数据结构适合新手程度热榜接口否弱登录建议带Cookie标准JSON非常适合搜索接口是存在加密签名逻辑需要JSON不适合详情页HTML否不需要HTML标签嵌套一般搜索接口为什么难因为它的请求参数里有加密签名每个请求都要用JavaScript动态计算新手还没学会爬虫就先要学会逆向这是另一门功力了。热榜接口没有这层考验正好拿来练手。1.2 学习采集的合规边界这几条红线要记住每次写爬虫教程都必须说清楚的是边界问题。我教的是技术不是教你拿技术去做伤害别人的事。知乎热榜本身是公开数据任何人都能看到采集它不比用眼睛看它有更大的破坏力。但既然写出来这几条底线我必须划清楚仅限个人学习使用。采集到的数据自己分析、自己练手不要整理成数据集公开发布更不要打包拿去卖钱。数据的二次传播是敏感地带别碰。控制请求频率。这个案例单次运行只发一个请求拿50条数据对知乎服务器来说微不足道。但如果你写了个for循环每小时跑一万次那性质就变了。采集不是抢资源是礼貌地获取公开信息。尊重平台服务协议。平台产品规则里通常明确写着禁止未经许可的自动化采集个人学习场景下问题不大但是所有抓到的数据只能停在个人电脑里。随时做好被拒绝的准备。网站有权通过技术手段阻止爬虫访问遇到被封IP、被要求登录验证停下就好不要想着突破。这四句话不是套话是这几年我见过太多人踩坑后的血泪教训。有人把爬来的数据分析结果发到社交平台上账号被平台方追究有人写了个采集脚本放在GitHub公开仓库里直接收到律师函。技术本身是干净的用在什么地方、怎么用是你自己的选择。2. 抓包定位热榜接口完整思路与操作演示2.1 准备阶段用无痕窗口加F12打开一个干净的现场抓包这件事看似技术复杂其实就是三个步骤打开浏览器开发者工具、刷新页面、在请求列表里找到目标接口。但很多人第一步就做错了——用自己日常使用的浏览器窗口直接操作。我强烈建议开一个无痕窗口再去抓包。原因很简单你自己浏览器里装了各种插件广告拦截、翻译助手、密码管理器这些插件会在页面加载时发出大量额外请求把Network面板搅成一锅粥。而且浏览器缓存会干扰判断你刷新页面时部分资源可能直接走缓存根本没发起网络请求看起来就不完整。无痕窗口初始状态下没有缓存、没有插件干扰Network面板清清白白每个请求都是页面真实发起的排查起来舒服得多。具体操作流程是这样打开桌面浏览器按CtrlShiftNMac上是CommandShiftN进入无痕模式。在地址栏输入www.zhihu.com/hot回车进入知乎热榜页面。按F12打开开发者工具它会自动贴在浏览器右侧或底部。切到Network面板在过滤条件里勾选Fetch/XHR。这一步很关键它会把图片、CSS、字体这些无关资源全部过滤掉只留下普通的异步请求。按F5刷新页面。做完这些Network面板就会开始记录页面发出的所有异步请求。动图演示里我按的就是这个顺序如果你抓到的界面和我描述的不一样大概率是漏了哪一步。2.2 Network面板里如何快速认出数据接口刷新完之后你会在面板里看到一串请求。新手到这里最容易懵这么多请求哪个才是热榜数据的真身我教你三个识别方法按顺序使用很快就能锁定目标。先看名字。数据接口的URL通常包含业务关键词。在热榜这个场景下你优先找名字里带hot、topstory、feed、recommendations这类词的请求。特别是hot-lists/total这个关键词组合几乎就是明牌告诉你“这是热榜接口”。再看类型。每个请求在面板里都有Type列标着xhr、fetch、document、script、stylesheet这些类型。你要找的是xhr或fetch。看到document可以直接跳过那是页面本身的HTML看到script、stylesheet也跳过那是JS和CSS文件。最后看响应内容。点中一个请求右侧会弹出详情面板切到Preview或Response标签。Preview里如果能展开成树状的JSON结构一级一级能点开那多半就是数据接口。如果Preview里是一坨压缩过的代码或者是一堆看不懂的乱码那这个请求跟数据没关系。在动图演示里操作过程是这样的鼠标从请求列表从上往下扫一连点了七八个Preview里要么是HTML代码要么是字体文件直到鼠标点到一个叫hot-lists/total的请求时右侧的Preview直接展开成三级树形结构最外层是data数组数组里整整齐齐排列着50条热榜数据。到这一步你不需要再怀疑接口已经找到了。2.3 从请求到响应的完整链路一条接口的解剖找到接口之后不要急着写代码先把这条请求的细节看明白。点击请求详情面板里面有几个信息是你写爬虫代码时必须要用到的Request URL请求地址是https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit50desktoptrue。这个URL拆开来看就清楚了hot-lists/total是接口路径问号后面是参数。limit50表示返回50条数据desktoptrue表示桌面端请求。这两个参数你也可以改比如把limit改成20返回的数据条数会相应变化。Request Method请求方法是GET没有请求体所有信息都放在URL和请求头里。记住这个后面写代码时用requests.get而不是requests.post。User-Agent需要特别关注。知乎服务器会根据User-Agent判断请求来自真实浏览器还是脚本。如果你在抓包时发现自己的浏览器UA字段是一个长长的、包含Mozilla/5.0、Windows NT、AppleWebKit、Chrome这些关键词的字符串说明你的访问环境正常。写爬虫代码时要把这个UA原样复制到代码里。Referer字段是https://www.zhihu.com/hot表示请求是从热榜页面发起的。有些服务器会用Referer做合法性校验非法来源的请求直接拒绝。代码里把Referer也带上能减少被拦截的概率。还有一个很实用的验证方法在热榜页面上找到第一名的问题标题然后在接口的Response内容里CtrlF搜索这个标题的一个关键词。如果搜到了就证明这个接口返回的就是你页面上看到的那些数据。这个方法适用于所有网站的抓包验证学会它比背一百条接口地址都管用。3. 热榜接口返回数据的结构拆解JSON字段逐个看3.1 先看骨架paging、data、fresh_text拿到接口返回的JSON后新手的第一反应往往是直接开写正则去抠数据。我劝你停一下先把JSON的结构打开一层一层看清楚。理解了数据结构解析代码自然就写出来了不理解结构代码写一半一定卡壳。热榜接口的JSON最外层有几个字段paging、data、fresh_text。先讲后面两个fresh_text字段的值是一个字符串类似“刚刚更新”页面上的更新时间就是它paging字段是分页信息包含is_end、next、previous三个子字段表示是否还有下一页以及跳转链接。data字段是关键核心它是一个数组里面每一项就是一条热榜内容。数组长度和你传入的limit参数一致15、20、50都可以默认50条。每一条内容的结构大致是type内容类型、detail_text热度文字描述、target实际的内容对象、index排名序号、children子内容通常是补充信息。3.2 target对象里有什么标题、摘要、回答数、关注数每一条热榜数据里真正承载内容的是target对象。这个对象是一个典型的“内容聚合体”大部分字段都是可读的英语单词我挑最常用的列了一个表格字段名含义示例值id问题ID602233456title问题标题你经历过最尴尬的事是什么excerpt问题摘要就是突然不知道怎么接话那种...answer_count回答数3287follower_count关注数89245comment_count评论数126created创建时间戳1612500000有时候你点开某一条数据发现target里没有title字段别慌。热榜里有一种内容类型叫“想法”类似短动态它的标题不在最外层而是嵌套在target里的question对象中。所以写解析逻辑的时候最好兼容两种结构先取target.title取不到再去target.question.title里找。我在后面的完整代码里就是这么处理的。3.3 detail_text热度值的处理思路每个data条目里都有一个detail_text字段它的值是类似1850 万热度这样的字符串。这个字段是给人看的不是给程序用的直接存下来后面做数据分析会很痛苦。你想算热度变化趋势总不能对“1850 万热度”和“896 万热度”两个字符串做减法吧所以在解析阶段就应该把文字热度值转换成数值。思路很简单判断字符串里有没有“万”字有的话把“万”字前后拆开前面的数字乘以10000没有“万”字说明热度数值还比较小直接转换成整数就行。举个例子1850 万热度→ 去掉“ 万热度” → 1850 → 乘以10000 → 1850000096 万热度→ 去掉“ 万热度” → 96 → 乘以10000 → 960000有了这个数值你就可以画热度趋势折线图做排序对比甚至用pandas做统计分析。这是整个案例里最能体现“数据清洗”思想的一个点也是新手最容易忽略的细节。很多人抓完数据存进CSV就不管了等到真想分析的时候才发现字段格式不对又要回头重新写清洗逻辑多走一遍冤枉路。4. 完整爬虫代码实现每行注释都给你写清楚4.1 整体设计请求、解析、存储三板斧写爬虫代码我最怕的就是那种把几十行逻辑全堆在全局作用域里的写法。变量满天飞函数一个没有跑完一次就废想改个URL还得在代码里到处搜索。正规一点的写法是拆成三个模块请求模块负责获取数据解析模块负责把JSON变成规整的列表存储模块负责写文件。这样每块逻辑都是独立的出了问题单独排查就行。基于这个设计思路我用了一个类来组织代码把请求、解析、保存都封装成类方法。类的好处是状态统一管理实例化一次之后headers、cookie、session这些配置都跟着实例走主函数里调用起来非常清爽。4.2 完整代码热榜采集器import requests import json import csv from datetime import datetime class ZhihuHotListSpider: 知乎热榜数据采集器 def __init__(self, cookieNone): # 接口地址limit控制返回条数desktop表示桌面端 self.base_url https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total # 请求头伪装成真实浏览器这是反爬的第一道门槛 self.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, Accept: application/json, text/plain, */*, Referer: https://www.zhihu.com/hot, Origin: https://www.zhihu.com, } # 如果有登录后的Cookie就加进去可以提高请求成功率 if cookie: self.headers[Cookie] cookie def fetch(self, limit50): 发送请求获取热榜原始JSON数据 params { limit: limit, desktop: true } # 使用Session保持请求会话连续请求时对服务器更友好 session requests.Session() session.headers.update(self.headers) resp session.get(self.base_url, paramsparams, timeout10) resp.raise_for_status() # 状态码不是200时直接抛异常 return resp.json() def parse(self, json_data): 解析JSON提取需要的关键字段 result [] for item in json_data.get(data, []): target item.get(target, {}) # 兼容两种内容结构 # 一种是target里直接有title一种是title在question子对象里 title target.get(title, ) if not title: question target.get(question, {}) title question.get(title, ) excerpt target.get(excerpt, ) answer_count target.get(answer_count, 0) follower_count target.get(follower_count, 0) # 热度值清洗1850 万热度 - 18500000 hot_text item.get(detail_text, ) hot_num 0 if 万 in hot_text: try: num_str hot_text.replace( 万热度, ).replace(万热度, ) hot_num int(float(num_str) * 10000) except ValueError: hot_num 0 result.append({ rank: item.get(index, 0), title: title, excerpt: excerpt, answer_count: answer_count, follower_count: follower_count, hot_text: hot_text, hot_num: hot_num, url: fhttps://www.zhihu.com/question/{target.get(id, )}, crawled_at: datetime.now().strftime(%Y-%m-%d %H:%M:%S) }) return result def save_as_json(self, data, path): 保存为JSON文件ensure_asciiFalse保留中文 with open(path, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) def save_as_csv(self, data, path): 保存为CSV文件utf-8-sig编码防止Excel打开中文乱码 fieldnames [ rank, title, hot_text, hot_num, answer_count, follower_count, url, crawled_at ] with open(path, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(data) if __name__ __main__: # 实例化爬虫建议传入你登录后浏览器里的Cookie成功率更高 spider ZhihuHotListSpider( cookie你的Cookie ) # 1. 请求热榜数据 raw_data spider.fetch(limit50) # 2. 解析为规整的字典列表 items spider.parse(raw_data) # 3. 分别保存为JSON和CSV两种格式 spider.save_as_json(items, zhihu_hot_list.json) spider.save_as_csv(items, zhihu_hot_list.csv) # 4. 在控制台打印前5条验证结果 for item in items[:5]: print(item[rank], item[title], item[hot_text])注意代码里的cookie你的Cookie需要替换成你自己浏览器里的真实Cookie。获取方法是在已登录的知乎页面按F12打开开发者工具切到Network面板刷新页面选中任意一个请求在Request Headers里找到Cookie:后面的内容整段复制粘贴过来。Cookie是你登录凭证的一部分不要把这个文件推送到公开的Git仓库里。4.3 运行与验证一次跑通整个采集流程第一次运行前确保你的环境里已经安装了requests库。没装的打开命令行执行pip install requests装完再运行脚本。如果用的是Anacondarequests通常已经内置了不用额外装。运行之后你会看到控制台打印出类似这样的内容1 你经历过最尴尬的事是什么 1850 万热度 2 有哪些你以为是常识但其实是误解的冷知识 1723 万热度 3 如何评价2025年春节期间上映的电影 1468 万热度同时当前目录下会多出两个文件zhihu_hot_list.json和zhihu_hot_list.csv。用记事本打开JSON文件能看到格式化过的完整数据用Excel打开CSV文件表格里每一列就是解析好的字段中文不会乱码热度值已经是纯数字。到了这一步一个完整的爬虫项目就算做完了。整个流程走下来你实际上掌握了四个核心技能用开发者工具定位异步接口、解析JSON数据、处理非规范文本、把结果落盘成文件。这四个技能是90%以上爬虫项目的通用骨架学会了这一套换任何网站都只是更换URL和解析字段的事。5. 实战中必踩的坑UA、Cookie、频率控制5.1 第一个坑不给User-Agent直接403新手写爬虫第一个请求经常这样写requests.get(url)就这么一行没有headers然后运行返回一个403 Forbidden。为什么because服务器看到你的请求头里的User-Agent是python-requests/2.31.0一眼识破这是脚本不是浏览器直接拒绝服务了。解决方式是给requests库穿上浏览器的马甲。把浏览器F12里看到的User-Agent完整字符串复制到代码的headers字典里请求就变得和正常浏览器访问没有区别了。这一招是所有网站通用的不管你是爬百度还是爬豆瓣先伪装UA再谈其他。5.2 第二个坑不带Cookie时好时坏热榜接口有个烦人的特点有时候不登录也能访问返回正常数据有时候又会把你重定向到验证页面返回的是HTML而不是JSON。这个“时好时坏”跟你的IP段、请求频率、以及平台当前的风控策略都有关系。最稳妥的做法是在请求头里带上Cookie。这里分享一个效率技巧不要手动复制Cookie——在Network面板里右键点击刚才找到的热榜接口请求选择Copy→Copy as cURL (bash)然后把整段curl命令粘贴到命令行工具里执行正常情况下会直接返回JSON数据。这说明你当前浏览器的Cookie是有效的。这时候再把curl命令转成Python代码推荐用 requests库结合curlconverter这类在线工具一键转换比自己手写强多了。5.3 第三个坑频率控制不到位账号被限制很多爬虫初学者拿到一个能用的接口就控制不住地想要“多爬一点”。今天爬了一次下午又爬一次明天写个循环爬一百次。一段时间后你会发现接口突然返回登录验证页甚至提示账号异常。这就是被风控了。我个人的建议是学习场景下每小时最多发几次请求哪怕做定时采集间隔也不要低于5分钟一次。热榜数据的更新频率本身就慢通常几分钟才变化一次你爬得再频繁拿到的数据也几乎一样。没必要用高频请求去冒账号风险。重要提示Cookie关联着你的知乎账号。假如采集频率过高导致平台发起了账号安全验证你不仅失去了接口访问权自己的日常账号使用也会受到影响。为了练一个爬虫把自己主账号搭进去这笔账怎么算都不划算。5.4 第四个坑返回的是HTML不是JSON还有一种情况容易让人迷惑请求的URL明明是对的UA也带了Cookie也带了但代码里执行resp.json()时报错说JSON解析失败。别急着改代码先把resp.text打印出来看一眼。大概率你会看到一段完整的HTML里面写着“请完成安全验证”或“访问太过频繁”。这说明你的请求被风控拦截重定向到了验证页面。接口本身并没有问题问题是你的当前环境触发了反爬。处理的方式无非是降低请求频率、换一个网络环境、或者等待一段时间再试。这四种情况我在不同网站上都碰到过处理思路是通用的先判断返回内容是不是预期格式不是就要怀疑被拦截而不是怀疑自己的代码逻辑。6. 从热榜采集到数据落地顺手把存储和扩展也做了6.1 CSV和JSON两种格式的取舍代码里我同时保存了JSON和CSV两种格式这不是多此一举而是两个格式的用途完全不同。JSON格式保留了数据的层级关系和完整度方便程序后续读取和处理。如果你想做更复杂的分析用Python的pandas库读取JSON数据非常顺手。CSV格式则方便人眼查看和Excel操作双击文件就能用表格软件打开排序、筛选、做图表都很直观。两个都存一份后续无论是给人看还是给程序用都有的选。需要注意CSV文件的编码细节。我用的是utf-8-sig而不是普通的utf-8。这个带-sig后缀的编码会在文件开头写入一个BOM标记Excel打开的时候就能正确识别中文字符编码不会出现一打开全是乱码的情况。很多新手在这里吃过亏存的时候明明用的utf-8用Excel打开却乱码就是因为少了这一个小小的后缀。6.2 定时采集让脚本自己跑起来热榜数据是动态变化的只跑一次拿到的只是一个时间点的快照。如果你想观察数据的变化趋势比如搞清楚一个热点话题在24小时内的热度走势那就需要定时采集。我常用的方式是用系统自带的任务计划程序或者写一个最简单的死循环加sleepimport time while True: spider ZhihuHotListSpider(cookie你的Cookie) raw_data spider.fetch(limit50) items spider.parse(raw_data) timestamp time.strftime(%Y%m%d_%H%M%S) spider.save_as_csv(items, fhotlist_{timestamp}.csv) print(f{timestamp} 采集完成共{len(items)}条) time.sleep(1800) # 每30分钟采集一次这样每30分钟自动采集一次文件名带上时间戳积累一个月你就拥有一份完整的热度变化数据集接下来可以做的事情就多了算热度排名波动、分析话题上升速度、对比不同时段的活跃度。这些都算是数据采集的上层应用本质上是你这篇博文学到的基础技能在延伸。6.3 关于这个案例的几句体己话带了几期入门爬虫项目之后我越来越发现一个规律新手能不能学好爬虫关键不在他掌握了多少库的API而在于他遇到问题时的第一反应是什么。是打开浏览器看网络请求还是瞎猜参数乱试是去查接口返回的数据结构还是满屏print碰运气这个案例把抓包思路放在最前面就是想让你养成一个职业习惯任何数据采集需求第一步永远是搞清楚数据从哪里来而不是急着打开编辑器写代码。你用开发者工具把接口定位清楚把返回的数据结构看上两遍写代码的时间可能只要十分钟。我在实际带项目的过程中见过太多人卡在同一个地方接口找到了数据结构也看懂了却不知道怎么把数据变成代码里的变量。其实这个问题没有门槛就是多用、多错、多改。就像学游泳你看再多的教程都不如下水呛两口水来得实在。现在热榜的采集脚本已经完整地摆在上面了建议你把它跑起来然后在它基础上改一改比如增加翻页、改成采集问题的全部回答、或者接一个简单的数据可视化页面。改着改着你就算真正入了门。