npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-web-fetch-playwright

v0.2.7

Published

Playwright/CDP web-fetch provider for DeepSeek Harness: renders pages in a real browser, denoises them (Readability + DOMPurify), and returns markdown.

Downloads

2,528

Readme

dsh-web-fetch-playwright

English · npm · GitHub

DeepSeek Harness(DSH)双端插件:为内置 web_fetch 工具提供 Playwright/CDP 后端——用真实浏览器渲染网页,经 Readability + DOMPurify + Turndown + GFM 降噪(清洗导航栏、侧边栏、页脚、贴片广告)后输出 Markdown。

npm license node CI stars

特性

  • 真实浏览器渲染 —— 以用户视角加载页面,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。
  • 热配置 —— 「设置 → 插件 → 插件配置」卡片可随时切换后端、上下文模式、降噪开关与并发数,改动对下一次抓取即时生效,无需重启。
  • 预算控制 —— 单次抓取 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/ | 注册 Playwright 网页爬取 配置卡片,通过 settings 服务把改动热写入 $DSH_HOME/settings.yaml。 |

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)

环境要求

  • 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 后重启。

配置项

设置卡片(设置 → 插件 → 插件配置 → Playwright 网页爬取)实时编辑 web-fetch-playwright 设置段:

Playwright 网页爬取 plugin configuration card

| 字段 | 默认 | 说明 | | --- | --- | --- | | backend | local | radio:本地 Playwright / 远端 CDP 地址,每个选项内嵌各自的填空。 | | playwrightPath | 空 | 本地后端:playwright 可执行文件或 Chromium 系浏览器二进制路径;留空按 $PATH 查找,再回退到内置 playwright-core。 | | cdpEndpoint | 127.0.0.1:9222 | 远端后端:host:porthttp(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               # 宿主入口:注册 provider 与设置段
├── 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