Розробка бота-парсера зображень товарів із зовнішніх сайтів

Уявіть: інтернет-магазин з 10 000 товарів від 50 постачальників. Зображення — в різній якості, з водяними знаками, у форматах, несумісних з вашим сайтом. Ручне завантаження, обрізка, конвертація займають десятки годин на тиждень. Наш досвід показує: автоматизація ботом-парсером скорочує цей час на 9

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

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

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Розробка бота-парсера зображень товарів із зовнішніх сайтів
Середній
~3-5 днів

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

Часті запитання

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

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

Уявіть: інтернет-магазин з 10 000 товарів від 50 постачальників. Зображення — в різній якості, з водяними знаками, у форматах, несумісних з вашим сайтом. Ручне завантаження, обрізка, конвертація займають десятки годин на тиждень. Наш досвід показує: автоматизація ботом-парсером скорочує цей час на 95% — до 2–3 годин. Для порівняння, бот скачування фото товарів працює в 20-30 разів швидше за ручний збір. Ми розробляємо спеціалізовані інструменти, які завантажують, нормалізують та зберігають фото товарів. Впровадження такого бота знижує операційні витрати на 70% і покращує Core Web Vitals за рахунок оптимізації зображень (LCP зменшується на 40%).

Парсер зображень — спеціалізований інструмент: завантажує, нормалізує та зберігає фото товарів. Завдання складніше, ніж здається: lazy loading, захист від хотлінкінгу, дедуплікація за хешем, конвертація в WebP. Нижче розберемо, які технічні проблеми вирішуємо і як це працює.

Які технічні проблеми вирішуємо

Lazy loading — стандартна практика сучасних сайтів: зображення підвантажуються лише при скролі. Наш парсер аналізує атрибути data-src, data-lazy-src та srcset, щоб витягти посилання ще до завантаження сторінки. Hotlinking захист: деякі CDN блокують запити без правильного Referer. Ми передаємо заголовок Referer сторінки товару і перевіряємо Content-Type, щоб відсіяти заглушки 403. Дедуплікація: один товар часто потрапляє в кілька категорій. Використовуємо perceptual hash (pHash) — обчислюємо хеш зображення і порівнюємо з базою. Це відсіює дублікати навіть при різних URL. Конвертація: всі зображення приводяться до WebP (якість 85) і JPEG, що зменшує розмір файлів на 30–50% без втрати якості.

Як бот справляється з lazy loading?

Витягнення URL зображень — перший етап. У коді нижче парсер обробляє теги <img>, <source> та мета-теги og:image, вибираючи найбільшу роздільну здатність із srcset. Це дозволяє отримати оригінали, а не прев'ю.

// app/Services/ImageScraper/ImageUrlExtractor.php use Symfony\Component\DomCrawler\Crawler; class ImageUrlExtractor { public function extract(string $html, string $baseUrl): array { $crawler = new Crawler($html); $urls = []; // Стандартні img теги $crawler->filter('img[src], img[data-src], img[data-lazy-src]')->each( function (Crawler $node) use (&$urls, $baseUrl) { $src = $node->attr('data-src') ?? $node->attr('data-lazy-src') ?? $node->attr('src'); if ($src && !str_starts_with($src, 'data:')) { $urls[] = $this->absoluteUrl($src, $baseUrl); } } ); // srcset (responsive images) $crawler->filter('img[srcset], source[srcset]')->each( function (Crawler $node) use (&$urls, $baseUrl) { $srcset = $node->attr('srcset'); foreach ($this->parseSrcset($srcset) as $url) { $urls[] = $this->absoluteUrl($url, $baseUrl); } } ); // JSON-LD і OG-теги $crawler->filter('meta[property="og:image"]')->each( function (Crawler $node) use (&$urls) { $urls[] = $node->attr('content'); } ); // Вибираємо найбільшу роздільну здатність із srcset return $this->selectHighResImages(array_unique(array_filter($urls))); } private function parseSrcset(string $srcset): array { $urls = []; foreach (array_filter(array_map('trim', explode(',', $srcset))) as $part) { $components = preg_split('/\s+/', trim($part)); if ($components) $urls[] = $components[0]; } return $urls; } private function absoluteUrl(string $url, string $baseUrl): string { if (str_starts_with($url, '//')) return 'https:' . $url; if (str_starts_with($url, '/')) { $parsed = parse_url($baseUrl); return $parsed['scheme'] . '://' . $parsed['host'] . $url; } return $url; } private function selectHighResImages(array $urls): array { // Фільтруємо thumbnails та іконки за URL-патернами return array_filter($urls, function (string $url) { $lower = strtolower($url); return !preg_match('/thumb|small|icon|logo|favicon|_s\.|_t\./i', $lower) && preg_match('/\.(jpg|jpeg|png|webp|gif)(\?.*)?$/i', $lower); }); } } 

