前阵子在给自己维护的智能体项目(OpenClaw)配联网搜索能力时,认真研究过一条完全不走商业付费的路线:用开源的 SearXNG 做元搜索入口,再用 Jina Reader 把网页正文转成干净文本,最后喂给智能体去总结。这个组合很适合那种“偶尔查资料、不想为每次搜索都掏 API 费用”的个人 Agent 场景,也适合小团队先把搜索链路跑起来再说。
先说结论:这套方案跑起来之后,OpenClaw 既能自己搜到最新的网页,又能把网页正文读成 LLM 好处理的 Markdown,全程不产生按次计费的搜索成本。整个部署只需要一台 1 核 2G 的云服务器,装好 Docker,剩下的就是配置和联调。我在这条链路上踩过不少坑,包括 SearXNG 的 JSON 接口返回 403、Jina Reader 超时、容器里连不上宿主机等等,下面会把每一步怎么做、为什么这样做、出问题了怎么排,一次讲透。
1. 为什么是 SearXNG + Jina Reader:免费方案不等于将就
1.1 搜索入口选型:自建元搜索是唯一的高性价比路径
给智能体接入联网能力,摆在面前的无非三条路:直接调商业搜索 API、自建爬虫、自建元搜索。
商业搜索 API 确实省事,但它的成本模型和 Agent 场景天然冲突。Agent 的搜索特点是请求量不稳定,可能一天几次,也可能一个任务连续触发十几条查询。商业 API 大多按千次请求计费,还有 QPS 限制,一旦跑长任务,费用和限流两个问题会同时冒出来。更难受的是,Agent 经常需要反复尝试不同的查询词,每试一次都是钱。
自建爬虫则是另一个极端。搜索引擎返回的只是链接列表,你要拿正文就自己去抓目标网站。现在的网站反爬手段一个比一个狠,登录墙、JS 渲染、验证码都是家常便饭,一个通用爬虫维护成本能拖垮整个项目。
SearXNG 属于第三条路:它是一个自托管元搜索引擎,自身不建索引,而是把 Google、Bing、DuckDuckGo、百度等上游搜索引擎的结果聚合起来。它把搜索请求的复杂性和反爬压力集中在自己这一层,对下游应用暴露一个干净接口。更重要的是,它没有按量计费的概念,跑在同一个人服务器上就是一次性的部署成本。
我用 Docker 方式部署,是因为搜索服务对运行环境比较敏感,涉及的依赖不少,Docker 可以一次性解决环境问题。另外云服务器上通常还要跑 OpenClaw 主程序和其他辅助服务,容器隔离能避免端口冲突和依赖打架。
1.2 内容读取选型:为什么需要 Jina Reader 这一层
搜索只负责“找到”,不负责“读懂”。SearXNG 返回的是 URL 列表、标题、摘要这些元信息,但 Agent 要回答“这篇文章到底说了什么”,必须拿到完整正文。
这时候如果自己写正文抽取工具,等于把之前想避开的爬虫难题又捡回来了。不同的网站有不同的 DOM 结构,正文容器从<article>到<div id="content">五花八门,还夹杂着导航、广告、推荐位这些噪音。
Jina Reader 做的就是这件事:给它一个 URL,它返回经过清洗的、接近 Readability 效果的 Markdown 文本。它把 URL 转成 LLM-friendly 内容,省掉了下载网页、抽取正文、清洗 HTML 的一整套脏活。这个过程对 Agent 来说非常关键——LLM 处理干净文本的效果,和处理一堆<script>标签加导航链接的效果,完全不是一个量级。
Jina Reader 官方提供托管 API,注册后有一定免费额度,用起来直接发请求就行。如果搜索频率极高或者不想依赖外部服务,也可以考虑自部署 Reader 的替代实现,只是多费点维护精力。这个小差别后面细说。
1.3 整条链路的实际工作过程
把两个服务串起来以后,OpenClaw 的联网查询大致走这样的流程:
- OpenClaw 生成搜索词,例如“最近云计算领域有哪些新技术发布”。
- OpenClaw 把这个词以 JSON 格式请求发给 SearXNG。
- SearXNG 聚合多个上游搜索引擎,返回一堆带标题、URL、摘要的结果。
- OpenClaw 根据任务需要,挑选其中最相关的几个 URL。
- OpenClaw 把选中的 URL 发给 Jina Reader,拿到网页正文的 Markdown。
- 最后将正文内容连同问题一起交给 LLM,由它完成归纳和回答。
这个链路的好处是每一层都解耦了:SearXNG 管“找”,Jina Reader 管“读”,OpenClaw 管“想”。任何一层都能单独替换,出问题时排查边界清晰。
2. 部署前的准备工作:一台小服务器就够
2.1 服务器配置与 Docker 环境
这套方案并不吃硬件,SearXNG 本身是 Python 项目,内存占用大头在运行中的上游引擎连接和缓存上,1 核 2G 内存完全跑得动。我实际测试时,SearXNG 容器空闲状态占用大约 300-400MB,峰值也不会超过 800MB。剩下的内存给 OpenClaw 进程完全够用。
如果你用的是国内主流公有云厂商的按量付费实例,建议直接开一台 1 核 2G 的 Linux 机器,系统选 Ubuntu 22.04 或 Debian 12 都行。数据盘不用单独挂,默认系统盘就足够,因为 SearXNG 和 OpenClaw 都不是重存储应用。
装 Docker 那一步没什么特殊门道,直接用官方安装脚本:
curl -fsSL https://get.docker.com | sh systemctl enable --now docker docker compose version看到Docker Compose version输出就说明环境就绪。注意千万别图省事跳过 Compose 的安装,后面所有服务的编排都靠它。
2.2 安全组与端口规划
这里分享一个很重要但容易踩坑的点:SearXNG 不需要暴露到公网。
搜索服务是给 OpenClaw 内部调用的,对外没有服务对象。安全组只需要放行 SSH 端口,以及你访问 OpenClaw 控制台可能用到的端口。SearXNG 默认监听 8080,Jina Reader 走的又是外部 API,理论上这些都不用暴露到公网。
这样做的好处是避免搜索引擎接口被外面扫到。SearXNG 一旦部署成公网可访问状态,就会被各种爬虫脚本利用来刷搜索结果,轻则耗光你的服务器带宽,重则上游引擎把你的出口 IP 封掉。
所以我的建议是:在云厂商控制台的安全组中,只保留 SSH 端口,其他端口一律不放行。SearXNG 地址用http://localhost:8080即可,OpenClaw 如果也宿主机上跑,直接访问 localhost 就能连通。
如果你确实需要远程查看 SearXNG 的搜索界面,可以用 SSH 隧道:
ssh -L 8080:localhost:8080 user@your-server这样本机浏览器访问http://localhost:8080就是服务器上的 SearXNG,而公网照样访问不到。这个用法比开放安全组安全得多。
3. SearXNG 部署与调优:核心就在 settings.yml
3.1 用 Docker Compose 拉起实例
SearXNG 官方提供了独立的 Docker 镜像,配合 Compose 文件管理最清晰。下面是我在用的最小化 Compose 配置:
version: "3.8" services: searxng: image: searxng/searxng:latest container_name: searxng restart: unless-stopped ports: - "127.0.0.1:8080:8080" volumes: - ./searxng-data:/etc/searxng environment: - SEARXNG_BASE_URL=http://localhost:8080/ cap_drop: - ALL cap_add: - CHOWN - SETGID - SETUID这里有一个细节需要注意:端口映射写的是127.0.0.1:8080:8080,而不仅是8080:8080。映射到本地回环地址可以让 Docker 只在本机访问该端口,外部网络根本连接不到。这是比安全组更底层的一层防护。
启动命令:
mkdir -p searxng-data docker compose up -d首次启动时容器会生成默认的settings.yml到searxng-data目录。如果拉镜像比较慢,记得在 Docker daemon 配置里加一下国内可用的镜像加速地址,不然可能等好几分钟。
3.2 修改 settings.yml:让外部调用正常工作
SearXNG 默认配置主要面向 Web 界面,要让 OpenClaw 能通过 JSON 格式调用,必须改三处:生成密钥、打开 JSON 格式、调整默认搜索格式。
先处理secret_key。这是它内部签名和 Cookie 加密的基础密钥,默认配置里是一段占位符,不换掉的话服务在外部访问时会出现异常。生成方式很简单:
openssl rand -hex 32建议把它写进环境变量,不直接放进 settings.yml。我通常会在 Compose 文件里加一行:
environment: - SEARXNG_SECRET=xxxxx接着编辑searxng-data/settings.yml,重点修改这两块:
use_default_settings: true server: secret_key: ${SEARXNG_SECRET} bind_address: "0.0.0.0" limiter: false search: formats: - html - jsonformats里加上json是 OpenClaw 能拿到结构化搜索结果的关键。搜索请求会变成类似这样的调用:
curl "http://localhost:8080/search?q=test&format=json"返回格式大致是:
{ "results": [ { "url": "https://example.com/article", "title": "示例文章标题", "content": "摘要文本" } ] }limiter: false是把内置限速关掉。这个限速原本是防 Web 界面被滥用,但在纯内部调用场景下,它会在你连续请求时直接拒绝服务,让人误以为是上游引擎封了 IP。我在调式阶段就被这个开关坑过,OpenClaw 连续跑三个任务就报 429,后来发现是 SearXNG 自己限的流。自己用就关掉,无妨。
3.3 引擎层面的取舍:别默认全开
SearXNG 默认会启用十几个引擎,包括 Google、Bing、DuckDuckGo、Wikipedia、GitHub 等。全部启用的问题在于:
- 每个引擎都需要时间去响应,总响应时间等于最慢的引擎决定。
- 某些引擎对自动化请求比较敏感,频繁调用容易被临时封禁。
- 结果数量多了之后,反而对 Agent 没意义——它只需要最相关的几条。
我的做法是只保留少数几个质优稳定的引擎。以下是针对通用搜索的推荐配置:
engines: - name: google disabled: false - name: bing disabled: false - name: duckduckgo disabled: false - name: wikipedia disabled: false - name: github disabled: false把其余引擎全部设成disabled: true。这样搜索延迟会明显缩短,单次搜索基本在 2-3 秒内返回,而全引擎开启时经常要等 5-8 秒,OpenClaw 的体验差距非常大。
但要注意一个现实问题:不同地域、不同 IP 的服务器,访问上游引擎的成功率差异很大。比如有些人服务器请求 Google 会频繁遇到验证码,DuckDuckGo 反而稳定。这没有统一答案,需要你在部署后用 curl 逐个测试,保留实际可用的引擎。
另外一个实用的点:在引擎具体配置里可以单独设置weight来调节它的结果优先级。比如你希望 Bing 的结果排在前面,就给它weight: 2.0,默认是 1.0。元搜索的原理是聚合排序,调 weight 比调代码里的排序逻辑简单得多。
3.4 搜索接口验证与稳定测试
配置修改完成后,重启容器再跑一遍验证:
docker compose restart curl "http://localhost:8080/search?q=linux&format=json"能正常拿到 JSON 就说明核心链路通了。这时候还可以做一个小压力实验,连续发 20 个请求,观察是否出现超时或空结果。这能帮你在接 OpenClaw 之前,提前暴露引擎稳定性问题。
我实际遇到过的情况是:前 10 个请求一切正常,第 11 个开始 Google 返回空结果,DuckDuckGo 也开始断续失败。后来确认是出口 IP 被上游引擎临时限流,缓解办法是给 SearXNG 加一层简单的缓存。SearXNG 原生支持 Redis 缓存,启用后相同查询词的二次请求会直接命中缓存,大幅减少对上引擎的调用:
redis: url: "redis://localhost:6379/0"对应的 Compose 文件里加一个 Redis 服务即可。缓存时间我习惯设在 5 分钟,够让 Agent 在长任务中重复查询时不打重请求。
4. 接入 Jina Reader 与 OpenClaw 配置:串起搜索到阅读的完整链路
4.1 Jina Reader 的两种接入方式
Jina Reader 最常见的用法是直接用官方托管服务。把目标 URL 替换掉固定域名前缀,就能拿 Markdown:
curl "https://r.jina.ai/https://example.com/article"返回内容就是清洗过后的正文。对于个人使用频率来说,注册账号拿到的免费额度通常够用,而且它不需要你自己维护任何代码。
不过免费额度对高频任务并不友好。比如 OpenClaw 在分析多个链接时,可能一分钟内发十几个读取请求,免费层很容易触发限流。这时候有两个替代思路:
一种是在 OpenClaw 的调度层做限速,把请求间隔拉长到 2-3 秒。另一种更彻底,是自部署一个开源的 Reader 服务。开源社区有几种实现,核心都是用 Playwright 或类似工具渲染页面后抽取正文,配合 Jina 的 URL 格式做等价替换。
我的建议是:先用官方托管 API 把链路调通,后续如果遇到限流,再考虑自部署。不要一上来就自部署,因为浏览器渲染方案的资源消耗远高于 SearXNG,需要额外权衡内存。
4.2 在 OpenClaw 中配置搜索与阅读工具
OpenClaw 这类 Agent 框架的核心思路是给模型注册工具,然后在自然语言交互中按需调用。搜索和阅读就是两个标准工具。
配置上大同小异,关键是定义好工具的入口 URL 和返回参数。下面是一个通用化的配置示例思路:
tools: - name: web_search type: http endpoint: "http://localhost:8080/search" method: GET params: q: "{query}" format: "json" response: "从 results 中取 url、title、content" - name: web_read type: http endpoint: "https://r.jina.ai/{url}" method: GET response: "返回的 Markdown 视为正文内容"这里最关键的是别把format=json漏掉。如果漏了,OpenClaw 拿到的是 HTML 页面,模型没法直接解析,还得自己写抽取规则,等于走回头路。
不同框架的工具注册方式可能差别很大,但你只需要关注一个本质:Agent 向搜索服务发请求、拿 structured response、再把 URL 交给 Reader 服务。无论配置语法怎么变,这条数据流是固定的。
4.3 联调验证:从搜索词到总结回答
配置完成后,做一次端到端测试。给 OpenClaw 发一句话:“搜索一下 Docker 和 Kubernetes 的区别,把网页内容读出来,总结三个关键点。”
接下来观察的链路行为应该是:
- OpenClaw 生成搜索词“Docker vs Kubernetes”。
- 调用 SearXNG 接口返回结果列表。
- OpenClaw 自动挑选 2-3 个链接。
- 对这些链接逐个调用 Jina Reader 拿正文。
- 模型汇总输出最终答案。
在这个测试里,最容易出问题的环节是第 4 步。Jina Reader 对某些网页会返回 403,或者等待时间过长导致 Agent 超时。我的经验是,在 OpenClaw 的阅读工具配置里设置一个略高于默认值的超时时间,比如 30 秒。因为 Reader 服务本身需要先下载页面再抽取正文,耗时比普通 API 长,超时设太短会让 Agent 误判为失败。
如果等待时间确实经常超长,可以考虑把官方 API 换成配套的自部署方案,内部网络调用延迟会低很多。不过这要看具体场景,不是必须的优化。
5. 常见问题与排查技巧实录
5.1 SearXNG 返回 403 / 404
403 最常见的原因是secret_key没有设置好。多数情况下是环境变量没传进容器,或者 settings.yml 里引用变量失败。排查顺序:
docker exec searxng env | grep SECRET docker exec searxng cat /etc/searxng/settings.yml | grep secret_key如果容器内环境变量存在、settings.yml 里也有值,再看是不是上游搜索引擎回源时拒绝了请求。可以通过直接请求引擎地址做对照测试。
404 的场景一般在 JSON 接口上,表现为curl返回纯 HTML 而不是 JSON。这种情况检查 settings.yml 的search.formats是否真的包含了json,改完记得重启容器。
5.2 Jina Reader 超时或 429
官方托管 Reader 对短时间大量请求会进行限流,表现为连续多个 429。这个问题的根源不是配置,而是调用频率。
最直接的缓解措施是在 OpenClaw 工具层加一个请求间隔,把并发调整为串行,每次请求间隔 2 秒以上。如果对网页批量读取的需求很高,自部署 Reader 才是根本解。但要注意,自部署 Reader 的内存占用比 SearXNG 高,1G 内存的服务器可能会紧张,建议至少 2G 起步。
另外 Reader 对某些目标网站返回 403,跟目标站的反爬策略有关,不是你配置问题。换个网站或换一种请求头(后面附带 UA 字符串)可能就能解决。
5.3 OpenClaw 连不上 SearXNG
如果你把 SearXNG 跑在 Docker 里,OpenClaw 也跑在 Docker 里,不能直接用localhost:8080。因为两个容器各自有独立的网络命名空间,localhost指向的是自己。解决办法有两种:
- 在同一个 Compose 网络里用服务名
searxng:8080。 - 或者在 OpenClaw 容器里用宿主机 IP
host.docker.internal:8080。
实际部署时,如果 OpenClaw 是直跑在宿主机上的进程,用localhost:8080就行。如果你把全部服务都容器化,就统一走 Compose 网络。这一点很容易忽略,现象就是 OpenClaw 报 connection refused 或 timeout。
建议在 OpenClaw 容器里先手动执行一次:
curl http://searxng:8080/search?q=test&format=json能通再谈别的。
5.4 上游引擎被封禁
SearXNG 的 IP 如果被某个上游引擎临时封禁,表现为这个引擎的结果突然全部消失,或者请求这个引擎时响应时间显著变长。
解决优先级:
- 降低对该引擎的调用频率,把并发设低。
- 给 SearXNG 启用 Redis 缓存,减少真实请求。
- 在引擎列表里临时禁用这个引擎,靠其他引擎顶上。
- 如果频繁被多个引擎封禁,考虑换一个出口 IP。
这里提醒一句,不要在服务器上对上游搜索引擎做大规模并发抓取,这既会连累你的 SearXNG 稳定运行,也会给其他使用者带来麻烦。合理使用、低频调用才是长期方案。
5.5 内存与性能问题
SearXNG 容器在某些场景下内存会缓慢增长,尤其启用大量引擎时。我发现的问题集中在长时间运行后,容器内存比刚启动时高了近一倍。这跟引擎连接池不释放有关。
最简单的处理是给容器加一个内存上限:
deploy: resources: limits: memory: 512M总体思路是控制引擎数量和并发数,内存问题就会自然缓解。真到了内存不足的程度,用docker compose restart searxng一次性释放连接池也就够了。
6. 一点经验与后续扩展
这条链路跑顺以后,我最大的体会是:SearXNG 的配置项非常多,但真正决定成败的只有几个——secret_key、formats、引擎开关和限流设置。把这几项处理干净,部署成功率就很高了。别一开始就调花哨的功能,先把最小闭环跑通,再逐步加引擎、加缓存、加自定义排序。
如果你后续想进一步扩展能力,有几个方向可以试着走:给 SearXNG 做语义缓存,把常见问题的搜索结果存到向量数据库里,OpenClaw 再次遇到类似问题时直接命中缓存,减少对外请求;或者对 Reader 的结果做本地快照,把常用页面的正文保存起来,形成一个轻量知识库。
这套免费体系的好处在于它没有单次调用的计费压力,也就给了你足够的空间去实验和优化。说到底,搜索能力对 Agent 而言只是基础设施,稳定、无顾虑地持续运行,比任何花哨的进阶功能都重要。希望这篇记录能帮你少走点弯路。