<!-- Source: https://deepseek-browser-use.io.nexus/05 -->

# 浏览器、profile 与登录态

本文说明当前工程「用哪个浏览器、用哪份 profile、登录态存在哪」，以及换浏览器带来的实际后果。前文 [04](<https://deepseek-browser-use.io.nexus/04>) 讲的是请求与响应的形状，本文讲的是浏览器本身。

[[toc]]

## 一、一个浏览器，多个任务

浏览器是**全进程共享**的：一个浏览器进程、一份 profile，任务之间靠**页签**隔离。

| 行为 | 说明 |
| --- | --- |
| 第一次 `start` | 把浏览器拉起来 |
| 之后每次 `start` | 配置兼容时复用浏览器，新开任务页签 |
| 还有其他任务时 `close` | 只关掉该任务自己的页签 |
| 最后一个任务 `close` | 保留浏览器与数据页，移除旧任务监听及路由 |
| `close --close-browser` | 显式关闭闲置浏览器；有其他任务时拒绝 |
| `shutdown` | 关掉全部任务与共享浏览器，服务进程不退出 |

**为什么不能一个任务一个浏览器。** 用户数据目录天生是单例：同一个 `User Data` 目录同时只允许一个浏览器进程，第二个进程会把命令行交给已有实例然后自己退出。所以「共用一份 profile、登录态只养一次」和「一个任务一个浏览器」只能二选一 —— 这里选了前者。

代价是**上下文级设置是所有任务共享的**（Cookie、地理位置、离线、额外请求头、权限），因为它们本来就挂在同一个浏览器上下文上；`get_browser_state` 只列任务自己的页签，`switch_tab` 的索引也只在任务自己的页签里数。

## 二、`start` 的 `browser` 参数

```json
{"method":"start","params":{"browser":"edge","headless":false}}
```

| 取值 | 浏览器 | 引擎 | 启动方式 | profile | 什么时候用 |
| --- | --- | --- | --- | --- | --- |
| 不传 / `auto` | 本机 Google Chrome，没装且未显式选 profile 时退回内置 Chromium | Chromium | 落到 Chrome 时走 CDP，退回内置 Chromium 时走持久化上下文 | 见第三节 | 默认（推荐） |
| `chrome` | **只**用本机 Google Chrome | Chromium | **CDP**（自己拉进程 + 远程调试端口） | 默认一份 `shared-default`；可显式选择命名 profile 或独立目录，见 [39](<https://deepseek-browser-use.io.nexus/39>) | 需要 Google 登录之类的站点，或要确认「本机 Chrome 下的表现」 |
| `chromium` | **只**用内置的那份（发行包内嵌），完全不碰本机 Chrome | Chromium | 持久化上下文 | 托管 profile（按端口派生） | 怀疑本机 Chrome 的扩展或登录态干扰时做对照 |
| `edge` | **只**用本机 Microsoft Edge | Chromium | CDP | Edge 专用托管 profile | 站点在 Chrome 下用不了，或要对照两个浏览器的差异 |
| `firefox` | Playwright 自带的那份 Firefox | Firefox | 持久化上下文 | 托管 profile（按端口派生） | 站点在 Chromium 下用不了，见第五节 |

「启动方式」这一列是**实现细节，但对结果有影响**：默认 CDP 路径使用独立托管 profile，因此通常不要求先关掉使用其它目录的 Chrome；显式选目录或克隆时必须检查目录占用；代价是创建期的某些设置要另想办法补，见 [27](<https://deepseek-browser-use.io.nexus/27>)。

别名也认（`msedge` / `google-chrome` / `bundled` / `ff` 等）；写了不认识的值会直接失败并列出可选值。

**`auto` 与显式取值的区别是「能不能悄悄换一个」**：没有显式选择 profile 且不传 `browser` 时服务按老规矩退让（本机没有 Chrome 就用内置 Chromium），并把原因写进 `data.browser.note`；显式写了 `chrome` / `edge` 就是「我就要这个」，没装会直接报错并说清怎么改 —— 否则「明明要了 Edge，结果用 Chrome 跑出另一种页面」这类问题很难查。

**一次只能有一个浏览器。** 类型与 profile 都必须在结束任务并显式关闭保留的浏览器后才能切换（只 `close` 自己的任务；其他任务由所属用户结束），不能在任务运行中强行切换：那会连累别的任务的页签，服务会明确报错。想同时用两个浏览器（例如一边 Chrome 一边 Edge），再起一个服务进程，给它们配不同的端口与 profile 目录。

### 回执里的「我要的」与「实际用的」

```json
{"data":{"id":"1001","requestedBrowser":"firefox","effectiveBrowser":"firefox","engineHonored":true,
  "browser":{"type":"firefox","engine":"firefox","mode":"managed","headless":false,
    "profileDir":"~/.config/browseruse/profiles/shared-10049"}}}
```

| 字段 | 含义 |
| --- | --- |
| `requestedBrowser` | 这次请求里写的那个值 |
| `effectiveBrowser` | 实际落到的那个（`auto` 不会出现在这里） |
| `engineHonored` | 服务实例有没有按 `browser` 参数切浏览器 |
| `mode` | `managed`（持久化上下文）或 `cdp`（自己拉进程 + 远程调试端口） |
| `cdp` | 只在 `mode=cdp` 时出现：`product`（浏览器亲口报的版本）、`protocolVersion`，以及 `notes`（用协议补了哪些创建期设置，补失败的也在这里）。见 [27](<https://deepseek-browser-use.io.nexus/27>) |
| `profileDir` | 这次真正用的 profile 目录 |
| `profileSeenBefore` | 这份 profile 在这次启动**之前**就已经有内容了吗 |
| `note` | 这次启动方式与 profile 的说明：走 CDP 时说明用的是哪条路与哪份 profile；用户 profile 用不上时，这里是退回托管 profile 的原因 |
| `profileNote` | 对「这份 profile 里有没有登录态」的说明，例如「之前用过（上次是 browser=chrome），里面已有的登录态应该还在」 |
| `previousEngine` / `previousBrowser` | 上次用这份 profile 的引擎与浏览器类型 |

`engineHonored:false` 表示这个服务实例没有按 `browser` 参数切浏览器（老版本发行包只认 `headless`，会静默忽略 `browser`，回执照样 `ok:true`），此时 `data.engineWarning` 会说明原因。要不要用 `get_config` 看服务端认的 `engine` / `configuredType` 也行。

**`profileSeenBefore` 回答的是「要不要重新登录」**：它只说明这份 profile 里有内容，不保证某个具体站点的登录态仍然有效。

## 三、profile 目录

直接启动 Java 时，默认托管 profile 位于 `~/.config/browseruse/profiles/` 下；命名根目录可通过 `browser.profiles.dir` 配置，显式目录可以在其它位置。**本机 Chrome 与其它浏览器用的不是同一份**。

### 3.1 CLI 启动的服务

`dsb server start` 按服务端口指定持久化目录：默认端口 10049 保留 `.dsb-backend/profile`，其他端口使用 `.dsb-backend/profile-<port>`。同一端口重启后仍用原目录；不同端口默认不争用同一份 profile。

### 3.2 直接启动 Java 服务

| 浏览器 | 默认端口 10049 | 其他端口 | 显式路径配置 |
| --- | --- | --- | --- |
| Chrome CDP | `shared-default` | `shared-<port>` | `browser.chrome.cdpProfileDir` 优先于 `browser.profileDir` |
| Edge | `edge` | `edge-<port>` | `browser.edge.profileDir` |
| 内置 Chromium / Firefox | `shared-10049` | `shared-<port>` | `browser.profileDir`；`browser.profileDir.perPort=false` 可恢复 `shared` |

表内默认目录位于 `~/.config/browseruse/profiles/` 下。显式目录优先；若多个服务被显式配置为同一目录，仍会发生占用冲突。登录态跟着目录走，不自动迁移。

同一服务的后续兼容任务复用浏览器并新开页签，不接管保留的数据页及用户打开的页面。配置不兼容时返回错误，即使没有活动任务也不会自动关闭浏览器。确认需要切换后使用 `close --close-browser`，再启动新任务。

用 `get_config` 与启动回执中的实际 `profileDir` 核验目录。选择了命名 profile 时以本次启动回执为准。Firefox 与 Chromium 的目录格式不通用，Edge 也不应与 Chrome 共用 profile。

## 四、显式选择与独立克隆

推荐先用 `dsb profiles` 查看服务端发现的目录，再用 `dsb profile clone` 离线复制需要的子 profile，最后通过命名 profile 启动：

```powershell
dsb profile clone --name litongjava --source-user-data-dir "<Chrome User Data>" --source-profile-directory Default
dsb --id 1001 start --browser chrome --headful --profile litongjava
```

完整字段、目录选择与安全边界见 [39：profile 列表、克隆与选择](<https://deepseek-browser-use.io.nexus/39>)。显式选择只支持 Chrome；此时省略 `browser` 或传 `auto` 也按 Chrome 处理，不会改用其它浏览器。未知名称、目录错误或启动失败都直接报错，不会回退到默认 profile；命名选择不能与显式目录混用。

`browser.chrome.useUserProfile=true` 是旧的配置入口，尝试直接使用用户自己的 `User Data`，仍然通过 CDP 连接。Chrome 对系统默认用户数据目录的远程调试限制与 `RemoteDebuggingAllowed` 策略不是同一件事：**把该策略设为允许并不能解除默认目录限制**，不应以修改注册表或降级浏览器作为本功能的前提。

支持的做法是使用**非默认目录的独立副本**。复制 profile 并非一概不可行，但加密 Cookie、已保存密码与网站会话可能无法沿用，需要人工重新登录；不保证保留 Google 或任何网站的登录状态。

仅未显式选 profile 的旧配置路径仍受 `browser.chrome.profileFallback` 控制：失败时可以退回托管目录，并在 `data.browser.note` 说明原因。它不覆盖显式选择的“失败不回退”规则。

这条路用之前先知道两件事：

| 事项 | 说明 |
| --- | --- |
| `set_credentials` 用不了 | HTTP 基本认证的凭据只能在创建上下文时设置，而 CDP 这条路的上下文不由服务创建。需要它时改用 `browser=chromium` 或 `browser=firefox`，失败信息里会直接给出这两个取值 |
| 页面触发的下载 | 服务已经在启动时用协议命令把下载目录设成约定目录（见 [27](<https://deepseek-browser-use.io.nexus/27>) 第五节），不设的话会落到浏览器自己的下载目录。`pdf` 命令不受影响（路径由服务自己算） |

**本机 Chrome 这条路的页面视口一直跟着窗口走**，不套视口模拟，所以 `browser.viewport` 对它没有影响（见 [06](<https://deepseek-browser-use.io.nexus/06>)）。

## 五、换成 Firefox

`browser.engine=firefox` 或 `browser=firefox` 时改走 Playwright 自带的 Firefox，配同一份托管 profile。

| 差异 | 说明 |
| --- | --- |
| `pdf` 命令 | 只有 Chromium 支持，Firefox 下返回明确的中文失败原因 |
| 用户自己的 Chrome profile | `browser.chrome.useUserProfile` 对 Firefox 无效（那条路要 CDP，Firefox 没有） |
| 启动参数 | 没有 `--no-sandbox` / `chromiumSandbox` / `--profile-directory`，Firefox 下不传 |
| 本机装的 Firefox | 用不上：Playwright 的 Firefox 是打过补丁的构建（juggler 协议），不要配 `browser.firefox.path` 指到本机那份 |
| UA | 不覆写成 Chrome，UA 就是 Firefox 自己的 |

**为什么要这个开关**：有的站点在 Chromium 下用不了。中国商标网统一身份认证（`sso.cnipa.gov.cn`）的 SPA 会做开发者工具检测，Chromium 会走到空白页或 HTTP 400，同一流程在 Firefox 下能正常渲染出登录表单。

`start` 的返回里会多一个 `data.browser.engine`，用它确认这次到底跑的是哪个引擎。**换引擎等于换一套 profile 格式，所以要重新登录一次。**

## 六、关于 Chromium 沙箱

默认策略：Windows / macOS 这类普通桌面环境**开启**，Linux **关闭**（服务多数跑在容器里、以 root 运行，而 Chrome 以 root 启动会直接报错退出）。

| 平台 / 浏览器 | 默认 | 原因 |
| --- | --- | --- |
| Windows / macOS 上的 `chrome`、`chromium`、`auto` | 开启 | 普通桌面环境沙箱可用；关掉换不来任何东西，只换来更差的隔离 |
| Windows / macOS 上的 `edge` | 开启 | Edge 走 CDP（端口）那条路，沙箱不影响启动 |
| Linux 上的任何浏览器 | 关闭 | 以 root 运行时必须传 `--no-sandbox` |

`browser.chromium.sandbox` 三档取值：`true` 始终开启 / `false` 始终关闭 / 不配按平台默认。两条启动路径（Playwright 的持久化上下文与 CDP 那条自己拉进程的路）看同一个开关，不会一半开一半关。

两点容易误解的地方：

- **`--no-sandbox` 是 Playwright 自己加的**，不是本服务传的：Playwright 的 `chromiumSandbox` 默认就是关的，只要不显式开启沙箱，这条标志就一直在。
- **Edge 的问题是「沙箱 + 管道」这个组合**：实测开沙箱时，Playwright 的 `--remote-debugging-pipe` 会让 Edge 启动即退出（`Target page, context or browser has been closed`），换成 `--remote-debugging-port` 就一切正常。所以解决办法是换启动方式，而不是关沙箱。

不要为了「藏掉自动化特征」去加 `--disable-blink-features=AutomationControlled`：那个参数会触发浏览器顶部的「不受支持的命令行标志」提示。需要补充启动参数用 `browser.chrome.extraArgs` / `browser.edge.extraArgs` / `browser.firefox.extraArgs`，改完要重启服务并重新创建浏览器实例，旧窗口不会自动应用新参数。

**调试端口不要用 9222。** 它不是浏览器的内置默认值，只是各类工具约定俗成的端口，跟着用会互相抢。CDP 那条路默认让浏览器自己挑空闲端口（一定在 10000 以上），需要固定端点时用 `browser.chrome.debugPort` 钉一个 10000 以上的值，原因与排障见 [27](<https://deepseek-browser-use.io.nexus/27>) 第八节。
