SearXNG выдаёт пустую выдачу: причины и решение
Сервис отвечает 200, JSON собирается без единой ошибки в логе, а в поле results — пустой массив: SearXNG не ищет, хотя формально жив и здоров. Почти всегда дело не в конфиге, который вы правили последним, — какой-то движок мог замолчать сам, на час, на сутки или на две недели, и вы об этом не узнаете, если не туда посмотреть. Разберём главные механизмы пустой выдачи — с точными текстами ошибок, командами диагностики и строками конфига, без пересказа установки и общих ошибок SearXNG.
Содержание
- Пустая выдача — это два разных диагноза, а не один
- Ноль движков в категории: молчаливый keep_only и опечатка в имени
- Как читать unresponsive_engines: словарь ошибок и что значит Suspended
- Один network на несколько движков — как чужой бан гасит и ваш
- Параллельные запросы от ИИ-агента съедают пул соединений — без единой капчи
- Ещё три причины: старый образ, safe_search и мёртвый shortcut
- Какой сервер и локация под SearXNG брать в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество 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 — десятки моделей в одном окне. Оплата картой РФ и по СБП.