Cloudflare во многом напоминает обратный прокси, но только не для клиентов, а для самих сайтов. Это продвинутый CDN-сервис, который одновременно может балансировать нагрузку, а также защищать сайты от DDoS-атак, распознавать ботов, работать как firewall и обеспечивать безопасный доступ к DNS.
В этой статье расскажем о наиболее эффективном средстве работы с динамическими сайтами — о Puppeteer. Подробнее о библиотеке в руководстве по веб-скрапингу с Puppeteer. Если максимально кратко, то это веб-драйвер для headless-браузера Chrome, задействующий протокол Chrome DevTool Protocol, написанный на языке программирования JavaScript (хотя существуют неофициальные портированные версии и для других языков + к Puppeteer можно обращаться по API, поэтому привязка к языку программирования номинальная). В последних релизах добавилась поддержка Firefox и протокола BiDi.
Итак, чтобы создать байпас для Cloudflare, нужно сначала установить и настроить Puppeteer.
Настройка Puppeteer для веб-скрапинга
Puppeteer устанавливается и работает в среде Node.js. Не стоит путать его с Pyppeteer, это форк на Python.
Node.js — это среда выполнения JavaScript, построенная на движке V8 из Google Chrome. Иными словами, вы получаете возможность выполнять JavaScript-код вне браузера, на стороне сервера. Node.js имеет развитую систему пакетов и модулей, API на все случаи жизни, а также открытый исходный код. Эта среда по архитектуре схожа с фреймворком Twisted в Python, а также с библиотекой для обработки событий EventMachine для Ruby.
Если на вашем ПК или на сервере пока ещё не установлена среда Node.js, нужно скачать инсталляционный пакет с официального сайта или установить Node из штатного репозитория (касается в основном Linux-дистрибутивов).
На момент написания статьи актуальная LTS-версия Node.js — 22.19. Стабильная ветка — 24.8. Для Windows-систем разработчики настоятельно рекомендуют использовать среду контейнеризации Docker, хотя есть и альтернативные подходы — Chocolatey и Volta. На других ОС существует больше альтернатив: Brew, n, Devbox, fnm, nvm и т.п.
В качестве штатного пакетного менеджера можно выбрать npm, Yarn или pnpm. Выбор больше зависит от предпочтений команды/разработчиков, чем от конкретных технических фишек.
Внимание! При установке Node.js не забудьте добавить вызов Node и пакетного менеджера в переменные среды (PATH).
Если в системе установлен Docker, то инсталляция Node.js будет выглядеть так (команды для PowerShell):
- docker pull node:24-alpine # Получение образа с 24-ой версией Node.js, для LTS-сборки команда будет выглядеть так: docker pull node:22-alpine
- docker run -it --rm --entrypoint sh node:24-alpine # Эта команда запустит контейнер и инициирует подключение к нему.
- node -v # Это запрос текущей версии Node.js. В ответе должно показаться что-то вроде «v24.8.0».
- npm -v # А это запрос версии менеджера пакетов NPM, должно отобразиться что-то типа «11.6.0».
Теперь можно переходить к установке библиотеки веб-парсинга Puppeteer:
- npm i puppeteer # Команда загружает совместимый headless-браузер Google Chrome и библиотеку puppeteer со всеми зависимостями.
- npm i puppeteer-core # Это альтернативная команда, которая может установить только библиотеку puppeteer, без загрузки Chrome.
Если вы используете менеджер пакетов Yarn, то команда будет другой:
- yarn add puppeteer
Всё, можно переходить к написанию своих скриптов парсинга на JavaScript с байпасом Cloudflare.
Методы парсинга с Puppeteer при работе с Cloudflare

