Apfel-Appstore-Reader - Update 2.0

Wie im Artikel - Apfel-Appstore-Reader - beschrieben, hält sich Apple bezüglich der App-Rankings (Positionierung im Appstore) sehr gedeckt. Auch im Report-Tool (ITC / iTunes-Connect -> Sales & Trends) keinerlei Informationen auftauchen, an welcher Position man sich mit der jeweiligen App in z.B. einer Kategorie wie “Produktivität” befindet, habe ich vor längerer Zeit ein kleines Java-Tool (mit dem einfallsreichen Namen Appstore-Reader) geschrieben.

amz box.jpgDie nakten Zahlen des Tools sind ganz nett, aber leider wenig aussagekräftig... Zum Einen variiert das App-Ranking über den Tag teils erheblich, zum Anderen hat man wohl kaum lust das Tool ständig aufzurufen, um sich die Werte dann irgendwo aufzuschreiben.

Eine Datenbank-Anbindung an MySQL MySQL sowie lokal an eine SQLite-DB hatte ich zwar realisiert, um mal eine Durchschnittsposition über den Monat hinweg zu errechnen, aber optimal und zufriedenstellend war das Ganze immer noch nicht.

Inhaltsverzeichnis:

Überlegungen zur Optimierung des Store-Readers

  • Auslagerung des Java-Tools vom lokalen Heim-Rechner auf einen Server im Internet.
  • Das Tool sollte selbstständig z.B. einmal pro Stunde gesteuert über einen Cron-Job alle Kategorien des Appstores durchsuchen und die Rankings der gewünschten Apps speichern.
  • Es muss eine Schnittstelle her, um aus den erfassten Daten eine grafische Übersicht zu zaubern. Tages- und Monatsübersicht -> zu welchen Zeiten finden die meisten Verkäufe/Downloads statt bzw. ist das App-Ranking am höchsten?!

Die Umsetzung - Java, PHP und JPGraph

appstore_reader_2_previewAusgangssituation ist der Apfel-Appstore-Reader. Lediglich die statischen Arrays wie die Länderkürzel, Appstore-Genres und die Links zum Appstore-RSS-Generator habe ich in die neue MySQL-Datenbank ausgelagert um etwas flexibler zusein, grade was das spätere Auswerten der Daten angeht. Aus dem Java-Code braucht man dann nurnoch ein Jar-File erstellen, auf den Server laden und einen Cron einrichten. Ich habe mich für einen stündlichen Cronjob entschieden, so soll man später Stundenweise sehen wie sich das App-Ranking verhält.

Auszug aus der Crontab - 10 Minuten nach jeder vollen Stunde von 9 bis 23 Uhr:
10 9-23 * * * root java -jar /var/www/myweb/httpdocs/java/sync.jar > /dev/null

amz music.pngDie Software speichert nun stündlich anhand der App-ID den aktuellen App-Rang und folgende Informationen. Felder: app_id, datum, uhrzeit, kategorie_id (ID des Appstore-Genres, z.B. 6002 für Dienstprogramme), store_kategorie_id (eindeutige Kennung, z.B. 1 = Top Apps Kostenlos, 2 = Top Apps iPad, 3 = Top Apps Umsatzstark, usw....), rank (Position der App im Store), store_lang (Appstore, z.B. de für Deutschland).

Jetzt wird es Zeit die Daten in Form zubringen. :) Ich habe mich für die OpenSource-Lösung JPGraph entschieden, um die erfassten App-Ranking-Daten visuell aufzubereiten. Mit diesem PHP-Framework kann man schnell tolle Grafiken erstellen. JPGraph ist sehr mächtig und verfügt über unzählige Anpassungsmöglichkeiten, daher gehe ich hier nur auf einige Basics ein. ;-)

Zunächst holt man sich die Library per include bzw. require in seine PHP-Funktion.
require_once ($jpgraph_path.'/src/jpgraph.php');
require_once ($jpgraph_path.'/src/jpgraph_line.php');

Jetzt erstellt man den Graphen, im Beispiel soll die Grafik 800x500 Pixel groß werden, und anhand einer Kurve den Ranking-Verlauf über den Tag hinweg zeigen. Eine Legende unter der Grafik soll helfen das gezeigte auch zu verstehen.