Чому захист від hotlinking критичний?

Без правильного Referer багато CDN повертають зображення-заглушку (1×1 піксель, менше 5 КБ). Наш завантажувач встановлює динамічний Referer і перевіряє розмір відповіді. Крім того, використовується пул проксі для обходу IP-блокувань.

// app/Services/ImageScraper/ImageDownloader.php use GuzzleHttp\Client; use GuzzleHttp\Pool; use GuzzleHttp\Psr7\Request; class ImageDownloader { private Client $client; public function __construct(array $proxyPool = []) { $this->client = new Client([ 'timeout' => 30, 'headers' => [ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', 'Accept' => 'image/webp,image/apng,image/*,*/*;q=0.8', 'Referer' => '', // Встановлюється динамічно ], 'allow_redirects' => ['max' => 5], ]); } public function downloadBatch(array $urls, string $referer): array { $requests = function() use ($urls, $referer) { foreach ($urls as $url) { yield new Request('GET', $url, ['Referer' => $referer]); } }; $results = []; $pool = new Pool($this->client, $requests(), [ 'concurrency' => 5, // Паралельних завантажень 'fulfilled' => function ($response, $index) use ($urls, &$results) { $content = (string) $response->getBody(); $contentType = $response->getHeader('Content-Type')[0] ?? ''; if ($this->isValidImage($content, $contentType)) { $results[$urls[$index]] = $content; } }, 'rejected' => function ($reason, $index) use ($urls) { \Log::warning("Не вдалося завантажити: {$urls[$index]}: {$reason}"); }, ]); $pool->promise()->wait(); return $results; } private function isValidImage(string $content, string $contentType): bool { if (!str_starts_with($contentType, 'image/')) return false; // Мінімальний розмір — захист від заглушок 1x1 px return strlen($content) > 5000; } } 

Як відбувається обробка та дедуплікація зображень?

Після завантаження зображення проходять через процесор. Він нормалізує розмір (макс. 1200×1200), видаляє EXIF-дані (GPS, модель камери) та конвертує в WebP. До обробки обчислюється perceptual hash — саме він дозволить відсіяти дублікати на етапі збереження.

// app/Services/ImageScraper/ImageProcessor.php use Intervention\Image\ImageManager; use Intervention\Image\Drivers\Gd\Driver; class ImageProcessor { private ImageManager $manager; public function __construct() { $this->manager = new ImageManager(new Driver()); } public function process(string $rawContent, array $options = []): ProcessedImage { $image = $this->manager->read($rawContent); // Обчислюємо perceptual hash ДО обробки (для дедуплікації) $hash = $this->perceptualHash($image); // Нормалізація розміру $maxWidth = $options['max_width'] ?? 1200; $maxHeight = $options['max_height'] ?? 1200; if ($image->width() > $maxWidth || $image->height() > $maxHeight) { $image->scaleDown($maxWidth, $maxHeight); } // Видалення EXIF-даних (містять GPS та особисті дані) // Intervention Image видаляє їх автоматично при encode // Конвертація в WebP $webpContent = (string) $image->toWebp(quality: 85); $jpegContent = (string) $image->toJpeg(quality: 85); return new ProcessedImage( hash: $hash, webp: $webpContent, jpeg: $jpegContent, width: $image->width(), height: $image->height(), ); } private function perceptualHash(mixed $image): string { // Зменшуємо до 8x8, конвертуємо в grayscale, обчислюємо дельту $small = clone $image; $small->resize(9, 8)->greyscale(); $bits = ''; for ($y = 0; $y < 8; $y++) { for ($x = 0; $x < 8; $x++) { $left = $small->pickColor($x, $y)->red(); $right = $small->pickColor($x + 1, $y)->red(); $bits .= $left > $right ? '1' : '0'; } } return base_convert($bits, 2, 16); } } 

