Сайты электронной коммерции, один из богатейших источников публично структурированных данных в интернете. Каждая карточка товара содержит название, цену, рейтинг, статус наличия и изображение, и эти данные используются для отслеживания цен, исследования конкурентов, мониторинга запасов и анализа рыночных тенденций. Проблема в том, что современные витрины рендерят большую часть этих деталей через 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-сайтов.

Зачем нужен JS-токен

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.

bash
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 задерживает выполнение на фиксированное количество миллисекунд после загрузки, чтобы карточки товаров с поздним рендерингом появились до захвата страницы.

javascript
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.

Crawlbase Crawling API

Только что загруженная страница требовала рендеринга в реальном браузере и доверенного IP в одном вызове. Crawling API принимает JS-токен, запускает витрину в реальном браузере, ротирует резидентные IP на стороне сервера и возвращает готовый HTML, избавляя вас от необходимости самостоятельно управлять флотом headless-браузеров и пулом прокси. Сначала протестируйте его на публичной странице поиска в бесплатном тарифе.

Шаг 2: Определение селекторов и парсинг каждого товара

Прежде чем что-либо извлекать, изучите реальную карточку товара. Кликните правой кнопкой по названию, цене или рейтингу в браузере и выберите «Просмотреть код», чтобы открыть инструменты разработчика, затем запишите имена классов и теги, оборачивающие каждое поле. На сайтах электронной коммерции каждый результат оформлен в повторяющийся блок, поэтому выберите каждую карточку, а затем считывайте название, цену, рейтинг, изображение и ссылку на товар изнутри неё. Защитное чтение каждого поля не даёт одному отсутствующему значению сломать весь запуск.

javascript
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, передайте его парсеру и выведите структурированные записи. Это минимальная сквозная версия, которая краулит одну страницу поиска.

javascript
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 или базу данных.

json
[
  {
    "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 поиска.

javascript
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 есть своё место; в руководстве по парсингу данных электронной коммерции рассматривается, когда использовать каждый из них.

javascript
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 запросов бесплатно, без карты.

Самообслуживание · Звонок отдела продаж не требуется · Доступны корпоративные объёмы краулинга