如何在IP切换时保持会话、Cookie和令牌:逐步指南
你很可能遇到过这种情况:脚本正常运行,在网站上进行登录,把商品加入购物车,然后突然切换了 IP,服务器突然表现得像你是第一次访问。用户被登出,购物车空了,令牌失效。烦人吧?那当然。但这个问题有明确的工程解决方案,本指南将从头到尾为你解析。
引言:为什么切换 IP 后会被登出、购物车变空
说实话:当会话在 IP 轮换后失效时,你的第一反应是归咎于地址切换这件事本身。就像说:IP 变了,服务器看到了,就把一切都重置了。有时候确实如此。但大多数情况下,问题并不在于 IP 本身,而在于你的代码实现。它混淆了不同会话的状态,丢失了 cookies,还从新地址发送了旧令牌。我们将学习如何避免这种情况发生。
读者最终将获得什么
到本教程结束时,你将能够做到以下几点。第一,理解服务器端真正与 IP 绑定的是什么,什么只是误解。第二,建立严格的对应规则:一个逻辑会话 = 一套 cookies = 一个 IP。第三,用 Python 和 Node.js 编写可运行的代码,在不同代理之间隔离状态,不让数据混淆。第四,在程序重启之间保存状态,并知道什么时候干脆弃掉它更简单。
本指南适合谁
本指南面向中等水平读者。你已经在用 Python 或 JavaScript 写代码,明白什么是 HTTP 请求,也知道代理的作用。我们不会讨论该选择哪种轮换类型——这有单独的文章。这里我们假设 IP 轮换已经按你的规则在进行,只解决一个任务:如何在轮换过程中不丢失状态。
需要提前了解的知识
了解一些基础概念会有帮助。Cookie 是一小块数据,服务器要求浏览器或客户端记住,并在每次请求时发回来。会话是服务器在多次请求之间识别你的方式。令牌是一串字符串,用来证明你的身份或操作权限。如果这些术语还很模糊,别担心:在基础概念部分,我们会用通俗的语言逐一讲解。
需要多长时间
阅读并理解理论大约需要四十分钟。用你自己的语言环境来解析和运行代码,还需要一到两个小时。通过真实任务进行实验来完全掌握,大约需要两到三个小时。不要着急。与其快速复制一段后来莫名其妙崩溃的代码,不如慢慢弄懂原理。
前期准备:工具和环境
在写代码之前,先把工作环境整理好。这会花一点时间,但能为你之后省下几小时的调试时间。
所需工具
- Python 3.10 或更高版本——如果你使用 Python。到 2026 年,主流版本是 3.12 和 3.13,但文中所有内容在 3.10 上即可运行。
- Node.js 20 LTS 或更高版本——如果你使用 JavaScript。22 LTS 也可以。
- 支持 IP 轮换的代理——你需要能够访问地址池。连接格式通常是:协议、主机、端口、用户名和密码。
- 代码编辑器——任何都可以,比如带有语法高亮的免费编辑器。
- 终端或命令行——用于安装库和运行脚本。
系统要求
系统要求很低。任何现代电脑都能胜任。4GB 内存就够用,但如果你打算同时运行大量会话,最好是 8GB 或更多。更关键的是稳定的网络连接,因为网络断开时 cookies 可能无法正确保存。
需要安装什么
对于 Python,你需要安装两个库。打开终端,运行安装 requests 包和代理相关包的命令。写法是:先写包管理器的安装命令,然后是 requests。状态序列化使用标准模块,无需单独安装。
对于 Node.js,你需要安装三个包:用于请求的 axios、用于管理 cookie 存储的 tough-cookie,以及用于通过代理连接的 https-proxy-agent。这三个包可以在项目里用一条安装命令一起安装。
建议:为项目创建一个独立的虚拟文件夹。在 Python 中是虚拟环境,在 Node.js 中是一个带依赖描述文件的独立目录。这样可以避免不同项目的库混在一起,防止版本冲突。
创建备份
如果你已经在把 cookies 保存到文件或数据库,实验前请先做备份。我们会修改序列化逻辑,有可能损坏现有数据。把保存状态的文件夹复制一份,并标记为 backup 即可。
验证:安装完成后,确认一切正常。在终端运行一个简短的命令来查看 Python 或 Node.js 版本。你应该能正常看到版本号,没有错误。然后在交互模式下导入已安装的库——如果导入没有报错,就说明一切就绪。
基础概念:哪些东西真的与 IP 绑定,哪些是误解
这是最重要的理论部分。如果你没搞清楚什么真的与 IP 相关,就只会治错病。我们逐一来看不同类型的数据。
Cookie 会话
Cookie 会话是指服务器以 cookie 的形式给你一个会话标识符,而状态则保存在服务器自己那里。例如,一个名为 sessionid 的 cookie,里面是一长串随机字符。服务器通过这串字符在内存中找到你的记录。这个机制绑定 IP 吗?本身并不绑定。 Cookie 与地址无关。但许多服务会额外增加一层检查:它们记住会话是在哪个 IP 下创建的,如果带有同一个 cookie 的请求来自其他地址,就会认为可疑。这时就会发生登出。
CSRF 令牌
CSRF 令牌用于防御请求伪造。服务器发放令牌,你在提交表单或执行关键操作时必须把它带回来。这个令牌几乎从不绑定 IP。它与会话相关,而不是与地址相关。它出问题的原因在于另外一点:如果你丢失了会话 cookie,那么 CSRF 令牌也就会失效,因为服务器无法把它与会话对应起来。也就是说,这里的问题是次要的——是 cookies 丢失的后果。
JWT
JWT 是一种自带信息并由服务器签名的令牌。客户端保存它,并在每次请求时通过请求头发送。经典的 JWT 完全不绑定 IP。它自包含:服务器只验证签名和有效期,不关心地址。但也存在一些实现,服务器会在自己那边额外保存令牌与 IP 的绑定关系,或者把地址放进令牌内部。这种情况下,切换 IP 会导致校验失败。这不是 JWT 本身的属性,而是具体服务的决定。
服务器会话
服务器会话是服务器在自己的存储中保存状态,并与你的标识关联起来的统称。购物车、浏览历史、登录状态——这些常常都存放在服务器会话里。这里是否绑定 IP 取决于服务的设置。有些服务出于安全考虑,会把会话强绑定到第一个 IP。其他服务则允许地址变化。你通常无法提前知道,所以按“存在绑定”来写代码是更安全的策略。
购物车
购物车是服务器会话或 cookie 的一个典型例子。在简单的商店里,购物车直接以 cookie 形式保存在客户端。在复杂的商店里,则保存在服务器上并与会话关联。如果切换 IP 后购物车变空,说明它绑定在一个会检查地址的服务器会话上。解决方案是一样的:在同一个逻辑会话内不要更换 IP,或者小心地保存好整套 cookies。
小结:IP 真正参与的地方
我们把整体情况梳理一下。cookies、CSRF、JWT 这些 HTTP 机制本身并不绑定 IP。绑定是服务端额外加的检查,你无法控制它。你唯一能控制的,是你这边的会话、cookies 集合和 IP 之间的对应关系。由此引出了本指南的核心规则。
验证:检查一下你的理解。如果切换 IP 后会话失效,但切回旧 IP 后一切恢复,说明服务器在严格检查地址。如果就连切回旧 IP 也无法恢复,那你只是在代码中丢失了 cookies。这是两种不同的诊断,治疗方法也不同。
第 1 步:明确对应规则
本阶段目标:巩固核心原则,并理解如何在代码结构中体现它。
规则很简单:一个逻辑会话 = 一套 cookies = 一个 IP。下面看看它在实践中意味着什么。
- 逻辑会话是一串请求,构成一次连续的操作:进入、登录、做某些事、退出。这一切都属于同一个逻辑会话。
- 一套 cookies 是一个独立的 cookie 存储,只属于这个逻辑会话,不属于其他任何东西。
- 一个 IP——在同一逻辑会话期间,地址不能改变。如果确实发生了轮换,逻辑会话就被视为已结束。
如何用代码表达?非常直观:创建一个容器对象,它内部既包含 cookie 存储,又包含代理设置。只要这个对象存在,逻辑会话就存在。当需要更换 IP 时,我们要么创建一个新容器,要么在服务允许更换地址的情况下,小心地把 cookies 迁移到带有新 IP 的新容器中。
⚠️ 注意:最常见的架构错误是保存一套公共 cookies,然后给它们配上不同的代理。这必定会破坏一切。不同的逻辑会话会互相覆盖 cookies,服务器就会收到相互矛盾的数据。绝对不要这样做。
建议:把逻辑会话想象成一个人。一个人只有一套证件(cookies)和一个家(IP)。不能让两个人共用一套证件,也不能让一个人同时住在两个家里。这个类比能帮你避免大部分错误。
验证:在纸上画出你未来的结构。你应该有多个独立的模块,每个模块都有自己的 cookie 存储和自己的代理。模块之间没有共享数据。如果符合这些,就说明你理解了规则。
第 2 步:Python 实践——每个代理一个 Session
本阶段目标:编写可运行的代码,让每个逻辑会话都有自己的 requests.Session 对象、自己的 CookieJar 和自己的代理,彼此隔离。
在 requests 库中有一个 Session 对象。它本身就是一个容器:内部持有 cookie 存储,并能把设置应用到所有请求上。这是我们逻辑会话的理想基础。
基本结构
- 创建一个函数,接收单个代理的数据,返回一个准备好的 Session 对象。
- 在函数内部创建一个新的 Session 对象。
- 为对象设置 proxies 配置——一个包含 http 和 https 协议代理地址的字典。
- 返回该对象。现在你就有了一个隔离的容器。
代码看起来是这样的。逐行说明:导入 requests。定义接收 proxy_url 字符串的 make_session 函数。函数内写 s = requests.Session()。然后 s.proxies = {'http': proxy_url, 'https': proxy_url}。最后 return s。函数就完成了。
为什么这样能隔离状态
每次调用 make_session 都会创建一个全新的对象。这个新对象有自己内部的 cookie 存储,叫作 cookiejar。一个会话收到的 cookies 在物理上不可能进入另一个会话,因为它们是内存中不同的对象。这正是我们想要的效果。
使用会话
- 通过调用函数并传入所需代理来获取会话对象。
- 通过该对象的方法发起请求:s.get 或 s.post。
- 服务器在 Set-Cookie 请求头中发送的 cookies 会自动保存在对象内部。
- 之后通过同一个对象发起请求时,这些 cookies 会自动被发送回去。
建议:在同一个逻辑会话内,不要为每个单独请求创建新的 Session。这样 cookies 就无法累积。一个逻辑会话只创建一个对象,并让它处理该会话的所有请求。
线程间的隔离
如果你使用多线程,每个线程必须有自己的 Session 对象。Session 对象不是线程安全的。也就是说,如果两个线程同时向同一个对象写入 cookies,数据可能会损坏。
- 使用线程本地数据机制。在 Python 中,这是 threading.local 对象。
- 每个线程启动时,为它创建独立的会话,并保存到该线程的本地存储中。
- 在线程内部,只访问自己的会话,不要去动其他线程的。
实际操作如下:创建一个全局对象 local = threading.local()。在线程工作开始时,检查 local 是否已有 session 属性。如果没有,就用分配给该线程的代理调用 make_session 来创建它。之后在线程中用 local.session 来处理所有请求。
⚠️ 注意:永远不要把一个 Session 对象作为共享资源在线程间传递。即使看起来请求是依次进行的,调度器也可能在最不合时宜的时候切换线程,导致 cookies 混在一起。每个线程要有自己的对象。
逻辑中的轮换
当 IP 轮换发生、你需要新地址时,可以这样处理。先结束当前逻辑会话:如果服务器强绑定 IP,就直接创建一个带新代理的新 Session 对象,并且从头开始——重新登录。如果服务器允许更换地址,你可以迁移 cookies,这会在“状态存储”那一步中讨论。
验证:启动两个使用不同代理的会话,分别在一个能显示 IP 和 cookies 的测试服务上登录。确认每个会话看到的是自己的 IP 和自己的一套 cookies。如果数据不混淆,说明隔离正确。
第 3 步:在 Node.js 中实现同样效果
本阶段目标:使用 axios、tough-cookie 和代理 agent 在 JavaScript 中构建等效结构。
在 Node.js 生态中没有现成的 Session 级别对象,所以我们要用三个部分来组装它。cookie 存储来自 tough-cookie。通过代理的连接由 agent 负责。请求由 axios 发出。
组装容器
- 从 tough-cookie 库导入 CookieJar 类。
- 从 https-proxy-agent 库导入创建代理 agent 的函数。
- 导入 axios。
- 创建一个 makeClient 函数,它接收代理地址并返回配置好的对象。
在函数内部,创建存储的新实例:const jar = new CookieJar()。创建代理 agent,把地址传给它:const agent = new HttpsProxyAgent(proxyUrl)。通过 axios.create 创建 axios 实例,并在配置中传入 httpsAgent: agent 和 httpAgent: agent。
启用自动的 cookie 处理
裸的 axios 无法自动把响应中的 cookies 放入存储,也无法在请求时取出它们。有两种方式。
- 第一种:使用现成的封装库,它把 axios 和 tough-cookie 连接起来,通过单独包安装。它会通过传入的 jar 存储自动读写 cookies。
- 第二种:通过请求和响应拦截器手动实现。请求前从存储中取出目标地址的 cookies 字符串,放入 Cookie 请求头;响应后读取 Set-Cookie 头,把每条 cookie 写入存储。
建议:刚开始使用现成封装更不容易出错。手动方案留到你需要精细控制流程时再用,比如记录每一条 cookie。
并行任务之间的隔离
Node.js 的模型不同——这里没有线程,而是事件循环中的异步任务。但原则相同:每个逻辑会话都有自己的 client 对象,以及自己专属的 jar 和 agent。
- 对每个并行任务单独调用 makeClient。
- 将客户端保存在数组或映射中,键为任务标识符。
- 绝不要让多个客户端同时共用一个 jar。
⚠️ 注意:在异步代码中,很容易不小心在多个 Promise 链之间共享同一个 client。这样 cookies 就会在逻辑会话间混来混去。始终确保每个链使用自己通过单独调用 makeClient 创建的客户端。
Node.js 中的轮换
逻辑与 Python 相同。当需要新 IP 且服务把会话绑定到地址时,就创建一个带新代理和新空 jar 的 client,然后重新登录。当服务允许更换地址时,把旧 jar 的内容迁移到带新 agent 的新 client 中。
验证:创建两个使用不同代理的客户端。向能够反射 IP 和 cookies 的测试服务发起请求。确认第一个客户端看到的是自己的 IP 和自己的 cookies,第二个客户端看到的是另一个 IP 和自己的 cookies。两者不能有交集。
第 4 步:在程序重启之间保存状态
本阶段目标:学会把 cookies 保存到磁盘,并在下次程序启动时恢复它们,同时注意有效期。
我们经常需要停止脚本,之后再次启动,同时保留登录状态。为此,需要把 cookies 序列化——转换成文本并保存到文件或数据库中。
Python 中的序列化
requests 中的 CookieJar 对象可以通过多种方式保存。最通用的方式是把 cookies 整理成简单的字典,然后写入 JSON 格式。
- 通过 s.cookies 遍历会话对象中的所有 cookies。
- 对每条 cookie 收集它的名称、值、域、路径和过期时间。
- 把这些信息组成字典列表。
- 将列表以 JSON 格式写入文件。
恢复时反向操作:读取文件,遍历列表,用设置 cookie 的方法把每条 cookie 添加到新的会话对象中,同时指定域和路径。
建议:把代理标识符或至少一个说明它们属于哪个逻辑会话的标记和 cookies 一起保存。这样你就不会用错误的 IP 恢复别人的 cookies,也不会破坏对应规则。
Node.js 中的序列化
tough-cookie 库内置了序列化方法。jar 对象有一个异步方法,能把整个存储转换成 JSON 对象。反向方法可以从该对象恢复 jar。
- 调用存储的序列化方法,得到对象。
- 把对象转成字符串并保存到文件。
- 启动时读取文件,把字符串解析回对象。
- 把对象传给反序列化方法,恢复 jar。
这比 Python 更方便,因为 tough-cookie 自己保存所有必要字段,包括过期时间和安全标志。
Cookie 的生命周期
每条 cookie 都有有效期。有些是会话级 cookie——它们会在浏览器关闭前一直有效,没有明确的日期。有些是持久 cookie——有具体的过期日期。只有还未过期的持久 cookies 才值得保存到磁盘。会话级 cookies 在重启后通常已在服务器端失效。
- 保存前检查每条 cookie 的过期日期。
- 丢弃已经过期的。
- 恢复时再次检查有效期,不要加载已过期的。
什么时候该干脆丢弃状态
不要总是执着于已保存的 cookies。有时候从头开始更快也更可靠。
- 如果距离上次启动已经过了很久——服务器端的会话很可能已经过期,没有什么可恢复的。
- 如果用恢复后的 cookies 发出第一个请求,服务器就返回了授权错误——那就丢弃它们并重新登录。
- 如果你不确定状态文件的完整性——最好从零开始,而不是去调试奇怪的行为。
⚠️ 注意:包含 cookies 和令牌的文件带有访问凭证。要把它们放在安全的地方,不要提交到公共仓库,不要通过不安全的渠道发送。这类文件泄露,就相当于账号访问权限泄露。
验证:保存状态,完全关闭程序,重新启动,恢复状态,然后发起一个需要登录的请求。如果服务器像是面对已登录用户那样响应,说明保存有效。如果被登出,就检查过期时间以及域和路径的恢复是否正确。
第 5 步:常见架构错误分析
本阶段目标:详细解析三种主要错误,让你在自己的代码中认出它们并避免。
错误一:所有代理共用一个 CookieJar
这是万恶之源。开发者创建一个 cookie 存储,然后给它配上不同的代理,以为能省事。实际发生的是:第一个 IP 上的会话 cookies 被带到了第二个 IP 的请求中。服务器看到一个在其他地址下创建的 cookie,于是要么重置会话,要么认为行为可疑。
解决方案很简单:每个代理要有自己的 cookie 存储。没有例外。我们已经在代码步骤中体现了这一点:每个逻辑会话都有自己的 Session 或 client,以及独立的 jar。
错误二:并行请求中的竞态
竞态是指两个操作同时访问同一数据并相互干扰。如果两个线程写入同一个 CookieJar,一次写入可能覆盖另一次。结果是:部分 cookies 会随机丢失,而且这种 bug 时好时坏,非常难以调试。
- 按照我们说的,通过线程本地数据为每个线程提供自己的会话。
- 在异步代码中,不要在同级任务链之间共享同一个客户端。
- 如果出于某种原因对象确实是共享的,就使用锁,确保同一时间只有一个线程在操作它。
建议:避免竞态的最好方法就是根本不使用共享的可变数据。按逻辑会话隔离能从根本上解决问题:如果数据不共享,就不可能有竞态。
错误三:重定向时丢失 Set-Cookie
当服务器返回重定向响应时,通常会在同一个响应中通过 Set-Cookie 头设置重要的 cookies。有些客户端配置在自动跟随重定向时会丢失这些 cookies——它们不会进入存储。
- 确保你的客户端在重定向链的每一步都保存 cookies,而不仅仅是在最终响应时。
- 在 requests 中,使用 Session 对象时默认就是这样做的——cookies 会一路收集。检查一下你是否在没必要的情况下关闭了跟随重定向。
- 在 axios 中手动处理 cookies 时,要处理每个中间响应的 Set-Cookie。如果使用封装库,请确认它能拦截重定向。
⚠️ 注意:如果登录成功,但下一个请求就掉线,常见原因就是在重定向时丢失了某条 cookie。开启对所有 Set-Cookie 头的日志记录,检查所有预期的 cookies 是否都到达了存储。
验证:在目标服务上找一个会触发重定向的操作,比如通过表单登录。跟踪整个链路,比较每一步之后的 cookies。所有服务器发放的 cookies 都应该出现在你的存储中。如果有丢失,你就找到了泄漏点。
第 6 步:把一切整合成工作流
本阶段目标:把你学到的内容整合成一个从开始到轮换都可预测的工作流。
现在你已经掌握了所有细节。让我们把它们组合成一条可以反复执行的动作链。
- 从代理池中取出一个代理,为它创建隔离的逻辑会话——在 Python 中是 Session,在 Node.js 中是 client。
- 如果该逻辑会话有已保存的状态且未过期,就恢复 cookies。否则重新登录。
- 通过该会话对象执行所需请求。cookies 会自动累积。
- 定期把状态保存到磁盘,避免故障时丢失进度。
- 当需要轮换 IP 时,正确地结束逻辑会话。
- 如果服务强绑定 IP,就在新地址上从零开始新的逻辑会话。
- 如果服务允许更换地址,就创建带新代理的新容器,并把旧容器中的 cookies 迁移过去。
建议:记录事件日志:会话何时创建、使用哪个 IP、何时发生轮换、是否出现登出。这样的日志能在五分钟内帮你发现规律,否则你可能要找好几个小时。
验证:跑一遍完整流程:创建会话、登录、执行几个操作、保存、轮换、继续。确保每一步的状态都可预测,并且只有在你预期时才会发生登出。
结果检查:最终清单
逐一核对这份清单。如果所有项目都满足,说明你的系统工作正常。
- 每个逻辑会话都有独立的容器对象,带自己的 cookie 存储。
- 每个容器在整个会话生命周期内只绑定一个代理。
- 没有任何地方能让一个会话的 cookies 进入另一个会话。
- 多线程代码中,每个线程通过线程本地数据使用自己的会话。
- 异步代码中,每条独立链都有自己的客户端。
- cookies 在重定向的所有步骤中都能被正确收集。
- 状态在两次启动之间可以保存和恢复,并考虑过期时间。
- 更换 IP 时,逻辑会话要么重新开始,要么有意迁移 cookies。
- 包含 cookies 和令牌的文件受到安全保护。
如何测试
- 找一个能显示当前 IP 和收到的 cookies 的测试服务。
- 创建两个使用不同代理的会话,确认数据完全隔离。
- 登录、保存状态、重启程序、恢复状态——验证登录仍然有效。
- 模拟轮换,并观察会话的行为。
成功的标准:不同会话的数据绝不混合;只有在服务器强绑定 IP 时才会登出;恢复的状态可以正常工作;并行工作时没有竞态。
典型错误与解决方法
问题:切换 IP 后用户被登出。原因:服务器将会话强制绑定到地址。解决:在同一个逻辑会话中不要更换 IP;如果必须更换,就在新地址上重新开始会话。
问题:不同会话的 cookies 混在一起。原因:多个代理共用一个 CookieJar。解决:给每个逻辑会话单独的存储,永远不要共享。
问题:并行工作时 bug 时有时无。原因:多个线程向同一对象写入时发生竞态。解决:通过线程本地数据按线程隔离会话,或使用锁。
问题:登录成功但很快失效。原因:重定向时丢失了 cookie。解决:检查重定向链所有步骤中的 cookies 收集,并启用 Set-Cookie 日志。
问题:恢复的状态无法使用。原因:保存了已过期或会话级 cookies,或者域和路径不正确。解决:只保存仍然有效的持久 cookies,并用正确的域和路径恢复。
问题:CSRF 令牌一直无效。原因:令牌所绑定的会话 cookie 丢失。解决:先恢复会话 cookies 的完整性,令牌会自动关联上。
问题:轮换后 JWT 无法使用。原因:具体服务在它们那边把令牌绑定到了 IP。解决:在令牌有效期内不要更换 IP,或者在新地址上获取新令牌。
扩展功能与优化
当基础方案运行正常后,你可以让它变得更强。
现成会话池
与其每次都创建会话,不如维护一个预先准备并已登录的逻辑会话池,每个会话都有自己的代理。取一个空闲会话使用,用完再放回池中。这样能加快速度,因为不需要重复登录。
自动存活检查
添加一个函数,向服务发送轻量请求,检查会话是否仍然有效。如果服务器像面对未登录用户那样响应,就会将会话标记为需要重新授权。这样你就能在过期问题破坏重要操作之前发现它。
建议:存活检查不要放在每个请求之前,而是按计划或长时间停顿后进行。过于频繁的检查只会增加无谓的负载,没有实际好处。
集中式状态存储
对于大型项目,不使用文件,而是用数据库作为 cookie 存储。键是逻辑会话标识符,值是序列化后的状态。这样更容易扩展,也存储得更安全。
指标与可观测性
统计登出发生的次数、需要重建多少会话、恢复成功的频率。这些数字能反映系统的健康状况,并提示哪些地方配置得不理想。
FAQ:常见问题
每次更换 IP 都必须创建新的会话对象吗?如果服务严格检查地址——是的,因为旧会话在新 IP 上本来就不会被接受。如果服务允许更换——可以把 cookies 迁移到带新代理的新容器,然后继续。
多个逻辑会话可以共用一个代理吗?从代码层面可以,但每个逻辑会话仍然必须有自己的独立 cookie 存储。只有连接设置可以共享,状态不能。
为什么不能把所有 cookies 放在一处,然后按域过滤?因为问题不在于域,而在于与具体逻辑会话和 IP 的绑定。同一个域上,一个会话的 cookies 绝不能进入另一个会话。
如何判断服务是否把会话绑定到 IP?做一个实验:在一个 IP 上登录,更换地址并发起请求。如果被登出,说明可能存在绑定。再切回旧 IP:如果恢复了,说明服务器记住了第一个地址。
保存到磁盘时该怎么处理会话级 cookies?通常没有必要保存它们,因为服务器在连接断开后会认为它们无效。保存仍然有效的持久 cookies 即可。
如果库本身在重定向时不保存 cookies 怎么办?在重定向链的每个中间响应中手动处理 Set-Cookie 头,并把 cookies 放入自己的存储。
如果每个线程都有自己的会话,还需要锁吗?不需要。如果数据不共享,就不可能有竞态,也不需要锁。只有在必须共同访问同一对象时才需要锁。
可恢复状态能保存多久?直到服务器认为会话仍然有效为止。确切时长取决于服务。更实际的做法是用请求来检查会话是否存活,而不是猜测时间。
保存 cookies 还是保存令牌更重要?这取决于服务的授权机制。有时只有令牌就够了,有时需要一整套 cookies。更安全的是保存完整状态,这样就不会漏掉需要的内容。
cookies 能在 Python 和 Node.js 之间迁移吗?可以,只要你以通用的中性格式保存它们,比如包含名称、值、域、路径和过期时间的 JSON。这样两种系统都能读取。
结论
我们来总结一下你所经历的内容。你明白了切换 IP 后被登出,并不是因为切换这件事本身,而是由于服务器检查和自身架构中的错误。你知道了 cookies、CSRF、JWT 本身并不绑定地址,绑定是由具体服务添加的。你掌握了一条核心原则:一个逻辑会话 = 一套 cookies = 一个 IP。
然后你编写了可运行的代码。在 Python 中,通过为每个代理创建独立的 Session 对象和 CookieJar,并在线程之间进行隔离。在 Node.js 中,通过 axios、tough-cookie 和代理 agent 的组合,为每个逻辑会话创建独立的客户端。你学会了在重启之间保存状态、考虑 cookies 的有效期,并判断什么时候干脆丢弃状态。
你剖析了三种隐蔽的错误:共用的 CookieJar、并行工作时的竞态,以及重定向时 Set-Cookie 的丢失。现在你有了上线前的检查清单,它能防止你发布一个不成熟的产品。
接下来做什么
从小处着手。选一个真实场景,为它实现隔离的逻辑会话,确认状态不会丢失。然后加入磁盘保存。再扩展到多个会话。一步一步前进,每一步都检查结果。
还可以往哪些方向发展
接下来,你可以在可观测性方面深入:配置会话存活指标和事件日志。然后研究在服务允许更换地址时如何有意识地迁移状态。最后,构建一个现成会话池来加快速度。每一步都会让你的系统更加健壮、更可预测。你一定能做到——你已经理解了原理,剩下的就是练习。