简介本资源是一份面向Python初学者与爬虫入门者的实战教程手把手讲解如何抓取微博平台的评论数据解决社交媒体舆情分析、粉丝行为研究等实际场景中的数据采集需求。内容涵盖登录模拟含RSA加密、预登陆参数获取、验证码处理、Cookie持久化管理、HTML/JSON数据解析及CSV存储全流程代码完整可直接运行调试。资源为单文件PDF文档共1页大小379KB内容结构清晰包含库引入说明、全局变量配置、目录创建逻辑、WeiboLogin类实现细节及关键函数注释便于边学边练。已有7702人学习下载适合希望掌握反爬应对策略、理解微博登录机制并构建可复用爬虫框架的学习者。1. 微博评论爬虫不是“点开网页右键另存为”它是一套带登录态、抗反爬、能续爬的完整会话链你有没有试过打开微博某条热门帖想看看底下到底吵成什么样结果翻到第5页就卡住F12 看 Network发现请求全被 302 重定向到 login.sina.com.cn或者更糟——刚输完账号密码弹出验证码图片但 PIL.open() 显示失败终端里只有一行OSError: cannot open resource这不是玄学是微博登录协议在 2023 年后彻底重构的真实切口。这个资源不是教你怎么用 requests.get() 抓静态页面而是交付一套可落地、可调试、可复现的完整登录评论拉取链它包含 RSA2 密码加密逻辑、pcid 验证码动态加载机制、sso 跳转状态机、m.weibo.cn 与 weibo.com 双域 Cookie 同步策略以及最关键的——callback参数如何从 JS 回调中安全提取并用于后续跳转。适合正在做舆情分析、社区情绪建模、或需要真实用户评论语料的 Python 工程师不适合只想复制粘贴就跑通的纯新手——因为这里没有“一键 bypass 验证码”的黑盒只有你能看懂、能改、能 debug 的每一步。它解决的不是“能不能抓”而是“为什么上一秒还行下一秒就 403”、“为什么 max_id 拿不到”、“为什么 CSV 里中文全是乱码”这些血泪问题。2. 登录不是 POST 一次就完事微博 SSO 登录协议拆解与关键参数溯源微博登录早已不是简单的表单提交。它走的是新浪统一认证中心SSO体系整个流程涉及至少 5 次 HTTP 跳转、3 套 Cookie 域名weibo.com / login.sina.com.cn / m.weibo.cn、以及 2 层加密Base64 RSA2。不理解这个链条任何“改个 headers 就能过”的想法都会在第 3 步直接翻车。2.1callback不是固定字符串而是 JS 函数名 时间戳拼接的动态标识在get_server_data()方法中这行代码常被忽略pre_url http://login.sina.com.cn/sso/prelogin.php?entryweibocallbacksinaSSOController.preloginCallBacksu su rsaktmodcheckpin1clientssologin.js(v1.4.19)_ str(int(time.time() * 1000))这里的callbacksinaSSOController.preloginCallBack是关键。它不是随便写的而是前端 JS 中定义的全局回调函数名。服务器返回的数据格式是sinaSSOController.preloginCallBack({retcode:0,servertime:1712345678,nonce:ABCD1234,pubkey:00B123...,rsakv:1234567890})注意eval(...replace(sinaSSOController.preloginCallBack, ))这行代码的本质是把 JS 函数调用包装壳剥掉只留下 JSON 对象。如果某天微博把回调名改成sinaSSOController.preloginV2而你没同步更新callback参数eval就会报SyntaxError。这不是 bug是协议契约。我一般会在pre_data_res.text打印后加一行断言assert sinaSSOController.prelogin in pre_data_res.text, Callback name changed! Check prelogin.php response format2.2rsakv和servertime必须成对使用且有效期极短 5 秒get_password()方法中明文拼接规则是message str(servertime) \t str(nonce) \n str(self.password)注意\t和\n是硬编码分隔符来自微博前端 JS 的encryptPassword函数。servertime是服务器当前时间戳秒级nonce是随机字符串。这两个值一旦过期RSA 加密后的sp字段就会被服务端拒绝。实测发现若prelogin返回后超过 3 秒才发登录请求大概率返回{retcode:404000001,reason:invalid servertime}。因此login()方法里try/except的设计非常务实——它先尝试无验证码登录利用showpin0的情况失败后再触发验证码流程避免无谓耗时。2.3door参数不是“输入验证码”而是 Base64 编码后的图片识别结果get_cha(pcid)下载的cha.jpg是 PNG 格式但微博后端校验的door值是用户肉眼识别后输入的 4 位字母数字组合区分大小写未经任何编码直接作为 POST body 字段发送。原文中self.postdata[door] input(u请输入验证码)是最简实现但生产环境必须替换为 OCR 接口。常见踩坑是有人误以为要传base64.b64encode(open(cha.jpg,rb).read())结果服务端返回{retcode:404000002,reason:invalid door}。正确做法是调用pytesseract.image_to_string()或接入第三方打码平台 API拿到纯文本后直接赋值。2.4 Cookie 同步是双域接力weibo.com登录态 ≠m.weibo.cn可用态WeiboLogin.login()方法末尾有两段关键跳转第一段通过jump_url https://passport.weibo.com/wbsso/login获取uniqueid构造web_weibo_url访问 PC 端主页目的是激活weibo.com域的 Cookie第二段用murl https://login.sina.com.cn/sso/login.php拼接returntypeMETA参数触发replace(...)跳转最终落到m.weibo.cn。这两步缺一不可。漏掉第一段m.weibo.cn请求会返回 302 到登录页漏掉第二段m.weibo.cn的isLogin:true检查永远失败。Mheaders[Host]的反复切换login.sina.com.cn→passport.weibo.cn→m.weibo.cn正是为了匹配不同域名的 Cookie 发送策略。requests.Session()的cookies对象会自动管理多域 Cookie但前提是session.get()的 URL Host 必须与 Cookie Jar 中存储的 domain 匹配。提示调试时可在self.session.cookies.save()后加一行print([c.name for c in self.session.cookies])确认SUB,SUHB,ALF等关键 Cookie 是否已写入Cookie.txt。若为空说明某次跳转未携带 Cookie 或 Host 设置错误。3. 评论拉取不是“翻页循环”max_id机制、分页陷阱与数据去重实战微博热评接口/comments/hotflow的分页逻辑远比page1,2,3复杂。它依赖max_id和max_id_type两个联动参数且max_id本身是服务端生成的 opaque token不能解析、不能预测、不能缓存。很多爬虫在这里卡死不是代码写错而是没吃透这个状态机。3.1max_id是游标cursor不是页码page number原始代码中base_url https://m.weibo.cn/comments/hotflow?id{}mid{}max_id_type0 next_url https://m.weibo.cn/comments/hotflow?id{}mid{}max_id{}max_id_type{}第一次请求用max_id_type0服务端返回 JSON 中的data.max_id是下一个游标值如1234567890123456789同时data.max_id_type会变为1。下一次请求必须用max_id1234567890123456789max_id_type1。若强行用max_id_type0再请求会返回空数据或 400 错误。max_id_type是状态标志0 表示初始请求1 表示后续分页。代码中id_type 1的递增逻辑是正确的但缺少对max_id是否为空的判断——当data.max_id 0或时即表示无更多评论应终止循环。3.2mid和id参数必须严格一致且来源必须是微博详情页 URL代码中start_crawl(cookie_dict, id)的id参数必须与你要爬的微博帖子的mid完全相同。例如某微博 URL 是https://weibo.com/1234567890/AbCdEfGhIjK?refer_flag1001030101_其mid是AbCdEfGhIjK注意不是数字 ID。但原文示例id 4477416430959369是一个数字这其实是旧版微博的id格式。2023 年后主流是字母数字混合的mid。获取方式必须是打开微博 H5 页面m.weibo.cn/status/xxxF12 → Network → Filterhotflow→ 找到第一个请求 → 查看 Query String Parameters → 取mid后的值。若填错返回{ok:0,msg:Parameter error}。3.3 评论嵌套结构需递归解析comments字段是二级评论楼中楼微博热评接口返回的data.data数组中每个元素代表一条一级评论。但部分评论带有comments字段这是该条评论下的回复即“楼中楼”。原文info_parser()函数中if c.get(comments, None): temp [] for cc in c.get(comments): temp.append(info_parser(cc)) wdata.append(info_parser(cc)) # ← 这里有严重 Bug最后一行wdata.append(info_parser(cc))是错的cc是循环变量info_parser(cc)只处理了最后一个cc且temp列表被创建却未使用。正确逻辑应是if comments in c and c[comments]: for cc in c[comments]: nested_row info_parser(cc) nested_row[parent_wid] c[id] # 标记父评论 ID wdata.append(nested_row)否则所有楼中楼数据都会丢失CSV 中只有一级评论。3.4 CSV 中文乱码的根因是utf-8-sig编码与 Excel 兼容性冲突原文open(..., encodingutf-8-sig)是为兼容 Windows Excel 的 BOM 头但csv.writer在utf-8-sig下写入时BOM 会被插入到第一行第一列导致 Excel 打开时首列显示wid。更稳妥的做法是with open({}/{}.csv.format(comment_path, id), modew, encodingutf-8, newline) as f: writer csv.writer(f) writer.writerow([wid, time, text, uid, username, following, followed, gender]) for d in wdata: # 对 text 字段做防错处理避免含换行符破坏 CSV 结构 safe_text d[text].replace(\n, ).replace(\r, ) writer.writerow([ d[wid], d[time], safe_text, d[uid], d[username], d[following], d[followed], d[gender] ])newline是csv模块强制要求否则 Windows 下会多出空行safe_text替换换行符是防止 CSV 解析器将一条评论切分成多行。注意time.sleep(3)是硬编码延时实际部署建议改为random.uniform(2.5, 4.0)避免请求间隔过于规律被风控。4. 避坑5 条血泪经验总结——从登录失败到数据残缺的完整排错路径爬微博评论最让人崩溃的不是代码写不出来而是“明明昨天能跑今天就 403”。以下是我在多个项目中踩过的坑按发生频率排序每条都附带现象、原因和可立即执行的解决方案。4.1 现象prelogin.php返回{retcode:403000001,reason:Invalid app key}原因clientssologin.js(v1.4.19)中的版本号已过期。微博会定期更新登录 JS 版本旧版本 key 被服务器拒绝。解决打开https://login.sina.com.cn/js/目录找最新ssologin.js?vxxx文件提取 URL 中的v参数如v1.4.22替换代码中所有v1.4.19。不要猜必须实测。我一般用curl -I https://login.sina.com.cn/js/ssologin.js看Last-Modified时间戳来判断是否更新。4.2 现象login.php返回{retcode:404000001,reason:invalid servertime}原因prelogin和login两次请求时间差超过 5 秒servertime失效。解决在pre_login()返回sever_data后立即记录时间戳start_time time.time()在构造login_url前检查if time.time() - start_time 4.5: raise Exception(Servertime expired)。若超时重新调用pre_login()。4.3 现象m.weibo.cn请求返回{ok:0,msg:Login required}但pro.text中找不到isLogin:true原因Mheaders[Host]未在每次session.get()前重置。requests.Session()会复用上一次请求的 Host若前一次是passport.weibo.cn这次发给m.weibo.cn却带着Host: passport.weibo.cn服务端直接拒收。解决在每次session.get()前显式设置Mheaders[Host]如原文所示。绝对不要在循环外只设一次。4.4 现象hotflow接口返回{ok:0,msg:Forbidden}且res.status_code 403原因Cookie 过期或不完整。Cookie.txt中缺失SUB或ALF字段或expires时间已过。微博 Cookie 有效期通常为 1~3 天。解决删除Cookie.txt重新运行登录流程或在get_cookies()中增加校验def get_cookies(): cookies cookielib.LWPCookieJar(Cookie.txt) try: cookies.load(ignore_discardTrue, ignore_expiresTrue) except FileNotFoundError: raise Exception(Cookie file not found. Please run login first.) # 检查关键 Cookie 是否存在且未过期 sub_cookie [c for c in cookies if c.name SUB] if not sub_cookie or sub_cookie[0].is_expired(): raise Exception(SUB cookie expired. Re-login required.) return requests.utils.dict_from_cookiejar(cookies)4.5 现象CSV 文件中text字段出现大量None或空字符串原因info_parser()中data[text]可能为None如用户删评但代码未做空值处理。解决在info_parser()中添加防御def info_parser(data): id data.get(id, ) time data.get(created_at, ) text data.get(text, ).strip() or [deleted] # 删除评论标记为 [deleted] user data.get(user, {}) uid user.get(id, ) username user.get(screen_name, ) following user.get(follow_count, 0) followed user.get(followers_count, 0) gender user.get(gender, unknown) return {wid:id,time:time,text:text,uid:uid,username:username,following:following,followed:followed,gender:gender}5. 数据验证与增量爬取用ETag和Last-Modified实现评论变更检测爬虫的价值不在“一次性拉全”而在“持续感知变化”。微博评论是动态流新评论不断涌入旧评论可能被删除或折叠。原代码是全量覆盖模式modew每次运行都重写 CSV无法知道哪些是新增、哪些已消失。我们用 HTTP 协议自带的缓存头低成本实现增量感知。5.1ETag是评论列表的指纹Last-Modified是最后更新时间微博hotflow接口响应头中包含ETag: abc123def456 Last-Modified: Wed, 10 Apr 2024 08:23:45 GMTETag是服务端为当前评论快照生成的唯一哈希只要评论列表不变ETag就不变Last-Modified是服务端认为该列表最后变动的时间。二者结合可精准判断数据是否需更新。5.2 实现增量爬取只在ETag变化时拉取新数据修改start_crawl()函数在首次请求前读取本地缓存的ETagdef start_crawl(cookie_dict, mid): cache_file f{comment_path}/{mid}_cache.json etag_cache {} if os.path.exists(cache_file): with open(cache_file, r, encodingutf-8) as f: etag_cache json.load(f) base_url fhttps://m.weibo.cn/comments/hotflow?id{mid}mid{mid}max_id_type0 headers_with_cache headers.copy() # 发送条件请求 if etag in etag_cache: headers_with_cache[If-None-Match] etag_cache[etag] if last_modified in etag_cache: headers_with_cache[If-Modified-Since] etag_cache[last_modified] res requests.get(urlbase_url, headersheaders_with_cache, cookiescookie_dict) if res.status_code 304: # 未修改直接退出 print(fComments for {mid} unchanged. ETag: {etag_cache.get(etag, N/A)}) return # 200 响应说明有更新继续爬取 if ETag in res.headers: etag_cache[etag] res.headers[ETag] if Last-Modified in res.headers: etag_cache[last_modified] res.headers[Last-Modified] # 保存新 ETag 到缓存 with open(cache_file, w, encodingutf-8) as f: json.dump(etag_cache, f, ensure_asciiFalse, indent2) # 后续分页逻辑保持不变... page 1 comment_count 0 while True: # ... 原有循环体 ... # 在每次成功获取一页后更新缓存文件中的 timestamp etag_cache[last_update] time.strftime(%Y-%m-%d %H:%M:%S, time.localtime()) with open(cache_file, w, encodingutf-8) as f: json.dump(etag_cache, f, ensure_asciiFalse, indent2)5.3 用diff工具对比两次 CSV定位新增/删除评论增量爬取后生成的 CSV 文件名可带上时间戳csv_name f{comment_path}/{mid}_{int(time.time())}.csv然后用 Python 的pandas做差异分析import pandas as pd old_df pd.read_csv(f{comment_path}/{mid}_20240410.csv) new_df pd.read_csv(f{comment_path}/{mid}_20240411.csv) # 找新增评论新有旧无 new_comments new_df[~new_df[wid].isin(old_df[wid])] # 找删除评论旧有新无 deleted_comments old_df[~old_df[wid].isin(new_df[wid])] print(fNew comments: {len(new_comments)}, Deleted: {len(deleted_comments)})这样你的爬虫就从“数据搬运工”升级为“舆情变化探测器”。从那以后我每次部署微博爬虫都强制走一遍ETag缓存校验 csv差异分析流程哪怕只是监控一条重点微博。因为真正的业务价值从来不在“拉了多少条”而在“哪一刻发生了什么变化”。希望帮到你。本文还有配套的精品资源点击获取