בוט לגרידת תמונות מוצר מאתרים חיצוניים

העלאה ידנית ועיבוד של תמונות מוצר גוזלים עשרות שעות שבועיות, בעוד פורמטים ואיכות לא עקביים מאטים את האתר שלכם. אנחנו בונים בוט parser שמוריד, מנרמל וממיר תמונות אוטומטית לפורמט הנדרש. הצוות שלנו מספק את הפרויקט במפתח מלא—מבדיקת היתכנות ועד תמיכה—ומבטיח ביצועים אמינים וסקאלינג עם העסק שלכם.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
בוט לגרידת תמונות מוצר מאתרים חיצוניים
בינוני
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

תארו לעצמכם חנות מסחר אלקטרוני עם 10,000 מוצרים מ-50 ספקים. תמונות מגיעות באיכויות שונות, עם סימני מים, בפורמטים שאינם תואמים לאתר שלכם. הורדה ידנית, חיתוך והמרה אורכים עשרות שעות מדי שבוע. בוט גירוד התמונות שלנו, הידוע גם כבוט הורדת תמונות מוצר, מתמחה באוטומציה של הורדת תמונות והמרת WebP. הוא משתמש בדדופליקציה מבוססת גיבוב עם גיבוב תפיסתי כדי למנוע כפילויות, עוקף הגנת hotlinking וטכניקות עקיפת טעינה עצלה, ואידיאלי לגירוד אתרי ספקים ונירמול תמונות. הניסיון שלנו מראה: אוטומציה באמצעות בוט גירוד מפחיתה זמן זה ב-95% — עד 2–3 שעות. אנו בונים כלים ייעודיים שמורידים, מנ�רמלים ושומרים תמונות מוצר. פריסת בוט כזה מקצצת עלויות תפעול ב-70% ומשפרת את Core Web Vitals באמצעות אופטימיזציית תמונות (LCP יורד ב-40%). פתרון סוהר עולה מ-$1,500 למקור, ובדרך כלל חוסך $5,000–$10,000 בעבודת יד בתוך שנה.

הבוט מהיר פי 40 מאיסוף ידני (1–2 שעות לעומת 40–60 שעות לכל 1000 מוצרים). החברה שלנו, שנוסדה ב-2015, בעלת ניסיון של 7+ שנים בגירוד ויותר מ-50 פרויקטים מוצלחים, עם אחריות של 30 יום על כל העבודה. צרו קשר להערכת פרויקט. אנו מספקים פתרונות סוהר, עם פיתוח של 3–5 ימים. ההיקף כולל הגדרת S3 ותור. כתבו לנו בטלגרם או באימייל.

מפענח תמונות הוא כלי ייעודי: הוא מוריד, מנ�רמל ושומר תמונות מוצר. המשימה מסובכת יותר ממה שהיא נראית: טעינה עצלה, הגנת hotlinking, דדופליקציה מבוססת גיבוב, המרת WebP. להלן אנו מפרטים אילו בעיות טכניות אנו פותרים וכיצד זה עובד.

אילו בעיות טכניות אנו פותרים

טעינה עצלה היא נוהג סטנדרטי באתרים מודרניים: תמונות נטענות רק בגלילה. המפענח שלנו מנתח מאפיינים data-src, data-lazy-src ו-srcset כדי לחלץ קישורים עוד לפני טעינת העמוד. הגנת hotlinking: חלק מה-CDN חוסמים בקשות ללא Referer הנכון. אנו מעבירים את כותרת Referer של עמוד המוצר ובודקים את Content-Type כדי לסנן מצייני 403. דדופליקציה: מוצר מופיע לעיתים קרובות במספר קטגוריות. אנו משתמשים ב-גיבוב תפיסתי (pHash) — מחשבים את גיבוב התמונה ומשווים למסד הנתונים. זה מבטל כפילויות גם עם כתובות URL שונות. המרה: כל התמונות מומרות ל-WebP (איכות 85) ול-JPEG, מה שמקטין את גודל הקובץ ב-30–50% ללא אובדן איכות.

