וואלהכביש 6 נחסם? עומסי תנועה חריגים בעקבות רכב שעלה באשESPN🏈 Fantasy Football: Start Bucs, sit Mahomes, more Week 1 decisionsESPN DeportesNo hubo quinto malo para Marlins, agrega otra rayitaRTP DesportoArsenal vence Sunderland e tem dia perfeito na Liga inglesaThe Hollywood ReporterLeaked Report Details Fallout if Paramount Follows Through on California Exit ThreatCollider5 Essential Stephen King Books for BeginnersSouth China Morning PostCeline Dion makes long-awaited return to stage in Parisn-tvTherapeut liebt "Friends"-Star : Jim Curtis spürt mit Jennifer Aniston "echte Freude"Mexico News DailyMND Tutor | Tamales and tomatoesBellaNaijaTaylor Townsend & Kateřina Siniaková Make History With US Open Women’s Doubles TriumphХабрАрхитектура универсального провайдера баз данных на TypeScript без JDBCSRF NewsWegen Sicherheitsbedenken – ChatGPT-Entwickler OpenAI verschiebt Börsengang
The Daily Newsstand · Free, Always
Saturday, September 12, 2026

Когда антибот притворяется РКН: ложные заглушки и локальный Web UI в rkn-block-checker 0.6.0

Translate

Проблема: Когда Авито притворяется Роскомнадзором В белом списке (контрольные сайты, которые должны работать всегда: Госуслуги, Яндекс и тд.) внезапно загорелся красным Авито:

avito   ✗ HTTP_STUB    HTTP 429 (37ms)
  └ response body matches a known ISP stub-page marker

Скрипт на полном серьезе утверждал: “На Авито висит заглушка провайдера о блокировке РКН, уверенность: HIGH” Откуда на Авито взялась заглушка РКН? Всё дело в наивном поиске подстрок. В коде был массив маркеров STUB_MARKERS, характерных для страниц провайдеров:

STUB_MARKERS = (
    "доступ ограничен",
    "решению роскомнадзора",
    "заблокирован",
    "единый реестр",
    # ...
)

Авито известен защитой от авто парса. Увидев сетевой запрос без накопленных кук от стандартной библиотеки (даже несмотря на имитацию Chrome User-Agent), WAF/антибот выдал стандартный статус 429 Too Many Requests.

А на самой HTML-странице ошибки 429 крупным шрифтом было написано: «Доступ с вашего IP временно ограничен». Скрипт скачал тело ответа, нашел вхождение подстроки “доступ ограничен” и радостно сообщил пользователю, что ресурс заблокирован цензурой на магистрали

Решение: Провайдерские заглушки в России имеют характерную особенность: они почти всегда маскируются под статус 200 OK (перехват с подменой тела) либо возвращают специализированный статус 451 Unavailable For Legal Reasons. HTTP 429 (Rate Limit) и 403 с капчей - это реакция самого целевого сервера, а не фильтра ТСПУ на магистрали. В core.py пришлось явно развести понятия цензуры и антибот-защиты:

    if http_mod.looks_like_stub(probe.body_snippet):
        if res.status_code == 429:
            res.verdict = Verdict.DOWN
            res.confidence = Confidence.LOW
            res.notes.append("http 429 (antibot rate limit) contained words similar to a block stub")
            return res

        res.verdict = Verdict.HTTP_STUB
        res.confidence = Confidence.HIGH
        res.notes.append(
            "response body matches a known ISP stub-page marker"
        )
        return res

Web UI: Зачем консольной утилите браузер?

Изначально проект задумывался строго как CLI. Но когда начинаешь разбирать спорные кейсы (“почему конкретный сервис то работает, то нет”), сухого текста в консоли становится мало. Хочется:

  1. Кликнуть по сайту и увидеть детальный таймлайн: на сколько миллисекунд задержался TCP, как ответил TLS, что именно вернул DNS.

  2. Дать инструмент тем, кто не привык работать в терминале.

  3. Добавлять кастомные домены прямо на лету и перепроверять их в один клик.

Тащить ради этого тяжелые зависимости вроде FastAPI, Flask или Uvicorn не хотелось категорически. Инструмент должен ставиться за секунду через pip install в чистый системный Python.

Поэтому веб-интерфейс был написан максимально легковесно:

  • Бэкенд: встроенный http.server.ThreadingHTTPServer из стандартной библиотеки Python.

  • Фронтенд: один файл index.html на ванильном JS и CSS без сборщиков, node_modules и внешних CDN.

Запускается одной командой:

rkn-check startweb
или
rkn-check startweb --port 8080

Сервер по умолчанию слушает 127.0.0.1:7777. Как устроен реалтайм без WebSocket Чтобы интерфейс не висел мертвым во время проверки нескольких десятков сайтов, результаты должны прилетать по мере готовности (как в CLI через as_completed()). Вместо того чтобы городить WebSocket или постоянный polling, бэкенд отдает результаты обычным HTTP-стримингом в формате NDJSON (Newline Delimited JSON):

self.send_response(200)
self.send_header("Content-Type", "application/x-ndjson; charset=utf-8")
self.end_headers()

for res in iter_check_urls(targets, max_workers=workers, timeout=timeout):
    line = json.dumps(res.to_dict()) + "\n"
    self.wfile.write(line.encode("utf-8"))
    self.wfile.flush() # Выталкиваем чанк в сокет сразу

А в браузере стандартный fetch читает поток чанков через ReadableStreamDefaultReader:

const response = await fetch('/api/scan', { method: 'POST', body: JSON.stringify(payload) });
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    
    buffer += decoder.decode(value, { stream: true });
    let lines = buffer.split('\n');
    buffer = lines.pop(); // Неполную строку оставляем в буфере

    for (let line of lines) {
        if (!line.trim()) continue;
        const result = JSON.parse(line);
        updateTableRow(result); // Обновляем конкретную строчку в таблице на лету
    }
}

Что в итоге (релиз 0.6.0)

В версию 0.6.0 вошли:

  1. Полноценный локальный Web UI (rkn-check startweb) с графиками, мультиязычностью (RU/EN) и ручным добавлением произвольных URL

  2. Фильтрация ложных срабатываний на антибот-заглушки (HTTP 429)

  3. Возможность экспорта полного отчета в JSON прямо из браузера в один клик

Код по-прежнему полностью открыт под MIT:

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.