Почему парсер получает 403 ошибку в Docker и как это исправить
Запуск веб-скрапинга с использованием undetected_chromedriver внутри Docker-контейнера часто приводит к мгновенной блокировке (403 Forbidden, Guru Meditation), даже если на хосте тот же скрипт работает идеально. Проблема не в IP-адресе, а в уникальных отпечатках контейнера, которые система защиты распознаёт как бота. В этой статье мы разберём причины блокировки и дадим пошаговое решение для Docker с Python 3.11, pyvirtualdisplay (Xvfb) и Chrome.
Причины блокировки в Docker
Даже после отключения AutomationControlled, подмены User-Agent и настройки локалей, контейнер выдаёт себя через:
- WebGL Vendor/Renderer - софтверный SwiftShader (отличается от реального GPU);
- deviceMemory и hardwareConcurrency - стандартные значения, выдающие виртуальную среду;
- Параметры экрана - отсутствие реального дисплея (Xvfb) оставляет следы;
- Заголовки запросов - порядок заголовков может отличаться от браузера реального пользователя.
Пошаговое решение для Docker
1. Настройка Dockerfile с Chrome и зависимостями
Установите все необходимые пакеты для работы Chrome и Xvfb:
FROM python:3.11-slim
RUN apt-get update && apt-get install -y \
wget gnupg unzip \
xvfb x11-utils \
fonts-liberation \
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
libcups2 libdrm2 libdbus-1-3 libxkbcommon0 \
libxcomposite1 libxdamage1 libxrandr2 \
libgbm1 libpango-1.0-0 libcairo2 \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google.list \
&& apt-get update && apt-get install -y google-chrome-stable \
&& rm -rf /var/lib/apt/lists/*2. Запуск Xvfb и настройка виртуального дисплея
Используйте pyvirtualdisplay для создания фейкового экрана с правильными параметрами:
from pyvirtualdisplay import Display
from undetected_chromedriver import Chrome, ChromeOptions
display = Display(visible=False, size=(1920, 1080))
display.start()
options = ChromeOptions()
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--window-size=1920,1080')
options.add_argument('--disable-gpu') # отключаем GPU, чтобы не было софтверного рендеринга
driver = Chrome(options=options)3. Маскировка отпечатков через CDP
После запуска драйвера примените следующие команды Chrome DevTools Protocol:
import random
# Подмена WebGL Vendor/Renderer
driver.execute_cdp_cmd('Emulation.setDeviceMetricsOverride', {
'width': 1920,
'height': 1080,
'deviceScaleFactor': 1,
'mobile': False,
'screenWidth': 1920,
'screenHeight': 1080,
})
# Подмена deviceMemory и hardwareConcurrency
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
'''
})
# Подмена User-Agent (реальный браузер)
driver.execute_cdp_cmd('Network.setUserAgentOverride', {
'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
})4. Добавление случайных задержек и движений мыши
Имитируйте поведение человека с помощью ActionChains:
from selenium.webdriver.common.action_chains import ActionChains
# Случайное движение мыши перед загрузкой страницы
action = ActionChains(driver)
action.move_by_offset(random.randint(100, 500), random.randint(100, 500))
action.perform()
time.sleep(random.uniform(0.5, 1.5))
driver.get('https://target-site.com')5. Проверка локалей и таймзоны
Убедитесь, что в Dockerfile или при запуске контейнера заданы правильные параметры. Добавьте в docker-compose или при запуске:
environment:
- TZ=Europe/Moscow
- LANG=ru_RU.UTF-8
- LANGUAGE=ru_RU:ru
- LC_ALL=ru_RU.UTF-8Альтернативный подход: без браузера
Если вам не нужен полноценный браузер, рассмотрите requests с подменой заголовков или playwright в headless-режиме. Playwright лучше маскируется в Docker из коробки. Пример с Playwright:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, args=['--no-sandbox'])
context = browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
viewport={'width': 1920, 'height': 1080}
)
page = context.new_page()
page.goto('https://target-site.com')
print(page.title())
browser.close()Заключение
Блокировка парсера в Docker возникает из-за отпечатков виртуальной среды. Основные шаги для решения: правильная настройка Xvfb, подмена WebGL и hardware-параметров через CDP, имитация пользовательского поведения. Если браузер не обязателен - используйте Playwright или requests с качественной маскировкой заголовков.