При автоматизации работы с веб-сайтами через n8n вы неизбежно столкнётесь с ресурсами, защищёнными системами вроде Wallarm или Cloudflare. Эти системы блокируют стандартные HTTP-запросы, определяя их как ботов. В этой статье я подробно разберу, как создать надёжное, полностью бесплатное и self-hosted решение на базе Docker и Playwright для n8n, которое позволит обходить даже продвинутую JavaScript-защиту.
🤔 В чём проблема?
Когда вы отправляете запрос из стандартного нода HTTP Request в n8n на защищённый сайт, вы часто получаете не данные, а:
- HTML-страницу с ошибкой
Forbiddenи вашим IP-адресом. - Страницу-заглушку с JavaScript-проверкой (спиннеры, редиректы, капчи).
- Пустой ответ или код
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
- Добавьте нод HTTP Request в ваш workflow.
- Настройте его:
- Method:
POST - URL:
http://playwright:3000/execute(используем имя сервиса Docker!) - Headers:
Content-Type: application/json - Body (JSON):
- Method:
{
"url": "https://target-site.com/protected-page",
"script": "() => { return document.querySelector('.data')?.innerText; }"
}
- Обработайте ответ: В последующих нодах проверяйте поле
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"}'
⚠️ Важные предупреждения
- Законность: Убедитесь, что ваши действия не нарушают Условия использования сайта и законодательство.
- Этика: Не перегружайте целевые сайты запросами. Добавляйте задержки между запросами.
- Производительность: Запуск браузера — ресурсоёмкая операция. Оптимально обрабатывайте 5-10 параллельных запросов на ядро CPU.
- Обновления: Регулярно обновляйте образ Playwright, так как защита сайтов постоянно совершенствуется.
🎉 Заключение
Вы создали мощный, автономный инструмент для обхода сложной веб-защиты в n8n. Это решение:
✅ Полностью бесплатное и под вашим контролем.
✅ Масштабируемое (можно запускать несколько контейнеров).
✅ Гибкое (легко добавлять новую логику).
✅ Надёжное (изолировано от основной системы n8n).
Теперь вы можете автоматизировать сбор данных даже с самых защищённых ресурсов, расширяя возможности ваших n8n workflows. Удачи в автоматизации!
Статья основана на реальном опыте внедрения. Все примеры кода проверены на работоспособность. Технологии постоянно развиваются — делитесь вашими улучшениями и находками в комментариях!