返回博客

2026.10.19

错误率 15%,日志却说它 58% 的时间不在场:我的自建搜索,到底有几个引擎在干活?

自建 SearXNG 的统计页说 Google 那一路错误率 15%,agent 拿到的结果条数三天里 1171 次都是满的 10 条。翻 raw 计数才发现,58% 的调用里 Google 根本没参战。这篇讲怎么用一个加法读出哪个引擎缺席,以及八月那次我修好了一台不在调用链上的机器。

自建服务搜索方法论排障

三天,1173 次搜索,我的 AI 助手每次要 10 条结果,有 1171 次拿到了满满的 10 条。

同一段时间,SearXNG 的统计页上,Google 那一路的错误率是 15%。

两个数都很好看。但日志里还有第三个数:每次搜索后端到底返回了多少条原始结果。这个数恰好等于 25 的,有 685 次,占 58%。而三个引擎全在场的时候,它应该是 45 左右。

也就是说,有一半多的搜索,Google 根本没出场,而上游下游两个指标都没告诉我。

这是我第二次在同一套自建搜索上栽跟头。八月那次更难看:我修好了一台搜索服务,报了「修好了」,结果修的那台根本不在调用链上。两件事是同一个病,第 3 章讲。想直接拿判读方法的看第 2 章和最后的清单。

1. 25 这个数是怎么来的

先交代一下这东西。SearXNG 是个开源的「元搜索」:你给它一个查询,它同时去问 Google、Bing、Yandex 这些引擎,把结果合并去重再还给你。我的 agent 联网搜索就靠它。

它每次返回的 JSON 里有个 results 数组。我的 agent 拿到之后按分数排序,截前 10 条用,日志里顺手记一句:

SearXNG search '...': 10 results (from 25 raw, limit 10)

那个 from 25 raw 就是截断之前的原始条数。

我把这三天的 raw 计数做了个直方图,排前几的是:

raw 次数
25 685
24 68
22 68
45 42
44 25
35 25

一个数独占 58%,这就很可疑了。正常情况下合并去重之后的条数应该散一点,不会这么整齐。

去现场打了十次探针,每次看是哪些引擎贡献了结果。答案特别规整:google cse 20 条,yandex 15 条,bing 7 到 10 条。加起来 42 到 45。

这就能对上账了:

raw 满载 ≈ 每个参战引擎一页的条数之和
         = 20 (google cse) + 15 (yandex) + 10 (bing) = 45
少了 google cse:15 + 10 = 25

就是一个加法。直方图上那个 25 的大尖峰,就是「google cse 缺席、只剩 yandex 和 bing」这个状态的指纹。

反过来用更有意思:知道每个引擎一页给几条,看 raw 计数的直方图,就能读出哪个引擎在哪些时候缺席。 不用开 SearXNG 的后台,agent 这一侧的日志就够了。你自己的实例每家给几条不一定跟我一样,探针打一次就知道。

2. 15% 和 58%,到底哪个是真的

去翻 SearXNG 的容器日志,原因写得很直白:

google cse: Our systems have detected unusual traffic from your network. (suspended_time=180)

Google 觉得我这边流量不正常,回了 429。SearXNG 收到之后把这个引擎罚停 180 秒,这 3 分钟里所有查询都不再问它。

那 15% 错误率是怎么回事?我的理解是:错误率只统计这个引擎真正发出去的请求。触发罚停的那一下算一次错误,之后 180 秒它压根没发请求,自然也不会再有错误。罚停期间的缺席,在错误率里是隐形的。

所以这两个数量的是两件事:

错误率   = 失败的请求 ÷ 它发出去的请求       → 15%
缺席率   ≈ raw 恰好掉到「少它那一档」的调用 ÷ 总调用 = 685 ÷ 1173 ≈ 58%

我关心的是后一个。我想知道的是「我搜的时候它在不在」,它发出去的请求成功率多少,我其实不在乎。

再拆一层更能说明问题。我把调用按小时分了一下,一小时 50 次以上的算忙时:

调用数 raw=25 占比
忙时(≥50 次/小时) 973 651 67%
闲时 200 34 17%

忙的时候三次里两次没有 Google,闲的时候六次里一次。忙时基本是我同时开了好几个 agent 在查资料,搜得越猛,Google 越嫌你烦,罚停越频繁。偏偏最需要搜得好的就是这种时候。

然后是最隐蔽的那一层:下游那个「10 条结果」把这一切都盖住了。 25 条照样够截 10 条,agent 每次都拿到满的,从它的角度看搜索一直很健康。少了 Google 之后前 10 条质量掉了多少,这个我没量,第 4 章会说。

这里可以抽出一条通用的判据,看任何聚合系统的健康数字都能用:

错误率只能告诉你「问了的时候成不成」,它不会告诉你「该问的时候问没问」。 任何带熔断、罚停、降级的系统,都要另外看一个「缺席」的数,最好是从调用方那一侧数出来的。

