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

# 配置项与运维自省

本文列出当前工程的全部运行时配置项与只读查询端点，用于部署、排障和「现在到底是哪个配置在生效」这类问题。

[[toc]]

## 一、配置从哪里读

配置项统一通过 `EnvUtils.get` 读取，**优先级从高到低**：

1. `EnvUtils` 通道（系统属性、环境变量、`app.properties` 等，具体来源以框架的加载顺序为准）；
2. 服务自带的 `browser.properties`；
3. 同一份 `browser.properties` 里的**全大写下划线**写法，例如 `BROWSER_CHROME_PATH` 等价于 `browser.chrome.path`，方便用环境变量覆盖。

代码里不要直接用 `System.getenv` 读环境变量：那样会绕过上面这套优先级，出现「配了却不生效」。

命令行覆盖示例：

```shell
java -Dbrowser.engine=firefox -jar <发行包文件名>.jar
java -Dserver.port=10050 -jar <发行包文件名>.jar
```

## 二、配置项清单

### 浏览器与 profile

| 配置项 | 默认 | 说明 |
| --- | --- | --- |
| `browser.type` | `auto` | `start` 不传 `browser` 时的默认浏览器类型 |
| `browser.engine` | `chromium` | 引擎开关：`chromium` / `firefox`。是 `browser.type` 的子集，表达不了 Chrome 与 Edge 的区别 |
| `browser.chrome.enabled` | `true` | 是否使用本机 Google Chrome；`false` 时完全退回内置 Chromium |
| `browser.chrome.path` | 自动探测 | 本机 Chrome 可执行文件；留空时按平台惯例探测（Windows 还会查注册表 `App Paths\chrome.exe`） |
| `browser.chrome.userDataDir` | 自动探测 | 用户数据目录（`User Data`）；旧启动路径在 `useUserProfile=true` 时使用，也供 profile 列表发现本机 Chrome 子目录 |
| `browser.chrome.profileDirectory` | `Default` | 用用户数据目录里的哪个子 profile；填 `auto` 读「上次使用的 profile」；填空串表示不传该参数 |
| `browser.chrome.useUserProfile` | `false` | 是否尝试直接用用户自己的 Chrome profile；不能绕过 Chrome 默认目录的远程调试限制，推荐独立克隆，见 [39](<https://deepseek-browser-use.io.nexus/39>) |
| `browser.chrome.profileFallback` | `true` | 旧配置路径的用户 profile 用不上时是否退回托管 profile；`false` 时直接报错。请求显式选择 profile 时不允许回退 |
| `browser.chrome.extraArgs` | 空 | 追加到 Chrome 命令行的额外参数，逗号分隔 |
| `browser.chrome.cdpProfileDir` | `~/.config/browseruse/profiles/shared-default` | 本机 Chrome 走 CDP 时用的托管 profile 目录。**不按端口派生**，换端口不换登录态；留空时显式配的 `browser.profileDir` 优先于它。见 [05](<https://deepseek-browser-use.io.nexus/05>) 第三节 |
| `browser.chrome.debugPort` | `0` | CDP 调试端口。`0` 表示让浏览器自己挑空闲端口（一定在 10000 以上）；需要固定端点时填 10000 以上的值。**不要填 9222**，原因见 [27](<https://deepseek-browser-use.io.nexus/27>) 第八节 |
| `browser.profileDir` | 按端口派生 | 托管 profile 目录（内置 Chromium 与 Firefox 用）；显式配置优先，不受按端口派生影响。本机 Chrome 用上面的 `cdpProfileDir` |
| `browser.profiles.dir` | `~/.config/browseruse/profiles/named` | 命名 profile 根目录；`clone_profile` 写入这里，`start.profile` 从这里解析，见 [39](<https://deepseek-browser-use.io.nexus/39>) |
| `browser.profileDir.perPort` | `true` | 没显式配 `browser.profileDir` 时，目录名按服务端口派生（`shared-<端口>`）。只影响内置 Chromium 与 Firefox |
| `browser.chromium.sandbox` | 按平台 | `true` 始终开启 / `false` 始终关闭 / 不配按平台默认（非 Linux 开，Linux 关） |
| `browser.edge.enabled` | `true` | 是否允许使用本机 Edge |
| `browser.edge.path` | 自动探测 | Edge 可执行文件 |
| `browser.edge.profileDir` | `~/.config/browseruse/profiles/edge` | Edge 专用托管 profile（与 Chrome 那份刻意分开） |
| `browser.edge.extraArgs` | 空 | 追加到 Edge 命令行的额外参数 |
| `browser.firefox.path` | 空（推荐留空） | 显式指定 Firefox 可执行文件；留空交给 Playwright 解析它自带的那份 |
| `browser.firefox.extraArgs` | 空 | 追加到 Firefox 命令行的额外参数 |

### 窗口与视口

| 配置项 | 默认 | 说明 |
| --- | --- | --- |
| `browser.viewport` | `window` | `window`（跟随真实窗口）/ `fixed`（按屏幕算出的固定尺寸）/ `宽x高`（如 `1440x900`）。见 [06](<https://deepseek-browser-use.io.nexus/06>) |

### 启动、动作与留档

| 配置项 | 默认 | 说明 |
| --- | --- | --- |
| `tio.json.skipNull` | 框架级默认 `false` | **框架的全局开关**：序列化是否跳过 null 值字段。本项目在 `app.properties` 里打开了它（响应不再输出每一条都带的 `msg` / `error` 空字段）。取值只认 `true`（忽略大小写），写错按"关"处理。见 [Json 转换](https://tio-boot.com/zh/14_tio-utils/05.html) 与[统一命令接口](<https://deepseek-browser-use.io.nexus/04>) |
| `browser.json.skipNull` | `true` | 项目自己的同名开关，优先级高于上面那个；启动时用它对齐框架开关（`Json.installSkipNull(..)`），并回报到启动日志。设 `false` 可单独关掉本项目的跳过行为，不动别人的设置 |
| `browser.launch.timeoutMs` | `60000` | 启动超时。Playwright 自身给的默认值偏大，启动卡住时不会报错、只会一直等到超时，所以这里收到 60 秒 |
| `browser.action.timeoutMs` | `5000` | 按索引操作的超时。界面较慢的 SPA 上可调大，否则本来能成的点击会被判死 |
| `browser.action.jsFallback` | `true` | auto 普通失败且未发现遮挡时，是否允许最后尝试 JS 派发；对象释放异常不补点，见[点击排障](<https://deepseek-browser-use.io.nexus/08>) |
| `browser.action.mouseFallback` | `true` | auto 普通失败且未发现遮挡时，是否尝试真实鼠标坐标点击；对象释放异常不补点 |
| `browser.capture.enabled` | `false` | 页面变化时是否自动留档截图。关掉后动作照常执行，只是不再落图（显式调 `screenshot` 不受影响） |

### 正文与 Markdown 提取（见 [17](<https://deepseek-browser-use.io.nexus/17>)）

| 配置项 | 默认 | 说明 |
| --- | --- | --- |
| `browser.extract.maxChars` | `20000` | `extract_structured_data` 与 `extract_markdown` 的**默认字符上限**（两条共用同一个键）。公告类页面整页动辄三四万字符，默认值一到就截断，需要读全就调大；单次调用仍可用 `maxChars` 覆盖 |

### 读图与 OCR（见 [11](<https://deepseek-browser-use.io.nexus/11>)）

| 配置项 | 默认 | 说明 |
| --- | --- | --- |
| `browser.ocr.engine` | `windows` | `ocr_image` 走哪条路：`windows` = 系统 OCR（`Windows.Media.Ocr`，擅长验证码这类短文本）；`command` = 跑下面那条外部命令 |
| `browser.ocr.command` | 空 | 外部 OCR 命令模板，支持 `{input}`（图片路径）、`{output}`（结果文本路径）、`{language}` 三个占位符。**带空格或中文的路径要用双引号包住**；不写 `{output}` 就从 stdout 读结果 |
| `browser.ocr.timeoutMs` | `120000` | 外部命令超时（毫秒） |

配了 `engine=command` 但 `command` 为空时**不会装作生效**：`engineId()` 退回 `windows`，而 `get_config` 的 `ocr.describe` 会明说「已退回系统 OCR」。

### 调用追踪（见 [09](<https://deepseek-browser-use.io.nexus/09>)）

| 配置项 | 默认 | 说明 |
| --- | --- | --- |
| `browser.trace.enabled` | `true` | 是否把每次调用的请求与响应落盘 |
| `browser.trace.dir` | `<启动目录>/logs/trace` | 落盘根目录，按自然日分子目录 |
| `browser.trace.maxRecordChars` | `8000000` | 单次调用完整报文的字符上限，超了截断并打标记 |
| `browser.trace.redact.enabled` | `true` | 落盘前脱敏（手机号、身份证号、统一社会信用代码、邮箱、长数字） |
| `browser.trace.redact` | 空 | 追加的自定义脱敏正则，逗号分隔（例如把公司名、商标名也掩掉） |
| `browser.trace.redact.mask` | `***` | 脱敏后的替换文本 |

### 文件暂存（见 [09](<https://deepseek-browser-use.io.nexus/09>)）

| 配置项 | 默认 | 说明 |
| --- | --- | --- |
| `browser.upload.enabled` | `true` | 是否开放 `POST /playwright/upload`。关掉之后上传文件只能靠服务端本来就有的路径 |
| `browser.upload.dir` | `<启动目录>/upload` | 暂存目录 |
| `browser.upload.maxBytes` | `67108864` | 单文件上限（字节），`0` 表示不限 |
| `browser.upload.overwrite` | `true` | 同名文件是覆盖，还是自动改名（`a.jpg` → `a-1.jpg`） |

### 配方与脚本

| 配置项 | 默认 | 说明 |
| --- | --- | --- |
| `browser.recipes.dir` | `<启动目录>/recipes` | 站点配方目录，见 [10](<https://deepseek-browser-use.io.nexus/10>) |
| `browser.js.dir` | `<启动目录>/scripts/js` | `execute_js` 的 `bodyFile` 只能从这个目录里读 |

以上共 38 个运行时可配项。另有 `chromium.revision` 属打包期配置（发行版内嵌浏览器的构建编号），运行时不改。

## 三、只读查询端点

除唯一的控制端点外，服务还开了三个只读端点，方便脚本与浏览器直接打开：

| 端点 | 用途 |
| --- | --- |
| `GET /playwright/health` | 健康检查，返回服务名。用于启动脚本的等待与存活探测 |
| `GET /playwright/methods` | 全部方法名清单（与 `list_methods` 同一份数据），不必靠猜 |
| `GET /playwright/tasks` | 当前活着的任务与共享浏览器状态；排查「孤儿浏览器占着 profile」「任务没关干净」先看它 |
| `GET /playwright/config` | 服务端**生效**的配置：引擎、实际浏览器类型、解析后的 profile 目录、上传与追踪目录、各超时与降级开关、命令数 |

另外两个非 `/playwright/` 前缀的路由：

| 路由 | 说明 |
| --- | --- |
| `POST /playwright/command` | 唯一的浏览器控制端点，见 [04](<https://deepseek-browser-use.io.nexus/04>) |
| `/playwright/upload` | 文件暂存，支持 `GET` 列出 / `POST` 上传 / `DELETE` 删除，见 [09](<https://deepseek-browser-use.io.nexus/09>) |
| `/data/**` | 截图与结构化文本的静态文件服务，`data/<id>/<序号>.png` 与 `.txt` 可以直接 GET |

### `get_config` 的返回结构

```json
{"data":{
  "engine":"chromium",
  "configuredType":"auto",
  "typeChoices":["auto","chromium","chrome","edge","firefox"],
  "profileDir":{"resolved":"<这次真正会用的 profile 绝对路径>","forType":"chrome","perPort":true,
    "configured":null,"cdpConfigured":null,"note":"..."},
  "upload":{"dir":"<暂存目录>","enabled":true,"maxBytes":"67108864"},
  "jsDir":"<execute_js 脚本目录>",
  "trace":{"dir":"<追踪目录>","enabled":true,"redact":true},
  "launchTimeoutMs":60000,
  "action":{"timeoutMs":5000,"jsFallback":true,"mouseFallback":true},
  "workDir":"<服务工作目录>",
  "tasks":0,
  "commands":["start","close","shutdown","..."]
}}
```

响应里还有 `java` 与 `playwright` 两个运行时与依赖版本字段，部署时可用于核对环境，本文不列举具体版本。

`profileDir.configured` 与 `cdpConfigured` 是**显式配置值**（没配就是空），`resolved` 才是这次真正用的目录，`forType` 说明它是按哪个浏览器类型解析出来的 —— 排查「配置看着对、登录态却没了」时看 `resolved`，未显式选 profile 时可与 `start` 回执里的 `data.browser.profileDir` 对照；请求中的 `profile` 或 `userDataDir` / `profileDirectory` 是单次选择，以启动回执为准。注意 `browser.type=chrome` 时 `resolved` 是那条 CDP 路径的目录，**不是** `shared-<端口>`（见 [05](<https://deepseek-browser-use.io.nexus/05>) 第三节）。

## 四、维护类方法

| 方法 | 用途 |
| --- | --- |
| `list_profiles` | 发现命名托管与本机 Chrome profile，返回 `data.profiles`；不启动浏览器 |
| `clone_profile` | 将所选子 profile 离线克隆到命名根目录；参数和安全限制见 [39](<https://deepseek-browser-use.io.nexus/39>) |
| `list_tasks` | 当前任务与共享浏览器状态（`GET /playwright/tasks` 同一份数据） |
| `cleanup` | 清理 `data/` 等运行期产物。**默认只预演**，要真删必须显式传 `dryRun:false` |
| `shutdown` | 关掉全部任务与共享浏览器，**服务进程不退出** —— 停服务前先调它，可以避免留下孤儿浏览器 |

## 五、启动与停止

```shell
# 开发态
cd playwright-server
mvn spring-boot:run

# 发行版
java -jar <发行包文件名>.jar

# Windows 上后台启动 / 干净停止（推荐）
scripts\run\start-server.cmd -Port 10049 -Engine chromium
scripts\run\stop-server.cmd -Port 10049
```

启动脚本会等健康检查通过，并把实际生效的引擎与 profile 目录打出来。停止脚本先 `shutdown`（关任务与共享浏览器）再按端口结束整棵进程树。

**停服务前先关任务。** 浏览器是独立进程：强杀服务不会关掉它启动的浏览器，残留的浏览器会一直占着 profile 目录，下一次 `start` 会失败 —— 走 CDP 那条路（本机 Chrome / Edge）时报的是「浏览器启动后立即退出（退出码 21）：(Chrome 没有输出)」，原因是新进程把命令行交给已有实例后自己退出、连输出也被那个实例吃掉了（见 [27](<https://deepseek-browser-use.io.nexus/27>) 第八节）；走持久化上下文时则表现为卡到启动超时。已经留下了孤儿时，先结束残留的浏览器进程再重启服务；服务侧也有兜底（清理 profile 里的残留锁标记，并在首次启动失败后重建驱动重试一次）。

另外两点：

- **重启服务是有代价的**：普通 `close` 保留最后一个任务的浏览器；显式关闭浏览器或重启服务可能使 session cookie 型的登录态失效，人工得重新登录一次；持久 cookie 不受影响。所以「顺手重启一下」之前先想清楚有没有正在进行的登录环节。
- **别直接关掉那个后台黑窗口**：窗口寿命等于进程寿命，关窗口就是杀进程。要停服务请用停止脚本。

## 六、安全提示

- 服务**没有鉴权**。`execute_js` 能执行任意脚本，`/data/**` 能读到所有截图与页面文本，`POST /playwright/upload` 能往服务端磁盘写文件（只能写进暂存目录、文件名会被清洗，但文件内容不限；`browser.upload.enabled=false` 可关掉这个接口）。
- 示例通过 localhost 访问，但应用配置只明确了端口，不能据此认定服务仅绑定回环地址。部署时检查实际监听地址；对外暴露前增加访问控制，不要把 `/data/**` 直接放到公网。
- `data/`、`logs/trace/` 与 `upload/` 里的文件**都不会自动清理**，长期运行要自己定期清理（或定期调 `cleanup`）。
- 追踪日志默认脱敏，但这是**尽力而为**：按模式认不出来的个人信息（姓名、门牌号）不会被掩掉，交付或共享日志前自己过一眼。详见 [09](<https://deepseek-browser-use.io.nexus/09>)。
