1. 项目概述:为什么盯上微博热搜这个数据源
最近接了个舆情分析的小项目,核心需求是把微博热搜榜和话题下的内容结构化落库,供后续做关键词聚类和情感分析用。一开始需求方说是“每天手动看一眼就行”,但真跑起来就知道,人工盯热搜这种高频变动的数据根本不现实——半夜的突发话题、凌晨的榜单换血,靠肉眼根本追不完,于是果断上了定时采集的方案。
这篇博文就完整记录我这次实战的整个过程:从逆向微博热搜榜和话题详情页的API、用异步并发把采集速度拉上去,到部署定时任务让脚本按分钟级自动跑,最后把结果清洗落库。整个流程里踩了不少坑,尤其是签名参数、频率限制、进程退出这几个老问题,前后折腾了三个晚上才算稳定下来。
先说结果:最终脚本跑一轮全量采集(热搜榜50条+每条话题下的热门博文50篇)只需要8到12秒,定时任务每5分钟触发一次,连续跑了一周没有出现过一次因代理、风控或超时导致的采集中断,单日新增原始数据约3万条。
这套方案里,核心难点不在requests或者parsel这种基础工具,而在三个地方:一是怎么把微博前端的接口摸清楚,二是异步并发时怎么控制频率避免被秒封,三是定时任务进程怎么保证长期稳定运行。这三个问题搞定了,爬微博热搜基本就是复制粘贴的事,下面的内容我会把这套完整的方法论和代码细节全部展开,照着做就能复现。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体设计思路:先看清微博热搜的数据链路
2.1 微博热搜的真实数据来源
微博的网页端、移动端、小程序端看着界面不一样,但底层数据都是走HTTP接口返回JSON,区别只是Host和部分参数不同。我们平时在weibo.com热搜榜页面上看到的那50条词条列表,实际上是网页端JavaScript异步加载后从一个接口拉来的,而不是服务端直接渲染在HTML里的。这一点非常关键——很多新手去抓weibo.com首页的HTML,结果发现里面根本没有热搜数据,就是这个原因。
我最终用的是网页版热搜榜背后的接口,路径是https://weibo.com/ajax/side/hotSearch。这个接口返回的数据长这样:data.realtime是一个列表,每条记录里面有word(热搜词)、raw_hot(热度值)、rank(排名)、category(分类标签)、label_name(比如“沸”“热”“新”这种标识)等字段,非常规整。
除了榜单本身,我加了一个话题内容采集的需求。热搜词点进话题页之后,下面的博文流走的是另一个接口,路径类似https://weibo.com/ajax/statuses/mymblog或https://weibo.com/ajax/statuses/search,需要传话题关键词和分页游标。这里和搜索爬虫最大的区别是:话题流接口返回的字段非常全,正文、转发数、评论数、点赞数、发布时间戳、用户信息全部嵌套在statuses字段里,比网页解析省事太多。
2.2 异步技术选型:为什么选asyncio+aiohttp
采集任务的第一步其实是选型。热点数据采集的特点是单次任务小、次数频繁、IO密集型,用多线程当然也可以,但Python的多线程受GIL限制,并发优势很难发挥出来。我用的是asyncio + aiohttp的组合,走协程异步,底层单线程事件循环,靠非阻塞IO把网络等待的时间全部压掉。
有人会问,既然requests那套同步代码写起来更顺手,为什么非要折腾异步?直接用一个简单的对比来说话:同步方式抓50个话题页,每个请求平均200毫秒,串行就得10秒钟;换成异步,50个请求同时发出去,总耗时还是200毫秒出头,这是数量级的差距。尤其是在微博这种接口比较稳定的场景下,异步并发带来的收益非常可观,而且Python的asyncio写起来并没有想象中那么复杂。
不过异步也不是没有代价——最大的坑就是频率控制变麻烦了。同步代码里你可以在每两次请求之间加个time.sleep来控制节奏,但异步环境下所有请求都在同一个事件循环里并发跑,如果不在信号量(Semaphore)上做限制,几十个请求瞬间同时打过去,不封你封谁。这个细节我在后面的实操部分会重点讲。
2.3 定时采集的完整架构
整套系统的结构其实非常简单,我画个文字版的关系图帮助理解:
code复制定时调度器(APScheduler)
↓ 每5分钟触发一次
HotSearchWorker(采集任务)
├── 异步抓取热搜榜接口 → 解析榜单数据
├── 对每个热搜词 → 异步抓取话题内容接口
├── 数据清洗与去重
└── 批量写入SQLite/CSV
我没有用Scrapy框架,也没上Celery这类分布式任务队列,因为这个项目的体量根本用不到那么重的组件。一个APScheduler负责定时触发,一个异步任务函数负责采集,一个SQLite库存数据,就这三个核心模块,全部代码不到500行。这也是我一直在说的:爬虫项目的复杂度应该严格跟着数据规模走,别动不动就上松耦合分布式架构,维护成本会吃掉你的所有效率。
3. 逆向分析:一步步摸清微博热搜API的请求链路
3.1 从浏览器开发者工具入手
所有接口逆向的起点都是浏览器开发者工具,这一步没有任何捷径。打开微博热搜页面,按F12切到Network面板,刷新页面,然后重点找XHR或Fetch类型的请求。热搜榜数据对应的请求会在页面加载后自动发出来,名字通常是s开头或者叫hotSearch,看一眼Response就能确认是不是我们要的JSON数据。
实际操作中有一个小技巧:在Network面板右上角的筛选栏里输入s或者ajax,可以直接过滤出所有异步请求,省得一页一页翻。另外,微博网页版的热搜接口可能不止一个,有些走ajax,有些走gateway,多试几个就能找到数据最全、字段最规整的那个。
我当时是个很笨的办法确认的:把每个可疑请求的Response都复制出来,统一搜索“阿凡达”这类当天的热点词,看哪些请求返回了包含这个关键词的JSON结构。这个方法效率虽然低,但胜在稳妥,不会漏掉正确的接口。
3.2 请求头和Cookie的伪装要点
找到接口地址之后,下一步是把请求参数和请求头完整抄下来。微博的接口有反爬机制,但只要你的请求头和浏览器保持一致,基本都能正常返回数据。
关键请求头字段如下:
http复制User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
Referer: https://weibo.com/hot/search
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: 你的登录Cookie
这里面,User-Agent和Referer必须带上,没有这两个字段微博的反爬系统会直接拒绝请求。Cookie方面,热搜榜接口即使不登录也能返回数据,但话题内容流必须要有有效的登录Cookie,否则返回的是游客受限版本,数据不完整甚至直接返回空列表。
3.3 签名参数的坑与绕过方案
当你把请求照抄下来跑第一遍的时候,大概率会遇到两种情况之一:返回200但数据为空,或者返回418/403。前者通常是Cookie失效或签名参数缺失,后者是真被风控拦了。
这里重点说一下签名参数。微博的某些接口会要求带上`x-xsrf-tok
