简介自动发布微博并自动回复评论的Python脚本包面向需要微博自动化运营的学生、站长及开发者基于HTTP接口实现适合具备Python基础、想快速搭建个人微博机器人的读者。资源压缩包共4个文件包含2个Python脚本、1个Markdown说明和1个Git忽略文件整体仅5KB结构精简。两个脚本分别覆盖微博发布与评论回复场景README详细说明了授权码获取及变量修改方法可帮助理解新浪微博开放接口的调用流程和token授权机制。该脚本为华中大导航网而写具备真实应用背景且预留了抓取与发布时间的自定义入口便于根据自身需求二次调整。已有1268人学习下载通过阅读源码与说明还能掌握授权有效期管理、客户端AppKey替换等注意事项是一份入门微博API自动化的实用参考。1. 用 Python 脚本自动发微博和评论它到底替你做了什么我维护过几个需要每天更新内容的微博账号最早全是手工操作直到某一天发现自己半天时间全耗在重复点击上。weibo-auto 这个项目要解决的就是这个痛点一套纯 Python 的 HTTP 脚本把发微博和发评论这两类高频操作变成程序化调用不需要打开浏览器页面。它的实现方式是模拟登录后的网页端请求没有浏览器驱动依赖运行占用资源很小部署到一台低配服务器上就能每天定时跑。适合批量维护账号的内容从业者、做接口自动化测试的工程师以及想理解模拟登录和 cookie 机制的新手。相比官方 API这个方案权限门槛低、频次限制可控灵活性高很多。2. 技术选型与前置准备为什么选 Python 裸 HTTP不碰浏览器内核2.1 三条技术路线的真实对比做微博自动化之前我实际尝试过三条完全不同的路线最后才定下来用 Python 的 requests 库直接写 HTTP 请求。第一条是官方开放平台 API。它的问题是权限获取门槛极高个人或小团队很难申请到发布和评论接口的调用权限。即便用测试应用跑通发布接口的调用频次也是按天计算的默认只允许少量调用和批量运营的场景天然冲突。我在这条路上卡了两周后基本放弃。第二条是 Selenium 无头浏览器。它模拟的是真实浏览器内核理论上能操作页面上的任意元素但代价是资源占用过高。一个无头浏览器实例要吃 300MB 到 500MB 内存同时跑三个账号一台 2GB 内存的云主机就会开始频繁交换内存操作延迟变得完全不可控。而且无头浏览器在微博的风控画像里特征很明显请求频率稍高就会被要求验证。第三条就是裸 HTTP 请求模拟。核心逻辑是浏览器能发的请求requests 库也能发只要请求头和 cookie 一致服务端不会去区分请求到底来自哪里。这个方案内存占用不到 60MB一次发布请求从构造到拿到返回结果在 1 秒内完成部署时只依赖 Python 环境和 requests 库不用安装任何浏览器组件。import requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept: application/json, text/plain, */*, Referer: https://weibo.com })Session 对象是整套脚本的会话容器它会自动保存服务端返回的 cookie并在后续请求中自动携带。header 里的三个字段各有分工User-Agent 让服务端认为请求来自 Windows 平台的 Chrome 浏览器Accept 声明只接收 JSON 和纯文本内容Referer 告诉服务端这次请求是从微博页面内部发起的。最开始调试时我把 Referer 删掉试过一次发布接口直接返回 403加回去之后一切恢复正常。2.2 HTTP 方案的边界哪些能自动化哪些不能定义清楚边界很重要。在微博网页端 HTTP 方案里请求构造简单、响应结构稳定的操作才适合做自动化。适合自动化的是发布、评论、转发、点赞、关注这类文本型操作。它们参数数量少返回数据中通过一个状态字段就能判断是否成功。图片上传这类媒体型操作也可以通过 HTTP 完成只是需要先走一次文件上传拿到图片 ID再填入发布参数属于两步操作。不适合自动化的是图形验证码识别、滑块拼图、扫码登录这类交互式验证。它们的设计目的就是阻止脚本运行在纯 HTTP 层面没有稳定解法硬做只会让账号进入更严格的验证状态。我在实际使用中还会主动避免自动化这几类操作私信发送频率限制比其他接口严得多、直播相关操作接口参数变动频繁、以及账号安全设置类操作不可逆且风险高。边界清晰之后设计脚本结构时就能避开那些表面能做、实际上会让账号变危险的错误方向。2.3 环境依赖与代码组织这套脚本的环境依赖非常轻。运行时只需要 Python 3.7 以上版本第三方库只用到 requestsjson、time、random、re 全部来自标准库。pip install requests安装之后我建议按功能拆成四个模块来组织代码而不是把所有逻辑堆在单个脚本里。见过不少项目把所有代码放在一个文件里结果接口一变动整个文件都要排查。按功能拆分之后接口变动时只需要定位到对应模块修改一次。文件职责改动频率config.py账号配置、cookie、间隔参数每次更换账号时login.py构建带登录态的 Session极低actions.py发布、评论、上传图片等接口封装接口变动时scheduler.py调度、频率限制、任务队列按需调整config.py 是整个脚本的入口配置账号相关信息和 cookie 都在这里维护。login.py 只负责登录态构建返回一个可用的 Session 对象。actions.py 里是每个接口对应的请求函数一个函数做一件事。scheduler.py 控制整体节奏包括请求间隔、任务调度和日志记录。我一般在 config.py 里还会预留调试开关打开后脚本会把每次请求的 URL 和返回前 100 个字符输出到控制台方便定位问题。2.4 登录态从哪里来最稳微博网页版的账号密码登录流程中密码在提交前需要经过加密而这个加密算法在不同时期有不同版本的实现。与其投入时间逆向加密参数不如采用一个更稳定的做法用浏览器手动登录一次然后把登录后的 cookie 复制出来交给脚本使用。def build_session_from_cookie(cookie_str): 把浏览器复制出的 cookie 字符串转成已登录的 Session session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://weibo.com }) for item in cookie_str.split(; ): key, _, value item.partition() session.cookies.set(key, value) return sessioncookie 字符串的标准格式是多个键值对用分号加空格连接例如 SUBxxx; SCFyyy。循环里用 partition() 把每一段拆成键和值再写入 Session 的 cookie 容器。这里不用 session.headers 直接设置 Cookie 字段的原因在于Session 的 cookie 容器支持自动更新而 headers 里的固定值不会跟随服务端返回的 Set-Cookie 更新。长时间运行后头部旧 cookie 与容器中的新 cookie 可能产生冲突导致偶发性的未登录响应。cookie 里最关键的字段是 SUB 和 SCF。SUB 携带登录凭证SCF 携带安全校验信息两者中任何一个失效后续请求都会跳转登录页。实测下来 cookie 有效期在 3 到 7 天不等。config.py 里我单独预留了 cookie 配置位过期后重新复制替换其他代码完全不用动。3. 核心实现拆解发布、评论与图片上传的完整请求链3.1 发布微博构造参数与校验返回发布微博的接口是一个 POST 请求核心参数是正文内容 text附带一些页面定位参数。接口返回 JSON 数据ok 字段用来判断是否成功。def post_weibo(session, text, pic_ids): url https://weibo.com/ajax/statuses/update data { text: text, pic_id: pic_ids, since_id: , location: page_100505_home, module: stissue, act: stissue } resp session.post(url, datadata) resp.raise_for_status() return resp.json() result post_weibo(session, 这是一条来自 weibo-auto 的测试微博) if result.get(ok) 1: weibo_id result[data][id] print(发布成功微博 ID:, weibo_id) else: print(发布失败:, result)data 里的参数按作用分三类text 是发送的正文pic_id 是可选的图片 ID 列表空字符串表示不带图片其余参数是页面定位信息用来告诉服务端这次提交来自哪个页面模块。这些定位参数第一次调试时不需要修改保持默认值即可。requests 会自动对表单数据做编码text 字段不需要手动调用 urlencode。返回数据中 data.id 是这条新微博的完整 ID后续如果要自动评论自己刚发的微博这个值可以直接拿来用。微博正文有长度上限超过 2000 字的部分不会被服务端接收脚本在调用前应该加一次字符数检查。我习惯用 len(text) 做判断超过 2000 就拆段发送保证内容不会莫名丢尾巴。3.2 评论微博与发布接口的三个差异评论接口和发布接口在参数结构上有明显区别。评论必须携带被评论对象的 ID而且提交的内容字段名是 content不是 text。def comment_weibo(session, weibo_id, content): url https://weibo.com/ajax/statuses/comment data { id: weibo_id, content: content, comment_type: status, is_comment_ori: 1, act: comment } resp session.post(url, datadata) resp.raise_for_status() return resp.json() comment_result comment_weibo(session, 1234567890123456, 自动化评论测试内容)评论接口与发布接口至少有三个差异点。第一个是 id 参数必须是被评论微博的 16 位完整 ID不能是页面 URL 里的短 ID。第二个是 content 字段名和发布接口的 text 不同提交错误字段名会直接返回参数错误。第三个是评论有层级概念is_comment_ori 为 1 表示直接评论原微博为 0 表示回复已有评论后者还需要额外传入被回复评论的 ID 字段。评论接口对频率的限制比发布更敏感。同一个账号连续快速提交评论很容易触发服务端风控。我在评论调用之间强制加了 8 秒以上的随机间隔并且把评论失败的错误码单独记录到日志文件方便事后分析触发条件。代码里 comment_type 参数保持 status 即可有些接口版本会要求传 comment遇到后再调整不迟。3.3 图片上传先拿到 pid 再发微博带图片的微博需要先调用图片上传接口拿到服务端返回的图片 ID再填入发布参数。图片上传走的是 multipart/form-data 格式与文本提交的编码方式不同。def upload_image(session, image_path): url https://weibo.com/ajax/uploadimg with open(image_path, rb) as f: files {file: (image_path, f, image/jpeg)} resp session.post(url, filesfiles) resp.raise_for_status() data resp.json().get(data, {}) if not data or pid not in data: raise RuntimeError(图片上传失败: resp.text) return data[pid] pid upload_image(session, cover.jpg) post_result post_weibo(session, 带图测试微博, pic_idspid)files 参数构造了一个三元组分别是文件名、文件对象和 MIME 类型。requests 会根据这个元组生成正确的 multipart 请求体。上传成功后的返回值里data.pid 是服务端保存图片后生成的唯一标识。一个容易踩的细节是pid 不是本地文件名也不是图片 URL而是服务端分配的内部 ID。上传之后如果想替换图片需要重新走一遍上传流程拿到新 pid不能手动拼接。如果图片格式是 PNG把 MIME 类型改成 image/png 即可或者直接省略这个参数让 requests 根据文件名推断实际测试两种方式服务端都接受。3.4 把发布、评论、上传串成一条完整任务链单个接口的调用函数写好后最自然的做法是把它们串成一条业务流程。我通常写的流程是构建登录会话 → 上传封面图 → 发布主微博 → 用微博 ID 发一条评论 → 等待随机间隔 → 处理下一条任务。def run_task(session, text, image_pathNone, comment_text): pic_ids if image_path: pic_ids upload_image(session, image_path) post_result post_weibo(session, text, pic_ids) if post_result.get(ok) ! 1: return False weibo_id post_result[data][id] if comment_text: comment_weibo(session, weibo_id, comment_text) time.sleep(random.uniform(8, 15)) return Truerun_task 函数把三个单步操作组合成一个可复用的任务单元。先上传图片取 pid再带 pid 发微博最后用新微博的 ID 发一条自动评论。这里的关键是发布和评论共用同一个 Session 对象登录态贯穿整个任务链。我在实际使用中会把 run_task 的返回值记录到日志文件里包含任务执行时间、微博 ID、评论是否成功。后期如果发现某条微博没有对应评论可以从日志里快速定位是哪一步失败。调度循环里只需要按队列顺序调用 run_task每个任务结束后用延时器等待随机间隔避免连续请求触发风控。4. 避坑与排查五个在真实环境中踩过的坑4.1 发布返回 ok 为 0但没有错误提示现象请求正常返回 HTTP 200JSON 里的 ok 字段是 0但 error 字段为空字符串完全没有排查线索。原因多数情况下是正文中包含了服务端风控过滤的词或者内容里有特殊字符被静默拦截比如连续的数字串、可疑的外部链接、高频表情符号。解决把 text 参数临时改成两个字的纯文本如果发布成功说明问题确实出在原文内容上。逐步删除内容中的特殊字符、URL 和 符号二分定位到具体触发点。我一般会在脚本里内置一个简单的过滤函数import re def sanitize_text(text): # 只保留中英文、数字、空白和常见标点其余替换为空格 return re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s。], , text)这个正则把不可见控制符、emoji 和特殊符号统一替换成空格减少偶发性的拦截概率。注意这只适用于纯文本内容如果业务上必须带图片或链接过滤逻辑要单独放开白名单。4.2 请求返回 403 或跳转到登录页现象某次请求的返回内容不是 JSON 而是 HTML或者状态码是 403响应体里出现登录跳转链接。原因cookie 过期是常见原因。SUB 或 SCF 字段失效后服务端认为当前请求未登录不再返回正常接口数据。解决先访问一个轻量接口检查登录态如果返回内容里出现登录关键字说明需要更换 cookie。我在脚本里维护了一个登录状态检查函数每次任务开始前先确认会话可用而不是等发完微博才发现登录失效。def check_login(session): try: resp session.get(https://weibo.com/ajax/profile/info, timeout10) data resp.json() return data.get(ok) 1 except Exception: return False这个检查函数在任务队列头部调用返回 False 就直接跳过当前任务批次并告警。每次更换 cookie 后建议在 config.py 里标注时间方便推算下一年过期日。不要尝试在脚本里自动刷新 cookie网页端目前没有稳定的无感续期机制。4.3 触发安全验证现象发布或评论接口返回数据中出现 captcha 相关字段页面要求输入验证码才能继续操作。原因同一账号短时间内操作过于密集触发了服务端风控阈值。以我实际观察发布超过 5 条/分钟或者评论超过 3 条/分钟时很容易触发。解决在请求之间加随机间隔不要用固定间隔。固定间隔会被识别出机器节奏随机间隔更接近人的行为模式。如果已经触发验证立刻停止该账号的所有请求等待 10 到 15 分钟再继续。冷却时间不够就硬顶着发会显著加重风控处罚严重时账号会被要求重新验证登录。脚本里要做的不是尝试解答验证码而是检测到 captcha 字段后把当前任务暂停、写入待处理队列并发送一条告警通知。人工处理完被拦截的任务后脚本从队列里继续执行后续任务。4.4 评论的 ID 提取出错现象评论请求返回正常但登录后查看评论列表发现评论没有出现在预期的微博下方日志里显示 id 不存在。原因从页面 URL 复制微博 ID 时带上了多余字符。微博的标准链接中 ID 后面可能跟有 _、?from 或者自定义后缀不做过滤直接取用就会失败。解决用正则做严格提取只保留 16 位数字。import re def extract_weibo_id(url): match re.search(r\d{16}, url) if not match: raise ValueError(链接中未找到 16 位微博 ID) return match.group()16 位数字的强匹配可以过滤掉绝大多数干扰后缀。如果匹配不到直接抛异常终止操作而不是带着脏数据继续请求。这个思路也适用于提取评论 ID 和用户 UID只是正则里的位数需要相应调整。比如评论 ID 不一定是 16 位提取前先通过接口拿到数据结构再确认位数。4.5 部署到服务器后请求超时变多现象脚本在本地电脑上运行一切正常部署到云服务器后请求频繁超时有时还会遇到连接被重置。原因数据中心的出口 IP 段与家庭宽带不同服务端对数据中心 IP 的信任度偏低连接更容易被中断或加入限速队列。解决给 requests 设置合理的超时参数并加一个轻量重试机制。def request_with_retry(session, method, url, **kwargs): for attempt in range(3): try: resp session.request(method, url, timeout(5, 15), **kwargs) if resp.status_code 200: return resp except requests.exceptions.RequestException: if attempt 2: raise time.sleep(10)timeout(5, 15) 表示连接阶段最多等 5 秒读取响应最多等 15 秒。分开设置比单个值更合理因为连接超时和读取超时的失败原因完全不同。重试次数控制在 2 次间隔 10 秒超过后直接抛异常。重试次数太多反而会让服务端认为这个 IP 在频繁尝试加剧问题。5. 把脚本变稳频率控制、验证码兜底与多账号管理接口层面能跑通只是第一步真正让脚本连续运行数周不出问题靠的是频率控制、验证码兜底和多账号管理这三件事。频率控制的核心是让请求间隔随机化。固定 5 秒的间隔看似安全但服务端会识别出规律的机器节奏。我实现了一个简单的间隔控制器每次请求前计算随机偏移保证操作之间不是等距的。class RateLimiter: def __init__(self, min_interval, max_interval): self.min_interval min_interval self.max_interval max_interval self.last_request_time 0 def wait(self): now time.time() elapsed now - self.last_request_time wait_time random.uniform(self.min_interval, self.max_interval) if elapsed wait_time: time.sleep(wait_time - elapsed) self.last_request_time time.time()发布和评论分别设置不同的间隔区间发布用 10 到 20 秒评论用 8 到 15 秒。这样即使多个任务同时排队请求时间也不会重叠在同一个秒级窗口里。random.uniform 生成的浮点秒数比整数秒更难被识别一个环节的小优化叠加起来能让脚本存活时间拉长不少。验证码兜底采取人工协作的姿态。脚本检测到返回数据里有 captcha 字段时先把任务暂停并写入待处理队列同时把拦截日志单独记录下来。不要尝试在脚本里堆 OCR 模型或者接图库识别服务验证码的形态随时可能变化投入产出比很低。我一般每天检查一次拦截日志用浏览器处理完被卡住的任务后脚本继续从队列里跑后续任务。多账号管理上我为每个账号维护独立的配置片段包括 cookie、间隔参数、每日任务上限。线程之间完全隔离每个账号的线程持有自己的 Session 和 RateLimiter 实例。同时给每个账号设置硬性上限发布每天不超过 20 条评论每天不超过 30 条超过后该账号自动跳过任务到第二天再跑。这套脚本在项目里跑了一段时间后我在一次更换账号时因为没做测试就直接上线第二天账号被要求重新验证登录整批任务卡了大半天。从那以后我每次换账号、换服务器或者改动请求参数都会强制走一遍测试账号全流程验证确认发布和评论都正常返回后再切到正式环境。日志也坚持留着每个任务的时间、结果、失败原因都有记录排查问题不用靠猜。希望这套流程对你能有帮助。本文还有配套的精品资源点击获取