Розробка бота-парсера товарів з сайтів постачальників

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка бота-парсера товарів з сайтів постачальників
Середня
~5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка бота-парсера товарів з сайтів поставщиків

Парсер сайтів поставщиків автоматизує отримання даних про товари, ціни та остатки без ручного копіювання. Результат — структуровані дані у вашій базі, оновлювані за розписанням.

Архітектура парсера

Scheduler (cron / Horizon)
    → Scraper Job
        → HTTP Client (Guzzle / curl)
        → HTML Parser (Symfony DomCrawler / Goutte)
        → Data Normalizer
        → Duplicate Checker
        → Product Repository
        → Notification (при ошибках)

Стек інструментів

Завдання Інструмент
HTTP-запити Guzzle 7
Парсинг HTML Symfony DomCrawler + CSS Selector
JS-сайти Puppeteer (Node) / Playwright
Черги Laravel Queue + Redis
Прокси Rotating proxy pool
Сховище PostgreSQL / MySQL

Базовий парсер на PHP

// app/Services/Scrapers/SupplierScraper.php
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;

class SupplierScraper
{
    private Client $client;

    public function __construct(
        private string $baseUrl,
        private array $proxyPool = []
    ) {
        $this->client = new Client([
            'timeout'         => 15,
            'connect_timeout' => 5,
            'headers'         => [
                'User-Agent'      => $this->randomUserAgent(),
                'Accept-Language' => 'uk-UA,uk;q=0.9',
                'Accept'          => 'text/html,application/xhtml+xml',
            ],
        ]);
    }

    public function scrapeProductList(string $categoryUrl): array
    {
        $html = $this->fetchWithRetry($categoryUrl);
        $crawler = new Crawler($html);

        return $crawler->filter('.product-card')->each(function (Crawler $node) {
            return [
                'url'   => $node->filter('a.product-link')->attr('href'),
                'title' => trim($node->filter('.product-title')->text()),
                'price' => $this->parsePrice($node->filter('.price')->text()),
                'sku'   => $node->filter('[data-sku]')->attr('data-sku'),
            ];
        });
    }

    public function scrapeProductDetail(string $productUrl): array
    {
        $html = $this->fetchWithRetry($this->baseUrl . $productUrl);
        $crawler = new Crawler($html);

        return [
            'title'       => $crawler->filter('h1.product-name')->text(),
            'description' => $crawler->filter('.description')->html(),
            'price'       => $this->parsePrice($crawler->filter('.current-price')->text()),
            'images'      => $crawler->filter('.gallery img')->each(
                fn(Crawler $img) => $img->attr('src')
            ),
            'in_stock'    => $crawler->filter('.in-stock')->count() > 0,
            'sku'         => $crawler->filter('[itemprop="sku"]')->text(''),
        ];
    }

    private function fetchWithRetry(string $url, int $attempts = 3): string
    {
        $proxy = $this->proxyPool ? $this->randomProxy() : null;

        for ($i = 0; $i < $attempts; $i++) {
            try {
                $options = $proxy ? ['proxy' => $proxy] : [];
                $response = $this->client->get($url, $options);
                return (string) $response->getBody();
            } catch (\Exception $e) {
                if ($i === $attempts - 1) throw $e;
                sleep(rand(2, 5));
            }
        }
    }

    private function parsePrice(string $text): float
    {
        return (float) preg_replace('/[^\d.,]/', '', str_replace(',', '.', $text));
    }
}

Фоновий Job

// app/Jobs/ScrapeSupplierProducts.php
class ScrapeSupplierProducts implements ShouldQueue
{
    use Queueable;

    public int $tries = 2;
    public int $timeout = 300; // 5 хвилин на категорію

    public function __construct(
        private int $supplierId,
        private string $categoryUrl
    ) {}

    public function handle(
        SupplierScraper $scraper,
        ProductImportService $importer
    ): void {
        $products = $scraper->scrapeProductList($this->categoryUrl);

        foreach ($products as $productPreview) {
            // Деталі кожного товара — окрема задача
            ScrapeSupplierProductDetail::dispatch(
                $this->supplierId,
                $productPreview['url']
            )->onQueue('scraper-detail');

            usleep(rand(500000, 1500000)); // 0.5–1.5 сек
        }
    }
}

Дедупликация та оновлення

// app/Services/ProductImportService.php
class ProductImportService
{
    public function upsert(int $supplierId, array $data): void
    {
        $product = SupplierProduct::updateOrCreate(
            [
                'supplier_id'  => $supplierId,
                'supplier_sku' => $data['sku'],
            ],
            [
                'title'       => $data['title'],
                'price'       => $data['price'],
                'in_stock'    => $data['in_stock'],
                'description' => $data['description'],
                'images'      => json_encode($data['images']),
                'scraped_at'  => now(),
            ]
        );

        if ($product->wasChanged('price')) {
            $change = abs($product->price - $product->getOriginal('price'));
            if ($change / $product->getOriginal('price') > 0.05) {
                PriceChangedNotification::dispatch($product);
            }
        }
    }
}

Базовий парсер одного поставщика (статичний HTML, 5-10 полів): 3-5 робочих днів, включаючи налаштування черг, розписання та базовий моніторинг.