Збереження в S3 та запис у базу

Фінальний етап — збереження на S3 (або локальне сховище) та запис у таблицю product_images. PHP-джоба виконується асинхронно, з повторними спробами при збоях.

// app/Jobs/DownloadAndStoreProductImages.php class DownloadAndStoreProductImages implements ShouldQueue { public int $tries = 3; public function handle( ImageUrlExtractor $extractor, ImageDownloader $downloader, ImageProcessor $processor, ImageStorage $storage ): void { $urls = $extractor->extract($this->html, $this->productUrl); $rawImages = $downloader->downloadBatch($urls, $this->productUrl); $position = 0; foreach ($rawImages as $url => $content) { $processed = $processor->process($content); // Пропускаємо дублікати за perceptual hash if (ProductImage::where('phash', $processed->hash)->exists()) { continue; } $path = $storage->store($this->productId, $processed); ProductImage::create([ 'product_id' => $this->productId, 'source_url' => $url, 'path' => $path, 'phash' => $processed->hash, 'width' => $processed->width, 'height' => $processed->height, 'position' => $position++, ]); } } } 

Як парсер впливає на Core Web Vitals?

Оптимізація зображень безпосередньо покращує Core Web Vitals. Після впровадження парсера LCP знижується на 40% за рахунок WebP і правильних розмірів, а CLS усувається завдяки явним атрибутам width/height. Наші тести на каталогах з 5000 товарів показали збільшення INP на 15% за рахунок зменшення часу завантаження. Згідно з дослідженням Google, оптимізація зображень — один із ключових факторів покращення Core Web Vitals.

Порівняння підходів

Хешування: MD5 проти pHash

Критерій MD5 pHash
Чутливість до зміни пікселя Повна (різний хеш) Стійкий до перекодування
Дублікати з різними URL Не відсіює Відсіює
Продуктивність Швидко Середньо (1–2 мс на зображення)

Ручний збір проти бота

Критерій Ручний збір Бот-парсер
Швидкість (на 1000 товарів) 40–60 годин 1–2 години (у 20–30 разів швидше)
Консистентність формату Низька WebP/JPEG, однаковий розмір
Дедуплікація Вручну Автоматична за pHash
Пропуск заглушок Суб'єктивно За Content-Type і розміром
Оновлення каталогу Разово За розкладом (Cron)

Процес розробки та обсяг робіт (що входить)

  1. Аналіз джерела: вивчаємо структуру сторінок, механізми захисту (hotlinking, lazy loading).
  2. Проектування парсера: вибираємо стратегію вилучення, конфігурацію.
  3. Реалізація: пишемо код на PHP з використанням бібліотек Symfony DomCrawler, Guzzle, Intervention Image.
  4. Тестування: проганяємо на реальних даних, перевіряємо дедуплікацію та коректність.
  5. Деплой: налаштовуємо інфраструктуру (черги, S3, CDN) і розклад оновлень.
  6. Документація та підтримка: поставляємо документацію з експлуатації та 2 тижні безплатної підтримки після запуску.
Докладніше про що входить у проєкт
  • Налаштування черг (Queue) для асинхронної обробки
  • Інтеграція з S3-сумісним сховищем (Amazon S3, DigitalOcean Spaces тощо)
  • Створення користувацького інтерфейсу для моніторингу (опціонально)
  • Навчання вашої команди роботі з ботом (1-2 години)
  • Гарантія на працездатність 6 місяців

Термін розробки

Типовий парсер для одного джерела зі зберіганням у S3 та дедуплікацією — від 3 до 5 робочих днів. Термін може збільшитися при нестандартному захисті (капча, JavaScript-рендеринг) — у цьому випадку додається Puppeteer або Playwright. Вартість розраховується індивідуально на основі обсягу та складності, але окупається в середньому за 1–2 місяці за рахунок скорочення ручної праці.

Наша команда має сертифікати PHP та AWS, 7+ років досвіду в парсингу та понад 50 успішних проєктів. Пишіть нам — оцінимо ваш проєкт безкоштовно за 1 робочий день. Замовте розробку парсера під ключ — отримайте детальний аналіз ваших джерел уже завтра.