15 KiB
SearXNG 自建搜索引擎搭建指南
用途:给 AI 助手提供联网搜索能力(后端
web_search工具的后端服务) 环境:Docker 29.7.2 / SearXNG2026.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 前置
docker --version # 需要 Docker
2.2 建配置目录
SearXNG 容器里以 uid 977 的用户运行,目录权限必须给对,否则它写不了配置:
mkdir -p /home/nzy/searxng
chown -R 977:977 /home/nzy/searxng
第一次启动时 SearXNG 会自动生成
settings.yml(含随机 secret_key)。预先把目录 chown 好,避免它因为没权限而静默失败。
2.3 启动容器
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 验证起来了
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 里加
search:
formats:
- html
- json # ← 没有这行,你的程序永远拿不到数据
改完必须重启容器:
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,它会告诉你哪些引擎挂了、为什么挂。这是排错的第一入口:
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 覆盖配置的,名字写错不会报错,只会静默失效——你会以为改了,其实没生效。
# 看镜像里装了哪些引擎模块
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 完整内容:
# 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
改完必做:
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. 验证清单
装完之后按顺序验三件事:
# ① 服务活着 + 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):
# /home/nzy/.env
SEARCH_BASE_URL=http://127.0.0.1:8888
manage.sh 会在启动 java 之前自动 source 这个文件。改完重启:
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=<urlencoded>
取前 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 常用命令
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
docker pull searxng/searxng
docker stop searxng && docker rm searxng
# 重新执行 2.3 的 docker run(配置在挂载目录里,不会丢)
升级后重新跑一遍第 5 节的验证——新版本可能改了默认引擎列表。
8. 附录
8.1 命令速查
# 启动
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 · 所有数据均为在本项目服务器上实测