Как спарсить Amazon через Playwright и мобильные прокси: полный пошаговый гайд
Содержание статьи
- Введение
- Предварительная подготовка
- Базовые понятия
- Шаг 1: установка node.js и подготовка проекта
- Шаг 2: установка playwright, playwright-extra и stealth-плагина
- Шаг 3: настройка мобильных прокси mobileproxy.space (ip и логин/пароль)
- Шаг 4: базовый скрипт запуска браузера с прокси и stealth
- Шаг 5: логика парсинга карточки товара amazon
- Шаг 6: рандомизация поведения, задержки, скролл, hover, клики
- Шаг 7: ротация user-agent и масштабирование на сотни товаров
- Шаг 8: сохранение результатов в json и csv
- Шаг 9: обработка ошибок, captcha, таймауты, retry с экспоненциальной задержкой
- Проверка результата
- Типичные ошибки и решения
- Дополнительные возможности
- Faq
- Заключение
Введение
В этом руководстве вы пройдете полный путь от пустой папки на компьютере до рабочего скрипта, который парсит карточки товаров Amazon через Playwright и мобильные прокси. Мы пошагово разберем установку Node.js и всех зависимостей, подключим stealth-плагины для снижения вероятности блокировок, настроим мобильные прокси с авторизацией по IP и логин/пароль, добавим эмуляцию реального поведения, рандомизацию задержек и ротацию User-Agent. Вы научитесь собирать название товара, цену, рейтинг, количество отзывов, ASIN, изображения и описание, а затем сохранять результат в JSON и CSV. Дополнительно внедрим обработку ошибок, распознаем CAPTCHA, реализуем retry с экспоненциальной задержкой и настроим масштабирование на сотни товаров.
Гайд рассчитан на начинающих с элементами для продвинутых. Мы объясняем простым языком, фиксируем каждый шаг и показываем, как проверить результат. Среднее время прохождения — от 4 до 8 часов, включая установку и тестирование. После завершения у вас будет воспроизводимый проект, который можно запустить на Windows, macOS или Linux. Также вы получите набор советов, как ускорить сбор данных без роста блокировок.
⚠️ Внимание: Скрейпинг может нарушать правила использования сайтов. Перед запуском убедитесь, что вы соблюдаете условия, действующее законодательство и требования к использованию данных в вашем регионе.
Совет: Сохраняйте промежуточные результаты и ведите заметки: какие прокси использовали, какие настройки задержек работали лучше, какие маркеты Amazon вы тестировали. Это ускорит отладку и масштабирование.
Предварительная подготовка
Прежде чем писать код, подготовим окружение и доступы. Вам понадобятся: доступ к компьютеру с интернетом, права на установку программ, учетная запись провайдера мобильных прокси, базовые знания командной строки. Даже если вы совсем новичок, не переживайте — ниже все описано щепетильно.
Необходимые инструменты и доступы
- Node.js версии 18 или выше (рекомендуется 20 LTS).
- Playwright и библиотека playwright-extra со stealth-плагином.
- Аккаунт мобильных прокси mobileproxy.space с активным тарифом.
- Редактор кода (подойдет любой, например, Visual Studio Code).
- Командная строка: Terminal на macOS и Linux, PowerShell или CMD на Windows.
Системные требования
- Операционная система: Windows 10/11, macOS 12+, Ubuntu 20.04+ или аналогичный Linux.
- Оперативная память: от 8 ГБ (рекомендуется 16 ГБ для параллельного парсинга).
- Диск: минимум 5 ГБ свободного места (браузерные бинарники Playwright занимают заметный объем).
- Стабильный интернет, желательно проводной.
Что скачать и установить
- Node.js: установите официальный дистрибутив и проверьте версии командой node -v и npm -v.
- Playwright и браузеры: установим через npm в процессе.
- Редактор кода: установите и откройте папку проекта.
Совет: Установите Git, чтобы версионировать проект и легко откатывать изменения. Это не обязательно, но очень полезно.
✅ Проверка: Команда node -v должна вывести версию (например, v20.11.0). Если команда не найдена, повторите установку Node.js и перезагрузите терминал.
Базовые понятия
Прежде чем писать скрипты, проясним ключевые термины и общие принципы работы. Это поможет избежать ошибок и быстрее понимать, что и зачем мы настраиваем.
Ключевые термины
- Парсинг — автоматическое извлечение данных со страниц сайта, например, названия, цен, рейтингов.
- Playwright — библиотека для управления браузерами (Chromium, Firefox, WebKit) через код. Позволяет нажимать кнопки, вводить текст, прокручивать страницу.
- Stealth — набор техник, снижающих вероятность распознавания автоматизации на сайте (скрытие автоматизации, правдоподобные параметры браузера).
- Мобильные прокси — прокси-серверы с мобильными IP-адресами, которые чаще считаются «человеческими» и реже блокируются.
- ASIN — уникальный идентификатор товара на Amazon.
- CAPTCHA — проверка «Вы робот?»; часто появляется при подозрительной активности.
- Retry — повторная попытка запроса при ошибке, часто с увеличением задержки (экспоненциальный backoff).
Основные принципы
- Не спешить: соблюдайте задержки, имитируйте реальные действия.
- Делайте скролл, hover и клики — это приближает поведение к пользователю.
- Рандомизируйте User-Agent и параметры контекста.
- Используйте прокси, лучше мобильные, и периодически меняйте IP.
- Обрабатывайте ошибки и добавляйте повторные попытки.
⚠️ Внимание: Большие объемы запросов без задержек почти гарантированно приведут к блокировкам и возможному появлению CAPTCHA. Уважайте ресурсы сайта и соблюдайте ограничения нагрузки.
✅ Проверка: Если вам понятны термины Playwright, прокси, ASIN, stealth и retry, можно переходить дальше. Если нет — перечитайте определения еще раз и держите их под рукой.
Шаг 1: Установка Node.js и подготовка проекта
Цель этапа
Подготовить рабочую папку и базовые файлы проекта Node.js, убедиться, что окружение готово к установке зависимостей.
Пошаговая инструкция
- Создайте папку проекта, например amazon-playwright-scraper.
- Откройте папку в терминале.
- Инициализируйте проект командой npm init -y. Будет создан файл package.json.
- Создайте подпапки src и data. В src будет код, в data — входные и выходные данные.
- Создайте файл .env в корне проекта для хранения конфиденциальных настроек: прокси, регион, константы. Пока оставьте пустым.
- Установите dotenv командой npm i dotenv для загрузки переменных окружения.
- Создайте файл src/index.js — это будет точка входа.
- Создайте файл data/input_asins.txt и добавьте несколько ASIN, по одному в строке (например, B0C61JQSG7, B08N5WRWNW). Эти ASIN вы можете взять из адресной строки карточек товара или из блока характеристик.
Важные моменты
- Структура проекта облегчает поддержку. Разделяйте код и данные.
- .env не коммитьте в общий репозиторий. В нем будут пароли и конфиденциальные настройки.
Совет: Сразу добавьте .gitignore и включите туда node_modules, datahost:port'.
Совет: Добавьте несколько прокси в список PROXY_SERVERS, чтобы у вас был запас для ротации и параллельных воркеров. Разделяйте их запятыми.
Ожидаемый результат
.env содержит корректные переменные, вы понимаете, как работает авторизация и как менять IP.
Возможные проблемы и решения
- Авторизация по IP не срабатывает. Решение: проверьте, какой внешний IP у вашей машины, добавьте его в белый список провайдера, перезапустите сессию.
- Неверный логин/пароль. Решение: проверьте учетные данные, сбросьте пароль в кабинете провайдера при необходимости.
✅ Проверка: В файле .env указаны PROXY_MODE, PROXY_SERVERS и MARKET. Вы уверены, что прокси активны и IP авторизован или логин/пароль верны.
Шаг 4: Базовый скрипт запуска браузера с прокси и stealth
Цель этапа
Создать минимальный рабочий скрипт, который запускает Chromium через playwright-extra со stealth-плагином, использует мобильный прокси, открывает главную страницу Amazon и корректно закрывается.
Пошаговая инструкция
- Откройте файл src/index.js.
- Добавьте базовый код импорта и инициализации: подключите dotenv, playwright-extra и stealth-плагин.
- Считайте переменные окружения из .env: режим прокси, список серверов, учетные данные, домен MARKET.
- Реализуйте функцию getProxyOptions, которая возвращает объект proxy для Playwright в зависимости от выбранного сервера и режима авторизации.
- Реализуйте простой запуск браузера с chromium.launch и проверкой, что страница MARKET загружается без ошибок.
- Закройте браузер и залогируйте успешность шага.
Пример кода для src/index.js (минимальный тест)
Скопируйте и вставьте, затем адаптируйте под себя:
require('dotenv').config();
const { chromium } = require('playwright-extra');
const stealth = require('puppeteer-extra-plugin-stealth')();
chromium.use(stealth);
const { setTimeout: sleep } = require('timers/promises');
const market = process.env.MARKET || 'https:/www.amazon.com';
const proxyMode = process.env.PROXY_MODE || 'ip';
const proxyServers = (process.env.PROXY_SERVERS || '').split(',').map(s => s.trim()).filter(Boolean);
const proxyUser = process.env.PROXY_USER || '';
const proxyPass = process.env.PROXY_PASS || '';
function pickProxy() {
if (!proxyServers.length) return null;
const i = Math.floor(Math.random() * proxyServers.length);
return proxyServers[i];
}
function getProxyOptions(server) {
if (!server) return {};
const [host, port] = server.split(':');
const proxyUrl = 'http:' + host + ':' + port;
if (proxyMode === 'login') {
return { server: proxyUrl, username: proxyUser, password: proxyPass };
}
return { server: proxyUrl };
}
(async () => {
const server = pickProxy();
const proxy = getProxyOptions(server);
const browser = await chromium.launch({ headless: true, proxy });
const context = await browser.newContext({
viewport: { width: 1366, height: 768 },
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
});
const page = await context.newPage();
await page.goto(market, { waitUntil: 'domcontentloaded', timeout: 45000 });
await sleep(1000 + Math.random() * 2000);
await browser.close();
console.log('Base launch OK via proxy:', server || 'no-proxy');
})().catch(err => {
console.error('Base launch failed', err);
process.exit(1);
});
Важные моменты
- Для смены прокси в Playwright обычно требуется новый процесс браузера. Планируйте это при масштабировании.
- stealth подключается через chromium.use(stealth). Это должно быть до launch.
Совет: На этапе отладки используйте headless: false, чтобы видеть, что происходит в окне браузера. При массовом парсинге верните headless: true.
Ожидаемый результат
Скрипт открывает главную страницу маркетплейса Amazon через ваш прокси, затем закрывает браузер без ошибок.
Возможные проблемы и решения
- Страница не открывается. Причина: прокси недоступен. Решение: замените сервер на рабочий или временно запустите без прокси.
- Ошибка авторизации прокси. Причина: неверный логин/пароль или IP. Решение: перепроверьте .env, исправьте данные.
✅ Проверка: В консоли выводится Base launch OK via proxy и адрес прокси (или no-proxy, если не настроено). Ошибок нет.
Шаг 5: Логика парсинга карточки товара Amazon
Цель этапа
Научиться открывать страницу товара по ASIN, корректно выбирать селекторы, извлекать название, цену, рейтинг, количество отзывов, ASIN, изображения и описание. Реализуем одну универсальную функцию parseProduct.
Пошаговая инструкция
- Создайте файл src/scraper.js для логики парсинга.
- Реализуйте функцию buildProductUrl(market, asin), которая возвращает ссылку вида market + '/dp/' + asin.
- Добавьте функцию extractors с безопасным получением текста по селекторам с несколькими fallback.
- Реализуйте parseProduct(page, asin), которая открывает карточку и возвращает объект данных.
- Подключите scraper.js в index.js и протестируйте на 1–2 ASIN.
Пример кода для src/scraper.js
Скопируйте и адаптируйте:
const { setTimeout: sleep } = require('timers/promises');
function buildProductUrl(market, asin) {
return market.replace(/\/+$/, '') + '/dp/' + asin;
}
async function getText(page, selectors) {
for (const sel of selectors) {
try {
const el = await page.locator(sel).first();
if (await el.count()) {
const txt = (await el.innerText()).trim();
if (txt) return txt;
}
} catch {}
}
return '';
}
async function getAttrAll(page, selector, attr) {
try {
const els = await page.locator(selector);
const count = await els.count();
const out = [];
for (let i = 0; i < count; i++) {
const el = els.nth(i);
const v = await el.getAttribute(attr);
if (v) out.push(v);
}
return out;
} catch {
return [];
}
}
function normalizePrice(str) {
if (!str) return '';
const m = str.replace(/[\s,]/g, '').match(/([0-9]+(?:\.[0-9]{1,2})?)/);
return m ? m[1] : '';
}
function parseReviewsCount(str) {
if (!str) return 0;
const n = str.replace(/[^0-9]/g, '');
return Number(n || '0');
}
async function parseProduct(page, market, asin) {
const url = buildProductUrl(market, asin);
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
try {
await page.waitForTimeout(500 + Math.random() * 700);
const accept = page.locator('input#sp-cc-accept, button#sp-cc-accept, input[name="accept"]');
if (await accept.count()) {
await accept.click({ delay: 50 + Math.random() * 60 });
await page.waitForTimeout(300 + Math.random() * 400);
}
} catch {}
const title = await getText(page, ['#productTitle', 'h1 span#productTitle', 'span#productTitle']);
const priceRaw = await getText(page, ['#corePriceDisplay_desktop_feature_div span.a-offscreen', '.a-price .a-offscreen', '#priceblock_ourprice', '#priceblock_dealprice']);
const price = normalizePrice(priceRaw);
const ratingRaw = await getText(page, ['i[data-hook="average-star-rating"] span.a-icon-alt', 'span[data-hook="rating-out-of-text"]', 'i.a-icon-star span.a-icon-alt']);
const rating = ratingRaw ? (ratingRaw.match(/[0-9,.]+/) || [''])[0] : '';
const reviewsRaw = await getText(page, ['#acrCustomerReviewText', '#acrCustomerReviewLink #acrCustomerReviewText']);
const reviewsCount = parseReviewsCount(reviewsRaw);
let asinFound = asin;
try {
const asinInput = page.locator('input#ASIN, input[name="ASIN"]');
if (await asinInput.count()) {
const v = await asinInput.first().getAttribute('value');
if (v) asinFound = v;
}
} catch {}
const thumbs = await getAttrAll(page, ['#altImages img', '#imageBlockThumbs img'].join(','), 'src');
const images = Array.from(new Set(thumbs.map(s => s.replace(/\._.*_\.jpg/i, '.jpg'))));
const description = await getText(page, ['#productDescription', '#bookDescription_feature_div', '#productFactsDesktopExpander']);
const bulletsEls = await page.locator('#feature-bullets ul li span');
const bulletsCount = await bulletsEls.count();
const bullets = [];
for (let i = 0; i < bulletsCount; i++) {
const t = (await bulletsEls.nth(i).innerText()).trim();
if (t && !/^\s*\(.*\)\s*$/.test(t)) bullets.push(t);
}
const features = bullets.join(' | ');
return { url, asin: asinFound, title, price, rating, reviewsCount, images, description, features };
}
module.exports = { parseProduct, buildProductUrl };
Интеграция со src/index.js (тест 1–2 ASIN)
Пример:
require('dotenv').config();
const { chromium } = require('playwright-extra');
const stealth = require('puppeteer-extra-plugin-stealth')();
chromium.use(stealth);
const { parseProduct } = require('./scraper');
const { setTimeout: sleep } = require('timers/promises');
const market = process.env.MARKET || 'https:/www.amazon.com';
const proxyMode = process.env.PROXY_MODE || 'ip';
const proxyServers = (process.env.PROXY_SERVERS || '').split(',').map(s => s.trim()).filter(Boolean);
const proxyUser = process.env.PROXY_USER || '';
const proxyPass = process.env.PROXY_PASS || '';
function pickProxy() {
if (!proxyServers.length) return null;
const i = Math.floor(Math.random() * proxyServers.length);
return proxyServers[i];
}
function getProxyOptions(server) {
if (!server) return {};
const [host, port] = server.split(':');
const proxyUrl = 'http:' + host + ':' + port;
if (proxyMode === 'login') {
return { server: proxyUrl, username: proxyUser, password: proxyPass };
}
return { server: proxyUrl };
}
(async () => {
const server = pickProxy();
const proxy = getProxyOptions(server);
const browser = await chromium.launch({ headless: true, proxy });
const context = await browser.newContext({
viewport: { width: 1366, height: 768 },
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
locale: 'en-US'
});
const page = await context.newPage();
const asin = 'B08N5WRWNW';
const data = await parseProduct(page, market, asin);
console.log(data);
await browser.close();
})().catch(console.error);
Важные моменты
- У Amazon динамический интерфейс. Мы используем несколько селекторов для надежности.
- ASIN можно получить и из URL, и из скрытого поля. Мы страхуемся.
Совет: Если данные не находятся, временно используйте page.screenshot с сохранением картинки в data/debug-скриншоты. По скриншоту проще понять, что отрисовалось и какие селекторы доступны.
Ожидаемый результат
В консоли появляется объект с полями asin, title, price, rating, reviewsCount, images, description, features. Некоторые поля могут быть пустыми на отдельных карточках — это нормально, позже добавим дополнительные проверки.
Возможные проблемы и решения
- Пустой title. Причина: баннеры закрывают контент. Решение: закрывайте баннеры или ждите дольше.
- Пустая цена. Причина: нет цены на конкретной карточке. Решение: добавьте fallback или парсинг блока вариантов.
✅ Проверка: Вы видите корректный title и непустой ASIN. Хотя бы одно изображение должно присутствовать в массиве images.
Шаг 6: Рандомизация поведения, задержки, скролл, hover, клики
Цель этапа
Снизить вероятность детекта автоматизации. Добавим случайные задержки, движения мыши, прокрутку, hover на элементы, клики и разные схемы ожидания.
Пошаговая инструкция
- Создайте файл src/humanize.js с функциями случайной задержки и поведения.
- Реализуйте randomDelay(min, max), randomMouseMove(page), humanScroll(page), hoverRandomThumb(page).
- Вызовите эти функции в процессе парсинга перед извлечением данных.
- Добавьте непредсказуемость: разные порядки действий, случайные точки кликов, небольшие паузы.
Пример кода для src/humanize.js
Пример:
const { setTimeout: sleep } = require('timers/promises');
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
async function randomDelay(minMs, maxMs) {
const t = rand(minMs, maxMs);
await sleep(t);
}
async function randomMouseMove(page) {
const w = 1366, h = 768;
const steps = rand(3, 9);
let x = rand(10, w - 10), y = rand(10, h - 10);
for (let i = 0; i < steps; i++) {
x = Math.max(1, Math.min(w - 1, x + rand(-60, 60)));
y = Math.max(1, Math.min(h - 1, y + rand(-40, 40)));
await page.mouse.move(x, y, { steps: rand(2, 5) });
await sleep(rand(40, 120));
}
}
async function humanScroll(page) {
const total = rand(2000, 6000);
let scrolled = 0;
while (scrolled < total) {
const delta = rand(200, 600);
await page.mouse.wheel(0, delta);
scrolled += delta;
await sleep(rand(200, 600));
}
}
async function hoverRandomThumb(page) {
const thumbs = page.locator('#altImages li, #imageBlockThumbs li');
const count = await thumbs.count();
if (count > 0) {
const i = rand(0, Math.min(count - 1, 4));
const el = thumbs.nth(i);
await el.hover();
await sleep(rand(200, 700));
}
}
module.exports = { randomDelay, randomMouseMove, humanScroll, hoverRandomThumb, rand };
Интеграция
В src/scraper.js, перед извлечением данных, вызовите:
const human = require('./humanize');
await human.randomMouseMove(page);
await human.humanScroll(page);
await human.hoverRandomThumb(page);
await human.randomDelay(400, 1100);
Важные моменты
- Слишком длинные задержки замедлят сбор, слишком короткие — повысят риски. Балансируйте.
- Имитация действий не должна быть шаблонной. Добавляйте случайность.
Совет: Сложите задержки в .env, чтобы не менять код: MIN_DELAY_MS и MAX_DELAY_MS. В humanize.js оттуда читайте значения.
Ожидаемый результат
Скрипт стал «вести себя как человек»: делает небольшие паузы, водит мышью, скроллит, наводит курсор на миниатюры. Блокировок и CAPTCHA меньше.
Возможные проблемы и решения
- Сильное замедление. Решение: уменьшите число действий, уберите часть hover, оптимизируйте задержки.
- CAPTCHA все равно появляется. Решение: добавьте прокси-ротацию и измените скорость запросов.
✅ Проверка: При запуске headless: false видно, что курсор двигается, страница скроллится, миниатюры подсвечиваются при hover. Ошибок нет.
Шаг 7: Ротация User-Agent и масштабирование на сотни товаров
Цель этапа
Снизить корреляцию между запросами. Добавим ротацию User-Agent, ограничим параллелизм, подготовим очередь задач и управление прокси-пулами.
Пошаговая инструкция
- Установленный пакет user-agents позволяет брать правдоподобные строки. Подготовьте функцию getRandomUA.
- Сделайте простую очередь: ограничьте число одновременно работающих браузеров с p-limit или p-queue.
- Добавьте пул прокси: выбирайте свободный прокси из списка, запускайте под него браузер, возвращайте в пул после завершения.
- Переработайте index.js, чтобы читать ASIN из файла, создавать задачи и сохранять результаты.
Пример кода для src/index.js (масштабируемая версия)
Пример:
require('dotenv').config();
const { chromium } = require('playwright-extra');
const stealth = require('puppeteer-extra-plugin-stealth')();
chromium.use(stealth);
const fs = require('fs');
const path = require('path');
const PQueue = require('p-queue').default;
const UserAgents = require('user-agents');
const { parseProduct } = require('./scraper');
const human = require('./humanize');
const market = process.env.MARKET || 'https:/www.amazon.com';
const proxyMode = process.env.PROXY_MODE || 'ip';
const proxyServers = (process.env.PROXY_SERVERS || '').split(',').map(s => s.trim()).filter(Boolean);
const proxyUser = process.env.PROXY_USER || '';
const proxyPass = process.env.PROXY_PASS || '';
const MIN_DELAY_MS = Number(process.env.MIN_DELAY_MS || '800');
const MAX_DELAY_MS = Number(process.env.MAX_DELAY_MS || '2000');
const CONCURRENCY = Number(process.env.CONCURRENCY || '2');
function getRandomUA() {
const ua = new UserAgents({ deviceCategory: 'desktop' }).toString();
return ua;
}
function getProxyOptions(server) {
if (!server) return {};
const [host, port] = server.split(':');
const proxyUrl = 'http:' + host + ':' + port;
if (proxyMode === 'login') {
return { server: proxyUrl, username: proxyUser, password: proxyPass };
}
return { server: proxyUrl };
}
async function withBrowser(server, fn) {
const proxy = getProxyOptions(server);
const browser = await chromium.launch({ headless: true, proxy });
try {
const context = await browser.newContext({
viewport: { width: 1366, height: 768 },
userAgent: getRandomUA(),
locale: 'en-US'
});
const page = await context.newPage();
await fn(page);
await context.close();
} finally {
await browser.close();
}
}
async function processAsin(server, asin) {
let result = null;
await withBrowser(server, async page => {
await human.randomDelay(MIN_DELAY_MS, MAX_DELAY_MS);
result = await parseProduct(page, market, asin);
});
return result;
}
function readAsins(file) {
const p = path.resolve(file);
const txt = fs.readFileSync(p, 'utf8');
return txt.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
}
(async () => {
const asins = readAsins('data/input_asins.txt');
const queue = new PQueue({ concurrency: CONCURRENCY });
const outputs = [];
let proxyIndex = 0;
function nextProxy() {
if (!proxyServers.length) return null;
proxyIndex = (proxyIndex + 1) % proxyServers.length;
return proxyServers[proxyIndex];
}
for (const asin of asins) {
queue.add(async () => {
const server = nextProxy();
const data = await processAsin(server, asin);
outputs.push(data);
console.log('Done', asin, data && data.title ? 'OK' : 'EMPTY');
await human.randomDelay(MIN_DELAY_MS, MAX_DELAY_MS);
});
}
await queue.onIdle();
fs.writeFileSync('data/output.json', JSON.stringify(outputs, null, 2));
console.log('Saved to data/output.json');
})().catch(err => {
console.error(err);
process.exit(1);
});
Важные моменты
- CONCURRENCY подбирайте осторожно. Начните с 1–2 и увеличивайте до 3–4, наблюдая за блокировками.
- User-Agent меняем при создании контекста. Это добавляет разнообразие.
Совет: Для отдельных рынков Amazon используйте локаль и часовой пояс контекста, соответствующие региону. Например, для Германии locale: 'de-DE'.
Ожидаемый результат
Скрипт обрабатывает список ASIN, использует пул прокси и сохраняет массив результатов в output.json.
Возможные проблемы и решения
- Чрезмерные блокировки. Решение: уменьшите CONCURRENCY, увеличьте задержки, добавьте больше прокси.
- Низкая скорость. Решение: оптимизируйте задержки, но без фанатизма, увеличьте пул прокси.
✅ Проверка: В data/output.json присутствуют объекты с полями title и asin. Большинство карточек заполнились корректно.
Шаг 8: Сохранение результатов в JSON и CSV
Цель этапа
Добавить надежное сохранение в два формата. JSON — для гибкого анализа, CSV — для быстрого импорта в таблицы и BI-системы.
Пошаговая инструкция
- Создайте файл src/save.js с функциями saveJSON и saveCSV.
- Добавьте формирование заголовков CSV и нормализацию данных (экранирование разделителей).
- В index.js вызовите сохранение после завершения очереди.
- Проверьте корректность файлов в data/output.json и data/output.csv.
Пример кода для src/save.js
Пример:
const fs = require('fs');
const path = require('path');
function saveJSON(arr, file) {
const p = path.resolve(file);
fs.writeFileSync(p, JSON.stringify(arr, null, 2), 'utf8');
}
function escapeCsv(v) {
if (v == null) return '';
const s = String(v);
if (/[",\n;]/.test(s)) return '"' + s.replace(/"/g, '""') + '"';
return s;
}
function saveCSV(arr, file) {
const p = path.resolve(file);
const header = ['asin','title','price','rating','reviewsCount','images','description','features','url'];
const rows = [header.join(';')];
for (const it of arr) {
if (!it) continue;
const row = [
it.asin || '',
it.title || '',
it.price || '',
it.rating || '',
it.reviewsCount || 0,
(it.images || []).join(' '),
it.description || '',
it.features || '',
it.url || ''
].map(escapeCsv).join(';');
rows.push(row);
}
fs.writeFileSync(p, rows.join('\n'), 'utf8');
}
module.exports = { saveJSON, saveCSV };
Интеграция
В src/index.js после обработки очереди импортируйте сохранение:
const { saveJSON, saveCSV } = require('./save');
saveJSON(outputs, 'data/output.json');
saveCSV(outputs, 'data/output.csv');
Важные моменты
- В CSV используйте стабильный разделитель, например точку с запятой. Это упростит импорт в разные таблицы.
- Экранируйте кавычки и переносы строк в текстах.
Совет: Добавьте timestamp в имя файла для версионирования, например data/output_2026-02-01_1200.csv.
Ожидаемый результат
В папке data появились два файла: output.json и output.csv. В JSON удобный массив объектов; в CSV — строки с заголовком.
Возможные проблемы и решения
- Кодировка. Решение: сохраняйте в UTF-8 и открывайте в редакторе с соответствующей кодировкой.
- Большие файлы. Решение: сохраняйте частями или построчно при больших объемах данных.
✅ Проверка: Откройте output.csv в любой таблице. Данные в столбцах не смещены, текст в кавычках, изображения объединены пробелом.
Шаг 9: Обработка ошибок, CAPTCHA, таймауты, retry с экспоненциальной задержкой
Цель этапа
Повысить надежность. Добавим распознавание проблемных ситуаций, повторные попытки с увеличением задержки, смену прокси при фейлах. Подготовим логи для анализа.
Пошаговая инструкция
- Добавьте функцию isCaptchaPage(page), которая проверяет URL на наличие слов captcha и наличие элементов проверки.
- Добавьте retry-обертку вокруг processAsin с экспоненциальной задержкой и джиттером.
- При повторных ошибках меняйте прокси и User-Agent.
- Логируйте коды и шаги, сохраняйте ошибочные страницы при необходимости.
Пример кода для retry в src/index.js
Пример:
function isCaptchaHtml(html) {
return /captcha/i.test(html) || /Type the characters/i.test(html) || /Enter the characters/i.test(html);
}
async function tryProcessWithRetry(asin) {
const maxRetries = Number(process.env.MAX_RETRIES || '5');
let attempt = 0;
let lastError = null;
while (attempt < maxRetries) {
attempt++;
let server = proxyServers.length ? proxyServers[(attempt - 1) % proxyServers.length] : null;
try {
const data = await withBrowser(server, async page => {
await human.randomDelay(MIN_DELAY_MS, MAX_DELAY_MS);
await page.goto(market.replace(/\/$/, '') + '/dp/' + asin, {
waitUntil: 'domcontentloaded',
timeout: 60000
});
const html = await page.content();
if (isCaptchaHtml(html)) {
throw new Error('CAPTCHA');
}
for (let i = 0; i < 2; i++) await human.randomMouseMove(page);
return await parseProduct(page, market, asin);
});
if (data && data.title) return data;
throw new Error('Empty data');
} catch (err) {
lastError = err;
const base = 1200;
const backoff = Math.min(base * Math.pow(2, attempt - 1), 20000);
const jitter = Math.floor(Math.random() * 400);
console.warn('Retry', attempt, 'ASIN', asin, 'server', server, 'err', err.message);
await new Promise(r => setTimeout(r, backoff + jitter));
}
}
throw lastError || new Error('Unknown error');
}
Интеграция в очередь
Замените в очереди процессинг:
const data = await tryProcessWithRetry(asin);
Важные моменты
- Экспоненциальный backoff помогает разгрузить частые повторы.
- При частых CAPTCHA снизьте параллелизм и увеличьте паузы. Смените IP.
Совет: Если провайдер мобильных прокси поддерживает принудительную смену IP по URL, вызывайте этот эндпоинт после нескольких подряд неудач. Делайте паузу 30–60 секунд после смены IP.
⚠️ Внимание: Не пытайтесь обходить защиту вредоносными методами. Не используйте взлом или несанкционированный доступ. Работайте только в рамках законных и этичных практик.
Ожидаемый результат
Скрипт устойчивее выдерживает блокировки. Ошибки повторяются с паузами, прокси и User-Agent меняются, итоговые данные сохраняются для большинства ASIN.
Возможные проблемы и решения
- Много пустых карточек. Решение: увеличьте таймаут загрузки, добавьте ожидания конкретных селекторов.
- Повторные CAPTCHA. Решение: уменьшите скорость, добавьте больше прокси, выполняйте парсинг в другие временные окна.
✅ Проверка: В логах видны шаги Retry. После нескольких попыток большинство ASIN парсятся успешно. Количество фатальных ошибок снизилось.
Проверка результата
Чек-лист
- Node.js установлен, npm работает.
- Playwright и браузер Chromium установлены.
- Stealth-плагин активен.
- Мобильные прокси подключены (IP или логин/пароль).
- Скрипт открывает Amazon и парсит карточку по ASIN.
- Данные сохраняются в JSON и CSV.
- Работает retry с backoff, обрабатываются CAPTCHA и таймауты.
- Скрипт масштабируется на десятки и сотни ASIN с очередью и пулом прокси.
Как протестировать
- Запустите на 3–5 ASIN. Убедитесь, что в output.json есть все ключевые поля.
- Увеличьте список до 50 ASIN. Проверьте стабильность, подсчитайте долю успешных парсингов.
- Попробуйте разное CONCURRENCY: 1, 2, 3. Выберите оптимум по скорости и блокировкам.
Показатели успеха
- Успешно спарсено не менее 80% карточек на небольшой выборке.
- CAPTCHA возникает редко и обрабатывается retry.
- Данные корректны, загружаются в таблицы без смещения колонок.
✅ Проверка: Если при повторном запуске на том же наборе ASIN вы стабильно получаете сопоставимые результаты, ваш пайплайн готов к масштабированию.
Типичные ошибки и решения
- Проблема: пустой title. Причина: страница не полностью прогрузилась. Решение: увеличьте таймаут и добавьте ожидание #productTitle.
- Проблема: пустая цена. Причина: нет цены для текущей вариации. Решение: парсите .a-price .a-offscreen и альтернативные селекторы, переключайте вариации при необходимости.
- Проблема: CAPTCHA на каждом шаге. Причина: слишком быстрый парсинг или повторяющиеся параметры. Решение: уменьшите параллелизм, добавьте задержки, ротацию IP и User-Agent.
- Проблема: таймауты. Причина: медленный прокси. Решение: смените прокси, увеличьте таймаут, добавьте retry с backoff.
- Проблема: сбой авторизации прокси. Причина: неверные креды или список IP. Решение: перепроверьте .env, белый список IP, перезапустите сессию.
- Проблема: CSV открывается с «ломаными» строками. Причина: неэкранированные кавычки и переносы. Решение: добавьте escapeCsv и используйте стабильный разделитель.
- Проблема: падение памяти при сотнях карточек. Причина: слишком много параллельных браузеров. Решение: уменьшите CONCURRENCY, закрывайте контексты и браузеры своевременно.
Дополнительные возможности
Продвинутые настройки
- Локали и часовые пояса: создавайте контексты с timezoneId, locale и geolocation, соответствующими рынку.
- Настройки устройств: имитируйте ноутбук или планшет с помощью viewport и user agent конкретного устройства.
- Кэширование: сохраняйте промежуточные результаты, чтобы не перезапрашивать карточки.
- Ротация IP по событию: при 2–3 ошибках подряд вызывайте ротацию IP у провайдера, затем ждите и повторяйте попытку.
Оптимизация
- Разогрев: делайте 1–2 «разогревающих» хита на нейтральные страницы, чтобы прогрузить куки и скрипты.
- Гибкая очередь: увеличивайте параллелизм постепенно при низком уровне ошибок.
- Хранение логов: сохраняйте логи в файлы с датой, анализируйте пики ошибок.
- Распределенный запуск: запускайте несколько экземпляров на разных машинах/серверных локациях.
Совет: Добавьте мониторинг метрик: среднее время парсинга на карточку, процент успешных запросов, частоту CAPTCHA, скорость сети и медленные прокси.
Совет: Храните результаты в базе данных (например, SQLite или Postgres), если объем растет. Это упростит аналитику и обновления.
Совет: Добавьте дедупликацию: если один и тот же ASIN встречается несколько раз, обновляйте запись, а не создавайте дубликаты.
Совет: Используйте расписание (cron) для регулярного обновления цен и отзывов. Запускайте в часы меньшей нагрузки.
Совет: Для некоторых карточек полезно загружать вкладку отзывов и парсить несколько страниц отзывов. Делайте это выборочно, чтобы не перегружать.
FAQ
- Можно ли парсить без прокси? Да, но риски блокировок выше. Мобильные прокси значительно уменьшают частоту CAPTCHA и банов.
- Как выбрать регион Amazon? Укажите MARKET в .env, например https://www.amazon.com или другой домен. Подберите locale контекста под регион.
- Нужен ли headless? Для отладки лучше headless: false, для серийного парсинга headless: true. Это быстрее и стабильнее.
- Почему цена иногда пустая? На некоторых карточках нет цены или она спрятана в вариациях. Добавьте дополнительные селекторы и логику переключения вариаций.
- Как понять, что меня заблокировали? Частые CAPTCHA, редирект на страницу с проверкой, пустые блоки. Снижайте скорость, меняйте IP, увеличивайте задержки.
- Сколько потоков можно запускать? Начните с 1–2. Если ошибок нет, попробуйте 3–4. Наблюдайте метрики и балансируйте с количеством прокси.
- Можно ли сохранять изображения? Мы сохраняем ссылки. Чтобы скачивать файлы, пройдитесь по массиву images и скачайте по одному с задержкой.
- Как обновлять данные? Храните прошлые результаты вместе с timestamp и регулярно запускайте скрипт на список ASIN. Обновляйте только изменившиеся поля.
- Чем мобильные прокси лучше обычных? Мобильные IP реже попадают под строгие блокировки и воспринимаются ближе к реальному трафику от пользователей мобильных сетей.
- Что делать, если провайдер прокси медленный? Добавьте еще несколько прокси, уменьшите параллелизм, отбрасывайте медленные серверы по метрикам времени отклика.
Заключение
Вы настроили полноценный пайплайн парсинга Amazon: установили Node.js и Playwright, подключили playwright-extra и stealth-плагин, конфигурировали мобильные прокси с авторизацией по IP и логин/пароль, добавили эмуляцию реального поведения, рандомизацию задержек и ротацию User-Agent. Вы научились извлекать ключевые данные карточек — название, цену, рейтинг, количество отзывов, ASIN, изображения и описание — и сохранять их в JSON и CSV. Мы внедрили обработку ошибок, распознавание CAPTCHA и retry с экспоненциальным backoff, а также масштабирование на сотни товаров через очередь и пул прокси.
Дальше вы можете углубляться: парсинг вариаций товаров, сбор отзывов по страницам, интеграция с базой данных, шедулинг регулярных запусков, мониторинг метрик и авто-переключение прокси при низкой успешности. Начинайте с маленьких партий, отлаживайте поведение, затем расширяйте масштаб.
⚠️ Внимание: Всегда учитывайте юридические ограничения и правила площадки. Соблюдайте разумный темп запросов, не вредите инфраструктуре платформы и уважайте конфиденциальность пользователей.
Совет: Автоматизируйте отчетность: после каждого запуска сохраняйте общую статистику (успехи, неудачи, среднее время), чтобы быстро принимать решения по оптимизации.