2026/9/16 3:49:10

微博数据爬虫实战:绕过动态渲染与反爬的工程化采集方案

微博数据爬虫实战:绕过动态渲染与反爬的工程化采集方案 简介本资源是一套完整可用的微博数据爬虫实战项目面向计算机专业本科生、毕业设计与课程设计学生以及Python/Java双栈开发初学者解决社交平台公开数据采集、清洗与分析的实际需求。压缩包共190个文件2.58MB涵盖34个核心Python脚本含爬虫主逻辑、反爬绕过、数据解析模块、22个JavaScript前端交互文件用于模拟用户行为与页面渲染、11个JSON格式配置与结果数据、以及C语言底层驱动代码如ds1302.c、DHT11.c等嵌入式通信模块体现软硬协同设计思路另有JPG/PNG图表、CSS/HTML可视化界面及VS工程文件.sln、.csproj支持开箱即用与二次开发。已有62人学习下载资源经严格评审获95分高分包含完整项目报告、可运行源码、测试截图与部署说明特别适合课设答辩、毕设原型构建及数据采集类项目实战演练。1. 微博数据爬虫不是“点开网页右键另存为”——它是一套需绕过动态渲染、反爬策略与接口鉴权的工程化数据采集链路很多人看到“微博数据爬虫-点赞转发评论全获取”这个标题第一反应是不就是用 requests 请求几个 URL 吗但实际落地时90% 的人卡在第一步——连微博首页都拿不到完整 HTML。原因很简单微博 PC 端和移动端均采用 React SSR 混合渲染关键字段如微博 ID、用户 UID、互动数全部由 JavaScript 动态注入更关键的是所有公开接口如https://weibo.com/ajax/statuses/repostTimeline、https://weibo.com/ajax/statuses/buildComments均强制校验X-XSRF-TOKEN、Cookie中的SUB和SUHB且请求头必须携带User-Agent、Referer、X-Requested-With三要素缺一不可。这不是“写个 for 循环就能跑通”的脚本而是涉及登录态维持、接口逆向、参数签名、频率控制、异常重试的端到端数据管道。适合需要批量分析竞品运营效果、舆情监测、学术研究中微博传播路径的 IT 工程师、数据分析师与合规合规的数据采集人员——前提是已获得平台公开数据授权或仅用于个人非商业研究场景。2. 从登录态构建到接口逆向微博互动数据采集的三层可信链路设计微博数据采集不能靠模拟浏览器点击而要建立可复现、可审计、可降级的三层链路认证层 → 接口层 → 解析层。这三层不是线性流程而是相互校验的闭环。认证层决定你能否拿到有效 Cookie接口层决定你能调通哪些 endpoint解析层决定你是否能稳定提取结构化字段。跳过任一层都会导致“今天能跑明天 403”或“返回空数组却无报错”的典型问题。2.1 认证层不依赖 Selenium用 Requests 手动 Cookie 注入实现轻量登录态复用微博 Web 端已全面弃用账号密码直登改用扫码登录或手机短信登录。但对爬虫而言最稳定的方式是复用已有登录态 Cookie。手动获取方式如下使用 Chrome 登录微博账号打开开发者工具F12→ Application → Cookies → 复制SUB、SUBP、SSOLoginState、XSRF-TOKEN四个关键字段构建最小化 Session 对象显式设置 Referer 和 User-Agentimport requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36, Referer: https://weibo.com/, X-Requested-With: XMLHttpRequest }) cookies { SUB: _2AkMmZqk-f8NhqwJRmPEaxurqaT6HzH7EieOaYJc_EPxMKDNzTnAKqjBtT5QbDvLWfVnKuUwGdNpRlIgSsC1yZi1hIj8_, SUBP: u32Cc72-4rM7e--fSiq5ir5g5x2RBj3l1.LR8ZQoA7jQZfQXzqQZ5JpX, SSOLoginState: 1715823456, XSRF-TOKEN: 1234567890abcdef } session.cookies.set_dict(cookies)提示XSRF-TOKEN不是固定字符串它会随每次页面加载刷新必须从https://weibo.com/响应 HTML 的script标签中正则提取如window.$CONFIG {.*?xsrfToken: (.*?),否则后续所有接口将返回{code: 100001, msg: invalid token}。2.2 接口层微博互动数据三大核心 endpoint 及其必传参数表微博未开放官方 API 获取完整互动数据但其前端 JS 会调用以下三个真实接口经实测2024 年 Q2仍可用且支持分页与增量拉取接口地址用途必传 Query 参数是否需 X-XSRF-TOKEN Headerhttps://weibo.com/ajax/statuses/repostTimeline获取转发列表id微博 bid、page页码从 1 开始、count每页条数默认 20是https://weibo.com/ajax/statuses/buildComments获取评论列表id微博 bid、page页码、count默认 20、type1普通评论2热门评论是https://weibo.com/ajax/statuses/likeCount获取点赞总数id微博 bid否仅 GET无需 token其中bid是微博唯一标识符格式为 9~10 位字母数字组合如KxYzAbC12不是 mid。它存在于微博详情页 URL 中https://weibo.com/xxxxxx/KxYzAbC12或通过https://weibo.com/ajax/statuses/show?idxxx接口返回的data.bid字段中。2.2.1 转发数据拉取带重试与状态码校验的健壮请求封装def fetch_reposts(session, bid, page1, count20): url https://weibo.com/ajax/statuses/repostTimeline params {id: bid, page: page, count: count} headers {X-XSRF-TOKEN: session.cookies.get(XSRF-TOKEN, )} for attempt in range(3): try: resp session.get(url, paramsparams, headersheaders, timeout10) if resp.status_code 200: data resp.json() if data.get(ok) 1 and data in data: return data[data].get(reposts, []) elif data.get(code) 100001: raise ValueError(XSRF-TOKEN expired, need refresh) elif resp.status_code 418: # 微博反爬常见状态码Im a teapot time.sleep(2 ** attempt) # 指数退避 continue except Exception as e: print(f[REPOST] Error on page {page}: {e}) time.sleep(1) return [] # 示例调用 reposts fetch_reposts(session, bidKxYzAbC12, page1) for r in reposts[:3]: print(f转发者UID: {r[user][id]}, 内容: {r[text][:30]}...)注意repostTimeline接口返回的user.id是用户 UID如1234567890不是昵称text字段含 HTML 实体如gt;需用html.unescape()解码created_at为 ISO8601 字符串如2024-05-15T14:23:11可直接转为datetime对象。2.3 解析层从 JSON 响应中稳定提取结构化字段的字段映射表微博接口返回 JSON 结构嵌套深、字段名不统一如评论有user和user_info两套字段必须定义明确的字段映射规则避免因前端微调导致解析失败原始 JSON 路径目标字段名类型说明user.id/user_info.iduidstr用户唯一标识可用于去重或关联用户资料user.screen_name/user_info.screen_namenicknamestr昵称可能含 emoji建议 UTF-8 存储textcontentstr原始文本含、#、链接等需清洗created_atcreated_atdatetime发布时间注意时区为 UTC8sourcedevicestr来源设备如iPhone客户端需正则提取关键词like_countlike_countint当前点赞数仅转发/评论对象自身非原博import html from datetime import datetime def parse_repost_item(item): user item.get(user) or item.get(user_info, {}) return { uid: str(user.get(id, )), nickname: user.get(screen_name, ), content: html.unescape(item.get(text, )), created_at: datetime.fromisoformat(item[created_at].replace( , T)), device: item.get(source, ).strip(来自), like_count: item.get(like_count, 0) } # 应用解析 parsed_reposts [parse_repost_item(r) for r in fetch_reposts(session, KxYzAbC12)]3. 全量数据采集实战从单条微博到批量 UID 的可配置 pipeline单条微博的点赞、转发、评论只是起点。真实业务场景中你需要① 批量输入微博 bid 列表② 自动发现关联用户 UID③ 按时间范围增量采集④ 输出标准 CSV/JSONL。这就要求把前述模块组装成可配置 pipeline而非一次性脚本。3.1 输入层支持三种微博 ID 来源的标准化读取器微博数据采集入口不能只靠人工复制 bid。常见来源有URL 列表https://weibo.com/2803301701/KxYzAbC12UID 时间范围通过https://weibo.com/ajax/profile/getWeibo?uid2803301701page1count10获取某用户近期微博列表关键词搜索结果调用https://weibo.com/ajax/side/hotSearch获取热搜再用https://weibo.com/ajax/searchall?keywordxxxpage1获取相关微博下面是一个通用 bid 提取函数兼容上述三种输入import re from urllib.parse import urlparse, parse_qs def extract_bid_from_input(input_str): 从 URL、UID 或纯 bid 字符串中提取 bid # case 1: URL like https://weibo.com/2803301701/KxYzAbC12 url_match re.search(rweibo\.com/\d/([a-zA-Z0-9]{9,10}), input_str) if url_match: return url_match.group(1) # case 2: pure bid (9-10 chars alphanumeric) bid_match re.match(r^[a-zA-Z0-9]{9,10}$, input_str.strip()) if bid_match: return input_str.strip() # case 3: UID (pure digits), trigger profile fetch uid_match re.match(r^\d{5,12}$, input_str.strip()) if uid_match: return fetch_bids_by_uid(session, uid_match.group(0), limit5) # 返回 list raise ValueError(fCannot extract valid bid from: {input_str}) def fetch_bids_by_uid(session, uid, limit10): 根据 UID 获取最近 limit 条微博的 bid 列表 url fhttps://weibo.com/ajax/profile/getWeibo params {uid: uid, page: 1, count: limit} resp session.get(url, paramsparams) if resp.status_code 200: data resp.json() return [item[bid] for item in data.get(data, {}).get(list, [])[:limit]] return []3.2 控制层可中断、可续采、带进度日志的采集调度器为避免网络波动或反爬限流导致全量任务失败必须实现断点续采。核心是记录每个 bid 的采集状态pending/done/failed到本地 SQLite 数据库import sqlite3 from pathlib import Path DB_PATH Path(weibo_crawl.db) def init_db(): conn sqlite3.connect(DB_PATH) conn.execute( CREATE TABLE IF NOT EXISTS crawl_status ( bid TEXT PRIMARY KEY, status TEXT DEFAULT pending, last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, error_msg TEXT ) ) conn.commit() conn.close() def mark_as_done(bid): conn sqlite3.connect(DB_PATH) conn.execute(INSERT OR REPLACE INTO crawl_status (bid, status) VALUES (?, done), (bid,)) conn.commit() conn.close() def get_pending_bids(limit100): conn sqlite3.connect(DB_PATH) cursor conn.execute(SELECT bid FROM crawl_status WHERE status pending LIMIT ?, (limit,)) bids [row[0] for row in cursor.fetchall()] conn.close() return bids # 主采集循环 init_db() pending_bids get_pending_bids(limit50) for bid in pending_bids: try: print(f[START] Processing bid {bid}) reposts fetch_reposts(session, bid, page1) comments fetch_comments(session, bid, page1) like_count fetch_like_count(session, bid) # 保存到 CSV按 bid 分文件 save_to_csv(foutput/{bid}_reposts.csv, reposts) save_to_csv(foutput/{bid}_comments.csv, comments) save_to_json(foutput/{bid}_meta.json, {bid: bid, like_count: like_count}) mark_as_done(bid) print(f[DONE] bid {bid} saved) except Exception as e: print(f[FAIL] bid {bid}: {e}) mark_as_failed(bid, str(e))提示save_to_csv()应使用csv.DictWriter并指定fieldnames为预定义字段列表如[uid,nickname,content,created_at]确保列顺序一致文件名含bid可避免多任务写入冲突CSV 编码必须为utf-8-sig否则 Excel 打开中文乱码。3.3 输出层生成含统计摘要与原始明细的双层报告结构“含全部资料报告.zip”中的“报告”不应是 Word 或 PDF而应是机器可读、人可查的结构化产物。推荐输出以下两类文件明细层{bid}_reposts.csv、{bid}_comments.csv、{bid}_meta.json—— 原始数据字段严格对齐 2.3 节映射表汇总层summary_report.json—— 包含总微博数、总互动数、用户去重数、时间分布直方图按小时聚合、高频设备统计等。# summary_report.json 示例结构 { crawl_time: 2024-05-18T10:23:4508:00, total_weibos: 12, total_reposts: 342, total_comments: 1897, unique_uids: 1243, top_devices: [ {device: iPhone客户端, count: 872}, {device: Android客户端, count: 651} ], hourly_distribution: { 00: 12, 01: 8, ..., 23: 21 } }该报告可直接导入 BI 工具如 Metabase、Superset做可视化也可用 Pandas 快速生成折线图/词云。4. 避坑指南微博反爬升级后最常触发的 5 类错误及对应解法微博反爬策略持续迭代2024 年以来新增了多项检测机制。以下 5 类错误出现频率最高且均有明确解法无需更换 IP 或购买代理池。4.1 错误类型 1{code:100001,msg:invalid token}—— XSRF-TOKEN 过期现象所有带X-XSRF-TOKEN的接口均返回此错误但 Cookie 中XSRF-TOKEN字段存在。根因微博服务端会校验XSRF-TOKEN与当前SUBCookie 的绑定关系且该绑定 30 分钟失效。解法每次请求前先 GEThttps://weibo.com/从响应 HTML 中用正则提取最新 tokendef refresh_xsrf_token(session): resp session.get(https://weibo.com/, timeout5) match re.search(rxsrfToken\s*:\s*([^]), resp.text) if match: session.headers[X-XSRF-TOKEN] match.group(1) session.cookies.set(XSRF-TOKEN, match.group(1)) else: raise RuntimeError(Failed to extract XSRF-TOKEN from homepage)4.2 错误类型 2status_code 418—— 请求被识别为自动化流量现象请求返回 HTTP 418Im a teapot无 JSON bodyheaders 中x-rate-limit-remaining为 0。根因微博服务端基于请求指纹User-Agent Referer 请求间隔判定为 bot。解法在session.headers中固定User-Agent不要随机所有接口请求Referer必须为https://weibo.com/不是微博某条具体博文页加入time.sleep(random.uniform(1.5, 3.0))避免固定间隔关键禁用 connection pooling每次请求新建 TCP 连接from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session.mount(https://, HTTPAdapter( pool_connections1, pool_maxsize1, max_retriesRetry(total0) ))4.3 错误类型 3评论返回{ok:0,msg:暂无数据}—— 评论开关关闭或权限不足现象buildComments接口返回空数据但微博页面可见评论。根因原博主设置了“仅好友可见评论”或“关闭评论”。解法先调用https://weibo.com/ajax/statuses/show?id{bid}检查返回 JSON 中data.comments_count是否 0若为 0则跳过评论采集记录reason: comments_disabled。4.4 错误类型 4UnicodeEncodeError: gbk codec cant encode char—— Windows 下 CSV 写入中文失败现象save_to_csv()报编码错误尤其在 Windows 系统。解法显式指定encodingutf-8-sig并用newline避免空行with open(filename, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(data)4.5 错误类型 5bid解析失败导致repostTimeline返回{ok:0,msg:参数错误}现象接口返回参数错误但 bid 看似合法。根因微博 bid 实际是 base62 编码的 mid长度固定为 9 或 10但部分旧微博2018 年前使用 8 位 bid已被新接口废弃。解法增加 bid 格式校验def validate_bid(bid): if not isinstance(bid, str): return False if not re.match(r^[a-zA-Z0-9]{9,10}$, bid): return False # 额外校验尝试访问 show 接口 resp session.get(fhttps://weibo.com/ajax/statuses/show?id{bid}) return resp.status_code 200 and resp.json().get(ok) 15. 进阶技巧用 UID 反查微博账号基础信息构建用户画像关联网络仅采集微博互动数据是单维视角。真正有价值的是将uid如1234567890映射到用户实体获取其昵称、所在地、简介、关注数、粉丝数、认证信息等从而构建“谁在转发谁”的传播图谱。微博提供https://weibo.com/ajax/profile/info?uid{uid}接口无需额外鉴权但需注意字段稳定性。5.1 UID 基础信息接口字段可靠性排序按 2024 年实测字段名是否稳定说明替代方案若缺失user.screen_name★★★★★昵称100% 存在—user.description★★★★☆简介95% 存在从用户主页 HTML 提取user.followers_count★★★★☆粉丝数90% 存在用https://weibo.com/ajax/friendships/friends/active?uid{uid}补充user.location★★☆☆☆所在地仅 40% 用户填写用 NLP 从简介中抽城市名user.verified_type★★★★★认证类型-1未认证0个人2企业3政府—user.avatar_hd★★★★☆高清头像 URL—5.2 批量 UID 查询的并发控制与缓存策略为避免频繁请求触发限流必须实现两级缓存内存缓存用functools.lru_cache(maxsize1000)缓存最近查询的 UID 结果磁盘缓存用 SQLite 存储uid → profile_json设置 TTL 7 天。import sqlite3 from functools import lru_cache DB_CACHE uid_profile_cache.db def init_cache_db(): conn sqlite3.connect(DB_CACHE) conn.execute( CREATE TABLE IF NOT EXISTS uid_profile ( uid TEXT PRIMARY KEY, profile_json TEXT NOT NULL, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) conn.execute(CREATE INDEX IF NOT EXISTS idx_updated ON uid_profile(updated_at)) conn.commit() conn.close() lru_cache(maxsize1000) def get_user_profile_cached(uid): # 先查内存缓存lru_cache # 再查磁盘缓存SQLite conn sqlite3.connect(DB_CACHE) cursor conn.execute(SELECT profile_json FROM uid_profile WHERE uid ? AND updated_at datetime(now, -7 days), (uid,)) row cursor.fetchone() conn.close() if row: return json.loads(row[0]) # 调用 API resp session.get(fhttps://weibo.com/ajax/profile/info?uid{uid}) if resp.status_code 200 and resp.json().get(ok) 1: profile resp.json()[data] # 写入磁盘缓存 conn sqlite3.connect(DB_CACHE) conn.execute(INSERT OR REPLACE INTO uid_profile (uid, profile_json) VALUES (?, ?), (uid, json.dumps(profile, ensure_asciiFalse))) conn.commit() conn.close() return profile return None5.3 构建传播关系 CSV从互动数据到有向图边集最终输出的interaction_edges.csv应包含以下字段可直接导入 Gephi 或 NetworkX 做社区发现字段类型说明source_uidstr转发者/评论者 UIDtarget_uidstr原博主 UID从微博user.id获取interaction_typestrrepost/comment/liketimestampdatetime互动发生时间weightint固定为 1或按互动深度加权生成逻辑示例def build_edge_list(bid, reposts, comments, original_user_id): edges [] # 转发边 for r in reposts: edges.append({ source_uid: r[uid], target_uid: original_user_id, interaction_type: repost, timestamp: r[created_at], weight: 1 }) # 评论边 for c in comments: edges.append({ source_uid: c[uid], target_uid: original_user_id, interaction_type: comment, timestamp: c[created_at], weight: 1 }) return edges # 调用示例 original_user_id 2803301701 # 从 show 接口获取 edges build_edge_list(KxYzAbC12, reposts, comments, original_user_id) save_to_csv(output/interaction_edges.csv, edges, fieldnames[source_uid,target_uid,interaction_type,timestamp,weight])这一张 CSV就是微博传播网络的原子级表达——它不依赖任何第三方平台完全由你掌控数据主权且可无限扩展至百万级 UID 关系分析。本文还有配套的精品资源点击获取