news 2026/9/17 1:46:07

微博评论爬虫实战:weibo_spider接口解析与稳定抓取策略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微博评论爬虫实战:weibo_spider接口解析与稳定抓取策略

简介:这是一份针对微博平台定制的网络爬虫项目,面向希望学习社交数据采集的爬虫开发者与数据分析人员。程序聚焦抓取微博正文与评论,覆盖API请求、HTML解析、动态内容加载、反爬规避及数据持久化等核心模块,适用于舆情监控、话题分析、用户行为研究等场景。压缩包约1.26MB,文件总数标注为0,具体文件类型明细暂未提供,资源本体应包含核心爬虫脚本与配置说明。已有1899人学习浏览,适合具备Python基础、希望以实战项目理解爬虫开发流程的读者。通过研读代码,可掌握requests会话构造、BeautifulSoup页面解析、Selenium模拟登录与滚动加载等实现方式,同时能体会代理IP轮换、请求间隔设置与登录态维持等反爬策略的落地写法。评论抓取部分还涉及分页拼接、评论者ID与时间字段提取等典型处理思路,有助于举一反三迁移到其他社交媒体平台,是一份值得参考的实操性案例。

1. 爬微博评论前,先看 weibo_spider 会遇到什么

我们试着把 weibo_spider 这类微博爬虫从“能打开”推到“能稳定跑完一个话题下的所有评论”时,最常见的卡点不是登录,而是评论流的翻页参数和风控边界。同一篇文章的评论,热门区能拿到几十条,全部区却经常在第三页后返回空 data;换个未登录的 cookie,可能直接 403。这不是脚本写错,而是微博把评论分成了热门评论与全部评论两套流,并且对未登录态做了频控。这篇文章面向打算自己实现或改造 weibo_spider 的开发者,讲清评论接口的数据结构、最小抓取脚本、翻页条件,以及怎么让爬取微博评论的过程可暂停、可续跑、可复核。默认你用 Python 3.9+,只依赖 requests 和标准库,这样后续接入任何调度框架都容易。

2. 评论接口的翻页模型:weibo_spider 抓取的数据源头

2.1 评论流不是一条:热门评论与全部评论的差异

写爬取微博评论的代码前,先确认你面对的是哪条流。web 端和移动端接口很不一样,weibo_spider 类项目通常优先选 m.weibo.cn 的评论接口,因为返回体小、分页参数明确。常见接口路径类似/comments/hotflow,也有一部分项目用/comments/flow,两者返回的 data 字段结构几乎一致,但 max_id 的语义不同。热门评论接口通常只返回被顶起来的评论;全部评论接口才返回完整楼层。如果只想要“所有评论”做情绪分析,别把它当成一条流去遍历,否则抓到的永远是热门区。

参数热门评论 hotflow全部评论 flow / buildComments
用途抓高赞、博主精选抓完整楼层
主要入参id, midid, mid,部分场景还要 since_id
翻页字段max_idmax_id 或 since_id
返回空 list 的场景评论数少时正常翻页到尾部时常见,也会遇到风控
处理方法data 里没有 max_id 就结束空 list 要配合响应码判断是否重试

刚开始跑 weibo_spider 时,我一般先分别请求一次热门和全部接口,比较返回条数与max_id的位置。这个动作看起来很笨,却能避免后续把“热门评论”误当成“全部评论”,直接省掉一轮返工。

2.2 一条评论的核心字段

观察一次返回的data['data'],里面每一项大致如下。字段名在不同接口版本略有差异,但 id、text、user、created_at 基本稳定。写 weibo_spider 时建议把原始 dict 整份先落盘,再提取字段,因为评论正文里有嵌套的 url_struct、pic_video 等扩展信息,单独挑字段时容易漏。

{ "id": 4707123456789012, "text": "<a href=\"/n/某用户\">@某用户</a>:这条评论内容", "like_count": 8, "created_at": "2024-05-12 10:24:31", "user": { "id": 123456, "screen_name": "昵称" }, "root_id": 0, "reply_comment_id": 0, "max_id": 15876543210987 }

逻辑说明:text 里带 HTML 标签,后面清洗时要去标签;max_id 是下一条评论的游标。这一步最好保留 root_id 以区分楼中楼。注意这里的max_id有时挂在评论对象上,有时挂在data顶层,两种取值方式都要兼容。实际抓取评论时,建议把 id 当作唯一主键,因为同一个用户的多条评论可以完全相同,不能靠 text 去重。

