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

# 调用追踪、页面留档与文件上传

本文说明服务在自己的工作目录里留下了什么、放在哪、以及客户端-服务器模式下怎么把文件送到服务端。这些产物是排障与事后复盘的唯一凭据，同时也是需要定期清理的磁盘占用。

[[toc]]

## 一、每一次调用都有留档

每一次调用（含批量里的每一步）的请求与响应都会落到 `<启动目录>/logs/trace/<自然日>/` 下，例如 `logs/trace/20260924/`：

| 文件 | 内容 | 用途 |
| --- | --- | --- |
| `steps.log` | 每次调用一行：时间、序号、任务 ID、方法、成功与否、耗时、一句话结果 | **人看的时间线**，一眼看出第几步开始不对 |
| `calls.jsonl` | 每次调用一行 JSON：调用摘要 + 从 `data` 里摘出的关键字段（url / title / seq / screenshot / state_file / changed 等）+ 完整请求体 | 机器读，按 id 或 method 过滤 |
| `NNNNNN-<任务ID>-<方法名>.json` | 这一次调用的**完整**请求与**完整**响应（含整页 `data.text`） | 要看原始报文、要复现某一步时的唯一凭据 |
| `uploads.log` | `POST /playwright/upload` 的落盘记录：文件名、大小、摘要 | 只记元数据，**不记文件内容** |

文件名形如 `000001-1001-get_browser_state.json`，序号与页面留档的序号可以互相印证。

**排查顺序建议**：先看 `steps.log` 定位出问题的那一步 → 再看同名序号的 `.json` 看完整报文 → 最后按里面的 `data/<id>/<序号>.txt` 与 `.png` 看当时页面。

写盘失败（磁盘满、目录没权限）只留一条警告，**不会让浏览器命令失败** —— 所以不能把日志存在当成命令成功的证据，反过来命令失败也不代表日志写失败。

### 脱敏

`browser.trace.redact.enabled` 默认**开启**，落盘前会把以下内容替换成掩码（默认 `***`）：

- 手机号；
- 18 位身份证号 / 统一社会信用代码；
- 邮箱；
- 16～19 位长数字。

`browser.trace.redact` 可以追加自定义正则（逗号分隔），例如把公司名、商标名也掩掉；`browser.trace.redact.mask` 改掩码文本。

> **脱敏是「尽力而为」**：按模式匹配，认不出来的个人信息（姓名、门牌号、账号）不会被掩掉。填表类任务里请求体带着姓名、手机号、证件号、详细地址是常态，交付或共享 `logs/trace/` 之前请自己过一眼。需要原文时把 `browser.trace.redact.enabled` 设为 `false`。

### 其他开关

