Zum Hauptinhalt springen

Data Collection Commands

Commands für das tägliche Scraping und Sammeln von Social-Media-Daten. Beide Plattformen nutzen ein Rotation-Bucket-System, um die Last über mehrere Tage zu verteilen.


social:scrape-daily-followers

Täglicher Scrape für YouTube mit Rotation-Bucket-Support. Profile werden über N Tage (Default: 3) verteilt, sodass jedes Profil alle 3 Tage gescraped wird.

social:scrape-daily-followers
{--force-today : Scrape für heute erzwingen}
{--watcher-id= : Nur bestimmte Watcher scrapen (kommaseparierte IDs)}
{--dry-run : Zeigt was gescraped würde, ohne auszuführen}

Beispiele

# Standard-Lauf (YouTube, heutiger Rotation-Bucket)
php artisan social:scrape-daily-followers

# Bestimmte Watcher sofort scrapen
php artisan social:scrape-daily-followers --force-today --watcher-id=12,15

# Vorschau ohne Ausführung
php artisan social:scrape-daily-followers --dry-run

Optionen

OptionBeschreibung
--force-todayScrape für heute erzwingen, unabhängig vom Rotation-Bucket
--watcher-id=Kommaseparierte Watcher-IDs (nur diese werden gescraped)
--dry-runZeigt an was passieren würde, keine Jobs werden dispatched

Rotation-Logik

  • Bucket-Berechnung: CRC32(handle) % rotation_days
  • Heutiger Bucket: dayOfYear % rotation_days
  • Reguläre Profile (>= Follower-Threshold): Rotation über youtube_rotation_days (max 4 Tage)
  • Low-Priority-Profile (< Follower-Threshold): Rotation über low_priority_rotation_days (max 7 Tage)
  • Priority-Profile umgehen jede Rotation (täglich gescrapt)

Priority-Profile (täglich, umgehen Rotation)

KategorieKriterium
PROYouTubeVideoSync.auto_sync_enabled = true oder pro_enabled = true
LeadersTop/Flop 100 aus Leaderboards
CandidatesRising-Profile die in Top 100 kommen könnten
PremiumTop X % nach Follower-Anzahl (automatisch, POSTBOX_PREMIUM_TIER_YOUTUBE_PERCENT, Default 10 %) — siehe „Premium-Tier" unten
FavoritesUser-favorisierte Profile
NewNoch nie gescraped (last_scraped_at = null)
CatchUpVerpasstes Rotation-Fenster

Low-Priority-Rotation

Profile werden als Low-Priority eingestuft wenn eines der folgenden Kriterien zutrifft:

  • followers_count < POSTBOX_LOW_PRIORITY_FOLLOWER_COUNT (Default: 100)
  • is_private = true (private Instagram-Profile liefern kaum verwertbare Daten)

Low-Priority-Profile nutzen einen separaten, langsameren Rotation-Bucket (low_priority_rotation_days, Default: 7 Tage). Ausnahmen: Favorites, PRO, Leaders und Candidates werden immer mit normaler Rotation behandelt. Wird ein privates Profil oeffentlich, setzt der naechste Scrape is_private=false und es kehrt automatisch zur regulaeren Rotation zurueck.

Premium-Tier (Top X % nach Follower-Anzahl)

