简介:这是一份面向Python爬虫爱好者的微博数据抓取实战资源,覆盖评论、转发、用户搜索三类典型采集任务,既适合入门者理解爬虫原理,也能为开发者提供可复用的代码范例。资源提供可直接运行的脚本,以及项目说明和依赖清单,从构造请求、解析返回数据、模拟登录到应对反爬机制均有代码示例。压缩包共18个文件,以13个py脚本为主,配合3个md文档梳理思路,另附一个txt记录依赖库,整体仅90KB,轻量且易于迁移。资源按功能模块拆分,例如评论采集、转发采集、用户搜索等,适合边读代码边上手调试;同时也可借助其中异步、代理、User-Agent配置等技巧理解常见的爬虫提速与封禁规避思路,并兼顾了数据存储、异常处理和合规性提醒。目前已有335人学习下载,对于刚接触网络数据采集、希望获得完整可运行案例的开发者,是较为直接的参考资料。
1. 用Python拿微博数据:从一个真实抓取需求说起
做数据分析的朋友常找我帮忙抓数据,需求十有八九绕不开微博:某品牌活动的舆情、某个热搜话题下的评论倾向、或者盯着某几个大V的动态做追踪。刚开始我用现成的第三方库,翻车翻得相当狼狈——有的库改了签名,有的被反爬策略盯上,更常见的是返回的JSON字段和文档对不上。后来我把一套经过多次调优的爬取流程打包成了一个可以直接跑的Python项目,也就是标题里这个"Python微博数据爬取.zip"。它不是一个花架子Demo,而是能处理登录态、能解析复杂嵌套JSON、能应对基础反爬限制的完整工程。
这个项目解决的核心问题很直接:如何稳定地把微博网页端的数据结构变成干净的Excel或SQLite记录。它适合三类人:需要批量拿公开数据进行课题研究的学生、做竞品或舆情分析的产品运营、以及想快速了解微博前端加密和请求签名机制的开发者。项目整体用requests库走HTTP请求,配合必要的浏览器模拟登录,做到了不开浏览器就能持续抓取。下面我会从技术选型讲到代码核心,再到跑通它和二次改造,按一线开发拆项目的顺序来。
2. 技术栈与工程结构:一个能落地的爬虫骨架
2.1 为什么选requests而不是scrapy
这个项目最初考虑过Scrapy框架,但Scrapy的异步调度和中间件配置对新手来说是个黑匣子,一旦出现微博反爬返回的异常页面,排查链路会很深。而requests库上手快,配合session对象维持登录态,再用retry策略处理偶发失败,足以支撑中小规模的数据抓取任务。微博网页端的接口虽然是动态渲染,但很多数据其实藏在XHR请求里,直接构造Ajax请求比渲染整个页面要轻量得多。
项目里还有一个值得说道的设计:数据抓取和数据处理分离。抓取模块只负责拿响应、校验状态码、解析字段;处理模块负责清洗、去重、类型转换。这样做的好处是,一旦微博改版导致某个字段缺失,你只需要修改解析函数,不用动存储逻辑。目录结构大概是这样:
weibo_spider/ ├── main.py # 入口文件,调度各模块 ├── config.py # 所有配置参数集中地 ├── spider/ │ ├── login.py # 登录态管理,二维码或Cookie注入 │ ├── fetcher.py # HTTP请求封装,带重试和代理开关 │ └── parser.py # JSON与HTML解析,字段抽取 ├── storage/ │ ├── excel_writer.py # 输出到Excel │ └── sqlite_writer.py # 输出到SQLite ├── utils/ │ └── logger.py # 统一日志输出 └── requirements.txt2.2 配置驱动一切:config.py里的门道
爬虫最忌讳把参数写死在代码里。这个项目的config.py集中管理了所有可变项:目标用户的UID列表、抓取的时间范围、请求头的User-Agent池、每次请求的延时区间、以及是否启用代理。其中延时区间的设置是个关键细节——固定延时是最容易触发反爬的写法,项目里用了random.randint(3, 7)这样的波动延时,模拟真实操作节奏。
# config.py 核心配置段 REQUEST_HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...", "Referer": "https://weibo.com/", "Accept": "application/json, text/plain, */*", } TIME_SLEEP_RANGE = (3, 7) # 请求间隔秒数,随机取值 MAX_RETRY = 3 # 单次请求失败重试次数 PROXY_ENABLED = False # 如需代理池,置为True并配置代理列表 DB_PATH = "weibo_data.db" # SQLite输出文件路径 EXCEL_PATH = "weibo_data.xlsx" # Excel输出文件路径这里有三个参数对实际运行影响最大:PROXY_ENABLED决定你是否走代理池,很多办公网络直接连微博接口会被限制;TIME_SLEEP_RANGE控制抓取频率,调得太小容易被封;MAX_RETRY则决定了网络抖动时任务能不能自愈。我一般会把重试逻辑写成指数退避,第一次失败等2秒,第二次等4秒,第三次等8秒,连续失败超过三次就写入日志并跳过当前目标。
3. 核心模块拆解:一个请求从进入到返回的完整链路
3.1 登录态管理:Cookie注入与二维码扫码的取舍
微博的数据接口大部分需要登录Cookie,特别是访问用户主页的完整微博列表。项目提供两种登录方式:手动Cookie注入和二维码扫码。手动Cookie适合服务器环境——你从浏览器里复制Cookie粘贴到配置文件中;二维码扫码适合本地开发机——程序调用系统浏览器展示二维码,扫码后自动截取Cookie。
# spider/login.py 中二维码登录的核心片段 import qrcode from selenium import webdriver def login_by_qrcode(): driver = webdriver.Chrome() driver.get("https://passport.weibo.com/signin/login") # 等待页面加载出二维码图片元素 qr_img = driver.find_element("css selector", "img[src*='qr_code']") qr_path = "qrcode.png" qr_img.screenshot(qr_path) # 本地终端打印出二维码(使用qrcode库从图片内容重新生成) qr_data = driver.execute_script("return document.querySelector('img[class*=code]').src") qr = qrcode.QRCode() qr.add_data(qr_data) qr.print_ascii() # 轮询等待登录成功后的Cookie for _ in range(60): cookies = driver.get_cookies() if any(c['name'] == 'SUB' for c in cookies): return {c['name']: c['value'] for c in cookies} time.sleep(2) raise TimeoutError("二维码登录超时")这段逻辑里值得说明的是:二维码图片本身可以从页面元素截图获得,但更稳妥的方式是直接读取图片的URL地址再转成终端可打印的ASCII码。SUB这个Cookie是微博登录态的核心凭证,只要它没过期,后续请求都能带上。如果你不想用selenium,也可以手动登录后从浏览器开发者工具里复制Cookie,放到config.py的COOKIE_STRING变量里。
3.2 请求封装与JSON解析:避开高频封禁的写法
爬虫的命门在请求层。fetcher.py里对requests.Session做了一个薄封装,统一处理超时、重试、异常捕获。一个常见坑是:requests的get方法默认不会抛出异常,而是把错误状态码放在resp.status_code里。如果忽略这一步,后续解析拿到的就是空数据或登录跳转页。
# spider/fetcher.py 请求重试与状态校验 import time import requests from config import REQUEST_HEADERS, MAX_RETRY, TIME_SLEEP_RANGE class Fetcher: def __init__(self, cookies=None): self.session = requests.Session() self.session.headers.update(REQUEST_HEADERS) if cookies: self.session.cookies.update(cookies) def get_json(self, url, params=None): for attempt in range(MAX_RETRY): try: resp = self.session.get(url, params=params, timeout=10) if resp.status_code == 200: return resp.json() # 418或414说明被限流,等待更长时间 if resp.status_code in (418, 414): time.sleep(30) except requests.RequestException as e: print(f"第{attempt + 1}次请求失败: {e}") time.sleep(TIME_SLEEP_RANGE[0] + (TIME_SLEEP_RANGE[1] - TIME_SLEEP_RANGE[0]) * attempt) return None这里把418和414单独列出来是有原因的,微博在识别到异常高频访问时,不会直接返回403,而是给一个418(I'm a teapot)的玩笑状态码。如果你在日志里频繁看到这个状态码,说明IP已经被标记,光靠降速还不够。常见的做法是等一段时间或切换代理IP。
解析层的核心是定位到微博接口返回的JSON结构。用户主页的时间线数据通常藏在data.list里,每条微博有id、text_raw(正文)、created_at(发布时间)、reposts_count(转发数)等字段。需要特别注意的是created_at是"刚刚"、"5分钟前"这类相对时间,项目里会统一转换成绝对时间戳。
# spider/parser.py 微博字段提取与时间归一化 import re import datetime def parse_weibo_item(item): text_raw = item.get("text_raw", "") or "" # 去除HTML标签和 等实体 clean_text = re.sub(r"<[^>]+>", "", text_raw).replace(" ", "") # 相对时间转绝对时间 created_at = item.get("created_at", "") if created_at.endswith("分钟前"): minutes = int(created_at.replace("分钟前", "")) publish_time = datetime.datetime.now() - datetime.timedelta(minutes=minutes) elif created_at.endswith("小时前"): hours = int(created_at.replace("小时前", "")) publish_time = datetime.datetime.now() - datetime.timedelta(hours=hours) else: # 形如"2024-01-15 10:30"的绝对时间 publish_time = datetime.datetime.strptime(created_at, "%Y-%m-%d %H:%M") return { "weibo_id": item.get("id"), "content": clean_text, "publish_time": publish_time.strftime("%Y-%m-%d %H:%M:%S"), "reposts": item.get("reposts_count"), "comments": item.get("comments_count"), "likes": item.get("attitudes_count"), "source": item.get("source", ""), "user_name": item.get("user", {}).get("screen_name", ""), }这段代码有三个细节决定了抓取质量:第一,text_raw字段如果不存在就默认空字符串,避免后续拼接时报错;第二,相对时间转换依赖本地服务器时间,如果服务器时区不是北京时间会出错,我一般在部署服务器上先执行timedatectl set-timezone Asia/Shanghai做校准;第三,user字段可能为None,如果某个微博被删除或用户私密化,这个字段会缺失,所以用了item.get("user", {})来防御。
3.3 翻页逻辑:游标还是页码?微博接口的特殊性
微博网页端的翻页不是简单的page=2换page=3,而是基于since_id游标。如果你直接改page参数,最多翻几页就会被重定向到登录页。项目里实现了一个迭代器风格的取数函数,利用上一页返回的since_id拼接下一页的请求。
def fetch_all_weibos(fetcher, uid, max_pages=50): url = "https://weibo.com/ajax/statuses/mymblog" since_id = "" page_count = 0 while page_count < max_pages: params = {"uid": uid, "since_id": since_id} data = fetcher.get_json(url, params=params) if not data or not data.get("data"): break since_id = data["data"].get("since_id", "") for item in data["data"].get("list", []): yield parse_weibo_item(item) page_count += 1 if not since_id: break注意这个接口的uid参数必须和目标用户的uid一致,而不是你自己的登录uid。如果返回的data为空,通常是Cookie过期或uid拼写错误。我在试跑时遇到过一个很隐蔽的问题:某个账号设置了仅半年可见,翻到半年前的微博时接口返回的list为空,但since_id依然有值,导致死循环。解决方案是记录连续空页次数,超过3次就主动终止。
4. 从零跑通:环境配置、依赖安装和第一次执行
4.1 环境准备与requirements.txt
这个项目对Python版本要求不苛刻,3.8到3.11都能跑。建议先建一个虚拟环境,避免污染全局Python。我的习惯是用venv而不是conda,因为venv创建更轻量,且项目依赖量不大。
python -m venv weibo_env source weibo_env/bin/activate # Windows下执行 weibo_env\Scripts\activate pip install -r requirements.txtrequirements.txt里的核心依赖有:requests、selenium(用于模拟登录)、qrcode(终端二维码)、pandas(数据清洗)、openpyxl(Excel输出)。如果你不需要图形界面登录,可以注释掉selenium和qrcode这两行,项目依然能靠手动Cookie方式正常运行。这也是项目设计上的一个弹性:依赖尽量按模块拆分。
4.2 首次运行配置清单
运行前必须确认三件事:当前网络能否直连微博域名、目标用户的UID是否填写正确、Cookie是否已生效。你可以先用一段简单的验证脚本测试Cookie:
# quick_test.py import requests from config import REQUEST_HEADERS, COOKIE_STRING session = requests.Session() session.headers.update(REQUEST_HEADERS) # COOKIE_STRING形如 "SUB=xxx; SUHB=yyy" session.cookies.update( dict(item.split("=", 1) for item in COOKIE_STRING.split("; ") if "=" in item) ) resp = session.get("https://weibo.com/ajax/statuses/mymblog", params={"uid": "你的目标UID", "since_id": ""}) print(resp.status_code) print(resp.text[:500])如果返回的JSON里包含"data"且"list"是数组,说明Cookie和UID配置都没问题。如果返回的是登录页面HTML或者"pincode"相关字段,说明Cookie已失效。我写过最多的一次坑就是:从浏览器复制的Cookie里包含了逗号分隔的多个Set-Cookie片段,直接用分号分割会漏掉一部分,导致登录态不完整。
4.3 数据入库与导出
项目默认把抓取结果同时写入SQLite和Excel。SQLite适合后续用SQL查询聚合数据,Excel方便业务人员直接打开看。在main.py里,你可以通过命令行参数控制输出方式:
python main.py --uid 1234567890 --output sqlite python main.py --uid 1234567890 --output excel python main.py --uid 1234567890 --output bothExcel导出用pandas的to_excel方法,需要注意的一点是微博正文里可能包含特殊字符如换行符、制表符,在写入Excel前要替换成空格,否则生成的表格格式会错乱。SQLite写入则用executemany批量插入,避免逐条插入导致的性能瓶颈。
5. 避坑指南:微博爬虫的五个经典问题
5.1 状态码200但数据为空
现象:接口返回HTTP 200,body里的JSON结构完整,但data.list为空数组。
原因:这类情况多半是Cookie过期但未触发重定向,微博静默返回空数据。还有一种可能是用户设置了内容可见范围(如仅粉丝可见)。
解决:先用quick_test.py验证Cookie状态,如果Cookie失效就重新扫码登录;如果Cookie正常,换一个公开账号测试。如果目标账号本身就限制了可见范围,那属于数据源问题,不是爬虫问题。
5.2 到了某个页数就被重定向到passport登录页
现象:前10页正常,第11页开始拿到的不是JSON而是一段HTML脚本,且包含"passport"字样。
原因:微博对单次会话的请求页数有频率限制,短时间翻太多页触发了风控。
解决:在翻页循环中加入自适应延时,每翻5页后休息30秒。如果还是被重定向,最稳妥的方案是暂停10分钟,让IP冷却。这个坑让我养成了一个习惯:高耗时抓取任务一定要中间状态落盘,别把结果只存在内存里。
5.3 微博正文里的HTML实体没有完全清除
现象:解析出来的正文包含"、&等字符,或者半角标签<br />变成了文字。
原因:微博的text_raw字段保留部分HTML标签,但另有text字段是纯文本。如果只取text_raw,需要更全面的清洗正则。
解决:建议优先使用text字段,它已经是去标签的纯文本;如果某些字段为空,再回退到text_raw清洗。清洗时先用html.unescape()处理实体字符,再移除残留标签。
5.4 相对时间转换偏差导致排序错乱
现象:同一时刻抓到的微博,有的显示"刚刚",有的显示"3分钟前",转成绝对时间后部分记录比实际时间晚了几分钟。
原因:服务器时间和本地时间存在毫秒级偏差,"刚刚"被归零处理,实际可能差几十秒。
解决:项目里保留了两个时间字段——raw_time用于展示原始字符串,publish_time用于排序。如果对时间精度要求高,可以改用接口返回的created_at原始时间戳(毫秒级),但这个字段经常被隐藏,需要额外字段映射。
5.5 多线程加速导致IP直接被封
现象:用ThreadPoolExecutor并发请求,跑了不到100条就被微博封禁,所有接口返回418。
原因:微博的风控系统对同一IP的并发连接数有严格限制,即使设置了延时,并发请求也会被识别为异常。
解决:单线程加随机延时是最保守但最稳的方式。如果一定要加速,要么接入代理池轮换IP,要么改用服务端API(如果有授权)。我的经验是:抓取1万条以下数据,单线程的耗时完全可以接受,没必要冒险上并发。
6. 二次开发思路:让这个爬虫变成你的数据管线
6.1 从单用户抓取扩展成关键词搜索
项目目前以抓取指定用户的时间线为主,但很多场景需要按关键词搜索匹配微博。这需要一个搜索接口的适配。搜索接口的路径是https://weibo.com/ajax/statuses/search,参数从uid换成keyword,分页逻辑同样是since_id。你可以参考已有的fetch_all_weibos函数,新增一个fetch_search_results方法。
def fetch_search_results(fetcher, keyword, max_pages=30): url = "https://weibo.com/ajax/statuses/search" since_id = "" for page in range(max_pages): params = {"keyword": keyword, "since_id": since_id} data = fetcher.get_json(url, params=params) if not data or not data.get("data"): break since_id = data["data"].get("since_id", "") for item in data["data"].get("list", []): yield parse_weibo_item(item)一个需要注意的地方:搜索接口返回的微博可能来自不同用户,因此解析函数里user_name字段不要依赖固定UID。如果后续想做舆情分析,可以把这个函数和Excel导出串联,让搜索结果直接生成带关键词的CSV文件,方便导入可视化工具。
6.2 定时增量抓取与断点续跑
爬虫跑久了最怕中断后全部重来。我给项目加了一个简单的时间水位功能:每次入库前记录MAX(publish_time),下次抓取时只取大于这个时间戳的新微博。这个方案有精度限制,因为微博接口不支持按时间过滤,只能翻页后过滤,但至少能减少重复抓取。SQLite里建一个metadata表存上次抓取位置,比读写本地文件更可靠。
def get_last_sync_time(conn): cur = conn.execute("SELECT value FROM metadata WHERE key='last_sync'") row = cur.fetchone() return row[0] if row else "1970-01-01 00:00:00"增量逻辑放在main.py的调度器里,每次抓完一轮就执行UPDATE metadata SET value=... WHERE key='last_sync'。如果你的目标是持续追踪某个话题的热度变化,建议把调度器接到系统crontab上,每小时跑一次。从那以后我每次搭数据爬虫都会强制过一遍这个设计:能不能断点续跑?中间状态有没有落盘?如果数据量到十万级,数据库索引和去重策略也要提前想好。
6.3 扩展反爬应对能力
如果遇到更严格的封禁策略,可以从两个方向改造。第一是升级到代理池,在fetcher里随机挑选代理IP,并定期测试代理可用性。第二是引入浏览器指纹扰动,每次请求随机调整User-Agent和Accept-Language,但这会增加请求失败的不确定性。我觉得对个人开发者而言,与其和风控对抗,不如降低抓取频率、优化数据质量。毕竟爬虫的价值在于稳定拿到干净的数据,而不是挑战平台的防御边界。
说回实际场景,我最近用这个项目抓了一批竞品账号,分析出他们一周内发布互动最高的三类内容方向,配合简单的情感分析,给运营团队提供了很有价值的参考。希望你拿到这个项目后,也能根据自己的需求做点有意思的事。希望帮到你。
本文还有配套的精品资源,点击获取