Mikroformate zeichnen herkömmliche HTML-Seiten mit allgemein anerkannten Tags wie Geo-Koordinaten oder sozialen Netzwerkverbindungen aus und erlauben so automatischen Auswertern, sie zu sammeln und grafisch ansprechend darzustellen.
Herbstzeit, Wanderzeit! Das warme Wetter Ende September bietet ideale Voraussetzungen, in die USA zu fliegen, einen der 58 Nationalparks aufzusuchen, zu bestaunen und dort Abenteuer zu erleben (Abbildung 1). Die englischsprachige Wikipediaseite "List of national parks of the United States" ([2]) listet die Parks in alphabetischer Reihenfolge auf, beschreibt die Hauptattraktionen und fügt außerdem im HTML-Text die geografische Breite und Länge als Geo-Koordinaten bei.
Abbildung 1: Autor mit Büffel auf freier Wildbahn im Yellowstone-Nationalpark. |
Damit nicht nur der geneigte Web-Surfer der Wikipedia-Seite in den Genuss dieser Daten kommt, sondern auch automatisch ablaufende Applikationen, haben sich sogenannte Mikroformate eingebürgert. HTML-Markup kodiert ja traditionell nicht direkt semantische Informationen, sondern konzentriert sich hauptsächlich auf die Darstellung. Steht so zum Beispiel auf einer Webseite die Adresse einer Firma, sind Name, Straße, Ort und Telefonnummer oft nur durch Zeilenumbrüche getrennt und Suchmaschinen müssen erraten, ob es sich überhaupt um eine Adresse handelt und anschließend die einzelnen Bestandteile herausfieseln.
Das in Abbildung 2 gezeigte HTML-Snippet des Tabelleneintrags für
den Nationalpark "Bryce Canyon" auf der Wikipedia-Seite
definiert ein span
-Tag der Klasse vcard
, einer sogenannten
Root-Klasse, die eine Instanz des Mikroformats hCard
ausweist ([3]).
Abbildung 2: Der HTML-Code für den Bryce-Canyon auf Wikipedia enthält auch die geografische Länge und Breite des Standorts. |
Weiter unten, tief verschachtelt innerhalb der vcard
-Struktur, findet
sich eine Klasse "fn org", die den Namen ("name, formatted/full") der
beschriebenen Firma oder Organisation angibt. Im Fall des Bryce Canyon
ist dies der Name des Nationalparks. Vier Zeilen weiter oben steht
die "geo"
-Klasse, die mit 37.57 und -112.18 die geografische
Breite und Länge eines zentralen Punkts im Park angibt.
Diese Angaben mit einem HTML-Parser zu extrahieren ist nicht schwer und
so nimmt es nicht Wunder, dass auf dem Web bereits Applikationen wie
microform.at
bereit stehen, die URLs engegennehmen, die entsprechenden
Seiten einholen, die semantischen Tags extrahieren und als XML
zurückliefen. Den angezeigten URL auf das XML-Ergebnis wiederum kann der
geneigte User wie in [4] beschrieben dann in das Suchfeld auf
Google Maps plumpsen lassen. Dort sieht er dann eine lange Textliste mit
den Namen der Nationalparks, und klickt er auf einen Eintrag, erscheint auf
der rechts daneben angezeigten Amerikakarte eine Sprechblase an den
Geo-Koordinaten des entsprechenden Parks. Allerdings versieht Google wegen
den von microform.at
gewählten Style-Definitionen die Landkarte
nicht mit digitalen Reißnägeln aller Parks. Möchte ein Tourist aber
zum Beispiel möglichst viele Parks in einem bestimmten Bundesstaat
abklappern, wäre diese Information hilfreich.
Abbildung 3: Die Applikation auf http://microform.at nimmt den URL der Wikipediaseite entgegen, extrahiert die Microformat-Informationen und formatiert sie als XML. |
Zum Glück ist das Auslesen der vcard-Informationen mit einem in Perl
geschriebenen Web-Scraper nicht weiter schwierig. Wie immer folgt
der Perl-Snapshot den neuesten Modetrends und wählt zur Ausführung
den modernen Scraper Web::Scraper vom CPAN. Der vom Tool Scrapi
aus der Ruby-Welt inspirierte Scraper nutzt eine Art Domain Specific Language
(DSL), um in die Tiefen der HTML-Tags vorzudringen und deren Inhalt
schön formatiert in Perl-Strukturen zurück zu liefern.
01 #!/usr/bin/perl -w 02 use strict; 03 use Web::Scraper; 04 use URI; 05 use Template; 06 use CGI qw( :all ); 07 08 print header( 09 -type => 10 'application/vnd.google-earth.kml+xml', 11 -content_location => 'microformat.kml', 12 -access_control_allow_origin => '*', 13 -expires => '-1d', 14 ); 15 16 my $url = "http://en.wikipedia.org/wiki/" . 17 "Us_national_parks"; 18 19 my $coords = scraper { 20 process 'span.vcard', 'vcards[]' => 21 scraper { 22 process '.geo', geo => 'TEXT'; 23 process '.org', name => 'TEXT'; 24 } 25 }; 26 27 my $res = $coords->scrape( 28 URI->new( $url ) ); 29 30 my @parks = (); 31 32 for my $vcard ( @{ $res->{ vcards } } ) { 33 next unless exists $vcard->{ geo }; 34 my( $lon, $lat ) = 35 split /\s*;\s*/, $vcard->{ geo }; 36 push @parks, { name => $vcard->{ name }, 37 lat => $lat, 38 lon => $lon }; 39 } 40 41 my $tmpl = Template->new(); 42 my $data = join( '', <DATA> ); 43 44 binmode STDOUT, ":utf8"; 45 46 $tmpl->process( \$data, { parks => 47 \@parks } ) || die $tmpl->error(); 48 49 __DATA__ 50 <?xml version="1.0" encoding="UTF-8"?> 51 <kml 52 xmlns="http://earth.google.com/kml/2.0"> 53 <Folder> 54 <name>US National Parks</name> 55 [% FOR park IN parks %] 56 <Placemark> 57 <name>[% park.name %]</name> 58 <Point> 59 <coordinates> 60 [% park.lat %], [% park.lon %], 0 61 </coordinates> 62 </Point> 63 </Placemark> 64 [% END %] 65 </Folder> 66 </kml>
Das CGI-Skript in Listing 1 setzt in Zeile 16 den URL der einzuholenden
Wikipedia-Webseite mit den Nationalparks. Der in Zeile 19 definierte
Scraper sucht mit dem Funktionsaufruf process
zunächst nach allen
<span>
-Tags mit einem Attribut class="vcard"
und legt
sie wegen des Parameters vcard[]
in einem Array innerhalb des
Ergebnis-Hashes ab.
Wird der Scraper fündig, definert der erneute,
verschachtelte Aufruf der scraper
-Funktion einen weiteren Scraper
in den Tiefen der vcard
-Struktur. Dieser sucht wegen des
Aufrufs process ".geo"
nach Tags mit dem Attribut class="geo"
,
extrahiert wegen des 'TEXT'-Parameters deren Textinhalt (also
zum Beispiel "37.57; -112.18") und legt ihn im Arrayeintrag des
Nationalparks unter dem Schlüssel geo
ab.
Ähnlich verfährt der Scraper mit den Microformat-Einträgen
im Format fn org
, die
den Namen des aktuell bearbeiteten Nationalparks enthalten. Mittels der
Anweisung ".org"
findet process
die Tags, und fieselt aufgrund des
TEXT
-Parameters den als Klartext im Tag stehenden Namen des Parks heraus
und legt ihn unter "name" in der Ergebnisstruktur ab.
Die for
-Schleife ab Zeile 32 braucht dann nur noch alle
abgelegten vcard
-Einträge durchzurattern, und zu prüfen, ob sich
dort eine geo
-Klasse findet. Falls ja, splittet Zeile 35 die
Geodaten-Information
am trennenden Semikolon in die geografische Länge und Breite und legt sie
in den Variablen $lon
und $lat
ab. Zusammen mit dem Namen
des Parks unter dem Schlüssel name
hängt Zeile 36 die
extrahierten Werte als weiteren Eintrag im Array @parks
an.
Zur Formatierung der Daten in XML nutzt Listing 1 den Template-Engine
Template
vom CPAN
und das ab der __DATA__
-Anweisung folgende XML-Rohgerüst
am Ende des Scripts. Dieses erhält in Zeile 46 mit dem Methodenaufruf
process()
den Array mit den Parkdaten unter dem Schlüssel
"parks" in einem Hash. In Zeile 55 definiert das Template mit
[% FOR ... %]
eine for
-Schleife bis zur Markierung
[% END %]
, iteriert über die Daten
aller Parks und gibt für jeden Name und Geo-Koordinaten in
einer XML-Struktur namens Placemark
aus. Während der Name eines
Parks in <name>
-Tags eingeschlossen ist, finden sich
die Koordinaten in einer <Point<gt
>-Struktur, die ihrerseits
wiederum Tags mit der Bezeichnung <coordinates<gt
> und den
Geodaten enthalten. Die Koordinaten liegen dort als vorzeichenbehaftete
geografische Breite, Länge und Höhe über dem Meeresspiegel vor. Letzerer
Wert steht nicht auf der Wikipedia-Seite, und deshalb setzt ihn das
Skript auf 0, was bei der später verwendeten zweidimensionalen
Google-Maps-Darstellung nicht weiter auffällt.
Alle Placemarks finden in
einem Container mit dem Namen Folder
Platz ab. Zeile 52 zeigt an,
dass es sich um XML-Daten im Google-Earth-Format handelt, die Google
Maps später klaglos akzeptieren und anzeigen wird. Wichtig ist noch
der binmode
-Aufruf in Zeile 44, der die Standardausgabe des Skripts
auf utf8
umstellt. Schließlich liegen die Werte der in das
Template einzufügenden Variablen utf8
-kodiert vor und das
ausgespuckte XML sollte ebenfalls als utf8
daher kommen, denn
die erste Zeile im XML-Template im __DATA__-Bereich legt als
encoding
"UTF-8" fest.
Da es sich bei Listing 1 um ein später auf einem Web-Server aufgerufenes
CGI-Skript handelt, gibt es vor dem Ergebnis-XML noch einige HTTP-Header
aus. Abbildung 4 zeigt die Ausgabe des Skripts,
wenn man parks-scraper
von der
Kommandozeile aus startet.
Als Typ legt Zeile 9 im Skript das Google-Earth-Format fest, gefolgt
vom Header Content-location
mit dem Namen einer Pseudo-Datei, den
Google Maps anscheinend benötigt. Zur späteren Nutzung auf Google Maps
ist es außerdem wichtig, Access-control-allow-origin
auf '*' zu setzen,
damit der Browser nicht die Kommunikation zwischen XML-Lieferant
und Google-Maps wegen der Same-Origin-Policy unterbindet. Der Expire-Header
steht mit -1d
auf "gestern", sorgt also dafür, dass Google Maps später
jedes Mal frische Ergebnisse vom Geodaten-Lieferant abholt, statt sie zu
cachen. Dies ist insbesondere in der Debugging-Phase des Skripts wichtig.
Abbildung 4: Das CGI-Skript extrahiert die Geodaten aus der Wikipedia-Seite und spuckt sie als XML aus. |
Läuft das Skript dann endlich auf einem öffentlich zugänglichen Webserver (oder gerne auch auf dem letztens vorgestellten Service auf Heroku.com), pflanzt der geneigte Wanderfreund den URL einfach ins Suchfeld von Google Maps ein (Abbidlung 5) und erhält auf der Landkarte für jeden Park einen digitalen Reißnagel.
Abbildung 5: Der Park-Scraper beliefert Google Maps mit Koordinaten für die Nationalparks. |
Hinter den Kulissen springt Google
Maps das auf perlmeister.com
hinterlegte CGI-Skript an, welches wiederum die
Wikipedia-Seite mit den Parks einholt, den Scraper anwirft, die
Geodaten extrahiert und als XML zurück gibt. Google Maps wiederum
analysiert das XML-Format, holt die Geodaten hervor, ermittelt die
digitalen Positionen der Reißnägel auf der Landkarte und stellt sie
dar.
Der Wanderfreund könnte den Daten nun noch mit künstlicher Intelligenz zu Leibe rücken ([5]) und zum Beispiel herausfinden, in welchen Gegenden der USA die meisten Nationalparks liegen. Oder vielleicht lassen sich ja mit fünf Flügen 70% aller Nationalparks abdecken, wenn man den Aktionsradius richtig wählt und die Parks intelligent in Cluster zusammenfasst? Aber das sparen wir uns für einen zukünftigen Snapshot auf.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2012/10/Perl
Wikipedia-Eintrag zu den amerikanischen Nationalparks, http://en.wikipedia.org/wiki/Us_national_parks
Das Mikroformat "hCard 1.0", http://microformats.org/wiki/hcard
"Mining the Social Web", Matthew A. Russell, 2011, O'Reilly
"Machine Learning for Hackers", Drew Conway and John Myles White, 2012, O'Reilly