dsh-web-search-free
DeepSeek Harness(dsh)的免费网页搜索 / 抓取插件:向 ctx.web 注册两个不需要任何 API Key 的 provider。
free(搜索):按顺序尝试多个公开搜索入口,首个出结果的即返回。free-http(抓取):浏览器 UA 的普通 HTTP 抓取,允许跨源跳转,默认拦截内网地址。
官方三个搜索 provider(deepseek-official / exa / perplexity)都需要 Key,缺 Key 时 web_search 直接失败。本插件替换的就是这条链路;web_fetch 本来也可以用官方 dsh-web-fetch-http,本插件的 free-http 只是补上浏览器 UA 与跨源跳转。
安装
前置:pnpm
dsh plugin 内部固定调用 pnpm(Windows 上走 .cmd 垫片),没装会报 'pnpm' 不是内部或外部命令 或 pnpm not found on PATH。npm 不能替代:
npm i -g pnpm # 或 corepack enable pnpm
三种装法
# 1. 从 npm 装(已发布时)
dsh plugin --profile web add dsh-web-search-free
# 2. 从本地 tgz 装(跨机器搬运用这个;tgz 由 npm pack 生成,内含已编译的 lib/)
dsh plugin --profile web add ~/Downloads/dsh-web-search-free-0.1.0.tgz
dsh plugin --profile web add ./dsh-web-search-free-0.1.0.tgz # 相对路径按当前目录解析
# 3. 从源码目录装(边改边用;目标机需先在该目录 npm install && npm run build)
dsh plugin --profile web add /path/to/dsh_free_web_search
profile 之间不共享,headless 要单独装一次:dsh plugin --profile headless add …。
tgz 是快照式安装:改了代码要重新 npm pack 再 add 一次。生成 tgz:
npm run build && npm pack # 产出 dsh-web-search-free-<version>.tgz
该包声明了 dsh.bundle.patch,安装后会作为一个 patch 层自动挂载,并把 web 行的 searchProvider 指到 free、fetchProvider 指到 free-http。重启 dsh 生效。
不装 pnpm 的手动装法
dsh plugin 实际只做三件事:确保 profile 目录有 package.json → 在该目录跑包管理器 → 把带 dsh.bundle.patch 的包名追加到 dsh.profile.bundles。用 npm 手动等价操作:
cd $DSH_HOME/profiles/web # Windows: %USERPROFILE%\.dsh\profiles\web
npm i /path/to/dsh-web-search-free-0.1.0.tgz
然后编辑同目录 package.json,把包名加到 dsh.profile.bundles 的末尾:
{
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-web-search-free"
]
}
}
}
顺序即补丁层顺序,后面的覆盖前面的——必须排在 @deepseek-ai/dsh-web-app 之后,否则它会把 web 行的 searchProvider 改回官方 provider。
确认装上了
设置界面里看不到 searchProvider:可编辑的插件卡片只覆盖少数几个官方插件,dsh-web 的 provider 选择属于组合层配置。插件清单(只读)里出现 web-search-free 只能说明插件加载了。要确认生效:
dsh --profile web --dump-config
这个 flag 不启动服务,只打印合成后的插件树。找 - id: web,其 config 应为 searchProvider: free / fetchProvider: free-http。若仍是 deepseek-official,就是上面说的补丁层顺序问题。
再问一个必须联网才能回答的问题,在 Trajectory 里看 SUBTOOL: web_search:
- 有结果且来源是真实 URL → 已走
free。 WEB_PROVIDER_AMBIGUOUS→searchProvider未生效,两个 provider 同时可用。WEB_PROVIDER_ERROR且 message 里列出bing: …; so360: …→ 确实走了本插件,只是当时所有后端都失败(限流或网络),换个问题重试。
Web 界面下的 web_fetch
web_search 装完即可用。web_fetch 在 Web 界面还需一步:Web surface 停用了宿主层的 tool-web,改由每会话的 agent preset 挂载,而 profile 补丁改不到 preset 里的行。做法:
- 把安装目录里的
config/agent-presets/standard/agent.cordis.yml复制到$DSH_HOME/.agent-presets/standard-free-web/agent.cordis.yml; - 把其中
- id: tool-web的fetch: false改成fetch: true; - 在设置里把该会话(或默认)preset 选成
standard-free-web。
用新名字是必需的:同名不会覆盖内置 preset。TUI / headless 走宿主层的 tool-web,本包补丁已直接写了 fetch: true,无需自建 preset。
后端
| id | 端点 | 默认启用 | 备注 |
|---|---|---|---|
bing |
www.bing.com/search?format=rss |
是 | RSS 是发布格式,最不易随页面改版失效;首选 |
so360 |
www.so.com/s |
是 | 真实 URL 取自 a[data-mdurl],无需逐条解跳转 |
ddg |
html.duckduckgo.com/html/ |
否 | 需可访问 DuckDuckGo;开发网络下 TLS 不通,未经实网验证 |
searxng |
<searxngBaseURL>/search?format=json |
否 | 需自托管实例且开启 json 格式 |
顺序即降级顺序:某后端抛错、超时或返回 0 条,就试下一个;全部失败才抛 WEB_PROVIDER_ERROR(message 汇总每个后端的原因)。调用方取消不降级,直接抛 WEB_ABORTED。
配置
挂在 web-search-free 行的 config 下:
| 字段 | 默认 | 说明 |
|---|---|---|
backends |
['bing','so360'] |
后端顺序;空数组等同默认 |
region |
wt-wt |
DuckDuckGo 式地区码,映射 Bing 的 mkt/setLang(cn-zh → zh-CN) |
timeoutMs |
8000 |
每个后端的超时 |
resultsPerBackend |
10 |
调用未给 maxResults 时向后端请求的条数 |
searxngBaseURL |
无 | 启用 searxng 时必填,非法则加载期报错 |
userAgent |
桌面 Chrome UA | 两个 provider 共用 |
fetchProvider |
true |
是否注册 free-http |
fetchTimeoutMs |
30000 |
抓取整体超时 |
maxResponseBytes |
5242880 |
保留的响应字节上限,超出截断并置 truncated |
maxRedirects |
5 |
跳转跳数上限 |
maxUrlLength |
2048 |
URL 长度上限 |
allowPrivateHosts |
false |
是否允许抓取回环 / 内网 / 链路本地地址 |
示例(中文结果优先,并加上自托管 SearXNG 兜底):
- id: web-search-free
config:
backends: [bing, so360, searxng]
region: cn-zh
searxngBaseURL: https://searx.example.internal
错误码
沿用 seam 的词汇,工具结果里会带上:
- 搜索:
WEB_PROVIDER_ERROR(全部后端失败)、WEB_ABORTED(调用方取消)。 - 抓取:
WEB_INVALID_URL、WEB_BLOCKED_URL(内嵌凭据或内网地址)、WEB_REDIRECT_BLOCKED、WEB_FETCH_TOO_LARGE、WEB_UNSUPPORTED_CONTENT_TYPE(二进制或无法识别的 charset)、WEB_FETCH_TIMEOUT、WEB_ABORTED、WEB_PROVIDER_ERROR。 - 非 2xx 响应不是错误:状态码原样返回给模型,这是 seam 的契约。
已知限制
- 稳定性弱于官方 API。依赖公开 HTML/RSS 端点,页面改版、限流、验证码、地区封锁都会让某个后端失效;多后端降级只降低概率,不能消除。Mojeek 已在开发期实测返回验证码页,因此没有纳入。
- 合规风险由使用者承担。Bing RSS 的
copyright字段明确限制「仅限个人非商业用途在 RSS 聚合器中呈现」,抓取结果页也可能违反对应引擎的服务条款。仅建议个人低频使用,不要做商业规模或高频抓取。 - 不返回生成式答案。只填
sources[],不填content:免费端点没有可信摘要来源,编造会污染模型输入。 publishedAt覆盖不全。Bing 会按 market 把pubDate本地化成「周日, 23 8月 2026 …」,Date无法解析,此时按契约省略该字段而不是猜。free-http的内网拦截只覆盖字面地址。它拦localhost、127/8、10/8、172.16/12、192.168/16、169.254/16、100.64/10、IPv6 回环与 ULA/链路本地,但拦不住解析到内网的公网域名——那属于 DNS 层的事。把allowPrivateHosts打开等于允许模型访问内网,请谨慎。- 无代理配置。Node 的
fetch不读HTTPS_PROXY,需要代理时得在系统层解决。
开发
npm install
npm run typecheck
npm test # 离线单测,不联网
npm run build # tsc → lib/
单测用 fixture(tests/fixtures/)固定当前页面结构:某个后端解析失效时,测试会先变红。
No comments yet. Be the first to write one.