Social Scraping
Die Scraping-Services bilden die Datenbeschaffungsschicht von Postbox. Sie transformieren API-Responses und HTML-Payloads in ein einheitliches ScrapedProfile-DTO und verwalten den Download sowie die Versionierung von Profil-Bildern und Video-Thumbnails.
ScrapedProfile (DTO)
Location: app/Services/Social/Scrapers/ScrapedProfile.php
Readonly Data Transfer Object, das als einheitliche Schnittstelle zwischen Scrapern und dem restlichen System dient. Alle Scraper liefern eine Instanz dieses DTOs zurück.
final readonly class ScrapedProfile
{
public function __construct(
public SocialPlatform $platform,
public string $handle,
public string $canonicalUrl,
public int $followersCount,
public ?string $externalId = null,
public ?int $viewCount = null,
public ?int $videoCount = null,
public ?int $commentCount = null,
public ?string $title = null,
public ?string $description = null,
public ?string $thumbnailUrl = null,
public ?string $customUrl = null,
public ?string $country = null,
public ?string $language = null,
public ?string $publishedAt = null,
public array $profileData = [],
public array $dailyRaw = [],
public ?int $followingCount = null,
public ?int $postCount = null,
public ?bool $isPrivate = null,
public ?bool $isVerified = null,
public ?string $profileType = null,
) {}
}
Plattform-spezifische Felder: viewCount, videoCount, commentCount werden von YouTube befüllt; followingCount, postCount, isPrivate, isVerified, profileType von Instagram.
YouTubeProfileScraper
Location: app/Services/Social/Scrapers/YouTubeProfileScraper.php
Mapped die YouTube Data API v3 Channel-Response auf ein ScrapedProfile. Nutzt den YouTubeChannelResolver zur URL-Auflösung und extrahiert snippet + statistics Daten.
Public Methods
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
scrape() | string $url | ScrapedProfile | YouTube-Kanal scrapen und als DTO zurückgeben |
Verarbeitungslogik
- Resolve der URL via
YouTubeChannelResolver::resolve() - Extraktion von Channel-ID, Handle (mit
@-Prefix-Bereinigung), Custom URL - Subscriber Count Prüfung: wirft
RuntimeExceptionbeihiddenSubscriberCount - Thumbnail-Auswahl in Priorität: maxres > standard > high > medium > default
dailyRawenthält die rohenstatisticsfür historische Vergleiche
Abhängigkeiten
YouTubeChannelResolver(Constructor Injection)
Verwendet von
ScraperFactory,EnsureSocialProfileFromUrl,SocialScrapeDailyFollowers-Command
InstagramProfileScraper
Location: app/Services/Social/Scrapers/InstagramProfileScraper.php
Scraped Instagram-Profile über den externen Collector-Service. Die eigentliche Datenerhebung erfolgt asynchron via CollectorJobDispatcher; der Scraper verarbeitet die Collector-Response.
Public Methods
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
scrape() | string $url | ScrapedProfile | Instagram-Profil scrapen und als DTO zurückgeben |
Abhängigkeiten
- Collector-API (externer Service)
Verwendet von
ScraperFactory,InstagramDailyScrapeProcessor,InstagramWatcherImportProcessor
ScraperFactory
Location: app/Services/Social/ScraperFactory.php
Factory, die anhand der Plattform (youtube, instagram) den korrekten Scraper instanziiert und den Scrape-Vorgang delegiert.
Public Methods
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
scrape() | string $platform, string $url | ScrapedProfile | Plattform-spezifischen Scraper aufrufen |
ProfileImageUpdater
Location: app/Services/Social/ProfileImageUpdater.php
Verwaltet den Download und die Speicherung von Profil-Bildern. Bilder werden als lokale Dateien gespeichert. Beim Update wird ein Hash-Vergleich durchgeführt, um unnötige Überschreibungen zu vermeiden. Nach dem Speichern werden automatisch optimierte Varianten (Thumbnail + Medium) über den ProfileImageProcessor erzeugt.
Public Methods
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
storeProfileImage() | SocialProfile $profile, string $imageUrl | bool | Bild herunterladen, Hash vergleichen, bei Änderung speichern und Varianten generieren |
Funktionsweise
- Download des Bildes von der Source-URL (YouTube API Thumbnail oder Instagram CDN)
- SHA-256 Hash des neuen Bildes berechnen
- Vergleich mit dem gespeicherten Hash aus
profile_image_hash - Bei Änderung: neues Bild speichern, Hash aktualisieren, alte Varianten löschen
ProfileImageProcessor::generateVariants()erzeugt Thumbnail (128×128) + Medium (256×256) als WebP- LQIP (4×4 Base64 WebP) wird als Blur-Placeholder gespeichert
Abhängigkeiten
- Laravel Storage (public disk)
SocialProfile,SocialProfileImageModelsProfileImageProcessor(Image Variants + LQIP)
Verwendet von
EnsureSocialProfileFromUrl,InstagramDailyScrapeProcessor, Daily Scrape Jobs
VideoThumbnailUpdater
Location: app/Services/Social/VideoThumbnailUpdater.php
Lädt YouTube Video-Thumbnails in maximaler Auflösung (maxres) herunter und speichert sie lokal. Erzeugt automatisch 16:9-Varianten (Medium 640×360, Thumbnail 320×180) über den ProfileImageProcessor.
Public Methods
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
storeVideoThumbnail() | YouTubeVideo $video, string $thumbnailUrl | bool | Thumbnail herunterladen, speichern und Varianten generieren |
Abhängigkeiten
- Laravel Storage (public disk)
YouTubeVideoModelProfileImageProcessor(Video Thumbnail Variants)
Verwendet von
SyncYouTubeVideoStats-Command, Video-Import Jobs
ProfileImageProcessor
Location: app/Services/Images/ProfileImageProcessor.php
Zentraler Service für die Erzeugung optimierter Bild-Varianten. Nutzt intervention/image v3 für serverseitige Bildverarbeitung. Unterstützt WebP und AVIF (konfigurierbar). Erzeugt LQIP (Low Quality Image Placeholder) als 4×4 Base64 WebP Data URI.
Public Methods
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
generateVariants() | SocialProfileImage $image, string $rawBytes | void | Medium + Thumbnail Varianten für Profilbilder erzeugen |
generateVideoThumbnailVariants() | YouTubeVideo $video, string $rawBytes | void | 16:9-Varianten für Video-Thumbnails erzeugen |
regenerateFromDisk() | SocialProfileImage $image | bool | Varianten aus existierender Datei regenerieren (Backfill) |
regenerateVideoThumbnailFromDisk() | YouTubeVideo $video | bool | Video-Varianten aus existierender Datei regenerieren |
deleteVariants() | SocialProfileImage $image | void | Varianten-Dateien löschen, Original behalten |
deleteVideoThumbnailVariants() | YouTubeVideo $video | void | Video-Varianten-Dateien löschen |
Konfiguration
Alle Werte über config('postbox.images'):
| Key | Default | Beschreibung |
|---|---|---|
driver | gd | Image-Driver (gd oder imagick) |
quality | 80 | Encode-Qualität (0–100) |
thumb_size | 128 | Thumbnail-Seitenlänge in px |
medium_size | 256 | Medium-Seitenlänge in px |
format | webp | Zielformat (webp oder avif) |
Varianten-Größen
| Kontext | Thumbnail | Medium |
|---|---|---|
| Profilbilder | 128×128 (1:1) | 256×256 (1:1) |
| Video-Thumbnails | 320×180 (16:9) | 640×360 (16:9) |
Abhängigkeiten
intervention/imagev3 (GD oder Imagick)- Laravel Storage (public disk)
Verwendet von
ProfileImageUpdater,VideoThumbnailUpdater,GenerateImageVariants(Backfill Command)
Zusammenspiel der Scraping-Pipeline
graph TD
A[URL] --> B[ScraperFactory]
B --> C[YouTubeProfileScraper]
B --> D[InstagramProfileScraper]
C --> E[ScrapedProfile DTO]
D --> E
E --> F[EnsureSocialProfileFromUrl]
F --> G[ProfileImageUpdater]
F --> H[SocialProfileDailyMetric]
G --> I[Download + Hash-Dedup]
I --> J[ProfileImageProcessor]
J --> K["Medium WebP (256×256)"]
J --> L["Thumbnail WebP (128×128)"]
J --> M["LQIP Base64 (4×4)"]
Der typische Flow: Eine URL wird via ScraperFactory an den passenden Scraper delegiert, der ein ScrapedProfile zurückgibt. EnsureSocialProfileFromUrl persistiert dann das Profil, erstellt die tägliche Metrik und aktualisiert das Profil-Bild über ProfileImageUpdater. Der ProfileImageUpdater speichert das Original und ruft den ProfileImageProcessor auf, der die optimierten Varianten und das LQIP generiert.