Разработка бота-парсера товаров с маркетплейсов (Ozon, Wildberries, Amazon)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка бота-парсера товаров с маркетплейсов (Ozon, Wildberries, Amazon)
Сложная
~5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1240
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1167
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    867
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1084
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    829
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    846

Разработка бота-парсера товаров с маркетплейсов (Ozon, Wildberries, Amazon)

Маркетплейсы — это другой класс задач по сравнению с обычным парсингом сайтов поставщиков. Ozon и Wildberries активно противодействуют скрапингу: защита Cloudflare, динамический JS, fingerprinting браузеров, rate limiting. Для каждого маркетплейса — отдельная стратегия.

Законные API vs парсинг

Прежде чем парсить, стоит проверить официальные возможности:

Маркетплейс Официальный API Ограничения
Ozon Seller API (для продавцов) Только свои товары
Wildberries Seller API, Statistics API Только свои данные
Amazon Product Advertising API Требует партнёрство
Яндекс.Маркет Партнёрский API Для партнёров

Парсинг чужих товаров с маркетплейсов — в серой зоне ToS. Используется для конкурентного анализа, мониторинга цен, исследования рынка.

Стратегия для Wildberries

Wildberries имеет публичные JSON API для карточек товаров, которые не требуют авторизации:

# scraper/wildberries.py
import httpx
import asyncio
from typing import Optional

class WildberriesScraper:
    # Публичные API эндпоинты WB (меняются — нужен мониторинг)
    CARD_URL = "https://card.wb.ru/cards/v2/detail"
    SEARCH_URL = "https://search.wb.ru/exactmatch/ru/common/v9/search"
    CATALOG_URL = "https://catalog.wb.ru/catalog/{shard}/v2/catalog"

    def __init__(self):
        self.client = httpx.AsyncClient(
            headers={
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
                "Accept": "*/*",
                "Origin": "https://www.wildberries.ru",
                "Referer": "https://www.wildberries.ru/",
            },
            timeout=15,
        )

    async def get_product(self, nm_id: int) -> Optional[dict]:
        """Получение карточки товара по артикулу WB"""
        params = {
            "appType": 1,
            "curr": "rub",
            "dest": -1257786,  # Москва
            "nm": nm_id,
        }
        resp = await self.client.get(self.CARD_URL, params=params)
        resp.raise_for_status()

        data = resp.json()
        products = data.get("data", {}).get("products", [])
        if not products:
            return None

        return self._normalize_product(products[0])

    def _normalize_product(self, raw: dict) -> dict:
        sizes = raw.get("sizes", [])
        price_data = sizes[0].get("price", {}) if sizes else {}

        return {
            "nm_id": raw["id"],
            "name": raw.get("name"),
            "brand": raw.get("brand"),
            "supplier_id": raw.get("supplierId"),
            "rating": raw.get("reviewRating"),
            "feedbacks": raw.get("feedbacks"),
            "price": price_data.get("product", 0) / 100,
            "sale_price": price_data.get("total", 0) / 100,
            "discount": raw.get("sale", 0),
            "colors": [c["name"] for c in raw.get("colors", [])],
        }

    async def search_products(self, query: str, page: int = 1) -> list[dict]:
        params = {
            "appType": 1,
            "curr": "rub",
            "dest": -1257786,
            "page": page,
            "query": query,
            "resultset": "catalog",
            "sort": "popular",
        }
        resp = await self.client.get(self.SEARCH_URL, params=params)
        resp.raise_for_status()

        products = resp.json().get("data", {}).get("products", [])
        return [self._normalize_product(p) for p in products]

    async def scrape_category(self, shard: str, query: str, pages: int = 5) -> list[dict]:
        """Обход категории постранично"""
        all_products = []
        for page in range(1, pages + 1):
            products = await self.search_products(query, page)
            if not products:
                break
            all_products.extend(products)
            await asyncio.sleep(1.5)  # Пауза между запросами
        return all_products

Стратегия для Ozon

Ozon использует React-приложение, данные передаются через XHR. Перехват API-запросов через браузерную автоматизацию:

# scraper/ozon.py
from playwright.async_api import async_playwright
import json

