基于浏览器自动化的 Cookie 获取与复用实践

本文只围绕三个问题展开:

  1. 如何稳定地获取浏览器中的 Cookie(以及相关会话状态)
  2. 如何在后续脚本中复用这份 Cookie,实现免登录访问
  3. 为什么这种方式在技术上是可行的(底层原理是什么)

一、如何获取 Cookie(认证采集脚本)

思路是:启动一个有界面的浏览器实例,让用户手动完成一次真实登录,然后由脚本在合适的时机导出当前会话状态并落盘。

下面是一个最小可用的“认证采集脚本”示例(Node.js + Playwright):

const { firefox } = require("playwright");
const fs = require("fs");
const path = require("path");

const AUTH_DIR = path.join(__dirname, "auth");
const AUTH_FILE = path.join(AUTH_DIR, "auth-state.json");
const BROWSER_EXECUTABLE = path.join(__dirname, "camoufox", "camoufox.exe");

function ensureDir(dir) {
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
}

async function captureAuth() {
  ensureDir(AUTH_DIR);

  const browser = await firefox.launch({
    headless: false,                      // 必须有界面,用户需要手动登录
    executablePath: BROWSER_EXECUTABLE,   // 如需指定自带浏览器,可配置此路径
  });

  const context = await browser.newContext();
  const page = await context.newPage();

  // 打开登录入口页面,用户在页面中完成登录
  await page.goto("https://example.com/login");

  console.log("请在新打开的浏览器中完成登录,然后回到终端按 Enter 继续...");
  await new Promise((resolve) => process.stdin.once("data", resolve));

  // 导出当前上下文的完整会话状态(包含 Cookies / Storage)
  const storageState = await context.storageState();

  fs.writeFileSync(AUTH_FILE, JSON.stringify(storageState, null, 2), "utf-8");
  console.log(`认证状态已写入: ${AUTH_FILE}`);

  await browser.close();
  process.exit(0);
}

captureAuth().catch((err) => {
  console.error(err);
  process.exit(1);
});

关键点说明:

  • 上下文级别会话
    browser.newContext() 创建的是独立的浏览器上下文,类似一个轻量级“用户配置目录”,其 Cookies、本地存储等和其他上下文隔离。

  • 人工登录 + 自动导出
    登录动作完全由用户在真实页面中完成,脚本只负责在用户确认后调用 context.storageState() 导出状态。

  • 会话状态文件化
    storageState 是一个普通 JS 对象,序列化为 auth/auth-state.json 后,就形成一个可长期复用的认证文件。


二、如何复用 Cookie(业务脚本)

有了认证文件之后,业务脚本只需要在创建浏览器上下文时,把这份状态注入进去,新的上下文就会继承原来的 Cookie 和存储数据。

下面是一个使用同一认证文件的业务脚本示例:

const { firefox } = require("playwright");
const fs = require("fs");
const path = require("path");

const AUTH_FILE = path.join(__dirname, "auth", "auth-state.json");

function loadStorageState() {
  if (!fs.existsSync(AUTH_FILE)) {
    throw new Error(`认证文件不存在: ${AUTH_FILE},请先运行采集脚本生成`);
  }
  const raw = fs.readFileSync(AUTH_FILE, "utf-8");
  return JSON.parse(raw);
}

