PHP: NHL-Spielerstatistiken in Excel ausgeben für Fantasy-Leagues

Das Problem

HockeyIch habe mir sagen lassen, dass es auch hier bei uns Leute gibt die in einer Fantasy League mitspielen. Irgendwie werden da virtuelle aber real existierende Sportler gezogen und verkauft und nach einem speziellen Algorithmus werden Punkte verteilt, die abhängig von den tatsächlichen Resultaten sind, die dieser Spieler erzielt. Nun, auf jeden Fall wird da viel mit Statistiken gewurstelt. Ein Fantasy League-Mitglied ist an mich herangetreten, weil er die online publizierten Resultate der Skaters und Goalies der NHL in einem Excel haben wollte.

Zuerst dachte ich, das sei ein Fall für Dapper. War es aber nicht, da auf der Webpage reine Text-Exporte waren.

Die Erfahrung hat gezeigt, dass die Form der Ausgabe auf diesen Seiten oftmals ändert und man sich auf so wenig wie möglich verlassen sollte.

Die Felder dieser Tabelle haben keine Trennzeichen, sondern eine feste Breite und sind links- oder rechtsbündig formatiert.

Einige Punkte der Lösung

Download der Webseite in eine Variable

Zuerst muss man mal an die Seite rankommen. In PHP den Inhalt einer URL in einen String laden ist in der Theorie relativ einfach. In der Praxis jedoch ist aus Sicherheitsgründen der Zugriff auf URLs für die file* Funktionen oftmals eingeschränkt. Nunja, dann wird halt ein cURL-Fallback mit eingebaut, so dass wir auf jeden Fall in $page ein Array aus den Zeilen der HTML-Datei haben:

  if(!($page = @file($url))) {
    $ch = curl_init();
    curl_setopt ($ch, CURLOPT_URL, $url);
    curl_setopt ($ch, CURLOPT_USERAGENT, 'User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070723 Iceweasel/2.0.0.6 (Debian-2.0.0.6-0etch1+lenny1)');
    curl_setopt ($ch, CURLOPT_HEADER, 0);
    curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec ($ch);
    curl_close ($ch);
    $page=explode("\n",$result);
  }

Überschriften auslesen

Ich lese die Tabellenübberschriften mit einem regulären Ausdruck aus (man beachte dass die Sternchen sich der Formatierung (links/rechts) anpassen), aus dem danach die Spaltenbreiten gelesen werden können. Aus den Spaltenbreiten wiederum kann ein regulärer Ausdruck gebaut werden über den auf die Werte zugegriffen werden kann.

Beispielsweise steht auf der Webpage:

NAME                                   POS GP   G    A PTS SOG +/- PIM PPG SHG

daraus lese ich mit folgendem Regexp die Feldbreiten:

'@^(NAME *)(POS *)(GP)( *G)( *A)( *PTS)( *SOG)( *\\+/\\-)( *PIM)( *PPG)( *SHG)$@'

Damit baue ich dynamisch einen neuen regulären Ausdruck, der die Tabellendaten auslesen kann:

/^(.{39})(.{4})(.{2})(.{4})(.{5})(.{4})(.{4})(.{4})(.{4})(.{4})(.{4})$/

Codiert sieht es so aus. $tline im Codebeispiel beinhaltet eine getrimmte Zeile aus dem HTML.

// Überschriften und Feldgrössen
if(preg_match('@^(NAME *)(POS *)(GP)( *G)( *A)( *PTS)( *SOG)( *\\+/\\-)( *PIM)( *PPG)( *SHG)$@',$tline,$matches)) {
  if(!$inresults) {
    // Nach den Überschriften kommen die Tabelleneinträge
    $inresults=true;
    // Überschriften auslesen
    $headercaptions=preg_split('/ +/', $tline, -1, PREG_SPLIT_NO_EMPTY);
    // Überschriften ausgeben
    echo "\r\n".join("\t",$headercaptions)."\r\n";
  }
  // Wie breit ist ein Feld (in Zeichen)
  $sizes=array();
  for($i=1;$i<count($matches);$i++) {
    $sizes[$i-1]=strlen($matches[$i]);
  }

  // Suchmuster für Einträge aufbauen
  $searchpattern="";
  foreach($sizes as $size) {
    $searchpattern.='(.{'.$size.'})';
  }
  //print $searchpattern."<br />";
  $searchpattern='/^'.$searchpattern.'$/';
  continue;
}
// Tabellenzeilen auslesen
if($inresults && preg_match($searchpattern, $tline, $matches)) {
   $row=array();
   for($i=1; $i<count($matches); $i++) {
     $row[]=trim($matches[$i]);
   }
   // Und ausgeben
   echo join("\t",$row)."\r\n";
   continue;
}

Ausgabe als Excel

Hier bescheisse ich… Ich gebe nur eine tabulatorseparierte Liste aus. Excel schnallts, OpenOffice leider nicht (hat da wer eine gescheitere Lösung? HTML-Tabellen?)…

Zuerst die Headers:

header("Content-type: application/vnd.ms-excel");
header('Content-Disposition: inline; filename="'.$dateiname.'.xls"');

Einzelne Zellen werden mittels Tabulator (\t) voneinander getrennt, Zeilenenden werden durch "\r\n" markiert:

echo join("\t",$row)."\r\n";

Die Lösung

Die komplette Lösung kann hier fantasy-league heruntergeladen werden und eine Demo gibts auf apps.oncode.info