☰
Python爬取微博数据:移动端接口、Cookie登录与游标翻页全解析
2026/10/21 13:17:24 网站建设 项目流程

简介:面向Python爬虫学习者和有微博数据采集需求的开发者,这份资源围绕微博平台信息自动化获取这一核心场景,系统梳理了网络请求库、HTML与JSON解析、模拟登录、反爬机制应对、数据持久化等关键知识点,并给出了从配置、抓取到入库的完整工程思路。压缩包共包含18个文件,主体为13个Python脚本,分别承担微博转发、评论抓取、用户搜索、任务创建和数据库检索等功能;配套3个Markdown说明文档,用于解释各脚本用途、请求构造方式和爬取流程;另含一个依赖清单txt文件和一个子压缩包,便于安装依赖与查阅项目说明。整个压缩包仅90KB,结构紧凑、代码量小,非常适合逐行研读与二次开发。内容还涉及多线程、异常处理以及数据库交互等实用技巧,可帮助使用者规避常见反爬限制。已有335人学习访问,适合具备一定Python基础、希望进阶掌握爬虫工程化及微博数据获取技巧的开发者。

1. 微博数据爬取,先解决「拿到能分析的数据」这件事

去年帮某公司做舆情分析,运营给了 30 个微博用户 ID,要求把微博正文、发布时间、转发评论点赞数全部导出来。我第一版脚本直接抓网页版 HTML,存下来打开全是 JS 渲染后的半成品,根本没法做分词。后来重构成了这个 Python 项目:用 requests 调微博移动端 JSON 接口,Cookie 维持登录态,按用户主页游标翻页,BeautifulSoup 清洗文本,最后统一落成 CSV 和 JSON。适合需要批量采集微博文本做词频、情感分析、传播指标统计的开发者,也适合想研究移动端接口分页和反爬策略的人。

2. 架构与技术选型:requests 同步抓取为主,浏览器渲染只兜底

2.1 移动端接口才是主路径

先交代结论:这个项目的核心数据源是 m.weibo.cn 那套 JSON 接口,不是桌面版 HTML。原因很直接——网页版是服务端渲染加大量 JS 动态加载,直接用 requests 拿回来的 HTML 里,正文经常被截断,评论区是异步加载,翻页要分析一堆参数。移动端接口返回结构化 JSON,每个用户主页围绕 cards 数组展开,字段覆盖稳定,解析成本低。

我一开始也试着抓桌面版 HTML,两周后彻底放弃。前端改版一次,CSS 选择器就要重写一轮,正文里的「展开全文」在 HTML 里是拆开的,拼回去很容易漏字。移动端接口里 mblog.text 会把整条正文一次性给出来,最多多一些 HTML 标签和表情占位,清洗规则相对固定。如果你的采集量在百万条以内,单机同步请求完全够用,不需要一上来就上 Scrapy 分布式;把请求频率控制好,比引入一套调度框架更省事。

我也测试过 Selenium 无头浏览器方案,结论是只适合做兜底。浏览器渲染路径要等待各种资源加载,500 条数据能跑十几分钟,而且内存占用会随页面积累持续上涨。requests 版本同样数据量两分钟内完成,差距在数量级上。这个项目里 Selenium 只出现在 login_helper.py 的扫码登录兜底中,主体采集绝不走浏览器。

2.2 目录结构与模块分工

项目解压后是典型的「请求、解析、存储」三层结构:

weibo_crawler/ ├── config.py # 请求头、Cookie、目标用户ID、间隔秒数 ├── login_helper.py # 扫码登录辅助,自动刷新Cookie ├── fetcher.py # 请求层:发请求、重试、翻页 ├── parser.py # 解析层:cards -> 普通dict ├── storage.py # 存储层:CSV / JSON 落盘 ├── main.py # 入口:串起全流程,打印进度 ├── requirements.txt └── output/ # 运行结果输出目录