2.3 翻页终止条件要区分“结束”和“风控”

weibo_spider 在爬评论时最忌一遇到空 data 就 break。全部评论流翻到 150 条以后,返回 data 可能为空,但 status 仍是 200;而一旦遭遇频控,接口会返回-100或 403。因此代码里需要三重判断:ok不等于 1 时记录错误;max_id为空或 0 时按正常结束;list为空但max_id不为空时重试。把这三条写进循环,后续排错才不用翻日志。另一个值得注意的点是,data存在但list为空的场景里,响应头里可能带Content-Encoding: gzip,所以 requests 要允许自动解压,否则你看到的是一串字节而不是空列表。

2.4 请求头里哪些字段影响评论接口返回

直接用requests.get打该接口会收到 403,常见做法是带上完整的浏览器请求头。最关键的三个键是 User-Agent、Referer 和 Cookie。Referer 应该指向具体微博页;Cookie 里至少包含 SUB 或 SUBP,否则评论接口可能只返回登录引导。下面给出 HEADERS 模板,用它去请求热门评论等页面都通用。

HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0", "Accept": "application/json, text/plain, */*", "Referer": "https://weibo.com/", "X-Requested-With": "XMLHttpRequest", "Cookie": "换成你自己的 SUB 和 SUBP" }

参数说明:这里 Referer 只填了根域,实际使用时可替换成目标正文页地址;X-Requested-With 是给反爬识别用的,不加有时也能通,但加了表现为更像浏览器。Cookie 不要写死在代码仓库里,建议放环境变量,否则换一台机器就要改源码。

3. 用 Python 请求库跑通爬取微博评论的最小脚本

3.1 先确认微博正文 id 和 mid

爬取微博评论需要两个参数:微博正文的 id 和 mid。从 PC 端地址里最容易拿到:/detail/数字那一段通常是 mid;打开浏览器开发者工具,在评论接口请求的 query string 里能看到 id 与 mid。weibo_spider 类项目通常直接解析正文接口返回,但临时脚本里可以写死这两个值来验证。我们要先跑通,再抽象,所以不要让第一步就写配置文件。

3.2 最小循环:从首页评论爬到最后一条

下面这段代码只依赖 requests,完成一次完整评论流的抓取,并把结果按 JSONL 追加保存。注意不要直接用resp.json()["data"]去覆盖列表,因为 data 里可能套着data['list']

import time import json import requests # HEADERS 沿用上一节的字典,Cookie 必须带上 HEADERS = { "User-Agent": "Mozilla/5.0 ...", "Referer": "https://weibo.com/", "X-Requested-With": "XMLHttpRequest", "Cookie": "SUB=xxx; SUBP=xxx" } def fetch_comments(weibo_id: str, mid: str, session: requests.Session, max_page: int = 50): base_url = "https://m.weibo.cn/comments/hotflow" params = {"id": weibo_id, "mid": mid} seen = set() for page in range(max_page): resp = session.get(base_url, params=params, headers=HEADERS, timeout=10) data = resp.json() if data.get("ok") != 1: # 常见错误码 -100 是频控,不要继续翻页 print("page", page, "failed with", data.get("ok"), data.get("msg")) time.sleep(10) continue card = data.get("data", {}) items = card.get("list", []) if not items: # 有 data 但 list 为空,表示评论流已经到底 break for c in items: cid = c.get("id") if cid and cid not in seen: seen.add(cid) line = json.dumps({ "id": cid, "text": c.get("text"), "like_count": c.get("like_count"), "created_at": c.get("created_at"), "user": (c.get("user") or {}).get("screen_name") }, ensure_ascii=False) with open("comments.jsonl", "a", encoding="utf-8") as f: f.write(line + "\n") max_id = card.get("max_id") if not max_id: break params["max_id"] = max_id time.sleep(2.5) return len(seen)

代码逻辑说明:我们把 params 对象直接交给 requests,requests 会处理好 URL 编码;每次翻页后把新的 max_id 放回 params,循环继续。seen 集合用于去重,防止同一条评论重复写文件。这里没有做异常捕获,实际长跑时要包try/except requests.RequestException,否则 SSL 抖动会让整个任务中断。

3.3 参数调整:哪些值需要改成变量

你可能发现自己的评论流第一页能返回、第二页就开始失败,多半是 max_id 没传对。较新的接口里,第一页的 data 对象不一定带 max_id,需要从评论项里的 max_id 字段读;有些实现读的是data['max_id'],两者都要兼容。字段名不统一是微博接口最常见的坑。建议第一次运行时打印data的顶层 key,再决定取值路径。

翻页字段可能的位置特征兼容写法
data.max_id顶层先取 card.get("max_id")
data.list[i].max_id挂在评论对象上取 items[-1].get("max_id")
data.since_id旧版接口请求参数用 since_id 而不是 max_id

如果三种情况都存在,最简单的方式是写一个get_next_cursor(card, items)函数,按顺序尝试上面三个位置,返回值非空即用。这样即使微博切换接口版本,改动也集中在一个函数里。

3.4 楼中楼是否要抓

多数爬取微博评论的场景只需要一层评论,但楼中楼会过滤掉回复。如果要抓,每个评论项的reply_comment_id不为 0 时,可以拿 root_id 去请求回复接口;这里容易死循环,建议不深度递归,只做两层。weibo_spider 项目里通常有专门的 reply 参数,普通分析场景一层就够。抓二层回复时,注意把根评论的 id 一并记录到每一条子评论里,否则后面做关系图时无法映射。

4. 把爬取微博评论做稳:限速、去重和异常恢复

4.1 并发不该超过 3 到 5

微博评论接口的频控会先作用于单条微博,再作用于账号。实测单账号并发超过 6 个时,hotflow 接口会随机返回-1001002,这是接口层限频,并非封号。更稳的做法是单账号并发控制在 3,每次请求的 sleep 随机在 2~4 秒;如果要跑大量微博,优先加账号池而不是加并发。用requests.Session并对每个 Session 单独配 cookie 即可,Session 会复用 TCP 连接,比每次新建 requests.get 少一截 TLS 握手开销。

4.2 断点续爬:记录 max_id 而不是记录页码

我们常犯的错误是把页码 page 当成断点,但评论流以 max_id 为准。如果第 7 页失败,重启后不需要从第 8 页开始,而是把第 6 页带回来的 max_id 作为起始参数。为了支持断点,脚本应该在每次翻页后,把 weibo_id、mid、max_id 写入一个 checkpoint 文件,覆盖式写入,只保留最新位置。下次启动时读文件,若存在就直接续爬。

def save_checkpoint(weibo_id, mid, max_id): with open("checkpoint.txt", "w", encoding="utf-8") as f: f.write(json.dumps({"weibo_id": weibo_id, "mid": mid, "max_id": max_id})) def load_checkpoint(): try: with open("checkpoint.txt", "r", encoding="utf-8") as f: return json.loads(f.read()) except FileNotFoundError: return None

上面两个函数的作用是保存和恢复游标。位置信息不依赖页码,因此进程死掉后重新拉起,最多重复最后一条评论,不会整体重来。要注意每次成功写完一条评论数据后再更新 checkpoint 文件,顺序反了会漏数。

4.3 用 SQLite 去重替代 Set

评论 id 是全局唯一的,用内存 Set 去重在进程重启后会失效。更可靠的做法是建一张 SQLite 表,以 id 为主键,INSERT OR IGNORE。单次任务几十万评论时,SQLite 比 JSONL 更适合做去重主库;JSONL 只做最终归档。下面是一段建表与写入的示例。

import sqlite3 import json conn = sqlite3.connect("weibo_comments.db") conn.execute("CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY, text TEXT, created_at TEXT, screen_name TEXT, raw TEXT)") def save_item(item): raw = json.dumps(item, ensure_ascii=False) conn.execute("INSERT OR IGNORE INTO comments VALUES (?,?,?,?,?)", (item["id"], item.get("text"), item.get("created_at"), (item.get("user") or {}).get("screen_name"), raw)) conn.commit()

参数说明:INSERT OR IGNORE意味着评论一旦插入就不会被重复记录,天然适合多轮抓取。raw 字段存整个原始对象,后面字段映射变化时不必回源接口。写入频繁时不要每条都 commit,可以先 100 条一次性 commit,能明显降低磁盘 IO。

4.4 失败需要区分:接口失败 vs 网络失败

requests 抛出的 ConnectionError 和接口返回的 ok != 1 要分开处理。前者通常不影响风控,可以退避 30 秒重试;后者如果是-100,说明短时间内请求太快,要等更久。这里建议对-100做连续 3 次退避,退避时间依次为 10、60、300 秒,超过就写死该微博并切下一条。不要遇到任何异常都直接 break,那样会把大量半截数据当作完成。排错时观察响应头里的X-Rate-Limit-*字段,有时能看到明确的剩余配额,这比盲猜 sleep 时间更有效。

5. 爬取微博评论后,按评论快照校验漏抓字段

5.1 用两次抓取对比产出快照

跑完一轮爬取微博评论不代表数据可信。最简单的验证是隔 30 分钟后重新抓取同一条微博,比较两次都存在的评论 id 数量以及新增数量。中间规则:如果第二次抓到的老评论数量少于第一次的 80%,说明翻页提前终止;如果老评论有缺失,先补 max_id 的取值逻辑。这一步也顺带检查缓存导致的假空数据,因为微博接口的 CDN 边缘节点可能缓存了上一次的响应。

5.2 清理 text 中的 HTML 标签

微博正文里的<a>与 emoji 字符,会让后续分词变得很难看。稳健的清洗方式是先替换换行和 @ 链接,再去标签,最后把 HTML 实体 unescape。注意不要过度清洗,以免直接丢 URL。这样评论里带的网页链接在情绪分析时可单独抽出来,不会被当成普通词。

import re import html def clean_text(text): text = re.sub(r"<br\s*/?>", "\n", text) text = re.sub(r"<[^>]+>", "", text) text = html.unescape(text) return text.strip()

