Bevor die Messwerte auf der gewünschten Weise angezeigt und ausgewertet werden können, müssen sie aus der Webseite der MA22 extrahiert und strukturiert gespeichert werden. Dazu benutze ich eine Kombination aus Unix-Funktion, Perl-Skript, PHP-Webservice und MySQL-Datenbank.
Die Datenquelle
Die Daten der MA22 werden in einer - archaisch anmutenden - HTML-Tabelle veröffentlicht:
Das Shell-Skript
Das verwendete Shell-Skript holt die HTML-Datei mit einem curl-Kommando, ersetzt mittels sed diverse HTML-Sonderzeichen und startet ein Perl-Skript zum Parsen der Daten. Dieses Perl-Skript übergibt die Daten an einen Webservice auf dem NAS der die Daten in eine Datenbank schreibt.
#!/bin/bash curl -L -o /home/worker/lqVienna/tb-aktuell.htm https://www.wien.gv.at/ma22-lgb/tb/tb-aktuell.htm sed -i 's/\ä/\ä/g;s/\ö/ö/g;s/\ü/ü/g;s/\ß/ß/g' /home/worker/lqVienna/tb-aktuell.htm perl /home/worker/lqVienna/lqVienna.pl rm /home/worker/lqVienna/tb-aktuell.htm
Daten extrahieren mit Perl
Perl habe ich zuletzt vor einem dutzend Jahren benutzt. Die Sprache ist beliebt und mächtig genug um eine eigene "Perl-Kultur" zu entwickeln. So widmen sich Entwickler z.B. unter anderem dem Thema "Perl-Haiku", also dem Verfassen "poetischer" Dreizeiler in Perl.
Mein Perl-Skript ist dagegen so "straight", so unpoetisch, so plump wie möglich. Es lädt die komplette HTML-Seite und arbeitet sie Zeile für Zeile ab. Werden Keywords (Straßen- oder Ortsnamen) gefunden, werden die zugehörigen Messdaten aus der Zeile extrahiert. So wird für jede Messstelle und jeden Messwert ein HTML-Aufruf für den Webservice erzeugt und abgesetz. Der Aufruf ist im Prinzip wie folgt aufgebaut:
http://lqvienna.bagend.local/admin/add.php?key="SECURITY_KEY"&cs=NO2&ts=1492373656&ix=1&v1=14&v2=15&zt=8:00&v3=19
Dabei ist
- http://lqvienna.bagend.local/admin/add.php: Der aufgerufene Webservice
- key="SECURITY_KEY": Ein Schlüssel der vom Webservice verifiziert werden muss
- cs=NO2: Die Kennung für die Datenart (hier Stickstoffdioxid)
- ts=1492373656: Der Unix-Timestamp der Verarbeitung (auf dem Raspberry)
- ix=1: Der Index der Messstelle
- v1=14: Ein Messwert (hier der akt. Messwert)
- v2=15: Ein Messwert (hier der max. Messwert der letzten 24h)
- zt=8:00: Die Uhrzeit, die die MA22 auf der Webseite vermerkt hat
- v3=19: Ein Messwert (hier der Mittelwert der letzten 24h)
#!/usr/bin/perl use utf8; utf8::encode( $_ ); #use strict; use warnings; use DateTime; use WWW::Mechanize; # Decklaration der Subroutinen sub ind; sub NO2; sub O3; sub PM10; sub PM25; sub SO2; sub CO; sub trim($); # UX-Zeit ermitteln $timestamp = time(); # Schlüssel $KEY = 'SEKURITYKEY'; # URL von add.php $url = "http://lqvienna.bagend.local/admin/add.php?key=".$KEY."&cs="; @Zeilen = ( "" ); open( MESSDAT, "/home/worker/lqVienna/data/tb-aktuell.htm" ) || die "lqVienna: Datei mit Messdaten nicht gefunden\n"; while() { push( @Zeilen,$_ ); } close( MESSDAT ); for( @Zeilen ) { if( /aktuelle / ) { $case = "LQ"; $mzeit = substr( $_, 35, 2 ); $mzeit = $mzeit.":00"; $awert = substr( $_, 50, 1 ); } if( /chste / ) { $case = "LQ"; $hwert = substr( $_, 51, 1 ); $values = $case."&ts=".$timestamp."&zt=". $mzeit."&v1=".$awert."&v2=".$hwert; post2php( $values ); } if( /Stephan/ || /Tabor/ || /AKH/ || /Belgrad/ || /Laaer/ || /Kaiser/ || /A23/ || /Gaudenz/ || /Hietzing/ || /Kendler/ || /Schaf/ || /Hermann/ || /Hohe/ || /Gericht/ || /Lobau/ || /Stadlau/ || /Liesing/ ) { # Index Messstelle zuweisen &ind(); # NO2-Tabelle befüllen &NO2(); } if( /Stephan/ || /Laaer/ || /Hermann/ || /Hohe/ || /Lobau/ ) { # O3-Tabelle befüllen &O3(); } if( /Tabor/ || /AKH/ || /Belgrad/ || /Laaer/ || /Kaiser/ || /A23/ || /Gaudenz/ || /Kendler/ || /Schaf/ || /Gericht/ || /Lobau/ || /Stadlau/ || /Liesing/ ) { # PM10-Tabelle befüllen &PM10(); } if( /Tabor/ || /AKH/ || /A23/ || /Kendler/ || /Lobau/ || /Stadlau/ ) { # PM25-Tabelle befüllen &PM25(); } if( /Stephan/ || /Kaiser/ || /A23/ || /Schaf/ || /Hermann/ || /Hohe/ || /Stadlau/ ) { # SO2-Tabelle befüllen &SO2(); } if( /Tabor/ || /A23/ || /Gaudenz/ || /Hietzing/ ) { # CO-Tabelle befüllen &CO(); } } # Funktion Messstelle Index zuweisen sub ind { if( /Stephan/ ) { $MSindex = "0"; } if( /Tabor/ ) { $MSindex = "1"; } if( /AKH/ ) { $MSindex = "2"; } if( /Belgrad/ ) { $MSindex = "3"; } if( /Laaer/ ) { $MSindex = "4"; } if( /Kaiser/ ) { $MSindex = "5"; } if( /A23/ ) { $MSindex = "6"; } if( /Gaudenz/ ) { $MSindex = "7"; } if( /Hietzing/ ) { $MSindex = "8"; } if( /Kendler/ ) { $MSindex = "9"; } if( /Schaf/ ) { $MSindex = "10"; } if( /Hermann/ ) { $MSindex = "11"; } if( /Hohe/ ) { $MSindex = "12"; } if( /Gericht/ ) { $MSindex = "13"; } if( /Lobau/ ) { $MSindex = "14"; } if( /Stadlau/ ) { $MSindex = "15"; } if( /Liesing/ ) { $MSindex = "16"; } } # Funktion NO2 sub NO2 { $case = "NO2"; $HMWakt = trim( substr( $_, 18, 4 ) ); $HMWmax = trim( substr( $_, 22, 4 ) ); $HMWzeit = trim( substr( $_, 26, 7 ) ); $MW24 = trim( substr( $_, 33, 5 ) ); if ( $HMWakt ne "---" ) { $values = $case."&ts=".$timestamp."&ix=".$MSindex. "&v1=".$HMWakt."&v2=".$HMWmax."&zt=".$HMWzeit. "&v3=".$MW24; post2php( $values ); } } # Funktion O3 sub O3 { $case = "O3"; $MWakt = trim( substr( $_, 40, 4 ) ); $MWmax = trim( substr( $_, 44, 4 ) ); $MWzeit = trim( substr( $_, 48, 7 ) ); $MW8akt = trim( substr( $_, 55, 5 ) ); $MW8max = trim( substr( $_, 60, 4 ) ); $Bewert = trim( substr( $_, 65, 1 ) ); if ( $MWakt ne "---" ) { $values = $case."&ts=".$timestamp."&ix=".$MSindex. "&v1=".$MWakt."&v2=".$MWmax."&zt=".$MWzeit. "&v3=".$MW8akt."&v4=".$MW8max."&v5=".$Bewert; post2php( $values ); } } # Funktion PM10 sub PM10 { $case = "PM10"; $MW24 = trim(substr( $_, 68, 5 ) ); $values = $case."&ts=".$timestamp. "&ix=".$MSindex."&v1=".$MW24; post2php( $values ); } # Funktion PM25 sub PM25 { $case = "PM25"; $MW24 = trim( substr( $_, 75, 5 ) ); $values = $case."&ts=".$timestamp. "&ix=".$MSindex."&v1=".$MW24; post2php( $values ); } # Funktion SO2 sub SO2 { $case = "SO2"; $HMWakt = trim( substr( $_, 82, 3 ) ); $HMWmax = trim( substr( $_, 85, 4 ) ); $MW24 = trim( substr( $_, 89, 5 ) ); $values = $case."&ts=".$timestamp."&ix=".$MSindex. "&v1=".$HMWakt."&v2=".$HMWmax."&v3=".$MW24; post2php( $values ); } # Funktion CO sub CO { $case = "CO"; $MW8akt = trim( substr( $_, 96, 4 ) ); $MW8max = trim( substr( $_, 101, 4 ) ); $values = $case."&ts=".$timestamp."&ix=".$MSindex. "&v1=".$MW8akt."&v2=".$MW8max; post2php( $values ); } sub post2php { $ua = WWW::Mechanize->new(); $URL = $url.$values; # print "URL: $URL\n"; $response = $ua->get( $URL ); if ( $response->is_success ) { # $c = $ua->content; print "Daten erfolgreich importiert." # print ( "$c" ); } else { print "Fehler beim öffnen der URL!"; # die $response->status_line; } } # Funktion trim sub trim($) { $string = shift; $string =~ s/^\s+//; $string =~ s/\s+$//; return $string; }
Der Webservice
Das PHP-Skript add.php übernimmt die Daten per GET. Wurde der übergebene Securitykey erfolgreich verifiziert (ansonsten endet es kommentarlos) , wird aus dem Unix-Timestamp ein Datum im Format YYYY-MM-DD hh:mm:ss (MySQL Datetime) erzeugt und die Nutzdaten mittels Case-Entscheidung - basierend auf der übergebenen Datenart - in die passende Tabelle mit einem Prepared Statement eingefügt.
$var) { if ($arg == 'key' AND $var == $SECURITYKEY) { $ValidKey=true; } if ($arg == 'cs') { $cs = $var; } if ($arg == 'ts') { $ts = $var; } if ($arg == 'ix') { $ix = $var; } if ($arg == 'zt') { $zt = $var; } if ($arg == 'v1') { $v1 = $var; } if ($arg == 'v2') { $v2 = $var; } if ($arg == 'v3') { $v3 = $var; } if ($arg == 'v4') { $v4 = $var; } if ($arg == 'v5') { $v5 = $var; } } if (!$ValidKey) { echo 'Ungültiger Schlüssel!'; exit(); } if ( isset($cs) AND isset($ts) ) { // UX-Zeit in Datetime $dt = gmdate("Y-m-d H:i:s", $ts); // Eingehende Daten verarbeiten switch ( $cs ) { case 'LQ': $sql = 'INSERT INTO tbl_LQ ( LQ_datetime, LQ_zeit, LQ_aktwert, LQ_maxwert ) VALUES ( ?, ?, ?, ? )'; $stmt = $mysqli->prepare( $sql ); $stmt->bind_param( 'ssii', $dt, $zt, $v1, $v2 ); break; case 'NO2': $sql = 'INSERT INTO tbl_NO2 ( NO2_datetime, NO2_index, NO2_zeit, NO2_HMWakt, NO2_HMWmax, NO2_MW24 ) VALUES ( ?, ?, ?, ?, ?, ? )'; $stmt = $mysqli->prepare( $sql ); $stmt->bind_param( 'sisiii', $dt, $ix, $zt, $v1, $v2, $v3 ); break; case 'O3': $sql = 'INSERT INTO tbl_O3 ( O3_datetime, O3_index, O3_1MWakt, O3_1MWmax, O3_MWzeit, O3_MW8akt, O3_MW8max, O3_bewertung) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )'; $stmt = $mysqli->prepare( $sql ); $stmt->bind_param( 'siiisiii', $dt, $ix, $v1, $v2, $zt, $v3, $v4, $v5 ); break; case 'PM10': $sql = 'INSERT INTO tbl_PM10 ( PM10_datetime, PM10_index, PM10_wert ) VALUES ( ?, ?, ? )'; $stmt = $mysqli->prepare( $sql ); $stmt->bind_param( 'sii', $dt, $ix, $v1 ); break; case 'PM25': $sql = 'INSERT INTO tbl_PM25 ( PM25_datetime, PM25_index, PM25_wert ) VALUES ( ?, ?, ? )'; $stmt = $mysqli->prepare( $sql ); $stmt->bind_param( 'sii', $dt, $ix, $v1 ); break; case 'SO2': $sql = 'INSERT INTO tbl_SO2 ( SO2_datetime, SO2_index, SO2_HMWakt, SO2_HMWmax, SO2_MW24 ) VALUES ( ?, ?, ?, ?, ? )'; $stmt = $mysqli->prepare( $sql ); $stmt->bind_param( 'siiii', $dt, $ix, $v1, $v2, $v3 ); break; case 'CO': $sql = 'INSERT INTO tbl_CO ( CO_datetime, CO_index, CO_MW8akt, CO_MW8max ) VALUES ( ?, ?, ?, ? )'; $stmt = $mysqli->prepare( $sql ); $stmt->bind_param( 'sidd', $dt, $ix, $v1, $v2 ); break; } $stmt->execute(); $mysqli->close(); } } ?>
Die Parameter für Auswahl und Anmeldung an der Datenbank erfolgt über die Include-Dateien db_connect.inc und db_config.inc
db_connect.inc
set_charset("utf8"); // Monatsnamen in Deutsch ausgeben $mysqli -> query("SET lc_time_names = 'de_DE'"); ?>
db_config.inc