CrawlGuard

Блог

Боты нагружают сервер: что делать до покупки железа

· 9 мин чтения

  • нагрузка
  • nginx
  • limit_req
  • CAPTCHA

Сервер тормозит, load average растёт, воркеры PHP-FPM заняты, база отвечает медленнее обычного. Хостер предлагает тариф побольше. Прежде чем платить, стоит узнать, кому именно вы собираетесь покупать ресурсы: людям или скриптам. Нередко выясняется, что заметную часть нагрузки создают несколько ботов, и решить это дешевле конфигом, чем железом.

Сначала убедитесь, что это боты

Признаки подробно разобраны в статье о признаках парсинга; для задачи с нагрузкой хватит трёх проверок по access.log nginx. Первая — кто создаёт запросы:

awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

Вторая — что именно запрашивают. Нагрузку создают не все страницы одинаково: карточка товара из кэша почти ничего не стоит, а страница фильтра с двадцатью параметрами, поиск или сортировка по цене — это запрос к базе. Посмотрите самые частые URL, особенно те, что содержат параметры:

awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -30

Самый частый URL не обязательно самый дорогой: один тяжёлый отчёт может стоить больше тысячи карточек. Добавьте в log_format поля $request_time и $upstream_response_time и смотрите на суммарное время по URL, а не только на количество.

Третья — сравните почасовое число запросов с просмотрами в Метрике. Счётчик видит только тех, у кого выполнился его JavaScript; если запросов в разы больше, а ночью не меньше, чем днём, разница требует объяснения: чаще всего это автоматический трафик, но сначала исключите сбой счётчика и разные часовые пояса. Если же нагрузка пришла вместе с ростом визитов и совпадает с рекламной кампанией, боты не исключены, но начинать всё равно стоит с кэша и оптимизации: они помогают в обоих случаях.

Кэш: первое, что стоит сделать

Бот, который получает страницу из кэша, почти ничего не стоит: nginx отдаёт готовый ответ, не будя PHP и базу. Если у вас ещё нет кэша целых страниц для анонимных посетителей (fastcgi_cache или proxy_cache в nginx либо плагин кэширования в CMS), это самая выгодная мера из всех. Она не уменьшит число запросов, но уменьшит их цену. Кэшировать можно только обезличенные ответы: корзину, личный кабинет, страницы с индивидуальными ценами и всё, что зависит от сессии, в общий кэш класть нельзя, иначе один посетитель увидит данные другого.

Предел кэша — уникальные URL. Парсер, который перебирает комбинации фильтров и сортировок, каждым запросом промахивается мимо кэша и идёт в базу. Здесь помогает нормализация: игнорировать неизвестные параметры, ограничить глубину пагинации, закрыть бессмысленные сочетания фильтров. Это работа на стороне приложения, но она окупается и без ботов.

Лимит запросов в nginx

Директива limit_req ограничивает число запросов с одного адреса. Минимальная схема размещения директив (у вас блоки http и server уже есть — добавьте строки в них, а не копируйте файл целиком, и проверьте результат командой nginx -t):

http {
    limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;

    server {
        location / {
            limit_req zone=perip burst=20 nodelay;
            limit_req_status 429;
            # здесь остаётся ваша обычная обработка запросов
        }
    }
}

Что здесь важно. Зона perip хранит счётчики по адресам; десяти мегабайт хватает на десятки тысяч адресов. rate — устойчивая скорость, burst — сколько запросов сверх неё можно принять пачкой: браузер при открытии страницы делает много запросов сразу, и без burst вы начнёте резать людей. Статику лучше вынести в отдельный location без лимита или с более мягким. Код 429 честнее, чем 503 по умолчанию: он говорит клиенту, что дело в частоте.

Пределы этой меры: лимит считается на адрес. Офис, университет или мобильный оператор за одним NAT-адресом выглядят как один клиент, и слишком строгий rate ударит по ним. А парсер, который распределяет запросы по сотне адресов, под лимит не попадает вовсе. limit_req хорошо снимает самый грубый случай — один скрипт с одного сервера — и плохо всё остальное.

Блок по User-Agent

Простые скрипты представляются честно: python-requests, Go-http-client, curl, Scrapy. Их можно отсечь одной картой:

map $http_user_agent $bad_agent {
    default 0;
    ~*(python-requests|go-http-client|scrapy|curl/) 1;
    "" 1;
}
server {
    if ($bad_agent) { return 403; }
}

