
直接进入正题。做视频号相关的自动化以前第一反应就是上Selenium模拟浏览器、打开页面、手动扫码、然后拿着Cookie去跑脚本。这套流程本身没毛病但用久了你就知道Selenium在这个场景里有多别扭内存吃满、启动慢、WebDriver特征太明显、跑在服务器上还得装无头浏览器。后来我把整个登录流程拆了一遍发现微信视频号的扫码登录本质上就是一次普通的HTTP交互用纯Python加Requests完全可以搞定而且实测下来更稳定、更轻量。这篇文章就把我完整跑通的做法、核心代码和踩过的坑都整理出来希望对你有用。这个方案适合谁如果你在写视频号相关的数据脚本、需要在服务器上维护登录态、或者已经受够了Selenium的资源占用那这篇文章就是给你准备的。不需要你会复杂的浏览器自动化只要懂一点Python基础能跑Requests跟着做就能拿到一个长期可用的登录Cookie。1. 为什么我放弃Selenium改用Requests1.1 Selenium在自动化登录场景的三大痛点先说内存。Selenium启动一个真实的Chrome实例默认情况下光浏览器进程就要吃掉三四百MB内存再加上WebDriver和脚本本身的资源占用跑在2G内存的小服务器上很容易直接把机器卡死。我之前在低配云服务器上跑过一个定时任务每隔几小时启动一次Selenium扫码登录结果内存飙升到Swap区任务直接OOM排查了半天才定位到是浏览器进程没被正确回收。再说检测问题。Selenium启动的浏览器即便你做了各种反检测配置navigator.webdriver这个属性还是会暴露你的自动化身份。微信的Web端风控对这类特征非常敏感我自己试过用Selenium登录视频号后台频繁的时候一天之内就被踢下线两三次有时候扫码刚确认完还没等脚本拿到Cookie会话就已经被标记了。第三点是维护成本。Selenium依赖本机安装的浏览器版本而WebDriver的版本必须和浏览器版本严格对应。Chrome每隔几周就自动更新一次一旦浏览器升级了老版本的WebDriver直接罢工你需要手动去下载匹配的驱动。如果是多个环境跑这个问题会被无限放大。相比之下Requests就是纯Python代码部署到哪里都一样的表现没有任何外部依赖需要维护版本对齐。1.2 纯Requests方案的核心优势纯Requests方案的本质是你直接和微信服务器进行HTTP交互而不是通过浏览器这个中间层。这个思路换了以后最直观的感受就是快。一次扫码登录的完整流程Selenium从启动浏览器到进入页面可能就要十几秒而Requests方案里获取二维码接口的响应时间通常只有几百毫秒轮询扫码状态也是轻量级请求整个登录过程五秒内就能完成。资源占用更是天壤之别。Requests方案跑起来就是几十MB内存跟Selenium动辄几百MB的占用完全不在一个数量级。这还意味着你可以放心地部署在服务器上甚至用crontab做定时任务不用再担心内存不够用。还有一个隐藏优势请求可控性。用Requests你可以精确控制每一个请求头、每一次请求的时机、代理的切换方式这些在Selenium里都是被浏览器封装好的你想干预反而麻烦。对于需要长期维护登录态的场景这种可控性极其重要。1.3 方案选型什么场景适合Requests方案当然如果你认为Requests是万能的那就错了。视频号的某些操作需要在浏览器环境里执行复杂的JavaScript逻辑比如数据大屏、互动营销工具这类重度交互功能Requests很难模拟这种情况继续用Selenium会事半功倍。我的经验是如果你的核心需求是获取登录态、然后拿着登录态去请求接口拉数据那Requests是绝对够用的如果需求是高度复杂的页面操作那最好还是老老实实用浏览器自动化。拿我自己的场景来说我需要的是一个稳定的登录Cookie用来调用视频号创作者后台的数据接口。Cookie拿到了以后剩下的请求全部走Requests跑了一个多月一直很稳定。今天这篇文章就是从这套实践里提炼出来的。2. 微信视频号登录机制与Cookie工作原理2.1 视频号登录有哪些方式视频号目前主流的登录方式是扫码登录用户在电脑端展示二维码用手机微信扫一扫完成身份确认。这种方式对用户来说最方便也最适合用纯代码来模拟因为它的整个交互链路非常清晰获取二维码 - 展示给用户 - 手机扫码 - 轮询登录状态 - 写入登录态。除了扫码还有手机号验证码登录的入口但那个方式需要接收短信自动化落地的成本更高而且视频号的手机号登录通常会绑定App内的安全校验纯代码模拟的难度比较大。所以做自动化的时候还是主推扫码登录方式。2.2 Cookie在登录态中的核心作用要理解Cookie的作用先要明白HTTP协议本身是无状态的。你发一次请求服务器处理完就忘了你是谁下一次再发请求服务器又把你当成陌生人。但是视频号后台需要识别你是谁于是就有了Cookie这种机制。服务器在验证完二维码的登录凭证之后会在响应里通过Set-Cookie字段写入一串凭证信息浏览器或者Requests会自动把这串信息保存下来在后续的每一次请求中自动带上。服务器看到这个Cookie就能判断你是已登录用户。这就是为什么Cookie对自动化脚本至关重要Cookie就是你的登录身份证。拿到它你就不需要重复扫码丢失或者过期了你就得重新走一遍登录流程。另外Cookie和Session是配套的Cookie是存在客户端的东西Session是存在服务端的东西你只需要管好CookieSession是服务端自己维护的。关于Cookie和Session的区别简单类比就是Session是服务端的储物柜Cookie是你手里的柜门钥匙。2.3 二维码登录的核心流程视频号的扫码登录本质上就是以下四步HTTP交互申请二维码客户端调用接口服务端生成一个二维码图片和对应的登录标识通常是一个uuid或者token返回给客户端。展示二维码客户端把图片显示给用户。手机确认用户用微信扫码在手机上点击确认登录。轮询状态客户端拿之前得到的登录标识反复去请求一个状态查询接口直到服务端返回登录成功。这里有个关键点整个流程中客户端一直没有被动地收到任何推送。它是通过高频轮询的方式主动去问服务端扫码了吗确认了吗成功了吗。这个轮询设计非常有意思你只要能照着这个流程走一遍Requests方案的核心就打通了。我要特别说明的是具体的接口地址和参数名称腾讯可能会调整所以你在看下面代码的时候重点看交互流程的抽象理解而不是背诵具体的URL。真到自己落地的时候打开浏览器开发者工具勾选Preserve log手动扫码登录一次就能抓到完整的网络请求然后把Requests脚本替换成你自己的接口地址就行。3. 完整代码实现Requests扫码登录获取Cookie3.1 准备工作安装依赖pip install requests qrcode pillow这里解释一下为什么要装qrcode和pillow。Requests负责HTTP请求这两个库负责二维码的展示。视频号接口返回的扫码信息通常是一个字符串内容你需要把它渲染成二维码图片才能用手机扫。qrcode负责生成二维码图片pillow是它的底层的图像处理依赖qrcode的安装会自动带上它但为了保险起见我还是显式装一下。另外如果你是在本地开发二维码可以直接弹出来用手机扫如果你是在服务器上你需要把二维码图片保存下来或者转发到手机上看。后面我会把这两种情况的处理都写出来。3.2 第一步获取二维码数据获取二维码的完整逻辑先从发起请求开始。微信这类平台的接口对请求头非常敏感所以在创建Session之后第一步就是把header配置好。import requests import time import json import os # 创建一个会话自动管理Cookie session requests.Session() # 模拟浏览器的请求头这一步极其重要 HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Content-Type: application/json, Referer: https://channels.weixin.qq.com/login.html, Origin: https://channels.weixin.qq.com, } session.headers.update(HEADERS) def get_qrcode(): 获取登录二维码和相关凭证 # 注意这里的URL是演示用实际请通过抓包获取最新的接口地址 url https://channels.weixin.qq.com/cgi-bin/xxx/getqrcode payload { appid: xxx, redirect_uri: xxx, scope: snsapi_login, state: xxx, } resp session.post(url, jsonpayload) data resp.json() # 假设返回里包含二维码内容和轮询用的凭证 qrcode_content data.get(qrcode_content) # 二维码内容字符串 session_id data.get(session_id) # 用于轮询状态的凭证 return qrcode_content, session_id这里我要强调一下Referer这个头。很多网站的反爬逻辑会检查请求的Referer是不是来自它自己的域名如果你不带或者带错了接口可能直接返回403。视频号的接口也一样Referer必须设置为https://channels.weixin.qq.com/这个域名下的页面否则很容易被拦截。至于实际接口地址我强烈建议你自己抓包一次。方法很简单打开Chrome的无痕窗口按F12进入开发者工具切到Network面板确认Preserve log是被勾选的状态然后访问视频号登录页、扫码、登录整个过程你会看到好几个XHR请求里面如果返回了类似qrcode、uuid之类的关键词那个就是获取二维码的接口。现在很多接口地址都加了时间戳、签名之类的动态参数你抓包后直接复制完整的请求地址和参数就行。3.3 第二步展示二维码拿到二维码内容字符串之后下一步就是把它变成能扫的图片。这一步用的是qrcode库import qrcode from PIL import Image def show_qrcode(qrcode_content): 把二维码内容渲染成图片并展示或保存 qr qrcode.QRCode( version1, error_correctionqrcode.constants.ERROR_CORRECT_L, box_size6, border2, ) qr.add_data(qrcode_content) qr.make(fitTrue) img qr.make_image(fill_colorblack, back_colorwhite) # 本地开发直接显示二维码图片 try: img.show() except Exception: pass # 保存图片备用 img.save(login_qrcode.png) print(二维码已保存为 login_qrcode.png请使用手机微信扫码登录)这段代码的逻辑很直白把字符串内容转成二维码图片然后直接弹窗显示同时保存一份到本地。img.show()在你本机会调用系统默认图片查看器把图片弹出来非常方便。如果你是在服务器上跑img.show()没有图形界面可用它可能会失败或者无操作但没关系因为图片已经保存到当前目录下了你可以把这张图下载下来或者推到自己的手机上扫。有个小细节值得一说box_size参数控制了二维码每个格子的像素大小如果二维码内容比较复杂或者你希望扫码识别率更高可以适当调大这个值。我自己用的默认配置在手机上扫是完全没问题的你不需要在这个参数上花太多时间。3.4 第三步轮询扫码状态这是整个流程里最核心的环节。二维码展示出去之后你需要不停询问服务端用户扫码了吗用户点击确认了吗。轮询的频率不能太密集避免触发限流但也不能太稀疏否则用户扫了码还要等你好几秒才反应体验不好。def poll_login_status(session_id, timeout120): 轮询登录状态成功则返回Cookie字典 status_url https://channels.weixin.qq.com/cgi-bin/xxx/poll start_time time.time() poll_interval 1.5 # 每1.5秒轮询一次 while time.time() - start_time timeout: params { session_id: session_id, } try: resp session.get(status_url, paramsparams, timeout5) data resp.json() except requests.exceptions.RequestException as e: print(f轮询请求异常: {e}, 1秒后重试) time.sleep(1) continue status data.get(status) # 假设状态码含义如下 # 0: 等待扫码 # 1: 已扫码等待确认 # 2: 登录成功 # 3: 二维码已过期 if status 2: print(登录成功) # 登录成功后Session中已经自动保存了Cookie return session.cookies.get_dict() if status 3: print(二维码已过期请重新获取) return None if status 1: print(已扫码请在手机上点击确认...) time.sleep(poll_interval) print(登录超时请重试) return None轮询的核心玩法就是死循环加条件跳出。状态判断的逻辑根据你抓包到的实际接口设计我这里写的三种状态只是最常见的划分方式你抓包的时候留意返回的JSON结构把状态码的含义对应好就能用。这里有几个注意点第一轮询请求我用的是session.get因为轮询本身是一个查询操作不需要提交数据。但具体是GET还是POST还是得以抓包看到的为准。第二超时处理很重要。我设置了120秒的超时时间超过这个时间用户还没扫码完成就主动取消。实际测试中用户一般10秒内就能完成扫码和确认所以你完全可以缩减到60秒。设置超时是为了防止脚本无限等下去在自动化任务里这是必须考虑的。第三session对象会自动保存Cookie。当登录成功的状态返回时session.cookies就已经拿到了完整的登录凭证不需要你去响应头里手动提取这是Requests库比手写HTTP客户端方便的地方。3.5 第四步Cookie持久化与自动加载拿到Cookie之后最重要的事情就是把它保存起来下次直接用不用再扫码。你可以把它存成JSON文件也可以存进数据库。我这里用最朴素的文件方式演示def save_cookie(cookie_dict, pathcookie.json): 把Cookie字典保存到文件 with open(path, w, encodingutf-8) as f: json.dump(cookie_dict, f, ensure_asciiFalse, indent2) print(fCookie已保存到 {path}) def load_cookie(pathcookie.json): 从文件加载Cookie并设置到Session中 if not os.path.exists(path): return None with open(path, r, encodingutf-8) as f: cookie_dict json.load(f) session.cookies.update(cookie_dict) return cookie_dict def is_cookie_valid(): 简单检测Cookie是否还有效通常做法是调用一个需要登录的接口 test_url https://channels.weixin.qq.com/cgi-bin/xxx/myinfo try: resp session.get(test_url, timeout5) if resp.status_code 200: data resp.json() # 如果返回里包含用户信息说明Cookie有效 return data.get(nickname) is not None except requests.exceptions.RequestException: pass return Falsesave_cookie和load_cookie是标准的文件读写不复杂。is_cookie_valid是这套流程里很重要的一个防护在发起正式请求之前先验证Cookie是否有效如果失效了再走扫码流程而不是直接把任务跑挂。你可以把它理解为分布式系统里的健康检查是成熟脚本必备的一部分。然后我把整个流程串成一个函数def ensure_login(): 确保有可用的登录状态如果没有就扫码 # 先尝试加载本地Cookie cookie load_cookie() if cookie and is_cookie_valid(): print(已有有效的登录Cookie) return cookie # Cookie不存在或已失效走扫码流程 qrcode_content, session_id get_qrcode() if not qrcode_content: print(获取二维码失败) return None show_qrcode(qrcode_content) cookie poll_login_status(session_id) if cookie: save_cookie(cookie) return cookie else: return None if __name__ __main__: cookie ensure_login() if cookie: print(登录态准备完成可以用它去请求数据了) # 这里继续写你的业务逻辑 else: print(获取登录态失败)到这一步你已经有了一个能自愈的登录模块每次打开脚本先加载Cookie和验证有效性失效了自动弹二维码重新扫码。这个过程对使用者来说几乎是无感的体验非常好。4. 实测经验与避坑指南4.1 高频问题429 Too Many Requests如果你的脚本运行了一段时间之后突然报错exceeded retry limit, last status: 429 too many requests别慌这是限流不是代码Bug。429状态码的含义很简单请求太频繁服务端不想理你了。出现这个问题的原因有几种。第一种是轮询间隔太短比如把轮询间隔设置成0.5秒甚至更低几分钟之内打了几百个请求被风控识别为异常行为。第二种是Cookie失效后没有及时感知脚本拿着旧Cookie去请求反复重试加重了服务端压力。第三种是多个任务共享同一个Cookie并发请求量超过了阈值。解决办法有三个层面控制频率轮询间隔不要低于1.5秒正式的接口请求之间加上0.5到1秒的随机延时模拟真人操作。随机化延时不要用固定延时time.sleep(0.8 random.random() * 0.5)这种写法让请求间隔有一个小幅度的波动更接近人类行为也能有效降低被限流识别的概率。做好退避当检测到429响应时不要立刻重试而是先停几秒再以更长的间隔慢慢试探。我用的是指数退避连续失败3次就暂停30秒再失败就暂停5分钟。def safe_request(url, paramsNone, retry_times3, sessionsession): 带限流处理的请求包装 delay 5 for i in range(retry_times): try: resp session.get(url, paramsparams, timeout8) if resp.status_code 429: print(f触发限流{delay}秒后重试) time.sleep(delay) delay * 2 continue resp.raise_for_status() return resp except requests.exceptions.HTTPError as e: print(f请求失败: {e}) time.sleep(delay) delay * 2 except requests.exceptions.ConnectionError: print(连接异常网络不稳定) time.sleep(delay) return None这段代码是一个通用包装函数我建议你所有的请求都走这个函数不要直接裸调session.get。这样限流处理、连接异常、超时重试的逻辑都收拢到一处维护起来省心很多。4.2 请求头伪装与细节请求头是所有爬虫脚本的生命线这句话说得一点不夸张。我在调试视频号接口的时候发现只要User-Agent不是正常的浏览器版本接口直接返回403连业务逻辑都不走。后面我把User-Agent换成了当前主流的Chrome版本再配合完整的Accept和Accept-Language头请求立刻就通了。另一个容易踩的坑是Referer。有些接口虽然不在页面里发起但服务端依然会校验来源。你从登录页的流程里抓包拿到的接口Referer通常就是登录页本身的地址。如果漏掉了这个字段某些接口会返回400或者掉登录态。还有Cookie的完整性。当你把Cookie保存下来下次加载进Session时要确保Cookie的域名匹配。Requests 库里session.cookies会自动管理域名但如果你手动构造Cookie字典去覆盖一定要检查Domain和Path属性是否和原来的一致。我自己写过一次手动拼接Cookie的脚本结果少了Path/这个属性导致接口一直返回未登录排查了很久才发现是Cookie不完整的问题。4.3 Cookie失效的几种场景Cookie失效是自动化脚本运行中最大的不稳定因素。以我总结的经验失效主要发生在下面几种场景长时间未使用微信的登录态通常不是永久有效的一段时间不活跃服务端会主动让Cookie过期。异地或异常环境登录当你用一个从来没在这个IP段出现过的环境去请求时风控系统会标记登录环境异常强制剔除登录态。频繁切换IP如果脚本每次请求切换一个代理而且这些代理分布在不同城市有可能触发安全策略。账号密码修改或安全提醒账号如果发生了密码变更、找回密码这类敏感操作所有历史登录态都会被清除。应对办法就是我在前面写到的ensure_login函数每次跑脚本前先验证Cookie有效性失效就重新扫码。你有可以安排定期任务去刷新Cookie来保持登录态的活跃度。4.4 常见问题速查表现象可能原因解决方案请求返回403请求头被识别缺少UA或Referer补全浏览器请求头确保Referer正确请求返回429请求频率过高触发限流增大轮询间隔加入随机延时和指数退避接口返回未登录Cookie失效或者不完整重新扫码登录检查Cookie的Domain/Path属性登录成功但无法取数据请求的接口参数不完整抓包对比参数补全签名或者时间戳字段二维码图片保存后扫不出二维码内容本身被截断检查获取二维码的接口返回对比原网页生成的二维码内容定时任务无法自动登录服务器没有图形界面无法扫码将二维码推送到手机或使用远程展示方案登录状态一两天就掉登录环境不稳定IP频繁变化固定出口IP减少代理切换频次请求偶发ConnectionError网络不稳定或连接被重置增加重试机制使用包装后的请求函数页面登录正常脚本却拿不到数据接口有动态签名参数抓包比对签名参数生成逻辑同步到脚本里表中最后一条要重点提示一下微信视频号的接口可能带了一些动态参数这些参数不是写死的字符串而是和当前时间戳或其他变量相关。如果你发现登录流程完全正常但拿不到数据多半是漏了动态签名参数。处理办法是回到浏览器抓一次包比较两个相近时间点的请求地址里的参数差异找出哪些参数是动态的然后在脚本里生成对应逻辑。4.5 独家小技巧如何减少扫码次数最后说一个我一直在用的小技巧。视频号登录态的失效周期和活跃度有关如果你每天都会用脚本请求几次数据Cookie的活跃度就能维持住稳定性会高很多。我自己的做法是写一个crontab定时任务每天早上8点自动跑一次ensure_login只做Cookie验证和有效刷新不执行具体业务逻辑。如果发现Cookie失效了立刻生成二维码通过微信把图片推给手机上自己给自己发文件就能实现我白天看到就顺手扫一下。这样我一个月下来主动扫码的次数屈指可数大部分时间都处于自动运行的状态。这个方法最大的好处是把被动发现失效变成主动提前刷新避免了在关键任务执行到一半的时候突然发现登录态没了。对于需要长期稳定跑数据脚本的场景这个小成本的前置检查非常值得做。我个人在实际操作中的体会是爬代码本身不是难点难的是把稳定性做到位。Requests方案不复杂也就几十行核心代码但真正让它可靠运行的是那些把异常处理、限流退避、状态检测都考虑完整的细节。你在跑通第一版之后一定要花一点时间把日志打全把各类异常都模拟一遍这套代码才会真正变成你的生产力工具。