☰
微博自动化脚本实战:用HTTP模拟请求实现发博与评论
2026/10/13 4:53:52 网站建设 项目流程

简介:面向新浪微博自动化运营的轻量 Python 工具,适合需要定时发布微博、自动对关注者评论进行回复的开发者或站点运营者。脚本以 HTTP 方式调用微博接口,围绕授权码完成登录态管理,并预留了抓取时间、发布时间、授权码等变量供修改;针对苹果客户端授权有效期差异,也给出了替换 APPKEY 的适配说明,方便扩展维护。资源包共 4 个文件,包含 2 个 Python 脚本、1 个 Markdown 说明文档和 1 个 Git 忽略配置文件,整包仅 5KB,结构精简。Python 脚本分别承担微博发布与评论回复逻辑,Markdown 说明文档提供授权码获取与配置指引,Git 忽略配置便于按规范管理项目。已有 1268 人学习下载,适合刚接触微博 API 或爬虫/自动化方向的 Python 学习者拿来改造二次开发。

1. weibo-auto 在做什么:不碰官方 API,用 HTTP 把发博和评论串成自动化脚本

微博的开放平台现在基本不向个人开发者提供免审核的内容发布接口,想拿到发博、评论的正式权限要过企业认证加接口审核,周期长、成功率低。于是很多做自动化运营的人绕开了 SDK,直接模拟网页端浏览器发出的 HTTP 请求:本机装一个 Python 脚本,带上自己的登录 Cookie,把「发一条微博」「给某条微博评论」变成两个 HTTP 调用,再通过任务队列、定时器把它们编排起来。这就是 weibo-auto 这类脚本的核心思路:不是黑科技,而是把浏览器动作降维成可编程的请求。

这类脚本能解决的问题非常具体:定时发微博、批量回复评论、活动期间自动互动、同一内容多号分发。适合的对象也很明确——有自己的微博账号、懂一点 Python、愿意接受「接口可能随前端改版而失效」这个前提的从业者。它不适合拿来搞营销轰炸,微博的风控对评论频率极其敏感,后文会专门讲怎么控节奏。要把这套脚本真正跑起来,需要落地三件事:登录态怎么进脚本、发布和评论两个接口怎么调、风控来了怎么退。下面按这个顺序拆开讲。

2. 登录态和请求头:weibo-auto 能跑起来的两个前提

2.1 为什么只能走 HTTP 网页端而不是 SDK

有人在动手前会纠结一个问题:既然微博有官方 SDK,为什么不用?答案很简单:官方 SDK 面向的是申请了开发者资质、创建了应用的账号,个人脚本想要的是「用自己的账号发自己微博上的内容」,但开放平台的发布接口要求应用必须有相应权限,个人应用默认拿不到。

另一条路是用浏览器自动化工具,比如无头浏览器模拟点击。这条路的问题是慢:每个操作都要起一个浏览器实例,内存占用高,登录态管理复杂。而网页端的所有操作本质上是浏览器往服务器发几个固定格式的请求,把这条链路直接用requests复现出来,请求数量级从几十个降到几个,单次操作耗时从秒级降到百毫秒级。这就是 weibo-auto 这类脚本存在的理由:不做浏览器,只做 HTTP。

明白了这一点,整个方案的地基就清楚了一半:你只需要两个东西——一个带着登录身份的会话,一个正确的请求格式。前者靠 Cookie 和 token 解决,后者靠抓包工具观察网页端真实发出的请求解决。

2.2 把浏览器 Cookie 和 XSRF token 接进 requests

微博网页端的前端框架里,所有写操作(发博、评论、转发)都会带一个 token 参数,这个参数叫 XSRF-TOKEN,在登录后的 Cookie 里就能找到。也就是说,登录态 = Cookie 里的 SUB/SSXT 等字段 + XSRF-TOKEN 这个特殊 Cookie,两者缺一不可。

常见做法是先在浏览器里登录微博,然后从开发者工具的 Network 面板里复制请求头中的 Cookie,存成本地文件,脚本启动时读入。我不建议把 Cookie 硬编码在脚本里,因为 Cookie 有生命周期,过期了要重新复制,存成独立文件方便替换。

