DeepSeek Harness 插件

chendefine/dsh-web-fetch-playwright

Star 数 ★ 6 下载量(近 30 天) 2,389 分类 浏览器与网页 收录于 2026-08-23 npm dsh-web-fetch-playwright

为内置 web_fetch 工具提供 Playwright/CDP 后端:用真实浏览器渲染网页,经 Readability + DOMPurify 降噪后返回 Markdown。

安装

# npm 包(预构建)

dsh plugin --profile web add dsh-web-fetch-playwright

# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)

dsh plugin --profile web add github:chendefine/dsh-web-fetch-playwright

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。

README

English · 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)会被替换为带大小的占位符,如 ![alt](data:image/png;base64,...8.9KB),避免 base64 字符流刷屏。
  • 两种后端 —— 本地启动 Playwright 浏览器,或通过 DevTools 协议(CDP)驱动一个已在运行的浏览器。
  • 浏览器解析 —— 配置路径 → $PATH 上的 playwright CLI → 插件自带的 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。若恢复出的响应本身来自 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 派生):

Playwright 网页爬取 plugin configuration card

字段 默认 说明
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 模式不需要本地浏览器:插件在生命周期内对远端浏览器保持一条共享连接(连接断开自动重连,地址改动后自动换连),每次抓取只租用远端浏览器里的一个标签页,抓取结束即关闭。因此并发数按"标签页"计,默认也更高(50)。插件卸载时断开共享连接(绝不会关闭远端浏览器本身)。

CDP 上下文模式(是否共享浏览器 profile)

「共享浏览器上下文」勾选(默认,profile 模式)时,每次抓取是远端浏览器默认 context——真实 profile——里的一个标签页:cookie 与 localStorage 双向互通,浏览器里已登录的站点会以登录态被抓取,和你手动开标签页一样。共享 context 永不关闭;资源过滤与弹窗守卫只挂在本次抓取自己的标签页上,不会干扰你人工打开的其他标签页。取消勾选(隔离模式)时,每次抓取使用全新隐身式 context——匿名读取,什么都不保留。

profile 模式风险须知 —— 它把 web_fetch 从"匿名读网页"升级为"以浏览器登录身份行动":

  • 被抓取的恶意页面若诱导 agent 请求 GET 型状态变更 URL(登出、改设置、API 操作),请求会自动携带会话 cookie。
  • 同一站点的并发抓取共享一个 cookie jar,一方的登出 / Set-Cookie 会影响另一方。
  • 输出开始依赖浏览器历史(A/B 分桶、语言偏好);远端 profile 的站点数据只增不减,插件不做清理。

登录态持久的前提是 user-data-dir 持久化。有头(推荐,手动登录一次):

google-chrome --remote-debugging-port=9222 --user-data-dir="$HOME/.config/chrome-dsh-profile"

无头服务器(先在有头环境预置登录态):chromium --headless=new --remote-debugging-port=9222 --user-data-dir=/data/chrome-dsh-profile。不要叠加 --incognito 或一次性 user-data-dir——都会让 profile 模式失效。设计依据与已核实的 playwright-core 源码事实见 docs/context-mode-profile.md。

Cloudflare 挑战处理(有界自然等待)

部分严格站点会在返回真实页面前先给一个 Cloudflare 验证中间页。真实浏览器通常几秒内就能自行通过验证;但只看第一次响应的抓取会把中间页当成正文返回(0.2.5 之前的旧行为;把 challengeWaitMs 设为 0 可随时复现,或在仓库检出、执行 pnpm build 后运行 node scripts/challenge-demo.mjs 看本地模拟站点的前后对比、node scripts/challenge-online.mjs <url> 对真实站点做在线对比)。

开启等待(默认)后的流程:

  1. 识别 —— 响应带 cf-mitigated: challenge(Cloudflare 官方文档注明所有挑战页类型都带此头),或 403/503 且 server: cloudflare 的 HTML 文档,或本地化的中间页本身("Just a moment…" / "请稍候…" / "Минутку…" 等 title 家族,以及结构性标记:/cdn-cgi/challenge-platform/ 脚本、#challenge-* 元素、cf-chl-widget- 框架、window._cf_chl_opt)。内容级标记只是兜底层,且仅对"挑战兼容"的响应(403/429/503 或来自 Cloudflare 边缘——server: cloudflare / cf-ray)运行——因为中间页从不会以普通 200 返回,所以正文里引用了挑战文案的普通文章绝不可能被误判。Cloudflare Bot Management 的被动 JavaScript-Detections 遥测(/cdn-cgi/challenge-platform/scripts/jsd/,会注入受保护站点的每一个正常页面——如 openrouter.ai)在前缀扫描前被显式中和,因此带真实内容的 200 正常页绝不会被误判。硬封锁页("Sorry, you have been blocked")单独分类并立即失败——等待无法解除。
  2. 同标签页、同上下文的有界等待 —— 每 500ms 轮询活 DOM,等浏览器跑完自己的验证;同时跟踪最后一次主 frame 导航响应,所以重载进来的真实文档的状态码和响应头才是最终上报的。SPA 式清除(无导航、纯内容替换)由同一个 DOM 探测捕获。
  3. 有界重试 —— 窗口耗尽后,同一标签页默认再导航一次(challengeRetries),上下文里已有的通关 cookie 继续生效。
  4. 明确失败 —— 返回独立的 WEB_FETCH_CHALLENGE 错误码(web seam 的 code 是开放字符串,允许 provider 专属码),消息中写明站点、等待预算与最后一次挑战响应的状态。

安全边界(刻意为之):不点击 Turnstile、不解验证码、不注入 token、不伪装指纹/UA、不轮换代理、不导出 cookie——隔离模式下本次抓取挣到的通关态随其 context 一起销毁;profile 模式下它留在远端浏览器自己的 profile 里,插件从不复制或清理。等待始终受 challengeWaitMs 与 45s 单次抓取预算双重约束,永不无限阻塞。

开发

pnpm install
pnpm typecheck   # tsc --noEmit
pnpm test        # vitest run(无浏览器时浏览器集成用例自动跳过)
pnpm build       # tsc 声明 + tsdown(宿主 ESM + 客户端 module-registration bundle)

仓库结构:

src/
├── index.ts               # 宿主入口:注册 fetch provider(设置段由插件的 Config schema 派生)
├── config.ts              # schemastery schema、CDP 端点归一化
├── provider.ts            # WebFetchProvider:导航、超时、信号量、截断
├── markdown.ts            # 降噪管线(Readability + DOMPurify + Turndown/GFM)
├── playwright-resolve.ts  # 本地后端发现(路径 / $PATH / 内置 core)
├── types.ts               # Playwright 结构化类型(运行时模块动态发现)
└── client/                # 浏览器半端:设置卡片、表单模型、多语言
tests/                     # 单元 + provider + 浏览器集成(可自跳过)

开发与发布流程见 CONTRIBUTING.md,安全模型与漏洞报告见 SECURITY.md。

安全边界

与内置 HTTP provider 同立场:未实现 SSRF/私网防护——浏览器能访问的目标,本 provider 就能抓。CDP 地址由设置页配置,不做回环限制,请在可信环境暴露设置页。抓取仅在本地渲染,除目标页面自身外不会向任何地方发送数据——但 profile 模式下请求(以及恶意页面诱导 agent 触发的状态变更)会携带远端浏览器的登录会话,见上文风险须知。

许可证

MIT © 2026 chendefine

内容来自项目 README(GitHub)↗

链接

同类插件

查看整个分类 →

社区评论

评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。