async function main() {
  const storageState = loadStorageState();

  // 业务脚本中通常可以使用无头模式
  const browser = await firefox.launch({ headless: true });

  // 关键点:在创建 context 的时候注入 storageState
  const context = await browser.newContext({ storageState });
  const page = await context.newPage();

  // 此处直接访问需要登录态的页面
  await page.goto("https://example.com/protected");

  console.log("当前页标题:", await page.title());

  await browser.close();
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

复用过程的本质是:不再从空上下文开始,而是用一份“预先持久化”的会话状态初始化上下文


三、为什么这样做可行

上面的流程之所以能工作,核心依赖三个技术点:

  1. HTTP Cookie 的工作机制
  2. 浏览器上下文(Browser Context)对会话状态的封装方式
  3. 自动化框架对“会话导出 / 会话注入”的支持(storageState 机制)

下面按这三个层次拆开说明。

在 Web 体系中,服务端识别一个“已登录用户”的典型方式是:

  1. 用户在登录页面输入账号密码,发起登录请求;
  2. 服务端校验成功后,返回一个或多个包含会话信息的 Set-Cookie 响应头,例如:
Set-Cookie: SESSION_ID=abcdef; Path=/; HttpOnly; Secure
  1. 浏览器将这些 Cookie 存入其 Cookie 存储中,并在后续访问同一域名(符合作用域和路径规则)时,自动携带这些 Cookie:
Cookie: SESSION_ID=abcdef
  1. 服务端根据 Cookie 中的会话标识(如 SESSION_ID)从自身会话存储中查找用户身份与权限,从而判断请求来自哪个登录用户。

因此,只要浏览器中持有与服务端会话对应的 Cookie 集合,并在后续请求中按同样的规则携带这些 Cookie,服务端就会认为这是同一个“已登录会话”。

3.2 浏览器上下文与会话隔离

现代自动化框架(如 Playwright)抽象了一个“浏览器上下文”(Browser Context)的概念:

  • 每个上下文拥有独立的 Cookie 存储、本地存储、会话存储等;
  • 同一浏览器实例下的多个上下文相互隔离,互不共享会话状态;
  • 上下文可以看作一个轻量级的“浏览器用户配置路径”。

以 Playwright 为例:

const browser = await firefox.launch();
const contextA = await browser.newContext(); // 上下文 A
const contextB = await browser.newContext(); // 上下文 B

// A 中登录,不会影响 B

这意味着:只要我们能把某一个上下文的 Cookie 和相关存储完整导出,并在未来用相同的结构去创建新上下文,就能“克隆”这个上下文的会话状态。

3.3 storageState():会话导出的抽象

自动化框架提供了一个关键 API,用于导出当前上下文的会话状态,例如:

const storageState = await context.storageState();

这个 storageState 通常包含如下信息:

  • 当前上下文中所有 Cookie(包含 name / value / domain / path / expires / secure / httpOnly 等字段)
  • 按 origin 组织的 localStorage / sessionStorage 等数据
  • 框架自身需要记录的与存储相关的元信息

从数据结构上看,它只是一个普通的 JavaScript 对象,可以安全地序列化为 JSON 文件并持久化:

fs.writeFileSync("auth-state.json", JSON.stringify(storageState, null, 2));

3.4 newContext({ storageState }):会话注入的抽象

在另一个脚本(或同一脚本的后续阶段),框架支持通过 storageState 初始化新的上下文:

const restoredState = JSON.parse(fs.readFileSync("auth-state.json", "utf-8"));

const context = await browser.newContext({
  storageState: restoredState,
});

本质上,这一步执行了两件事情:

  1. restoredState.cookies 写入新上下文的 Cookie 存储区;
  2. restoredState.origins 对应的数据写入到各个 origin 的本地存储空间中。

完成这一步之后,从新上下文发出的所有网络请求,将自动带上与原上下文完全一致的 Cookie 集合,因此在服务端看来:

  • 这就是一个已经登录过的浏览器实例;
  • 只要服务端的会话未过期,所有需要登录态的接口都可以直接访问。

3.5 为什么不需要在脚本中“模拟登录”

由于浏览器上下文的会话状态是可序列化和可恢复的,认证采集脚本只需要在“已经登录成功”的时刻做一次导出即可。

从那一刻起:

  • 登录页面、登录表单、验证码、二次认证等流程全部被“折叠”进一次操作;
  • 业务脚本无需再实现这段重复且高耦合的逻辑,只需要依赖认证文件即可。

这在工程上带来的好处是非常明显的:

  • 登录流程的维护成本从“散落在每个脚本中”变为“集中在一个工具脚本中”;
  • 自动化任务本身完全与认证细节解耦,职责清晰;
  • 对于风控敏感系统,可以降低频繁模拟登录的触发风险。

四、写在最后

在此基础上,可以按需进一步扩展诸如多账号管理、有效期检测、安全加密等能力,但本质仍然基于同一技术原理:会话状态是可序列化、可持久化、可注入的新上下文的

关注 【松哥AI自动化】 公众号,每周获取深度技术解析,从源码角度彻底理解各种工具的实现原理。更重要的是,遇到技术难题时,直接联系我!我会根据你的具体情况,提供最适合的解决方案和技术指导。

上期回顾:(我为什么认为未来的自动化应该是:人提需求,AI生成工作流,AI工具执行

posted @ 2025-11-20 16:52  松哥_ai_自动化  阅读(25)  评论(0)    收藏  举报