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
| Option | Beschreibung |
|---|---|
--force-today | Scrape für heute erzwingen, unabhängig vom Rotation-Bucket |
--watcher-id= | Kommaseparierte Watcher-IDs (nur diese werden gescraped) |
--dry-run | Zeigt 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)
| Kategorie | Kriterium |
|---|---|
| PRO | YouTubeVideoSync.auto_sync_enabled = true oder pro_enabled = true |
| Leaders | Top/Flop 100 aus Leaderboards |
| Candidates | Rising-Profile die in Top 100 kommen könnten |
| Premium | Top X % nach Follower-Anzahl (automatisch, POSTBOX_PREMIUM_TIER_YOUTUBE_PERCENT, Default 10 %) — siehe „Premium-Tier" unten |
| Favorites | User-favorisierte Profile |
| New | Noch nie gescraped (last_scraped_at = null) |
| CatchUp | Verpasstes 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
PremiumTierServiceberechnet je Plattform den Follower-Wert am(100 − X)-ten Perzentil (PostgreSQLpercentile_cont), gecacht für 4 h. Ein Profil ist Premium, wennfollowers_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) mitPOSTBOX_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-statuszeigt je Plattform eine „Premium-Tier (Top X %, ≥ N Follower)"-Zeile mit Soll/Erledigt-Quote.
Interne Schritte
- Rotation-Bucket für heute bestimmen (regulär + low-priority)
- 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
- Reguläre Profile im heutigen Bucket laden (getrennt nach regular/low-priority)
ImportWatcherFromUrlJobs aufimports-youtube/imports-youtube-prioritydispatchenDailySyncRunmit 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
| Option | Beschreibung |
|---|---|
--force-today | Re-Queue auch bei bereits vorhandenen Jobs/Scrapes |
--watcher-id= | Kommaseparierte Watcher-IDs |
--reset-open | Queued/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
- Rotation-Bucket für heute bestimmen (regulär + low-priority)
- PRO-Profile, Leaders, Candidates, Favorites, New, CatchUp sammeln
- Reguläre und Low-Priority-Profile im jeweiligen Bucket laden
- Offene Jobs prüfen (queued/leased) um Duplikate zu vermeiden
- Stale Jobs > 2 Tage prunen
- Collector-Jobs via
CollectorJobDispatchererstellen (Chunks à 1000, 250ms Delay) - 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ät | Konfiguration | Default | Pool |
|---|---|---|---|
| New Profile | POSTBOX_INSTAGRAM_PRIORITY_NEW_PROFILE | 60 | regulär |
| Top-Leader (Top/Flop 100) | POSTBOX_INSTAGRAM_PRIORITY_TOP_LEADER | 50 | regulär |
| Candidate (Rising) | POSTBOX_INSTAGRAM_PRIORITY_CANDIDATE | 40 | regulär |
| Reguläre Rotation (heutiger Bucket) | POSTBOX_INSTAGRAM_PRIORITY_REGULAR_ROTATION | 30 | regulär |
| Favorite / PRO | POSTBOX_INSTAGRAM_PRIORITY_FAVORITE / _PRO | 20 | regulär |
| Low-Priority-Rotation | POSTBOX_INSTAGRAM_PRIORITY_LOW_PRIORITY_ROTATION | 15 | low-priority |
| CatchUp (verpasste Rotation) | POSTBOX_INSTAGRAM_PRIORITY_CATCH_UP | 10 | je nach Profil |
| Low-Priority New Profile | POSTBOX_INSTAGRAM_PRIORITY_LOW_PRIORITY_NEW_PROFILE | 5 | low-priority |
| Premium-Tier (läuft zuletzt) | POSTBOX_INSTAGRAM_PRIORITY_PREMIUM | 3 | low-priority |
| Default | POSTBOX_INSTAGRAM_PRIORITY_DEFAULT | 0 | regulä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:
SocialScrapeDailyFollowersverlangt keinewatcher_source-Verknüpfung (nurblocked_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
| Option | Beschreibung |
|---|---|
--limit=100 | Max Profile für synchrone Verarbeitung |
--force | Re-Detection auch bei vorhandenen Daten |
--profile= | Einzelnes Profil via ID |
--queue | Asynchroner Queue-Modus |
--queue-limit=500 | Max Profile pro Queue-Dispatch |
--flush | Alle pending ai-detection Jobs entfernen |
Interne Schritte (Queue-Modus)
- Hard-Cap prüfen: Überspringen wenn Queue bereits voll
- Profile ohne Sprach-/Land-Daten laden (sortiert nach Follower-Anzahl)
- Cooldown beachten (Default: 365 Tage)
DetectProfileLanguageJobs 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
| Option | Beschreibung |
|---|---|
type | Plattform: instagram oder youtube |
--min-follower= / --max-follower= | Follower-Range |
--min-following= / --max-following= | Following-Range |
--min-posts= / --max-posts= | Post/Video-Range |
--force | Instagram: force=true im Collector-Job setzen |
Interne Schritte
- Profile nach Latest-Metriken filtern (mindestens ein Filter erforderlich)
- Ergebnisse nach Workspace gruppieren
- Instagram: Collector-Jobs erstellen / YouTube:
ImportWatcherFromUrlJobs dispatchen - Pro Workspace ein Run erstellen
Schedule
Manuell (kein automatischer Schedule).
Location: app/Console/Commands/QueueFollowerRangeRescrape.php