Сайты электронной коммерции, один из богатейших источников публично структурированных данных в интернете. Каждая карточка товара содержит название, цену, рейтинг, статус наличия и изображение, и эти данные используются для отслеживания цен, исследования конкурентов, мониторинга запасов и анализа рыночных тенденций. Проблема в том, что современные витрины рендерят большую часть этих деталей через JavaScript и защищают страницы от автоматического трафика, поэтому обычный HTTP-запрос чаще всего возвращает почти пустую оболочку вместо нужного каталога.
Это пошаговое руководство по краулингу данных о товарах с сайта электронной коммерции на Node.js надёжным способом. Вы создадите небольшой рабочий скрапер, который получает отрендеренную страницу листинга через Crawling API, парсит каждую карточку товара с помощью Cheerio, обходит пагинацию и сохраняет чистый структурированный вывод в формате JSON или CSV. Весь процесс намеренно ограничен публичными данными о товарах, а раздел о правовых аспектах в конце не является шаблонным, поэтому прочитайте его, прежде чем применять это к реальному объёму.
Что вы создадите
Node.js-скрипт, который принимает URL публичного поиска или категории в интернет-магазине, получает отрендеренный HTML через Crawling API и извлекает структурированную запись для каждого товара на странице результатов. В качестве основного примера используется поисковый запрос, и для каждого товара извлекаются следующие поля:
- Название, имя товара, отображаемое на карточке, например «Men's Analog Wrist Watch».
- Цена, указанная цена в том виде, как она показана, например «Rs. 1,299».
- Рейтинг, количество отзывов или звёздный рейтинг рядом с товаром.
- Наличие, сигнал о запасах или местоположении, отображаемый на карточке, если есть.
- Изображение, URL миниатюры товара.
- URL товара, ссылка на отдельную страницу товара.
Почему обычный запрос не работает на сайтах электронной коммерции
Если запросить URL поиска в витрине обычным HTTP-клиентом, вы обычно получите ответ со статусом 200 и лишь частью данных листинга в теле. Тому есть две причины. Во-первых, большинство сайтов электронной коммерции рендерит цены, рейтинги и основную часть каждой карточки товара в браузере через JavaScript и AJAX, поэтому начальный HTML неполон до запуска скриптов страницы. Во-вторых, ритейл-платформы быстро выявляют автоматический трафик: IP-адреса дата-центров и паттерны запросов, не похожие на действия реального браузера, подвергаются вызовам, ограничению частоты или блокируются ещё до получения отрендеренного контента.
Таким образом, рабочий краулер интернет-магазина должен в одном запросе решить две задачи: браузер, который реально рендерит страницу, и IP-адрес, который платформа воспринимает как адрес настоящего посетителя. Можно собрать это самостоятельно с помощью headless-браузера и пула ротирующих резидентных прокси, но их объединение и поддержка в рабочем состоянии составляют большую часть трудозатрат. Crawling API совмещает обе задачи в одном вызове: вы передаёте URL с JavaScript-токеном, API рендерит страницу с доверенного IP и возвращает готовый HTML для парсинга. Подробнее о том, почему клиентские цели ведут себя именно так, рассказано в руководстве по краулингу JavaScript-сайтов.
Crawlbase предлагает два типа токенов. Обычный токен (TCP) получает статический HTML; JavaScript (JS) токен сначала рендерит страницу в реальном браузере. Страницы поиска в интернет-магазинах загружают ключевые поля на стороне клиента, поэтому JS-токен даёт наиболее полную страницу. С ним можно также передавать параметры ожидания, такие как ajax_wait и page_wait, для обработки AJAX-загрузки. Обычный токен может вернуть частичный результат с отсутствующими ценами или рейтингами, что не позволит надёжно выполнить парсинг.
Предварительные требования
Прежде чем писать код, нужно подготовить несколько вещей. Это не займёт много времени.
Базовые знания JavaScript и Node.js. Вы должны уметь писать и запускать Node-скрипты и устанавливать пакеты через npm. Если вы только знакомитесь с Node, официальная документация и любой вводный курс приведут вас к нужному уровню. Более подробное руководство доступно в нашей статье о создании веб-скрапера с Node.js.
Node.js 16 или выше. Проверьте версию командой node --version. Если нет, установите последний LTS-релиз с сайта Node.js или через менеджер версий вроде nvm. NPM поставляется вместе с Node.js.
Аккаунт Crawlbase и JS-токен. Зарегистрируйтесь, откройте панель управления и скопируйте JavaScript (JS) токен со страницы документации аккаунта. Crawlbase выдаёт два токена: обычный для статических страниц и JS-токен для динамических, рендерящихся через JavaScript страниц. Обращайтесь с токеном как с паролем: не добавляйте его в систему контроля версий.
Настройка проекта
Создайте папку проекта, инициализируйте её как npm-пакет и установите нужные библиотеки. Флаг npm init -y принимает все значения по умолчанию и создаёт package.json.
node --version mkdir ecommerce-crawling && cd ecommerce-crawling npm init -y npm install crawlbase cheerio csv-writer
Три зависимости выполняют основную работу: crawlbase, официальный Node-клиент для Crawling API, cheerio парсит возвращаемый HTML через jQuery-подобный API, позволяя извлекать отдельные поля по CSS-селекторам, а csv-writer преобразует структурированные записи в CSV-файл. Если селекторы для вас новы, хорошим дополнением будет руководство по XPath и CSS-селекторам.
Шаг 1: Получение отрендеренной страницы поиска
Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его с вашим JS-токеном и запросите URL поиска. Проверка кода статуса перед парсингом делает ошибки явными. Параметр page_wait задерживает выполнение на фиксированное количество миллисекунд после загрузки, чтобы карточки товаров с поздним рендерингом появились до захвата страницы.
const { CrawlingAPI } = require('crawlbase'); // Replace with your actual Crawlbase JS token const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); async function crawl(pageUrl) { const options = { ajax_wait: 'true', page_wait: 5000 }; const response = await api.get(pageUrl, options); if (response.statusCode === 200) { return response.body; } console.error(`Request failed: ${response.statusCode}`); return null; } const searchUrl = 'https://example-shop.com/catalog/?q=watches+for+men'; crawl(searchUrl).then((html) => { console.log(html ? html.slice(0, 500) : 'No HTML returned'); });
Два параметра ожидания важны для витрины, рендерящейся на стороне клиента. ajax_wait указывает API дождаться завершения загрузки асинхронного контента, а page_wait задерживает выполнение на 5000 миллисекунд (5 секунд) после загрузки, чтобы элементы с поздним рендерингом появились перед захватом. Пять секунд, разумный начальный порог; увеличьте значение, если цены или рейтинги приходят пустыми. JS-токен также автоматически гарантирует ротацию IP, поэтому запрос выглядит как действие реального посетителя. Запустите скрипт командой node scraper.js, и вы должны увидеть реальную разметку товаров, а не урезанную оболочку. Это подтверждает работу рендеринга ещё до написания первого селектора. Если нужно освежить знания о слое запросов, см. руководство по HTTP-запросам в Node.js.
Только что загруженная страница требовала рендеринга в реальном браузере и доверенного IP в одном вызове. Crawling API принимает JS-токен, запускает витрину в реальном браузере, ротирует резидентные IP на стороне сервера и возвращает готовый HTML, избавляя вас от необходимости самостоятельно управлять флотом headless-браузеров и пулом прокси. Сначала протестируйте его на публичной странице поиска в бесплатном тарифе.
Шаг 2: Определение селекторов и парсинг каждого товара
Прежде чем что-либо извлекать, изучите реальную карточку товара. Кликните правой кнопкой по названию, цене или рейтингу в браузере и выберите «Просмотреть код», чтобы открыть инструменты разработчика, затем запишите имена классов и теги, оборачивающие каждое поле. На сайтах электронной коммерции каждый результат оформлен в повторяющийся блок, поэтому выберите каждую карточку, а затем считывайте название, цену, рейтинг, изображение и ссылку на товар изнутри неё. Защитное чтение каждого поля не даёт одному отсутствующему значению сломать весь запуск.
const cheerio = require('cheerio'); function parseSearch(html) { const $ = cheerio.load(html); const results = []; $('div[data-qa-locator="general-products"] div[data-qa-locator="product-item"]').each((index, element) => { const card = $(element); const product = {}; product.productPageUrl = card.find('.mainPic--ehOdr a').attr('href') || null; product.thumbnailImage = card.find('.mainPic--ehOdr img').attr('src') || null; product.title = card.find('.info--ifj7U .title--wFj93 a').text().trim() || null; product.price = card.find('.info--ifj7U .price--NVB62 span').text().trim() || null; product.noOfReviews = card.find('.info--ifj7U .rateAndLoc--XWchq .rating__review--ygkUy').text().trim() || null; product.location = card.find('.info--ifj7U .rateAndLoc--XWchq .location--eh0Ro').text().trim() || null; if (product.title) results.push(product); }); return results; }
Несколько деталей делают код устойчивым. Каждое поле возвращает null при отсутствии элемента, что обычно бывает, поскольку не каждая карточка показывает рейтинг или строку местоположения. URL товара и изображение читаются из атрибута href ссылки и src изображения, поэтому используется attr, а не text. Внешний селектор нацеливается на сетку товаров по атрибутам data-qa-locator, которые, как правило, стабильнее хэшированных имён классов, а финальная проверка if (product.title) пропускает пустые или заглушечные блоки.
Хэшированные имена классов выше (.mainPic--ehOdr, .title--wFj93, .price--NVB62 и прочие) генерируются системой сборки витрины и меняются без предупреждения. Воспринимайте здешние селекторы как начальный шаблон, а не как контракт. Когда поле возвращает null, повторно изучите живую страницу в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание селекторов, норма для любого производственного скрапера, а не признак неисправности.
Шаг 3: Сборка краулера
Теперь объедините получение страницы и парсинг в один рабочий скрипт. Получите отрендеренный HTML, передайте его парсеру и выведите структурированные записи. Это минимальная сквозная версия, которая краулит одну страницу поиска.
const { CrawlingAPI } = require('crawlbase'); const cheerio = require('cheerio'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); async function crawl(pageUrl) { const options = { ajax_wait: 'true', page_wait: 5000 }; const response = await api.get(pageUrl, options); if (response.statusCode === 200) return response.body; console.error(`Request failed: ${response.statusCode}`); return null; } function parseSearch(html) { const $ = cheerio.load(html); const results = []; $('div[data-qa-locator="general-products"] div[data-qa-locator="product-item"]').each((index, element) => { const card = $(element); const product = { productPageUrl: card.find('.mainPic--ehOdr a').attr('href') || null, thumbnailImage: card.find('.mainPic--ehOdr img').attr('src') || null, title: card.find('.info--ifj7U .title--wFj93 a').text().trim() || null, price: card.find('.info--ifj7U .price--NVB62 span').text().trim() || null, noOfReviews: card.find('.info--ifj7U .rateAndLoc--XWchq .rating__review--ygkUy').text().trim() || null, location: card.find('.info--ifj7U .rateAndLoc--XWchq .location--eh0Ro').text().trim() || null, }; if (product.title) results.push(product); }); return results; } async function main() { const searchUrl = 'https://example-shop.com/catalog/?q=watches+for+men'; const html = await crawl(searchUrl); if (!html) return; const results = parseSearch(html); console.log(JSON.stringify(results.slice(0, 3), null, 2)); } main();
Функция main формирует URL поиска, отправляет его через Crawling API с 5-секундным ожиданием страницы для завершения JavaScript-рендеринга и парсит возвращаемый HTML с помощью Cheerio. Извлечённые записи, включая URL товаров, изображения, названия, цены, количество отзывов и местоположения, собираются в массив и выводятся для проверки. Запустите командой node scraper.js, чтобы убедиться, что поля возвращаются заполненными.
Как выглядит результат
Запустите полный скрипт, и вы получите чистый массив записей, по одной на товар, готовых для записи в JSON, CSV или базу данных.
[ { "productPageUrl": "https://example-shop.com/products/mens-analog-watch-1.html", "thumbnailImage": "https://img.example-shop.com/p/mens-analog-watch-1.jpg", "title": "Men's Analog Wrist Watch Stainless Steel", "price": "Rs. 1,299", "noOfReviews": "(128)", "location": "Karachi" }, { "productPageUrl": "https://example-shop.com/products/sport-digital-watch-2.html", "thumbnailImage": "https://img.example-shop.com/p/sport-digital-watch-2.jpg", "title": "Sport Digital Watch Waterproof", "price": "Rs. 899", "noOfReviews": "(54)", "location": "Lahore" } ]
Шаг 4: Обработка пагинации по страницам листинга
Одна страница результатов, это демо; реальная задача проходит по пагинации. Результаты поиска в интернет-магазинах разбиты на множество страниц, поэтому для сбора полного каталога нужно сначала прочитать общее количество страниц, затем итерировать с 1-й по N-ю, получая и парсуя каждую. Общее количество обычно отображается в элементе управления пагинацией в нижней части первой страницы; его можно считать селектором и привести к числу. Каждый URL страницы просто добавляет параметр &page= к URL поиска.
async function getTotalPages(query) { const searchUrl = `https://example-shop.com/catalog/?q=${encodeURIComponent(query)}`; const html = await crawl(searchUrl); if (!html) return 0; const $ = cheerio.load(html); const totalPages = parseInt($('ul.ant-pagination li:nth-last-child(2)').attr('title'), 10); return Number.isNaN(totalPages) ? 1 : totalPages; } async function crawlPage(query, page) { const searchUrl = `https://example-shop.com/catalog/?q=${encodeURIComponent(query)}&page=${page}`; const html = await crawl(searchUrl); return html ? parseSearch(html) : []; } async function crawlAll(query) { const totalPages = await getTotalPages(query); const results = []; for (let page = 1; page <= totalPages; page++) { const pageResults = await crawlPage(query, page); results.push(...pageResults); } return results; }
Поток разбит на три небольшие функции. getTotalPages получает первую страницу поиска и считывает количество страниц из элемента управления пагинацией, используя 1 как запасное значение. crawlPage получает и парсит одну страницу, добавляя к URL параметр page. crawlAll объединяет их: определяет итог, проходит цикл с 1-й по N-ю страницу и агрегирует результаты всех страниц в один массив. Поскольку все страницы результатов имеют одинаковую структуру карточек, написанная вами функция parseSearch работает для всех без изменений. Для очень больших каталогов ограничьте цикл разумным числом страниц, а не краулите тысячи за один раз.
Шаг 5: Сохранение данных в CSV
Собрав записи, запишите их в CSV-файл, чтобы можно было открыть данные в электронной таблице или загрузить в другой инструмент. Библиотека csv-writer позволяет определить заголовки, соответствующие именам ваших полей, и записать все записи одним вызовом. У JSON и CSV есть своё место; в руководстве по парсингу данных электронной коммерции рассматривается, когда использовать каждый из них.
const createCsvWriter = require('csv-writer').createObjectCsvWriter; const csvWriter = createCsvWriter({ path: 'ecommerce_products.csv', header: [ { id: 'productPageUrl', title: 'Product Page URL' }, { id: 'thumbnailImage', title: 'Thumbnail Image URL' }, { id: 'title', title: 'Title' }, { id: 'price', title: 'Price' }, { id: 'noOfReviews', title: 'Number of Reviews' }, { id: 'location', title: 'Location' }, ], }); async function saveToCsv(data) { await csvWriter.writeRecords(data); } (async () => { const products = await crawlAll('watches for men'); await saveToCsv(products); console.log(`Saved ${products.length} products to ecommerce_products.csv`); })();
Массив заголовков сопоставляет id каждого поля с удобочитаемым названием столбца, поэтому итоговый CSV открывается чисто в Excel или Google Sheets с подписанными столбцами. Чтобы сохранить те же записи в базу данных, структурированные объекты напрямую отображаются на строки таблицы: используйте те же имена полей как столбцы и вставляйте по одной строке на товар. Форма данных не меняется, меняется только место назначения.
Масштабирование и защита от блокировок
Даже при решённой задаче рендеринга ритейл-платформы отслеживают трафик, похожий на работу скрапера. Несколько полезных привычек помогут сохранить работоспособность запуска; они применимы к любой жёстко защищённой коммерческой цели.
- Контролируйте темп запросов. Бомбардировка страниц в плотном цикле, самый быстрый способ получить throttling. Распределяйте запросы и варьируйте запросы вместо краулинга одного пути на полной скорости.
- Опирайтесь на ротацию. Пул резидентных IP распределяет запросы по многим адресам реальных пользователей, и ни один не превышает лимит. Crawling API делает это за вас; если вы создаёте собственный стек, именно здесь нужно постараться.
- Читайте коды статуса. Запуск, начинающий возвращать вызовы или ошибки, сигнализирует о том, что текущая частота или уровень IP больше не достаточны. Воспринимайте это как сигнал снизить нагрузку, а не как шум.
- Кэшируйте и дедуплицируйте. Сохраняйте отрендеренный HTML или распарсенные строки, чтобы повторный запуск не перезагружал уже полученные страницы. Это снижает как затраты, так и объём запросов.
Более широкое руководство доступно в статье о парсинге сайтов без блокировок. Если вы предпочитаете маршрутизировать собственный трафик через ротирующий пул вместо использования управляемого API, Smart AI Proxy предоставляет ту же ротацию резидентных IP в качестве drop-in прокси-endpoint. Паттерн «получить, затем парсить» из этого руководства применим к большинству витрин: только селекторы и параметр пагинации меняются от сайта к сайту.
Законно ли краулить сайты электронной коммерции?
Допустимость краулинга сайта электронной коммерции зависит от его условий обслуживания, вашей юрисдикции и того, что вы делаете с данными. Многие витрины ограничивают автоматический доступ в условиях, поэтому краулинг может противоречить им вне зависимости от аккуратности вашего инструментария. Ни один из приведённых здесь кодов не меняет этого; он лишь обеспечивает техническую сторону. Прочитайте условия обслуживания сайта и его robots.txt, и воспринимайте оба документа как границу того, что вы собираете и как часто запрашиваете.
Несколько правил, которых стоит придерживаться. Собирайте только публичные данные о товарах: название, цену, рейтинг, наличие, изображение и ссылку на товар, которые любой может видеть без аккаунта. Держите объём запросов достаточно низким, чтобы не нагружать серверы сайта, и соблюдайте любые ограничения частоты. Держитесь подальше от всего за авторизацией, включая страницы аккаунта, корзины и историю заказов, и избегайте персональных данных, привязанных к идентифицируемым покупателям или продавцам. Не перераспространяйте защищённые авторским правом медиаматериалы, такие как фотографии товаров или описания, способами, не лицензированными сайтом; их коммерческое использование, отдельный вопрос, не связанный с чтением публичной цены.
Для больших объёмов или коммерческого использования предпочтительнее официальный канал, где он существует. Многие крупные ритейлеры и маркетплейсы публикуют официальные API товаров или партнёрские API с гарантированной структурой и чёткими правами использования, и именно они являются правильным инструментом, когда нужны большие объёмы или коммерческое переиспользование. Это руководство намеренно ограничено публичными страницами листинга и поиска, потому что именно такой подход позволяет сохранить работу в рамках допустимого. Здесь не рассматривается ничего за аутентификацией, персональные данные, частная информация об аккаунте или заказах, или попытки обойти вход в систему. Если ваш проект требует большего, чем публичные листинги, официальный API или соглашение о данных, верный путь, а не более хитрый скрапер.
Ключевые выводы
- Сайты электронной коммерции рендерят листинги на стороне клиента. Обычный запрос возвращает неполную страницу, поэтому перед парсингом её необходимо отрендерить.
-
Нужны рендеринг и доверенный IP вместе. Crawling API с JS-токеном делает и то, и другое в одном вызове;
ajax_waitиpage_waitуправляют временем ожидания контента. - Cheerio выполняет извлечение. Выберите каждую карточку товара, затем сопоставьте название, цену, рейтинг, изображение и URL товара с текущими селекторами и ожидайте дрейфа этих хэшированных селекторов.
- Масштабируйтесь, обходя пагинацию. Считайте общее количество страниц, перебирайте в цикле с 1-й по N-ю, добавляя параметр страницы, и используйте тот же парсер на каждой странице с разумным темпом.
- Оставайтесь в рамках публичных данных. Соблюдайте ToS и robots.txt каждого сайта, предпочитайте официальный API товаров для большого объёма или коммерческого использования и никогда не касайтесь авторизации, персональных данных или защищённых авторским правом медиаматериалов, на которые у вас нет прав.
Часто задаваемые вопросы
В чём разница между веб-краулингом и веб-скрапингом?
Веб-краулинг, это процесс систематической навигации по сайту и сбора данных с множества страниц с переходами по ссылкам и пагинации. Веб-скрапинг, это извлечение конкретных полей, например цены или названия, с заданной страницы. На практике они работают вместе: краулер проходит по страницам листинга, а шаг скрапинга извлекает структурированные поля из каждой. Скрипт в этом руководстве делает и то, и другое.
Почему обычный запрос возвращает неполные данные с сайтов электронной коммерции?
Потому что большинство витрин рендерит цены, рейтинги и основную часть каждой карточки товара на стороне клиента через JavaScript и AJAX. Начальный HTML неполон до запуска скриптов страницы в браузере, поэтому обычный HTTP-запрос возвращает статус 200 с отсутствующими или пустыми ключевыми полями. Для получения полной страницы её нужно сначала отрендерить, что и делает JS-токен Crawling API.
Нужен ли обычный токен или JS-токен?
Используйте JS-токен для страниц поиска и листинга в интернет-магазинах. Обычный токен получает статический HTML, который на витрине с клиентским рендерингом может вернуться с отсутствующими ценами или рейтингами. JS-токен рендерит страницу в реальном браузере перед возвратом HTML и позволяет передавать параметры ожидания вроде ajax_wait и page_wait, чтобы динамически загружаемые карточки были доступны при парсинге с Cheerio.
Мои селекторы возвращают null. Что изменилось?
Почти наверняка разметка сайта. Хэшированные имена классов, генерируемые системами сборки витрин, меняются без предупреждения и отличаются между страницами поиска и отдельными страницами товаров, поэтому селекторы, работавшие в прошлом месяце, могут сломаться. Повторно изучите живую страницу в инструментах разработчика браузера и обновите селекторы, опираясь на стабильные атрибуты вроде data-qa-locator там, где они есть. Периодическое обслуживание селекторов, норма для любого производственного скрапера.
Как хранить данные о парсинге товаров?
Для быстрого результата запишите записи в CSV-файл с помощью csv-writer, чтобы данные открывались в любой электронной таблице. Для повторных или более крупных запусков вставьте те же структурированные объекты в базу данных, используя имена полей как столбцы и по одной строке на товар. В любом случае форма записи остаётся той же; меняется только место назначения, поэтому можно начать с CSV и перейти к базе данных позже без переписывания парсера.
Как не попасть в блокировку при краулинге сайтов электронной коммерции?
Держите частоту запросов на один IP низкой, варьируйте запросы вместо повторения одного пути и маршрутизируйте через ротирующие резидентные IP, чтобы ни один адрес не превышал лимит. Crawling API управляет ротацией и доверенным пулом IP за вас; если вы создаёте собственный стек, именно здесь стоит вложить усилия. Следите за кодами статуса и снижайте нагрузку при появлении вызовов, а не пытайтесь пробиться через них.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
