Обход сложной защиты сайтов в n8n: Self-hosted решение на Playwright без Browserless

5 мин.

При автоматизации работы с веб-сайтами через n8n вы неизбежно столкнётесь с ресурсами, защищёнными системами вроде Wallarm или Cloudflare. Эти системы блокируют стандартные HTTP-запросы, определяя их как ботов. В этой статье я подробно разберу, как создать надёжное, полностью бесплатное и self-hosted решение на базе Docker и Playwright для n8n, которое позволит обходить даже продвинутую JavaScript-защиту.

🤔 В чём проблема?

Когда вы отправляете запрос из стандартного нода HTTP Request в n8n на защищённый сайт, вы часто получаете не данные, а:

  1. HTML-страницу с ошибкой Forbidden и вашим IP-адресом.
  2. Страницу-заглушку с JavaScript-проверкой (спиннеры, редиректы, капчи).
  3. Пустой ответ или код 403/429.

Это происходит потому, что:

  • Защита анализирует HTTP-заголовки (например, User-Agent).
  • Проверяет наличие и поведение JavaScript-движка.
  • Ищет следы автоматизации (webdriver флаги, типичные для headless-браузеров).
  • Использует отпечаток браузера (fingerprinting).

Прямое использование puppeteer или playwright внутри нода Code n8n невозможно из-за политик безопасности самого n8n.

🏗️ Архитектура решения

Вместо того чтобы пытаться «впихнуть» браузер в n8n, мы вынесем его в отдельный микросервис. Это правильный архитектурный подход.

Схема работы:

sequenceDiagram
    participant N as n8n Workflow
    participant P as Playwright Server
    participant B as Browser
    participant W as Website

    N->>+P: HTTP POST /execute {url, script}
    Note over P: Запускает браузер в стелс-режиме
    P->>+B: Запрос на создание страницы
    B->>+W: GET / (с реальными заголовками)
    W-->>-B: HTML (возможно, с проверкой JS)
    Note over B,W: Выполняется JavaScript,<br>ожидание элементов
    B-->>-P: Данные страницы (HTML/результат скрипта)
    P-->>-N: JSON ответ {success, data}

Преимущества:

  • Полный контроль: Вы сами управляете версиями и настройками.
  • Бесплатно: Никаких лимитов или подписок, как в Browserless.
  • Гибкость: Можно добавить любую логику для обхода конкретной защиты.
  • Изоляция: Падение браузера не затронет ваши основные workflows n8n.

📦 Шаг 1: Создаём сервер Playwright

Создайте директорию playwright-server рядом с вашим docker-compose.yml.

1. Файл package.json:

{  
  "name": "playwright-api-server",  
  "version": "1.0.0",  
  "main": "server.js",  
  "scripts": { "start": "node server.js" },  
  "dependencies": {  
    "express": "^4.18.2",  
    "playwright": "^1.57.0"  
  }  
}

2. Файл Dockerfile:

FROM mcr.microsoft.com/playwright:v1.57.0-noble
WORKDIR /app
# Копируем файлы зависимостей
COPY package.json .
RUN npm install
# Копируем исходный код сервера
COPY server.js .
# Открываем порт сервера
EXPOSE 3000
# Команда для запуска
CMD ["npm", "start"]

3. Файл server.js (основная логика):

const express = require('express');
const { chromium } = require('playwright');

const app = express();
app.use(express.json());
const PORT = process.env.PORT || 3000;

