# SearXNG 自建搜索引擎搭建指南 > 用途:给 AI 助手提供**联网搜索**能力(后端 `web_search` 工具的后端服务) > 环境:Docker 29.7.2 / SearXNG `2026.9.23-3cd69d30e` / Ubuntu(腾讯云 231) > 验证状态:**已上线运行**,实测 14–17 条结果、零异常、约 1.5 秒 --- ## 0. 这套东西解决什么问题 大模型自己**不能上网**。你问它"今天的天气"或"Flutter 最新版有什么变化",它只能靠训练数据里的旧信息编——这叫做幻觉。 正确做法是给它一个**工具**: ``` 你提问 → LLM 判断"我需要联网查一下" → 它让后端调用 web_search("Flutter 3.47 新特性") → 后端去搜索引擎拿结果 → 把结果喂回给 LLM → LLM 基于真实结果回答 ``` 问题在于**中间那个搜索引擎从哪来**: | 方案 | 问题 | |---|---| | Google / Bing 官方 API | 要钱、要申请 key、按次计费 | | 直接爬搜索引擎页面 | 很快被封 IP,还要处理反爬 | | **SearXNG(本方案)** | 开源自托管、免费、有现成的 JSON 接口 | --- ## 1. 先理解原理(不然后面调优会瞎猜)★ **这是全文最重要的一节。** SearXNG 自己**不会搜索**,它是**元搜索引擎**(聚合器): ``` ┌─→ Google ─┐ 你的查询 ─→ SearXNG ─→ DuckDuckGo ─┼─→ 汇总 / 去重 / 排序 ─→ 返回 ├─→ Brave ─┤ └─→ 百度 ─┘ ``` **推论(记住这一条,能省你一小时):** > SearXNG 的可用性 = 它底下那些引擎在你服务器上**通不通**。 所以: - 装在**国外**服务器上 → 默认配置开箱即用(Google/Bing 都通) - 装在**国内**服务器上 → **默认配置返回 0 条结果**,因为默认开的全是墙外的 这不是 SearXNG 装坏了,是它的引擎连不上。第 4 节专门讲怎么解决。 --- ## 2. 安装 ### 2.1 前置 ```bash docker --version # 需要 Docker ``` ### 2.2 建配置目录 SearXNG 容器里以 **uid 977** 的用户运行,目录权限必须给对,否则它写不了配置: ```bash mkdir -p /home/nzy/searxng chown -R 977:977 /home/nzy/searxng ``` > 第一次启动时 SearXNG 会自动生成 `settings.yml`(含随机 secret_key)。预先把目录 chown 好,避免它因为没权限而静默失败。 ### 2.3 启动容器 ```bash docker run -d --name searxng --restart unless-stopped \ -p 127.0.0.1:8888:8080 \ -v /home/nzy/searxng:/etc/searxng:rw \ -e BASE_URL=http://127.0.0.1:8888/ \ -e INSTANCE_NAME=qiangbao-search \ searxng/searxng ``` 逐项说明: | 参数 | 作用 | |---|---| | `-p 127.0.0.1:8888:8080` | **只绑本机**。外面访问不到,降低攻击面和运维负担 | | `-v .../searxng:/etc/searxng:rw` | 配置持久化,容器删了配置还在 | | `--restart unless-stopped` | 开机自启 / 挂了自动拉起来 | | `BASE_URL` | 影响 SearXNG 生成的链接,即使本机用也建议设对 | ### 2.4 为什么只绑 127.0.0.1 后端和 SearXNG 在**同一台机器**上,走 localhost 就够了。不对外暴露的好处: - 不用管 `limiter`(防爬限流)—— 本项目就是关掉的 - 不用担心被当成公开实例滥用、被扫 - 不用配 HTTPS、不用备案 > 如果哪天你需要从别的机器访问它,**不要**直接把端口开到公网。至少加上 `limiter: true` + 反代 + 鉴权。 ### 2.5 验证起来了 ```bash docker ps --filter name=searxng --format " {{.Names}} {{.Status}} {{.Ports}}" # 期望:searxng Up xx seconds 127.0.0.1:8888->8080/tcp ls -la /home/nzy/searxng/ # 期望:看到自动生成的 settings.yml ``` --- ## 3. 两个必踩的坑 ★ ### 坑一:JSON 接口默认是**关**的 **现象**:请求 `?format=json` 返回 **403**,而且报错信息完全看不出原因。 **原因**:SearXNG 官方镜像默认只开启 `html` 格式,JSON 需要显式打开。 **修法**:在 `settings.yml` 里加 ```yaml search: formats: - html - json # ← 没有这行,你的程序永远拿不到数据 ``` 改完**必须重启**容器: ```bash docker restart searxng ``` > 排查技巧:如果你看到 403 但浏览器打开首页是正常的,**先怀疑这一条**。 ### 坑二:国内服务器上默认引擎**全军覆没** **现象**:接口返回 200,但 `results` 是**空数组**。 **原因**:默认启用的通用引擎是 `google cse` / `duckduckgo` / `brave` / `wikipedia` / `wikidata` —— 在国内服务器上**全部连不上**。 **实测数据**(2026-09-24,腾讯云国内机器): ``` 结果数: 0 无响应引擎: [['brave','timeout'], ['duckduckgo','timeout'], ['google cse','timeout'], ['wikidata','timeout'], ['wikipedia','timeout']] ``` **更糟的是**:这些引擎不是立刻失败,而是**各等 3 秒超时**。所以你还会觉得"搜索怎么这么慢"。 **修法**:见下一节。 --- ## 4. 引擎调优(国内服务器重点) ### 4.1 第一步:学会看诊断信息 SearXNG 的 JSON 返回里有个**关键字段** `unresponsive_engines`,它会告诉你哪些引擎挂了、为什么挂。这是排错的第一入口: ```bash curl -s --get --data-urlencode "q=Flutter 状态管理" --data "format=json" \ http://127.0.0.1:8888/search | python3 -c " import json, sys d = json.load(sys.stdin) print('结果数:', len(d.get('results', []))) print('异常引擎:', d.get('unresponsive_engines', [])) eng = {} for r in d.get('results', []): eng[r.get('engine')] = eng.get(r.get('engine'), 0) + 1 print('各引擎贡献:', eng) " ``` **三个指标都要看**: | 指标 | 含义 | |---|---| | 结果数 | 0 就是没搜到,说明引擎全挂 | | 异常引擎 | 谁挂了、挂的原因(timeout / CAPTCHA / crash) | | 各引擎贡献 | 谁真的在干活。**贡献 0 的引擎可以直接关掉**,省它的等待时间 | ### 4.2 第二步:拿到引擎的**准确名字** **这一步千万别跳过。** SearXNG 是按 `name` 覆盖配置的,**名字写错不会报错,只会静默失效**——你会以为改了,其实没生效。 ```bash # 看镜像里装了哪些引擎模块 docker exec searxng ls /usr/local/searxng/searx/engines/ # 用 /config 接口拿准确名字和当前启用状态(推荐) curl -s http://127.0.0.1:8888/config | python3 -c " import json, sys keys = ['baidu', '360', 'quark', 'sogou', 'bing'] for e in json.load(sys.stdin).get('engines', []): if any(k in e['name'].lower() for k in keys): print(f\"{e['name']:<20} enabled={e['enabled']}\") " ``` ### 4.3 第三步:实测结论(本项目) 逐个开关测出来的结果: | 引擎 | 分类 | 实测 | 处置 | |---|---|---|---| | **360search** | general | ✅ 可用 | **开启** | | **quark**(夸克) | general | ✅ 可用 | **开启** | | bing | general,web | ⚠️ 没报错但贡献 0 条 | 保留(无害) | | baidu | general | ❌ `CAPTCHA`(被风控拦) | 关闭 | | sogou | general | ❌ `unexpected crash` | 关闭 | | google cse | general,web | ❌ `timeout` | 关闭 | | duckduckgo | general | ❌ `timeout` | 关闭 | | brave | general | ❌ `timeout` | 关闭 | | wikipedia | general | ❌ `timeout` | 关闭 | | wikidata | general | ❌ `timeout` | 关闭 | > **注意**:这个结论**只对国内服务器成立**。如果你的服务器在香港/海外,Google 系反而应该开着。**别抄结论,抄方法**。 **为什么百度也不行?** 搜狗、百度这类国内引擎的反爬很激进,SearXNG 的对应实现经常被拦(返回验证码页)。所以"国内引擎"不等于"国内服务器上能用"——必须实测。 ### 4.4 最终配置 `/home/nzy/searxng/settings.yml` 完整内容: ```yaml # SearXNG —— 给强宝小助手用 # # 关键一条:search.formats 必须包含 json。官方镜像默认只开 html, # 不开的话 /search?format=json 直接返回 403,且报错信息看不出原因。 use_default_settings: true server: secret_key: "<首次启动自动生成的随机串,保持原样别改>" image_proxy: true limiter: false # 本机自用,限流会让 JSON 接口返回 429 public_instance: false search: formats: - html - json # 引擎取舍:SearXNG 自己不会搜索,它只是聚合各家引擎。 # 这台机器在国内,google/duckduckgo/brave/wikipedia 全部连不上, # 而且每个都要白等 3 秒超时 —— 必须关掉,否则搜索又慢又空。 engines: # 国内能通的 - name: 360search disabled: false - name: quark disabled: false - name: bing disabled: false # 实测不行的:baidu 被 CAPTCHA 拦、sogou 报 unexpected crash - name: baidu disabled: true - name: sogou disabled: true # 实测 timeout 的(2026-09-24 测出来 5 个全挂) - name: google cse disabled: true - name: duckduckgo disabled: true - name: brave disabled: true - name: wikipedia disabled: true - name: wikidata disabled: true ``` **改完必做**: ```bash chown 977:977 /home/nzy/searxng/settings.yml # 权限别丢 docker restart searxng sleep 7 # 给它点启动时间 docker logs searxng 2>&1 | grep -iE "error|invalid|unknown" | tail -5 ``` > 日志里如果有 `can't register engine` 说明**引擎名写错了**,配置那行会静默失效——这就是 4.2 要你先拿准确名字的原因。 ### 4.5 调优后的效果 ``` 查询「Flutter 状态管理」 → 12 条结果,1.5 秒,异常 [] 查询「MySQL 主从复制 搭建」→ 14 条结果,异常 [] 查询「深度求索 DeepSeek」 → 17 条结果,异常 [] 各引擎贡献: {'360search': 6, 'quark': 8} ``` --- ## 5. 验证清单 装完之后按顺序验三件事: ```bash # ① 服务活着 + JSON 格式已开(这一步只验格式,不验引擎) curl -s -o /dev/null -w "HTTP %{http_code}\n" \ "http://127.0.0.1:8888/search?format=json&q=test" # 200 = 格式开了;403 = 回去看 3.1 # ② 引擎真的能搜到东西 curl -s --get --data-urlencode "q=测试" --data "format=json" \ http://127.0.0.1:8888/search | python3 -c " import json,sys; d=json.load(sys.stdin) print('结果数:', len(d.get('results',[])), '异常:', d.get('unresponsive_engines',[])) " # 结果数 >0 且 异常=[] 才算通 # ③ 中文查询也正常(很多引擎对中文支持差) curl -s --get --data-urlencode "q=今天有什么新闻" --data "format=json" \ http://127.0.0.1:8888/search | python3 -c " import json,sys; print(len(json.load(sys.stdin).get('results',[]))) " ``` --- ## 6. 接入后端(本项目) ### 6.1 配置 后端通过环境变量读地址(见 `application.yml` 的 `ai.search.base-url`): ```bash # /home/nzy/.env SEARCH_BASE_URL=http://127.0.0.1:8888 ``` `manage.sh` 会在启动 java 之前自动 source 这个文件。改完重启: ```bash cd /home/nzy && ./manage.sh restart # 确认变量真的进到进程里了 tr '\0' '\n' < /proc/$(cat app.pid)/environ | grep SEARCH_ ``` **不配这个变量会怎样**:后端**不注册** `web_search` 工具——模型不知道自己能联网,也就不会瞎编"我查到了"。这是刻意的设计。 ### 6.2 后端怎么用它 ``` LLM 决定联网 ↓ tool_calls: web_search({"query": "..."}) SearxngApiClient.search(query, maxResults=5) ↓ GET {SEARCH_BASE_URL}/search?format=json&q= 取前 5 条 → 拼成 JSON(title/url/snippet)→ 回填给模型 ↓ 模型基于真实结果回答,并标注来源 ``` 代码在 `src/main/java/com/accounting/service/SearxngApiClient.java`。 ### 6.3 ⚠️ 一个安全提醒 **脱敏层保护的是「发给 LLM 的内容」,不包含搜索词。** 搜索词会原样发给 360search / 夸克(以及它们的上游)。所以: - ✅ 「帮我查一下 Flutter 3.47 有什么新特性」 - ❌ 「帮我查一下 155.103.159.109 这台机器」 第二句会把你的服务器 IP 送到第三方搜索引擎。**别在联网搜索里带敏感信息。** --- ## 7. 日常维护与排错 ### 7.1 常用命令 ```bash docker logs searxng 2>&1 | tail -30 # 看日志 docker restart searxng # 改配置后重启 docker stats searxng --no-stream # 资源占用 ``` ### 7.2 排错对照表 | 现象 | 原因 | 处置 | |---|---|---| | `?format=json` 返回 **403** | `search.formats` 没加 json | 见 3.1 | | 返回 200 但**结果数为 0** | 引擎全连不上 | 见 3.2 / 4.3 | | 结果里有 **`CAPTCHA`** | 该引擎被风控拦 | 关掉它 | | 结果里有 **`unexpected crash`** | 该引擎实现有 bug | 关掉它 | | 结果里有 **`timeout`** | 网络不通 | 关掉它(别留着白等) | | 返回 **429** | `limiter` 开着 | 设 `limiter: false` 并重启 | | 日志报 `can't register engine` | **引擎名写错** | 用 `/config` 拿准确名字(4.2) | | 容器反复重启 | 配置目录权限不对 | `chown -R 977:977 /home/nzy/searxng` | | 本机 curl 不通 | 端口没发布 / 绑错地址 | 检查 `-p 127.0.0.1:8888:8080` | | 搜索很慢(3 秒以上) | 有引擎在等超时 | 用 `unresponsive_engines` 找出并关掉 | ### 7.3 升级 SearXNG ```bash docker pull searxng/searxng docker stop searxng && docker rm searxng # 重新执行 2.3 的 docker run(配置在挂载目录里,不会丢) ``` > 升级后**重新跑一遍第 5 节的验证**——新版本可能改了默认引擎列表。 --- ## 8. 附录 ### 8.1 命令速查 ```bash # 启动 docker start searxng # 看哪些引擎启用中 curl -s http://127.0.0.1:8888/config | python3 -c " import json,sys for e in json.load(sys.stdin)['engines']: if e['enabled']: print(e['name'], '|', ','.join(e.get('categories',[]))) " # 完整搜索测试(三个指标一起看) curl -s --get --data-urlencode "q=关键词" --data "format=json" \ http://127.0.0.1:8888/search | python3 -c " import json,sys; d=json.load(sys.stdin) print('结果数:', len(d.get('results',[]))) print('异常引擎:', d.get('unresponsive_engines',[])) eng={} for r in d.get('results',[]): eng[r['engine']]=eng.get(r['engine'],0)+1 print('各引擎贡献:', eng) " # 浏览器里直接用(图形界面,方便手工试) # 把 8888 端口临时转发出来,或直接在服务器上用 lynx/curl 看 html ``` ### 8.2 本项目的部署信息 | 项 | 值 | |---|---| | 服务器 | 腾讯云 231(国内) | | 容器名 | `searxng` | | 监听 | `127.0.0.1:8888`(不对外) | | 配置目录 | `/home/nzy/searxng/` | | 版本 | SearXNG `2026.9.23-3cd69d30e` | | 后端配置项 | `SEARCH_BASE_URL`(在 `/home/nzy/.env`) | | 可用引擎 | 360search、quark(bing 保留) | | 停用方式 | `docker stop searxng`(后端会自动不给模型 web_search 工具) | ### 8.3 一句话总结 > **SearXNG 装起来只要三行命令,难的是「它在你的服务器上能不能真的搜到东西」——而那 100% 取决于底层引擎的网络可达性。先测、再配,别抄别人的引擎清单。** --- *文档创建:2026-09-24 · 所有数据均为在本项目服务器上实测*