前言¶
给 DSH 智能体补网页抓取能力时,常遇到两个问题。一是页面类型不同:SPA 和 JS 重度页面必须经过真实浏览器渲染才能拿到内容,而普通文章页用提取 API 更快,往往要维护两套方案。二是 DSH 的 ctx.web.registerSearchProvider 在同一能力上注册多个提供者时会抛 WEB_PROVIDER_AMBIGUOUS,模型没有选择权。
dsh-web-fetch(作者 runfali,MIT 许可,分类:联网工具)同时处理这两个问题:把 CDP 浏览器渲染和 Tavily Extract 封装成两个独立的 LLM 工具,由模型按上下文自主选择。下面介绍它的设计、安装和使用。
它是什么¶
一句话定位:DeepSeek Harness 的双源网页内容抓取插件。每个数据源注册为一个独立的 DSH 工具,各自带 description 和 schema,LLM 根据上下文挑选,而不是依赖写死的规则。
两个工具的分工:
| 工具 | 适用场景 | 做法 |
|---|---|---|
web_fetch_cdp |
JS 重度页面、SPA、需要真实渲染的站点 | 经 CDP 连接远程 Chrome / cloakbrowser,返回渲染后内容 |
web_fetch_tavily |
快速提取、无需浏览器 | 调用 Tavily Extract API |
核心特性¶
双源独立注册,规避多提供者限制¶
每个数据源是独立的 DSH 工具,模型可以自主选择。两个工具可独立启用/禁用,禁用的工具对 LLM 完全隐藏。
零侵入、零额外依赖¶
这是一个 Cordis bundle 插件,不需要修改 DSH 核心,全部通过 ctx.tools.register 和 settings.installSection 实现(要求 dsh ≥ 0.1.2-alpha)。依赖只有三个包:@deepseek-ai/dsh-settings、@deepseek-ai/dsh-tools、@deepseek-ai/schemastery。
配置热重载¶
设置界面是 UI 卡片加 ~/.dsh/settings.yaml 热重载,改配置不用重启。lib/client.js 内置中英文(zh/en)界面,React 实现。
并发安全与策略可插拔¶
工具声明 isConcurrencySafe: true,支持 AbortSignal。新增数据源只需实现 FetchStrategy 契约(1 个文件),再在 src/index.js 注册 1 行。
安装与启用¶
运行要求:Node.js >= 22,DSH >= 0.1.0-rc.7。先在插件目录执行 pnpm install,再注册插件,最后重启 DSH:
cd /data/dsh-workspace/dsh-web-fetch
pnpm install
dsh plugin --profile web add /data/dsh-workspace/dsh-web-fetch
# 发布后也可用:
# dsh plugin --profile web add dsh-web-fetch
# systemd 环境:
sudo systemctl restart dsh
pnpm install 这一步不能省:Cordis 加载器只从插件目录解析依赖,跳过它会报 ERR_MODULE_NOT_FOUND: @deepseek-ai/schemastery。
配置¶
打开 Settings → Plugin Config → 通用 Web 内容获取(web-fetch),顶部是 CDP / Tavily 的启用复选框,下面是两组配置项:
- CDP 组:CDP Endpoint(默认
http://10.200.0.5:9222)、Timeout ms(60000)、Extra wait after load ms(2000) - Tavily 组:Endpoint(
https://api.tavily.com/extract)、API Key(留空即禁用)、Timeout ms(30000)
保存后写入 ~/.dsh/settings.yaml 的 web-fetch: 下并热重载。也可以用 profile 覆盖,编辑 ~/.dsh/profiles/web/cordis.patch.yml:
- id: web-fetch
config:
cdpEnabled: true
cdpEndpoint: 'http://10.200.0.5:9222'
cdpTimeoutMs: 60000
cdpWaitMs: 2000
tavilyEnabled: false
tavilyEndpoint: 'https://api.tavily.com/extract'
tavilyApiKey: ''
tavilyTimeoutMs: 30000
注意:覆盖 config 会替换整个配置块,必须包含全部键。
典型用法¶
重启后 LLM 会自动看到这两个工具,不需要额外指令。手动测试:
User: 用 web_fetch_tavily 提取 https://example.com 的正文
User: 用 web_fetch_cdp 抓取 https://example.com 这个需要渲染的页面
工具输出结构如下:
{
"sources": [{ "url": "...", "title": "...", "snippet": "...", "content": "...", "provider": "cdp|tavily" }],
"truncated": false
}
新增数据源¶
在 src/strategies/my.js 实现 FetchStrategy 契约:
export function makeMyStrategy(config) {
return {
id: "my",
title: "My Fetcher",
available() { return Boolean(config.apiKey) }, // 轻量检查,不做 I/O
async fetch(req, signal) {
return { sources: [{ url, title, snippet, content, provider: "my" }], truncated: false }
},
}
}
再在 src/index.js 注册一行:
import { makeMyStrategy } from "./strategies/my.js"
ctx.tools.register(makeToolDef("my", makeMyStrategy, "myEnabled", current))
如需新配置项,可选地在 lib/client.js 的 Config 和 FIELD_VIEWS 中补充字段。路由和核心逻辑不用改。
测试¶
仓库自带离线单测,不调用真实浏览器或 Tavily:
node tests/test-cdp-unit.mjs # 21 个测试
node tests/test-tavily-unit.mjs # 9 个测试
适用场景与注意事项¶
适合两类人:需要同时覆盖渲染型页面和轻量提取的 DSH 智能体开发者;想以插件方式扩展 DSH、不想动核心代码的维护者。
使用前注意:
- 插件以当前 dsh 进程的权限运行,安装前建议检查仓库源码与许可证(MIT)。
tavilyApiKey以明文存储于~/.dsh/settings.yaml(settings 系统,非凭据保险库),敏感环境建议通过 profile 的cordis.patch.yml覆盖。- CDP 实现用 Node 原生
http加手写 WebSocket 帧(无ws依赖),未启用 permessage-deflate,与 cloakbrowser 默认配置(关闭压缩)兼容。 - 版本兼容性跟随
@deepseek-ai/dsh-settings/dsh-tools/schemastery。
经过上面的步骤,DSH 就多了一组可插拔的双源抓取工具:模型自主选择渲染或提取,扩展新源只需一个文件加一行注册。仓库与目录页:
- GitHub:https://github.com/runfali/dsh-web-fetch
- 社区插件目录收录页:https://www.skillhub.cn/plugins/runfali/dsh-web-fetch