config.py 放所有会变的变量,包括 User-Agent、Cookie 占位、目标用户 ID 列表、两页之间的延时。fetcher.py 只做一件事:把请求拿回来,状态码不对就重试,JSON 解析失败就记录日志,这一层不关心数据长什么样。parser.py 反过来,只做字段提取,不关心请求细节。storage.py 是最后一道工序,统一处理编码和字段顺序。

这样拆的核心原因是换数据源时不用四处打补丁。比如要把「按用户 ID 爬时间线」改成「按关键字爬搜索结果」,只需要在 fetcher.py 里换 URL 和参数,parser 和 storage 基本不用动。main.py 用 argparse 解析参数,默认按 fetch -> parse -> save 的顺序执行,每完成一个阶段打印一行进度日志,方便排查哪一步出错。

2.3 落盘格式:CSV 给运营,JSON 给分析

项目默认写两个文件:output/weibo_年月日_时分秒.csv 和同名 .json。CSV 用 utf-8-sig 编码,直接用 Excel 打开不乱码;JSON 保留原始字段,方便后续接 jieba 分词或者情感分析模型。每行数据字段如下:

字段类型说明
idstr微博ID,可用来去重
author_namestr作者昵称
created_atstr发布时间,原始格式
text_cleanstr清洗后的纯文本
reposts_countint转发数
comments_countint评论数
attitudes_countint点赞数

字段顺序固定,保证多次运行导出的 CSV 表头一致。如果要接数据库,storage.py 里加一个 to_mysql 函数,用 pandas 的 to_sql 三行就能写完:df.to_sql("weibo_posts", engine, if_exists="append", index=False)。注意两点,一是 created_at 建议先转成字符串再入库,否则 pandas 会自动推断成 datetime 类型,可能产生时区偏移;二是 text_clean 要确认长度上限,微博最长几万字,数据库字段别设成 VARCHAR(255)。

3. 核心链路拆解:Cookie、游标翻页、HTML 清洗三段代码

3.1 Cookie 登录与请求头装饰

微博不登录能看数据,但接口会明显限流,翻几页就返回错误码。项目里登录态收敛在 config.py,运行前手动从浏览器复制 Cookie 填入。请求头用移动端 iPhone UA,配合 Referer 指向 m.weibo.cn:

# config.py WEIBO_HEADERS = { "User-Agent": ( "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) " "AppleWebKit/605.1.15 (KHTML, like Gecko) " "Mobile/15E148 MicroMessenger/8.0.49" ), "Referer": "https://m.weibo.cn/", "X-Requested-With": "XMLHttpRequest", } # 从浏览器登录后复制,注意是 m.weibo.cn 域名下的 Cookie COOKIE = "SUB=xxx; SUBP=xxx; WBUBI=xxx"

发请求时用字典展开把 Cookie 拼进去:requests.get(url, headers={**WEIBO_HEADERS, "Cookie": COOKIE})。这里的 COOKIE 叫什么名字不重要,重要的是必须来自 m.weibo.cn 域。接口校验的是 Cookie 里 SUB 字段的签名和过期时间,如果你复制的是 www.weibo.cn 的 Cookie,请求头带过去了但接口不认,返回的 ok 字段永远是 0,这类问题排查起来挺费时间。

Cookie 有效期从几小时到几天不等,取决于账号活跃度。项目里的 login_helper.py 是备用方案:请求二维码图片地址,60 秒内用手机微博扫码,轮询等待回调拿到新 Cookie 后自动写回 config.py。这个功能当时是被运营同学「帮我再导出一次」逼出来的,因为手动复制一次要两分钟,扫码自动化后每次采集前能省不少等待时间。

3.2 时间线翻页:since_id 才是真正的页码

移动端接口的翻页不是 page=1、page=2 这种数字页码,而是游标式设计:每一页响应里带一个 since_id,把这个值传给下一页,服务端才知道「接着上次的位置继续给」。如果自己维护数字页码,翻到第 5 页左右接口就开始返回空 cards,数据直接断掉。核心请求代码如下:

