Damit Hobby-Youtuber nicht verpassen, wenn ihre Guckerzahlen durch die Decke gehen, analysiert ein Perlskript einmal täglich Trends und schlägt Alarm, falls ein Video plötzlich zum Massenhit wird.
Steht ein Hobby-Handwerker wie ich heute vor einem scheinbar unlösbaren mechanischen Problem, zum Beispiel weil ein Gadget-Gehäuse sich gegen das garantieverachtende Öffnen sträubt, hilft meist ein Youtube-Video weiter. Auch bei nicht ganz einfach zu bedienender Software wie dem Gimp zeigt meist ein Fachmann in einem Screencast auf Youtube wie man als Hobby-Gimper auch komplizierte Aufgaben löst.
Als es mir deshalb vor einiger Zeit gelang, Gimps Scissor-Tool erfolgreich anzuwenden, machte ich gleich einen Screencast daraus, und warte nun vergeblich darauf, dass dieses meisterlich produzierte Video in den Charts aufsteigt. Erst setzte ich mir monatliche Erinnerungsnachrichten in Evernote, um in regelmäßigen Abständen von Hand die Anzahl der "Views" auf Youtube zu inspizieren, aber das kostet Zeit, die ich nicht habe. Dank des CPAN-Moduls WebService::GData::YouTube geht es nun auch automatisch.
Abbildung 1: Der Hobbyproduzent dieses Youtube-Screencasts möchte wissen, wie sich seine Userzahlen entwickeln (297 Views bisher). |
01 - 02 name: GIMP - Scissors Select Tool 03 id: _Cxu3-UP0G8 04 - 05 name: How to fix Acura/Honda not starting in hot weather 06 id: ssYpyXjGw9g 07 - 08 name: Flip it: Record Messages and play them Backwards 09 id: LdSTIa2Tx4o 10 - 11 name: Scored a goal at Garfield Square 12 id: bvOvnyJTyss
Hierzu listet die YAML-Datei in Listing 1 die IDs der zu überwachenden Videos auf. Diese Hex-Nummern lassen sich einfach aus den entsprechenden Youtube-URLs extrahieren, zum Beispiel der String "_Cxu3-UP0G8" in der URL-Zeile des Browsers oben in Abbildung 1 (vielleicht in der Abbildung farblich markieren?).
=cut ###########################################################################
Abbildung 2: Die JSON-Ausgabe von Listing 2 mit den von Youtube eingeholten Guckerzahlen pro Video. |
Das Skript in Listing 2 geht nun mit Hilfe des YAML-Moduls vom CPAN
der Reihe nach durch die Einträge der YAML-Datei und übergibt jeweils
die Video-ID an die Methode get_video_by_id()
des Pakets
WebService::GData::YouTube. Dieses wiederum kontaktiert den API-Server von
Youtube und bekommt einen Wust an Metadaten über das Video
zurück. Unter dem Schlüssel _feed
findet das Skript
im Eintrag yt$statistics
den Wert für ViewCount
, also die
Anzahl der Aufrufe des Videos von begeisterten Youtube-Kunden.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use WebService::GData::YouTube qw(); 04 use YAML qw( LoadFile ); 05 use JSON qw( to_json ); 06 07 my @result = (); 08 my $videos = 09 LoadFile( "youtube-watch.yml" ); 10 my $yt = WebService::GData::YouTube->new(); 11 12 for my $video ( @$videos ) { 13 14 my $meta = 15 $yt->get_video_by_id( $video->{ id } ); 16 17 $video->{ count } = $meta->{ _feed }-> 18 { 'yt$statistics' }->{ viewCount }; 19 20 push @result, $video; 21 } 22 23 print to_json( \@result, 24 { pretty => 1, canonical => 1 } );
Das Skript formt die Ausgabedaten am Ende noch schnell mittels des
JSON-Moduls vom CPAN in das maschinenlesbare Format um, damit nachfolgende
Skripts with Listing 3 sie leicht verarbeiten können. Dabei setzt es
die Option pretty
, um die Ausgabe auch für menschliche Konsumenten
leicht lesbar darzustellen und canonical
, damit die sonst in Perl
unsortierten Hashschlüssel sortiert herauskommen.
Im Ergebnisarray @result
führt dann jedes Element
die Felder name
(den Titel
des Videos), count
(den in Zeile 17 hineingeschmuggelten Viewcount) und
id
, die eindeutige Youtube-Kennung des Videos.
Abbildung 2 zeigt die Ausgabe des Skripts, und offenbart, dass meine bislang wenig öffentlich bekannte Tätigkeit als Automechaniker an zwanzig Jahre alten Honda-Motoren mit ca. 56.000 Aufrufen zu den Highlights meines Oevres gehört, während der Gimp-Screencast mit nur 297 Aufrufen noch (!) vor sich hindümpelt.
Läuft Listing 1 mittels eines Cronjobs jeden Tag um dieselbe Uhrzeit einmal ab, steht als nächstes die Archivierung der Daten zur späteren historischen Auswertung an. Hierzu dient Listing 4 die Minidatenbank SQLite, die ihre Daten in einer einzigen Datei ablegt, aber mittels eines Query-Engines SQL-Abfragen erlaubt.
1 CREATE TABLE views ( 2 video_id TEXT, 3 views INTEGER, 4 queried DATE 5 );
Das in Listing 3 gezeigte Datenbankschema enthält
Felder für die Youtube-ID des Videos (video_id
),
die Anzahl der zum Zeitpunkt der
Abfrage festgestellte Anzahl der Views (views
)
und den Datumsstempel der Abfrage (queried
).
Mit dem Kommando
$ sqlite3 viewcounts.db <viewcounts.sql
erzeugt das mit sudo apt-get sqlite3
installierte SQLite dann die
Datenbank in der frischen Datei viewcounts.db
. Damit kann dann
Listing 4 als zweite Brennstufe nach Listing 2 mittels
youtube-viewcounts | viewcounts-todb
die von Listing 2 erzeugten JSON-Daten entgegennehmen und für jeden
Array-Eintrag einen Datenbankrecord anlegen. Das Datumsfeld jedes neuen
Eintrags füllt es in Zeile 16 mit CURRENT_TIMESTAMP
, was SQLite in
der Datenbank durch das aktuelle Datum mit Uhrzeit ersetzt. So entsteht
für jedes überwachte Video pro Tag ein Datenbankeintrag mit Zeitstempel,
die sich hinterher leicht zeitlich sortiert wieder herausholen lassen.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use DBD::SQLite; 04 use JSON qw( from_json ); 05 use DBI; 06 my $dbh = DBI->connect( 07 "dbi:SQLite:dbname=viewcounts.db", 08 "", "", { RaiseError => 1} ); 09 10 my $data = from_json( join '', <STDIN> ); 11 12 for my $video ( @$data ) { 13 my $sth = $dbh->prepare( 14 "INSERT INTO 15 views(video_id,views,queried) 16 VALUES(?,?,CURRENT_TIMESTAMP)" ); 17 $sth->execute( $video->{ id }, 18 $video->{ count } ); 19 }
Abbildung 3: In der SQLite-Datenbank speichert das Skript täglich die aktuellen Viewzahlen ab. |
Anhand der in Abbildung 3 gezeigten Datenbankeinträge versucht nun Listing 5, herauszufinden, ob die Zuschauerzahlen nur stetig ansteigen or sich explosionsartig erhöhen. In diesem Fall soll das Skript später den interessierten Beobachter alarmieren.
Was macht nun einen hitverdächtigen Sprung aus? Bei einem Video mit 30.000 Aufrufen, das im Schnitt 10 Views pro Tag zulegt, bedeuten 10 mehr nicht viel. Bei einem weniger erfolgreichen, das lange unentdeckt blieb und nun auf einmal 10 Views zulegt, wäre vielleicht eine Notiz angebracht.
Da ich letztens das ausgezeichnete Buch "Machine Learning with R" ([2]) studiert habe, das allerhand statistische Methoden erläutert, kam mir die Idee, außergewöhnliche Sprünge in den Viewerzahlen mittels linearer Regression ([3]) zu ermitteln.
Abbildung 4: Einen Hit erkennt Listing 5 wenn der neueste Wert (blauer Punkt ganz rechts) die per linearer Regression geschätzten Wert weit übersteigt. |
Dabei versucht das Modul Statistics::LineFit vom CPAN in Listing 5, durch flache oder stetig ansteigende historische Zuschauerzahlen eine Gerade zu legen (Abbildung 4). Auf der x-Achse läuft die Zeit von links nach rechts und die y-Achse gibt die Anzahl der zum Messzeitpunkt vorliegenden Views an.
Dabei ist es nicht so wichtig, dass die Gerade jeden
Messpunkt genau trifft (was auch gar nicht möglich ist), solange das
Verfahren die zwangsläufig auftretenden Fehler minimal hält. Das Problem
ist wissenschaftlich seit Urzeiten gelöst und das CPAN-Modul implementiert
nur den Algorithmus, um die Parameter a
und b
aus der Geraden-Formel
y = b + a*x
zu berechnen. Der Parameter b
gibt dabei den y-Wert
am Nulldurchgang der Geraden an ("intercept"),
während a
die Steigung ("slope") der Geraden darstellt.
Listing 5 iteriert in Zeile 16 durch alle zu überwachenden Videos und
setzt für jedes einen SQL-Query ab, um die historischen View-Zahlen
aufsteigendend sortiert nach dem Messzeitpunkt hervorzuholen. Diese
y-Werte stopft es in den Array @yvalues
, während es die
zugehörigen X-Werte bei 1 anfangen lässt und dann an jedem
Messtag um jeweils eins erhöht. In $row[0]
, dem ersten Element
einer jeden Zeile des SQL-Ergebnisses steht der View-Count des gerade
bearbeiteten Videos zum Messzeitpunkt.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use DBI; 04 use DBD::SQLite; 05 use Statistics::LineFit; 06 use YAML qw( LoadFile ); 07 use JSON qw( to_json ); 08 09 my $dbh = DBI->connect( 10 "dbi:SQLite:dbname=viewcounts.db", 11 "", "", { RaiseError => 1} ); 12 13 my $videos = 14 LoadFile( "youtube-watch.yml" ); 15 16 for my $video ( @$videos ) { 17 my $sth = $dbh->prepare( 18 "SELECT views from views 19 WHERE video_id = ? ORDER BY 20 queried ASC" ); 21 $sth->execute( $video->{ id } ); 22 23 my @xvalues = (); 24 my @yvalues = (); 25 26 my $x = 1; 27 while ( my @row = 28 $sth->fetchrow_array ) { 29 push @xvalues, $x; 30 push @yvalues, $row[0]; 31 $x++; 32 } 33 34 my $last_x = pop @xvalues; 35 my $last_y = pop @yvalues; 36 37 my $fit = Statistics::LineFit->new(); 38 $fit->setData (\@xvalues, \@yvalues) or 39 die "Invalid data"; 40 41 my ($intercept, $slope) = 42 $fit->coefficients(); 43 44 my $y_predicted = $intercept + 45 $last_x * $slope; 46 47 if( abs( $last_y - $y_predicted ) > 48 3 * $slope ) { 49 print 50 "Hooray, $video->{ name } is a hit!\n"; 51 } 52 }
Die Zeilen 34 und 35 entfernen anschließend den letzten (tagesaktuellen)
Messwert, damit das Skript die Regressionsgerade nur durch die mehr als
einen Tag zurückliegenden Messpunkte legt. Die Methode coefficients()
in Zeile 42 liefert schließlich den errechneten Nulldurchgang in
$intercept
und die Steigung der Geraden in der Variablen $slope
.
Mit diesen interpoliert Zeile 44 den Wert für die letzte Messung
$y_predicted
, ermittelt also, welcher Wert zu erwarten wäre, falls
die Zuschauerzahlen wie bisher weitergestiegen wären. Ist die
Differenz zum wirklich gemessenen Wert dreimal so groß wie die
Geradensteigung, liegt offensichtlich ein virales Video vor, und der
User erfährt davon durch die ausgegebene Nachricht. Mit dem Faktor 3
sollte man etwas experimentieren, manch einer möchte schon bei kleineren
Erfolgen Bescheid bekommen. Statt der print-Anweisung empfiehlt sich
die Benachrichtigung per Email, zum Beispiel mit dem CPAN-Modul
Mail::DWIM, das dazu nur ein paar Zeilen benötigt, und auch HTML-Emails
beherrscht, sodass der beglückte Empfänger gleich auf sein
frischgebackenes Hitvideo klicken kann.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2015/01/Perl
"Machine Learning with R", Brett Lantz, Packt Publishing, 2013
"Lineare Regression", http://de.wikipedia.org/wiki/Lineare_Regression