微博数据采集实战:Cookie、containerid与SQLite落库全流程解析
简介Python微博数据爬取.zip 是一套面向 Python 爬虫开发者与初学者的微博数据采集实战资源聚焦从请求构造、HTML/JSON 解析到模拟登录、反爬应对、数据存储的完整爬虫链路。压缩包共 18 个文件整体约 90KB其中 13 个 Python 脚本构成核心代码辅以 3 个 Markdown 说明文档、1 个配置文件及 1 个项目说明压缩包目录结构便于按转发、评论、用户搜索等功能模块查阅。代码中不仅包含微博转发、评论、用户搜索等采集功能还围绕模拟登录、Cookie 维持、反爬应对与数据存储给出了可运行方案并设计了任务创建、数据库查询、配置管理等辅助模块能够帮助读者理解任务调度、参数管理与异常处理思路随附的说明文档与依赖配置可支持快速搭建环境并对照运行。目前已有 335 人学习/下载适合希望在实际项目中掌握微博数据采集、或系统巩固爬虫工程化能力的初中级开发者。1. 从登录态到数据落库这个微博脚本包替你踩平了最陡的那段坡做微博数据采集的人有一个共识登录态、参数构造、字段解析这三关任何一关过不去数据都跑不出来。这个Python微博数据爬取压缩包把这三关压缩成了三件具体的事填Cookie、填containerid、读SQLite。它解决的是从微博搜索页批量抓取正文与转评赞数据的完整链路适合正在做舆情样本采集、简历项目演示或行业数据观察的开发者。如果你只想抓几十条数据验证想法它偏重如果你正准备从零写采集器它正好当脚手架。接下来我会按选型、跑通、避坑、长期化的顺序把这个包拆给你看。2. 先把路线选对公开API、网页版JSON、移动端接口三条路为何最终选了网页版2.1 三条采集路线的实际成本对比做微博采集第一件事不是写代码而是选接口。不同路线对应不同的维护成本这决定你未来是每周花十分钟维护脚本还是每天花两小时补参数。采集路线登录成本数据完整性维护难度适合场景公开API需要企业认证字段裁剪明显低机构内部长期任务网页版JSON手动Cookie字段完整含转评赞中个人与小组采集移动端接口需处理签名字段最全高专门做客户端逆向的团队公开API的问题在于字段裁剪。转评赞数量、正文全文、发帖设备这些关键字段经常拿不到而舆情分析恰恰最依赖这几项。移动端接口的字段确实全但签名算法会随版本调整我做过的多数项目里维护签名的成本远高于数据清洗本身。网页版JSON接口是折中方案有Cookie就能跑返回的cards结构基本覆盖m.weibo.cn页面上展示的全部内容。这个资源包的主入口也建立在这一层。2.2 网页版JSON的时间线链路从containerid到响应里的cards这个包抓数据查的是m.weibo.cn的container/getIndex接口。它的请求路径不算复杂核心URL是https://m.weibo.cn/api/container/getIndex请求参数里containerid决定你抓取的是哪个频道或哪个搜索结果q是搜索关键词page是页码位置。容器ID不是拍脑袋填的它是你在浏览器里打开一个微博搜索页时地址栏URL里那一大串参数的一部分。比如你在m.weibo.cn搜“芯片”地址栏里的containerid会变成类似100103type1q芯片这样的编码形态复制后作为参数传给接口就能定位到对应的内容流。接口返回的JSON里真正有价值的数据在data.cards这个数组里。每个card对象都带一个card_type字段card_type等于9的时候card.mblog里的内容是一条完整的微博正文card_type等于11时可能只是一个热词推荐位或广告卡片。很多人在这一步翻车因为他们没有做card_type过滤拿到一批混杂的结果后才发现正文数量对不上。我在实际项目里的处理方式是写一个白名单判断只收card_type为9的对象。2.3 这个接口不需要逆向加密参数真正要管的只有Cookie、Referer和时间窗口不少第一次接触微博采集的人会问网页版是不是也要做逆向是不是要处理某种签名算法。从我拆过的项目来看m.weibo.cn这个接口没有复杂的签名逻辑。你需要准备的无非三样东西一个有效的登录Cookie、一个指向m.weibo.cn的Referer、以及一个持久化的请求时间窗口。Cookie解决的是身份识别问题。不登录也能请求部分接口但返回的数据量和字段完整度都会缩水。Referer的作用是让服务端认为请求来自站内页面跳转缺少Referer时接口偶尔会直接返回错误码。时间窗口是最容易忽略的变量——请求频率一旦冲得太快返回的数据会被替换成验证页面。建议把每次请求的间隔控制在3秒以上并做随机化处理。我给这类采集脚本的常规设定是random.uniform(2, 5)既不过度拖慢速度也能规避集中请求带来的连锁反应。3. 让脚本真正跑起来环境、Cookie、代码与首批数据落库3.1 资源包里的文件都在干什么动手之前先看清目录结构。这个包的代码组织方式比较直接核心文件就几个我把它们的作用和改法列在下面。文件作用你需要改什么weibo_spider.py主入口负责调度采集与入库一般不需要动config.json关键词、容器ID、间隔范围等参数集中地必改cookie.txt存放浏览器里登录后的完整Cookie必改schema.sqlSQLite建表语句字段够用可以不动requirements.txt依赖清单安装时使用运行前先执行依赖安装pip install -r requirements.txt。主脚本用的是requests和标准库没有依赖重量级的爬虫框架所以部署门槛很低。3.2 第一步手动登录并导出Cookie不要复制地址栏里的短串Cookie是这套脚本能跑起来的先决条件。我的常规操作是用浏览器打开m.weibo.cn完成扫码登录然后在开发者工具里找到网络请求从请求头里复制完整Cookie粘贴进cookie.txt。这里有个细节有些人习惯复制浏览器地址栏里以SINAGLOBAL开头的短Cookie这种不完整。至少要包含SUB、SUBP、WBPSESS这几个关键项。为了让Cookie是否有效变得可感知我一般先跑一段验证代码import requests cookie open(cookie.txt, encodingutf-8).read().strip() headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X), Referer: https://m.weibo.cn/, Cookie: cookie, } resp requests.get(https://m.weibo.cn/api/config/get, headersheaders, timeout10) data resp.json().get(data, {}) print(login status:, data.get(login, False), uid:, data.get(uid, ))这段代码的逻辑是请求一个轻量级配置接口通过返回的login字段判断Cookie是否有效。它不涉及抓取正文数据只做身份探测所以即使拿到结果也不会污染数据流。如果login返回False说明Cookie已过期或格式不完整需要重新登录再导出。这里额外说明一下User-Agent。m.weibo.cn是移动端页面使用iPhone或Android端的UA更容易得到常规响应。我用的是iPhone的UA抓取时没有遇到过内容缺失的问题。3.3 第二步把搜索词和containerid写进config.jsonconfig.json相当于这个资源包的操作面板。打开后你会看到类似下面的结构{ keyword: 芯片, containerid: 100103type1q芯片, cookie_path: cookie.txt, db_path: weibo.db, min_interval: 2.0, max_interval: 5.0, retry_limit: 3 }keyword是搜索词containerid需要和keyword保持一致否则接口会返回空数据。min_interval和max_interval是请求间隔的上下限脚本将在两者之间取随机值这是用来应对频率限制的最简单可靠的办法。retry_limit是请求失败后的重试次数建议不要超过5否则一旦账号触发限制重试只是在加重风险。3.4 第三步跑通核心采集函数主脚本里的抓取函数是这个资源包的灵魂。它的核心逻辑是从config.json读取配置请求container/getIndex接口再把响应中的mblog字段提取出来。下面是去掉了日志和异常分支的核心片段import json import time import random import requests from urllib.parse import urlencode def fetch_page(keyword, containerid, cookie, page1): params { containerid: containerid, q: keyword, page: page, } headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15, Referer: https://m.weibo.cn/, Cookie: cookie, } url https://m.weibo.cn/api/container/getIndex? urlencode(params) resp requests.get(url, headersheaders, timeout15) result resp.json() cards result.get(data, {}).get(cards, []) posts [] for card in cards: if card.get(card_type) 9: mblog card.get(mblog, {}) posts.append(mblog) return posts这段代码做了三件事拼参数、发请求、过滤cards。注意urlencode的使用——如果keyword里有中文或特殊符号直接拼URL会导致请求失败urlencode会把参数编码成接口可识别的形式。过滤条件card_type 9是关键没有这一行返回列表里会混入大量非正文内容典型表现是采集总数看着很大实际有效正文却很少。这里还建议在调用fetch_page后加一行time.sleep(random.uniform(cfg[min_interval], cfg[max_interval]))把请求间隔控制交给调用方。每次请求结束之后随机等几秒比一次性抓完再睡更贴近真实用户的行为节奏。3.5 第四步建表与去重入库采集到的数据存SQLite不用CSV。原因有两个一是微博正文里经常包含换行和引号CSV解析时容易出现列错位二是增量采集场景下SQLite的INSERT OR IGNORE可以天然完成去重。schema.sql里给你准备好了表结构CREATE TABLE IF NOT EXISTS weibo_posts ( id TEXT PRIMARY KEY, bid TEXT, text_raw TEXT, reposts_count INTEGER, comments_count INTEGER, attitudes_count INTEGER, created_at TEXT, user_id TEXT, screen_name TEXT );id是微博ID在微博体系里全局唯一作为主键可以直接承担去重职责。text_raw保存清洗后的纯文本正文不含HTML标签。created_at建议保留原始字符串格式不要在建表环节转成时间戳因为原始字符串包含时区信息后续处理弹性更大。insert语句写成INSERT OR IGNORE INTO weibo_posts...重复数据会被SQLite自动忽略比先查询再插入的性能好很多。4. 避坑排查微博采集的五个高频翻车点现象、原因、解法都在这里4.1 跑了一个小时接口开始返回401现象脚本前半小时工作正常某个时间点之后所有请求开始返回401程序抛异常退出。原因请求频率过高Cookie被服务端风控标记。这种机制不是封死而是暂时失真——过几小时可能自动恢复但正在跑的采集任务已经中断。解决先用3.2的验证代码重新检查Cookie如果login仍然为True说明是频率问题。把config.json里的min_interval和max_interval调大到6和12然后重新启动。从那次之后我把这个包的默认节奏改成随机间隔宁慢勿快。4.2 请求返回的是一整页HTMLcards却是空的现象status_code是200脚本没报错但解析不到cards打印resp.text发现返回的是登录页面或验证页面的HTML。原因请求被服务端重定向到登录或验证流程。常见场景是Cookie部分失效或者请求头里缺少Referer导致站点把请求当成外部冷访问。解决打印resp.url如果看到地址变成了passport或login路径基本可以确定是重定向。应对手段有两个第一补上Referer: https://m.weibo.cn/第二重新导出一次完整Cookie。这种问题有个特征换一个Cookie立竿见影但如果不降频过一段时间还会复发。4.3 containerid不对接口返回成功却只有空cards现象接口返回200status字段是0data.cards是空数组或null脚本正常结束但没有数据。原因containerid和keyword不匹配或者containerid已过期。微博搜索类的containerid会随平台结构调整旧容器ID有冷却期。解决不要从旧配置里复制containerid。打开m.weibo.cn搜索目标关键词在浏览器开发者工具里找到getIndex请求从请求参数里复制当前的containerid。这个操作几乎不需要动代码只改config.json即可。值得记住的是containerid不是永久不变的每隔几个月重新抓一次参数更省事。4.4 时间字段解析失败时区还差了八小时现象created_at字段是类似“Thu Jun 08 12:34:56 0800 2023”的字符串用常规%Y-%m-%d %H:%M:%S格式解析直接抛异常。原因微博返回的时间格式是C标准格式并且自带时区偏移。如果直接截取中间一部分来解析处理和展示时就会出现8小时漂移。解决使用与格式完全匹配的模板解析import time def parse_weibo_time(s): return time.strptime(s, %a %b %d %H:%M:%S %z %Y)模板里的%z专门处理0800这种时区写法。解析完成后可以再转成其他格式但原始字符串不建议丢弃我习惯在表里保留一个created_at_raw字段方便回溯。4.5 翻页越翻越乱第二页又重复第一页内容现象page参数从1递增到10但第二页返回的数据与第一页大量重复去重之后实际没拿到几页。原因这个接口的分页不全靠page而是游标式分页。只改page数字说明你一直在请求同一个游标位置所以数据重复。解决从上一轮响应的data里取max_id或since_id作为下一轮请求参数。这在增量采集里尤其重要下一章会专门展开。最简单的验证方式抓两页统计id列表的去重数量如果去重后数量少于单页数据量的1.5倍说明翻页方式需要改。5. 从一次性脚本到长期任务增量更新、限速、断点续爬怎么做5.1 增量采集的判断since_id、max_id与最后一条时间戳微博的翻页不靠page增长而是靠游标。响应数据里的since_id和max_id一个是增量游标一个是翻页游标。如果你要抓一个话题下的新内容应该使用since_id如果你只是从头到尾翻老内容使用max_id更合适。实际操作中我把这两个字段的关系简化成一句话since_id用于告诉服务端“从哪个位置开始给我新内容”max_id用于告诉服务端“给我下一页”。所以增量任务的状态需要记录上一次的since_id常规翻页任务则需要保存当前max_id。资源包里推荐的做法是把这些状态集中放在一个JSON文件里{ keyword: 芯片, last_since_id: 1234567890, last_run_time: 2025-01-01 12:00:00 }每次任务启动时读这个文件结束前写回新的游标和运行时间。这样做的好处是即使任务因为断电或网络原因中断下次启动仍然能从断点继续。5.2 主动限速是一道保险随机间隔加指数退避微博接口对请求速度的忍耐度有限。把每次请求间隔固定成3秒效果并不好——机器特征太明显。我的做法是随机区间加指数退避失败重试时等待时间逐次翻倍import time import random def wait_before_request(attempt0): if attempt 0: delay random.uniform(2, 5) else: delay min(5 * (2 ** attempt), 30) time.sleep(delay)attempt从0开始第一次失败后等待5秒第二次10秒第三次20秒上限30秒。这样即使遇到接口抖动也不会在短时间内高频撞击。指数退避的原则是在重试成本和等待收益之间找一个平衡点。微博场景下单次任务持续时间不太长30秒的上限已经够用再多就影响采集进度了。5.3 断点续爬把进度写进文件而不是指望屏幕输出做过长周期采集的人都有过这种经历脚本跑了大半天终端窗口被关掉前功尽弃。为了避免重复劳动我建议你在脚本里加上状态保存和恢复逻辑。下面是一个骨架示例import json import os STATE_FILE collect_state.json def load_state(): if os.path.exists(STATE_FILE): with open(STATE_FILE, encodingutf-8) as f: return json.load(f) return {max_id: , seen_ids: [], current_page: 0} def save_state(state): with open(STATE_FILE, w, encodingutf-8) as f: json.dump(state, f, ensure_asciiFalse, indent2) def collect(keyword, containerid, total_pages): state load_state() start_page state.get(current_page, 0) seen_ids set(state.get(seen_ids, [])) for page in range(start_page, total_pages): posts fetch_page(keyword, containerid, cookie, pagepage) for p in posts: if p[id] not in seen_ids: insert_post(p) seen_ids.add(p[id]) state[current_page] page 1 state[seen_ids] list(seen_ids) save_state(state) wait_before_request()这个循环里有两个关键习惯一是翻页游标和已处理ID都写进文件这样才能在意外退出后从真实位置继续二是每处理完一页就立刻保存状态而不是等全部结束再写。我见过很多脚本只在最后保存一次进度中途一旦崩溃之前所有状态全部丢失。seen_ids这个字段要注意内存占用。如果采集量超过十万级建议把ID集合存到SQLite临时表而不是留在内存里。这个资源包默认场景是万级数据内存方案完全够用。6. 把它变成可复现的数据管道配置外置、字段校验与最小回归测试6.1 config.json集中管理采集前只改一个文件成熟的采集任务不应该每次改代码。把关键词、容器ID、库路径、间隔范围全部外置到config.json这样换一个话题只需要改JSON不用碰Python文件。配置里还可以加一个dry_run字段设为true时只抓一页并退出用来快速验证配置是否正确。6.2 采集数据完整性的三重校验上线前我会给入库前置一层校验函数把常见脏数据挡在门外import time def validate_post(row): assert row.get(id), missing id assert row.get(text_raw, ).strip(), empty text parse_weibo_time(row[created_at]) # 解析失败会抛出异常 return True这个函数检查三件事是否存在微博ID、正文是否为空、时间字段能否解析。任何一项不满足就让采集脚本停下而不是跳过。跳过脏数据会导致最终统计口径不一致而停下之后人工排查问题才可控。6.3 最小回归测试只抓一页验证链路整套配置改完之后不要直接跑几十页。不管是资源包自带的脚本还是你自己改过的版本我都建议给主入口加一个--pages 1参数只抓一页数据并执行校验。看到所有校验通过再放开页数限制。这个小习惯的成本几乎为零但能挡住一大堆低级错误比如containerid忘了改、Cookie过期这类问题通常跑一页就会暴露。我从第一次跑这个包到现在每次上线采集前都会强制走一遍单页测试、字段校验、状态文件清理这三步。这套流程帮我避开了不少隐性问题也让我对这个数据管道的把握更强。希望这三步操作也能成为你的固定习惯帮到你。本文还有配套的精品资源点击获取