Instagram Pipeline
End-to-End-Ablauf vom Watcher-Import ueber den Collector bis zur Related-Profile-Berechnung.
Architektur-Ueberblick
Instagram wird nicht lokal gescraped. Alle Daten kommen ueber den Collector-Flow:
User/Command -> CollectorJob (DB-Queue) -> Browser Extension least Job -> Scrape -> POST /complete -> Processor
Watcher Import
Ablauf
- User erstellt Watcher(s) in
Watchers/Create.php(Single oder Multi-Import, inkl. CSV-Upload) - Pro Instagram-URL wird ein
CollectorJobmitjob_type: watcher_importerstellt - Browser Extension least den Job via
POST /api/collector/jobs/lease - Extension scraped das Instagram-Profil
- Extension sendet Ergebnis via
POST /api/collector/jobs/{job}/complete InstagramWatcherImportProcessor::handleCompletion()verarbeitet das Ergebnis
Collector Job Payload (Watcher Import)
{
"job_type": "watcher_import",
"run_id": 22,
"workspace_id": 1,
"created_by": 1,
"input_url": "https://www.instagram.com/example/",
"dedupe_key": "handle:example",
"name": null
}
Processor-Ergebnis
Der InstagramWatcherImportProcessor fuehrt folgende Schritte aus:
SocialProfileerstellen/aktualisieren (Handle, Titel, Follower, Verified-Status, Profile-Type)SocialProfileDailyMetricfuer den aktuellen Tag anlegenWatcher+WatcherSourceim Workspace erstellen (falls noch nicht vorhanden)- Import-Run Zaehler aktualisieren (success/failed/total)
ProfileDescriptionParserfuer Link-Extraktion ausfuehren
Location: app/Services/Collector/InstagramWatcherImportProcessor.php, app/Livewire/Watchers/Create.php
Daily Scrape
Command: social:queue-daily-instagram
Laeuft alle 2 Stunden (gerade UTC-Stunden: 00:00, 02:00, 04:00, ...). Erstellt Collector-Jobs fuer alle tracking-aktiven Instagram-Profile.
Thundering-Herd-Schutz: Profile werden in 1000er-Chunks verarbeitet. Zwischen Chunk-Dispatches liegt ein usleep(250ms) Delay, damit Collector-Clients die neuen Jobs nicht alle gleichzeitig leisten. Ohne diesen Delay stuerzen Collector-Plugins bei Mitternachts-Bursts (5.000-10.000 Profile) durch Server-Ueberlast ab.
Rotation-Bucket-System
$key = $profile->handle_normalized ?: $profile->handle ?: (string) $profile->id;
$hash = crc32(Str::lower($key));
$bucket = abs($hash) % $rotationDays; // Default: 3 Tage
$isRotationDay = $bucket === (dayOfYear % $rotationDays);
Konfiguration: POSTBOX_INSTAGRAM_ROTATION_DAYS (Default: 3).
Priority-System
Profile mit hoeherer Prioritaet werden von der Browser Extension bevorzugt geleased. Der Collector bedient immer zuerst alle low_priority=false Jobs, bevor low_priority=true Jobs an die Reihe kommen.
| Gruppe | Priority-Wert | Pool | Bypass Rotation |
|---|---|---|---|
| New Profile regulaer (>= 100 Followers) | 60 | regular | Ja |
| Top Leaders (Top/Flop 100) | 50 | regular | Ja |
| Candidates (Leaderboard-Grenze) | 40 | regular | Ja |
| Favorites | 20 | regular | Ja |
| CatchUp (verpasstes Fenster) | 10 | regular | Ja |
| Default (Rotation-Bucket) | 0 | regular | Nein |
| New Profile low-prio (< 100 Followers) | 5 | low_priority | Ja |
Zwei-Pool-System: Der Collector-Lease-Endpoint (/api/collector/lease) fragt zuerst low_priority=false ab, sortiert nach priority DESC, created_at ASC. Nur wenn dieser Pool leer ist, werden low_priority=true Jobs geleased. Dadurch blockieren Low-Priority-Importe niemals die taegliche Rotation.
Einmal-Korrektur bestehender Jobs: instagram:deprioritize-non-rotation setzt alle queued non-rotation Jobs (watcher_import + daily_scrape fuer nie-gescrapte Profile) auf Priority 5. Danach instagram:prioritize-rotation zum Boosten der Rotation-Jobs.
CatchUp-Erkennung
Ein Profil gilt als CatchUp, wenn:
- Es schon einmal queued wurde (
last_daily_queue_on IS NOT NULL) - Es seit dem letzten Queuing nicht erfolgreich gescraped wurde (
last_daily_scrape_on < last_daily_queue_on) - Es heute noch nicht queued/gescraped wurde
Stale Job Pruning
Offene Daily-Scrape-Jobs (Status queued oder leased) die aelter als 2 Tage sind, werden automatisch geloescht. Dies verhindert, dass sich verwaiste Jobs ansammeln.
Location: app/Console/Commands/QueueDailyInstagramScrapes.php
Processor: InstagramDailyScrapeProcessor
Verarbeitet abgeschlossene Daily-Scrape-Jobs:
SocialProfileaktualisieren (Follower, Post-Count, Verified, Private-Status)SocialProfileDailyMetricfuer heute anlegenProfileDescriptionParserausfuehrenProfileKeywordExtractorfuer Keyword-Extraktion ausfuehrenFindRelatedInstagramProfilesJob dispatchen (falls noch nie berechnet)- Daily-Scrape-Progress fuer User-Benachrichtigungen aktualisieren
Location: app/Services/Collector/InstagramDailyScrapeProcessor.php
Keyword Extraction
ProfileKeywordExtractor extrahiert Keywords aus Instagram-Profilen:
- Quellen: Bio-Text, Titel/Display-Name, Handle (aufgeteilt an Unterstrichen/Punkten)
- Stopword-Entfernung: DE, EN, ES, FR, IT
- Minimum-Laenge: 3 Zeichen pro Keyword
- Speicherung:
instagram_profile_keywordsTabelle (keyword, source, social_profile_id)
Location: app/Services/Instagram/ProfileKeywordExtractor.php
Related Profiles (Instagram)
Lokale DB-Berechnung (keine API-Calls erforderlich).
Ablauf
- Profil wird erfolgreich gescraped -> Keywords extrahiert
FindRelatedInstagramProfilesJob dispatcht (fallsrelated_profiles_statusnoch nie berechnet)- Job findet Profile mit mindestens 2 gemeinsamen Keywords
- Relevance-Score (0-100) wird berechnet:
| Faktor | Max Punkte | Berechnung |
|---|---|---|
| Bio-Keyword-Match | 40 | min(40, matchCount * 8) |
| Follower-Aehnlichkeit | 20 | Log-Scale Similarity |
| Name-Aehnlichkeit | 15 | similar_text() Prozent |
| Gleicher Profile-Typ | 10 | Exakter Match |
| Sprache/Land | 10 | 5 Punkte je Match |
| Handle-Pattern | 5 | Wort-Ueberlappung |
- Top 25 werden als
instagram_related_profilesgespeichert - UI zeigt horizontalen Slider (gleiche Design wie YouTube Related Channels)
Location: app/Jobs/Instagram/FindRelatedInstagramProfiles.php, app/Livewire/Watchers/RelatedProfiles.php
Collector Payload (Complete - Daily Scrape)
{
"result": {
"handle": "example",
"canonical_url": "https://www.instagram.com/example/",
"followers_count": 15420,
"title": "Example Creator",
"is_private": false,
"is_verified": true,
"profile_type": "creator",
"following_count": 312,
"post_count": 847,
"thumbnail_url": "https://scontent-xxx.cdninstagram.com/...",
"profile_data": { "is_private": false },
"daily_raw": { "post_count": 847 }
}
}
.env Variablen
| Variable | Default | Beschreibung |
|---|---|---|
POSTBOX_INSTAGRAM_ROTATION_DAYS | 3 | Rotation-Tage fuer Daily Scrape |
POSTBOX_INSTAGRAM_PRIORITY_DEFAULT | 0 | Default-Prioritaet fuer regulaere Profile |
POSTBOX_INSTAGRAM_PRIORITY_FAVORITE | 20 | Prioritaet fuer Favoriten |
POSTBOX_INSTAGRAM_PRIORITY_NEW_PROFILE | 30 | Prioritaet fuer neue Profile |
POSTBOX_INSTAGRAM_PRIORITY_CANDIDATE | 40 | Prioritaet fuer Leaderboard-Kandidaten |
POSTBOX_INSTAGRAM_PRIORITY_TOP_LEADER | 50 | Prioritaet fuer Top/Flop Leaders |