3. 八月:实例全绿,修的却是另一台

说回八月那次,因为这个病我其实早就见过一次了。

当时我觉得搜索后端「老是挂」。先看实例:容器跑了四周没重启过,没有被 OOM 杀过,HTTP 200,响应 2.35 秒。全绿。

真正的问题在引擎层。/stats/errors 拉出来:

引擎 错误率 类型
mojeek 95% 403
duckduckgo 75% CAPTCHA
brave 70% 429
baidu 15% CAPTCHA
startpage 5% CAPTCHA
google 0% —

Google 错误率 0%,结果恒为 0。这个组合我后来专门记了一条判读规则:

  • 引擎名出现在返回的 unresponsive_engines 里 → 被反爬封了
  • 不在 unresponsive 里、错误率 0%、但结果永远是 0 → 解析器过期

第二种最阴。HTTP 200,没异常,没日志,就是解析不出东西。那次的根因是镜像落后上游两个月,搜索引擎改了页面结构,旧解析器就瞎了。起了一个新版临时容器同题对拍,google 从 0 条变成 44 条,根因当场锁死。

还有两个小发现顺便记一下:

  • 被封的引擎换住宅代理没用。brave、mojeek、startpage 直连和走代理的返回一模一样,人家认的是整体指纹(TLS、HTTP/2、行为模式),换个出口 IP 骗不过去。
  • 响应从 2.35 秒降到 0.90 秒,主要功劳是把那几家被封的直接禁了。被封的引擎每次都要等满超时才放弃,聚合结果得等最慢的那个回来。

修完之后,同一批 10 个查询,结果中位数从 36 涨到 44,空结果 0/10。我很高兴地报告:修好了。

然后被追问了一句「真的修好了吗」。我让 agent 真跑一次搜索工具,返回 {"web": []}。空的。

原来这台机器上有两个 SearXNG:一台在 NAS 上,一台在本机的容器里。agent 读的是 .env 里写的地址,指向本机那台。我看了 systemd 的服务配置、看了覆盖文件、看了进程的环境变量,三处都写着 NAS 的地址,就认定它生效了。实际上 .env 的优先级比这三处都高,这三处显示的都是被压掉的值。

我花了一上午,把一台不在调用链上的机器修得很漂亮。

最尴尬的是,「看运行时实际生效的值,别看配置文件写了什么」这条原则,是我那天上午刚从 Google 解析器那件事里总结出来的。我对 SearXNG 的配置严格执行了它,对 agent 的配置没执行。

回头看这两次是一个病。八月是「实例健康」盖住了「引擎死了」,这次是「每次都返回 10 条」盖住了「Google 一半时间不在」。指标挂在哪一层,就只能替那一层作证。

4. 这次没做完的地方

几处我没量,结论先不说死:

  • 质量损失没量。 我只知道 58% 的调用少了 Google 那 20 条,没测过前 10 条的相关性因此掉了多少。可能 Bing 加 Yandex 已经够用,也可能差很多。
  • 忙时的阈值是我拍的。 「一小时 50 次」只是为了把两种状态分开,换个阈值比例会变,方向应该不会变。
  • 两边日志的时间是我手动对齐的。 容器日志是 UTC,agent 日志是本地时间。我按时区换算后看到罚停高峰和调用高峰大体重合,但只是目测,没做逐条匹配。
  • 罚停时长和缺席次数没逐条对上。 容器日志三天里 Google 这条 429 记了 79 次,79 × 180 秒 ≈ 4 小时。调用是扎堆的,这 4 小时能不能刚好罩住那 685 次,我没逐条对,也不排除罚停有我没搞懂的累加机制。
  • 中文引擎现在全挂着。 今天的探针里 baidu、quark、sogou 一直在 unresponsive 里,统计页上 baidu 和 sogou 都是 90% 的 CAPTCHA。也就是说中文查询这会儿也是靠那三家英文引擎撑着的,影响多大同样没量。

5. 下次怀疑自建搜索不对劲,按这个顺序查

  1. 先确认你修的是调用方真正在用的那一台。去调用方的代码里看它最终读的是哪个值,systemd 和进程环境变量显示的可能是被压掉的那个。
  2. 跑一次调用方自己的搜索工具,看它返回什么。curl 后端不算数。
  3. 打一次探针,记下每个引擎一页给几条,把满载 raw 加出来。
  4. 在调用方日志里拉 raw 计数的直方图,找那个突出的尖峰,用第 3 步的加法反推谁缺席。
  5. 把缺席率按忙时、闲时分开算。只在忙时缺席的,基本就是被限流罚停了。
  6. 某个引擎错误率 0% 但结果恒为 0,先怀疑解析器过期,起个新版临时实例对拍。
  7. 被 CAPTCHA 或 429 封的引擎直接禁掉,别指望换代理。
  8. 中文引擎至少挂两家,单押一家会被罚停搞出整段空窗。