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

Представьте: интернет-магазин с 10 000 товаров от 50 поставщиков. Изображения — в разном качестве, с водяными знаками, в форматах, несовместимых с вашим сайтом. Ручное скачивание, обрезка, конвертация занимают десятки часов в неделю. Наш опыт показывает: автоматизация ботом-парсером сокращает это вр

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, 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
    Разработка веб-сайта для компании ФИКСПЕР
    1000

Представьте: интернет-магазин с 10 000 товаров от 50 поставщиков. Изображения — в разном качестве, с водяными знаками, в форматах, несовместимых с вашим сайтом. Ручное скачивание, обрезка, конвертация занимают десятки часов в неделю. Наш опыт показывает: автоматизация ботом-парсером сокращает это время на 95% — до 2–3 часов. Мы разрабатываем специализированные инструменты, которые скачивают, нормализуют и сохраняют фото товаров. Внедрение такого бота снижает операционные расходы на 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 часа
Консистентность формата Низкая WebP/JPEG, одинаковый размер
Дедупликация Вручную Автоматическая по pHash
Пропуск заглушек Субъективно По Content-Type и размеру
Обновление каталога Разово По расписанию (Cron)

Процесс разработки и объём работ

  1. Анализ источника: изучаем структуру страниц, механизмы защиты (hotlinking, lazy loading).
  2. Проектирование парсера: выбираем стратегию извлечения, конфигурацию.
  3. Реализация: пишем код на PHP с использованием библиотек Symfony DomCrawler, Guzzle, Intervention Image.
  4. Тестирование: прогоняем на реальных данных, проверяем дедупликацию и корректность.
  5. Деплой: настраиваем инфраструктуру (очереди, S3, CDN) и расписание обновлений.
  6. Документация и поддержка: поставляем документацию по эксплуатации и 2 недели бесплатной поддержки после запуска.

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

Типовой парсер для одного источника с хранением в S3 и дедупликацией — от 3 до 5 рабочих дней. Срок может увеличиться при нестандартной защите (капча, JavaScript-рендеринг) — в этом случае добавляется Puppeteer или Playwright. Стоимость рассчитывается индивидуально на основе объёма и сложности, но окупается в среднем за 1–2 месяца за счёт сокращения ручного труда.

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