Создание собственного инструмента для сетевой разведки — задача, с которой рано или поздно сталкивается каждый, кто интересуется безопасностью и автоматизацией сбора данных. В этой статье я расскажу, как прошёл путь от простого синхронного скрипта, который то и дело натыкался на блокировки и ошибки 403, до асинхронного пула потоков и базы данных SQLite с решением проблемы параллельной записи. Материал будет полезен тем, кто хочет понять практические аспекты разработки масс-сканеров и OSINT-комбайнов, не углубляясь в избыточную теорию.

Этап 1: Скрипт «в лоб» — просто, но медленно и уязвимо

Начинать всегда стоит с простого. Первая версия инструмента представляла собой последовательный перебор списка URL с помощью библиотеки requests. Для каждого адреса выполнялся GET-запрос, и результат сохранялся в текстовый файл. Такой подход понятен новичку, но быстро выявляет ограничения: скорость обработки сотен адресов оставляет желать лучшего, а при встрече с сайтами, которые блокируют подозрительные запросы, скрипт получает ошибки 403 или вовсе зависает.

Типичные ошибки на этом этапе:

  • Отсутствие таймаутов — запрос может висеть бесконечно, если сервер не отвечает.
  • Игнорирование заголовка User-Agent — многие сайты отсекают запросы без него или с явно автоматизированными значениями.
  • Отсутствие обработки исключений — одна ошибка соединения останавливает весь процесс.
  • Запись результатов в файл без структуры — потом сложно анализировать.

Чтобы сделать скрипт устойчивее, добавьте таймауты, случайные User-Agent из списка и обработку исключений с логированием. Но главное — задумайтесь о параллелизме, ведь последовательная обработка не масштабируется.

Этап 2: Переход к многопоточности с ThreadPoolExecutor

Следующий шаг — использование пула потоков из модуля concurrent.futures. ThreadPoolExecutor позволяет одновременно обрабатывать несколько URL, что значительно ускоряет сканирование. Важно правильно выбрать количество потоков: слишком мало — не получите прироста, слишком много — рискуете получить блокировку по IP или исчерпать ресурсы. Оптимально начинать с 10–20 потоков, увеличивая при необходимости.

При работе с потоками нужно учитывать:

  • Потоки в Python не дают истинного параллелизма из-за GIL, но для сетевых запросов, где основное время уходит на ожидание ввода-вывода, это не проблема.
  • Необходимо ограничивать частоту запросов, чтобы не провоцировать сервер на блокировку. Используйте задержки между запросами или семафоры.
  • Обработка ошибок должна быть потокобезопасной — например, счётчики успехов и неудач стоит защищать блокировками или использовать атомарные операции.

Пример простого пула: создаётся список URL, затем с помощью executor.map или submit запускаются задачи. Результаты по мере готовности можно складывать в список или сразу писать в базу.

Этап 3: Хранение результатов в SQLite и борьба с «database is locked»

Когда данных становится много, текстовые файлы перестают быть удобными. На помощь приходит SQLite — лёгкая встраиваемая база, не требующая отдельного сервера. Однако при параллельной записи из нескольких потоков вы неизбежно столкнётесь с ошибкой «database is locked». Причина в том, что SQLite блокирует базу на время записи, и другие потоки получают отказ.

Решения проблемы:

  • Использовать одно соединение с базой и сериализовать записи через очередь или блокировку (threading.Lock).
  • Применять режим WAL (Write-Ahead Logging), который позволяет читателям и писателям не блокировать друг друга. Включается командой PRAGMA journal_mode=WAL;.
  • Увеличить таймаут ожидания блокировки при подключении: sqlite3.connect('db.sqlite', timeout=10).
  • Разделить потоки: один поток-писатель, остальные — читатели (если логика позволяет).

На практике удобно собирать результаты в очередь и записывать их в базу из отдельного потока или после завершения всех задач. Такой подход упрощает обработку ошибок и снижает нагрузку на базу.

Этап 4: Обход блокировок и повышение отказоустойчивости

Даже с пулом потоков инструмент может наткнуться на защиту сайтов. Чтобы снизить риск блокировки, используйте:

  • Ротацию User-Agent и других заголовков.
  • Задержки между запросами (например, случайная задержка 0.5–2 секунды).
  • Прокси-серверы (если есть легальный доступ).
  • Обработку кодов ответа: при 403 или 429 делать паузу и повторять попытку с увеличенным интервалом.

Также стоит предусмотреть логирование всех ошибок и сохранение промежуточных результатов, чтобы при сбое не начинать заново. Например, можно записывать в базу статус каждого URL и при перезапуске пропускать уже обработанные.

Заключение: что дальше?

Построив инструмент от простого скрипта до многопоточного приложения с базой данных, вы получаете основу для дальнейшего развития. Следующие шаги могут включать добавление асинхронности на asyncio для ещё большей производительности, интеграцию с другими источниками OSINT, создание простого веб-интерфейса для управления сканированием. Главное — помнить о легальности и этичности: сканируйте только те ресурсы, на которые у вас есть разрешение. Удачи в разработке!