$graph = new Graph(800,500);
$graph->legend->Pos(0.00,0.5,"center","bottom");
$graph->legend->SetLayout(LEGEND_HOR);
$graph->legend->SetFont(FF_FONT1, FS_NORMAL);
$graph->SetScale("textlin");
$graph->SetAxisStyle(AXSTYLE_BOXOUT);

Nun fügen wir ein bischen Margin, also freie Fläche rund um die Grafik ein, damit das Ganze nicht ganz so gedrückt aussieht. Dann fügen wir eine Überschrift (Ranking + App-Name) über das Diagramm ein, und etwas kleiner darunter das Datum der gezeigten Daten inkl. Appstore-Kategorie und Land/Sprache des Stores. Dann noch die Beschriftung für die X- und Y-Achse.

$graph->img->SetMargin(70,70,80,30);

$graph->title->Set("Ranking - ".$app_name);
$graph->title->SetFont(FF_FONT1,FS_BOLD);
       
$graph->subtitle->Set($this->getDateLongFromSqlDate($sqldate)." - ".$kategorie_name." / ".$lang);
$graph->subtitle->SetFont(FF_FONT1,FS_NORMAL);

$graph->yaxis->SetTitle("Rang");
$graph->xaxis->SetTitle("Uhrzeit");

An dieser Stelle laden wir die erfassten Daten in ein Array, woraus dann die entsprechenden Kurven erstellt werden. $dataLabels enthält die Punkte auf der X-Achse (Uhrzeiten: 8 bis 22 Uhr). $datay ist ein mehrdimensionales Array und behinhaltet alle Y-Daten (App-Store-Positionen zur entsprechenden Uhrzeit). Da in meinem Fall bis zu 6 Store-Sektionen angezeigt werden sollen (Top-Apps, Top-Apps iPad, usw...) speichere ich die IDs und Namen in separaten Arrays ($stores und $store_kategorie_ids). Die Daten aus Sektion 0 (z.B. Top-Apps mit ID 1) werden dann in $datay[0] gespeichert, Sektion 1 in $datay[1] etc... Auf den ersten Blick etwas wirsch, auf den Zweiten sehr logisch und schön wie ich finde. ;-)

$dataLabels = array();
$count = 0;
       
for ($i=8; $i<=22; $i++)
{
               for ($s=0; $s<$store_count; $s++)
               {
                   $q = "SELECT rank FROM ".TABLE_APPS_RANKING." WHERE app_id='".$id."'"
                    ." AND store_kategorie_id='".$store_kategorie_ids[$s]."' AND store_lang='".$lang."'"
                       ." AND kategorie_id='".$kategorie_id."'"
                       ." AND datum='".$sqldate." ".$i.":00:00'";
                   $res = $this->query($q);
                                                                       
                   if ($data = mysql_fetch_assoc($res))
                   {
                       if (!$has_data)
                           $has_data = true;
                   }
                   else
                   {
                       $data = array();
                       $data['rank'] = '';
                   }
                   $datay[$s][$count] = $data['rank'];
               }
       
               $dataLabels[$count] = $i.":00";
               $count++;
}
$graph->xaxis->SetTickLabels($dataLabels);

Damit der bessere Rang oben steht und nicht unten müssen wir noch die Y-Achse spiegeln. Das funktioniert ganz einfach indem wir die Y-Werte ins Negative bringen.

for ($s=0; $s<$store_count; $s++)
{
    $n = count($datay[$s]);
    for($i=0; $i<$n; ++$i)
    {
         if ($datay[$s][$i]!="")
                $datay[$s][$i] = round(-$datay[$s][$i]);
    }
}

if (!function_exists("_cb_negate"))
{
     function _cb_negate($aVal)
    {  return round(-$aVal);  }
}

$graph->yaxis->SetLabelFormatCallback("_cb_negate");