// Основной эндпоинт для выполнения скрипта
app.post('/execute', async (req, res) => {
  const { url, script, options = {} } = req.body;
  let browser = null;

  try {
    // Запуск браузера (можно добавить аргументы для стелс-режима)
    browser = await chromium.launch({
    headless: true,
    args: [
        '--no-sandbox',
        '--disable-blink-features=AutomationControlled', // Ключевой аргумент
        '--disable-dev-shm-usage',
        '--disable-web-security',
        '--disable-features=IsolateOrigins,site-per-process',
        '--start-maximized'
    ]
    });

    const context = await browser.newContext({
        viewport: { width: 1920, height: 1080 },
        userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        locale: 'ru-RU',
        timezoneId: 'Europe/Moscow',
        // Переопределяем свойства, по которым сайты определяют автоматизацию
        permissions: ['geolocation'],
        geolocation: { latitude: 55.7558, longitude: 37.6173 }, // Координаты Москвы
        colorScheme: 'light',
        // Эмулируем наличие плагинов
        extraHTTPHeaders: {
            'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
            'Sec-CH-UA': '"Not_A Brand";v="8", "Chromium";v="120"',
            'Sec-CH-UA-Platform': '"Windows"',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Upgrade-Insecure-Requests': '1'
        }
    });

    // Критически важный шаг: удаляем свойство "webdriver" из объекта navigator
    await context.addInitScript(() => {
        Object.defineProperty(navigator, 'webdriver', { get: () => false });
        Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3] });
        window.chrome = { runtime: {} };
    });

    const page = await context.newPage();

    // 1. ПОДПИСЫВАЕМСЯ НА ЗАПРОСЫ И ОТВЕТЫ (Очень важно для отладки)
    page.on('response', async response => {
      const url = response.url();
      // Логируем только ключевые запросы к домену защиты
      if (url.includes('servicepipe.ru') || url.includes('alfabank.ru')) {
        console.log(`← ${response.status()} ${url}`);
      }
    });
    page.on('request', request => {
      const url = request.url();
      if (url.includes('servicepipe.ru')) {
        console.log(`→ ${request.method()} ${url}`);
      }
    });

    // 2. ПЕРЕХОДИМ НА СТРАНИЦУ и ждём СЕТЬ, а не только загрузку
    await page.goto(url, {
      waitUntil: 'networkidle', // Ждём, когда прекратятся сетевые запросы
      timeout: 45000
    });

    // 3. ДОЖИДАЕМСЯ ИСЧЕЗНОВЕНИЯ СПИННЕРА ЗАГРУЗКИ
    // Защита показывает спиннер, пока идёт проверка
    try {
      await page.waitForSelector('#id_spinner', { state: 'hidden', timeout: 15000 });
      console.log('Спиннер проверки исчез.');
    } catch (e) {
      console.log('Спиннер не исчез или не найден.');
    }

    // 4. ДОПОЛНИТЕЛЬНОЕ ОЖИДАНИЕ И ПРОВЕРКА СОДЕРЖИМОГО
    // Ждём появления какого-либо осмысленного контента, а не заглушки
    await page.waitForTimeout(3000); // Краткая пауза после исчезновения спиннера
    const isRealContent = await page.evaluate(() => {
      const bodyText = document.body.innerText;
      // Проверяем, что на странице НЕТ ключевых слов заглушки
      return !bodyText.includes('servicepipe.ru') &&
            !bodyText.includes('spinner') &&
            bodyText.length > 500; // И что текст достаточно большой
    });

    let result = { html: '', url: page.url() };

    if (isRealContent) {
      // 5. ЕСЛИ КОНТЕНТ НАСТОЯЩИЙ — получаем HTML
      result.html = await page.content();
      console.log('Успех: Загружена реальная страница.');
    } else {
      // 6. ЕСЛИ НЕТ — делаем скриншот для отладки и сохраняем сырой HTML
      await page.screenshot({ path: '/tmp/debug_captcha.png', fullPage: true });
      result.html = await page.content();
      result.debug_note = 'Возможно, требуется капча или проверка не пройдена. Скриншот сохранён.';
      console.log('Внимание: Страница, вероятно, всё ещё показывает проверку.');
    }

    // 7. ВЫПОЛНЯЕМ ПОЛЬЗОВАТЕЛЬСКИЙ СКРИПТ (если передан)
    if (script) {
      result.custom = await page.evaluate(script);
    }

    res.json({ success: true, data: result });
  } catch (error) {
    console.error('Execution error:', error);
    res.status(500).json({ success: false, error: error.message });
  } finally {
    if (browser) await browser.close();
  }
});

// Эндпоинт для проверки здоровья
app.get('/health', (req, res) => res.send('OK'));

app.listen(PORT, () => console.log(`Playwright API server running on port ${PORT}`));