# fetcher.py import requests import time def fetch_user_timeline(uid, container_id, since_id="", max_pages=100): results = [] for _ in range(max_pages): params = { "type": "uid", "value": uid, "containerid": container_id, "since_id": since_id, } resp = requests.get( "https://m.weibo.cn/api/container/getIndex", params=params, headers={**WEIBO_HEADERS, "Cookie": COOKIE}, timeout=10, ) data = resp.json() if data.get("ok") != 1: break cards = data["data"]["cards"] if not cards: break results.extend(cards) since_id = data["data"].get("since_id", "") if not since_id: break time.sleep(1.5) # 防止请求过快被限流 return results

参数里 container_id 是用户主页的分类容器 ID,常见格式是 107603 加用户 ID 拼接而成,比如 1076031234567890,它决定了接口返回的是「全部微博」还是「某一分类」。since_id 首次为空字符串,服务端返回第一页;之后每轮把上一轮响应里的 since_id 回传,形成游标。time.sleep(1.5) 是项目里默认的节流值,如果只爬几百条改成 1 秒问题不大,爬几万条建议调到 2 秒以上,这是被风控教育出来的经验值。

3.3 文本清洗与 CSV 落盘

mblog.text 字段不是纯文本,里面混着<br />、<a href="/n/xxx">@用户名</a>、<span class="url-icon">这类 HTML 片段,以及表情图片的占位。直接用正则抠容易把 @ 人和表情搞碎,项目里用 BeautifulSoup 统一清洗:

# parser.py from bs4 import BeautifulSoup def clean_html(html_text): if not html_text: return "" soup = BeautifulSoup(html_text, "html.parser") return soup.get_text(" ", strip=True) def parse_mblog(card): if card.get("card_type") != 9: return None mblog = card["mblog"] return { "id": mblog["id"], "author_name": mblog["user"]["screen_name"], "created_at": mblog["created_at"], "text_clean": clean_html(mblog["text"]), "reposts_count": mblog["reposts_count"], "comments_count": mblog["comments_count"], "attitudes_count": mblog["attitudes_count"], }

clean_html 里的 get_text(" ", strip=True) 会把标签全部剥离,标签之间的空白用单个空格拼接,这样 @人、话题词、正文之间不会粘连。card_type 的判断很关键,用户时间线里每页 10 到 20 张卡片,除了微博正文卡片(card_type=9),还有「header」「推荐关注」这些杂项卡片,不过滤掉会把脏数据带进 CSV。

存储层用 pandas 的 DataFrame 直接落盘:

# storage.py import pandas as pd def save_csv(rows, path): df = pd.DataFrame(rows) df.to_csv(path, index=False, encoding="utf-8-sig")

编码用 utf-8-sig 而不是 utf-8,是因为 Excel 打开无 BOM 的 UTF-8 文件时默认按 GBK 解码,中文会全部乱掉。utf-8-sig 会在文件头写入 BOM 标记,Excel 识别正确编码,这是爬虫项目里的老常识,但每次新手接手都会翻一次车。

4. 从 zip 到跑通:安装依赖、配置 Cookie、验证数据

4.1 环境准备与依赖安装

项目不挑系统,Windows、macOS、Linux 都能跑,前提是 Python 版本 3.8 以上。解压后在项目根目录执行:

cd weibo_crawler python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt

requirements.txt 里主要就是三样:requests、beautifulsoup4、pandas。pandas 是本项目里最重的依赖,如果不想为几万行数据装它,也可以改成 csv 标准库自己写 writer,但 pandas 的 to_sql 和 DataFrame 清洗在二次开发时太方便了,我建议保留。

安装时如果在墙内遇到超时,比较顺手的方式是换 pip 镜像源,比如 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt,这个细节在实际部署时经常能省下十分钟。装完可以用 python -c "import requests, bs4, pandas" 验证三个包都能正常导入,再进入下一步配置。