Abschließend werden noch die entsprechenden Kurven (LinePlot) mit Hilfe der Y-Daten erstellt und in den Graphen geladen. Ein kleines Array namens $colors mit Farbwerten hilft, die einzelnen Linien zu unterscheiden. ->setLegend(titel) darf nicht fehlen, um die Beschreibung dieser der Legende hinzuzufügen. Alle anderen Aufrufe wurden aufs wesentliche runtergebrochen und sollten selbsterklärend sein. :-)

$colors = array("#00FF00", "#0000FF", "#FF0000", "#00FFFF", "#FFFF00", "#FF00FF");
for ($i=0; $i<$store_count; $i++)
{
               $p1 = new LinePlot($datay[$i]);
               $graph->Add($p1);
                                                       
               $p1->SetLegend($stores[$i]);
                                                       
               $p1->mark->SetType(MARK_SQUARE, 1.0);
               $p1->mark->SetWidth(3);
               $p1->mark->SetFillColor($colors[$i]);
                                                  
               $p1->SetColor($colors[$i]);
               $p1->SetWeight(2);                                                                             
               $p1->SetStepStyle(true);
}

Jetzt noch $graph->Stroke() aufrufen, um die Grafik zu erstellen. Als Parameter wird der Pfad übergeben, wohin die Datei gespeichert werden soll. Alternativ kann man die Funktion Stroke() auch ohne Parameter aufrufen, was zur Folge hat, dass die Grafik direkt zurückgeben wird. Funktioniert auch wenn man das PHP-Script einzig zum Generieren nutzt und die Grafik mit z.B.  <img src="meinscript.php"> aufrufen will.

Grafik als Datei speichern: $saveas z.B. ./images/grafik.png
$graph->Stroke($saveas);

Das Resultat - Apfel-Appstore-Reader 2.0

Das Resultat ist eine kleine ansehnliche Grafik des App-Rankings der jeweiligen App.

appstore_reader_2_screen

Passt man das Script hier und da etwas an, kann man ganz einfach auch mehrere Apps mit einander vergleichen, oder Monats- und Jahresübersichten visualisieren.

Grafiken / Reports automatisiert per E-Mail verschicken

Kaum jemand hat lust sich jeden Tag in sein Backoffice einzuloggen um einzelnt die Reports durchzuklicken. Daher hier noch kurz ein Beispiel für den Mailversand der Grafiken bzw. Berichte. Mit Hilfe von PHP und eines Crons am morgen kein Problem! Alles was wir brauchen ist eine Funktion der wir die E-Mail-Adresse des Empfängers, einen Betreff, eine Nachricht und ein Array mit den Pfaden zu den erstellten Reports (PNG-Files). Das Script sollte selbsterklärend sein.:)

function sendMailWithAttachment($email, $subject, $msg, $files=array())
   {
           $nl = "\r\n";
           
           $header = "From: ".ADMIN_EMAIL;
           $boundary = strtoupper(md5(uniqid(time())));
       
           $header .= "\nMIME-Version: 1.0";
           $header .= "\nContent-type: multipart/mixed; boundary=".$boundary;
           $header .= "\n\nThis is a multi-part message in MIME format  --  Dies ist eine mehrteilige Nachricht im MIME-Format";
       
           /* mail-inhalt */
           $header .= $nl."--".$boundary;
           $header .= $nl."Content-type: text/html";
           $header .= $nl."Content-Transfer-Encoding: 8bit";
           $header .= $nl.$nl.$msg;
       
           /* anhänge anfügen */
           for ($i=0; $i<count($files); $i++)
           {
               $file = $files[$i];
           
               if (file_exists($file))
               {
                   $inhalt = $this->read_file($file);
                   
                   //nur png-files unterstützt; ggf. noch content-types einfügen; jpg, gif, pdf, etc...
                   $filename = "anhang".($i+1).".png";
                   
                   $header .= $nl."--".$boundary;
                   $header .= $nl."Content-type: images/png; name=\"".$filename."\"";
                   $header .= $nl."Content-Transfer-Encoding: base64";
                   $header .= $nl."Content-Disposition: attachment; filename=\"".$filename."\"";
                   $header .= $nl.$nl.$inhalt;
               }
           }
           
//ende der mail
$header .= $nl."--".$boundary."--";
           
//senden
mail($email, $subject, "", $header);

Schlusswort

Interessant was man so über die eigene App und Zielgruppe erfährt. Eine der Überlegungen wäre, wenn die meisten Downloads der App z.B. zwischen 19 und 21 Uhr gefahren werden, könnte man seine Facebook-Werbekampagnen genau auf diese Stoßzeiten legen, um das Ranking noch weiter in die Höhe zu treiben, anstatt plätscherweise sein Budget über den Tag zu verschleudern. ;-) *Nur ein Gedanke*