🐳 Шаг 2: Интеграция в существующий стек Docker

Добавьте в ваш docker-compose.yml новый сервис:

  playwright:
    build: ./playwright-server
    restart: unless-stopped
    networks:
      - default  # Подключаем к той же сети, что и n8n
    # Важно: не открывайте порт наружу, только для внутренней сети
    expose:
      - "3000"
    # Оптимизация для Playwright в контейнере
    environment:
      - PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
    # Для улучшения производительности и стабильности
    shm_size: '2gb'
    cap_add:
      - SYS_ADMIN
    volumes:
      - playwright_cache:/home/pwuser/.cache/ms-playwright

# Не забудьте добавить том в секцию volumes:  
volumes:  
playwright_cache:

Соберите и запустите:

docker compose build playwright  
docker compose up -d playwright

🔗 Шаг 3: Настройка Workflow в n8n

  1. Добавьте нод HTTP Request в ваш workflow.
  2. Настройте его:
    • Method: POST
    • URL: http://playwright:3000/execute (используем имя сервиса Docker!)
    • Headers: Content-Type: application/json
    • Body (JSON):
{  
"url": "https://target-site.com/protected-page",  
"script": "() => { return document.querySelector('.data')?.innerText; }"  
}

  1. Обработайте ответ: В последующих нодах проверяйте поле success и работайте с html или customData.

🎯 Продвинутые техники обхода защиты

Если базовая настройка не сработала, используйте следующие методы:

1. Эмуляция поведения человека

Добавьте в server.js случайные задержки и движения мыши:

// Случайная пауза между действиями  
await page.waitForTimeout(Math.random() * 3000 + 1000);

// Эмуляция движения мыши  
await page.mouse.move(100, 100);  
await page.mouse.move(200, 200, { steps: 5 });

2. Использование прокси

Если ваш IP заблокирован, добавьте прокси в контекст браузера:

const context = await browser.newContext({  
proxy: {  
server: 'http://proxy:port',  
username: 'user',  
password: 'pass'  
}  
});

3. Работа с куки и сессиями

Для авторизации сохраняйте контекст между запросами:

// Сохранение куки  
const cookies = await context.cookies();  
// ... сохранение в БД или файл ...

// Восстановление в новом контексте  
await context.addCookies(cookies);

4. Обработка капчи

При появлении капчи можно:

  • Использовать сервисы распознавания (Anti-Captcha, 2Captcha).
  • Сохранять скриншот для ручного ввода:
await page.screenshot({ path: '/tmp/captcha.png' });

🔍 Отладка и мониторинг

Проверка связи между контейнерами:

docker compose exec n8n curl -I http://playwright:3000/health

Просмотр логов Playwright:

docker compose logs playwright --tail=50 --follow

Прямой тест сервера:

curl -X POST http://localhost:3000/execute \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com"}'

⚠️ Важные предупреждения

  1. Законность: Убедитесь, что ваши действия не нарушают Условия использования сайта и законодательство.
  2. Этика: Не перегружайте целевые сайты запросами. Добавляйте задержки между запросами.
  3. Производительность: Запуск браузера — ресурсоёмкая операция. Оптимально обрабатывайте 5-10 параллельных запросов на ядро CPU.
  4. Обновления: Регулярно обновляйте образ Playwright, так как защита сайтов постоянно совершенствуется.

🎉 Заключение

Вы создали мощный, автономный инструмент для обхода сложной веб-защиты в n8n. Это решение:

✅ Полностью бесплатное и под вашим контролем.
✅ Масштабируемое (можно запускать несколько контейнеров).
✅ Гибкое (легко добавлять новую логику).
✅ Надёжное (изолировано от основной системы n8n).

Теперь вы можете автоматизировать сбор данных даже с самых защищённых ресурсов, расширяя возможности ваших n8n workflows. Удачи в автоматизации!


Статья основана на реальном опыте внедрения. Все примеры кода проверены на работоспособность. Технологии постоянно развиваются — делитесь вашими улучшениями и находками в комментариях!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *


Срок проверки reCAPTCHA истек. Перезагрузите страницу.