
给 openclaw 配一个私有搜索引擎其实是我最近在 Windows 上用 Docker Desktop 折腾出来的刚需。openclaw 自带 web_search 默认走的是第三方搜索服务一方面有配额和 key 的麻烦一方面每次搜什么都会被那边记一笔遇到规模化使用或者接本地 agent 的场景总感觉不踏实。我把 SearXNG 用 Docker 部署成自己的私有搜索引擎之后这些问题都消停了它不需要任何搜索 API 的 key一个 JSON 接口就能喂给 openclaw 的 web_search 用整个链路完全自持。这篇文章会先讲为什么要换然后把 Docker 部署、settings.yml 打开 JSON 接口、浏览器和 curl 双重验证、接入 openclaw 的两种方式一条条写出来最后再分享我真正踩过的坑。适合谁看已经在跑 openclaw、想给它配一个稳定搜索后端的同学以及凡是需要给自家 AI agent 配搜索工具的不限定 openclaw 也成立。1. 为什么给 openclaw 换一个自建搜索后端1.1 默认 web_search 用着别扭的四个点如果你只是偶尔让 agent 查一次天气第三方搜索接口完全够用。但 openclaw 这类 agent 的特点是会在一次对话里反复调用工具搜索需求一旦变多问题就藏不住了配额。第三方搜索 API 的免费额度都是按千次/天算的agent 多问几句查一下最新文档搜一下相关案例额度几分钟就烧光。Key 管理。很多搜索服务需要申请密钥还要在环境变量里维护换个机器、换个环境就忘了配置排查起来特别浪费时间。隐私。每次查询都会把关键词发到别人服务器不仅问题文本连带 IP、UA、访问时间都会留日志。给本地 agent 用这种查询记录本来应该留在自己手里。不可控。第三方接口哪天改格式、加验证码、开始限流openclaw 的搜索结果就跟着断你甚至不知道断在哪一环。这些点单独出现都能忍凑齐了就很伤。我在本地调试 openclaw 的 skill 时一个下午就撞上了配额耗尽和 429 限流之后才下决心把搜索环节彻底换成自建实例。1.2 SearXNG 的元搜索方案恰好打在痛点上SearXNG 是一个开源的元搜索引擎它自己不去爬网页而是把 Bing、DuckDuckGo、Brave、Wikipedia 等多个搜索引擎的结果聚合回来去重、排序之后统一给前端或 API。你部署的是聚合层不是爬虫集群所以不需要维护索引也基本不涉及存储增长。和典型第三方搜索 API 对比差异非常明显对比项第三方搜索 API自建 SearXNG是否需要 API key通常需要申请不需要开箱即用查询日志去向第三方服务器自己的 Docker 容器配额限制有按量计费无硬配额受限于带宽结果可控性接口给什么就是什么可开关引擎、调语言和类别部署成本无一条 docker run核心感受就一句话用 SearXNG 之后openclaw 的 web_search 依然叫 web_search但背后查询走哪个引擎、结果怎么解析、限流怎么设都由你说了算。而且 Docker 官方镜像已经内置了配置生成逻辑不需要会 nginx也不需要写后端这对大多数 openclaw 用户来说是最好的入场方式。2. Docker 把 SearXNG 跑起来命令与配置全解析2.1 一条 docker run 命令起步先看最简命令docker run -d --name searxng \ -p 8080:8080 \ -e SEARXNG_BASE_URLhttp://127.0.0.1:8080/ \ -e SEARXNG_SECRET$(openssl rand -hex 32) \ -v searxng-data:/etc/searxng \ searxng/searxng:latest逐行解释-p 8080:8080镜像内部的 Web 服务固定监听 8080映射到宿主机 8080。如果本地 8080 已被占用可以改成-p 8081:8080但后面所有访问和 openclaw 配置都要同步改成 8081。SEARXNG_BASE_URL公开访问地址影响页面里生成的绝对链接。给 openclaw 的 API 调用不依赖它但网页端部分资源链接会用它拼接建议一开始就写对。SEARXNG_SECRET新版镜像是强制项不设会在启动日志里明确报secret_key not set。用openssl rand -hex 32生成一个长随机串即可不要拿 123456 顶数。-v searxng-data:/etc/searxng首次启动会在命名卷里生成默认 settings.yml。用命名卷的好处是删容器不丢配置。启动后检查docker ps docker logs searxng --since 1m看到进程正常、日志里没有异常堆栈浏览器访问http://127.0.0.1:8080能看到搜索页就是成功了。2.2 修改 settings.yml打开 JSON 输出并把限流调低这是接入 openclaw 最关键的一步。用命名卷时配置文件藏在卷里直接改不方便。我推荐第一次启动后先把配置拷出来改成 bind mount 再重启docker cp searxng:/etc/searxng/settings.yml ./settings.yml docker stop searxng docker rm searxng docker run -d --name searxng \ -p 8080:8080 \ -e SEARXNG_BASE_URLhttp://127.0.0.1:8080/ \ -e SEARXNG_SECRET$(openssl rand -hex 32) \ -v $(pwd)/settings.yml:/etc/searxng/settings.yml \ searxng/searxng:latestsettings.yml 里重点改两个地方第一打开 JSON 接口。在search:段把formats加上 jsonsearch: formats: - html - json不加这一项浏览器网页能打开但访问/search?qxxxformatjson会拿不到预期结果openclaw 自然也就没东西可吃。第二关闭限流本地/私有网络场景。server: limiter: falseSearXNG 默认会把短时间内的高频请求判定成机器人并临时拉黑。如果只是给本机 openclaw 用关掉最省事。要把服务暴露到公网的话请不要照抄这个设置第 5 章会说清楚。提示settings.yml 是 YAML缩进错了容器会拒载配置改完务必跑一次docker logs searxng --since 1m看到 Invalid YAML 就先修缩进再继续。2.3 用 docker compose 固化配置升级重启不手忙脚乱命令式 docker run 适合临时验证长期用我建议上 compose。我当前的 compose 文件长这样services: searxng: image: searxng/searxng:latest container_name: searxng ports: - 127.0.0.1:8080:8080 environment: - SEARXNG_BASE_URLhttp://127.0.0.1:8080/ - SEARXNG_SECRET${SEARXNG_SECRET} volumes: - ./settings.yml:/etc/searxng/settings.yml depends_on: - redis restart: unless-stopped redis: image: redis:7-alpine container_name: searxng-redis volumes: - redis-data:/data restart: unless-stopped volumes: redis-data:解释几个设计选择端口绑定写成127.0.0.1:8080:8080明确只允许本机访问。openclaw 如果跑在同一台机器这样绑最安全也比监听所有网卡少很多麻烦。引入 Redis 是为了以后开limiter: true时有地方存频率统计如果你确定一直不开限流redis 这段可以直接删掉SearXNG 单独跑也没问题。环境变量用${SEARXNG_SECRET}而不是硬编码值。启动前先export SEARXNG_SECRET$(openssl rand -hex 32)避免密钥写在 compose 文件里泄露。启动命令export SEARXNG_SECRET$(openssl rand -hex 32) docker compose up -d以后改配置只需要编辑宿主机上的./settings.yml然后docker compose restart searxng即可。日常更新镜像则用docker compose pull加docker compose up -d配置不会丢。3. 部署后第一件事浏览器和 API 双重验证3.1 浏览器页面先确认实例是活的打开http://127.0.0.1:8080能看到一个朴素的搜索主页。输入 docker 回车如果出结果说明至少有一个搜索引擎后端是通的。如果提示没有找到结果先别急着判定部署失败可能是默认引擎里有一部分没响应SearXNG 还没有走到自动降权那一步。换个更热门的词比如 openclaw一般就能看到内容。浏览器页面的主要作用是验证三件事容器本身没挂静态页面能正常渲染SEARXNG_BASE_URL 设置没有把页面资源搞坏默认界面语言对不对中文别扭的话在 settings.yml 里调ui.default_locale。3.2 curl 验证 JSON 接口openclaw 真正关心的东西SearXNG 的 JSON API 并不是额外开的服务只要 settings.yml 里search.formats包含 json直接对/search加formatjson就能拿结构化结果curl -s http://127.0.0.1:8080/search?qopenclawformatjson | jq .系统里没装 jq 的话用head -c 2000也能看到开头结构。正常返回长这样{ query: openclaw, number_of_results: 0, results: [ { url: https://example.com/openclaw, title: OpenClaw, content: 这是一个 AI 助手项目..., engine: bing, score: 0.95, category: general } ], infoboxes: [], suggestions: [], unresponsive_engines: [] }openclaw 接入时真正消费的是results数组里的title、url、content三个字段。engine可以帮你追查每条结果来自哪个引擎unresponsive_engines列出超时的后端——偶尔出现一两个不代表实例有问题全部引擎都超时才需要担心网络和配置。常用参数也顺手记一下languagezh-CN、languagezh-TW、languageen指定搜索语言。time_rangemonth、time_rangeyear限定时间范围搜新闻和技术动态很实用。categoriesgeneral,news,images指定结果类别。openclaw 通用搜索场景保留general就够。pageno2翻页。把命令变成 openclaw 后面要用的形式确认能稳定取到 3 条以上结果curl -s http://127.0.0.1:8080/search?qopenclawformatjsonlanguagezh-CN \ | jq .results[:3] | .[] | {title, url, content}3.3 从 docker logs 判断引擎健康度浏览器和 curl 都正常之后再花 30 秒看容器日志docker logs searxng --since 5m日志里出现某个引擎 timeout、connection failed 之类很正常SearXNG 的容错机制是单个引擎失败不影响整体返回最多结果少几条。真正需要警惕的是容器反复重启多半是 settings.yml 权限或 YAML 缩进问题。每次请求都 500优先查 SEARXNG_BASE_URL 和 secret_key 是否设置正确。大量 429 响应你已经触发限流确认 limiter 是不是误开了。验证到这里SearXNG 本身是健康的下一步就是把它交给 openclaw。4. 把 SearXNG 接进 openclaw两种接入方式4.1 直接配置 openclaw 内置 web_search 指向 SearXNG如果你的 openclaw 版本里内置 web_search 工具支持自定义搜索服务地址那这就是最省事的路径。不同版本的环境变量名可能有差异常见的是SEARXNG_URL、SEARXNG_BASE_URL、WEB_SEARCH_URL去 openclaw 的配置界面或文档里搜 searxng 关键词一般都能找到对应位置。把值填成http://127.0.0.1:8080/核心要求是 openclaw 最终能发出这样的请求/search?q关键词formatjson。所以配置时有两个细节地址末尾的/写不写通常不影响但别顺手填成/search工具内部很可能自己拼 path写全了会变成/search/search。openclaw 和 SearXNG 必须网络可达。openclaw 跑在宿主机就用127.0.0.1openclaw 也在容器里就改成 docker compose 里的服务名searxng:8080并确认两个容器在同一网络。4.2 写一个轻量 skillJSON 转成自然语言文本喂给 agent如果 openclaw 版本没有内置配置项或者你想完全自定义返回格式可以写一个 skill。思路很简单skill 里放一个脚本接收 query 参数调 SearXNG JSON 接口把结果整理成给大模型看的文本。以 openclaw 的 skill 目录机制为例先建一个描述文件skills/web_search/SKILL.md--- name: web_search description: 使用自建 SearXNG 搜索网页。适用于任何需要查询实时信息的场景。 参数: query - 搜索关键词 ---同目录放一个 Node 脚本searxng_search.jsconst SEARXNG_URL process.env.SEARXNG_URL || http://127.0.0.1:8080; async function search(query, count 5) { const url ${SEARXNG_URL}/search?q${encodeURIComponent(query)}formatjsonlanguagezh-CN; const res await fetch(url, { headers: { Accept: application/json } }); if (!res.ok) throw new Error(SearXNG HTTP ${res.status}); const data await res.json(); if (!data.results || !data.results.length) return 未搜索到结果; return data.results.slice(0, count).map((r, i) ${i 1}. ${r.title}\n ${r.url}\n ${(r.content || ).slice(0, 200)} ).join(\n\n); } search(process.argv[2]).then(out console.log(out)).catch(err { console.error(err.message); process.exit(1); });先在终端把脚本跑通node searxng_search.js openclaw 是什么输出几条带标题、带链接、带摘要的结果说明脚本没问题。把它放进 openclaw 的 skills 目录后重启 openclawagent 在对话里遇到需要实时信息的请求就会调用这个 skill不再依赖外部搜索服务。提示脚本里故意用process.env.SEARXNG_URL做可覆盖配置。这样以后 SearXNG 换机器只需要改 openclaw 的环境变量skill 代码一行不动。Node 18 以上自带 fetch不需要装任何依赖。如果你更习惯 Python等价思路就是用 requests 发同一个 URL解析 json 后拼文本。核心只有一条把 SearXNG 返回的 JSON 规整成带链接的纯文本给 agent 看。4.3 接好后的实际效果预期接完之后openclaw 的对话体验变化不会太大。你问一句帮我查一下最近和 Docker 相关的技术文章agent 会触发 web_search然后返回 3-5 条带链接的摘要。区别在于这次查询不会经过任何第三方搜索 API不受别人的配额和限流影响结果里的链接也都是可以点开的原始来源。如果第一次调用后 agent 说找不到可用的搜索工具优先查两件事SKILL.md 里的 name 是不是和其他 skill 重名了脚本有没有执行权限node 是否在 openclaw 进程的 PATH 里。同一套思路也适用于 Windows Companionskill 目录路径对了、node 可用脚本完全跨平台。5. 生产环境必须处理的细节与踩坑记录5.1 配置文件权限与镜像更新的配合这是我最开始被折腾的一环。把 settings.yml 用 bind mount 挂进容器后如果宿主机文件权限过严或者 uid/gid 和容器内用户不匹配容器会启动失败或反复重启。遇到这种情况先看日志docker logs searxng --since 2m权限相关报错时最简单的处理就是保证宿主机文件可读给成 644 或者 664 都可以。不要为了省事把整个目录chmod -R 777保持最基本的干净习惯能少碰很多坑。镜像更新反而简单docker compose pull docker compose up -d因为配置已经 bind mount 在宿主机升级不会覆盖你的 settings.yml。唯一要注意的是大版本升级后旧配置里写的某些引擎名如果被上游改名或移除启动日志会出现 unknown engine 警告这些引擎会被忽略不影响整体运行有空顺手清理即可。5.2 公网暴露前必须补的两块短板限流与访问控制SearXNG 本身不提供 API key 机制谁拿到地址谁就能用。如果只是 openclaw 在同一台机器调用用127.0.0.1:8080绑定就够了。一旦想暴露给局域网其他设备或者放到远端服务器就必须处理两个问题。第一是限流。把 settings.yml 里server.limiter设回true让 compose 里的 Redis 生效。SearXNG 会基于 IP 维度统计请求频率超了会返回 429 并暂时屏蔽防止实例被别人当免费搜索接口刷。注意这个限流同样会影响 openclaw 自己接入时尽量让请求之间留出间隔或者在 skill 里做重试退避。第二是访问控制。最朴素的方案是在入口处加一道带 Basic Auth 的网关Nginx、Caddy 都能几行配置搞定Caddy 更省事openclaw 的 skill 脚本在请求头里带上账号密码其他匿名请求直接挡掉。如果不方便加网关退一步用防火墙只放行指定来源 IP 也可以这属于网络基本功但很多 Docker 教程不写导致大家习惯性把端口开到公网。5.3 结果质量调试只留对你有用的引擎SearXNG 默认启用的引擎很多好处是覆盖面大坏处是慢引擎拖慢返回速度、低质量引擎占结果位。在 openclaw 场景下我更建议主动剪枝。打开 settings.yml 的engines:段把不常用的引擎设成disabled: true。以中文加英文的通用搜索为例保留这几个就够engines: - name: bing disabled: false - name: duckduckgo disabled: false - name: brave disabled: false - name: wikipedia disabled: false - name: github disabled: false剪完之后JSON 接口的返回速度会明显变快因为 SearXNG 不用再等那些容易超时的引擎。还有一个容易被忽略的点SearXNG 有自动降权机制响应慢的引擎会被逐渐排到后面。刚部署的实例前几次搜索的结果排序波动很大不用急着调引擎顺序跑一两天之后再回来看才是真实状态。5.4 几个真实场景下的实际体会Windows 加 Docker Desktop 的组合下只要容器端口映射正常openclaw 在 Windows 宿主上直接访问http://127.0.0.1:8080/就行不需要去翻 WSL2 的 IP 地址。openclaw 开 debug 模式的话能看到工具调用的完整日志。把 SearXNG 请求 URL 复制出来自己 curl 一遍是排查接口有结果但 agent 说没结果的最高效手段九成问题都出在返回格式和字段名上。如果担忧/search接口暴露面太大又不想动网络层也可以把 SearXNG 的端口绑定改成只在回环地址监听也就是 compose 里写127.0.0.1:8080:8080不要去掉。openclaw 在本地用着一点都不受影响外部扫描等于少了一个靶子。最后说一个我自己的配置习惯openclaw 的 skill 环境变量里SEARXNG_URL永远配http://127.0.0.1:8080/即使以后 SearXNG 迁移到别的机器我也只在环境变量里改一下skill 脚本一行不动。这套组合我已经跑了几周从第三方搜索接口切到私有实例之后最直观的感受就是不用再盯着配额过日子了。如果你也卡在给 agent 配搜索这一步照着上面的流程部署一个 SearXNG 试试应该能体会到这种自己的搜索后端的踏实感。