4.2 配置 Cookie 和目标用户

打开 config.py 做两件事。第一,把 COOKIE 换成自己账号登录后的值:打开 m.weibo.cn 页面,登录后在浏览器开发者工具的 Application 面板里找到对应域名下的 Cookie,整段复制出来填进去。第二,把 USER_IDS 数组改成你要爬的用户数字 ID,一个 ID 一行,用逗号分隔。

注意:不要在隐私模式窗口里复制 Cookie,那个窗口一旦关闭 Cookie 立即失效。取 Cookie 的浏览器标签页要保持登录状态,直到采集跑完。

如果嫌手动复制麻烦,可以在登录状态下运行 python login_helper.py,脚本会打印一个二维码图片路径,用手机微博扫码后自动把新 Cookie 写回 config.py。这个辅助脚本不是主路径,但适合要长期、多次采集的场景,比自己反复打开开发者工具省事。

4.3 入口参数与运行日志

main.py 用 argparse 接收参数,最常见的运行方式:

python main.py --users 1234567890 9876543210 --max-pages 30 --output output/

这会按顺序爬两个用户的时间线,每个用户最多翻 30 页,结果输出到 output 目录。运行过程中会看到类似这样的进度日志:

[fetch] user=1234567890 page=1 got 12 cards, since_id=2025011012 [fetch] user=1234567890 page=2 got 10 cards, since_id=2025011009 [fetch] user=9876543210 page=1 got 0 cards, stopped [save] output/weibo_20250110_143022.csv (22 rows)

日志里有两个关键信号:got 后面的数字是本页实际拿到的卡片数,stopped 表示接口返回了非 1 的 ok 值或者空 cards。遇到某个用户 0 cards 立即停是正常的,说明这个用户设置了「仅展示半年内微博」或者当前 Cookie 对这个用户不可见,不是脚本 bug。

4.4 数据验证:抽样对比最稳

跑完之后别急着交付数据,至少要验证三件事。第一,CSV 行数跟网页端该用户的微博总数对得上;第二,随机抽 3 条微博去微博搜索页对比转发数和点赞数,确认字段映射没有错位;第三,检查 text_clean 列有没有明显的乱码或空行。如果第三条有问题,多半是清洗逻辑漏了某种卡片类型,回 parser.py 调整 card_type 判断。

验证这步我建议写进交付流程而不是靠肉眼扫。哪怕是给自己用,把 CSV 读进来跑一遍合法性检查——id 是否重复、时间字段能否解析、三个 count 字段是否为正整数——也比事后拿着表格被运营追着问要舒服。

5. 避坑记录:微博采集最容易翻车的 5 个点

5.1 第一页就返回 ok: 0,提示需要登录

现象:运行 main.py 后第一条请求就失败,日志里所有用户都是 got 0 cards,或者返回的 JSON 里出现登录相关提示。

原因:Cookie 没填对或已过期。最常见的填错方式是复制了 www.weibo.cn 的 Cookie 而不是 m.weibo.cn 的,移动端接口校验的是 m 域下的登录态签名。

解决:重新打开 m.weibo.cn 登录,确认地址栏域名确实是 m.weibo.cn,再从开发者工具里复制整段 Cookie。复制完先跑一个单用户单页的小命令验证:python main.py --users 一个ID --max-pages 1,通了再放量。

5.2 翻到第 5 页就开始重复数据

现象:前面几页数据正常,后面每页返回的微博 ID 跟第一页高度重合,CSV 里全是重复 id。

原因:用了 page 参数自增翻页,而移动端接口的翻页游标是 since_id。当接口不认 page 时,它默认返回第一页,表现就是继续翻页但内容重复。另一种情况是上一次的 since_id 在服务端已经查不到对应位置,游标回退到了首页。