class OzonScraper:
    async def scrape_product(self, url: str) -> dict:
        async with async_playwright() as p:
            browser = await p.chromium.launch(headless=True)
            context = await browser.new_context(
                user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
                viewport={"width": 1366, "height": 768},
            )

            # Перехватываем API-ответы с данными товара
            product_data = {}

            async def handle_response(response):
                if "/api/entrypoint-api.bx/page/json" in response.url:
                    try:
                        data = await response.json()
                        # Структура ответа Ozon: data.widgetStates содержит JSON-строки
                        widget_states = data.get("widgetStates", {})
                        for key, value in widget_states.items():
                            if "webProductHeading" in key:
                                product_data["heading"] = json.loads(value)
                            elif "webPrice" in key:
                                product_data["price"] = json.loads(value)
                    except Exception:
                        pass

            context.on("response", handle_response)
            page = await context.new_page()

            await page.goto(url, wait_until="networkidle")
            await browser.close()

            return self._normalize_ozon(product_data)

    def _normalize_ozon(self, data: dict) -> dict:
        heading = data.get("heading", {})
        price = data.get("price", {})

        return {
            "name": heading.get("title"),
            "sku": heading.get("sku"),
            "price": self._parse_price(price.get("price", "")),
            "original_price": self._parse_price(price.get("originalPrice", "")),
            "discount": price.get("discount"),
        }

    def _parse_price(self, s: str) -> float:
        return float("".join(c for c in s if c.isdigit() or c == ".") or 0)

Amazon через официальный API

Для Amazon предпочтительно использовать Product Advertising API 5.0:

# scraper/amazon_pa.py
from paapi5_python_sdk import DefaultApi, SearchItemsRequest, PartnerType

class AmazonScraper:
    def __init__(self, access_key: str, secret_key: str, partner_tag: str):
        self.api = DefaultApi(
            access_key=access_key,
            secret_key=secret_key,
            host="webservices.amazon.com",
            region="us-east-1",
        )
        self.partner_tag = partner_tag

    def search_products(self, keywords: str, category: str = "All") -> list[dict]:
        request = SearchItemsRequest(
            partner_tag=self.partner_tag,
            partner_type=PartnerType.ASSOCIATES,
            keywords=keywords,
            search_index=category,
            item_count=10,
            resources=[
                "ItemInfo.Title",
                "Offers.Listings.Price",
                "Images.Primary.Large",
                "ItemInfo.Features",
            ],
        )
        response = self.api.search_items(request)
        return [self._normalize(item) for item in response.search_result.items]

    def _normalize(self, item) -> dict:
        price = None
        if item.offers and item.offers.listings:
            price = item.offers.listings[0].price.amount

        return {
            "asin": item.asin,
            "title": item.item_info.title.display_value if item.item_info else None,
            "price": price,
            "image": item.images.primary.large.url if item.images else None,
            "url": item.detail_page_url,
        }

Laravel: оркестрация парсеров

// app/Console/Commands/ScrapeMarketplace.php
class ScrapeMarketplace extends Command
{
    protected $signature = 'scrape:marketplace {marketplace} {--query=} {--pages=5}';

    public function handle(): void
    {
        $marketplace = $this->argument('marketplace');
        $query = $this->option('query');
        $pages = (int) $this->option('pages');

        // Запуск Python-скрипта через Process
        $process = new Process([
            'python3', base_path('scraper/run.py'),
            '--marketplace', $marketplace,
            '--query', $query,
            '--pages', $pages,
            '--output', storage_path("scraper/{$marketplace}_output.json"),
        ]);

        $process->setTimeout(300)->run();

        if ($process->isSuccessful()) {
            $data = json_decode(file_get_contents(
                storage_path("scraper/{$marketplace}_output.json")
            ), true);

            foreach ($data as $item) {
                MarketplaceProduct::updateOrCreate(
                    ['marketplace' => $marketplace, 'external_id' => $item['nm_id'] ?? $item['asin']],
                    $item + ['scraped_at' => now()]
                );
            }

            $this->info("Импортировано: " . count($data) . " товаров");
        } else {
            Log::error($process->getErrorOutput());
        }
    }
}

Anti-detection меры

Угроза Решение
IP-блокировка Rotating proxy (bright data, IPRoyal)
User-Agent fingerprint Randomize + обновление
Browser fingerprint Playwright stealth plugin
Rate limiting Случайные паузы 1-5 сек
CAPTCHA 2captcha / anti-captcha API
Honeypot links Фильтрация невидимых ссылок

Срок разработки

Маркетплейс Сложность Срок
Wildberries (JSON API) Средняя 3-5 дней
Ozon (Playwright) Высокая 5-8 дней
Amazon (PA API) Низкая 2-3 дня
Яндекс.Маркет Средняя 3-5 дней
+ Мониторинг и алерты +2-3 дня