MAATRIX / Блог / SearXNG выдаёт пустую выдачу: причины и решение

SearXNG выдаёт пустую выдачу: причины и решение

SearXNG выдаёт пустую выдачу: причины и решение

MAATRIX

Сервис отвечает 200, JSON собирается без единой ошибки в логе, а в поле results — пустой массив: SearXNG не ищет, хотя формально жив и здоров. Почти всегда дело не в конфиге, который вы правили последним, — какой-то движок мог замолчать сам, на час, на сутки или на две недели, и вы об этом не узнаете, если не туда посмотреть. Разберём главные механизмы пустой выдачи — с точными текстами ошибок, командами диагностики и строками конфига, без пересказа установки и общих ошибок SearXNG.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Пустая выдача — это два разных диагноза, а не один

Прежде чем открывать settings.yml, разделите симптом на два случая — лечатся они по-разному, и перепутать их — способ потратить вечер не на то. Проверка одна и та же для обоих:

curl -s 'http://127.0.0.1:8080/search?q=nginx&format=json' \
  | jq '{results: (.results|length), unresponsive: .unresponsive_engines}'

Случай А — движки вообще не спрашивали. Ответ выглядит так:

{"results": 0, "unresponsive": []}

Пустой results при пустом же unresponsive_engines значит одно: ни один движок даже не сделал запрос наружу. Дело не в блокировках и не в сети — дело в том, какие движки вообще участвуют в этой категории. На веб-странице то же самое: блок «Sorry! No results were found» есть, а раскрывающийся блок «Messages from the search engines» пуст. Разбор — в разделе 2.

Случай Б — движки спрашивали и получили отказ. unresponsive_engines не пуст:

{"results": 0, "unresponsive": [["google", "Suspended: CAPTCHA"], ["startpage", "Suspended: CAPTCHA"], ["duckduckgo", "Suspended: CAPTCHA"]]}

Три разных движка с одинаковым текстом «Suspended: CAPTCHA» одновременно — не совпадение, а конкретный механизм из разделов 3 и 4. На веб-странице тот же блок будет открыт по умолчанию и покажет ровно эти строки вместе со временем ответа тех, кто всё же откликнулся.

Ноль движков в категории: молчаливый keep_only и опечатка в имени

Самая частая причина случая А — короткий список движков, из которого что-то выпало незаметно. Как писать use_default_settings.engines.keep_only, разобрано в статье про установку; здесь — что происходит, когда список неверный.

Опечатка в имени движка не даёт исключения: несуществующее имя в keep_only просто ничего не оставляет в тех категориях, где оно было единственным. Проверка — прямо на живом инстансе, без похода в YAML:

curl -s http://127.0.0.1:8080/config | jq '[.engines[] | select(.enabled)] | length'
curl -s http://127.0.0.1:8080/config \
  | jq -r '.engines[] | select(.enabled) | .categories[]' | sort | uniq -c

Второй запрос честно покажет, сколько включённых движков реально стоит за каждой категорией:

  9 general
  0 images
  0 it
  0 map
  3 news
  0 science

Ноль напротив science — не баг и не сеть: во вкладке действительно не осталось ни одного движка, который к ней приписан, и она обязана быть пустой при любом запросе. Та же логика работает и с блоком remove: — убрав из списка google, bing, duckduckgo и qwant из соображений приватности, легко не заметить, что во вкладке images они были единственными представителями категории. Та же команда /config разоблачает и мёртвый bang: если !go запрос вдруг ищет буквально фразу !go запрос, а не делает редирект в Google, — jq -r '.engines[] | select(.shortcut=="go")' покажет пустой ответ, и станет ясно, что шортката go в инстансе просто нет.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть SearXNG

Как читать unresponsive_engines: словарь ошибок и что значит Suspended

Случай Б устроен иначе, и это отдельный, довольно богатый словарь: SearXNG подставляет в unresponsive_engines не сырое исключение, а человекочитаемый текст по его типу. Разобрать его по составу стоит один раз — дальше любая строка из ответа читается за секунду:

Что произошло у движкаТекст в unresponsive_engines
Исключение не распознаноunexpected crash
Любой вид таймаута (ConnectTimeout, ReadTimeout, WriteTimeout)timeout
Ответ с кодом ошибки (httpx.HTTPStatusError)HTTP error
Не удалось установить соединение (httpx.ConnectError)HTTP connection error
Обрыв на уровне протокола или при чтении/записиHTTP protocol error / network error
Отказ прокси (httpx.ProxyError)proxy error
Источник показал капчу (SearxEngineCaptchaException)CAPTCHA
Источник ответил «слишком много запросов»too many requests
Источник отклонил запрос явноaccess denied
Ошибка самого API источникаserver API error
Не удалось разобрать ответ (XPathException, KeyError, JSONDecodeError)parsing error
Сертификат не прошёл проверкуSSL error: certificate validation has failed