Это дёшево, но не безвредно: под curl, python-requests и пустой User-Agent попадают и ваши собственные интеграции, мониторинг, клиенты API. Сначала проверьте по логу, кто с такими строками к вам ходит, и применяйте фильтр к публичным разделам, а не ко всему сайту. Пределы очевидны: User-Agent — строка, которую клиент выбирает сам. Как только автор скрипта заметит 403, он подставит строку настоящего Chrome, и на этом всё закончится. Ещё одна ловушка — блокировать по подстроке bot: под неё попадут Googlebot и YandexBot, и вы выпадете из поиска. Поисковых роботов проверяют не по строке, а по подтверждённому обратному DNS: PTR-имя адреса должно принадлежать домену поисковика, а прямое разрешение этого имени — вернуть тот же адрес. Одного PTR мало, его можно подделать.

Блок подсетей и почему это гонка

Если подозрительные запросы идут из одной подсети хостинга, соблазн велик: deny 203.0.113.0/24; — и тишина. На день. Дальше начинается гонка, которую вы не выиграете: парсеры работают через прокси, включая адреса домашних и мобильных операторов, и меняют их быстрее, чем вы обновляете список. Каждый добавленный диапазон — риск закрыть дорогу реальным людям: за адресом хостинга может стоять корпоративный VPN, за адресом оператора — тысячи абонентов. Блок подсети уместен как точечная мера против конкретного источника, но не как система.

Почему CAPTCHA везде — плохая идея

Первая мысль после всего перечисленного — поставить CAPTCHA на вход. Не стоит, по трём причинам. Первая — она бьёт по всем: каждый посетитель, включая того, кто пришёл с рекламы и уже почти положил товар в корзину, получает задачу с картинками. Часть просто уйдёт, на телефоне — особенно. Вторая — она не останавливает целеустремлённого парсера: сервисы распознавания CAPTCHA существуют и стоят недорого. Третья — доступность: людям с нарушениями зрения и на слабых устройствах CAPTCHA даётся тяжело. Она уместна на действиях, а не на просмотрах: регистрация, восстановление пароля, отправка формы, подозрительная попытка входа.

Проверка браузера перед сервером

Есть промежуточный вариант между «ничего не проверять» и «спрашивать всех». Перед сайтом ставится прокси, который смотрит на каждого посетителя. У кого есть действующая cookie доверия — проходит без задержки. У кого нет — получает лёгкую страницу без картинок и кнопок: браузер решает на ней вычислительную задачу в JavaScript, отправляет ответ, и сервер ставит подписанную cookie на заданный срок. Человек видит короткую страницу «проверяем браузер», а простой HTTP-клиент, который не умеет пройти проверку, до защищённых страниц не добирается: его запросы отклоняются на прокси и не доходят до приложения. Нагрузка от таких клиентов исчезает, а не перераспределяется. Это проверка клиента, а не доказательство, что за ним человек.

Важные свойства такой проверки: решение принимает сервер, а не JavaScript на странице; поисковые роботы подтверждаются по обратному DNS с обратной проверкой имени и пропускаются; для ваших интеграций, вебхуков и API нужны исключения, иначе они тоже упрутся в проверку. Есть и цена: задержка при первом визите и лишняя работа на слабых устройствах.

Когда она оправдана: каталог или прайс, который парсят систематически; нагрузка, распределённая по многим адресам, где limit_req не помогает; сайт, где CAPTCHA на входе недопустима. Когда не оправдана: один скрипт с одного адреса (хватит limit_req), сайт-API без браузерных посетителей, маленький сайт без заметной нагрузки — там достаточно кэша.

Честно про пределы

Проверка браузера отсекает скрипты без JavaScript — это основная масса, но не все. Парсер на настоящем браузере под управлением Playwright или Puppeteer задачу решит. Наивные сборки выдают себя признаками автоматизации (например, флагом navigator.webdriver или программным рендером WebGL), и их можно ловить дополнительными сигналами; тщательно замаскированные проходят. Это повышение цены парсинга, а не стена: цель — сделать так, чтобы обходить ваш сайт стало дороже, чем он того стоит парсеру.

Порядок действий

  1. Убедиться по логам, что нагрузку создают боты, а не люди.
  2. Включить кэш страниц для анонимных посетителей и починить самые тяжёлые URL.
  3. Поставить limit_req с разумным burst и карту очевидных User-Agent.
  4. Если нагрузка распределена по адресам и меры выше не помогли — проверка браузера перед сервером.
  5. Покупать железо только после этого, если оно всё ещё нужно.

Один из вариантов для четвёртого шага — CrawlGuard: проверка браузера без CAPTCHA; роботы Яндекса, Google, Bing, Mail.ru, DuckDuckGo и Apple пропускаются; подключение через DNS; тариф Free — один сайт и миллион запросов в месяц без ограничения срока.