☰
Python微博数据爬取实战:从接口构造到增量采集的完整拆解
2026/10/26 5:47:20 网站建设 项目流程

简介:这份资源面向具备一定Python基础、希望系统掌握微博数据采集流程的学习者与开发者,围绕转发、评论、搜索用户、关键词检索等典型场景,提供可运行的爬虫脚本与配套说明。压缩包共18个文件,约90KB,以13个Python脚本为主体,覆盖请求发送、数据解析、任务调度与配置管理等环节,另有3个Markdown文档和1个txt依赖清单,便于快速理解项目结构与运行环境。内容涉及模拟登录、反爬应对、数据存储及API调用等关键知识点,脚本按功能模块拆分,读者可对照说明逐个调试,理解从发起请求到落库的完整链路,并借鉴异常处理与代码组织思路。目前已有335人学习下载,适合作为课程设计、数据分析前期采集或爬虫练手的参考素材。

1. 微博数据采集包拆解:一份 Python 爬虫资源能落地到什么程度

手里这份Python微博数据爬取.zip,名字朴素得像随手起的,但拆开看,它解决的是一个很具体的问题:把微博上公开可见的博文、用户信息、互动数据按关键词或用户维度批量拉下来,落成结构化文件,供后续做舆情分析、账号画像或者内容选题。很多做数据分析的同行卡在第一步——不是不会写requests,而是不知道接口怎么拼、翻页怎么控、字段怎么对齐、反爬怎么绕。这份资源把这条链路打包好了,适合两类人:一是刚接触爬虫、想拿一个真实站点练手的开发者;二是做运营分析、需要定期取数的从业者,不想每次从零搭轮子。下面按「它是什么、怎么跑起来、哪里会翻车」的顺序拆,能抄的代码和参数我都标出来。

2. 采集链路拆解:从请求构造到字段落库的四个环节

2.1 请求层:接口地址与参数怎么拼

微博的公开数据入口主要分两类:一类是移动端m.weibo.cn的 JSON 接口,返回结构干净、字段齐全,是这类采集包最常用的入口;另一类是网页版 HTML,解析成本高,资源里一般只作兜底。移动端接口的典型形态是https://m.weibo.cn/api/container/getIndex,靠containerid和page两个参数驱动翻页。containerid的构造规则是理解整个采集逻辑的钥匙:搜关键词时用100103type=1&q=关键词,进某个用户主页时用107603加上用户 uid。资源里的请求封装大致长这样:

import requests HEADERS = { "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) " "AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", "Referer": "https://m.weibo.cn/", "X-Requested-With": "XMLHttpRequest", } def fetch_page(containerid, page=1, timeout=10): url = "https://m.weibo.cn/api/container/getIndex" params = {"containerid": containerid, "page": page} resp = requests.get(url, params=params, headers=HEADERS, timeout=timeout) resp.raise_for_status() return resp.json()

这段代码的关键不在requests.get本身,而在三个细节。第一,User-Agent必须伪装成移动端浏览器,桌面端 UA 请求这个接口大概率返回空cards,这是最常见的「代码没报错但一条数据都没有」的根因。第二,Referer带上m.weibo.cn,部分时段接口会校验来源,缺了它偶发 403。第三,timeout一定要设,微博接口在高峰期响应会拖到十几秒,不设超时整个采集脚本会卡死在一个请求上。参数层面,page从 1 开始递增,返回 JSON 里ok字段为 1 表示还有数据,为 0 通常意味着翻到底了或者触发了频控,这两种情况要区分对待,后面避坑章节细说。

2.2 解析层:cards 嵌套结构与字段提取

接口返回的 JSON 不是扁平列表,而是data.cards数组,每个 card 的card_type决定它装的是什么:card_type=9是博文卡片,正文在mblog.text;card_type=11是用户卡片组,里面套着card_group。解析时最容易翻车的是把cards当成博文列表直接遍历,结果一半元素取不到mblog字段。资源里的解析函数做了类型判断:

import re def parse_cards(cards): posts = [] for card in cards: if card.get("card_type") != 9: continue mblog = card.get("mblog") if not mblog: continue posts.append({ "id": mblog.get("id"), "uid": mblog.get("user", {}).get("id"), "screen_name": mblog.get("user", {}).get("screen_name"), "created_at": mblog.get("created_at"), "text": clean_html(mblog.get("text", "")), "reposts": mblog.get("reposts_count", 0), "comments": mblog.get("comments_count", 0), "attitudes": mblog.get("attitudes_count", 0), }) return posts def clean_html(raw): text = re.sub(r"<[^>]+>", "", raw) return text.replace("&nbsp;", " ").strip()

