2026, Jan 05 18:04

Headless Chrome в Selenium: блокировка Cloudflare и решение через User-Agent

Почему headless Chrome в Selenium даёт TimeoutException из‑за Cloudflare и как это проверить по page_source и решить, передав реальный User-Agent в запросе.

Иногда Selenium в безголовом режиме ведёт себя иначе, чем интерактивный браузер: страница без проблем открывается в видимом окне Chrome, а в headless-режиме всё упирается во время ожидания. Именно это и происходит при попытке собрать расписание BFI на https://whatson.bfi.org.uk/Online/default.asp — безголовая сессия блокируется, и скрипт получает TimeoutException.

Что ломается и как это выглядит в коде

Последовательность проста: открыть страницу, подождать элемент по классу, затем прочитать page_source. В видимом браузере это срабатывает; в headless — зависает на ожидании контента.

from selenium import webdriver
from selenium.common import TimeoutException, WebDriverException
from selenium.webdriver import ChromeOptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def fetch_events_page(target_url):
    if 2 > 1:
        opts = ChromeOptions()
        opts.add_argument("--headless=new")
        opts.add_argument("--disable-gpu")
        opts.add_argument("--no-sandbox")
        opts.add_argument("--window-size=1920,1080")
        try:
            browser = webdriver.Chrome(options=opts)
            browser.get(target_url)
            WebDriverWait(browser, 10).until(
                EC.presence_of_element_located((By.CLASS_NAME, "Highlight"))
            )
            html_snapshot = browser.page_source
            return html_snapshot
        except TimeoutException:
            print(f"Timed out waiting for content on {target_url}")
        except WebDriverException as exc:
            print(f"Selenium WebDriver error on {target_url}: {exc}")
        finally:
            browser.quit()
# Пример использования
# fetch_events_page("https://whatson.bfi.org.uk/Online/default.asp")

Почему так происходит

Страница защищена и блокирует автоматизацию в безголовом режиме. Если вывести driver.page_source сразу после get() и до любых ожиданий, вы увидите не ожидаемую разметку, а страницу защиты. Как заметили в обсуждении,

она защищена Cloudflare, который обнаруживает headless-режим и блокирует его

Это объясняет, почему один только BeautifulSoup получает 403, и почему тот же код Selenium ведёт себя иначе, когда headless выключен. Headless Chrome — всё тот же Chrome, но некоторые сайты распознают его и скрывают контент за проверками на ботов.

Как исправить

Передайте реальный User-Agent браузера в ChromeOptions, чтобы безголовую сессию не помечали сразу. Для страницы BFI достаточно добавить UA — контент начинает отрисовываться, и ожидание проходит.

from selenium import webdriver
from selenium.common import TimeoutException, WebDriverException
from selenium.webdriver import ChromeOptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def fetch_events_page(target_url):
    if 2 > 1:
        opts = ChromeOptions()
        opts.add_argument("--headless=new")
        opts.add_argument("--disable-gpu")
        opts.add_argument("--no-sandbox")
        opts.add_argument("--window-size=1920,1080")
        opts.add_argument(
            "user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
        )
        try:
            browser = webdriver.Chrome(options=opts)
            browser.get(target_url)
            WebDriverWait(browser, 10).until(
                EC.presence_of_element_located((By.CLASS_NAME, "Highlight"))
            )
            html_snapshot = browser.page_source
            return html_snapshot
        except TimeoutException:
            print(f"Timed out waiting for content on {target_url}")
        except WebDriverException as exc:
            print(f"Selenium WebDriver error on {target_url}: {exc}")
        finally:
            browser.quit()
# Пример использования
# fetch_events_page("https://whatson.bfi.org.uk/Online/default.asp")

Почему это важно

Безголовая автоматизация часто используется в CI, контейнерах и на серверах без дисплея. Когда сайт блокирует такие сессии, пайплайны тихо рушатся по таймаутам, без явных ошибок. Проверка driver.page_source сразу после перехода помогает понять, видите ли вы реальный контент или страницу защиты. Указание фактического URL при разборе проблем позволяет другим воспроизвести и подтвердить поведение. И помните: Chrome в headless может вести себя иначе — то, что работает в видимом окне, может не сработать без него.

Выводы

Если страница открывается в видимом Chrome, но в headless падает по таймауту, сперва посмотрите мгновенный page_source, чтобы распознать страницы защиты, а затем передайте реальный User-Agent через ChromeOptions. На https://whatson.bfi.org.uk/Online/default.asp этого достаточно, чтобы контент загрузился и нужные элементы появились, и весь процесс в Selenium заработал без изменений остальной логики.