Zum Hauptinhalt springen

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

  1. User erstellt Watcher(s) in Watchers/Create.php (Single oder Multi-Import, inkl. CSV-Upload)
  2. Pro Instagram-URL wird ein CollectorJob mit job_type: watcher_import erstellt
  3. Browser Extension least den Job via POST /api/collector/jobs/lease
  4. Extension scraped das Instagram-Profil
  5. Extension sendet Ergebnis via POST /api/collector/jobs/{job}/complete
  6. 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:

  • SocialProfile erstellen/aktualisieren (Handle, Titel, Follower, Verified-Status, Profile-Type)
  • SocialProfileDailyMetric fuer den aktuellen Tag anlegen
  • Watcher + WatcherSource im Workspace erstellen (falls noch nicht vorhanden)
  • Import-Run Zaehler aktualisieren (success/failed/total)
  • ProfileDescriptionParser fuer 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.

GruppePriority-WertPoolBypass Rotation
New Profile regulaer (>= 100 Followers)60regularJa
Top Leaders (Top/Flop 100)50regularJa
Candidates (Leaderboard-Grenze)40regularJa
Favorites20regularJa
CatchUp (verpasstes Fenster)10regularJa
Default (Rotation-Bucket)0regularNein
New Profile low-prio (< 100 Followers)5low_priorityJa

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:

  • SocialProfile aktualisieren (Follower, Post-Count, Verified, Private-Status)
  • SocialProfileDailyMetric fuer heute anlegen
  • ProfileDescriptionParser ausfuehren
  • ProfileKeywordExtractor fuer Keyword-Extraktion ausfuehren
  • FindRelatedInstagramProfiles Job 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_keywords Tabelle (keyword, source, social_profile_id)

Location: app/Services/Instagram/ProfileKeywordExtractor.php

Lokale DB-Berechnung (keine API-Calls erforderlich).

Ablauf

  1. Profil wird erfolgreich gescraped -> Keywords extrahiert
  2. FindRelatedInstagramProfiles Job dispatcht (falls related_profiles_status noch nie berechnet)
  3. Job findet Profile mit mindestens 2 gemeinsamen Keywords
  4. Relevance-Score (0-100) wird berechnet:
FaktorMax PunkteBerechnung
Bio-Keyword-Match40min(40, matchCount * 8)
Follower-Aehnlichkeit20Log-Scale Similarity
Name-Aehnlichkeit15similar_text() Prozent
Gleicher Profile-Typ10Exakter Match
Sprache/Land105 Punkte je Match
Handle-Pattern5Wort-Ueberlappung
  1. Top 25 werden als instagram_related_profiles gespeichert
  2. 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

VariableDefaultBeschreibung
POSTBOX_INSTAGRAM_ROTATION_DAYS3Rotation-Tage fuer Daily Scrape
POSTBOX_INSTAGRAM_PRIORITY_DEFAULT0Default-Prioritaet fuer regulaere Profile
POSTBOX_INSTAGRAM_PRIORITY_FAVORITE20Prioritaet fuer Favoriten
POSTBOX_INSTAGRAM_PRIORITY_NEW_PROFILE30Prioritaet fuer neue Profile
POSTBOX_INSTAGRAM_PRIORITY_CANDIDATE40Prioritaet fuer Leaderboard-Kandidaten
POSTBOX_INSTAGRAM_PRIORITY_TOP_LEADER50Prioritaet fuer Top/Flop Leaders