# cookie_loader.py import json from pathlib import Path def load_cookies(path: str) -> dict: """从浏览器导出的 cookies 文件加载登录态 文件格式:每行一条 cookie,key=value,用分号拼接。 也可以直接用浏览器插件导出为 JSON 后读入。 """ raw = Path(path).read_text(encoding="utf-8").strip() cookies = {} for item in raw.split(";"): item = item.strip() if "=" in item: k, v = item.split("=", 1) cookies[k] = v return cookies

这段代码做的事是把浏览器里复制出来的 Cookie 字符串转成字典。split("=", 1)里这个1很关键:有些 Cookie 值里本身带等号,不限制分割次数会把值切碎。很多第一次写脚本的人在这里翻车,解析出来的 Cookie 缺了后半截,请求发出去一直提示未登录,排查半天。

2.3 最小请求模板与 UA 伪装

微博的接口层面会校验请求头,至少要带 User-Agent 和 Referer。浏览器发出的请求里,UA 是完整的 Chrome/Firefox 标识,Referer 是当前页面地址;脚本里这两项要尽量贴近真实浏览器,否则可能被 CDN 层的风控直接拦掉。

import requests UA = ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36" ) def build_session(cookie_dict: dict) -> requests.Session: s = requests.Session() s.headers.update({ "User-Agent": UA, "Accept": "application/json, text/plain, */*", "Referer": "https://weibo.com/", "X-Requested-With": "XMLHttpRequest", }) s.cookies.update(cookie_dict) return s

这里用requests.Session而不是直接requests.post,是为了让同一个连接池复用,保持 Cookie 和 Header 的一致状态。Session 对象会自动管理 Cookie,后续的每次请求都带上登录信息,不需要手动往请求里塞 Cookie。

X-Requested-With这个头是网页端 AJAX 请求的标志,加上它之后,接口对请求来源的判断会更友好。UA 不要用太老的版本,服务器端可能会对旧版浏览器下发不兼容的页面逻辑。

3. 实现自动发布微博:抓包、参数与返回码

3.1 发布文本微博的请求链路

有了会话之后,发微博的请求本身并不复杂。网页端发布微博时,前端会向发布接口提交一组表单字段,核心字段是微博正文文本,外加一些辅助字段。不同页面版本字段名会有差异,我建议第一次做的时候先在浏览器里手动发一条,在 Network 面板里看请求体,把字段原样搬过来。

这里给出一个可用的通用请求结构,字段名以实际抓包为准:

def publish_weibo(session, text: str) -> dict: """发布一条纯文本微博""" url = "https://weibo.com/ajax/statuses/publish" form = { "text": text, "location": "v6_content_home", "is_pic": "0", "style_type": "1", "publish_time": "0", "api": "/ajax/statuses/publish", } # XSRF token 从 Cookie 中读取 token = session.cookies.get("XSRF-TOKEN") if token: form["_token"] = token resp = session.post(url, data=form) return resp.json()

登录 Cookie 里的 XSRF-TOKEN 值就是发送写请求时需要的_token参数,这个字段缺失时接口会返回_token相关的错误码。session.cookies.get("XSRF-TOKEN")直接读会话里的 Cookie 值,比从本地文件再传一遍更省事。

请求体建议用data=以表单形式提交,微博网页端的接口接收的是application/x-www-form-urlencoded格式,不是 JSON。如果用了json=参数,请求体变成application/json,接口可能不识别,返回成功但实际没发出去。

3.2 图文微博要先上传拿 pid

纯文本微博比较简单,带图就多一个前置步骤:先把图片上传到微博的图床接口,拿到一个 pid(图片 ID),再把 pid 填进发布请求里。图片上传接口接收的是文件流,返回结果里包含的 pid 就是后续发布时pic_ids字段的值。

def upload_image(session, image_path: str) -> str: """上传图片,返回 pic_id""" url = "https://weibo.com/ajax/pic/upload" # files 参数里 key 名要抓包确认,不同版本可能是 pic1 或 file with open(image_path, "rb") as f: files = {"pic1": (image_path, f, "image/jpeg")} resp = session.post(url, files=files).json() # 返回结构里通常包含 pid 字段 return resp["data"]["pid"]

上传接口的返回结构在不同登录状态下略有差异,有的版本返回pic_id,有的返回pid,还有的在一个大 JSON 里嵌套多层。写脚本时不要假设固定结构,先打印一次完整返回体确认字段路径。图片大小也有上限,超过微博限制的上传接口会直接报错,本地处理时可以先压缩到 1MB 以内,能规避大部分失败。

拿到 pid 之后,发布请求里加一个pic_ids字段,多个图用逗号拼接。同时把is_pic改成1,否则带 pid 也不一定触发图片展示逻辑。

def publish_with_pic(session, text: str, image_path: str) -> dict: pid = upload_image(session, image_path) url = "https://weibo.com/ajax/statuses/publish" token = session.cookies.get("XSRF-TOKEN") form = { "text": text, "pic_ids": pid, # 多图用逗号连接,例如 "pid1,pid2" "is_pic": "1", "location": "v6_content_home", "style_type": "1", "_token": token or "", } resp = session.post(url, data=form) return resp.json()

注意pic_ids的格式:即使只传一张图,这个字段也不能是空字符串,否则图片不会显示。多图上传时逐张调用上传接口拿 pid,再拼接成逗号分隔的字符串,顺序就是图片在微博里的展示顺序。

3.3 返回值校验与重试策略

发布接口的返回体里,code字段等于 0 才算成功,非 0 值的含义各不相同。很多脚本只检查 HTTP 状态码 200 就当成成功,这是最大的坑:接口返回 200 但 code 非 0 时,微博实际没有发布。

def publish_and_verify(session, text: str) -> bool: """发布并校验返回码,失败时记录原因""" result = publish_weibo(session, text) if result.get("code") == 0: return True # 常见的非 0 code 处理 error_code = result.get("code") if error_code in (501, 502): # 登录过期或触发风控,需要人工介入 raise RuntimeError(f"登录态失效或风控,code={error_code}") if error_code == 100003: # 内容含敏感词 print("内容被拦截,检查正文文本") return False print(f"未知错误: {result}") return False

重试策略要克制。常见做法是失败后等 30 秒再重试一次,连续两次失败就停止,而不是无限循环。接口失败大概率是内容问题或登录态问题,重试次数多了只会加重账号被风控的风险。我自己做的时候倾向于「失败两次就断,留日志人工看」,这比反复重试更保护账号。

4. 自动评论的完整实现:动态频控下的节奏控制

4.1 评论接口的入参和 URL 里微博 ID 的三种形态

评论接口和发布接口类似,也是一次表单提交,但入参更讲究。评论时前端要提交两个核心参数:被评论微博的 ID 和评论内容。问题在于,微博网页版里一个链接上的 ID 可能是三种形态:bid(短链 ID,形如 Lxxxxx)、mid(数字 ID)、id(发布成功时返回的内部 ID)。评论接口接收的通常是mid或内部id,直接把 URL 里的 L 开头的短链 ID 传过去会报参数错误。

def extract_mid_from_url(url: str) -> str: """从微博详情页 URL 里抽出数字 mid 例如 https://weibo.com/1234567890/LxYzAbC 返回 LxYzAbC 但评论接口需要的是 mid,需要调用解析接口转换 """ import re m = re.search(r"/\d+/([A-Za-z0-9]+)", url) if not m: raise ValueError("无法从 URL 中提取微博 ID") return m.group(1)

拿到短链 ID 后,要再走一次解析接口拿到数字 mid。这个解析接口会返回该微博的完整信息,包括id、mid、user等,评论时用id字段最稳。

def get_status_info(session, bid: str) -> dict: """根据 bid 获取微博详情,返回数字 ID""" url = "https://weibo.com/ajax/statuses/show" params = {"bid": bid} resp = session.get(url, params=params).json() return resp["data"]

详情接口返回的data里的id就是评论接口要的cid(被评论微博的 ID)。填mid也能用,但不同版本兼容性有差异,实测下来id全版本通用。注意这里用的是 GET 请求,参数放params里。

4.2 延时队列:随机间隔比固定间隔更稳

评论是最容易触发风控的操作,频率判断比发布严格得多。短时间内连续评论、多次评论相同内容、评论文本里带明显营销词,都会被拦。应对方法不是「找更隐蔽的接口」,而是设计一个合理的执行节奏。

固定间隔 5 秒一条看起来均匀规整,但机器特征非常明显,而且一旦被识别,风控系统会对固定间隔样本做更精准的拦截。随机间隔的好处是行为分布更接近人工操作。我在自己的脚本里用了一个简单的区间随机:

import random import time from datetime import datetime def wait_random(min_sec: float = 3.0, max_sec: float = 8.0): """随机等待,模拟人工阅读节奏""" interval = random.uniform(min_sec, max_sec) now = datetime.now().strftime("%H:%M:%S") print(f"[{now}] 等待 {interval:.1f}s 下发下一条评论") time.sleep(interval)

random.uniform产生的是均匀分布随机浮点数,3 到 8 秒的区间是我自己用的默认值。如果是给活跃账号做互动,可以放宽到 5-15 秒;如果是新注册的账号,前一周建议单次任务最多 10 条评论,间隔 20 秒以上。

另一个要注意的细节是「评论内容的可变性」:完全相同的评论内容连续发 10 条,就算间隔正常也容易被标记。常见做法是准备一个同义句列表,随机取用,比如「学习了」「赞同这个观点」「很有启发」交替出现。这不算规避风控,而是让脚本行为更像真实用户。

4.3 定时任务与多账号轮询

把发布和评论串起来,最常见的落地方式有两种:单账号定时执行,或者多账号接力。定时执行用操作系统的 cron 或 Windows 任务计划程序就行,不一定要在脚本里写调度逻辑;多账号轮询则需要一个账号列表,每次执行随机选一个账号使用。

# account_pool.py import random from cookie_loader import load_cookies from request_core import build_session class AccountPool: """简单的账号轮询池,支持随机选择""" def __init__(self, account_cookie_paths: list): self.sessions = [] for path in account_cookie_paths: cookies = load_cookies(path) self.sessions.append(build_session(cookies)) def random_session(self): return random.choice(self.sessions)

这里有一个实践上的边界提醒:多账号轮询的底气和前提是每个账号都有独立的登录环境,如果多个账号在同一台设备同一浏览器的同一个 Cookie 容器里换来换去,且用的是同一出口 IP,风控侧看到的是「同一设备多个账号切换」,这本身就是高风险信号。所以多账号不是抄起脚本改个 Cookie 路径就行的,要把「同设备切换」视为和「短时间高频操作」同一等级的危险行为。

5. weibo-auto 避坑记录:登录过期、评论被吞、频率锁死

5.1 明明登录成功,接口却返回 501

现象:Session 里的 Cookie 是从浏览器刚复制的,手动在浏览器里发微博也没问题,但脚本一发请求就返回 code 501,提示登录失效。

原因:多数情况下是 XSRF-TOKEN 没传,或者 Cookie 复制的时候漏了关键字段。浏览器开发者工具里复制 Cookie 时,默认复制的可能是没有XSRF-TOKEN的那一段,尤其当你在 Network 面板里看某个资源请求时,请求可能根本不带这个 Cookie。另一个常见场景是账号同时在 Web 端和移动端登录,Web 端会话被新登录挤掉了。

解决:打印出session.cookies.get("XSRF-TOKEN"),如果是 None,说明 Cookie 里根本没这个字段。回到浏览器,刷新首页,重新复制完整 Cookie。顺手看一下 Cookie 里有没有SUB字段,没有的话登录态本来就是无效的。

5.2 发布成功但评论消失,或被系统提示「操作频繁」

现象:评论接口返回 code 0,页面上也显示评论发送成功,但刷新页面后评论不存在;或者评论几次之后,接口开始返回「操作频繁」。

原因:这两件事其实是一个机制的两个阶段。「评论发送成功但消失」说明内容通过了接口校验,但进入了后台审核,被审核系统过滤掉了,常见触发点是内容含链接、联系方式、或者重复度太高。「操作频繁」则是频控触顶的信号,前面说的随机间隔没挡住阈值。

解决:内容层面,评论里不要放 URL,尽量不要和存量评论完全重复。频控层面,把等待区间的下限提高到 8 秒以上,同时限制单个账号单小时的评论总数。我在实际项目里给某账号做互动时,用过「每 5 条评论后休息 3 分钟」的节奏,比连续慢速评论效果好很多。

5.3 固定间隔安插的请求被要求验证码

现象:脚本运行了几天之后,突然某次请求返回了验证码页面,返回体里出现验证码链接相关字段,脚本 log 里全是异常 JSON 解析错误。

原因:到这一步,账号已经被风控系统重点标记了,验证码是干预升级前的最后一道闸。多数情况下是因为运行了一段日子的脚本一直维持同样的请求特征——同样的 UA、同样的头部顺序、同样的执行时间点,机器指纹太稳定。

解决:遇到验证码不要尝试自动破解,最稳妥的做法是脚本检测到验证码明文后立刻停止,打印提示让人工登录一次账号,再手动解除验证。硬要绕过验证码的成本和风险都很高,不是个人脚本该承担的事。停止脚本后,浏览器登录同账号,完成验证,第二天再重新复制 Cookie 继续跑。

5.4 内容里带链接被「安全提示」降权

现象:评论或微博里包含一条普通链接,接口返回成功,但内容只有自己可见;切小号看,发现博文确实发出去了,但没有任何互动流量。

原因:带外链的内容会进入更严格的内容审核池,非认证账号的外链权重本来就低,新号带链接发出去大概率被限制曝光。这不是技术问题,是平台规则问题。

解决:评论内容里不带任何 URL,需要留联系方式时先引导用户评论一个预设词,再私信发送。发布微博需要带链接时,先发纯文本,再编辑追加链接,被拦截的概率比首条直接带链接低一些。这条是血泪经验,项目里因为这个翻车三次才总结出来。

5.5 长时间运行后 Cookie 静默失效也没报错

现象:脚本的任务队列还在跑,每条请求返回的都是成功 JSON,但点开账号一看,什么内容都没发出去。

原因:最阴的坑——接口返回的 code 是 0,但发布的内容实际没生效。这种情况通常是登录态已经失效,但某些接口对失效会话返回的是「假成功」响应,或者脚本把data里的提示误读成了成功。脚本只判断 code 字段,没检查返回体里是否包含真实的内容 ID。

解决:发布成功的判定要加一层:返回体里必须有本次发布内容的 ID,result["data"]["id"]存在才算真成功。没有拿到 ID 就按失败处理,打印返回体全文用于排查。这个黑匣子状态坑了多少脚本,值得把它写进校验函数的第一行注释里。

6. 最后一步:日志、验证与健康检查,让脚本能自己报病

6.1 每条操作留结构化日志

脚本跑在无人值守的机器上,最怕的不是报错,而是「报错了你都不知道」。写日志时不要只打印一句话,要留足够上下文:操作类型、目标 ID、返回 code、返回体摘要、时间戳。日志文件按天滚动,保留最近 7 天,方便回溯。

import json import logging from datetime import datetime logging.basicConfig( filename=f"weibo_auto_{datetime.now():%Y%m%d}.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", ) def log_action(action: str, target: str, resp: dict): logging.info(f"{action} target={target} code={resp.get('code')} data={json.dumps(resp, ensure_ascii=False)[:200]}")

6.2 用断言式检查替代「看起来成功」

写脚本时养成一个习惯:每个关键步骤结束都做一次校验,校验失败就抛异常,让任务队列停下来。这个「fail fast」的思路比跑完再检查日志更省心。发布后校验 ID,评论后校验返回的评论 ID,上传后校验 pid 存在,三层校验都通过才继续下一步。

def publish_strict(session, text: str) -> str: result = publish_weibo(session, text) assert result.get("code") == 0, f"发布失败: {result}" try: status_id = result["data"]["id"] except (KeyError, TypeError): raise RuntimeError(f"返回体里没有内容 ID,疑似假成功: {result}") return status_id

这里用assert当防线,实质是把「返回码正确」和「返回数据合理」两个校验合并,缺一即失败。日志里输出返回体前 200 个字符,既能定位问题又不刷屏。

6.3 每天的产出核对与收尾

脚本维护到后期,我养成了一个习惯:每天早上花两分钟看一眼昨天的日志文件,数一下成功和失败的数量。数据最直观,如果某天成功率从 95% 掉到 70%,不用分析代码,先把 Cookie 换掉试试,多半是登录态出问题了。

这个方案值不值得做,取决于你的使用频率。如果只是偶尔发几条定时微博,手动在浏览器里设个定时发送就够了;但如果你有多个账号要维护运营节奏,weibo-auto 这种 HTTP 脚本一晚上能省下一个小时的重复操作。坑确实有——接口改版、Cookie 失效、频控升级,但把这些机制写进校验和日志之后,脚本的稳定性完全可以达到长期运行的级别。

最后给一条我自己的教训:脚本越自动化,越要留一个「手动总开关」。我踩过最重的一次,是脚本在凌晨因为 Cookie 失效循环重试,一晚上把账号的评论频控彻底触顶,第二天整个账号互动被限制。从那以后,脚本里永远有一个全局停用标志,连续失败三次就自杀式停止发通知。这份克制,是维稳的第一课。希望帮到你。

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

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

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

立即咨询