Zusätzlich zu den manuell kuratierten Favoriten/PRO-Profilen werden automatisch die Top X % der Profile nach Follower-Anzahl je Plattform täglich gescrapt — unabhängig vom Rotation-Bucket. So bleiben die relevantesten Accounts immer tagesaktuell, ohne dass ein Admin sie einzeln als Favorit/PRO markieren muss. Das Premium-Tier läuft dabei als letzter Duty-Tier (siehe „Priorität" unten): es wird täglich eingeplant, verdrängt aber die geplante Rotation nicht, sondern füllt nur die verbleibende Kapazität.

  • Prozentsatz je Plattform getrennt konfigurierbar (POSTBOX_PREMIUM_TIER_YOUTUBE_PERCENT / POSTBOX_PREMIUM_TIER_INSTAGRAM_PERCENT, Default jeweils 10 %, Hard-Cap 50 %, 0 = deaktiviert).
  • Schwellwert: Der PremiumTierService berechnet je Plattform den Follower-Wert am (100 − X)-ten Perzentil (PostgreSQL percentile_cont), gecacht für 4 h. Ein Profil ist Premium, wenn followers_count >= Schwellwert.
  • Min-Floor: Der Schwellwert wird auf POSTBOX_PREMIUM_TIER_MIN_FOLLOWER_FLOOR (Default 1.000) nach unten begrenzt — so kann eine schiefe Verteilung (viele 0-/NULL-Follower-Profile) das Tier nicht mit Kleinstprofilen fluten.
  • Universum: YouTube alle tracking-aktiven, nicht-gesperrten Profile; Instagram zusätzlich nur watcher-verknüpfte und nicht-private Profile (private liefern keine verwertbaren Daten).
  • Deduplizierung: Premium fügt nur Profile hinzu, die nicht ohnehin schon täglich laufen (Favorit/PRO/Leader/Candidate/New/CatchUp/heutiger Bucket).
  • Priorität (läuft zuletzt, seit 2026-06-16): Premium soll die geplante Rotation nicht verdrängen und wird daher als letzter Duty-Tier abgearbeitet — nach der regulären und der Low-Priority-Rotation. YouTube (synchroner Lauf): Premium-Profile werden in der Verarbeitungs-Sortierung ans Ende gestellt (Favoriten behalten ihren Slot, ein Favorit+Premium läuft früh mit den Favoriten). Instagram (Zwei-Pool-Lease): „reine" Premium-Jobs werden in den Low-Priority-Pool gelegt (low_priority=true) mit POSTBOX_INSTAGRAM_PRIORITY_PREMIUM (Default 3) — unter der Low-Priority-Rotation (15). Da der reguläre Pool stets komplett vor dem Low-Priority-Pool bedient wird, läuft Premium garantiert nach beiden Rotationen. Premium-Profile, die heute ohnehin in einer höheren Gruppe oder im regulären Bucket sind, behalten deren Priorität/Pool (max()).
  • Sichtbarkeit: /admin/update-status zeigt je Plattform eine „Premium-Tier (Top X %, ≥ N Follower)"-Zeile mit Soll/Erledigt-Quote.

Interne Schritte

  1. Rotation-Bucket für heute bestimmen (regulär + low-priority)
  2. Daily-Profile sammeln (PRO, Leaders, Candidates, Favorites, New, CatchUp, Premium) — Premium wird beim Sortieren ans Ende gestellt bzw. (Instagram) in den Low-Priority-Pool gelegt
  3. Reguläre Profile im heutigen Bucket laden (getrennt nach regular/low-priority)
  4. ImportWatcherFromUrl Jobs auf imports-youtube / imports-youtube-priority dispatchen
  5. DailySyncRun mit expected/processed Counts erstellen

Schedule

Alle 2 Stunden (ungerade: 01:00, 03:00, 05:00, ...) mit .withoutOverlapping(30).

Fail-Streak-Cooldown

Profile mit scrape_fail_streak >= 3 (aber unter dem Deaktivierungs-Threshold von 14) werden nicht mehr täglich gescrapt, sondern nur alle 3 Tage. Die Prüfung basiert auf last_scrape_failed_at — wenn weniger als 3 Tage seit dem letzten Fehlversuch vergangen sind, wird das Profil übersprungen. Übersprungene Profile werden in der Summary als skipped_fail_streak gezählt.

Erfolgs-Reset: Jeder erfolgreiche Scrape setzt scrape_fail_streak auf 0 zurück (inkl. last_scrape_failed_at, last_scrape_error, api_status='active'). Das gilt auch für den Admin-„Bulk Queue"-Pfad (RefreshSocialProfile-Job) — vorher behandelte dieser Job nur Fehlschläge, sodass ein wieder funktionierendes Profil im Cooldown hängen blieb.

Dashboard-Zählung: Im Admin-Update-Status werden aktiv im Cooldown geparkte Profile (Streak ≥ N und letzter Fehler jünger als das Cooldown-Fenster) nicht in die „Catch-Up"-Soll-Zahl gezählt — sie erscheinen separat unter „im Fail-Streak-Cooldown". So spiegelt die Catch-Up-Quote tatsächlich abarbeitbare Arbeit (sonst wirkte Catch-Up festgefahren, weil tote Profile im Cooldown den Nenner aufblähten). Profile, deren Cooldown-Fenster abgelaufen ist, zählen wieder als (fällige) Catch-Up.

Konfiguration

POSTBOX_YOUTUBE_ROTATION_DAYS=3            # Reguläre Profile über 3 Tage verteilen (max 4)
POSTBOX_LOW_PRIORITY_FOLLOWER_COUNT=100 # Threshold für Low-Priority
POSTBOX_LOW_PRIORITY_ROTATION_DAYS=7 # Low-Priority über 7 Tage verteilen (max 7)
POSTBOX_FAIL_STREAK_COOLDOWN_AFTER=3 # Ab welchem Fail-Streak der Cooldown greift
POSTBOX_FAIL_STREAK_COOLDOWN_DAYS=3 # Tage zwischen Retry-Versuchen im Cooldown

# ── Automatisches Premium-Tier (täglicher Abruf der größten Profile) ──
# Zusätzlich zu Favoriten/PRO werden die Top-X-% der Profile je Plattform TÄGLICH
# abgerufen (statt nur im Rotations-Bucket), dedupliziert gegen die Daily-Kriterien.
# Schwelle = Follower-Wert am (100−X)-ten Perzentil (4 h gecacht). 0 = aus, Hard-Cap 50 %.

# YouTube: kostet ~1 API-Quota-Unit pro Profil/Tag (serielle channels.list) — bei großem
# Universum die Quota beachten.
POSTBOX_PREMIUM_TIER_YOUTUBE_PERCENT=10
# Instagram: läuft über die Collector-Queue (keine API-Quota, dafür Durchsatz). Nur
# watcher-verknüpfte UND nicht-private Profile zählen zum Universum/Tier.
POSTBOX_PREMIUM_TIER_INSTAGRAM_PERCENT=10
# Untergrenze (Follower) für den Premium-Schwellwert, beide Plattformen. Schützt vor schiefer
# Verteilung (viele 0-/NULL-Follower-Profile zögen das Perzentil sonst nach unten). Höherer
# Wert aus Perzentil und Floor gewinnt.
POSTBOX_PREMIUM_TIER_MIN_FOLLOWER_FLOOR=1000
# Instagram Collector-Job-Priorität des Premium-Tiers (höher = früher geleast). Direkt UNTER
# Favoriten/PRO (20), über Low-Priority-Rotation (15). YouTube braucht keine Env (Sortier-Rang im Code).
POSTBOX_INSTAGRAM_PRIORITY_PREMIUM=18
# Instagram Collector-Job-Priorität für PRO-Profile (pro_enabled). Schließt eine Altlast: PRO
# hatte bisher keinen eigenen Slot (fiel auf 0). Jetzt auf Favoriten-Niveau (20).
POSTBOX_INSTAGRAM_PRIORITY_PRO=20

Location: app/Console/Commands/SocialScrapeDailyFollowers.php


social:queue-daily-instagram

Queued Instagram-Scrapes via Collector-Queue mit Rotation-Buckets. Profile werden in Buckets aufgeteilt, sodass jedes Profil alle N Tage gescraped wird.

social:queue-daily-instagram
{--force-today : Re-Queue auch wenn heute schon gescraped/gequeued}
{--watcher-id= : Nur bestimmte Watcher queuen (kommaseparierte IDs)}
{--reset-open : Offene daily_scrape Jobs zurücksetzen}

Beispiele

# Standard-Lauf
php artisan social:queue-daily-instagram

# Erneut queuen, auch wenn schon gescraped heute
php artisan social:queue-daily-instagram --force-today

# Bestimmte Watcher queuen
php artisan social:queue-daily-instagram --watcher-id=12,15

# Offene daily_scrape Jobs zurücksetzen (Watcher-Imports bleiben unberührt)
php artisan social:queue-daily-instagram --reset-open

Optionen

OptionBeschreibung
--force-todayRe-Queue auch bei bereits vorhandenen Jobs/Scrapes
--watcher-id=Kommaseparierte Watcher-IDs
--reset-openQueued/Expired daily_scrape Jobs zurücksetzen

Low-Priority-Rotation

Gleiche Logik wie bei YouTube (siehe oben), plus: Private Instagram-Profile (is_private = true) werden ebenfalls als Low-Priority eingestuft, da sie kaum verwertbare Daten liefern (keine Posts, kein Engagement). Favorites, PRO-Profile, Leaders und Candidates sind ausgenommen.

Interne Schritte

  1. Rotation-Bucket für heute bestimmen (regulär + low-priority)
  2. PRO-Profile, Leaders, Candidates, Favorites, New, CatchUp sammeln
  3. Reguläre und Low-Priority-Profile im jeweiligen Bucket laden
  4. Offene Jobs prüfen (queued/leased) um Duplikate zu vermeiden
  5. Stale Jobs > 2 Tage prunen
  6. Collector-Jobs via CollectorJobDispatcher erstellen (Chunks à 1000, 250ms Delay)
  7. Cache-Marker health:instagram_queue_ran:{date} mit akkumuliertem Queued-Count setzen

Priority-System

Innerhalb eines Collector-Pools wird nach Priorität (höher = zuerst geleast) abgearbeitet. Der Lease-Endpoint bedient den regulären Pool (low_priority=false) stets komplett vor dem Low-Priority-Pool (low_priority=true) — die Zahl sortiert also nur innerhalb eines Pools.

PrioritätKonfigurationDefaultPool
New ProfilePOSTBOX_INSTAGRAM_PRIORITY_NEW_PROFILE60regulär
Top-Leader (Top/Flop 100)POSTBOX_INSTAGRAM_PRIORITY_TOP_LEADER50regulär
Candidate (Rising)POSTBOX_INSTAGRAM_PRIORITY_CANDIDATE40regulär
Reguläre Rotation (heutiger Bucket)POSTBOX_INSTAGRAM_PRIORITY_REGULAR_ROTATION30regulär
Favorite / PROPOSTBOX_INSTAGRAM_PRIORITY_FAVORITE / _PRO20regulär
Low-Priority-RotationPOSTBOX_INSTAGRAM_PRIORITY_LOW_PRIORITY_ROTATION15low-priority
CatchUp (verpasste Rotation)POSTBOX_INSTAGRAM_PRIORITY_CATCH_UP10je nach Profil
Low-Priority New ProfilePOSTBOX_INSTAGRAM_PRIORITY_LOW_PRIORITY_NEW_PROFILE5low-priority
Premium-Tier (läuft zuletzt)POSTBOX_INSTAGRAM_PRIORITY_PREMIUM3low-priority
DefaultPOSTBOX_INSTAGRAM_PRIORITY_DEFAULT0regulär

Onboarding neuer Profile (wichtig): Ein noch nie gescraptes Profil (last_daily_scrape_on IS NULL) wird immer über den regulären Pool mit new_profile-Priorität (60) eingeplant — unabhängig von seiner (noch unbekannten, 0/NULL) Follower-Anzahl. Erst ab dem ersten Scrape greift die Low-Priority-Einstufung (Follower < POSTBOX_LOW_PRIORITY_FOLLOWER_COUNT oder privat). Andernfalls würde ein neues Profil wegen followers_count = 0 fälschlich als Low-Priority eingestuft, im Low-Priority-Pool landen (Onboarding-Prio 5) und vom Collector — der den regulären Pool zuerst leert — nie erreicht (Symptom: „Neue Profile" dauerhaft bei 0 % erledigt).

Fail-Streak-Cooldown

Gleiche Logik wie bei YouTube: Profile mit scrape_fail_streak >= 3 werden nur alle 3 Tage gescrapt (basierend auf last_scrape_failed_at). Übersprungene Profile werden als skipped_fail_streak in der Summary gezählt. Erfolgs-Reset und Dashboard-Zählung (Cooldown-Profile aus dem Catch-Up-Soll ausgenommen, separat ausgewiesen) gelten identisch zu YouTube — siehe oben.

Schedule

Alle 2 Stunden (gerade: 00:00, 02:00, 04:00, ...) mit .withoutOverlapping(30).

Location: app/Console/Commands/QueueDailyInstagramScrapes.php

Queue-Fähigkeit & Dashboard-Konsistenz

Der tägliche Instagram-Queue-Command plant nur Profile ein, die mit mindestens einer watcher_source verknüpft und nicht blockiert (blocked_at IS NULL) sind — ein Profil, das niemand beobachtet, verbraucht keine Scrape-Kapazität. Wichtig: Der InstagramUpdateStatusService (Admin-Update-Status + Bonus-Gatekeeper) zählt im Tages-Soll exakt dieselbe queue-fähige Menge. Andernfalls bleiben verwaiste tracking_enabled-Profile (deren Watcher gelöscht wurde) oder blockierte Profile dauerhaft als „Neue Profile" bzw. „hinter Rotationsplan" stehen und halten not_updated_today über null — was das Bonus-Scraping permanent blockiert (siehe unten). Die Gesamtzahl und der Low-Priority-Pool im Dashboard bleiben hingegen Headline-Werte über alle getrackten Profile.

YouTube unterscheidet sich bewusst: SocialScrapeDailyFollowers verlangt keine watcher_source-Verknüpfung (nur blocked_at IS NULL), daher gibt es dort keinen solchen strukturellen „Boden".


social:detect-languages

Erkennt Sprache, Land und Kategorie von Social Profiles via Gemini AI. Unterstützt synchrone Verarbeitung und asynchrones Queue-Processing.

social:detect-languages
{--limit=100 : Maximale Anzahl Profile}
{--force : Auch bereits erkannte Profile neu erkennen}
{--profile= : Bestimmte Profile-ID}
{--queue : Jobs asynchron in Queue dispatchen}
{--queue-limit=500 : Maximale Profile pro Queue-Run}
{--flush : Alle pending ai-detection Jobs aus Queue entfernen}

Beispiele

# Synchrone Erkennung (100 Profile)
php artisan social:detect-languages --limit=100

# Asynchron in Queue dispatchen
php artisan social:detect-languages --queue --queue-limit=200

# Bestimmtes Profil erneut erkennen
php artisan social:detect-languages --profile=12345 --force

# Queue leeren
php artisan social:detect-languages --flush

Optionen

OptionBeschreibung
--limit=100Max Profile für synchrone Verarbeitung
--forceRe-Detection auch bei vorhandenen Daten
--profile=Einzelnes Profil via ID
--queueAsynchroner Queue-Modus
--queue-limit=500Max Profile pro Queue-Dispatch
--flushAlle pending ai-detection Jobs entfernen

Interne Schritte (Queue-Modus)

  1. Hard-Cap prüfen: Überspringen wenn Queue bereits voll
  2. Profile ohne Sprach-/Land-Daten laden (sortiert nach Follower-Anzahl)
  3. Cooldown beachten (Default: 365 Tage)
  4. DetectProfileLanguage Jobs dispatchen (Rate-Limited: 15 RPM via Job-Middleware)

Schedule

Alle 15 Minuten mit --queue und dynamischem --queue-limit (hourly_limit / 4).

Location: app/Console/Commands/DetectChannelLanguages.php


watchers:queue-rescrape

Queued Re-Scrapes für Watcher basierend auf Metriken-Ranges. Unterstützt Instagram (Collector) und YouTube (Queue Job).

watchers:queue-rescrape {type}
{--min-follower=} {--max-follower=}
{--min-following=} {--max-following=}
{--min-posts=} {--max-posts=}
{--force}

Beispiele

# Instagram: Re-Scrape für Follower-Range
php artisan watchers:queue-rescrape instagram --min-follower=123 --max-follower=999 --force

# YouTube: Re-Scrape für niedrige Follower
php artisan watchers:queue-rescrape youtube --min-follower=100 --max-follower=999

# Kombinierte Filter
php artisan watchers:queue-rescrape instagram --min-follower=500 --max-posts=200

Optionen

OptionBeschreibung
typePlattform: instagram oder youtube
--min-follower= / --max-follower=Follower-Range
--min-following= / --max-following=Following-Range
--min-posts= / --max-posts=Post/Video-Range
--forceInstagram: force=true im Collector-Job setzen

Interne Schritte

  1. Profile nach Latest-Metriken filtern (mindestens ein Filter erforderlich)
  2. Ergebnisse nach Workspace gruppieren
  3. Instagram: Collector-Jobs erstellen / YouTube: ImportWatcherFromUrl Jobs dispatchen
  4. Pro Workspace ein Run erstellen

Schedule

Manuell (kein automatischer Schedule).

Location: app/Console/Commands/QueueFollowerRangeRescrape.php