requests.Session()是维持登录态的唯一可靠方案,它自动处理Cookie的接收与发送;手动管理cookies字典或每次新建Session均会导致登录态丢失。

requests.Session() 是唯一靠谱的持久化方案
手动管理 cookies 字典或反复调用 requests.get(..., cookies=...) 无法维持登录态——每次新请求都新建连接,服务端认不出你是谁。必须用 requests.Session() 实例,它自动携带上一次响应 Set-Cookie 中的值,并在后续所有请求中透传 Cookie 请求头。
常见错误是把登录逻辑写成函数但每次都新建 Session:
def login(): # ❌ 错误:每次调用都丢弃旧 session
s = requests.Session()
s.post("https://www.php.cn/link/d9976f1c2c0c972d1cee0c3647cbd194", data={"u": "a", "p": "b"})
return s.cookies # 只返回 cookies 字典,没绑定 session 实例
<p>cookies = login() # 拿到的是 dict
requests.get("<a href="https://www.php.cn/link/6499e19d47d7cbd3302a26fdb40d0b41">https://www.php.cn/link/6499e19d47d7cbd3302a26fdb40d0b41</a>", cookies=cookies) # ❌ 不带 session 上下文,登录态失效
</p>
正确做法是复用同一个 Session 实例:
- 登录后不返回
cookies,直接返回s - 后续所有请求都调用
s.get()或s.post() - 若需跨脚本/进程复用,再考虑序列化
s.cookies到文件
保存和加载 cookies 必须用 requests.utils.dict_from_cookiejar()
session.cookies 是 requests.cookies.RequestsCookieJar 类型,不能直接用 pickle.dump() 或 json.dump() 存——它包含不可序列化的属性(如 domain 验证逻辑)。强行 dump 会报 TypeError: can't pickle _thread.RLock objects。
正确流程是先转成标准字典再存:
import requests
import json
<p>s = requests.Session()
s.post("<a href="https://www.php.cn/link/d9976f1c2c0c972d1cee0c3647cbd194">https://www.php.cn/link/d9976f1c2c0c972d1cee0c3647cbd194</a>", data={"u": "a", "p": "b"})</p><h1>✅ 正确保存</h1><p>with open("cookies.json", "w") as f:
json.dump(requests.utils.dict_from_cookiejar(s.cookies), f)</p><h1>✅ 正确加载</h1><p>with open("cookies.json") as f:
cookies_dict = json.load(f)
s.cookies.update(cookies_dict) # 注意是 .update(),不是赋值
</p>
- 不要用
s.cookies = requests.utils.cookiejar_from_dict(...)—— 会破坏 Session 内部状态 - 加载后建议发一个简单请求(如
s.get("/check-login"))验证是否有效,有些网站会校验 User-Agent 或 Referer,单纯 Cookie 未必够
遇到 Cloudflare / 登录页跳转 / JS 渲染时 requests 失效
requests 无法执行 JavaScript,如果目标网站有以下任一情况,纯 requests 登录必然失败:
- 登录表单提交前需执行 JS 计算 token(如 Cloudflare 的
__cf_bm) - 登录成功后重定向地址由 JS 动态生成(
window.location.href = ...) - 验证码是 Canvas 渲染或需要滑动验证
此时必须换方案:
轻量级:用 playwright 启动无头 Chromium,它能完整执行 JS 并自动管理 Cookie:
from playwright.sync_api import sync_playwright
<p>with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("<a href="https://www.php.cn/link/d9976f1c2c0c972d1cee0c3647cbd194">https://www.php.cn/link/d9976f1c2c0c972d1cee0c3647cbd194</a>")
page.fill("#username", "a")
page.fill("#password", "b")
page.click("button[type=submit]")
page.wait_for_url("<strong>/dashboard</strong>") # 等待跳转完成</p><pre class="brush:php;toolbar:false;"># ✅ 直接导出 cookies(含 domain/path/expires 等完整字段)
cookies = page.context.cookies()
with open("pw_cookies.json", "w") as f:
json.dump(cookies, f)
注意:playwright 导出的 cookies 格式与 requests 不兼容,不能直接给 requests.Session().cookies.update() 用——需用 requests.utils.cookiejar_from_dict() 转换,且要手动过滤掉 httpOnly 或过期项。
多账号并发登录必须隔离 Session 实例
用一个全局 Session 对象跑多个账号,Cookies 会互相覆盖。例如账号 A 登录后,账号 B 登录会把 A 的 Cookie 替换掉,再用这个 Session 请求 A 的页面就 401 了。
解决方案只有两个:
- 每个账号分配独立的
requests.Session()实例,用字典或列表管理:user_sessions = {"user_a": s_a, "user_b": s_b} - 改用
playwright的browser.new_context(),每个上下文自带独立 Cookie 和存储空间,天然隔离
别试图用线程锁保护一个 Session——锁住也没用,因为 Cookie 是实例属性,不是共享内存;而且高并发下锁竞争反而拖慢速度。
真正麻烦的是 Cookie 过期时间不一致、部分账号被风控要求二次验证、或网站对同一 IP 的请求频率限流——这些没法靠封装 Session 解决,得加随机延迟、换代理、或人工介入处理验证码。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











