# 自托管SearXNG服务 **Repository Path**: ccq/searxng-selfhost ## Basic Information - **Project Name**: 自托管SearXNG服务 - **Description**: 自托管SearXNG服务 - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-19 - **Last Updated**: 2026-09-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # searxng-selfhost — 自托管 AI 检索网关 **零 API key** 的自托管检索/行情网关:SearXNG 网页搜索 + Reader 网页正文抽取 + 实时行情, 统一经 APISIX(JWT 鉴权 + 限流 + 缓存)收口为**一个端口**,供 AI 服务 / agent 框架调用。 平替 MiroFlow 依赖 SERPER(搜索)/ JINA(抓取)的能力,并新增实时行情引擎。 --- ## 一、三个引擎 | 引擎 | 网关端点 | 能力 | 数据源 | 关键限制 | |---|---|---|---|---| | `search` | `/` | 网页搜索(中文引擎优先) | baidu / sogou / 360 / bing | 结果是网页快照,非实时 | | `read` | `/read` | 抓取指定 URL 并抽正文 | 任意外网 URL(SSRF 防护) | lite 版不执行 JS | | `quote` | `/quote` | A 股实时行情 | 新浪主源 + 腾讯兜底 | 准实时,非交易所直连 | | `stock_cache` | `/stock_cache/query` | 股票财务/行情指标本地缓存与复用 | 首次走 `search` 搜索,后续命中本地缓存 | 动态股票池仅 admin(`service_admin`)可改;`stocks.json` 为本地个人配置(gitignore),仓库仅留 `stocks.json.example` 模板 | 共性:**无 token → 401**;带 JWT → 200;每 consumer 独立限流;重复搜索命中缓存(quote 除外)。 ## 二、架构总览 ``` AI 服务 / agent ──HTTPS + JWT──▶ [APISIX 网关 :9080/:9443]──┬─▶ / → SearXNG :8080(搜索) ├─▶ /read → Reader :8000(抓取+抽正文) ├─▶ /quote → 新浪/腾讯行情(双源兜底,经 reader 转发) └─▶ /stock_cache → Reader :8000(指标缓存复用;增删池仅 service_admin) ``` - 网关是**唯一对外入口**:`searxng` / `reader` 不暴露宿主机端口。 - APISIX standalone 模式(免 etcd):`jwt-auth` + `limit-count`(按 consumer 隔离)+ `proxy-cache`(/quote 除外)。 - **三种部署形态,对外接口完全一致**(agent 框架无感): | 形态 | compose 文件 | 容器 | 适用 | |---|---|---|---| | **lite·split**(默认推荐) | `docker-compose.lite.yml` | 3 容器(apisix / searxng / reader) | 本地 / 小 ECS,要故障隔离 | | **lite·merged** | `docker-compose.lite-merged.yml` | 1 容器(supervisord 管 3 进程) | 只要管 1 容器 1 端口,运维极简 | | **full** | `docker-compose.secure.yml` | 4 容器(+ browserless JS 渲染) | SPA / 强反爬目标站(📌 TODO 未验证) | 仓库结构: ``` searxng-selfhost/ ├── docker-compose.lite.yml # lite·split(推荐) ├── docker-compose.lite-merged.yml # lite·merged ├── docker-compose.secure.yml # full(browserless,TODO) ├── docker-compose.yml # 最小版:裸 SearXNG(可信内网用) ├── apisix/conf/ # standalone 网关配置(apisix.yaml / config.yaml / gen_token.py) ├── reader/ # Reader 服务(/read 抓取 + /quote 行情) ├── merged/ # merged 单容器构建(Dockerfile / supervisord / entrypoint) ├── core-config/settings.yml # SearXNG 配置(中文引擎 + json 格式) ├── tests/test_stack.sh # 8/8 端到端测试矩阵 ├── query.py / query.sh # 多引擎查询客户端(零依赖) ├── stock_cache/ # 股票信息本地缓存与复用(见 stock_cache/README.md) ├── .env # 密钥(gitignore) └── docs/TECHNICAL.md # 实现细节 / 踩坑 / 历史记录 ``` ## 三、快速开始(本地) ```bash cd searxng-selfhost # 1) 起栈(默认推荐 split;用 merged 只需换文件名) docker compose -f docker-compose.lite.yml up -d # 2) 端到端验证(8/8:鉴权 / 搜索 / read / SSRF / 限流 / quote) bash tests/test_stack.sh # 3) 查询(query.py 自动生成 JWT,纯标准库零依赖) python3 query.py "孩子王 股价" # 搜索 python3 query.py "孩子王" --engine quote # 实时行情(名字 / 6位码 / 前缀码均可) python3 query.py --read "https://example.com" # 抽正文 ``` > 首次部署前把 `.env` 的 `JWT_SECRET` 与 `core-config/settings.yml` 的 `secret_key` 换成 `openssl rand -hex 32` 生成的真实值。 ## 四、鉴权(JWT) - 每服务一把凭证:consumer `service_a` / `service_b`,HS256,密钥 = `.env` 的 `JWT_SECRET`。 - 签发 token:`JWT_SECRET=<密钥> python3 apisix/gen_token.py service_a` - 请求头:`Authorization: Bearer `(**带 `Bearer` 前缀**,与 `query.py` / `stock_cache/gateway.py` 一致)。 ## 五、接口契约(curl) ```bash GW=http://127.0.0.1:9080 TOKEN=<上节签发> # search:网页搜索 curl -H "Authorization: Bearer $TOKEN" --get --data-urlencode "q=孩子王 股价" --data-urlencode "format=json" "$GW/" # → {"results":[{title,url,content},...]} # read:抓网页抽正文(非法 URL→400;私网/黑名单→403 SSRF) curl -H "Authorization: Bearer $TOKEN" --get --data-urlencode "url=https://example.com" --data-urlencode "format=json" "$GW/read" # → {"url","status_code","title","text","needs_login"} # quote:实时行情(三种输入形式等价) curl -H "Authorization: Bearer $TOKEN" "$GW/quote?code=孩子王" # 名字(腾讯 smartbox 解析) curl -H "Authorization: Bearer $TOKEN" "$GW/quote?code=301078" # 6 位代码(自动猜市场) curl -H "Authorization: Bearer $TOKEN" "$GW/quote?code=sz301078" # 前缀代码 # → {"code":"sz301078","name":"孩子王","current":7.1,"change":0.12,"change_pct":1.72, # stock_cache:指标本地缓存查询(对外,任意合法 JWT) curl -H "Authorization: Bearer $TOKEN" "$GW/stock_cache/query?stock=孩子王&period=2024年年报&indicator=营收" # → {"value":"1234.56","unit":"亿元","raw_text":...,"source_url":...,"fetched_at":"...","cached":false} # 动态股票池增删仅 admin(service_admin 凭证):见 stock_cache/README.md # POST /pool 请求体已 Pydantic 强校验(未知字段/缺 code → 422),完整接口契约见 stock_cache/README.md # "open":6.99,"prev_close":6.98,"high":7.14,"low":6.95, # "volume":19369081,"amount":136856885,"time":"2026-08-20 15:35:30"} ``` ## 六、部署到 ECS | 项 | 做法 | |---|---| | 端口 | compose `ports` 改 `80:9080` / `443:9443`;安全组**只开这两端口** | | 密钥 | `.env` 的 `JWT_SECRET`、`settings.yml` 的 `secret_key` 换成随机值 | | 镜像源 | ECS 配阿里云镜像加速器;pip 走阿里云 PyPI 镜像 | | 中文引擎 | 已默认启用 baidu/sogou/360(`settings.yml` `engines:` 段)——国内网络必读 | | HTTPS | 可选:APISIX acme 插件自动签发(细节见 docs/TECHNICAL.md §2) | | 安全 | SearXNG 本身无鉴权,勿把 8080 裸奔公网(网关收口方案见 docs/TECHNICAL.md §7) | ## 七、当前状态(2026-08-21) | 形态 | 状态 | |---|---| | lite·split | ✅ 8/8 测通(Mac Docker 真机) | | lite·split(外网) | ✅ 8/8 测通(外网 ECS 实测验收) | | lite·merged | ✅ 8/8 测通(Mac Docker 真机)+ 已发布 Docker Hub | | full | 📌 TODO:browserless 3GB 镜像,本地 4GB VM 跑不动,待 ECS(≥8GB)验证 | > 版本:v1.0(2026-08-21 发布,详见 CHANGELOG.md)。 **诚实边界**:`quote` 为网页行情快照源(A 股盘中准实时、收盘后为当日收盘价,**非交易所直连**,不满足严格合规行情需求);`search` 结果为搜索引擎缓存快照。精确实时行情请走专业数据源。 ## 八、文档导航 | 文档 | 内容 | |---|---| | **docs/TECHNICAL.md** | 实现细节、构建踩坑、版本历史、验证记录、排错速查、设计决策、MiroFlow 接入示例 | | `tests/test_stack.sh` | 8/8 端到端测试矩阵 | | `query.py` / `query.sh` | 多引擎查询客户端(`--engine search\|quote`) | | `stock_cache/` | 股票信息本地缓存与复用:三层归一化、SQLite 缓存、pytest 验收;路由合并进 reader,对外 `/stock_cache/query`、池管理仅 `service_admin`(详见 `stock_cache/README.md`) | | `apisix/conf/apisix.yaml` | 网关声明式配置:consumers(service_a / service_b / **service_admin**)+ 路由(`/`、`/read`、`/quote`、**`/stock_cache/*`**) |