clean_html这一步不能省。微博正文里混着<a>标签、表情占位符和&nbsp;实体,直接入库会让后续的文本分析全是噪声。reposts_count这类互动字段在部分卡片里缺失,用.get(..., 0)兜底比直接下标取值稳。created_at的格式是"Sat Dec 09 12:00:00 +0800 2023"这种非标准串,落库前建议统一转成datetime,否则按时间排序会乱。字段对齐这件事,资源里给的是最小集,实际做分析时我一般还会补source(发布设备)、pic_ids(配图数量)、is_long_text(是否长文),这几个字段对判断内容质量很有用。

2.3 翻页与去重:page 递增和 id 集合

翻页逻辑看着简单,实际有两个坑。一是page到某个值后接口开始返回重复数据,微博的分页不是严格游标,同一批博文可能在相邻两页都出现;二是翻到一定深度后ok变 0,但这不是真的到底,而是触发了软频控。资源里的做法是维护一个已采集id集合,每页解析完做差集:

def crawl_keyword(keyword, max_page=50, delay=2): containerid = f"100103type=1&q={keyword}" seen, all_posts = set(), [] for page in range(1, max_page + 1): data = fetch_page(containerid, page) if data.get("ok") != 1: break posts = parse_cards(data["data"]["cards"]) new_posts = [p for p in posts if p["id"] not in seen] if not new_posts: break seen.update(p["id"] for p in new_posts) all_posts.extend(new_posts) time.sleep(delay) return all_posts

delay参数是这份资源的良心所在,默认 2 秒。我见过太多人把它改成 0 追求速度,结果跑几十页就被限流,反而更慢。max_page设 50 是保守值,关键词搜索实际能翻到的深度和关键词热度有关,冷门词可能十几页就到底,热门词翻到 50 页后数据相关性已经很低,继续翻性价比不高。new_posts为空就break这个判断很关键,它同时处理了「翻到底」和「全是重复」两种情况,避免无效请求继续消耗配额。

2.4 落库层:CSV 与 SQLite 的取舍

资源里给了两种输出方式,CSV 和 SQLite。小批量、一次性分析用 CSV 够了,pandas.DataFrame(posts).to_csv(..., encoding="utf-8-sig")一行搞定,utf-8-sig是为了 Excel 打开不乱码。但如果要做增量采集、按时间窗口反复跑,SQLite 更合适,用id做主键天然去重:

import sqlite3 def save_to_sqlite(posts, db_path="weibo.db"): conn = sqlite3.connect(db_path) conn.execute("""CREATE TABLE IF NOT EXISTS posts ( id TEXT PRIMARY KEY, uid TEXT, screen_name TEXT, created_at TEXT, text TEXT, reposts INTEGER, comments INTEGER, attitudes INTEGER)""") conn.executemany( "INSERT OR IGNORE INTO posts VALUES (?,?,?,?,?,?,?,?)", [(p["id"], p["uid"], p["screen_name"], p["created_at"], p["text"], p["reposts"], p["comments"], p["attitudes"]) for p in posts]) conn.commit() conn.close()

INSERT OR IGNORE配合主键,重复采集同一关键词时不会产生脏数据,这是增量采集的基础。字段类型上id用 TEXT 而不是 INTEGER,因为微博的博文 id 是长整型字符串,转 int 在某些环境下会丢精度。这套落库逻辑不复杂,但把「采集」和「存储」解耦之后,后面换存储引擎(比如换 MySQL)只需要改这一个函数,采集层不用动。

3. 跑通第一轮采集:环境、配置与验证步骤

3.1 依赖安装与目录结构确认

拿到压缩包先别急着跑主脚本,解压后看一眼目录。这类资源通常包含requirements.txt、主采集脚本、配置文件和一个README。依赖一般就三四个:requests、pandas、lxml(如果带 HTML 兜底解析)、fake-useragent(可选)。安装命令:

pip install -r requirements.txt

如果requirements.txt里锁了老版本requests,和你本地环境冲突,别硬装,单独建虚拟环境:

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

这一步的血泪经验是:不要用系统全局 Python 跑这类采集脚本,依赖冲突排查起来很折磨。虚拟环境隔离之后,出问题直接删掉重建,成本极低。

3.2 配置项逐个说明

配置文件里通常有几个需要改的项,我按重要性排:

配置项作用建议值说明
keyword搜索关键词按需多个词用列表,脚本一般会遍历
max_page最大翻页数20~50越大越容易触发频控
delay请求间隔(秒)2~5别低于 1.5
output输出路径相对路径确保目录存在
cookie登录态可选不带也能取公开数据,带了字段更全