解决:严格使用响应体里的 data.since_id 作为下一页参数,如果返回的 since_id 为空或与上一轮相同,立即停止。采集完成后对 id 列做一次去重统计,重复率超过 5% 就要回头检查游标逻辑。

5.3 文本清洗后 @人 和话题词是碎的

现象:text_clean 里「@用户名」只剩一半,话题#xxx#中间被插入空格,有的行直接是空字符串。

原因:BeautifulSoup 的 get_text(" ", strip=True) 用空拼接标签内文本,但微博正文里 @人 和话题的 HTML 是嵌套 span,空格拼接位置恰好落在标签边界上,导致词被切断。另外长微博处于「全文」模式时,mblog 里有 isLongText 字段,正文被拆在 text 和 longText 两个字段里,只清洗 text 会丢内容。

解决:parser.py 里对 isLongText 为 1 的卡片改取 mblog.longText 再清洗;清洗时先合并标签内文本再去多余空白。调试时可以随机挑 20 条文本打印断词位置,再决定分隔策略。

5.4 生成 CSV 后 Excel 打开全是乱码

现象:CSV 文件用 VS Code 看正常中文,用 Excel 双击打开后变成不可读字符。

原因:默认编码用了 utf-8 但没有 BOM,Excel 在简体中文系统下按 GBK 解码,中文全部错位。这个坑在 macOS Numbers 下不明显,但公司里用 Windows Excel 的人一开就炸。

解决:统一用 encoding="utf-8-sig" 写 CSV。如果已有历史文件不想重新爬,用 pandas 读进来重新另存为 utf-8-sig 编码,几秒钟处理完。以后每次交付 CSV 之前都确认一次文件头有没有 BOM 标记。

5.5 请求频率一高,整段 IP 被临时限制

现象:持续爬了十几分钟后,请求开始返回 418 或 503,过一会儿浏览器访问微博也提示异常。

原因:单 IP 请求频率太高,服务端做了 IP 级别限流。每页 sleep 1 秒看起来不慢,但加上解析和落盘时间,请求密度仍然偏高,多用户时间线连爬时最容易触发。

解决:把请求间隔提高到 2 到 3 秒,每个用户之间额外休息 5 到 10 秒。重试逻辑放在 fetcher.py:遇到 418 先 sleep 60 秒再重试一次,再失败就跳过这个用户,不要死磕。长任务每完成一个用户打印一次计数,观察整体速度而不是盯着单页。

6. 二次开发:改成命令行参数化工具,加断点续爬兜底

想真正把这个项目当工具用,第一件事是把用户 ID 从配置文件改成命令行多值参数,再把输出目录、最大页数全部参数化,这样才能交给定时任务反复调用。main.py 里大致这样扩展:

import argparse parser = argparse.ArgumentParser() parser.add_argument("--users", nargs="+", required=True) parser.add_argument("--max-pages", type=int, default=50) parser.add_argument("--output-dir", default="output") args = parser.parse_args()

第二件事是加断点续爬。for 循环里每成功翻完一页,就把当前用户 ID 和 since_id 写进 checkpoint.json;下次启动时检测到该用户的 checkpoint,直接从上一次的 since_id 继续,不用从头爬。增量采集同理,每天定时跑一次,只爬最近一天的新微博,然后 append 到历史 CSV。这个能力对舆情监控几乎是刚需,因为隔几天全量重爬,转发评论数已经变了,历史数据就不准了。

验证修改是否可靠,我的做法是跑一个用户最近两页数据,先正常跑一遍,再加 checkpoint 并在第一页后手动中断,重新启动,对比两次输出的 id 集合是否连续。若中间有缺失,就检查 checkpoint 写入的时机是否在 break 之前。那天我把写入位置写错了,重启后每次漏一页数据,排查了半个小时才意识到是时机问题。从那以后我每次加状态恢复功能,都强制先写一遍「中断再续跑」的验证用例。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询