Приставка Suspended: перед любым из этих текстов меняет смысл строки целиком: не «отказал прямо сейчас», а «ещё отбывает наказание за прошлый отказ». Получив капчу, SearXNG сам перестаёт спрашивать этот движок на время, зависящее от типа ошибки, — от часа за обычную капчу до двух недель за капчу под защитой Cloudflare. Полная таблица пауз по каждому типу — в статье SearXNG на сервере: частые ошибки и решения; здесь важнее другое — что происходит, когда эта пауза достаётся не одному движку, а сразу нескольким.

Один network на несколько движков — как чужой бан гасит и ваш

Самый неочевидный вариант случая Б — когда «Suspended: CAPTCHA» разом получают движки, формально ничего не нарушавшие. Причина — в том, как SearXNG хранит состояние паузы внутри процесса.

В процессоре поиска (searx/search/processors/abstract.py) пауза привязана не всегда к имени движка, а к идентичности объекта сети, через который он ходит наружу:

key = get_network(engine.name)
key = id(key) if key else engine.name
suspended_status = SUSPENDED_STATUS.setdefault(key, SuspendedStatus())

Если у движка нет своей секции network, ключом становится его имя, и пауза персональная. Но стоит нескольким движкам сослаться на один и тот же именованный network строкой — они получат один и тот же объект сети, а значит, общий id() и общую паузу на всех:

outgoing:
  networks:
    no_proxy:
      proxies: ~
engines:
  - name: google
    network: no_proxy
  - name: bing
    network: no_proxy
  - name: duckduckgo
    network: no_proxy
  - name: startpage
    network: no_proxy

С таким конфигом капча на Startpage подвешивает на тот же срок заодно Google, Bing и DuckDuckGo — не потому что их тоже заблокировали, а потому что для SearXNG это один и тот же ключ в таблице пауз. Узнать эту картину в unresponsive_engines легко: несколько разных движков с идентичным текстом ошибки и совпадающим моментом появления в списке — это не серия совпадений, а общий счётчик.

Лечится строкой конфига: либо убрать network: у движка целиком — тогда он получит собственное подключение и собственную паузу, либо задать сеть инлайн-словарём вместо ссылки по имени:

engines:
  - name: google
    network:
      proxies: ~

Общий network: по-прежнему имеет смысл — например, чтобы держать один исходящий прокси для группы движков. Разумная граница: группируйте так только те движки, отказ которых вы готовы принимать вместе. У этого же пула соединений есть и собственная причина пустой выдачи — без единой капчи, разберём её дальше.

Параллельные запросы от ИИ-агента съедают пул соединений — без единой капчи

Есть третий вариант случая Б, не имеющий отношения ни к капче, ни к паузам из предыдущих разделов: пустая или урезанная выдача под нагрузкой, когда unresponsive_engines показывает timeout без приставки Suspended: . Причина — исходящие соединения SearXNG делит на пул, а не открывает по требованию бесконечно:

outgoing:
  pool_connections: 100
  pool_maxsize: 20

Один поисковый запрос уже открывает по соединению на каждый включённый движок — при пятнадцати активных это пятнадцать одновременных HTTPS-подключений. Если ИИ-агент шлёт несколько запросов почти одновременно, что для многошагового поиска обычное дело, счётчик подходит к pool_maxsize быстрее, чем кажется, и часть соединений ждёт свободное место вместо того, чтобы открыться сразу. Ожидание превысило request_timeout — движок для этого запроса не успел, и в unresponsive_engines это ляжет как timeout либо unexpected crash, безо всякой капчи.

Отличить причину от разделов 3–4 просто: Suspended повторяется одинаково на каждом запросе, пока не истечёт время, а нехватка пула плавает вместе с текущей параллельной нагрузкой. Проверка — сравнить одиночный запрос с параллельной пачкой:

for i in $(seq 1 8); do
  curl -s "http://127.0.0.1:8080/search?q=test$i&format=json" \
    | jq -c --arg n "$i" '{n: $n, unresponsive: (.unresponsive_engines|length)}' &
done; wait

Если у одиночного запроса unresponsive равен нулю, а у части из восьми параллельных — двум-трём, дело не в движках и не в IP, а в пуле. У именованной сети из раздела 4 может быть и собственный, более узкий max_connections, независимый от общих pool_connections/pool_maxsize. Поднимать пул стоит с оглядкой: больше параллельных соединений к одному источнику — это и более быстрый путь к too many requests из раздела 3.

Ещё три причины: старый образ, safe_search и мёртвый shortcut