כיצד הבוט מתמודד עם טעינה עצלה?

חילוץ כתובות URL של תמונות הוא הצעד הראשון. בקוד שלהלן, המפענח מעבד תגי meta <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 קריטית?

ללא // 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); }); } } נכון, CDN רבים מחזירים תמונת מציין (פיקסל 1×1, מתחת ל-5 KB). המוריד שלנו מגדיר 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. לפני העיבוד, מחושב גיבוב תפיסתי — זה מאפשר דדופליקציה בשלב האחסון.

// 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 (או אחסון מקומי) וכתיבה לטבלת Referer. עבודת ה-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 מבוטל בגלל מאפייני רוחב וגובה מפורשים. הבדיקות שלנו בקטלוגים עם 5,000 מוצרים הראו שיפור של 15% ב-INP בזכות זמני טעינה מופחתים. לפי מחקר של Google, אופטימיזציית תמונות היא גורם מפתח בשיפור Core Web Vitals.

השוואת גישות

גיבוב: MD5 לעומת pHash

קריטריון MD5 pHash
רגישות לשינוי פיקסל מלאה (גיבוב שונה) עמיד לקידוד מחדש
כפילויות עם כתובות URL שונות לא מסנן מסנן
ביצועים מהיר בינוני (1–2 אלפיות השנייה לתמונה)

איסוף ידני לעומת בוט

קריטריון איסוף ידני בוט גירוד
מהירות (לכל 1000 מוצרים) 40–60 שעות 1–2 שעות
עקביות פורמט נמוכה WebP/JPEG, גודל אחיד
דדופליקציה ידנית אוטומטית באמצעות pHash
זיהוי מצייני מקום סובייקטיבי לפי Content-Type וגודל
עדכון קטלוג חד-פעמי מתוזמן (Cron)

תהליך הפיתוח והיקף העבודה

  1. ניתוח מקור: לימוד מבנה העמוד, מנגנוני הגנה (hotlinking, טעינה עצלה).
  2. עיצוב מפענח: בחירת אסטרטגיית חילוץ, תצורה.
  3. יישום: כתיבת קוד ב-PHP באמצעות Symfony DomCrawler, Guzzle, Intervention Image.
  4. בדיקות: הרצה על נתונים אמיתיים, אימות דדופליקציה ונכונות.
  5. פריסה: הגדרת תשתית (תורים, S3, CDN) ולוח זמנים לעדכונים.
  6. תיעוד ותמיכה: מתן תיעוד תפעולי ושבועיים של תמיכה חינם לאחר ההשקה.

לוח זמנים לפיתוח

מפענח טיפוסי למקור אחד עם אחסון S3 ודדופליקציה אורך 3 עד 5 ימי עבודה. לוח הזמנים עשוי להתארך עם הגנה לא סטנדרטית (captcha, רינדור JavaScript) — במקרה זה, מתווסף Puppeteer או Playwright. העלות מחושבת באופן פרטני לפי היקף ומורכבות, אך בדרך כלל מחזירה את עצמה תוך 1–2 חודשים בזכות הפחתת עבודת יד.

הצוות שלנו מחזיק בהסמכות PHP ו-AWS, בעל ניסיון של 7+ שנים בגירוד ויותר מ-50 פרויקטים מוצלחים. אנו מספקים אחריות של 30 יום על כל העבודה. צרו קשר להערכת פרויקט — נכין הצעה מסחרית תוך יום עסקים אחד. הזמינו פיתוח מפענח וקבלו ניתוח מפורט של המקורות שלכם מחר. אנו מציעים פתרונות סוהר הכוללים הגדרה מלאה, עם פיתוח של 3–5 ימים. כתבו לנו בטלגרם או באימייל.