dsh-web-search-scrape
DSH(DeepSeek Harness)的六档分级抓取式网页检索插件。
用 Node 内置 fetch 直接抓取搜索引擎与社交平台的公开结果页,为 ctx.web 注册一个名为 web-scrape 的搜索 provider,并接管模型侧的 web_search 工具——无需任何搜索 API key。
功能
- 可切换搜索后端:设置卡片里选
backend,即时生效、无需重启 ——local(默认):本插件的六档抓取管线;official:复用内置@deepseek-ai/dsh-web-search-deepseek的 provider(进程内委托),端点/模型/密钥仍由官方「Web search」卡片管理。
- 六档检索深度(T1 极速 → T6 研究):模型可传
tier参数,缺省时按查询特征自动选档。 - 双池引擎:P0 搜索引擎池 + P1 社交平台池,按档位取前 N 个。
- 可选验证与子代理查证:确定性启发式评级(域名信誉 + 相似度聚类),T4 以上派发 WebSearch 子代理分诊,T5/T6 可用
curl打开页面核查上游来源,输出证据等级。 - 设置页卡片:浏览器半在「设置 → 插件」注册配置卡片,配置经
dsh-settings持久化。 卡片默认收起(与同页其他插件卡片一致),点标题栏展开;展开状态记在localStorage(键web-search-scrape.card.open),收起时会显示一行摘要 (当前后端 / 缺省档位 / 是否有未保存修改)。
目录结构
.
├── package.json # 包元信息(name/exports/dsh.engines/dsh.client 声明)
├── LICENSE # MIT
├── cordis.patch.yml # 随包补丁(dsh.bundle.patch),安装时自动应用
└── lib
├── index.js # 宿主半:provider(router) + web_search 工具 + 设置命名空间
└── client.js # 浏览器半:设置页卡片(含 backend 选择)
本仓库直接存放可运行的 ESM 源码,无构建步骤:
lib/*.js即发布产物。
变更记录
1.3.1
修复 DuckDuckGo 解析的两个缺陷(会导致结果重复与静默丢结果):
uddgUrl对非跳转的直接链接返回空串 ——parseDdgHtml里if (!url) continue因此把 DDG 给出的直链结果全部丢弃。现在无uddg=参数时回退为直链本身 (与既有yandexUrl的写法一致;cleanUrl仍负责过滤非 http(s) 的 href)。parseDdgLite未解包l/?uddg=跳转 —— lite 端点的包装链接与 html 端点的直链 在调用方seen集合里是两个不同的 URL,去重不上,同一篇文章会在一次结果里出现两次。 现在 lite 同样走uddgUrl。
实测(同一查询):结果里 URL 含 duckduckgo.com/l/ 的条数由 5 降为 0。
1.3.0
- 新增
allowOfficial(默认false)—— 计费硬保险:backend: official从此不再等于消费许可。 未授权时官方路径直接抛错、零网络请求,误改配置不会产生账单。要用官方搜索需显式打开。 - 新增
maxAutoTier(随包补丁默认3):T4–T6 会派发 LLM 子代理(按会话模型计费), 而autoTier会被「分析/对比/趋势/报告」这类常见词抬到 T5 —— 自动档位封顶可避免静默产生 模型费用;模型显式传tier不受上限影响。 - README 新增「计费」章节:两处成本点、各自的硬开关、实测证据,以及最省心的配置片段。
1.2.0
- 新增
web_search_deep工具(deepTool,默认开):DSH>= 0.1.5把模型侧的web_search移到了 agent 预设平面,预设的同名工具会遮蔽本插件的注册,导致tier传不进来、且结果被预设的searchMaxResults: 8截断。名字不同的工具不会被遮蔽, 深度控制因此在任何预设下都可用。详见上文「为什么有两个工具」。 - 新增
dsh.bundle.patch:仓库自带cordis.patch.yml,安装时由 loader 自动并入 profile 层栈 —— 不再需要手工编辑 profile 的补丁文件。方式 B(符号链接)仍需手工应用。
1.1.0
- 新增
backend设置:可在「设置 → 插件」卡片里于local(本地六档抓取)与official(内置 DeepSeek 搜索)之间实时切换,无需重启。 - 设置卡片改为默认收起:标题栏可点开/收起,展开状态持久化到
localStorage,收起时显示一行摘要。 - 修复 DSH 0.1.5-rc.2 兼容性(1.0.0 在该版本上会直接加载失败):
- 设置 API 迁移到
ctx.settings.installSection(...)(installSettingsSection/settingsNamespace已从dsh-settings移除)。 - 修正
setSource的 thunk 语义:旧代码把() => T当值用,导致设置卡片改了不生效。 - 浏览器半:
@deepseek-ai/dsh-client-runtime→@deepseek-ai/dsh-client-store。 - 浏览器半:设置卡片注册改用 keyed slot 要求的
key(原id会抛错导致卡片不显示)。
- 设置 API 迁移到
- 新增 MIT LICENSE;
package.json补dsh.engines.dsh版本要求。
1.0.0
- 首个版本:六档分级抓取式检索。
为什么有两个工具:web_search 与 web_search_deep
DSH >= 0.1.5 把面向模型的 web_search 工具挪到了 agent 预设平面(每个预设里的
tool-web 行,作用域是 agent 级)。按 dsh-scope 的层级语义,agent 级的同名工具会遮蔽
本插件在 profile 层注册的同名工具。实测证据:实时工具列表里 web_search 的参数是
queries(数组),不是本插件的 query + tier。
这带来两个后果:
tier参数传不进来 —— 模型无法显式指定深度,只能靠查询特征自动选档,或用设置卡片里的defaultTier固定档位;- 结果被截到 8 条 ——
tool-web的searchMaxResults默认是8,而 seam 会按请求的maxResults截断返回值。也就是说即便 T5/T6 抓了 50+80 条,模型最终也只看得到 8 条。
解决方式:本插件同时注册一个名字不同的工具 web_search_deep。名字不同就不会被遮蔽
(没有别的注册者占用该名字),并且它按本插件的配置发请求(searchMaxResults: 130),
所以深度控制和结果预算在任何预设下都可用,不需要用户切换或改造预设。
| 工具 | 注册者 | 何时生效 | 参数 | 结果上限 |
|---|---|---|---|---|
web_search |
预设的 tool-web(0.1.5+) |
总是(遮蔽本插件的同名注册) | queries: string[] |
8(预设配置) |
web_search |
本插件 | 仅当预设不带 web 工具时 | query, tier |
130(本插件配置) |
web_search_deep |
本插件 | 总是(名字不冲突) | query, tier |
130(本插件配置) |
三个都走同一个 provider,所以无论模型点哪一个,六档管线、消息甄别、多源验证都在生效。
不想多一个工具就把 deepTool 设为 false。
想要"只留一个工具"? 见下一节的预设配方。
💰 计费:什么时候会花钱,怎么保证不会
抓取本身永远免费(只是 HTTP 抓公开结果页)。会产生费用的只有两处,两处都有硬开关:
| # | 成本点 | 计费方式 | 默认 | 硬保险 |
|---|---|---|---|---|
| A | backend: official |
走 {baseURL}/messages(Anthropic 兼容 Messages API),按 token 计费 |
local(免费) |
allowOfficial: false |
| B | 档位 T4–T6 的 WebSearch 子代理 | 派生一个 LLM 子代理,按会话所用模型计费 | 仅显式 T4+ 触发 | maxAutoTier: 3 |
A. 官方搜索:双重开关
allowOfficial 默认 false。此时即使 backend 被写成 official,插件也会拒绝执行并直接抛错,
一个请求都不会发出:
backend is "official" but allowOfficial is false — refusing to call the billed
DeepSeek search API. To allow billed official searches, enable "allowOfficial"
in this plugin's settings card. To keep searching for free, set backend back to "local".
也就是说:backend: official 本身不是消费许可。要真的用官方搜索,必须两个都打开
(卡片里选「官方 DeepSeek 搜索(⚠️ 按 token 计费)」+ 打开「⚠️ 允许计费 API」)。
要回到免费,把 backend 切回 local 即可,无需清理 allowOfficial。
实测:
allowOfficial: false+backend: official→available()返回 false,search()抛WEB_PROVIDER_UNAVAILABLE,官方 API 调用次数 0。
B. T4–T6 子代理:自动档位可能"静默"升档
T4 起会派生一个 LLM 子代理做分诊。它的费用不在搜索 API 上,而在你当前会话的模型上。
麻烦的是 autoTier 的关键词表包含 分析 对比 趋势 报告 这类极常见的词:
查询「对比一下这两款产品并分析趋势」 → autoTier 选中 T5(派发子代理)
maxAutoTier 就是给自动档位设天花板。默认随包补丁给的是 3,即:
- 自动档位最高到 T3(全引擎 + 1 个社媒,纯抓取,零模型费用);
- 模型显式传
tier: 5仍然照做(maxAutoTier只约束自动分支); - 想恢复原有行为(不设限)就改成
6。
maxAutoTier: 3 # 1-6;3 = 自动最高到"标准",T4-T6 只能显式调用
想彻底关掉子代理(连显式 T5/T6 也不派发),把 subagentVerify 设为 false。
最省心的配置
backend: local # 不碰付费搜索 API
allowOfficial: false # 就算 backend 写错也不会请求
maxAutoTier: 3 # 自动档位不会静默买 LLM 子代理
subagentVerify: false # 连显式 T5/T6 也不派发子代理(可选,最严格)
配方:用自定义预设做「正统替换」
如果你更想要一个 web_search(而不是 web_search + web_search_deep 两个),可以做一个
自定义 agent 预设,把模型侧的搜索工具交还给本插件。这是平面上更"正统"的做法:模型侧工具
本来就归预设管,改预设即可,不需要和遮蔽机制对抗。
代价:只在选了该预设的会话里生效,新建会话时需要切一下预设。
步骤
1) 从官方 standard 复制一份到用户预设根 ${DSH_HOME:-~/.dsh}/.agent-presets/<id>/:
mkdir -p ~/.dsh/.agent-presets/dsh-scrape
P=<dsh 安装目录>/node_modules/@deepseek-ai/dsh-agent-presets/presets/standard
cp "$P/agent.cordis.yml" "$P/preset.yml" ~/.dsh/.agent-presets/dsh-scrape/
也可以在 GUI「设置 → Agent 预设」里用"复制"完成这一步。
2) 把复制出来的 agent.cordis.yml 里的 tool-web 行改成:
- id: tool-web
name: '@deepseek-ai/dsh-tool-web'
config:
fetch: true # web_fetch 仍由这里提供
search: false # 交出 web_search —— 让本插件的(带 tier)成为唯一一个
searchTimeoutMs: 60000
3) 改 preset.yml 的 name / description(可选,否则选择器里只有目录名)。
4) 新建会话时选这个预设。模型侧就只剩本插件的 web_search,带 tier 参数,
结果上限取本插件的 searchMaxResults(默认 130,不再是预设的 8)。
验证
不必先开会话,可以让宿主单独组合一遍这个预设(真实 mount,但不启动任何会话):在 cordis
预设的会话里用动态 Cordis 插件注入 agentPresets,调用 standingKeyFor('<预设 id>') ——
正常返回即组合可用;抛错会给出四种失败原因之一(包无法解析 / config 非法 / 有行未激活 /
行把服务发布到了进程全局域)。
注意
standingKeyFor是一次真实 mount:成功后会在该进程内驻留一个 standing generation 直到进程退出;失败则清理干净、不留痕迹。
撤销
rm -rf ~/.dsh/.agent-presets/<id>
再把新会话切回 standard 即可 —— 已经在跑的会话仍用它启动时的预设,不受影响。
六档深度
| 档位 | 名称 | 耗时量级 | 引擎 | 社交 | 结果数 | 验证方式 |
|---|---|---|---|---|---|---|
| T1 | 极速 | ~4s | 1 | 0 | 6 | 不验证 |
| T2 | 快速 | ~10s | 2 | 0 | 10 | 启发式评级 |
| T3 | 标准 | ~20s | 全部 | 1 | 20+10 | 启发式评级 |
| T4 | 增强 | ~60s | 全部 | 全部 | 50+80 | 子代理分诊 |
| T5 | 深度 | ~150s | 全部 | 全部 | 50+80 | 子代理 + ≤6 页核实 |
| T6 | 研究 | ~280s | 全部 | 全部 | 50+80 | 子代理 + ≤15 页多轮阅读 |
engines / socials 配置是池:档位按配额取前 N 项(T1=1 个引擎,T2=2 个,T3 及以上=全部)。
引擎池
- P0 搜索引擎:
duckduckgo、bing、baidu、google、yandex - P1 社交平台:
weixin、bilibili、weibo、x、zhihu、douyin、reddit
安装
前置条件
- DSH
>= 0.1.5-rc.2(见package.json的dsh.engines.dsh)。本插件直接引用若干宿主包的内部 API,跨小版本可能断裂:ctx.settings.installSection(...)(dsh-settings,0.1.5-rc.2 起;旧的installSettingsSection/settingsNamespace导出已移除)createSnapshotStore来自@deepseek-ai/dsh-client-store(旧的dsh-client-runtime已不存在)- 设置卡片注册到
settings.plugin.item这个 keyed slot,必须传key(命名空间)而非id DeepSeekSearchProvider/WEB_SEARCH_DEEPSEEK_SETTINGS_NAMESPACE(backend: official委托用)
- DSH 已安装,且使用
webprofile(~/.dsh/profiles/web)。 pnpm-workspace.yaml建议包含nodeLinker: hoisted:本插件不声明依赖(宿主包由 DSH 提供),严格布局下包解析可能失败。- 本仓库为公开仓库,git 安装无需任何认证。
方式 A:从 GitHub 安装(推荐,零配置)
本插件不需要发布到 npm —— GitHub 即可完整分发,三条路径任选:
# A1) 装最新(跟 main)
dsh plugin --profile web add github:BaihaWhite/dsh-web-search-scrape
# A2) 锁定版本(tag,推荐生产用)
dsh plugin --profile web add github:BaihaWhite/dsh-web-search-scrape#v1.3.0
# A3) Release tarball(无需 git,走 HTTPS 下载)
dsh plugin --profile web add \
https://github.com/BaihaWhite/dsh-web-search-scrape/releases/download/v1.3.0/web-search-scrape-1.3.0.tgz
dsh plugin 是 pnpm 的包装命令:写完依赖后会按已安装状态重整 profile 的层栈 —— 任何声明了
dsh.bundle 的依赖会自动加入 dsh.profile.bundles,而本仓库正是这样声明的:
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
于是仓库自带的 cordis.patch.yml 会作为一层补丁自动应用
(web.searchProvider 改指本插件、停用 tool-web 的 search、插入本插件行)——
不需要手工编辑 profile 的 cordis.patch.yml。装完重启 dsh web 即可。
该机制按 node_modules 里解析到的依赖判定,与来源无关:git / tarball / link / npm 都成立。 已装过的用户升级后同样生效:
dsh plugin --profile web update会重新对账层栈。
仓库带有
dsh-plugintopic,因此也会被 DSH 插件商店(按 GitHub topic 检索)收录。若发布了 npm 版本,也可用
dsh plugin --profile web add web-search-scrape(短名 + semver), 但那只是便利性差异,不是分发的前提。
发布到 npm(可选,本项目不需要)
本仓库以 GitHub 分发,package.json 保留了 "private": true 以防手滑误发
(npm 会以 EPRIVATE 拒绝)。若哪天真要发 npm:
# 1) 去掉 package.json 里的 "private": true
# 2) 本机默认 registry 是 npmmirror 只读镜像,publishConfig 已锁定官方源
npm login --registry https://registry.npmjs.org/
npm publish # publishConfig 会把包发到 registry.npmjs.org
包名 web-search-scrape 目前未被占用(2026-09 核对)。
方式 B:本地目录 + 符号链接(开发调试)
注意:克隆目录必须放在 profile 目录内(如下),否则 Node 按真实路径解析裸导入时 找不到宿主包。放到
/tmp之类的目录会报Cannot find package '@deepseek-ai/dsh-...'。方式 B 不会自动应用仓库自带的补丁,需要手工把
cordis.patch.yml的内容复制进 profile 的补丁层(见下一节)。
cd ~/.dsh/profiles/web
git clone https://github.com/BaihaWhite/dsh-web-search-scrape.git web-search-scrape
ln -s ../web-search-scrape node_modules/web-search-scrape
符号链接让补丁里的 name: 'web-search-scrape' 能被解析到包目录。
手动安装时的补丁配置
方式 A 会自动应用,此节仅方式 B(或想自定义)时需要。把以下条目加进
~/.dsh/profiles/web/cordis.patch.yml(内容与仓库自带补丁一致):
# 1) web 行:searchProvider 改指本插件的 provider id
# (补丁语义:整段替换目标行 config,故需重述全部字段)
# 本插件是该 id 的 router:真正走哪条后端由设置卡片的 backend 决定。
- id: web
config:
searchProvider: web-scrape
# 2) tool-web 行:停用内置 web_search,交给本插件注册同名工具
- id: tool-web
config:
fetch: false
search: false
searchTimeoutMs: 180000
searchMaxResults: 130
# 3) web-search-deepseek:**保持启用**(不要 disabled)
# 它是 backend=official 的委托目标,也提供官方「Web search」设置卡片
# (端点 / 模型 / 密钥)。把它 disabled 后 official 仍可跑(走默认端点 +
# 凭据),但用户将无法在界面上配置官方端点。
# 它注册的 deepseek-official provider 不会被选中——第 1 项已把
# searchProvider 钉在 web-scrape 上,因此不会产生歧义。
# 4) 挂载本插件
- insert:
- id: web-search-scrape
name: 'web-search-scrape'
config:
backend: local # local | official(设置卡片里可随时切换)
engines: [duckduckgo, bing, baidu, google, yandex]
socials: [weixin, bilibili, weibo, x, zhihu, douyin, reddit]
verify: true
subagentVerify: true
defaultTier: 0
toolBudgetMs: 300000
searchMaxResults: 130
生效
# 预检:确认没有 Cannot find package 报错
dsh --profile web --dump-config | grep -i "cannot find"
# 重启 dsh web(页面刷新不够)
配置项
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
allowOfficial |
boolean | false |
计费硬保险。为 false 时 backend: official 会被拒绝且不发请求;见上文「计费」 |
maxAutoTier |
number | 6(随包补丁给 3) |
自动档位上限,只约束自动分支;显式 tier 不受影响 |
deepTool |
boolean | true |
是否额外注册 web_search_deep(名字不同、不被预设遮蔽、带 tier、结果上限取本插件的 searchMaxResults) |
backend |
local | official |
local |
搜索后端。local = 本插件六档抓取;official = 委托内置 DeepSeek 搜索。改动即时生效(每次搜索重新取快照) |
engines |
string[] | [duckduckgo, bing, baidu, google, yandex] |
P0 搜索引擎池 |
socials |
string[] | [weixin, bilibili, weibo, x, zhihu, douyin, reddit] |
P1 社交平台池 |
engineResults |
number | 50 |
单引擎抓取结果上限 |
socialResults |
number | 80 |
社交平台结果上限 |
socialPerSite |
number | 30 |
单社交站点结果上限 |
verify |
boolean | true |
是否启用启发式验证评级 |
subagentVerify |
boolean | true |
是否派发 WebSearch 子代理查证 |
subagentTimeoutMs |
number | 120000 |
子代理超时(毫秒) |
subagentMaxSources |
number | 60 |
交给子代理的来源上限 |
scrapeTimeoutMs |
number | 20000 |
抓取阶段截止时间(毫秒) |
toolBudgetMs |
number | 60000 |
web_search 工具层强制超时,子代理预算据此自适应 |
defaultTier |
number | 0 |
0 = 自动启发式;1-6 = 固定档位 |
searchMaxResults |
number | 130 |
每次 seam 请求的 maxResults |
timeoutMs |
number | 20000 |
单请求超时(毫秒) |
maxPages |
number | 130 |
抓取页数上限 |
档位自身拥有预算与验证模式:显式传入的
tier会覆盖上述engineResults等字段。
注意事项
toolBudgetMs与工具超时相关;把工具超时改大(例如 180s)需要重启 dsh web 才生效。- 抓取的是搜索引擎/社交平台的公开结果页,对方改版或限流会导致该引擎失败——插件会记录失败项并在结果里给出说明,而非静默丢弃。
- T5/T6 会启动子代理并可能执行
curl打开外部页面,耗时与资源消耗显著高于 T1/T2。 - 本插件开发与验证环境:DSH
0.1.0-rc.6,profile~/.dsh/profiles/web。在更高版本 DSH 上使用前建议先在测试 profile 验证。
卸载 / 回滚
# 方式 A 安装的:
dsh plugin --profile web remove web-search-scrape
# 方式 B 安装的:
rm ~/.dsh/profiles/web/node_modules/web-search-scrape
rm -rf ~/.dsh/profiles/web/web-search-scrape
随后从 cordis.patch.yml 中删除第 4 项 insert 段(若要恢复内置搜索,再一并删除第 1-3 项补丁),重启 dsh web。
许可证
MIT License,见 LICENSE。
No comments yet. Be the first to write one.