Cloudflare работает на базе масштабной распределённой сети серверов (точки присутствия более чем в 120 странах). Трафик анализируется по множеству показателей и блокируется в случае обнаружения проблем ещё до того, как он достигнет целевого сайта.
В связи с этим, нужно особо позаботиться о том, чтобы поведение и цифровой отпечаток парсера ничем не отличались от цифровых отпечатков реальных пользователей. Как этого можно достичь? Давайте рассмотрим в привязке к основным вариантам того, как обходить Cloudflare-защиту.
На всякий случай сразу скажем, что «универсального» решения нет, плюс механизмы защиты Cloudflare постоянно совершенствуются и усложняются. В связи с этим нужно делать ставку не на один конкретный подход, а на их комбинацию.
Итак, на что нужно обратить особое внимание, при создании байпаса Cloudflare.
Скрытие признаков headless-браузера
Любой браузер имеет свои цифровые отпечатки и особые метки в HTTP-заголовках. Чтобы снизить вероятность обнаружения Headless-Chrome, которым управляет веб-драйвер Puppeteer, можно использовать следующие решения:
- Отключить атрибут navigator.webdriver = true. Он в явном виде сообщает сайту, что работает в режиме автоматизации. Правильное значение атрибута для байпаса Cloudflare — navigator.webdriver = false.
- Задать естественные User-Agent и другие заголовки (headless UA). В безголовом Хроме может использоваться явное указание признака «headless». Его нужно убрать и заменить на такой же, как в обычном Хроме стабильной сборки.
- По аналогии нужно актуализировать другие настройки браузера: язык, разрешение экрана, доступные системные шрифты, WebGL / графический профиль, набор предустановленных плагинов (отсутствие расширений тоже может выглядеть неестественно), статус для AudioContext (он не должен быть «оффлайн»), набор медиаустройств (mediaDevices должны отдавать максимально «естественную» конфигурацию), адрес WebRTC (этот протокол часто способствует утечкам, сообщая другой IP-адрес, отличный от адреса прокси) и т.п.
- Установить модули puppeteer-extra и стелс-режим (puppeteer-extra-plugin-stealth).
- При возникновении проблем отключить скрытый режим и работать с браузером в явном виде (аргумент «headless: false»).
- Вместо предустанавливаемого headless-хрома использовать автоматизацию реального браузера с его цифровым профилем (executablePath: '/путь/до/реального/хрома').
Обязательная проверка редиректа на Cloudflare IUAM
IUAM (расшифровывается как I'm Under Attack Mode) — это особый механизм защиты сайтов, которые в текущий момент находятся под атакой ботов, например, во время DDoS. Обойти защиту от ботов Cloudflare может только человек. Например, ему нужно будет решить капчу или поставить специальную галочку «Я человек» (в этот момент считываются перемещения указателей и анализируется другие поведенческие факторы).
Чтобы не напороться на более серьёзные санкции, скрипт Puppeteer веб-парсинга должен мониторить 503-ошибки и отслеживать JS-челенджи (challenge-platform или Turnstile, это вариант челенджа с оригинальной капчей Cloudflare).
А ещё при первом обращении к сайту логично выжидать паузу в 7-8 секунд и проверять наличие специальных кук (они часто включают текст «cf_clearance» или «__cf_bm»), которые обозначают, что проверка на «человечность» была пройдена. Такие куки будут действовать в течение всей сессии, поэтому после получения доверенного статуса нужно постараться его сохранить — удерживать один и тот же IP-адрес прокси и сохранять куки для повторных обращений к сайту.
Часть настроек для подключения Cloudflare хранит в файле cookie __cflb, его тоже стоит оставлять в памяти — он сохраняет актуальность до 23 часов.
Резидентные прокси
Идеальные прокси-серверы для доступа к ценным данным со всего мира.
Решение капчи (Cloudflare Turnstile, reCAPTCHA)
Так как Cloudflare выборочно показывает капчу в том числе и реальным пользователям, несмотря на «трастовость» их цифровых профилей и поведенческих факторов, нужно предусмотреть вариант развития событий при необходимости решения капчи.
Что можно предложить для этого направления:
- Опять же, можно либо отключить headless-режим браузера, либо настроить остановку парсера для ручного решения капчи оператором. При многопоточном масштабном парсинге появляется проблема — пропускная способность одного человека. Он может не справиться с решением большого числа капч.
- Можно интегрировать в скрипт Puppeteer web-скрапинга сторонний сервис решения капчи. Он обеспечит многопоточность и работу с разными форматами капчи. Речь о таких сервисах, как 2captcha, CapMonster, AntiCaptcha и т.п. Для быстрой интеграции есть готовый модуль — puppeteer-extra-plugin-recaptcha (как можно догадаться из названия, работает он в основном с Google reCAPTCHA).
- Не стоит забывать, что капча бывает сложной, и за одну итерацию её можно не решить. Поэтому в парсере обязательно нужно предусмотреть достаточное количество повторных попыток и логику пропуска/перехода к анализу следующей страницы.
- В определённых ситуациях для снижения количества показов капчи можно представляться Cloudflare новым браузером. Это делается за счёт смены указания юзер-агента. Для автоматизации процесса есть готовый модуль npm — random-useragent.
Как понять, что Cloudflare показывает капчу? Можно проанализировать HTML-код на наличие текстовых маркеров, например, Checking your browser… или Please enable JavaScript and Cookies to continue (такое содержимое отображается на промежуточной странице при редиректе). Но самый лучший способ — проверить вёрстку на наличие элементов:
- div-блок с классом class="cf-turnstile"> или data-sitekey ...
- div-блок с классом class="g-recaptcha" или скрипт, ссылающийся на источник src="https://www.google.com/recaptcha/api.js".
- iframe-блок с вхождением src=".../recaptcha/" или iframe с title содержащим reCAPTCHA / Turnstile.
Ротация прокси
Так как блокировки Cloudflare привязываются к конкретным IP-адресам пользователей, то вы всегда можете пойти от обратного и:
- Оперативно менять IP-адреса для каждого нового запроса.
- Менять user-agent и цифровые отпечатки/браузерные профили. К слову, для автоматизации этого процесса, а также для получения максимально естественных цифровых профилей на рынке есть антидетект-браузеры. Они же позволяют подключать каждый экземпляр браузера через отдельный прокси.
Динамическая ротация прокси может не сработать при наличии активного механизма защиты Cloudflare IUAM. Здесь мы, наоборот, рекомендуем получать трастовые куки и удерживать сессию по максимуму. В противном случае вы будете вынуждены решать капчу для каждого нового подключения.
Чтобы не ротировать прокси вручную, лучше всего использовать коннект через качественный прокси-сервис, такой как Froxy. Логика ротации здесь может настраиваться для каждого отдельного прокси-порта. К парсеру с байпасом Cloudflare прокси подключаются всего один раз (по схеме BACKCONNECT). При этом выходные IP-адреса могут подбираться не только в конкретной локации, но и с привязкой к определённому оператору связи (ISP).
Самые трастовые прокси для работы с Cloudflare — мобильные и резидентные.
Этические нормы и ограничения
Никакой метод обхода ограничений Cloudflare не гарантирует 100% результата: сигнатуры ботов и парсеров постоянно обновляются. И чем больше вы делаете автоматических запросов к сайтам через Cloudflare, тем больше вероятность, что будет создана сигнатура конкретно для вашего парсера.
Массовый обход защиты целевых сайтов может нарушать его внутренние правила и/или законодательство страны, в чьей юрисдикции он работает. Поэтому всегда детально читайте юридические и пользовательские соглашения, иначе рискуете получить судебный иск, а также иные санкции.
Некоторые подмены (например, слишком агрессивная маскировка WebGL) могут ломать рендеринг страниц и скрипты на сайте. Делайте только минимально необходимые изменения.
Stealth-плагины и автоматические решения со временем устаревают — периодически проверяйте и обновляйте их. Выявляйте и внедряйте свои индикаторы/атрибуты, на основе которых могут срабатывать механизмы защиты Cloudflare.
Полный пример скрипта Puppeteer с байпасом Cloudflare

Создайте каталог на диске, переключитесь в него и инициализируйте проект:
mkdir cloudflare-scrapercd cloudflare-scrapernpm init -y
Не забудьте установить все необходимые библиотеки Puppeteer для парсинга (у нас это набор из стелс-плагина и инструментов для работы с файловой системой):
npm install puppeteer-extra puppeteer-extra-plugin-stealth puppeteer fs-extra
/**
* ПРИМЕЧАНИЯ!
* Шаблон: Puppeteer + Stealth + 2captcha
* Настройте:
* - PROXY (rotating) или массив прокси
* - PATH_TO_CHROME (если нужно использовать реальный браузер Chrome вместо headless)
* - API_KEY_2CAPTCHA
*
* Примечание: пример использует глобальный fetch (Node v18+). Для старых Node используйте node-fetch/axios.
*/
// Подключаем библиотеки
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const fs = require('fs-extra');
const path = require('path');
// Используем стелс-режим
puppeteer.use(StealthPlugin());
// ========== CONFIG ==========
const API_KEY_2CAPTCHA = 'REPLACE_WITH_YOUR_2CAPTCHA_KEY';
const PROFILE_DIR = path.resolve(__dirname, 'profiles'); // путь для сохранения профиля и кук
const COOKIES_FILE = profileId => path.join(PROFILE_DIR, `${profileId}_cookies.json`);
const PATH_TO_CHROME = undefined; // путь до реального браузера '/usr/bin/google-chrome' или undefined, чтобы использовать встроенный headless-браузер
const HEADLESS = false; // рекомендуем false для начала
// Прокси: если у вас ротатор — просто передаём прокси-сервер (socks5/http)
const PROXY = 'http://username:password@proxy-host:port'; // или null
// ========== HELPERS ==========
//Асинхронное ожидание и промисы
async function sleep(ms){ return new Promise(r => setTimeout(r, ms)); }
//Сохранение кук
async function saveCookies(profileId, page){
await fs.ensureDir(PROFILE_DIR);
const cookies = await page.cookies();
await fs.writeJson(COOKIES_FILE(profileId), cookies, { spaces: 2 });
}
//Загрузка имеющихся кук
async function loadCookies(profileId, page){
const f = COOKIES_FILE(profileId);
// Если куки на месте, то считываем их
if (await fs.pathExists(f)) {
const cookies = await fs.readJson(f);
try {
await page.setCookie(...cookies);
return true;
} catch(e){
// Если куки установить невозможно, то выводим ошибку в консоль
console.warn(Error when installing cookies:', e.message);
}
}
return false;
}
// 2captcha: отправка задачи и опрос результата
async function submit2CaptchaTask({ apiKey, sitekey, pageurl, method }) {
// method: 'userrecaptcha' | 'hcaptcha' | 'turnstile' etc.
// returns { requestId }
const params = new URLSearchParams({
key: apiKey,
method,
googlekey: sitekey, // для recaptcha userrecaptcha
pageurl
});
// для turnstile 2captcha требуется метод "method=turnstile" и "sitekey"
if (method === 'turnstile') {
params.delete('googlekey');
params.set('sitekey', sitekey);
}
params.set('json', 1);
const res = await fetch(`http://2captcha.com/in.php?${params.toString()}`);
const json = await res.json();
if (json.status !== 1) throw new Error('2captcha submit error: ' + JSON.stringify(json));
return { requestId: json.request };
}
// Устанавливаем число попыток решения капчи и интервал между запросами (в миллисекундах)
async function poll2CaptchaResult({ apiKey, requestId, maxAttempts = 10, interval = 5000 }) {
const params = new URLSearchParams({
key: apiKey,
action: 'get',
id: requestId,
json: 1
});
for (let i = 0; i < maxAttempts; i++){
await sleep(interval);
const res = await fetch(`http://2captcha.com/res.php?${params.toString()}`);
const json = await res.json();
if (json.status === 1) {
return json.request; // token
} else if (json.request === 'CAPCHA_NOT_READY') {
// продолжаем
console.log(`2captcha: not ready (${i+1}/${maxAttempts})`);
continue;
} else {
// пишем ошибку в консоль
throw new Error('2captcha error: ' + JSON.stringify(json));
}
}
throw new Error('2captcha: timeout waiting for solution');
}
// Детекция капчи в HTML/DOM
async function detectCaptchaOnPage(page){
// Ждём прогрузки контента
const html = await page.content();
// быстрый поиск по HTML, ищем признаки капчи recaptcha, hcaptcha и turnstile
const quick = /challenges\.cloudflare\.com\/turnstile|cdn-cgi\/challenge-platform|g-recaptcha|hcaptcha|www\.google\.com\/recaptcha|Checking your browser|One more step/i.test(html);
// ищем селекторы и другие признаки
const hasRecaptcha = !!(await page.$('.g-recaptcha, [data-sitekey][data-widget-id*="recaptcha"], iframe[src*="recaptcha"]'));
const hasHcaptcha = !!(await page.$('.h-captcha, iframe[src*="hcaptcha.com"]'));
const hasTurnstile = !!(await page.$('[data-cf-turnstile], .cf-turnstile, iframe[src*="turnstile"]'));
// попытка найти явный ключ сайта в HTML
const sitekeyMatch = html.match(/data-sitekey=["']([^"']+)["']/i);
const iframeUrls = (await page.frames()).map(f => f.url()).join(' ');
const iframeSitekeyMatch = iframeUrls.match(/sitekey=([^&]+)/i);
// Ищем по кукам
const cookies = await page.cookies();
const cookieNames = cookies.map(c => c.name).join(',');
const hasCfClearance = /cf_clearance/i.test(cookieNames);
return {
quick,
selectors: { hasRecaptcha, hasHcaptcha, hasTurnstile },
sitekey: (sitekeyMatch && sitekeyMatch[1]) || (iframeSitekeyMatch && decodeURIComponent(iframeSitekeyMatch[1])) || null,
cookieNames,
hasCfClearance,
iframeUrls
};
}
// возврат решённой капчи в DOM и submit формы
async function injectCaptchaToken(page, token, type){
// type: 'recaptcha' | 'hcaptcha' | 'turnstile'
if (type === 'recaptcha'){
// classic: g-recaptcha-response textarea
await page.evaluate((t) => {
let el = document.querySelector('textarea[name="g-recaptcha-response"]');
if (!el) {
el = document.createElement('textarea');
el.name = 'g-recaptcha-response';
el.style.display = 'none';
document.body.appendChild(el);
}
el.value = t;
// некоторые сайты ожидают явного запроса на обратный коллбэк
if (window.grecaptcha && grecaptcha && grecaptcha.getResponse) {
}
}, token);
// Попытка ввода решения
await page.evaluate(() => {
// триггеры, которые отслеживаются некоторыми формами
document.dispatchEvent(new Event('recaptcha-token-injected'));
});
} else if (type === 'turnstile'){
await page.evaluate((t) => {
// Типичное имя входного параметра Cloudflare turnstile: cf-turnstile-response
let el = document.querySelector('textarea[name="cf-turnstile-response"], input[name="cf-turnstile-response"]');
if (!el) {
el = document.createElement('textarea');
el.name = 'cf-turnstile-response';
el.style.display = 'none';
document.body.appendChild(el);
}
el.value = t;
document.dispatchEvent(new Event('turnstile-token-injected'));
}, token);
} else if (type === 'hcaptcha'){
// Ввод решения hcaptcha
await page.evaluate((t) => {
let el = document.querySelector('textarea[name="h-captcha-response"], input[name="h-captcha-response"]');
if (!el) {
el = document.createElement('textarea');
el.name = 'h-captcha-response';
el.style.display = 'none';
document.body.appendChild(el);
}
el.value = t;
document.dispatchEvent(new Event('hcaptcha-token-injected'));
}, token);
}
// попытаться автоматически отправить форму, если она есть
try {
await page.evaluate(() => {
const forms = Array.from(document.forms);
if (forms.length === 1) forms[0].submit();
else {
// если есть кнопка submit
const btn = document.querySelector('button[type="submit"], input[type="submit"]');
if (btn) btn.click();
}
});
} catch(e){
console.warn('Error when auto-submitting the form:', e.message);
}
}
// Основной парсер
async function runScrape({ url, profileId = 'default', proxy = PROXY }) {
// Дополнительные аргументы для скрытия следов headless-браузера
const launchArgs = [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-blink-features=AutomationControlled',
'--lang=en-US,en'
];
if (proxy) launchArgs.push(`--proxy-server=${proxy}`);
const browser = await puppeteer.launch({
headless: HEADLESS,
executablePath: PATH_TO_CHROME || undefined,
args: launchArgs,
// Не лишним будет задать реалистичные параметры окна браузера
defaultViewport: { width: 1366, height: 768 }
});
const page = await browser.newPage();
// HTTP заголовки, юзерагент + UA
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36');
await page.setExtraHTTPHeaders({ 'Accept-Language': en-US,en;q=0.9,en-US;q=0.8,en;q=0.7' });
// Важно: ранние переопределения до загрузки страниц
await page.evaluateOnNewDocument(() => {
// navigator.webdriver
Object.defineProperty(navigator, 'webdriver', { get: () => false });
// languages
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en', 'ru-RU', 'ru'] });
// plugins - простая имитация
Object.defineProperty(navigator, 'plugins', {
get: () => [{ name: 'Chrome PDF Plugin' }, { name: 'Native Client' }]
});
// chrome runtime
window.chrome = window.chrome || { runtime: {} };
// WebGL patch (вставляем примитивную имитацию)
try {
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
if (parameter === 37445) return 'Intel Inc.';
if (parameter === 37446) return 'Intel(R) UHD Graphics 620';
return getParameter.call(this, parameter);
};
} catch(e){}
try {
const origQuery = navigator.permissions.query;
navigator.permissions.query = (params) => (
params.name === 'notifications' ? Promise.resolve({ state: Notification.permission }) : origQuery(params)
);
} catch(e){}
});
// Пытаемся загрузить cookies из профиля
await loadCookies(profileId, page);
// Создаём CDP сессию для перезаписи тайм-зоны и локали, георасположения, если нужно
const client = await page.target().createCDPSession();
try {
await client.send('Emulation.setTimezoneOverride', { timezoneId: 'America/New_York' });
await client.send('Emulation.setLocaleOverride', { locale: 'en_US' });
// geolocation example (uncomment if needed)
// await client.send('Emulation.setGeolocationOverride', { latitude: 55.75, longitude: 37.616, accuracy: 20 });
} catch(e){ console.warn('CDP emulation not available:', e.message); }
// Навигация + детекция Cloudflare IUAM и капчи
console.log('Opening the address:', url);
const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }).catch(e => null);
// даём JS-челленджу время выполниться
await sleep(8000);
// дополнительно ждём навигации, если есть редиректы
try { await page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 5000 }).catch(()=>{}); } catch(e){}
// детект
const detection = await detectCaptchaOnPage(page);
console.log('Detection:', detection.selectors, 'quickHtml:', detection.quick);
console.log('Cookies:', detection.cookieNames);
if (detection.hasCfClearance) {
console.log('There is a Cloudflare clearance cookie — most likely the check was passed.');
}
// Если найдена капча — решаем через 2captcha
if (detection.selectors.hasRecaptcha || detection.selectors.hasHcaptcha || detection.selectors.hasTurnstile) {
const sitekey = detection.sitekey;
console.log('sitekey detected:', sitekey);
let captchaType = null;
if (detection.selectors.hasRecaptcha) captchaType = 'recaptcha';
else if (detection.selectors.hasHcaptcha) captchaType = 'hcaptcha';
else if (detection.selectors.hasTurnstile) captchaType = 'turnstile';
if (!sitekey) {
// попытка вытащить sitekey из iframe URLs
const iframeUrls = detection.iframeUrls || '';
const sk = iframeUrls.match(/sitekey=([^&]+)/i);
if (sk) {
detection.sitekey = decodeURIComponent(sk[1]);
}
}
if (!detection.sitekey) {
console.warn('The sitekey could not be found automatically. Try manually specifying a sitekey or adding a detector.');
} else {
try {
console.log('Sending the task to 2captcha (type=${captchaType})...');
const methodMap = { recaptcha: 'userrecaptcha', hcaptcha: 'hcaptcha', turnstile: 'turnstile' };
const submit = await submit2CaptchaTask({
apiKey: API_KEY_2CAPTCHA,
sitekey: detection.sitekey,
pageurl: url,
method: methodMap[captchaType]
});
console.log('2captcha requestId=', submit.requestId || submit.request);
const requestId = submit.requestId || submit.request;
const token = await poll2CaptchaResult({ apiKey: API_KEY_2CAPTCHA, requestId });
console.log('2captcha token received, inserted into the page:', token.substring(0, 20) + '...');
// инжектим токен и пробуем автоподать
await injectCaptchaToken(page, token, captchaType);
// ждём редиректа / обновления страницы
await sleep(3000);
await page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 15000 }).catch(()=>{});
console.log('We check the result after inserting the token...');
const afterDetection = await detectCaptchaOnPage(page);
console.log('After:', afterDetection.selectors, 'cookies:', afterDetection.cookieNames);
} catch (e) {
console.error('Error solving captcha:', e.message);
}
}
} else {
console.log('Captcha not found on the page (by selectors).');
}
// Если всё ок — сохраняем куки для последующих сессий
await saveCookies(profileId, page);
console.log('Cookies saved.');
// дальше — собственный парсинг страницы
const content = await page.content();
// TODO: распарсить нужные данные
console.log('Length HTML:', content.length);
// Завершение
await browser.close();
}
// ========== Запуск ==========
(async () => {
try {
const targetUrl = process.argv[2] || 'https://target-site.example/';
await runScrape({ url: targetUrl, profileId: 'default' });
console.log('Done.');
} catch (e) {
console.error('Fatal error:', e);
}
})();
Сохраните скрипт в файл cloudflare_puppeteer_scraper.js и запустите его в консоли:
node cloudflare_puppeteer_scraper.js 'https://target-site.example/'
Заключение и рекомендации

Ещё раз подчеркнём, что универсального решения для обхода защиты Cloudflare нет и никогда не будет. Всё дело в том, что активно меняются не только сами механизмы защиты, но и инструменты, которые позволяют их обходить. Этот процесс бесконечный.
Мало просто позаботиться о базовых параметрах, на основе которых Cloudflare может выявить ботов и системы автоматизации/парсинга: юзер-агенты, HTTP-заголовки и т.п. Важно подходить к проблеме более комплексно: использовать прокси, эмулировать поведение пользователя, следить за правдоподобностью цифрового отпечатка, рандомизировать задержки между обращениями и т.п. Если вы сталкиваетесь с капчей, это не значит, что всё закончилось неудачно. Наоборот, если пройти такую проверку вручную, то можно сильно повысить доверие к своему парсеру.
Если вам нужны высококачественные ротационные прокси, добро пожаловать в Froxy. Мы предоставляем более 10 миллионов IP по всему миру. Вы также можете попробовать готовый скрапер (Froxy Scraper) — с ним не придётся решать никакой капчи.

