OpenCLI DuckDuckGo 适配器实战:浏览器搜索 + 补全建议双命令深度解析
【免费下载链接】OpenCLIMake Any Website into CLI & Use your logged-in browser by AI agent.项目地址: https://gitcode.com/gh_mirrors/ope/OpenCLI
本文以 OpenCLI 仓库中的 DuckDuckGo 适配器文档(docs/adapters/browser/duckduckgo.md)为主体,结合其源码实现(clis/duckduckgo/search.js、clis/duckduckgo/suggest.js)与测试用例,完整讲解opencli duckduckgo search与opencli duckduckgo suggest两条命令的参数语义、底层原理与使用边界。读完本文,你将能够在终端中完成 DuckDuckGo 的区域化检索、时间过滤、跨页分页、结果提取与搜索词联想,并理解浏览器模式与纯 HTTP API 两种适配器实现路线的差异。
适配器总览
DuckDuckGo 适配器属于 OpenCLI 的公开(Public)站点适配器,工作模式为 🌐 公开访问,覆盖两个域名:
| 域名 | 用途 |
|---|---|
html.duckduckgo.com | search命令使用的 HTML 精简版搜索结果页 |
duckduckgo.com | suggest命令使用的补全 API 所在域名 |
适配器对外暴露两条命令:
| Command | Description |
|---|---|
opencli duckduckgo search <keyword> | Search DuckDuckGo and extract results from the page |
opencli duckduckgo suggest <keyword> | Get DuckDuckGo search suggestions |
从源码注册信息可以看到两条命令的元数据:search为只读(access: 'read')、公开策略(Strategy.PUBLIC)、需要浏览器(browser: true);suggest同样为只读公开策略,但browser: false,即完全不需要浏览器环境(见 clis/duckduckgo/search.js 与 clis/duckduckgo/suggest.js)。
search命令:浏览器模式下的结果提取
参数语义
search命令共支持 5 个参数,其中keyword为必填位置参数,其余均有默认值:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
keyword | string(位置参数) | 必填 | 搜索关键词 |
--limit | int | 10 | 单页结果数,范围1~10(HTML 版每页最多 10 条) |
--offset | int | 0 | 分页偏移量,必须是10 的倍数(0、10、20…),内部走 XHR POST |
--region | string | 全部区域 | 区域代码,如jp-jp、us-en、cn-zh |
--time | string | 无 | 时间范围:d(天)、w(周)、m(月)、y(年) |
这些参数并非"文档建议",而是在源码中被严格校验后才会发起网络请求:
--limit通过共享工具requireBoundedInteger校验,越界直接抛出ArgumentError(见 clis/duckduckgo/search.js);--offset通过requireNonNegativeInteger校验后,还会单独检查offset % 10 !== 0并抛出--offset must be a multiple of 10 for DuckDuckGo HTML pagination错误(clis/duckduckgo/search.js);--time必须匹配^(d|w|m|y)$正则,否则报错(clis/duckduckgo/search.js)。
输出列结构
search的结果以表格/JSON 形式输出,固定 7 个列(源码columns定义于 clis/duckduckgo/search.js):
| 列名 | 含义 |
|---|---|
rank | 排名,从 1 开始;分页时等于index + 1 + offset |
title | 结果标题 |
url | 解码后的真实目标 URL(已处理uddg=重定向) |
snippet | 摘要文本,取自.result__snippet节点 |
displayUrl | 页面展示的 URL 文本 |
icon | 站点图标地址,取自.result__icon__img |
resultType | 结果类型:web/news/video/image |
输出格式与 OpenCLI 全局约定一致,search同样支持-f json、-f yaml、-f csv、-f md等格式化输出,便于管道传递给jq或 LLM 消费。
使用示例
# Basic search opencli duckduckgo search "machine learning" # Limit results opencli duckduckgo search "machine learning" --limit 5 # Region-specific search opencli duckduckgo search "machine learning" --region jp-jp # Time filter (past week) opencli duckduckgo search "machine learning" --time w # Pagination (second page) opencli duckduckgo search "machine learning" --offset 10 # JSON output opencli duckduckgo search "machine learning" -f json # Search suggestions opencli duckduckgo suggest "machine" --limit 5请求构造细节
search的请求 URL 在源码中按如下规则拼装(clis/duckduckgo/search.js):
https://html.duckduckgo.com/html/?q=<编码后的关键词>[&kl=<区域代码>][&df=<时间范围>]其中kl是 DuckDuckGo 的区域(region)参数,df是时间范围参数。区域代码遵循 DuckDuckGo 自己的格式(如jp-jp、us-en、uk-en),默认不传即表示全部区域。区域与时间过滤均以用户传入值为准直接透传,未做白名单枚举,因此传入有效区域代码即可生效。
suggest命令:免浏览器的公开 JSON API
与search不同,suggest走的是 DuckDuckGo 的公开补全接口duckduckgo.com/ac/,全程使用 Node 内置fetch,不依赖 Chrome 浏览器。
参数与实现
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
keyword | string(位置参数) | 必填 | 搜索词前缀 |
--limit | int | 8 | 返回建议数量上限,范围1~20 |
实现要点(见 clis/duckduckgo/suggest.js):
- 校验
keyword非空、limit在 1~20 之间; - 请求
https://duckduckgo.com/ac/?q=<关键词>&type=list; - 解析 JSON,取数组第二个元素(
data[1])作为建议短语列表; - 过滤空字符串,按
limit截断,逐条映射为{ phrase }行输出,输出列为['phrase']。
fetch失败(网络错误、非 2xx 状态码、畸形 JSON)时,分别被包装为带COMMAND_EXEC错误码的CommandExecutionError,保证错误可被程序化捕获而非静默吞掉(clis/duckduckgo/suggest.js)。
使用示例
# 基本联想 opencli duckduckgo suggest "machine" # 指定建议数量 opencli duckduckgo suggest "opencli" --limit 5 # JSON 输出便于程序消费 opencli duckduckgo suggest "machine" -f json底层原理:从源码看两条命令的工程实现
1.uddg=重定向解码
DuckDuckGo 的搜索结果链接统一经过uddg=参数跳转(形如/l/?uddg=https%3A%2F%2Fexample.com%2F...),适配器在decodeDdgUrl函数中解析该参数并还原出干净的最终 URL;解码失败时退回原href,并统一通过toHttpsUrl保证只输出http/https协议地址(见 clis/duckduckgo/search.js 与共享工具 clis/_shared/search-adapter.js)。测试用例 clis/duckduckgo/search.test.js 专门验证了/l/?uddg=...到https://github.com/jackwener/OpenCLI的解码正确性。
2. 浏览器内 DOM 提取脚本
search之所以必须走浏览器模式,是因为 DuckDuckGo 存在反爬保护,普通 HTTP 请求难以稳定获取结果。适配器通过page.evaluate在页面上下文内执行一段注入脚本(buildExtractFn),其提取逻辑为(clis/duckduckgo/search.js):
- 遍历
.result节点; - 通过 CSS 类名识别并跳过广告结果(
result--ad、result--ads、badge--ad); - 分别抓取标题
.result__a、摘要.result__snippet、展示 URL.result__url、图标.result__icon__img; - 通过类名判定结果类型:
news-result→news、video-result→video、image-result→image,否则为web; - 以 href 为 key 去重,按
limit提前截断。
测试用例使用JSDOM模拟了广告结果与自然结果的混合 DOM,验证广告被过滤、自然结果被正确提取并映射为规范行结构(clis/duckduckgo/search.test.js)。
3. 分页为何用 XHR POST
DuckDuckGo HTML 版的分页跳转依赖form.submit()行为,在浏览器自动化场景下容易引发页面导航问题。适配器因此改用页面内 XHR POST实现翻页:在首屏加载后,通过XMLHttpRequest向/html/提交表单编码参数(q、s=<offset>、v=l、o=json、可选kl),再用DOMParser解析返回的 HTML 并复用同一套提取函数(见buildPaginateJs,clis/duckduckgo/search.js)。
这种方式避免了二次页面导航,同时让--offset与结果rank精确对齐(第 2 页首条即rank: 11,测试见 clis/duckduckgo/search.test.js)。代价是分页边界处结果可能与上一页存在少量重叠,且--offset必须是 10 的倍数。
4. 共享校验与错误封装
两条命令复用clis/_shared/search-adapter.js中的一组工具函数,这体现了 OpenCLI 搜索类适配器的通用工程约束(clis/_shared/search-adapter.js):
requireSearchQuery:关键词去空格后非空校验;requireBoundedInteger/requireNonNegativeInteger:整数区间校验;requireRows/unwrapBrowserResult:兼容浏览器会话信封({session, data})与纯数组两种载荷形态,载荷形状异常时抛出带COMMAND_EXEC码的类型化错误;runBrowserStep:将浏览器步骤(导航、提取)的底层异常统一包装为类型化CommandExecutionError;emptySearchResults:结果为空时抛出EmptyResultError。
search测试中有一例专门验证:当提取载荷形状不符合预期(如{rows: []})时,命令以COMMAND_EXEC错误失败,而不是静默返回空数组(clis/duckduckgo/search.test.js)。suggest测试则验证了空关键词/超限 limit 在fetch之前就被拦截(clis/duckduckgo/suggest.test.js)。
前置条件与运行环境
suggest不需要 Chrome,任何能运行 OpenCLI 的环境均可直接使用;search需要 Chrome 处于运行状态(Standalone 模式会自动启动),或安装并启用 Browser Bridge 扩展(详见 docs/guide/browser-bridge.md)。
Browser Bridge 是 OpenCLI 连接浏览器会话的轻量方案:安装扩展后,守护进程(daemon)会在首次执行浏览器命令时自动启动,无需手工配置。可用opencli doctor检查扩展与守护进程的连通性。需要强调的是,浏览器命令复用的是你 Chrome 中已登录的会话状态,凭证不会离开浏览器;DuckDuckGo 为公开站点,无登录要求,但浏览器模式本身仍是其反爬防护下最稳定的取数路径。
已知限制与注意事项
结合文档与源码,使用本适配器时需注意以下边界:
search必须使用浏览器模式:由于 DuckDuckGo 的反爬保护,纯 HTTP 方式不可行;- 单页最多 10 条结果:HTML 版每页固定返回最多 10 条,
--limit只能在 1~10 之间取值,需要更多结果请用--offset翻页; - 分页结果可能重叠:分页走 POST 导航,页面边界处可能与前页结果重复;
- 摘要提取依赖 DOM 结构:摘要取自 HTML 版的
.result__snippet节点,若 DuckDuckGo 调整前端结构,提取逻辑(clis/duckduckgo/search.js)需同步跟进; - CJK 关键词联想为拼音近似:
ac/补全 API 对中文等 CJK 查询返回的是拼音音近建议(phonetic suggestions),可能与预期结果不完全一致; - 区域代码格式:遵循 DuckDuckGo 自身格式(如
jp-jp、us-en、uk-en),默认全部区域。
测试与质量保障
本适配器带有完整的 Vitest 测试套件,是理解其行为契约的最佳入口:
- clis/duckduckgo/search.test.js:覆盖命令注册元数据、参数校验前置拦截、
uddg=解码、广告过滤与 DOM 提取、分页信封解包、畸形载荷的类型化报错; - clis/duckduckgo/suggest.test.js:覆盖命令注册、参数前置校验、公开 API 载荷解析与过滤、网络/JSON 异常映射为类型化错误。
这些测试同时印证了本文所述的全部参数边界与错误语义——ARGUMENT类错误在发请求前抛出,COMMAND_EXEC类错误统一封装底层异常,空结果抛出EMPTY_RESULT。在 OpenCLI 中编写或审计新适配器时,DuckDuckGo 适配器是一个兼具"浏览器模式 + 纯 API 模式"双路实现的参考范例。
【免费下载链接】OpenCLIMake Any Website into CLI & Use your logged-in browser by AI agent.项目地址: https://gitcode.com/gh_mirrors/ope/OpenCLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考