上述 trace 脱敏不代表 `data/<id>/` 的截图、页面文本或临时参数文件也已经脱敏。六位短信验证码、密钥、签名链接和图片中的个人信息尤其不能依赖普通模式匹配识别；分享前应逐类核验。操作经验见[表单与多层弹窗排障](<https://deepseek-browser-use.io.nexus/08>)。

| 配置项 | 默认 | 说明 |
| --- | --- | --- |
| `browser.trace.enabled` | `true` | 关掉之后完全不落盘 |
| `browser.trace.dir` | `<启动目录>/logs/trace` | 换目录 |
| `browser.trace.maxRecordChars` | `8000000` | 单次调用完整报文的字符上限，超了截断并打标记。只有 `screenshot` 不带 `path` 时的内联大图可能撞到 |

## 二、页面留档：`data/<id>/`

默认保留结构化文本，截图需显式请求。产物落在进程工作目录下的 `data/<任务ID>/` 里：

| 时机 | 产物 |
| --- | --- |
| 「会改变页面」的方法执行成功 | 默认不截图；显式配置 `browser.capture.enabled=true` 后才生成 PNG |
| 每次 `get_browser_state` | `<序号>.txt`；显式传 `screenshot:true` 或 CLI `state --screenshot` 时另有同序号 PNG |
| 显式调 `screenshot` / `get_element_screenshot` | `data/<id>/shot-N.png` |

几点要知道：

- 序号是**每个任务独立的自增序号，从 1 开始**；一对 `.png` / `.txt` 序号相同，表示是同一时刻的页面。
- 两个文件都可以直接 GET：`GET <服务地址>/data/<id>/<序号>.png` 与 `.../<序号>.txt`。
- `<序号>.txt` 的内容是「页签文本块 + 空行 + 可交互结构化文本」，也就是 `data.browser_state` 加 `data.text`，方便事后离线复看某一步的页面。
- 哪些方法算「会改变页面」：导航类、点击与交互类、滚动与鼠标类、页签类、等待类、`execute_js` 与部分设置类。纯读取类（`get_url`、`get_cookies`、`is_visible` 等）不截图，否则每读一个值就多一张一模一样的图。
- 显式启用动作自动截图后，批量调用时每一步结果包含对应截图，所以一次批量请求就能拿到整段操作的页面变化历史。
- 截图前会尽力等页面进入 `DOMCONTENTLOADED`（有上限，等不到也照常截图），不会因为等待失败丢掉这一张。
- 截图失败不会让方法失败，原因在 `data.screenshot_error`。
- 长时间跑下来 `data/` 会涨得很快，`browser.capture.enabled` 默认 false，仅在明确需要动作自动截图时设置 true；文本留档独立保留（显式调 `screenshot` 不受影响）。

**这些文件不会自动清理**，`data/` 已经加进版本控制的忽略列表；不需要时直接删目录，或用 `cleanup`（**默认只预演**，要真删必须显式传 `dryRun:false`）。

## 三、客户端侧的留档

服务端记的是「收到了什么」，客户端记的是「发出去了什么」—— 两者在「服务没起来、请求根本没发出去」这种情况下只有客户端那份能说明问题。

| | PowerShell 脚本 | Python 客户端 |
| --- | --- | --- |
| 形态 | 传一个请求文件发一次请求 | 子命令式 CLI，也可作为库导入 |
| 留档位置 | `logs/agent/<会话>/` | 同左 |
| 文件 | `NNN.req.json` / `NNN.res.json` 成对 + 一份 `steps.log` | 同左 |

请求在发送前就落盘，所以连「没发出去」也能看出来。客户端侧使用与服务端同一套脱敏规则。

## 四、文件上传：`POST /playwright/upload`

客户端-服务器模式下，`upload_file` 的 `path` 必须是**服务端**能打开的路径。所以客户端要先把文件送到服务端，再把返回的路径回填给 `upload_file`。

同一个路由支持四种用法：

| 用法 | 说明 |
| --- | --- |
| `POST /playwright/upload?filename=图样.jpg` | 请求体直接是文件字节 |
| `POST /playwright/upload`（`multipart/form-data`） | 文件字段默认叫 `file` |
| `POST /playwright/upload`（`application/json`） | `{"filename":"图样.jpg","contentBase64":"..."}` |
| `GET /playwright/upload` | 列出暂存目录里的文件 |
| `DELETE /playwright/upload?name=图样.jpg` | 删除一个文件 |

回执里的 `path`（服务端绝对路径）与 `relativePath`（相对暂存目录的名字）都可以直接喂给 `upload_file`。

安全约束：

- 文件名会被清洗（只留基本名、去掉路径分隔符与控制字符、保留中文），并且**只能落在暂存目录里**；
- 单文件上限由 `browser.upload.maxBytes` 控制（默认 64MB，`0` 表示不限）；
- 同名文件默认覆盖，`browser.upload.overwrite=false` 时自动改名（`a.jpg` → `a-1.jpg`）；
- 整个接口可以用 `browser.upload.enabled=false` 关掉 —— **对外暴露的服务建议关掉它**。

暂存目录默认是 `<启动目录>/upload`，同样**不会自动清理**。

## 五、`execute_js` 的脚本目录

`execute_js` 支持把脚本放在服务端，用 `bodyFile` 引用：

```json
{"id":"1001","method":"execute_js","params":{"bodyFile":"read-table.js","vars":{"sel":".vxe-table--body-wrapper"}}}
```

- `bodyFile` **只能读脚本目录下的文件**（默认 `<启动目录>/scripts/js`，可用 `browser.js.dir` 改），防止用它去读任意文件；
- 脚本里可以用 `{{变量名}}` 占位，由 `vars` 注入 —— 中文、引号、换行都不用在客户端拼字符串。

这条路径存在的意义是绕开传输层的坑：多行脚本经命令行或消息队列传递时可能被截断，到服务端只剩第一行，报的还是语法错误（`Unexpected end of input`），很难联想到是传输问题。服务在检测到「脚本像是被截断了」时会直接给出改用 `bodyFile` 的提示。
