dsh-web-fetch-playwright
Playwright/CDP web-fetch provider for the built-in web_fetch tool: renders pages in a real browser, denoises them (Readability + DOMPurify) and returns markdown.
Install
dsh plugin --profile web add dsh-web-fetch-playwright
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
dsh-web-fetch-playwright
[English](./README.md) · npm · GitHub
DeepSeek Harness(DSH)双端插件:为内置 web_fetch 工具提供 Playwright/CDP 后端——用真实浏览器渲染网页,经 Readability + DOMPurify + Turndown + GFM 降噪(清洗导航栏、侧边栏、页脚、贴片广告)后输出 Markdown。
特性
- 真实浏览器渲染 —— 以用户视角加载页面,SPA 客户端渲染内容也能抓到,而非只有原始 HTML。
- 降噪管线 —— Mozilla Readability 提取正文,DOMPurify 移除布局/噪音标签(导航、侧边栏、页脚、广告、表单),Turndown + GFM 插件按与内置
tool-web渲染器一致的风格转成 Markdown。内联data:图片(Docusaurus 等构建工具会把截图以 base64 内嵌进 HTML)会被替换为带大小的占位符,如,避免 base64 字符流刷屏。 - 两种后端 —— 本地启动 Playwright 浏览器,或通过 DevTools 协议(CDP)驱动一个已在运行的浏览器。
- 浏览器解析 —— 配置路径 →
$PATH上的playwrightCLI → 插件自带的playwright-core;CDP 模式完全不需要本地浏览器。 - 共享或隔离会话(CDP) —— 每次抓取严格限定为一个标签页。本地后端每次抓取启动并关闭自己的浏览器;CDP 后端对远端浏览器保持一条共享连接,每次抓取只在其里开一个标签页、用完即关。默认该标签页位于远端浏览器的真实 profile(沿用其 cookie、localStorage 与已登录会话,效果类似
playwright-cli open);取消勾选「共享浏览器上下文」则切换为每次抓取全新隔离 context。 - 热配置 —— 插件页(侧边栏 → 插件 → dsh-web-fetch-playwright,dsh ≥ 0.1.7)在插件自己的包页面直接展示完整表单(也可通过该行的「配置」入口进入同一表单);可随时切换后端、上下文模式、降噪开关与并发数,提交的改动对下一次抓取即时生效,无需重启。
- 预算控制 —— 单次抓取 45s 超时;并发按后端定价(
maxConcurrency,默认本地 4 个浏览器 / CDP 50 个标签页;排队的抓取等不到空位会在 20s 内尽快报错并提示重试,而不是一直挂到被工具层中止);拦截图片/字体/媒体子请求;返回体 10 万字符封顶。 - Cloudflare 挑战有界等待 —— 导航落到验证中间页("Just a moment…" 及其多语言同族,通过官方
cf-mitigated: challenge响应头 + 结构性页面标记识别)时,抓取保持同一标签页与上下文,等待浏览器自行通过验证:跟踪最后一次主 frame 响应(真实页面随后重载进来),并轮询活 DOM 以捕获 SPA 式清除。有界且可配置(challengeWaitMs,默认 15s;0恢复旧版首响应行为),附带同标签页有界重试(challengeRetries,默认 1)。预算耗尽时以独立的WEB_FETCH_CHALLENGE错误码明确失败,而不是把中间页当正文返回。全程不点击、不注入验证码答案、不伪造浏览器状态、不导出或复制 cookie。
工作原理
半端 · 位置 · 职责
宿主(服务端) · src/ · 向 ctx.web 注册 fetch provider(id playwright);cordis.patch.yml 把 web seam 的 fetchProvider 固定为本插件,并启用 web_fetch 工具(60s 预算)。
浏览器(客户端) · src/client/ · 在插件页的 plugins.bundle.config / plugins.row.config 席位注册 Playwright 网页爬取 配置表单,通过共享的设置表单服务提交改动。
web_fetch (tool-web)
└─ ctx.web.fetchProvider = playwright
├─ local: 解析(路径 → $PATH → 内置 playwright-core)→ chromium.launch
├─ cdp: connectOverCDP(endpoint)
├─ page.goto → 等待稳定(networkidle,尽力而为)→ page.content()
├─ 降噪:jsdom → 内联 data: 图片改占位符 → Readability → DOMPurify → Turndown(GFM)
└─ Markdown(关闭降噪时返回原始 HTML)
非 2xx 状态码是结果而非错误 —— 这是 web seam 的契约(@deepseek-ai/dsh-web):抓取到的 404/418/500 会连同状态码一起正常返回,与 dsh-web-fetch-http 的行为一致。但对仅有状态的响应 Chromium 会把事情搞砸 —— 空 body 的错误状态(httpbin 的 /status/404)会让整个导航以 net::ERR_HTTP_RESPONSE_CODE_FAILURE 失败,204/205 则因无可渲染内容被中止(net::ERR_ABORTED)。因此 provider 在每次抓取中都跟踪最后一个主框架响应(该响应先于 Chromium 判定导航失败到达),并把这类失败恢复成带状态码的正常结果,web_fetch 会报告 Fetched … (HTTP 404) 而不是抛出 WEB_PROVIDER_ERROR。同样的恢复也覆盖下载型响应 —— 带 Content-Disposition: attachment 的 body(publish.twitter.com 的 oembed 端点——agent 读取推文常用的替代 URL——返回的 JSON 正是如此)永远不会被提交为文档:goto 以 Download is starting 拒绝,且 body 已从网络栈中流失,provider 因此通过页面的 API request context 重新拉取并返回可解码内容;不可解码的下载(PDF、ZIP)仍以 WEB_UNSUPPORTED_CONTENT_TYPE 失败。若恢复出的响应本身来自 Cloudflare 挑战边缘,仍以 WEB_FETCH_CHALLENGE 失败;传输错误与取消也依旧按错误处理。
环境要求
- DSH web profile(
dsh web),Node.js ≥ 20。 - 本地后端:装有 Chromium 的 Playwright、Chromium 系浏览器可执行文件,或默认缓存里有浏览器的
playwright-core。 - CDP 后端:任意已带
--remote-debugging-port启动的浏览器(如chromium --headless --remote-debugging-port=9222)。
安装
从 npm registry 安装(预构建产物,无需构建授权):
dsh plugin --profile web add dsh-web-fetch-playwright
从 GitHub 仓库安装(源码型,pnpm 会在安装时跑 prepare 构建;若 pnpm 拦截构建脚本,请在 profiles/web/pnpm-workspace.yaml 中放行该包):
dsh plugin --profile web add github:chendefine/dsh-web-fetch-playwright
或通过 DSH 插件市场(设置 → DSH插件市场)一键安装——本仓库带 dsh-plugin topic,会被自动收录。
bundle 插件加入 profile 层栈后需重启 dsh web 生效;卸载用 dsh plugin --profile web remove dsh-web-fetch-playwright 后重启。
配置项
插件页(侧边栏 → 插件)从列表一跳即达:dsh-web-fetch-playwright 卡片落在包页面,完整表单位于其描述与行列表之间;该行的「配置」入口在插件自己的标题下打开同一表单。表单实时编辑 web-fetch-playwright 设置段(由插件条目的 Config schema 派生):
字段 · 默认 · 说明
backend · local · radio:本地 Playwright / 远端 CDP 地址,每个选项内嵌各自的填空。
playwrightPath · 空 · 本地后端:playwright 可执行文件或 Chromium 系浏览器二进制路径;留空按 $PATH 查找,再回退到内置 playwright-core。
cdpEndpoint · 127.0.0.1:9222 · 远端后端:host:port、http(s)://… 或 ws(s)://…。
shareBrowserContext · true · 仅 CDP 后端。勾选(profile 模式):每次抓取是远端浏览器默认 context(真实 profile)里的一个标签页,cookie/localStorage 与之互通、已登录会话直接生效,抓取结束只关标签页;取消勾选(隔离模式):每次抓取使用全新隐身式 context,互不共享。本地后端忽略此字段。
denoise · true · 是否启用降噪;关闭时返回整页渲染 HTML,交由工具层转换。
maxConcurrency · (自动) · 同时渲染的页面上限(1–200)。留空按后端取默认:本地 4(每个槽位启动一个浏览器)/ CDP 50(远端浏览器已就位,每个槽位只是一个标签页)。超出的请求短暂排队;20s 内等不到空位则以 WEB_FETCH_TIMEOUT 尽快失败并提示重试或调大该值,而不是一直挂起直到工具层预算中止。
challengeWaitMs · 15000 · Cloudflare 挑战的有界自然等待上限(毫秒,0–60000),在同一标签页内等待浏览器自行通过验证。0 关闭整条挑战处理链路——直接返回首次响应(0.2.5 之前的旧行为)。
challengeRetries · 1 · 一个等待窗口耗尽后的同标签页重新导航次数(0–3);浏览器已拿到的通关 cookie 留在上下文里供重试使用。总耗时始终受 45s 单次抓取预算约束。
本地后端解析顺序:
1. 配置的路径(自动判别 Playwright CLI / 浏览器二进制);
2. $PATH 上的 playwright(其包自带该安装的浏览器注册表);
3. 插件内置的 playwright-core——需要 PLAYWRIGHT_BROWSERS_PATH 或默认缓存里有浏览器,否则报错会提示 playwright install chromium。
Windows 说明 ——$PATH已按平台分隔符(;)扫描,但 npm/pnpm 全局安装暴露的playwright是.cmd/.ps1垫片,从垫片位置向上找不到包根,自动发现可能仍落在第 3 步(内置 core)。要使用指定安装的浏览器注册表,请把playwrightPath显式指向playwright包目录或浏览器二进制。
CDP 模
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::15,最后更新 2026-10-26