Hoffe das Lesen hat Spaß gemacht, Feedback und Anregungen erwünscht! :-)

Author: Sascha von Saschas-Bastelstube.de

Autor: sascha

Ein paar Worte über mich selbst. Mein Name ist Sascha, wie der Titel dieses Blogs erahnen lässt. :-) Ich bin von Beruf selbstständiger Programmierer und Trader. Neben Programmierung, Trading und Kampfsport sind einige meiner Interessen und Hobbys das Kochen, Fitness und das Hören lauter Heavy-Metal Musik. :D

Dies könnte dich auch interessieren

Unity 2D Platformer - Level Generator

Seit einiger Zeit experimentiere ich hier und da mit der 2D- und 3D Engine Unity. Insbesondere die Programmierung eines 2D-Platformers hat es mir aktuell angetan. Allerdings ist das programmieren eines 2D-Games (sofern man Programmierer ist) nicht das größte Problem, sondern meiner Meinung nach die Optik, da ich kein Grafiker bin. :D So kommt man nicht … „Unity 2D Platformer - Level Generator“ weiterlesen


DIYS Smart-Home V1 - Irgendwo muss man ja anfangen!

Schon seit einiger Zeit plane ich mein Smart-Home. Neben dem Kostenfaktor für all die tollen Dinge die ich mir so vorstelle fehlt es oft an der notwendigen Zeit zur detailierten Planung und Umsetzung. Daher habe ich beschlossen einfach mal irgendwo anzufangen und dieses DIY-Projekt parallel niederzuschreiben...mal sehen wohin das führt...Ich bitte um Nachsicht wenn ich … „DIYS Smart-Home V1 - Irgendwo muss man ja anfangen!“ weiterlesen


Programmieren: PHP image captcha code

Hier möchte ich meine kleine aber feine capture code Lösung vorstellen, welche ich vor einiger Zeit mal programmiert habe. Ich war es leid, auch in Hinblick auf die DSGVO, Capture-Lösungen externer Anbieter einzubinden. Darüber hinaus werden durch diverse Privacy-Browser z.B. auch Javascripts geblockt, sodass ein externes Image-Capture-Modul für etwa das eigene Kontakt- oder Anmeldeformular nicht … „Programmieren: PHP image captcha code“ weiterlesen


3 Gründe warum es App-Entwickler nicht einfach haben

Aus gegebenem Anlass heute mal ein kleiner Beitrag aus der Sektion "Nicht ganz so ernst gemeinte Beiträge". Hier nun einige Überlegungen warum es App-Entwickler heute schwieriger haben, als noch vor einigen Jahren. Eines vorweg: Wer in diesem Beitrag einen Hauch von Ironie und Sarkasmus findet, darf ihn behalten! ;-) Inhaltsverzeichnis:Problem 1: Die Masse an Apps … „3 Gründe warum es App-Entwickler nicht einfach haben“ weiterlesen


Hinzufügen von Website-Icons für iPhone, iPad und Android-Geräte

So ziemlich jeder Webseitenbetreiber hinterlegt im Wurzelverzeichnis seiner Webseite ein Favicon. Diese kleine Grafik wird dann im Browser-Tab oben neben dem Namen der Webseite angezeigt, genauso wie beim setzen eines Bookmarks bzw. Lesezeichens. Manch einer vergisst jedoch, dass inzwischen etliche Benutzer vorwiegend mit Mobilgeräten bzw. Smartphones wie iPhone, iPad oder Android-Smartphones und Tablets im Internet … „Hinzufügen von Website-Icons für iPhone, iPad und Android-Geräte“ weiterlesen