Устаревший образ и разбор HTML. Движок в SearXNG — набор правил, как разобрать HTML- или JSON-страницу источника. Источники меняют вёрстку без предупреждения, и движок, который вчера работал, начинает отвечать parsing error — этим текстом помечены сразу SearxEngineXPathException, KeyError, JSONDecodeError и lxml.etree.ParserError. В отличие от капчи из раздела 3, у ошибки разбора нет записи в suspended_times: движок падает на каждом следующем запросе, а не уходит в паузу. Версия образа — docker inspect searxng --format '{{.Config.Image}}'; тег latest означает, что вы не знаете, какой код запущен сегодня. Разумная практика — фиксировать тег вида 2026.8.22-9fea412.

safe_search. Значение 2 (строгий фильтр) у некоторых движков в категории images способно обнулить выдачу на нейтральных с виду запросах:

curl -s 'http://127.0.0.1:8080/search?q=test&format=json&safesearch=0' | jq '.results | length'

Появились результаты с safesearch=0 — дело было в фильтре, а не в движках.

Диверсификация по категориям, а не наугад. Подсчёт движков на категорию из раздела 2 полезен и здесь: категории, где включено всего один-два движка, — тонкое место задолго до всякой капчи, там пауза даже одного из них из раздела 3 сразу обнуляет вкладку. Стоит осознанно добавить туда ещё пару из полутора сотен движков в поставке, а не рассчитывать, что дефолтные не откажут одновременно.

Какой сервер и локация под SearXNG брать в MAATRIX

Отсюда не самый очевидный вывод: пустоту от Suspended-пауз (разделы 3–4) более мощный сервер не чинит — обработка паузы стоит SearXNG буквально ничего. А вот пустоту из раздела 5, от нехватки пула, мощность снимает: больше памяти держит больше одновременных соединений и воркеров, не упираясь в pool_maxsize при первой же пачке запросов от агента.

Честный минимум: 1 vCPU, 1–2 ГБ RAM, 20 ГБ NVMe. Хватает и для обычной установки, и для инстанса, у которого половина движков время от времени сидит в Suspended, — пауза не грузит ни память, ни диск. Ограничение честное: на 1 ГБ нельзя поднимать UWSGI_WORKERS выше двух, а с одним-двумя воркерами пул из раздела 5 исчерпывается ещё быстрее.

Комфортный вариант: 2 vCPU, 4 ГБ RAM, 40 ГБ NVMe. Прибавка двойная: память на пятнадцать-двадцать включённых движков вместо стартовых пяти-семи (раздел 6) и запас в outgoing.pool_maxsize, поднимать который без упора в UWSGI_WORKERS — здесь можно. Если рядом планируется Perplexica или Open WebUI, память под них считайте отдельно — общая схема в статье как поднять свой ИИ-поиск на сервере, точный расчёт — в статье сколько RAM нужно для SearXNG и Perplexica.

Локация — Лондон (UK). На длительность пауз из раздела 3 и на нехватку пула из раздела 5 страна сервера не влияет напрямую — таймеры и лимиты одни и те же что в Лондоне, что в Москве. Но частота, с которой источники вообще выставляют капчу, тесно связана с репутацией диапазона IP-адресов, а датацентровые диапазоны в России в среднем грязнее западноевропейских: через них годами идёт весь обходной трафик, ради которого во многом и существует эта статья. Лондон — не гарантия обойтись без единой капчи, а способ реже упираться в тяжёлые строки из раздела 3. Франция подходит по той же логике; США — если рядом стоят зарубежные ИИ-инструменты, которым нужен именно американский адрес.

SearXNG есть в каталоге apps.maatrix.io и ставится автоматически при заказе — на Ubuntu и на Debian, без единой команды руками. Адрес интерфейса и доступы появляются в личном кабинете, в разделе «Доступ». Оплата — картой российского банка, по СБП, криптовалютой или токеном MAAT: сервер в Лондоне, а иностранная карта для заказа не нужна.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть SearXNG

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

unresponsive_engines пустой, и results тоже пустой — с чего начинать?

Это случай А из раздела 1: движки вообще не спрашивались. Проверьте curl -s http://127.0.0.1:8080/config | jq -r '.engines[] | select(.enabled) | .categories[]' | sort | uniq -c — ноль напротив нужной категории значит дело в keep_only, remove или в отключённых поодиночке движках.

Google, Bing и DuckDuckGo разом показывают Suspended: CAPTCHA — совпадение?

Почти наверняка нет. Проверьте network: у этих движков: если несколько ссылаются на один и тот же именованный network, они делят одну паузу на всех. Уберите network: у движка или замените ссылку по имени на инлайн-словарь.

Можно ли уменьшить suspended_times, чтобы движки быстрее возвращались в строй?

Технически да, значения переопределяются в settings.yml. Практически это чаще ухудшает картину: источник ставит капчу по своей оценке частоты запросов с вашего IP, а не по таймеру SearXNG, и более частые попытки обычно означают более частую капчу. Надёжнее увеличить число движков в категории и выбрать локацию с менее заметным IP-диапазоном.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.