Blog-Archive
Tutorial: Website parsen mit dem ESP8266 – Teil 1
Einleitung
In den letzten Tagen habe ich mich verstärkt mit der Abfrage verschiedener Websites beschäftigt und der Nutzung der Informationen im eigenen Programm. Das Finden und Extrahieren von Informationen aus einem Text, einem String oder einer Seite nennt man parsen. Da ich dabei viel gelernt habe, möchte ich dieses Wissen gerne weitergeben. Wichtig ist mir dabei wie immer, dass ihr das versteht und selbstständig nachvollziehen könnt. Deshalb werden meine Erklärungen vielleicht etwas ausführlicher und richten sich an die Programmieranfänger unter euch. Da ich das Tutorial für den ESP schreibe, werde ich auch auch auf die grundsätzliche Abfrage von Websites mit dem ESP eingehen.
Der ESP als Client
In den Libraries des ESP ist ein Beispiel enthalten, wie mit den ESP als Client Informationen von einem Webserver abgerufen werden können. In meinem Tutorial über die Speicherung von MySQL Daten habe ich ja bereits ein Beispiel zur Abfrage eines Webservers gezeigt. Das folgende Beispiel zeigt die Anwendung des ESP als Client mit Nutzung der neuesten Libraries. Das Beispiel ruft google.de auf und schreibt die Ausgabe auf die serielle Schnittstelle.
/* * Basic ESP Client example, based on the ESP libraries examples */ #include <ESP8266WiFi.h> const char* ssid = "yourSSID"; const char* password = "yourPASSPHRASE"; const char* host = "google.de"; void setup() { Serial.begin(115200); delay(10); // We start by connecting to a WiFi network Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); Serial.println(); } void loop() { Serial.print("connecting to "); Serial.println(host); // Use WiFiClient class to create TCP connections WiFiClient client; const int httpPort = 80; if (!client.connect(host, httpPort)) { Serial.println("connection failed"); return; } // We now create a URI for the request String url = "/"; Serial.print("Requesting URL: "); Serial.println(url); // This will send the request to the server client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); // start waiting for the response unsigned long lasttime = millis(); while (!client.available() && millis() - lasttime < 1000) {delay(1);} // wait max 1s for data // Read all the lines of the reply from server and print them to Serial while(client.available()){ char readchar = client.read(); Serial.print(readchar); } Serial.println(); Serial.println("closing connection"); delay(5000); }
Auf einige Teile dieses Codes möchte ich nochmal tiefer eingehen.
// We start by connecting to a WiFi network Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Im setup wird die Verbindung zum lokalen WLAN aufgebaut, dazu müssen im Parameterblock oben im Programm SSID und Passwort eingetragen werden.
Das Programm versucht nun (hier endlos) eine Verbindung zum WLAN aufzubauen. In meinem Fall wird die Verbindung nach ca. 5-8 Sekunden aufgebaut, wenn es bis dahin keine Verbindung gibt, muss man ein Reset machen und manchmal auch den Code neu flashen.
WiFiClient client; const int httpPort = 80; if (!client.connect(host, httpPort)) { Serial.println("connection failed"); return; }
Im loop wird dann die Verbindung zum Host, hier google.de aufgebaut
// We now create a URI for the request String url = "/"; Serial.print("Requesting URL: "); Serial.println(url); // This will send the request to the server client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
Zunächst wird die URL für die Abfrage zusammengesetzt. Als erstes wird der Teil der nach dem Host in der URL kommt in einen String geschrieben. In diesem Fall kommt nach google.de nichts mehr, deshalb steht hier nur ein „/“. Im nchsten Schritt werden die für die Abfrage notwendigen Parameter zum String hinzugefügt. ( die \r\n stehen für CR und LF und müssen an diesen Stellen stehen, am Ende der Abfrage sogar zweimal). Die client.print() Anweisung schickt die Abfrage zum Server.
// start waiting for the response unsigned long lasttime = millis(); while (!client.available() && millis() - lasttime < 1000) {delay(1);} // wait max 1s for data
Jetzt wartet der Client auf eine Antwort des Servers. Manche Server lassen sich damit Zeit, deshalb wartet der Client entweder bis er Daten bekommt (client-available()=true) oder die Zeit abgelaufen ist (hier 1000ms).
while(client.available()){ char readchar = client.read(); Serial.print(readchar); }
Solange Daten empfangen werden, gibt der ESP diese Buchstabe für Buchstabe auf der seriellen Schnittstelle aus. Im Client der Library ist hier alternativ eine Methode vorgeschlagen, die Daten zeilenweise (bis zum CR) zu lesen und auszugeben. Diese Methode werden wir für die nächsten Beispiele verwenden, also jetzt schon mal in das Beispiel einbauen. Manchmal ist es sinnvoller, statt nach einem CR ein LF zu suchen, dann sind die Strings möglicherweise kürzer, doch dazu später mehr.
// Read all the lines of the reply from server and print them to Serial while(client.available()){ String line = client.readStringUntil('\r'); Serial.print(line); }
Die Aussgabe auf der seriellen Schnittstelle sieht dann so aus
Connecting to DD-WRT ............. WiFi connected IP address: 192.168.2.109 connecting to google.de Requesting URL: / HTTP/1.1 301 Moved Permanently Location: http://www.google.de/ Content-Type: text/html; charset=UTF-8 Date: Sat, 30 Jan 2016 22:25:54 GMT Expires: Mon, 29 Feb 2016 22:25:54 GMT Cache-Control: public, max-age=2592000 Server: gws Content-Length: 218 X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Connection: close <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="http://www.google.de/">here</A>. </BODY></HTML> closing connection
Am Anfang der Ausgabe kommen Informationen über den Server, die eigentliche Ausgabe der Webseite beginnt ab <HTML>. Die Webseite bietet nicht viel an nutzbarer Information, allerdings könnte man aus den Header Daten die Uhrzeit parsen, anstatt den NTP Client einzubinden. Das möchte ich als abschließendes erstes Beispiel für das parsen zeigen. Wichtig beim parsen ist, dass man ein sich eindeutige Suchbegriffe sucht, über die man die Zeile in der die Information steht und als zweites der Platz in der Zeile eindeutig bestimmen kann. Für die Zeitabfrage ist das die Zeile
Date: Sat, 30 Jan 2016 22:25:54 GMT
Als erstes suchen wir also im eingelesenen String nach dem Suchwort „Date“ und wissen dann, dass wir in der richtigen Zeile sind. Das Schöne an diesem Beispiel ist, dass die Zeit immer genau an der selben Stelle im String steht.
Die beiden wichtigsten Befehle beim parsen sind:
String.indexOf(„Suchstring“) -> gibt als int die Position im String aus, wo der Suchstring gefunden wird
String.substring(von, bis) -> liefert String, der bei „von“ beginnt und vor „bis“ endet.
Da wir wissen, dass das Suchwort „Date“ immer an Position 1 im String zu finden ist (auf Position 0 steht ein LF), können wir einfach die Position im String abzählen, wo der gesuchte Teil steht. Doch zuvor testen wir, ob der Suchstring im gelesenen String vorkommt.
_________1_________2_________3_________4
1234567890123456789012345678901234567890
Date: Sat, 30 Jan 2016 22:25:54 GMT
Der entsprechende Code-Block sieht dann so aus:
while (client.available()) { String line = client.readStringUntil('\r'); if (line.indexOf("Date:")>=0) { // Searchstring exists? Serial.println(); Serial.print("Heute ist der "); Serial.print(line.substring(12, 23)); Serial.print(", es ist "); Serial.println(line.substring(24, 32)); } Serial.print(line); }
Die Uhrzeit wird in GMT ausgegeben, zur korrekten Darstellung muss (in der Winterzeit) 1 Stunde addiert werden. Dafür müssen Teile des Strings zunächst mit dem Befehl atoi() in einen Integer umgewandelt werden. Dieser Befehl kann nicht direkt auf den String ausgeführt werden. Dafür wird zunächste der Teilstring extrahiert und an die Funktion atoi() der Pointer auf den neuen String übergeben. Dafür setzen wir die Funktion String.c_str() ein.
while (client.available()) { String line = client.readStringUntil('\r'); if (line.indexOf("Date:")>=1) { // Searchstring exists? Serial.println(); Serial.print("Heute ist der "); Serial.print(line.substring(12, 23)); String stundeSubString = line.substring(24,26); int stunde = atoi(stundeSubString.c_str()); if (stunde==23) stunde=0; else stunde++; Serial.print(", es ist "); if (stunde<10) Serial.print("0"); Serial.print(stunde); Serial.print(line.substring(26, 32)); if (stunde==0) Serial.print(" + 1 Tag"); else Serial.println(); // Tagsprung ?? }
Natürlich müsste bei einem Tagsprung, also bei 0 Uhr bis 0:59 auch der Tag geändert werden. Das ist eine nette Übung für euch. Man sieht, mit der Umwandlung der Ausgabe in rechenbares Format, kann der Code schnell umfangreicher werden.
Die Ausgabe sieht jetzt so aus:
Connecting to DD-WRT ............. WiFi connected IP address: 192.168.2.109 connecting to google.de Requesting URL: / HTTP/1.1 301 Moved Permanently Location: http://www.google.de/ Content-Type: text/html; charset=UTF-8 Heute ist der 31 Jan 2016, es ist 08:33:24 Date: Sun, 31 Jan 2016 07:33:24 GMT Expires: Tue, 01 Mar 2016 07:33:24 GMT Cache-Control: public, max-age=2592000 Server: gws Content-Length: 218 X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Connection: close <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="http://www.google.de/">here</A>. </BODY></HTML> closing connection
Die war der erste Teil meines Tutorials zum parsen von Webseiten. Ich hoffe, ihr konntet meinen Erklärungen folgen und die Beispiele funktionierten auch bei euch. Wie immer, freue ich mich auf eure Rückmeldungen. Im zweiten Teil werde ich euch weitere Tips geben, wie eine Webseite „auseinander genommen“ werden kann.
Anmerkung: google.de gibt dem ESP nur eine sehr reduzierte Information zurück. Offensichtlich erkennt google, dass hier kein Browser anfragt.
Tutorial: Speicherung von Arduino Messdaten auf Webserver und deren Darstellung – Teil 2
Tutorial Teil 2 – Anlegen eines kostenlosen Webspace
Für die Speicherung der Logging Daten benötigen wir einen Server mit Webserver inklusive PHP und MySQL Datenbank. Natürlich kann man sich einen Webserver sehr einfach z.B. auf einem Linux Rechner oder einem RaspberryPi installieren, aber diese Server müssen immer eingeschaltet sein. Als eine reizvolle Alternative gibt es verschiedene Anbieter im Markt, die Webserver mit MySQL kostenlos anbieten. Unter chip.de findet man eine Liste der Anbieter.
Ich habe mir hier den Anbieter kilu.de ausgesucht, weil angeblich werbefrei 10GB Speicherplatz und unlimitierter Traffic versprochen wird. Die Anmeldung verläuft problemlos.
Aktualisierung 17.07.2015 Leider ist kilu.de ab dem 01.08.2015 kostenpflichtig. Die Anleitung gilt sinngemäß natürlich auch für andere Webspaces. Die angegebenen Links auf meinen Webspace bei kilu.de werden deshalb nicht mehr funktionieren.
Die Anmeldung erfordert die Angabe der persönlichen Daten, die sicherlich zu Werbezwecken weiterverwendet werden. Nach Angabe der Daten erhgält man eine Bestätigungmail mit den Zugangsdaten. Bein ersten Login sollte man das Passwort ändern. Man wir zur Eingabe einer Mobilnummer aufgefordert, über die dann ein Bestätigungcode gesendet wird, der eingegeben werden muss.
Als erstes richten wir den Webspace ein, das geht über „neues Projekt“ und „Webspace erstellen“. Nach Angabe von Titel und Beschreibung kann man den Webspace einer Kategorie zuordnen.
Die zuvor gewählte Subdomain wird übernommen. Die Einrichtung des Webspaces dauert bis zu 15 min. Nach kurzer Zeit ist der neue Webspace unter „meine Projekte“ sichtbar. Zur Konfiguration muss man dann ganz nach unten auf die Seite scrollen. Ganz unten findet man dann die Zugangsdaten für FTP und MySQL. Leider ist die Seite doch nicht werbefrei, wie sich jetzt zeigt. Nachdem auch MySQL aktiviert ist, können die Zugangsdaten der Dienste notiert werden. Wir benötigen für FTP: Server, Benutzer, Passwort und für MySQL: Server/Host, Benutzer, Passwort. Insgesamt sind 3 Datenbanken möglich.
Über phpMyAdmin können die Datenbanken und Tabellen sehr einfach gepflegt werden. In einem späteren Tutorial werde ic die Anlage von Tabellen zeigen. Mit einem FTP-Client seiner Wahl kann man sich dann mal ansehen, was schon auf dem Webspace liegt. Man kann natürlich auch seinen Webspace schon mal aufrufen.
Als erstes sollte man sich mal per FTP in das Verzeichnis www eine Datei mit dem Namen phpinfo.php und folgendem Inhalt kopieren
<?php phpinfo(); ?>
Über den Aufruf <webspacename>/phpinfo.php kann man sich dann ansehen, was der Webserver so leistet.
Wer das Beispiel aus Teil 1 des Tutorials zur Ausgabe der Uhrzeit jetzt auf seinem eigenen Webspace anlegen möchte, muss nur eine Datei unter dem Namen clock.php mit folgendem Inhalt in das www Verzeichnis seines Webspaces legen und im Arduino Programm bei char server[]= den Servernamen eintragen.
<?php echo "Es ist ".date('H:i:s'); ?>
Im nächsten Teil dieser Tutorial Reihe (coming soon) geht’s dann weiter mit der Anlage einer Webseite über die ich Daten per Arduino als WebClient ablegen kann.