Files
Aq-Accounting-Spring/docs/SearXNG搭建指南.md
T
2026-09-24 14:27:12 +08:00

468 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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=<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 常用命令
```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 · 所有数据均为在本项目服务器上实测*