这段用正则把<br>替换成换行,再去掉剩余标签,最后把&amp;这类实体还原。参数上不需要额外调,只提醒一点:如果评论里有视频卡片的 URL,标签去掉后链接会保留;不想让它参与情绪分析,可以在词表中过滤。

5.3 校验时间窗口与数据边界

如果你的评论快照只有 30 天迁移数据,而接口里最早评论还没到底,检查是否把 max_id 忽略了。最后一步,把评论 id 的量级和日期范围用 SQL 跑出来,范围在合理区间内,比盯着日志看到“成功”有价值。

SELECT COUNT(*), MIN(created_at), MAX(created_at) FROM comments WHERE weibo_id = ?;

如果 max(created_at) 离当前时间超过发布时间的合理窗口,说明第一次抓取时可能拿到的是缓存结果;如果 min(created_at) 比微博发布时间还早,就要检查是不是用了旧的 max_id 去续爬。这条 SQL 本身很简单,但它能把“看起来成功”和“真抓全”分开,挡住大多数翻页中断产生的数据空洞。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 1:45:29

Mermaid + VSCode:写代码画流程图的高效实战指南

上周三晚上十一点&#xff0c;我在微信群里把新项目的模块依赖图发出去&#xff0c;同事回了一句&#xff1a;“这图你是用draw.io画的吧&#xff1f;改了三次&#xff0c;git记录里全是XML diff。”那一刻我意识到&#xff0c;对写代码的人来说&#xff0c;流程图早就不是“画…

作者头像 李华
网站建设 2026/9/17 1:43:42

STM32游戏手柄实验解析:从GPIO按键扫描到USB HID移植

简介&#xff1a;基于STM32的游戏手柄开发资料包&#xff0c;面向嵌入式系统学习者与电子竞赛备赛者&#xff0c;适合希望通过完整项目掌握STM32硬件驱动、外设接口与通信协议设计的实践人群。资源为“实验28 游戏手柄实验”工程&#xff0c;采用模块化框架&#xff0c;将按键检…

作者头像 李华
网站建设 2026/9/17 1:43:17

航空EMC设计核心:DO-160G Level 5实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 1:42:42

Hygon C86 7280 UnixBench 基准测试与调优实战

去年底接手一台 Hygon C86 7280 的单路机器&#xff0c;任务很直接&#xff1a;判断它能不能扛住我们那套 Java 后端加 Redis 的组合。团队里有人主张拿 JMeter 直接压业务接口&#xff0c;我拦了一下——业务压测出来的数字里混着框架开销、GC、连接池、数据库的账&#xff0c…

作者头像 李华