cookie这一项要单独说。不带 cookie 能拿到大部分公开博文,但部分用户的博文对未登录请求返回不全,互动数据也可能缺。如果你只是做关键词层面的内容分析,不带 cookie 完全够用;要做账号维度的深度采集,再考虑补 cookie。补的时候从浏览器开发者工具里复制,注意别把整段请求头贴进去,只取Cookie字段的值。

3.3 小样本验证:先跑 3 页再放开

正式跑之前,把max_page改成 3,跑一轮看输出。验证清单就三条:输出文件有没有生成、条数是否合理(3 页大概 30~60 条)、text字段是不是干净的中文。如果条数为 0,先查 UA 和containerid;如果text里还有 HTML 标签,说明clean_html没生效,检查正则有没有被转义。小样本跑通再放开页数,这个习惯能省掉大量「跑了一小时发现字段全错」的时间。

4. 避坑与排查:五条真实翻车记录

4.1 返回 ok=0 但并非到底

现象:翻到第 8 页突然ok变 0,前面 7 页数据正常。原因:这是软频控,不是数据到底。微博对短时间高频请求会返回ok=0且cards为空,和真正翻到底的表现一模一样。解决:在break之前加一层判断,如果当前页数小于max_page的一半,先sleep30 秒再重试一次,重试仍为 0 才认定到底。我一般会记录每次ok=0时的页码,跑几次就能摸出这个关键词的频控阈值。

4.2 中文关键词 URL 编码问题

现象:关键词搜英文正常,搜中文返回空。原因:containerid里的中文没有做 URL 编码,requests的params虽然会自动编码,但containerid是拼在值里的复合串,编码行为不可控。解决:手动对关键词部分做urllib.parse.quote:

from urllib.parse import quote containerid = f"100103type=1&q={quote(keyword)}"

这个坑很隐蔽,因为不报错,只是静默返回空数据。

4.3 时间字段排序错乱

现象:采集下来的数据按created_at排序,结果 12 月的排在 1 月前面。原因:created_at是"Sat Dec 09 12:00:00 +0800 2023"这种英文月份格式,字符串排序按字母序走,Dec排在Jan前面。解决:落库前统一转换:

from datetime import datetime dt = datetime.strptime(created_at, "%a %b %d %H:%M:%S %z %Y")

转成datetime对象或 ISO 格式字符串再存,排序就正常了。

4.4 长文被截断

现象:部分博文text字段以「...」结尾,内容不完整。原因:微博长文在列表接口里只返回摘要,全文要单独请求https://m.weibo.cn/statuses/extend?id=博文id。解决:解析时判断mblog.get("isLongText")为真,再发一次请求取全文。这个补充请求频率低,不影响整体速度,但能让文本分析的数据质量上一个台阶。

4.5 输出文件 Excel 打开乱码

现象:CSV 用 Excel 打开中文全是乱码。原因:默认utf-8编码没有 BOM,Excel 识别不了。解决:写文件时用encoding="utf-8-sig"。这个坑不涉及代码逻辑,但交付给非技术同事时必踩,提前处理掉省得来回沟通。

5. 从能跑到好用:增量采集与数据质量校验

资源跑通只是起点,真正决定这份采集包能不能长期用的是两件事:增量采集和数据校验。增量采集的思路前面提过,用 SQLite 主键去重,每次只采最近 N 页,配合定时任务(cron或 Windows 计划任务)每天跑一次,delay可以设小一点因为单次请求量少。这里补一个校验函数,每次采集完跑一遍,把异常数据拦在入库前:

def validate(posts): issues = [] for p in posts: if not p["text"] or len(p["text"]) < 5: issues.append((p["id"], "text_too_short")) if p["reposts"] < 0 or p["comments"] < 0: issues.append((p["id"], "negative_count")) if not p["uid"]: issues.append((p["id"], "missing_uid")) return issues

校验结果不用阻断流程,落一份日志就行,跑一周后回头看日志,能发现哪些字段在哪些时段容易缺,反过来指导解析逻辑的调整。另一个进阶技巧是把containerid的构造抽成独立函数,关键词搜索、用户主页、话题页三种模式各一个分支,这样扩展采集维度时不用改主流程。我自己的习惯是每次改完采集逻辑,先拿一个冷门关键词跑 3 页,对比上一次的输出字段和条数,确认没有回归再放开跑。从那以后我每次动采集脚本都强制走一遍这个小样本回归,翻车次数少了一大半。希望这份拆解帮到你,资源在压缩包里,解压就能上手。

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

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

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

立即咨询