Большая часть контента, который вы хотите получить со страницы, не находится в первом ответе. Социальные ленты, сетки товаров, результаты поиска и списки отзывов часто загружают несколько элементов, а затем получают и отрисовывают больше только тогда, когда вы прокручиваете к нижней части. Этот паттерн, бесконечная прокрутка, поддерживает начальную страницу лёгкой, но это также означает, что один HTTP-запрос передаёт вам часть данных, которые вы можете видеть в браузере.
В этом руководстве показано, как прокручивать сайт во время краулинга с помощью JavaScript и Node.js. Вы строите небольшой исполняемый скрапер, который управляет прокруткой через Crawling API, используя его параметры scroll и scroll_interval, позволяет странице загружать больше элементов, затем разбирает загруженный контент с помощью Cheerio и экспортирует его. В пошаговом руководстве используется нейтральный URL-заполнитель листингов, чтобы вы могли подставить свою реальную публичную цель, и оно ограничено только публичными данными.
Что вы построите
Скрипт Node.js, который указывает на публичную страницу листинга с бесконечной прокруткой, просит Crawling API отрисовать её и продолжать прокрутку в течение заданного количества секунд, чтобы загрузилось больше элементов, а затем извлекает структурированную запись на каждый элемент из возвращённого HTML. В качестве примера извлекаются следующие поля из обобщённого публичного листинга:
- Заголовок название или заголовок элемента, показанного на карточке.
- Подзаголовок вторичная строка, такая как поставщик, автор или категория, если присутствует.
- Цена указанная цена или стоимость, если карточка её показывает.
- Ссылка абсолютный URL на страницу отдельного элемента.
- Количество результатов сводное количество элементов, о которых сообщает страница.
Почему обычный запрос пропускает прокрученный контент
Отправьте обычный HTTP-запрос на страницу с бесконечной прокруткой, и вы получите обратно разметку, которую сервер вернул до какой-либо прокрутки. Обычно это первая партия элементов и не более того. Остальная часть списка получается JavaScript-кодом страницы в ответ на события прокрутки, часто через фоновые AJAX-вызовы, и внедряется в DOM только после того, как браузер прокрутится вниз. Запрос, который никогда не прокручивается, никогда не инициирует эти вызовы, поэтому дополнительные элементы никогда не существуют в получаемом HTML.
Чтобы получить полный список, в одном запросе должны произойти две вещи. Страница должна отрисоваться в настоящем браузере, и этому браузеру нужно прокрутиться достаточно далеко и достаточно долго, чтобы инициировать каждый раунд ленивой загрузки. Вы можете построить это самостоятельно с помощью headless-браузера, который прокручивается в цикле и ждёт между проходами, а также пула ротируемых IP, чтобы цель воспринимала трафик как реального посетителя. Crawling API сворачивает всё это в один вызов: вы отправляете URL с установленными параметрами прокрутки, он отрисовывает и прокручивает страницу на стороне сервера за доверенным IP и возвращает полностью загруженный HTML для разбора с помощью Cheerio.
Три вещи обеспечивают работу прокрутки через Crawling API. JavaScript-токен отрисовывает страницу в настоящем браузере. Параметр scroll указывает API прокручивать страницу после загрузки. Параметр scroll_interval устанавливает, сколько секунд продолжается прокрутка, вплоть до максимума в 60 секунд; после этого API захватывает загруженную страницу и возвращает её.
Предварительные требования
Прежде чем писать какой-либо код, должно быть выполнено несколько условий. Ни одно из них не займёт много времени.
Базовый JavaScript и Node.js. Вы должны уметь писать и запускать Node-скрипт и устанавливать пакеты с помощью npm. Если вы новичок в этом, наше руководство по созданию веб-скрапера с Node.js охватывает основы, которые предполагает этот учебник.
Node.js 16 или новее. Проверьте свою версию с помощью node --version. Если у вас её нет, установите с сайта Node.js или через менеджер версий вроде nvm.
Аккаунт Crawlbase и JavaScript-токен. Зарегистрируйтесь, откройте панель управления и скопируйте свой JavaScript-токен. Бесплатный тариф даёт вам до 5 000 запросов без карты, и вы платите только за успешные запросы. JavaScript-запросы стоят больше кредитов, чем обычные, потому что они отрисовывают страницу, что важно при прокрутке. Относитесь к токену как к паролю и не включайте его в систему контроля версий.
Настройка проекта
Создайте папку проекта, инициализируйте её и установите две библиотеки, необходимые скраперу.
node --version mkdir scroll-scraper && cd scroll-scraper npm init -y npm install crawlbase cheerio
Две зависимости выполняют работу: crawlbase является официальным Node-клиентом для Crawling API, а cheerio разбирает возвращаемый HTML с помощью API в стиле jQuery, чтобы вы могли извлекать поля по CSS-селектору. Создайте файл с именем scraper.js в этой папке и добавьте код из приведённых ниже шагов.
Шаг 1: Получение страницы с включённой прокруткой
Начните с получения загруженной страницы. Импортируйте класс CrawlingAPI, инициализируйте его с помощью JavaScript-токена и запросите целевой URL с включённым параметром scroll. Без scroll_interval API по умолчанию прокручивает 10 секунд, что загружает первую дополнительную партию. Проверка статуса перед разбором делает сбои явными, а не скрытыми.
const { CrawlingAPI } = require('crawlbase'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); const targetUrl = 'https://example.com/listings'; api .get(targetUrl, { scroll: true }) .then((response) => { if (response.statusCode === 200) { console.log(response.body.length, 'bytes of rendered HTML'); console.log(response.body.slice(0, 500)); } }) .catch((error) => console.error('API request error:', error));
Запустите скрипт с помощью node scraper.js. Передача { scroll: true } говорит Crawling API отрисовать страницу и прокрутить её перед возвратом, поэтому получаемый HTML уже содержит элементы, загрузившиеся при прокрутке, а не только первую партию. Количество байт и фрагмент в верхней части тела подтверждают, что вы получаете реальную отрисованную страницу, а не пустую оболочку. Поскольку вы ещё не установили интервал, по умолчанию используется прокрутка на 10 секунд, что естественно загружает меньше элементов, чем более длительная.
Этот единственный вызов только что отрисовал страницу с бесконечной прокруткой и прокрутил её за вас, без headless-браузера, прокручивающегося в цикле, или пула прокси на вашей стороне. Crawling API запускает страницу в настоящем браузере, продолжает прокрутку столько, сколько указывает scroll_interval, ротирует резидентские IP на стороне сервера и обрабатывает CAPTCHA, поэтому вы получаете полностью загруженный HTML из одного запроса. Попробуйте его на публичном листинге в рамках бесплатного тарифа, затем добавьте свой парсер.
Шаг 2: Загрузка большего количества элементов с помощью scroll_interval
Прокрутка по умолчанию на 10 секунд загружает только первую дополнительную партию. Чтобы загрузить больше, установите scroll_interval на количество секунд, в течение которых API должен продолжать прокрутку. Максимум составляет 60 секунд; после этого API захватывает всё, что загрузилось, и возвращает это. Каждая дополнительная секунда прокрутки даёт странице больше времени для получения и отрисовки дополнительных элементов.
const { CrawlingAPI } = require('crawlbase'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); async function fetchScrolled(url, seconds) { const response = await api.get(url, { scroll: true, scroll_interval: seconds, }); if (response.statusCode === 200) return response.body; console.error(`Request failed: ${response.statusCode}`); return null; } fetchScrolled('https://example.com/listings', 20) .then((html) => html && console.log(html.length, 'bytes after 20s scroll'));
Здесь вызов прокручивает 20 секунд вместо стандартных 10, поэтому у страницы есть время загрузить больше элементов, и возвращаемый HTML в результате больше. Держите соединение открытым достаточно долго, чтобы прокрутка завершилась: если вы прокручиваете все 60 секунд, допускайте до примерно 90 секунд на завершение запроса до возврата загруженной страницы. Начните с меньшего интервала и увеличивайте его только до тех пор, пока количество элементов не перестанет расти, поскольку более длительная отрисовка стоит больше и даёт мало после исчерпания списка.
Нет единственно верного значения для scroll_interval. Короткая лента может быть полностью загружена за 10–20 секунд, тогда как длинная может продолжать загружаться до предела в 60 секунд. Увеличивайте интервал постепенно и сравнивайте, сколько элементов вы разбираете; как только счётчик выровняется, более длительная прокрутка только тратит кредиты без добавления строк.
Шаг 3: Разбор загруженных элементов с помощью Cheerio
Имея загруженный HTML, передайте его в Cheerio и пройдитесь по каждой карточке элемента. Используйте селекторы, соответствующие вашей реальной цели; приведённые ниже предполагают общий листинг, где каждый элемент находится в карточке .listing-item. Защитное чтение каждого поля предотвращает сбой прогона из-за одного отсутствующего значения.
const cheerio = require('cheerio'); function parseItems(html, baseUrl) { const $ = cheerio.load(html); const result = { resultCount: $('.results-count').text().trim(), items: [], }; $('.listing-item').each((_, element) => { const card = $(element); const title = card.find('.item-title').text().trim(); const subtitle = card.find('.item-subtitle').text().trim(); const price = card.find('.item-price').text().trim(); let link = card.find('a.item-link').attr('href'); if (link && link.startsWith('/')) { link = new URL(link, baseUrl).href; } if (title) { result.items.push({ title, subtitle: subtitle || '', price: price || 'N/A', link: link || '', }); } }); return result; }
Сводный счётчик берётся из .results-count, а каждый элемент находится в карточке .listing-item. Внутри карточки заголовок берётся из .item-title, вторичная строка из .item-subtitle, цена из .item-price, а ссылка из якоря a.item-link, преобразованного в абсолютный URL, чтобы он работал вне страницы. Поскольку элементы, загрузившиеся во время прокрутки, уже находятся в HTML, тот же цикл захватывает первую партию и все последующие в одном проходе.
Приведённые выше имена классов являются заполнителями для обобщённой страницы. Для реальной цели откройте живую страницу в инструментах разработчика браузера, прокрутите до загрузки элементов и прочитайте фактический класс или атрибут на каждой карточке. Многие производственные сайты используют сгенерированные имена классов, которые меняются без предупреждения, поэтому относитесь к селекторам как к начальному шаблону, а не как к контракту, и повторно проверяйте, когда поле возвращается пустым.
Шаг 4: Сборка полного скрипта с экспортом в JSON и CSV
Теперь объедините получение данных с прокруткой и разбор в один исполняемый скрипт, затем запишите записи на диск как в JSON, так и в CSV.
const fs = require('fs'); const { CrawlingAPI } = require('crawlbase'); const cheerio = require('cheerio'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); async function fetchScrolled(url, seconds) { const response = await api.get(url, { scroll: true, scroll_interval: seconds, }); if (response.statusCode === 200) return response.body; console.error(`Request failed: ${response.statusCode}`); return null; } function toCsv(rows) { const headers = ['title', 'subtitle', 'price', 'link']; const escape = (value) => `"${String(value).replace(/"/g, '""')}"`; const lines = [headers.join(',')]; for (const row of rows) { lines.push(headers.map((h) => escape(row[h])).join(',')); } return lines.join('\n'); } async function main() { const url = 'https://example.com/listings'; const html = await fetchScrolled(url, 20); if (!html) return; const data = parseItems(html, url); fs.writeFileSync('items.json', JSON.stringify(data, null, 2)); fs.writeFileSync('items.csv', toCsv(data.items)); console.log(`Saved ${data.items.length} items to JSON and CSV`); } main();
Вставьте функцию parseItems из Шага 3 в тот же файл, чтобы main могла её вызвать. Запустите с помощью node scraper.js, и вы получите два файла: items.json с полными структурированными записями и items.csv, готовый к открытию в таблице. Вспомогательная функция toCsv заключает каждое поле в кавычки и удваивает любые встроенные кавычки, что важно, потому что заголовки и цены часто содержат запятые. Теперь у вас есть работающий скрапер с прокруткой менее чем в 50 строках, и вы можете добавить его в существующий скрапер или позже обернуть в конечную точку, если захотите.
Как выглядит вывод
JSON-файл содержит количество результатов и один объект на каждый загруженный элемент с заголовком, подзаголовком, ценой и ссылкой.
{ "resultCount": "248 results", "items": [ { "title": "Wireless Headphones", "subtitle": "AudioWorks", "price": "$59.00", "link": "https://example.com/listings/wireless-headphones" }, { "title": "Mechanical Keyboard", "subtitle": "KeyForge", "price": "$89.00", "link": "https://example.com/listings/mechanical-keyboard" } ] }
CSV отражает те же строки элементов со строкой заголовка, поэтому он сразу открывается в Excel, Google Sheets или любом конвейере, который читает файлы с разделителями.
title,subtitle,price,link "Wireless Headphones","AudioWorks","$59.00","https://example.com/listings/wireless-headphones" "Mechanical Keyboard","KeyForge","$89.00","https://example.com/listings/mechanical-keyboard"
Масштабирование за пределы одной прокручиваемой страницы
Прокрутка загружает больше элементов на одном URL, но она не является заменой пагинации, и существует практический предел. Ограничение в 60 секунд и скорость загрузки страницы ограничивают количество элементов, которое один запрос может получить. Для более крупного извлечения данных объедините оба метода: прокручивайте каждую страницу, чтобы загрузить её полную партию, затем переходите к следующей странице, если сайт использует пагинацию, и разбирайте каждую с помощью той же функции.
async function scrapeAllPages(baseUrl, maxPages, seconds) { const allItems = []; for (let page = 1; page <= maxPages; page++) { const pageUrl = `${baseUrl}?page=${page}`; const html = await fetchScrolled(pageUrl, seconds); if (!html) break; const { items } = parseItems(html, baseUrl); if (items.length === 0) break; allItems.push(...items); console.log(`Page ${page}: ${items.length} items`); await new Promise((r) => setTimeout(r, 2000)); } return allItems; }
Сопоставьте шаблон страницы с вашей целью, проверив реальную ссылку «следующая страница» в браузере. При большом объёме вам не нужно выполнять эти запросы по одному и ждать. Асинхронный Crawler позволяет отправлять много URL и собирать результаты по мере их готовности, что лучше подходит для крупных заданий с прокруткой и пагинацией, чем плотный последовательный цикл. Подробнее об отрисованных, JavaScript-тяжёлых страницах, подобных этим, смотрите наше руководство по краулингу JavaScript-сайтов, а для полностью фоновых AJAX-лент, скрапинг данных с AJAX-сайтов.
Ответственный скрапинг
Держите это в рамках публичных данных и делайте это добросовестно. Прочитайте Условия использования целевого сайта и его robots.txt, прежде чем направлять на него скрапер с прокруткой, и относитесь к обоим как к границе того, что вы собираете. Держитесь публичных листингов, а не чего-либо за логином, и делайте паузы между запросами, чтобы не нагружать сервер: прокрутка уже удерживает отрисовку открытой в течение многих секунд, поэтому добавьте задержку между страницами, а не запускайте их в плотном цикле. Когда данные касаются идентифицируемых людей, применяются законы о конфиденциальности, такие как GDPR и CCPA, поэтому избегайте составления профилей отдельных лиц и не публикуйте персональные данные, привязанные к чьей-либо личности. Более полную инструкцию смотрите в том, как скрапить сайты без блокировки.
Ключевые выводы
- Бесконечная прокрутка скрывает данные от обычного запроса. Обычный HTTP-вызов возвращает только первую партию элементов; остальные загружаются при прокрутке страницы, поэтому запрос, который никогда не прокручивается, никогда их не видит.
-
Crawling API прокручивает за вас. Отправьте URL с
scroll: true, и он отрисует страницу в настоящем браузере, прокрутит её на стороне сервера за ротируемым IP и вернёт полностью загруженный HTML в одном вызове. - scroll_interval контролирует объём загрузки. По умолчанию прокрутка составляет 10 секунд; увеличивайте интервал до предела в 60 секунд, чтобы загрузить больше элементов, и допускайте до примерно 90 секунд на выполнение запроса полной длины.
- Cheerio разбирает загруженный HTML. Выбирайте каждую карточку элемента, читайте заголовок, подзаголовок, цену и ссылку защитно, ожидайте устаревания сгенерированных имён классов и экспортируйте в JSON и CSV.
- Прокрутка плюс пагинация для масштаба. Прокручивайте каждую страницу, чтобы загрузить её полную партию, переходите к следующей странице, когда сайт использует пагинацию, делайте паузы между запросами и используйте асинхронный Crawler для крупных заданий.
Часто задаваемые вопросы
Что такое бесконечная прокрутка и почему она нарушает обычный скрапинг?
Бесконечная прокрутка представляет собой паттерн, при котором страница сначала загружает небольшой набор элементов, а затем получает и отрисовывает больше по мере прокрутки пользователя к нижней части, обычно через фоновые AJAX-вызовы. Обычный HTTP-запрос захватывает страницу до какой-либо прокрутки, поэтому он видит только первую партию. Чтобы получить остальные, необходимо отрисовать страницу в настоящем браузере и фактически прокрутить её, что и делает Crawling API при передаче параметров прокрутки.
Что делают параметры scroll и scroll_interval?
Параметр scroll указывает Crawling API прокрутить страницу после её загрузки, что инициирует ленивую загрузку, которая приносит больше элементов. Параметр scroll_interval устанавливает, сколько секунд продолжается прокрутка, вплоть до максимума в 60. При включённом scroll и без установленного интервала API по умолчанию прокручивает 10 секунд. Оба требуют JavaScript-токена, поскольку страница должна отрисоваться в настоящем браузере, чтобы прокрутка имела смысл.
Сколько секунд устанавливать для scroll_interval?
Начните с малого и увеличивайте. Короткая лента может быть полностью загружена за 10–20 секунд, тогда как длинная продолжает загружаться до предела в 60 секунд. Увеличивайте интервал постепенно и сравнивайте, сколько элементов вы разбираете; как только счётчик перестаёт расти, более длительная прокрутка только тратит дополнительные кредиты без добавления строк. Если вы прокручиваете все 60 секунд, допускайте до примерно 90 секунд на выполнение запроса.
Нужен ли мне JavaScript-токен для прокрутки страницы?
Да. Прокрутка имеет смысл только на странице, отрисовывающейся в настоящем браузере, и именно JavaScript-токен обеспечивает эту отрисовку. Обычный токен возвращает неотрисованный HTML, где прокрутка не имеет эффекта и дополнительные элементы никогда не загружаются. JavaScript-запросы используют больше кредитов, чем обычные, из-за отрисовки, поэтому учитывайте это при планировании крупного прогона.
Является ли прокрутка заменой пагинации?
Нет. Прокрутка загружает больше элементов на одном URL, но ограничение в 60 секунд и скорость загрузки страницы ограничивают количество, которое она может получить в одном запросе. Для полного набора данных на сайте, который также использует пагинацию, объедините оба метода: прокручивайте каждую страницу, чтобы загрузить её полную партию, затем переходите к следующей странице и разбирайте её с помощью той же функции. Для крупных заданий асинхронный Crawler обрабатывает множество таких запросов параллельно.
Мои селекторы возвращают пустые значения после прокрутки. В чём проблема?
Обычно одно из двух. Либо страница не прокручивалась достаточно долго, поэтому нужные вам элементы никогда не загружались, в таком случае увеличьте scroll_interval и убедитесь, что возвращаемый HTML вырос. Либо разметка сайта использует другие или сгенерированные имена классов, чем ожидают ваши селекторы. Откройте живую страницу в инструментах разработчика браузера, прокрутите до появления элементов, прочитайте реальный класс или атрибут на каждой карточке и обновите селекторы в parseItems в соответствии с ними.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
