资讯动态

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

发布时间:2026/9/17 1:46:23 来源:尧图企业网站定制
简介这是一份针对微博平台定制的网络爬虫项目面向希望学习社交数据采集的爬虫开发者与数据分析人员。程序聚焦抓取微博正文与评论覆盖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: SUBxxx; SUBPxxx } 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, paramsparams, headersHEADERS, timeout10) 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_asciiFalse) with open(comments.jsonl, a, encodingutf-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 对象直接交给 requestsrequests 会处理好 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 接口会随机返回-100或1002这是接口层限频并非封号。更稳的做法是单账号并发控制在 3每次请求的 sleep 随机在 24 秒如果要跑大量微博优先加账号池而不是加并发。用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, encodingutf-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, encodingutf-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_asciiFalse) 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(rbr\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 本身很简单但它能把“看起来成功”和“真抓全”分开挡住大多数翻页中断产生的数据空洞。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价